1 Commits

4 changed files with 192 additions and 30 deletions

View File

@@ -366,8 +366,15 @@
"price" "price"
], ],
"name": "单反相机" "name": "单反相机"
},
{
"id": "90ce312b560d",
"name": "口袋云台相机",
"icon": "ri-folder-line",
"key_features": [],
"feature_labels": {}
} }
], ],
"updated_at": "2026-04-28 09:44:52" "updated_at": "2026-04-28 10:55:02"
} }
] ]

View File

@@ -171,7 +171,10 @@
"raw_text": "据tweaktown报道NVIDIA为中国市场定制的全新工作站显卡RTX 6000D近日迎来首度拆解。该卡搭载84GB GDDR7显存、19968个CUDA核心采用被动散热设计专为服务器机箱风道优化。\n\n\n相较于满血RTX PRO 600096GB GDDR7/512-bit中国特供版RTX 6000D在规格上进行了多处调整。国内团队“技数犬”发布了拆解视频。\n\n据了解RTX 6000D为无风扇被动散热设计完全依靠机箱气流降温。\n\nRTX 6000D搭载28颗VRAM模块总计84GB GDDR7显存显存总线为448位相比RTX PRO 6000的96GB/512位有所减少。\n\nRTX 6000D GPU 核心为156 SM单元19,968个CUDA核心比RTX PRO 6000少约17%。\n\nRTX 6000D核心频率为2430MHzRTX PRO 6000为2600MHzTDP暂未公布。性能方面RTX 6000D在Geekbench 6 OpenCL测试中获得390,656分低于RTX PRO 6000的4550万分。", "raw_text": "据tweaktown报道NVIDIA为中国市场定制的全新工作站显卡RTX 6000D近日迎来首度拆解。该卡搭载84GB GDDR7显存、19968个CUDA核心采用被动散热设计专为服务器机箱风道优化。\n\n\n相较于满血RTX PRO 600096GB GDDR7/512-bit中国特供版RTX 6000D在规格上进行了多处调整。国内团队“技数犬”发布了拆解视频。\n\n据了解RTX 6000D为无风扇被动散热设计完全依靠机箱气流降温。\n\nRTX 6000D搭载28颗VRAM模块总计84GB GDDR7显存显存总线为448位相比RTX PRO 6000的96GB/512位有所减少。\n\nRTX 6000D GPU 核心为156 SM单元19,968个CUDA核心比RTX PRO 6000少约17%。\n\nRTX 6000D核心频率为2430MHzRTX PRO 6000为2600MHzTDP暂未公布。性能方面RTX 6000D在Geekbench 6 OpenCL测试中获得390,656分低于RTX PRO 6000的4550万分。",
"currency": "CNY", "currency": "CNY",
"price_usd": 45000, "price_usd": 45000,
"updated_at": "2026-04-20 18:28:10" "updated_at": "2026-04-28 11:56:48",
"subcategory_id": "professional",
"views": 0,
"images": []
}, },
{ {
"name": "RTX PRO 6000", "name": "RTX PRO 6000",
@@ -185,7 +188,10 @@
"cuda_cores": 24064, "cuda_cores": 24064,
"currency": "CNY", "currency": "CNY",
"price_usd": 65000, "price_usd": 65000,
"updated_at": "2026-04-20 18:28:23", "updated_at": "2026-04-28 11:56:38",
"manufacturer": "NVIDIA" "manufacturer": "NVIDIA",
"subcategory_id": "professional",
"views": 0,
"images": []
} }
] ]

View File

