@@ -49,7 +49,7 @@
< i class = "ri-search-line absolute left-4 top-1/2 -translate-y-1/2 text-gray-400" > < / i >
< input type = "text" id = "searchInput" placeholder = "搜索..."
class = "w-full pl-12 pr-4 py-2 border border-gray-200 rounded-lg focus:outline-none focus:border-indigo-400"
onkeyup = "filterItems()" >
oninput = "filterItems()" >
< / div >
< select id = "sortBy" onchange = "loadItems()" class = "px-4 py-2 border border-gray-200 rounded-lg focus:outline-none" >
< option value = "default" > 默认排序(置顶优先)< / option >
@@ -65,14 +65,32 @@
< / div >
< / div >
<!-- 数据列 表 -->
< div class = "bg-white rounded-xl shadow-sm p-6 " >
< div id = "itemsList" class = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 ">
< div class = "text-center text-gray-400 py-8" > 加载中...< / div >
< / div >
<!-- 数据表格 -->
< div class = "bg-white rounded-xl shadow-sm overflow-hidden " >
< table class = "w-full ">
< thead class = "bg-gray-50 border-b" id = "tableHead" >
<!-- 动态生成 -- >
< / thead >
< tbody id = "itemsTable" >
< tr > < td colspan = "10" class = "text-center text-gray-400 py-8" > 加载中...< / td > < / tr >
< / tbody >
< / table >
< / div >
< / main >
<!-- 详情弹窗 -->
< div id = "detailModal" class = "fixed inset-0 bg-black/50 z-50 hidden flex items-center justify-center" >
< div class = "bg-white rounded-xl max-w-2xl w-full mx-4 max-h-[80vh] overflow-auto" >
< div class = "p-6 border-b flex justify-between items-center sticky top-0 bg-white z-10" >
< h2 class = "text-xl font-bold text-gray-800" id = "modalTitle" > 详情< / h2 >
< button onclick = "closeModal()" class = "text-gray-400 hover:text-gray-600" >
< i class = "ri-close-line text-2xl" > < / i >
< / button >
< / div >
< div id = "modalContent" class = "p-6" > < / div >
< / div >
< / div >
<!-- 页脚 -->
< footer class = "bg-white border-t mt-8 py-6 text-center text-gray-500 text-sm" >
ParamHub - 参数百科
@@ -82,12 +100,23 @@
const categoryId = '{{ category.id }}' ;
let allItems = [ ] ;
let categories = [ ] ;
let currentCategory = null ;
let displayFields = [ ] ; // 显示的字段列表
// 加载导航
async function loadNav ( ) {
const res = await fetch ( '/api/categories' ) ;
categories = await res . json ( ) ;
// 获取当前类别
currentCategory = categories . find ( c => c . id === categoryId ) ;
if ( currentCategory && currentCategory . fields ) {
// 过滤要显示的字段( 排除id、created_at等内部字段)
displayFields = currentCategory . fields
. filter ( f => ! [ 'id' , 'category_id' , 'created_at' , 'updated_at' , 'visible' , 'is_pinned' , 'views' , 'publish_date' , 'subcategory_id' , 'parse_sources' , 'product_images' ] . includes ( f . key ) )
. slice ( 0 , 6 ) ; // 最多显示6个字段
}
// 内置页面映射
const builtinPages = [
{ id : 'home' , name : '首页' , href : '/' } ,
@@ -129,50 +158,78 @@
allItems = await res . json ( ) ;
document . getElementById ( 'itemCount' ) . textContent = allItems . length ;
renderTableHead ( ) ;
renderItems ( allItems ) ;
}
// 渲染数据
// 渲染表格头部
function renderTableHead ( ) {
let html = '<tr>' ;
// 名称列
html += '<th class="px-4 py-3 text-left text-sm font-medium text-gray-600">名称</th>' ;
// 动态字段列
displayFields . forEach ( f => {
html += ` <th class="px-4 py-3 text-left text-sm font-medium text-gray-600"> ${ f . label } </th> ` ;
} ) ;
// 发布日期列
html += '<th class="px-4 py-3 text-left text-sm font-medium text-gray-600">发布日期</th>' ;
// 操作列
html += '<th class="px-4 py-3 text-center text-sm font-medium text-gray-600">操作</th>' ;
html += '</tr>' ;
document . getElementById ( 'tableHead' ) . innerHTML = html ;
}
// 渲染数据表格
function renderItems ( items ) {
if ( items . length === 0 ) {
document . getElementById ( 'itemsList' ) . innerHTML = `
<div class="col-span-3 text-center py-12">
const colCount = displayFields . length + 3 ; // 名称 + 字段 + 日期 + 操作
document . getElementById ( 'itemsTable' ) . innerHTML = `
<tr><td colspan=" ${ colCount } " class="text-center py-12">
<i class="ri-inbox-line text-4xl text-gray-300 mb-4 block"></i>
<p class="text-gray-400">暂无数据</p>
</div >
</td></tr >
` ;
return ;
}
document . getElementById ( 'itemsList' ) . innerHTML = items . map ( item => {
const fields = Object . entries ( item )
. filter ( ( [ key , val ] ) => ! [ 'id' , 'category_id' , 'created_at' , 'updated_at' , 'visible' , 'is_pinned' , 'views' , 'publish_date' ] . includes ( key ) && val )
. slice ( 0 , 5 )
. map ( ( [ key , val ] ) => ` <span class="text-gray-500 text-sm"> ${ key } : ${ val } </span> ` )
. join ( '<br>' ) ;
return `
<div class="border border-gray-200 rounded-lg p-4 hover:shadow-md transition group ${ item . is _pinned ? 'bg-yellow-50 border-yellow-300' : '' } ">
<div class="flex items-start justify-between">
<div>
<h3 class="font-medium text-gray-800 group-hover:text-indigo-600 flex items-center gap-2">
${ item . is _pinned ? '<i class="ri-pushpin-fill text-yellow-500" title="置顶"></i>' : '' }
${ item . name || item . title || '未命名' }
</h3>
<div class="mt-2 space-y-1">
${ fields }
</div>
</div>
<div class="text-right">
<div class="text-xs text-gray-400">
${ item . publish _date || ( item . created _at ? item . created _at . split ( ' ' ) [ 0 ] : '' ) }
</div>
${ item . views ? ` <div class="text-xs text-gray-400 mt-1"><i class="ri-eye-line"></i> ${ item . views } </div> ` : '' }
</div>
</div>
const html = items . map ( item => {
let row = ` <tr class="border-b hover:bg-gray-50 transition ${ item . is _pinned ? 'bg-yellow-50' : '' } "> ` ;
// 名称列
row += ` <td class="px-4 py-3">
<div class="font-medium text-gray-800 flex items-center gap-2">
${ item . is _pinned ? '<i class="ri-pushpin-fill text-yellow-500" title="置顶"></i>' : '' }
${ item . name || item . title || '未命名' }
</div>
` ;
${ item . views ? ` <div class="text-xs text-gray-400 mt-1"><i class="ri-eye-line mr-1"></i> ${ item . views } </div> ` : '' }
</td> ` ;
// 动态字段列
displayFields . forEach ( f => {
const value = item [ f . key ] || '-' ;
row += ` <td class="px-4 py-3 text-gray-600 text-sm"> ${ value } </td> ` ;
} ) ;
// 发布日期列
row += ` <td class="px-4 py-3 text-gray-500 text-sm"> ${ item . publish _date || ( item . created _at ? item . created _at . split ( ' ' ) [ 0 ] : '-' ) } </td> ` ;
// 操作列
row += ` <td class="px-4 py-3 text-center">
<button onclick="showDetail(' ${ item . id } ')" class="text-indigo-600 hover:text-indigo-800 text-sm">
<i class="ri-eye-line mr-1"></i>详情
</button>
</td> ` ;
row += '</tr>' ;
return row ;
} ) . join ( '' ) ;
document . getElementById ( 'itemsTable' ) . innerHTML = html ;
}
// 搜索过滤
@@ -191,6 +248,56 @@
renderItems ( filtered ) ;
}
// 显示详情
function showDetail ( id ) {
const item = allItems . find ( i => i . id === id ) ;
if ( ! item ) return ;
document . getElementById ( 'modalTitle' ) . textContent = item . name || '详情' ;
let html = '<div class="space-y-3">' ;
// 按字段顺序显示
if ( currentCategory && currentCategory . fields ) {
currentCategory . fields . forEach ( f => {
if ( item [ f . key ] ) {
const value = item [ f . key ] ;
html += `
<div class="flex justify-between py-2 border-b">
<span class="text-gray-500"> ${ f . label } </span>
<span class="text-gray-800 ${ f . input _style === 'long' ? 'text-right max-w-xs' : '' } "> ${ value } </span>
</div>
` ;
}
} ) ;
}
// 添加统计信息
if ( item . views ) {
html += `
<div class="flex justify-between py-2 border-b">
<span class="text-gray-500">热度</span>
<span class="text-gray-800"><i class="ri-eye-line mr-1"></i> ${ item . views } </span>
</div>
` ;
}
html += '</div>' ;
document . getElementById ( 'modalContent' ) . innerHTML = html ;
document . getElementById ( 'detailModal' ) . classList . remove ( 'hidden' ) ;
}
// 关闭弹窗
function closeModal ( ) {
document . getElementById ( 'detailModal' ) . classList . add ( 'hidden' ) ;
}
// 点击弹窗外部关闭
document . getElementById ( 'detailModal' ) . addEventListener ( 'click' , function ( e ) {
if ( e . target === this ) closeModal ( ) ;
} ) ;
// 初始化
loadNav ( ) ;
loadItems ( ) ;