@@ -9,13 +9,16 @@
"input_price": 0.03, "input_price": 0.03,
"output_price": 0.06, "output_price": 0.06,
"mmlu": 86.4, "mmlu": 86.4,
"humaneval": 67.0, "humaneval": 67,
"is_open_source": false, "is_open_source": false,
"license": "Proprietary", "license": "Proprietary",
"description": "OpenAI最强大的多模态大模型", "description": "OpenAI最强大的多模态大模型",
"created_at": "2024-01-01", "created_at": "2024-01-01",
"updated_at": "2026-04-28 10:09:47", "updated_at": "2026-04-28 11:57:02",
"raw_text": "\nGPT-4 Turbo version with 128K context length, price is $10 per 1M input tokens" "raw_text": "\nGPT-4 Turbo version with 128K context length, price is $10 per 1M input tokens",
"subcategory_id": "chat",
"views": 0,
"images": []
}, },
{ {
"id": "gpt4turbo", "id": "gpt4turbo",

View File

@@ -159,6 +159,11 @@
<button onclick="openAddModal('dynamic')" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700"><i class="ri-add-line mr-2"></i>手动添加</button> <button onclick="openAddModal('dynamic')" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700"><i class="ri-add-line mr-2"></i>手动添加</button>
</div> </div>
</div> </div>
<div class="bg-gray-50 rounded-lg p-3 mb-4 flex items-center gap-4" id="dynamic-filter-area">
<span class="text-sm text-gray-600">子类别筛选:</span>
<button onclick="filterDynamicBySubcategory('')" class="px-3 py-1 rounded text-sm bg-white border text-gray-600 hover:bg-gray-100">全部</button>
<div id="dynamic-subcategory-filters" class="flex gap-2"></div>
</div>
<div class="bg-white rounded-xl shadow-sm overflow-hidden"> <div class="bg-white rounded-xl shadow-sm overflow-hidden">
<table class="w-full"><tbody id="admin-dynamic-table"><tr><td class="text-center text-gray-400 py-8">加载中...</td></tr></tbody></table> <table class="w-full"><tbody id="admin-dynamic-table"><tr><td class="text-center text-gray-400 py-8">加载中...</td></tr></tbody></table>
</div> </div>
@@ -173,6 +178,11 @@
<button onclick="openAddModal('model')" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700"><i class="ri-add-line mr-2"></i>手动添加</button> <button onclick="openAddModal('model')" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700"><i class="ri-add-line mr-2"></i>手动添加</button>
</div> </div>
</div> </div>
<div class="bg-gray-50 rounded-lg p-3 mb-4 flex items-center gap-4">
<span class="text-sm text-gray-600">子类别筛选:</span>
<button onclick="filterModelsBySubcategory('')" class="px-3 py-1 rounded text-sm bg-white border text-gray-600 hover:bg-gray-100">全部</button>
<div id="model-subcategory-filters" class="flex gap-2"></div>
</div>
<div class="bg-white rounded-xl shadow-sm overflow-x-auto"> <div class="bg-white rounded-xl shadow-sm overflow-x-auto">
<table class="w-full min-w-[1200px]"> <table class="w-full min-w-[1200px]">
<thead class="bg-gray-50 border-b"> <thead class="bg-gray-50 border-b">
@@ -180,18 +190,17 @@
<th class="px-3 py-3 text-left text-sm font-medium text-gray-600">置顶</th> <th class="px-3 py-3 text-left text-sm font-medium text-gray-600">置顶</th>
<th class="px-3 py-3 text-left text-sm font-medium text-gray-600">名称</th> <th class="px-3 py-3 text-left text-sm font-medium text-gray-600">名称</th>
<th class="px-3 py-3 text-left text-sm font-medium text-gray-600">厂商</th> <th class="px-3 py-3 text-left text-sm font-medium text-gray-600">厂商</th>
<th class="px-3 py-3 text-left text-sm font-medium text-gray-600">子类别</th>
<th class="px-3 py-3 text-left text-sm font-medium text-gray-600">参数量</th> <th class="px-3 py-3 text-left text-sm font-medium text-gray-600">参数量</th>
<th class="px-3 py-3 text-left text-sm font-medium text-gray-600">上下文</th> <th class="px-3 py-3 text-left text-sm font-medium text-gray-600">上下文</th>
<th class="px-3 py-3 text-left text-sm font-medium text-gray-600">类型</th> <th class="px-3 py-3 text-left text-sm font-medium text-gray-600">类型</th>
<th class="px-3 py-3 text-left text-sm font-medium text-gray-600">发布日期</th> <th class="px-3 py-3 text-left text-sm font-medium text-gray-600">发布日期</th>
<th class="px-3 py-3 text-left text-sm font-medium text-gray-600">热度</th> <th class="px-3 py-3 text-left text-sm font-medium text-gray-600">热度</th>
<th class="px-3 py-3 text-left text-sm font-medium text-gray-600">创建时间</th>
<th class="px-3 py-3 text-left text-sm font-medium text-gray-600">更新时间</th>
<th class="px-3 py-3 text-center text-sm font-medium text-gray-600">显示</th> <th class="px-3 py-3 text-center text-sm font-medium text-gray-600">显示</th>
<th class="px-3 py-3 text-center text-sm font-medium text-gray-600">操作</th> <th class="px-3 py-3 text-center text-sm font-medium text-gray-600">操作</th>
</tr> </tr>
</thead> </thead>
<tbody id="admin-models-table"><tr><td colspan="12" class="text-center text-gray-400 py-8">加载中...</td></tr></tbody> <tbody id="admin-models-table"><tr><td colspan="11" class="text-center text-gray-400 py-8">加载中...</td></tr></tbody>
</table> </table>
</div> </div>
</section> </section>
@@ -205,6 +214,11 @@
<button onclick="openAddModal('gpu')" class="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700"><i class="ri-add-line mr-2"></i>手动添加</button> <button onclick="openAddModal('gpu')" class="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700"><i class="ri-add-line mr-2"></i>手动添加</button>
</div> </div>
</div> </div>
<div class="bg-gray-50 rounded-lg p-3 mb-4 flex items-center gap-4">
<span class="text-sm text-gray-600">子类别筛选:</span>
<button onclick="filterGpusBySubcategory('')" class="px-3 py-1 rounded text-sm bg-white border text-gray-600 hover:bg-gray-100">全部</button>
<div id="gpu-subcategory-filters" class="flex gap-2"></div>
</div>
<div class="bg-white rounded-xl shadow-sm overflow-x-auto"> <div class="bg-white rounded-xl shadow-sm overflow-x-auto">
<table class="w-full min-w-[1200px]"> <table class="w-full min-w-[1200px]">
<thead class="bg-gray-50 border-b"> <thead class="bg-gray-50 border-b">
@@ -212,18 +226,17 @@
<th class="px-3 py-3 text-left text-sm font-medium text-gray-600">置顶</th> <th class="px-3 py-3 text-left text-sm font-medium text-gray-600">置顶</th>
<th class="px-3 py-3 text-left text-sm font-medium text-gray-600">名称</th> <th class="px-3 py-3 text-left text-sm font-medium text-gray-600">名称</th>
<th class="px-3 py-3 text-left text-sm font-medium text-gray-600">厂商</th> <th class="px-3 py-3 text-left text-sm font-medium text-gray-600">厂商</th>
<th class="px-3 py-3 text-left text-sm font-medium text-gray-600">子类别</th>
<th class="px-3 py-3 text-left text-sm font-medium text-gray-600">显存</th> <th class="px-3 py-3 text-left text-sm font-medium text-gray-600">显存</th>
<th class="px-3 py-3 text-left text-sm font-medium text-gray-600">架构</th> <th class="px-3 py-3 text-left text-sm font-medium text-gray-600">架构</th>
<th class="px-3 py-3 text-left text-sm font-medium text-gray-600">价格</th> <th class="px-3 py-3 text-left text-sm font-medium text-gray-600">价格</th>
<th class="px-3 py-3 text-left text-sm font-medium text-gray-600">发布日期</th> <th class="px-3 py-3 text-left text-sm font-medium text-gray-600">发布日期</th>
<th class="px-3 py-3 text-left text-sm font-medium text-gray-600">热度</th> <th class="px-3 py-3 text-left text-sm font-medium text-gray-600">热度</th>
<th class="px-3 py-3 text-left text-sm font-medium text-gray-600">创建时间</th>
<th class="px-3 py-3 text-left text-sm font-medium text-gray-600">更新时间</th>
<th class="px-3 py-3 text-center text-sm font-medium text-gray-600">显示</th> <th class="px-3 py-3 text-center text-sm font-medium text-gray-600">显示</th>
<th class="px-3 py-3 text-center text-sm font-medium text-gray-600">操作</th> <th class="px-3 py-3 text-center text-sm font-medium text-gray-600">操作</th>
</tr> </tr>
</thead> </thead>
<tbody id="admin-gpus-table"><tr><td colspan="12" class="text-center text-gray-400 py-8">加载中...</td></tr></tbody> <tbody id="admin-gpus-table"><tr><td colspan="11" class="text-center text-gray-400 py-8">加载中...</td></tr></tbody>
</table> </table>
</div> </div>
</section> </section>
@@ -237,6 +250,11 @@
<button onclick="openAddModal('cpu')" class="px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700"><i class="ri-add-line mr-2"></i>手动添加</button> <button onclick="openAddModal('cpu')" class="px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700"><i class="ri-add-line mr-2"></i>手动添加</button>
</div> </div>
</div> </div>
<div class="bg-gray-50 rounded-lg p-3 mb-4 flex items-center gap-4">
<span class="text-sm text-gray-600">子类别筛选:</span>
<button onclick="filterCpusBySubcategory('')" class="px-3 py-1 rounded text-sm bg-white border text-gray-600 hover:bg-gray-100">全部</button>
<div id="cpu-subcategory-filters" class="flex gap-2"></div>
</div>
<div class="bg-white rounded-xl shadow-sm overflow-x-auto"> <div class="bg-white rounded-xl shadow-sm overflow-x-auto">
<table class="w-full min-w-[1200px]"> <table class="w-full min-w-[1200px]">
<thead class="bg-gray-50 border-b"> <thead class="bg-gray-50 border-b">
@@ -244,18 +262,17 @@
<th class="px-3 py-3 text-left text-sm font-medium text-gray-600">置顶</th> <th class="px-3 py-3 text-left text-sm font-medium text-gray-600">置顶</th>
<th class="px-3 py-3 text-left text-sm font-medium text-gray-600">名称</th> <th class="px-3 py-3 text-left text-sm font-medium text-gray-600">名称</th>
<th class="px-3 py-3 text-left text-sm font-medium text-gray-600">厂商</th> <th class="px-3 py-3 text-left text-sm font-medium text-gray-600">厂商</th>
<th class="px-3 py-3 text-left text-sm font-medium text-gray-600">子类别</th>
<th class="px-3 py-3 text-left text-sm font-medium text-gray-600">核心/线程</th> <th class="px-3 py-3 text-left text-sm font-medium text-gray-600">核心/线程</th>
<th class="px-3 py-3 text-left text-sm font-medium text-gray-600">主频</th> <th class="px-3 py-3 text-left text-sm font-medium text-gray-600">主频</th>
<th class="px-3 py-3 text-left text-sm font-medium text-gray-600">价格</th> <th class="px-3 py-3 text-left text-sm font-medium text-gray-600">价格</th>
<th class="px-3 py-3 text-left text-sm font-medium text-gray-600">发布日期</th> <th class="px-3 py-3 text-left text-sm font-medium text-gray-600">发布日期</th>
<th class="px-3 py-3 text-left text-sm font-medium text-gray-600">热度</th> <th class="px-3 py-3 text-left text-sm font-medium text-gray-600">热度</th>
<th class="px-3 py-3 text-left text-sm font-medium text-gray-600">创建时间</th>
<th class="px-3 py-3 text-left text-sm font-medium text-gray-600">更新时间</th>
<th class="px-3 py-3 text-center text-sm font-medium text-gray-600">显示</th> <th class="px-3 py-3 text-center text-sm font-medium text-gray-600">显示</th>
<th class="px-3 py-3 text-center text-sm font-medium text-gray-600">操作</th> <th class="px-3 py-3 text-center text-sm font-medium text-gray-600">操作</th>
</tr> </tr>
</thead> </thead>
<tbody id="admin-cpus-table"><tr><td colspan="12" class="text-center text-gray-400 py-8">加载中...</td></tr></tbody> <tbody id="admin-cpus-table"><tr><td colspan="11" class="text-center text-gray-400 py-8">加载中...</td></tr></tbody>
</table> </table>
</div> </div>
</section> </section>
@@ -448,11 +465,46 @@
let categories = []; let categories = [];
let currentFilter = ''; let currentFilter = '';
let dynamicCategoryId = ''; let dynamicCategoryId = '';
let modelSubcategoryFilter = '';
let gpuSubcategoryFilter = '';
let cpuSubcategoryFilter = '';
let dynamicSubcategoryFilter = '';
// 生成随机ID12位十六进制 // 生成随机ID12位十六进制
function generateId() { function generateId() {
return Math.random().toString(16).slice(2, 8) + Math.random().toString(16).slice(2, 8); return Math.random().toString(16).slice(2, 8) + Math.random().toString(16).slice(2, 8);
} }
// 获取子类别名称
function getSubcategoryName(categoryId, subcategoryId) {
const cat = categories.find(c => c.id === categoryId);
if (!cat || !cat.subcategories) return '-';
const sub = cat.subcategories.find(s => s.id === subcategoryId);
return sub ? sub.name : '-';
}
// 获取子类别图标
function getSubcategoryIcon(categoryId, subcategoryId) {
const cat = categories.find(c => c.id === categoryId);
if (!cat || !cat.subcategories) return '';
const sub = cat.subcategories.find(s => s.id === subcategoryId);
return sub ? sub.icon : '';
}
// 渲染子类别筛选按钮
function renderSubcategoryFilters(categoryId, containerId, filterFunction) {
const cat = categories.find(c => c.id === categoryId);
const container = document.getElementById(containerId);
if (!cat || !cat.subcategories || cat.subcategories.length === 0) {
container.innerHTML = '';
return;
}
container.innerHTML = cat.subcategories.map(sub => `
<button onclick="${filterFunction}('${sub.id}')" class="px-3 py-1 rounded text-sm bg-white border text-gray-600 hover:bg-gray-100">
<i class="${sub.icon} mr-1"></i>${sub.name}
</button>
`).join('');
}
const colorMap = { const colorMap = {
blue: 'bg-blue-100 text-blue-600', blue: 'bg-blue-100 text-blue-600',
@@ -582,6 +634,7 @@
// 显示动态分类数据 // 显示动态分类数据
async function showDynamicCategory(categoryId) { async function showDynamicCategory(categoryId) {
dynamicCategoryId = categoryId; dynamicCategoryId = categoryId;
dynamicSubcategoryFilter = ''; // 重置筛选
const cat = categories.find(c => c.id === categoryId); const cat = categories.find(c => c.id === categoryId);
document.querySelectorAll('section').forEach(s => s.classList.add('hidden')); document.querySelectorAll('section').forEach(s => s.classList.add('hidden'));
@@ -599,21 +652,44 @@
document.getElementById('dynamic-title').textContent = cat.name + '管理'; document.getElementById('dynamic-title').textContent = cat.name + '管理';
// 渲染子类别筛选按钮
if (cat.subcategories && cat.subcategories.length > 0) {
document.getElementById('dynamic-filter-area').classList.remove('hidden');
renderSubcategoryFilters(categoryId, 'dynamic-subcategory-filters', 'filterDynamicBySubcategory');
} else {
document.getElementById('dynamic-filter-area').classList.add('hidden');
}
// 加载该分类的数据(后台显示全部,包括隐藏的) // 加载该分类的数据(后台显示全部,包括隐藏的)
const res = await fetch(`/api/items/${categoryId}?all=1`); const res = await fetch(`/api/items/${categoryId}?all=1`);
const items = await res.json(); let items = await res.json();
// 子类别筛选
if (dynamicSubcategoryFilter) {
items = items.filter(i => i.subcategory_id === dynamicSubcategoryFilter);
}
if (items.length === 0) { if (items.length === 0) {
document.getElementById('admin-dynamic-table').innerHTML = '<tr><td class="text-center text-gray-400 py-8">暂无数据,点击上方"添加数据"按钮添加</td></tr>'; document.getElementById('admin-dynamic-table').innerHTML = '<tr><td class="text-center text-gray-400 py-8">暂无数据,点击上方"添加数据"按钮添加</td></tr>';
} else { } else {
const keys = Object.keys(items[0]).filter(k => !['id', 'created_at', 'updated_at', 'visible', 'raw_text'].includes(k)); const keys = Object.keys(items[0]).filter(k => !['id', 'created_at', 'updated_at', 'visible', 'raw_text', 'subcategory_id'].includes(k));
let html = `<thead class="bg-gray-50 border-b"><tr>`; let html = `<thead class="bg-gray-50 border-b"><tr>`;
// 添加子类别列
if (cat.subcategories && cat.subcategories.length > 0) {
html += `<th class="px-4 py-3 text-left text-sm font-medium text-gray-600">子类别</th>`;
}
keys.forEach(k => { html += `<th class="px-4 py-3 text-left text-sm font-medium text-gray-600">${k}</th>`; }); keys.forEach(k => { html += `<th class="px-4 py-3 text-left text-sm font-medium text-gray-600">${k}</th>`; });
html += `<th class="px-4 py-3 text-center 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 += `<th class="px-4 py-3 text-center text-sm font-medium text-gray-600">操作</th></tr></thead><tbody>`; html += `<th class="px-4 py-3 text-center text-sm font-medium text-gray-600">操作</th></tr></thead><tbody>`;
items.forEach(item => { items.forEach(item => {
html += `<tr class="border-b hover:bg-gray-50 ${item.visible === false ? 'bg-gray-100 opacity-60' : ''}">`; html += `<tr class="border-b hover:bg-gray-50 ${item.visible === false ? 'bg-gray-100 opacity-60' : ''}">`;
// 子类别显示
if (cat.subcategories && cat.subcategories.length > 0) {
const subName = getSubcategoryName(categoryId, item.subcategory_id);
const subIcon = getSubcategoryIcon(categoryId, item.subcategory_id);
html += `<td class="px-4 py-3">${item.subcategory_id ? `<span class="px-2 py-1 bg-indigo-100 text-indigo-600 rounded text-xs"><i class="${subIcon} mr-1"></i>${subName}</span>` : '<span class="text-gray-400">-</span>'}</td>`;
}
keys.forEach(k => { html += `<td class="px-4 py-3 text-gray-600">${item[k] || '-'}</td>`; }); keys.forEach(k => { html += `<td class="px-4 py-3 text-gray-600">${item[k] || '-'}</td>`; });
html += `<td class="px-4 py-3 text-center"> html += `<td class="px-4 py-3 text-center">
<button onclick="toggleVisible('dynamic', '${item.id}')" class="${item.visible === false ? 'text-gray-400' : 'text-green-600'} hover:opacity-80" title="${item.visible === false ? '点击显示' : '点击隐藏'}"> <button onclick="toggleVisible('dynamic', '${item.id}')" class="${item.visible === false ? 'text-gray-400' : 'text-green-600'} hover:opacity-80" title="${item.visible === false ? '点击显示' : '点击隐藏'}">
@@ -630,6 +706,11 @@
document.getElementById('admin-dynamic-table').innerHTML = html; document.getElementById('admin-dynamic-table').innerHTML = html;
} }
} }
function filterDynamicBySubcategory(subId) {
dynamicSubcategoryFilter = subId;
showDynamicCategory(dynamicCategoryId);
}
// 切换显示区域 // 切换显示区域
function showSection(section) { function showSection(section) {
@@ -800,9 +881,16 @@
// 加载模型列表 // 加载模型列表
async function loadAdminModels() { async function loadAdminModels() {
renderSubcategoryFilters('ai-models', 'model-subcategory-filters', 'filterModelsBySubcategory');
const res = await fetch('/api/models?all=1'); const res = await fetch('/api/models?all=1');
const models = await res.json(); let models = await res.json();
if (models.length === 0) { document.getElementById('admin-models-table').innerHTML = '<tr><td colspan="12" class="text-center text-gray-400 py-8">暂无数据</td></tr>'; return; }
// 子类别筛选
if (modelSubcategoryFilter) {
models = models.filter(m => m.subcategory_id === modelSubcategoryFilter);
}
if (models.length === 0) { document.getElementById('admin-models-table').innerHTML = '<tr><td colspan="11" class="text-center text-gray-400 py-8">暂无数据</td></tr>'; return; }
document.getElementById('admin-models-table').innerHTML = models.map(m => ` document.getElementById('admin-models-table').innerHTML = models.map(m => `
<tr class="border-b hover:bg-gray-50 ${m.visible === false ? 'bg-gray-100 opacity-60' : ''} ${m.is_pinned ? 'bg-yellow-50' : ''}"> <tr class="border-b hover:bg-gray-50 ${m.visible === false ? 'bg-gray-100 opacity-60' : ''} ${m.is_pinned ? 'bg-yellow-50' : ''}">
<td class="px-3 py-3 text-center"> <td class="px-3 py-3 text-center">
@@ -812,13 +900,14 @@
</td> </td>
<td class="px-3 py-3 font-medium text-gray-800">${m.name}</td> <td class="px-3 py-3 font-medium text-gray-800">${m.name}</td>
<td class="px-3 py-3 text-gray-600">${m.organization}</td> <td class="px-3 py-3 text-gray-600">${m.organization}</td>
<td class="px-3 py-3">
${m.subcategory_id ? `<span class="px-2 py-1 bg-blue-100 text-blue-600 rounded text-xs"><i class="${getSubcategoryIcon('ai-models', m.subcategory_id)} mr-1"></i>${getSubcategoryName('ai-models', m.subcategory_id)}</span>` : '<span class="text-gray-400">-</span>'}
</td>
<td class="px-3 py-3">${m.parameters}B</td> <td class="px-3 py-3">${m.parameters}B</td>
<td class="px-3 py-3 text-gray-600">${m.context_length || '-'}</td> <td class="px-3 py-3 text-gray-600">${m.context_length || '-'}</td>
<td class="px-3 py-3">${m.is_open_source ? '<span class="text-green-600">开源</span>' : '<span class="text-gray-600">商业</span>'}</td> <td class="px-3 py-3">${m.is_open_source ? '<span class="text-green-600">开源</span>' : '<span class="text-gray-600">商业</span>'}</td>
<td class="px-3 py-3 text-gray-500 text-sm">${m.publish_date || '-'}</td> <td class="px-3 py-3 text-gray-500 text-sm">${m.publish_date || '-'}</td>
<td class="px-3 py-3 text-gray-500 text-sm">${m.views || 0}</td> <td class="px-3 py-3 text-gray-500 text-sm">${m.views || 0}</td>
<td class="px-3 py-3 text-gray-500 text-sm">${formatDateShort(m.created_at)}</td>
<td class="px-3 py-3 text-gray-500 text-sm">${formatDateShort(m.updated_at)}</td>
<td class="px-3 py-3 text-center"> <td class="px-3 py-3 text-center">
<button onclick="toggleVisible('model', '${m.id}')" class="${m.visible === false ? 'text-gray-400' : 'text-green-600'} hover:opacity-80" title="${m.visible === false ? '点击显示' : '点击隐藏'}"> <button onclick="toggleVisible('model', '${m.id}')" class="${m.visible === false ? 'text-gray-400' : 'text-green-600'} hover:opacity-80" title="${m.visible === false ? '点击显示' : '点击隐藏'}">
<i class="${m.visible === false ? 'ri-eye-off-line' : 'ri-eye-line'}"></i> <i class="${m.visible === false ? 'ri-eye-off-line' : 'ri-eye-line'}"></i>
@@ -832,12 +921,24 @@
</tr> </tr>
`).join(''); `).join('');
} }
function filterModelsBySubcategory(subId) {
modelSubcategoryFilter = subId;
loadAdminModels();
}
// 加载GPU列表 // 加载GPU列表
async function loadAdminGpus() { async function loadAdminGpus() {
renderSubcategoryFilters('gpus', 'gpu-subcategory-filters', 'filterGpusBySubcategory');
const res = await fetch('/api/gpus?all=1'); const res = await fetch('/api/gpus?all=1');
const gpus = await res.json(); let gpus = await res.json();
if (gpus.length === 0) { document.getElementById('admin-gpus-table').innerHTML = '<tr><td colspan="12" class="text-center text-gray-400 py-8">暂无数据</td></tr>'; return; }
// 子类别筛选
if (gpuSubcategoryFilter) {
gpus = gpus.filter(g => g.subcategory_id === gpuSubcategoryFilter);
}
if (gpus.length === 0) { document.getElementById('admin-gpus-table').innerHTML = '<tr><td colspan="11" class="text-center text-gray-400 py-8">暂无数据</td></tr>'; return; }
document.getElementById('admin-gpus-table').innerHTML = gpus.map(g => ` document.getElementById('admin-gpus-table').innerHTML = gpus.map(g => `
<tr class="border-b hover:bg-gray-50 ${g.visible === false ? 'bg-gray-100 opacity-60' : ''} ${g.is_pinned ? 'bg-yellow-50' : ''}"> <tr class="border-b hover:bg-gray-50 ${g.visible === false ? 'bg-gray-100 opacity-60' : ''} ${g.is_pinned ? 'bg-yellow-50' : ''}">
<td class="px-3 py-3 text-center"> <td class="px-3 py-3 text-center">
@@ -847,13 +948,14 @@
</td> </td>
<td class="px-3 py-3 font-medium text-gray-800">${g.name}</td> <td class="px-3 py-3 font-medium text-gray-800">${g.name}</td>
<td class="px-3 py-3 text-gray-600">${g.manufacturer}</td> <td class="px-3 py-3 text-gray-600">${g.manufacturer}</td>
<td class="px-3 py-3">
${g.subcategory_id ? `<span class="px-2 py-1 bg-green-100 text-green-600 rounded text-xs"><i class="${getSubcategoryIcon('gpus', g.subcategory_id)} mr-1"></i>${getSubcategoryName('gpus', g.subcategory_id)}</span>` : '<span class="text-gray-400">-</span>'}
</td>
<td class="px-3 py-3">${g.memory_gb}GB</td> <td class="px-3 py-3">${g.memory_gb}GB</td>
<td class="px-3 py-3 text-gray-600">${g.architecture || '-'}</td> <td class="px-3 py-3 text-gray-600">${g.architecture || '-'}</td>
<td class="px-3 py-3 text-gray-600">${formatPrice(g)}</td> <td class="px-3 py-3 text-gray-600">${formatPrice(g)}</td>
<td class="px-3 py-3 text-gray-500 text-sm">${g.publish_date || '-'}</td> <td class="px-3 py-3 text-gray-500 text-sm">${g.publish_date || '-'}</td>
<td class="px-3 py-3 text-gray-500 text-sm">${g.views || 0}</td> <td class="px-3 py-3 text-gray-500 text-sm">${g.views || 0}</td>
<td class="px-3 py-3 text-gray-500 text-sm">${formatDateShort(g.created_at)}</td>
<td class="px-3 py-3 text-gray-500 text-sm">${formatDateShort(g.updated_at)}</td>
<td class="px-3 py-3 text-center"> <td class="px-3 py-3 text-center">
<button onclick="toggleVisible('gpu', '${g.id}')" class="${g.visible === false ? 'text-gray-400' : 'text-green-600'} hover:opacity-80" title="${g.visible === false ? '点击显示' : '点击隐藏'}"> <button onclick="toggleVisible('gpu', '${g.id}')" class="${g.visible === false ? 'text-gray-400' : 'text-green-600'} hover:opacity-80" title="${g.visible === false ? '点击显示' : '点击隐藏'}">
<i class="${g.visible === false ? 'ri-eye-off-line' : 'ri-eye-line'}"></i> <i class="${g.visible === false ? 'ri-eye-off-line' : 'ri-eye-line'}"></i>
@@ -867,12 +969,24 @@
</tr> </tr>
`).join(''); `).join('');
} }
function filterGpusBySubcategory(subId) {
gpuSubcategoryFilter = subId;
loadAdminGpus();
}
// 加载CPU列表 // 加载CPU列表
async function loadAdminCpus() { async function loadAdminCpus() {
renderSubcategoryFilters('cpus', 'cpu-subcategory-filters', 'filterCpusBySubcategory');
const res = await fetch('/api/cpus?all=1'); const res = await fetch('/api/cpus?all=1');
const cpus = await res.json(); let cpus = await res.json();
if (cpus.length === 0) { document.getElementById('admin-cpus-table').innerHTML = '<tr><td colspan="12" class="text-center text-gray-400 py-8">暂无数据</td></tr>'; return; }
// 子类别筛选
if (cpuSubcategoryFilter) {
cpus = cpus.filter(c => c.subcategory_id === cpuSubcategoryFilter);
}
if (cpus.length === 0) { document.getElementById('admin-cpus-table').innerHTML = '<tr><td colspan="11" class="text-center text-gray-400 py-8">暂无数据</td></tr>'; return; }
document.getElementById('admin-cpus-table').innerHTML = cpus.map(c => ` document.getElementById('admin-cpus-table').innerHTML = cpus.map(c => `
<tr class="border-b hover:bg-gray-50 ${c.visible === false ? 'bg-gray-100 opacity-60' : ''} ${c.is_pinned ? 'bg-yellow-50' : ''}"> <tr class="border-b hover:bg-gray-50 ${c.visible === false ? 'bg-gray-100 opacity-60' : ''} ${c.is_pinned ? 'bg-yellow-50' : ''}">
<td class="px-3 py-3 text-center"> <td class="px-3 py-3 text-center">
@@ -882,13 +996,14 @@
</td> </td>
<td class="px-3 py-3 font-medium text-gray-800">${c.name}</td> <td class="px-3 py-3 font-medium text-gray-800">${c.name}</td>
<td class="px-3 py-3 text-gray-600">${c.manufacturer}</td> <td class="px-3 py-3 text-gray-600">${c.manufacturer}</td>
<td class="px-3 py-3">
${c.subcategory_id ? `<span class="px-2 py-1 bg-purple-100 text-purple-600 rounded text-xs"><i class="${getSubcategoryIcon('cpus', c.subcategory_id)} mr-1"></i>${getSubcategoryName('cpus', c.subcategory_id)}</span>` : '<span class="text-gray-400">-</span>'}
</td>
<td class="px-3 py-3">${c.cores}/${c.threads}</td> <td class="px-3 py-3">${c.cores}/${c.threads}</td>
<td class="px-3 py-3 text-gray-600">${c.base_clock_ghz || '-'}-${c.boost_clock_ghz || '-'}GHz</td> <td class="px-3 py-3 text-gray-600">${c.base_clock_ghz || '-'}-${c.boost_clock_ghz || '-'}GHz</td>
<td class="px-3 py-3 text-gray-600">${formatPrice(c)}</td> <td class="px-3 py-3 text-gray-600">${formatPrice(c)}</td>
<td class="px-3 py-3 text-gray-500 text-sm">${c.publish_date || '-'}</td> <td class="px-3 py-3 text-gray-500 text-sm">${c.publish_date || '-'}</td>
<td class="px-3 py-3 text-gray-500 text-sm">${c.views || 0}</td> <td class="px-3 py-3 text-gray-500 text-sm">${c.views || 0}</td>
<td class="px-3 py-3 text-gray-500 text-sm">${formatDateShort(c.created_at)}</td>
<td class="px-3 py-3 text-gray-500 text-sm">${formatDateShort(c.updated_at)}</td>
<td class="px-3 py-3 text-center"> <td class="px-3 py-3 text-center">
<button onclick="toggleVisible('cpu', '${c.id}')" class="${c.visible === false ? 'text-gray-400' : 'text-green-600'} hover:opacity-80" title="${c.visible === false ? '点击显示' : '点击隐藏'}"> <button onclick="toggleVisible('cpu', '${c.id}')" class="${c.visible === false ? 'text-gray-400' : 'text-green-600'} hover:opacity-80" title="${c.visible === false ? '点击显示' : '点击隐藏'}">
<i class="${c.visible === false ? 'ri-eye-off-line' : 'ri-eye-line'}"></i> <i class="${c.visible === false ? 'ri-eye-off-line' : 'ri-eye-line'}"></i>
@@ -902,6 +1017,11 @@
</tr> </tr>
`).join(''); `).join('');
} }
function filterCpusBySubcategory(subId) {
cpuSubcategoryFilter = subId;
loadAdminCpus();
}
// 加载知识列表 // 加载知识列表
async function loadAdminKnowledge() { async function loadAdminKnowledge() {
@@ -1421,11 +1541,28 @@
</form>`; </form>`;
} }
// 生成子类别选择器
function getSubcategorySelect(categoryId, currentValue = '') {
const cat = categories.find(c => c.id === categoryId);
if (!cat || !cat.subcategories || cat.subcategories.length === 0) {
return '';
}
const options = cat.subcategories.map(sub =>
`<option value="${sub.id}" ${currentValue === sub.id ? 'selected' : ''}><i class="${sub.icon} mr-1"></i>${sub.name}</option>`
).join('');
return `<div><label class="text-sm text-gray-600 mb-1 block">子类别</label><select name="subcategory_id" class="w-full px-3 py-2 border rounded-lg"><option value="">请选择</option>${options}</select></div>`;
}
function getDynamicForm(data = {}) { function getDynamicForm(data = {}) {
const cat = categories.find(c => c.id === dynamicCategoryId); const cat = categories.find(c => c.id === dynamicCategoryId);
currentImages = data.images || []; currentImages = data.images || [];
const subcategorySelect = getSubcategorySelect(dynamicCategoryId, data.subcategory_id);
return `<form id="itemForm" class="space-y-4"> return `<form id="itemForm" class="space-y-4">
<div class="grid grid-cols-2 gap-4"> <div class="grid grid-cols-2 gap-4">
${subcategorySelect}
<div><label class="text-sm text-gray-600 mb-1 block">名称 *</label><input type="text" name="name" value="${data.name || ''}" required class="w-full px-3 py-2 border rounded-lg"></div> <div><label class="text-sm text-gray-600 mb-1 block">名称 *</label><input type="text" name="name" value="${data.name || ''}" required class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">品牌</label><input type="text" name="brand" value="${data.brand || ''}" class="w-full px-3 py-2 border rounded-lg"></div> <div><label class="text-sm text-gray-600 mb-1 block">品牌</label><input type="text" name="brand" value="${data.brand || ''}" class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">价格</label><input type="number" name="price" value="${data.price || ''}" step="0.01" class="w-full px-3 py-2 border rounded-lg"></div> <div><label class="text-sm text-gray-600 mb-1 block">价格</label><input type="number" name="price" value="${data.price || ''}" step="0.01" class="w-full px-3 py-2 border rounded-lg"></div>
@@ -1441,8 +1578,11 @@
function getModelForm(data = {}) { function getModelForm(data = {}) {
currentImages = data.images || []; currentImages = data.images || [];
const subcategorySelect = getSubcategorySelect('ai-models', data.subcategory_id);
return `<form id="itemForm" class="space-y-4"> return `<form id="itemForm" class="space-y-4">
<div class="grid grid-cols-2 gap-4"> <div class="grid grid-cols-2 gap-4">
${subcategorySelect}
<div><label class="text-sm text-gray-600 mb-1 block">名称 *</label><input type="text" name="name" value="${data.name || ''}" required class="w-full px-3 py-2 border rounded-lg"></div> <div><label class="text-sm text-gray-600 mb-1 block">名称 *</label><input type="text" name="name" value="${data.name || ''}" required class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">厂商 *</label><input type="text" name="organization" value="${data.organization || ''}" required class="w-full px-3 py-2 border rounded-lg"></div> <div><label class="text-sm text-gray-600 mb-1 block">厂商 *</label><input type="text" name="organization" value="${data.organization || ''}" required class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">参数量(B) *</label><input type="number" name="parameters" value="${data.parameters || ''}" required class="w-full px-3 py-2 border rounded-lg"></div> <div><label class="text-sm text-gray-600 mb-1 block">参数量(B) *</label><input type="number" name="parameters" value="${data.parameters || ''}" required class="w-full px-3 py-2 border rounded-lg"></div>
@@ -1464,8 +1604,11 @@
function getGpuForm(data = {}) { function getGpuForm(data = {}) {
currentImages = data.images || []; currentImages = data.images || [];
const subcategorySelect = getSubcategorySelect('gpus', data.subcategory_id);
return `<form id="itemForm" class="space-y-4"> return `<form id="itemForm" class="space-y-4">
<div class="grid grid-cols-2 gap-4"> <div class="grid grid-cols-2 gap-4">
${subcategorySelect}
<div><label class="text-sm text-gray-600 mb-1 block">名称 *</label><input type="text" name="name" value="${data.name || ''}" required class="w-full px-3 py-2 border rounded-lg"></div> <div><label class="text-sm text-gray-600 mb-1 block">名称 *</label><input type="text" name="name" value="${data.name || ''}" required class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">厂商 *</label><input type="text" name="manufacturer" value="${data.manufacturer || ''}" required class="w-full px-3 py-2 border rounded-lg"></div> <div><label class="text-sm text-gray-600 mb-1 block">厂商 *</label><input type="text" name="manufacturer" value="${data.manufacturer || ''}" required class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">架构</label><input type="text" name="architecture" value="${data.architecture || ''}" class="w-full px-3 py-2 border rounded-lg"></div> <div><label class="text-sm text-gray-600 mb-1 block">架构</label><input type="text" name="architecture" value="${data.architecture || ''}" class="w-full px-3 py-2 border rounded-lg"></div>
@@ -1493,8 +1636,11 @@
function getCpuForm(data = {}) { function getCpuForm(data = {}) {
currentImages = data.images || []; currentImages = data.images || [];
const subcategorySelect = getSubcategorySelect('cpus', data.subcategory_id);
return `<form id="itemForm" class="space-y-4"> return `<form id="itemForm" class="space-y-4">
<div class="grid grid-cols-2 gap-4"> <div class="grid grid-cols-2 gap-4">
${subcategorySelect}
<div><label class="text-sm text-gray-600 mb-1 block">名称 *</label><input type="text" name="name" value="${data.name || ''}" required class="w-full px-3 py-2 border rounded-lg"></div> <div><label class="text-sm text-gray-600 mb-1 block">名称 *</label><input type="text" name="name" value="${data.name || ''}" required class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">厂商 *</label><input type="text" name="manufacturer" value="${data.manufacturer || ''}" required class="w-full px-3 py-2 border rounded-lg"></div> <div><label class="text-sm text-gray-600 mb-1 block">厂商 *</label><input type="text" name="manufacturer" value="${data.manufacturer || ''}" required class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">架构</label><input type="text" name="architecture" value="${data.architecture || ''}" class="w-full px-3 py-2 border rounded-lg"></div> <div><label class="text-sm text-gray-600 mb-1 block">架构</label><input type="text" name="architecture" value="${data.architecture || ''}" class="w-full px-3 py-2 border rounded-lg"></div>