2 Commits

View File

@@ -185,23 +185,8 @@
<div id="model-subcategory-filters" class="flex gap-2"></div> <div id="model-subcategory-filters" class="flex gap-2"></div>
</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" id="admin-models-table">
<thead class="bg-gray-50 border-b"> <tbody><tr><td colspan="20" class="text-center text-gray-400 py-8">加载中...</td></tr></tbody>
<tr>
<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>
</tr>
</thead>
<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>
@@ -221,23 +206,8 @@
<div id="gpu-subcategory-filters" class="flex gap-2"></div> <div id="gpu-subcategory-filters" class="flex gap-2"></div>
</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" id="admin-gpus-table">
<thead class="bg-gray-50 border-b"> <tbody><tr><td colspan="20" class="text-center text-gray-400 py-8">加载中...</td></tr></tbody>
<tr>
<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>
</tr>
</thead>
<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>
@@ -257,23 +227,8 @@
<div id="cpu-subcategory-filters" class="flex gap-2"></div> <div id="cpu-subcategory-filters" class="flex gap-2"></div>
</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" id="admin-cpus-table">
<thead class="bg-gray-50 border-b"> <tbody><tr><td colspan="20" class="text-center text-gray-400 py-8">加载中...</td></tr></tbody>
<tr>
<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>
</tr>
</thead>
<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>
@@ -676,8 +631,11 @@
// 显示动态分类数据 // 显示动态分类数据
async function showDynamicCategory(categoryId) { async function showDynamicCategory(categoryId) {
dynamicCategoryId = categoryId; dynamicCategoryId = categoryId;
dynamicSubcategoryFilter = ''; // 重置筛选 dynamicSubcategoryFilter = '';
const cat = categories.find(c => c.id === categoryId); const cat = categories.find(c => c.id === categoryId);
const fields = cat ? (cat.fields || []) : [];
const fixedFields = ['id', 'created_at', 'updated_at', 'visible', 'raw_text', 'category_id', 'subcategory_id', 'is_pinned', 'images'];
const displayFields = fields.filter(f => !fixedFields.includes(f.key));
document.querySelectorAll('section').forEach(s => s.classList.add('hidden')); document.querySelectorAll('section').forEach(s => s.classList.add('hidden'));
document.getElementById('section-dynamic').classList.remove('hidden'); document.getElementById('section-dynamic').classList.remove('hidden');
@@ -694,7 +652,6 @@
document.getElementById('dynamic-title').textContent = cat.name + '管理'; document.getElementById('dynamic-title').textContent = cat.name + '管理';
// 渲染子类别筛选按钮
if (cat.subcategories && cat.subcategories.length > 0) { if (cat.subcategories && cat.subcategories.length > 0) {
document.getElementById('dynamic-filter-area').classList.remove('hidden'); document.getElementById('dynamic-filter-area').classList.remove('hidden');
renderSubcategoryFilters(categoryId, 'dynamic-subcategory-filters', 'filterDynamicBySubcategory'); renderSubcategoryFilters(categoryId, 'dynamic-subcategory-filters', 'filterDynamicBySubcategory');
@@ -702,42 +659,50 @@
document.getElementById('dynamic-filter-area').classList.add('hidden'); 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`);
let items = await res.json(); let items = await res.json();
// 子类别筛选
if (dynamicSubcategoryFilter) { if (dynamicSubcategoryFilter) {
items = items.filter(i => i.subcategory_id === 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', '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) { if (cat.subcategories && cat.subcategories.length > 0) {
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-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>`; }); 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-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) { if (cat.subcategories && cat.subcategories.length > 0) {
const subName = getSubcategoryName(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="${getSubcategoryIcon(categoryId, item.subcategory_id)} mr-1"></i>${getSubcategoryName(categoryId, item.subcategory_id)}</span>` : '<span class="text-gray-400">-</span>'}</td>`;
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>`; }); displayFields.forEach(f => {
const value = item[f.key];
let displayValue = '-';
if (value !== null && value !== undefined && value !== '') {
if (f.type === 'boolean') {
displayValue = value ? '<span class="text-green-600">是</span>' : '<span class="text-gray-400">否</span>';
} else if (f.type === 'json') {
displayValue = typeof value === 'object' ? JSON.stringify(value).substring(0, 30) + '...' : String(value).substring(0, 30);
} else {
displayValue = String(value).substring(0, 30);
}
}
html += `<td class="px-4 py-3 text-gray-600">${displayValue}</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">
<i class="${item.visible === false ? 'ri-eye-off-line' : 'ri-eye-line'}"></i> <i class="${item.visible === false ? 'ri-eye-off-line' : 'ri-eye-line'}"></i>
</button> </button>
${item.raw_text ? `<button onclick="showRawData('${item.id}', 'dynamic')" class="text-gray-400 hover:text-gray-600 ml-1" title="查看原始数据"><i class="ri-file-text-line"></i></button>` : ''} ${item.raw_text ? `<button onclick="showRawData('${item.id}', 'dynamic')" class="text-gray-400 hover:text-gray-600 ml-1"><i class="ri-file-text-line"></i></button>` : ''}
</td>`; </td>`;
html += `<td class="px-4 py-3 text-center"> html += `<td class="px-4 py-3 text-center">
<button onclick="editDynamicItem('${item.id}')" class="text-blue-600 hover:text-blue-800 mr-2"><i class="ri-edit-line"></i></button> <button onclick="editDynamicItem('${item.id}')" class="text-blue-600 hover:text-blue-800 mr-2"><i class="ri-edit-line"></i></button>
@@ -928,6 +893,12 @@
// 加载模型列表 // 加载模型列表
async function loadAdminModels() { async function loadAdminModels() {
renderSubcategoryFilters('ai-models', 'model-subcategory-filters', 'filterModelsBySubcategory'); renderSubcategoryFilters('ai-models', 'model-subcategory-filters', 'filterModelsBySubcategory');
// 获取类别字段配置
const cat = categories.find(c => c.id === 'ai-models');
const fields = cat ? (cat.fields || []) : [];
const subcats = cat ? (cat.subcategories || []) : [];
const res = await fetch('/api/models?all=1'); const res = await fetch('/api/models?all=1');
let models = await res.json(); let models = await res.json();
@@ -936,36 +907,80 @@
models = models.filter(m => m.subcategory_id === 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; } if (models.length === 0) {
document.getElementById('admin-models-table').innerHTML = models.map(m => ` document.getElementById('admin-models-table').innerHTML = '<tr><td colspan="20" class="text-center text-gray-400 py-8">暂无数据</td></tr>';
<tr class="border-b hover:bg-gray-50 ${m.visible === false ? 'bg-gray-100 opacity-60' : ''} ${m.is_pinned ? 'bg-yellow-50' : ''}"> return;
<td class="px-3 py-3 text-center"> }
<button onclick="togglePin('model', '${m.id}')" class="${m.is_pinned ? 'text-yellow-500' : 'text-gray-400'} hover:opacity-80" title="${m.is_pinned ? '取消置顶' : '置顶'}">
<i class="${m.is_pinned ? 'ri-pushpin-fill' : 'ri-pushpin-line'}"></i> // 动态生成表头
</button> const fixedFields = ['subcategory_id', 'id', 'images']; // 固定字段
</td> const displayFields = fields.filter(f => !fixedFields.includes(f.key));
<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> let headerHtml = `
<td class="px-3 py-3"> <thead class="bg-gray-50 border-b">
${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>'} <tr>
</td> <th class="px-3 py-3 text-left text-sm font-medium text-gray-600">置顶</th>
<td class="px-3 py-3">${m.parameters}B</td> <th class="px-3 py-3 text-left text-sm font-medium text-gray-600">名称</th>
<td class="px-3 py-3 text-gray-600">${m.context_length || '-'}</td> <th class="px-3 py-3 text-left text-sm font-medium text-gray-600">子类别</th>`;
<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> // 添加动态字段列只显示前6个关键字段
<td class="px-3 py-3 text-gray-500 text-sm">${m.views || 0}</td> displayFields.slice(0, 6).forEach(f => {
<td class="px-3 py-3 text-center"> headerHtml += `<th class="px-3 py-3 text-left text-sm font-medium text-gray-600">${f.label}</th>`;
<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>
</button> headerHtml += `
${m.raw_text ? `<button onclick="showRawData('${m.id}', 'model')" class="text-gray-400 hover:text-gray-600 ml-1" title="查看原始数据"><i class="ri-file-text-line"></i></button>` : ''} <th class="px-3 py-3 text-center text-sm font-medium text-gray-600">显示</th>
</td> <th class="px-3 py-3 text-center text-sm font-medium text-gray-600">操作</th>
<td class="px-3 py-3 text-center"> </tr>
<button onclick="editItem('model', '${m.id}')" class="text-blue-600 hover:text-blue-800 mr-2"><i class="ri-edit-line"></i></button> </thead>`;
<button onclick="deleteItem('model', '${m.id}')" class="text-red-600 hover:text-red-800"><i class="ri-delete-bin-line"></i></button>
</td> // 动态生成表格内容
</tr> let bodyHtml = '<tbody>';
`).join(''); models.forEach(m => {
bodyHtml += `
<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">
<button onclick="togglePin('model', '${m.id}')" class="${m.is_pinned ? 'text-yellow-500' : 'text-gray-400'} hover:opacity-80">
<i class="${m.is_pinned ? 'ri-pushpin-fill' : 'ri-pushpin-line'}"></i>
</button>
</td>
<td class="px-3 py-3 font-medium text-gray-800">${m.name || '-'}</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>`;
// 添加动态字段值
displayFields.slice(0, 6).forEach(f => {
const value = m[f.key];
let displayValue = '-';
if (value !== null && value !== undefined && value !== '') {
if (f.type === 'boolean') {
displayValue = value ? '<span class="text-green-600">是</span>' : '<span class="text-gray-400">否</span>';
} else if (f.type === 'number') {
displayValue = value;
} else {
displayValue = String(value).substring(0, 20);
}
}
bodyHtml += `<td class="px-3 py-3 text-gray-600">${displayValue}</td>`;
});
bodyHtml += `
<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">
<i class="${m.visible === false ? 'ri-eye-off-line' : 'ri-eye-line'}"></i>
</button>
${m.raw_text ? `<button onclick="showRawData('${m.id}', 'model')" class="text-gray-400 hover:text-gray-600 ml-1"><i class="ri-file-text-line"></i></button>` : ''}
</td>
<td class="px-3 py-3 text-center">
<button onclick="editItem('model', '${m.id}')" class="text-blue-600 hover:text-blue-800 mr-2"><i class="ri-edit-line"></i></button>
<button onclick="deleteItem('model', '${m.id}')" class="text-red-600 hover:text-red-800"><i class="ri-delete-bin-line"></i></button>
</td>
</tr>`;
});
bodyHtml += '</tbody>';
document.getElementById('admin-models-table').innerHTML = headerHtml + bodyHtml;
} }
function filterModelsBySubcategory(subId) { function filterModelsBySubcategory(subId) {
@@ -976,44 +991,68 @@
// 加载GPU列表 // 加载GPU列表
async function loadAdminGpus() { async function loadAdminGpus() {
renderSubcategoryFilters('gpus', 'gpu-subcategory-filters', 'filterGpusBySubcategory'); renderSubcategoryFilters('gpus', 'gpu-subcategory-filters', 'filterGpusBySubcategory');
const cat = categories.find(c => c.id === 'gpus');
const fields = cat ? (cat.fields || []) : [];
const fixedFields = ['subcategory_id', 'id', 'images'];
const displayFields = fields.filter(f => !fixedFields.includes(f.key));
const res = await fetch('/api/gpus?all=1'); const res = await fetch('/api/gpus?all=1');
let gpus = await res.json(); let gpus = await res.json();
// 子类别筛选
if (gpuSubcategoryFilter) { if (gpuSubcategoryFilter) {
gpus = gpus.filter(g => g.subcategory_id === gpuSubcategoryFilter); gpus = gpus.filter(g => g.subcategory_id === gpuSubcategoryFilter);
} }
if (gpus.length === 0) {
document.getElementById('admin-gpus-table').innerHTML = '<tr><td colspan="20" class="text-center text-gray-400 py-8">暂无数据</td></tr>';
return;
}
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; } let headerHtml = `<thead class="bg-gray-50 border-b"><tr>
document.getElementById('admin-gpus-table').innerHTML = gpus.map(g => ` <th class="px-3 py-3 text-left text-sm font-medium text-gray-600">置顶</th>
<tr class="border-b hover:bg-gray-50 ${g.visible === false ? 'bg-gray-100 opacity-60' : ''} ${g.is_pinned ? 'bg-yellow-50' : ''}"> <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>`;
displayFields.slice(0, 6).forEach(f => {
headerHtml += `<th class="px-3 py-3 text-left text-sm font-medium text-gray-600">${f.label}</th>`;
});
headerHtml += `<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></thead>`;
let bodyHtml = '<tbody>';
gpus.forEach(g => {
bodyHtml += `<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">
<button onclick="togglePin('gpu', '${g.id}')" class="${g.is_pinned ? 'text-yellow-500' : 'text-gray-400'} hover:opacity-80" title="${g.is_pinned ? '取消置顶' : '置顶'}"> <button onclick="togglePin('gpu', '${g.id}')" class="${g.is_pinned ? 'text-yellow-500' : 'text-gray-400'} hover:opacity-80">
<i class="${g.is_pinned ? 'ri-pushpin-fill' : 'ri-pushpin-line'}"></i> <i class="${g.is_pinned ? 'ri-pushpin-fill' : 'ri-pushpin-line'}"></i>
</button> </button>
</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"> <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>'} ${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>`;
<td class="px-3 py-3">${g.memory_gb}GB</td> displayFields.slice(0, 6).forEach(f => {
<td class="px-3 py-3 text-gray-600">${g.architecture || '-'}</td> const value = g[f.key];
<td class="px-3 py-3 text-gray-600">${formatPrice(g)}</td> let displayValue = '-';
<td class="px-3 py-3 text-gray-500 text-sm">${g.publish_date || '-'}</td> if (value !== null && value !== undefined && value !== '') {
<td class="px-3 py-3 text-gray-500 text-sm">${g.views || 0}</td> displayValue = f.type === 'boolean' ? (value ? '<span class="text-green-600">是</span>' : '<span class="text-gray-400">否</span>') : value;
<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 ? '点击显示' : '点击隐藏'}"> bodyHtml += `<td class="px-3 py-3 text-gray-600">${displayValue}</td>`;
});
bodyHtml += `<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">
<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>
</button> </button>
${g.raw_text ? `<button onclick="showRawData('${g.id}', 'gpu')" class="text-gray-400 hover:text-gray-600 ml-1" title="查看原始数据"><i class="ri-file-text-line"></i></button>` : ''} ${g.raw_text ? `<button onclick="showRawData('${g.id}', 'gpu')" class="text-gray-400 hover:text-gray-600 ml-1"><i class="ri-file-text-line"></i></button>` : ''}
</td> </td>
<td class="px-3 py-3 text-center"> <td class="px-3 py-3 text-center">
<button onclick="editItem('gpu', '${g.id}')" class="text-blue-600 hover:text-blue-800 mr-2"><i class="ri-edit-line"></i></button> <button onclick="editItem('gpu', '${g.id}')" class="text-blue-600 hover:text-blue-800 mr-2"><i class="ri-edit-line"></i></button>
<button onclick="deleteItem('gpu', '${g.id}')" class="text-red-600 hover:text-red-800"><i class="ri-delete-bin-line"></i></button> <button onclick="deleteItem('gpu', '${g.id}')" class="text-red-600 hover:text-red-800"><i class="ri-delete-bin-line"></i></button>
</td> </td></tr>`;
</tr> });
`).join(''); bodyHtml += '</tbody>';
document.getElementById('admin-gpus-table').innerHTML = headerHtml + bodyHtml;
} }
function filterGpusBySubcategory(subId) { function filterGpusBySubcategory(subId) {
@@ -1024,44 +1063,68 @@
// 加载CPU列表 // 加载CPU列表
async function loadAdminCpus() { async function loadAdminCpus() {
renderSubcategoryFilters('cpus', 'cpu-subcategory-filters', 'filterCpusBySubcategory'); renderSubcategoryFilters('cpus', 'cpu-subcategory-filters', 'filterCpusBySubcategory');
const cat = categories.find(c => c.id === 'cpus');
const fields = cat ? (cat.fields || []) : [];
const fixedFields = ['subcategory_id', 'id', 'images'];
const displayFields = fields.filter(f => !fixedFields.includes(f.key));
const res = await fetch('/api/cpus?all=1'); const res = await fetch('/api/cpus?all=1');
let cpus = await res.json(); let cpus = await res.json();
// 子类别筛选
if (cpuSubcategoryFilter) { if (cpuSubcategoryFilter) {
cpus = cpus.filter(c => c.subcategory_id === 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; } if (cpus.length === 0) {
document.getElementById('admin-cpus-table').innerHTML = cpus.map(c => ` document.getElementById('admin-cpus-table').innerHTML = '<tr><td colspan="20" class="text-center text-gray-400 py-8">暂无数据</td></tr>';
<tr class="border-b hover:bg-gray-50 ${c.visible === false ? 'bg-gray-100 opacity-60' : ''} ${c.is_pinned ? 'bg-yellow-50' : ''}"> return;
}
let headerHtml = `<thead class="bg-gray-50 border-b"><tr>
<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>`;
displayFields.slice(0, 6).forEach(f => {
headerHtml += `<th class="px-3 py-3 text-left text-sm font-medium text-gray-600">${f.label}</th>`;
});
headerHtml += `<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></thead>`;
let bodyHtml = '<tbody>';
cpus.forEach(c => {
bodyHtml += `<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">
<button onclick="togglePin('cpu', '${c.id}')" class="${c.is_pinned ? 'text-yellow-500' : 'text-gray-400'} hover:opacity-80" title="${c.is_pinned ? '取消置顶' : '置顶'}"> <button onclick="togglePin('cpu', '${c.id}')" class="${c.is_pinned ? 'text-yellow-500' : 'text-gray-400'} hover:opacity-80">
<i class="${c.is_pinned ? 'ri-pushpin-fill' : 'ri-pushpin-line'}"></i> <i class="${c.is_pinned ? 'ri-pushpin-fill' : 'ri-pushpin-line'}"></i>
</button> </button>
</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"> <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>'} ${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>`;
<td class="px-3 py-3">${c.cores}/${c.threads}</td> displayFields.slice(0, 6).forEach(f => {
<td class="px-3 py-3 text-gray-600">${c.base_clock_ghz || '-'}-${c.boost_clock_ghz || '-'}GHz</td> const value = c[f.key];
<td class="px-3 py-3 text-gray-600">${formatPrice(c)}</td> let displayValue = '-';
<td class="px-3 py-3 text-gray-500 text-sm">${c.publish_date || '-'}</td> if (value !== null && value !== undefined && value !== '') {
<td class="px-3 py-3 text-gray-500 text-sm">${c.views || 0}</td> displayValue = f.type === 'boolean' ? (value ? '<span class="text-green-600">是</span>' : '<span class="text-gray-400">否</span>') : value;
<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 ? '点击显示' : '点击隐藏'}"> bodyHtml += `<td class="px-3 py-3 text-gray-600">${displayValue}</td>`;
});
bodyHtml += `<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">
<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>
</button> </button>
${c.raw_text ? `<button onclick="showRawData('${c.id}', 'cpu')" class="text-gray-400 hover:text-gray-600 ml-1" title="查看原始数据"><i class="ri-file-text-line"></i></button>` : ''} ${c.raw_text ? `<button onclick="showRawData('${c.id}', 'cpu')" class="text-gray-400 hover:text-gray-600 ml-1"><i class="ri-file-text-line"></i></button>` : ''}
</td> </td>
<td class="px-3 py-3 text-center"> <td class="px-3 py-3 text-center">
<button onclick="editItem('cpu', '${c.id}')" class="text-blue-600 hover:text-blue-800 mr-2"><i class="ri-edit-line"></i></button> <button onclick="editItem('cpu', '${c.id}')" class="text-blue-600 hover:text-blue-800 mr-2"><i class="ri-edit-line"></i></button>
<button onclick="deleteItem('cpu', '${c.id}')" class="text-red-600 hover:text-red-800"><i class="ri-delete-bin-line"></i></button> <button onclick="deleteItem('cpu', '${c.id}')" class="text-red-600 hover:text-red-800"><i class="ri-delete-bin-line"></i></button>
</td> </td></tr>`;
</tr> });
`).join(''); bodyHtml += '</tbody>';
document.getElementById('admin-cpus-table').innerHTML = headerHtml + bodyHtml;
} }
function filterCpusBySubcategory(subId) { function filterCpusBySubcategory(subId) {
@@ -1782,23 +1845,64 @@
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>`; 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 generateFormFields(categoryId, data = {}, subcategoryId = '') {
const cat = categories.find(c => c.id === categoryId);
if (!cat) return '';
const fields = cat.fields || [];
const fixedFields = ['id', 'subcategory_id', 'images', 'visible', 'created_at', 'updated_at', 'raw_text', 'is_pinned', 'views'];
// 合并子类别额外字段
let allFields = fields;
if (subcategoryId) {
const subcat = cat.subcategories?.find(s => s.id === subcategoryId);
if (subcat && subcat.extra_fields) {
allFields = [...fields, ...subcat.extra_fields];
}
}
const formFields = allFields.filter(f => !fixedFields.includes(f.key));
return formFields.map(field => {
const value = data[field.key] || '';
const required = field.required ? 'required' : '';
const requiredMark = field.required ? '<span class="text-red-500">*</span>' : '';
const desc = field.description ? `<p class="text-xs text-gray-400 mt-1">${field.description}</p>` : '';
let inputHtml = '';
if (field.type === 'boolean') {
inputHtml = `<select name="${field.key}" class="w-full px-3 py-2 border rounded-lg" ${required}>
<option value="true" ${value === true ? 'selected' : ''}>是</option>
<option value="false" ${value === false || !value ? 'selected' : ''}>否</option>
</select>`;
} else if (field.type === 'number') {
inputHtml = `<input type="number" name="${field.key}" value="${value}" step="any" class="w-full px-3 py-2 border rounded-lg" ${required}>`;
} else if (field.type === 'date') {
inputHtml = `<input type="date" name="${field.key}" value="${value}" class="w-full px-3 py-2 border rounded-lg" ${required}>`;
} else if (field.type === 'json') {
inputHtml = `<textarea name="${field.key}" rows="3" class="w-full px-3 py-2 border rounded-lg font-mono text-sm" ${required}>${typeof value === 'object' ? JSON.stringify(value, null, 2) : value}</textarea>`;
} else if (field.type === 'url') {
inputHtml = `<input type="url" name="${field.key}" value="${value}" class="w-full px-3 py-2 border rounded-lg" ${required}>`;
} else {
inputHtml = `<input type="text" name="${field.key}" value="${value}" class="w-full px-3 py-2 border rounded-lg" ${required}>`;
}
return `<div><label class="text-sm text-gray-600 mb-1 block">${field.label}${requiredMark}</label>${inputHtml}${desc}</div>`;
}).join('');
}
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); const subcategorySelect = getSubcategorySelect(dynamicCategoryId, data.subcategory_id);
const formFields = generateFormFields(dynamicCategoryId, data, 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} ${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> ${formFields}
<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="year" value="${data.year || ''}" 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="date" name="publish_date" value="${data.publish_date || ''}" 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="views" value="${data.views || 0}" class="w-full px-3 py-2 border rounded-lg"></div>
</div> </div>
<div><label class="text-sm text-gray-600 mb-1 block">参数JSON格式</label><textarea name="specs" rows="4" class="w-full px-3 py-2 border rounded-lg font-mono text-sm" placeholder='{"key": "value"}'>${data.specs || ''}</textarea></div>
<div><label class="text-sm text-gray-600 mb-1 block">描述</label><textarea name="description" rows="2" class="w-full px-3 py-2 border rounded-lg">${data.description || ''}</textarea></div>
${getImageUploadComponent(currentImages, 'dynamic')} ${getImageUploadComponent(currentImages, 'dynamic')}
</form>`; </form>`;
} }
@@ -1806,25 +1910,13 @@
function getModelForm(data = {}) { function getModelForm(data = {}) {
currentImages = data.images || []; currentImages = data.images || [];
const subcategorySelect = getSubcategorySelect('ai-models', data.subcategory_id); const subcategorySelect = getSubcategorySelect('ai-models', data.subcategory_id);
const formFields = generateFormFields('ai-models', data, 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} ${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> ${formFields}
<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">架构</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="number" name="context_length" value="${data.context_length || ''}" class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">类型</label><select name="is_open_source" class="w-full px-3 py-2 border rounded-lg"><option value="false" ${!data.is_open_source ? 'selected' : ''}>商业</option><option value="true" ${data.is_open_source ? 'selected' : ''}>开源</option></select></div>
<div><label class="text-sm text-gray-600 mb-1 block">MMLU分数</label><input type="number" name="mmlu" value="${data.mmlu || ''}" step="0.1" class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">HumanEval分数</label><input type="number" name="humaneval" value="${data.humaneval || ''}" step="0.1" class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">输入价格($/1K)</label><input type="number" name="input_price" value="${data.input_price || ''}" step="0.001" class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">输出价格($/1K)</label><input type="number" name="output_price" value="${data.output_price || ''}" step="0.001" 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="license" value="${data.license || ''}" 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="date" name="publish_date" value="${data.publish_date || ''}" 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="views" value="${data.views || 0}" class="w-full px-3 py-2 border rounded-lg"></div>
</div> </div>
<div><label class="text-sm text-gray-600 mb-1 block">描述</label><textarea name="description" rows="3" class="w-full px-3 py-2 border rounded-lg">${data.description || ''}</textarea></div>
${getImageUploadComponent(currentImages, 'model')} ${getImageUploadComponent(currentImages, 'model')}
</form>`; </form>`;
} }
@@ -1832,31 +1924,13 @@
function getGpuForm(data = {}) { function getGpuForm(data = {}) {
currentImages = data.images || []; currentImages = data.images || [];
const subcategorySelect = getSubcategorySelect('gpus', data.subcategory_id); const subcategorySelect = getSubcategorySelect('gpus', data.subcategory_id);
const formFields = generateFormFields('gpus', data, 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} ${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> ${formFields}
<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">显存(GB) *</label><input type="number" name="memory_gb" value="${data.memory_gb || ''}" required class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">CUDA核心</label><input type="number" name="cuda_cores" value="${data.cuda_cores || ''}" class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">Tensor核心</label><input type="number" name="tensor_cores" value="${data.tensor_cores || ''}" class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">显存带宽(GB/s)</label><input type="number" name="memory_bandwidth_gbs" value="${data.memory_bandwidth_gbs || ''}" class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">FP16性能(TF)</label><input type="number" name="fp16_tflops" value="${data.fp16_tflops || ''}" step="0.1" class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">币种</label><select name="currency" class="w-full px-3 py-2 border rounded-lg">
<option value="USD" ${data.currency === 'USD' || !data.currency ? 'selected' : ''}>美元 (USD)</option>
<option value="CNY" ${data.currency === 'CNY' ? 'selected' : ''}>人民币 (CNY)</option>
<option value="EUR" ${data.currency === 'EUR' ? 'selected' : ''}>欧元 (EUR)</option>
</select></div>
<div><label class="text-sm text-gray-600 mb-1 block">价格</label><input type="number" name="price_usd" value="${data.price_usd || ''}" 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="min_price" value="${data.min_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="max_price" value="${data.max_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="text" name="price_unit" value="${data.price_unit || ''}" placeholder="如: 万" 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="date" name="publish_date" value="${data.publish_date || ''}" 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="views" value="${data.views || 0}" class="w-full px-3 py-2 border rounded-lg"></div>
</div> </div>
<div><label class="text-sm text-gray-600 mb-1 block">描述</label><textarea name="description" rows="3" class="w-full px-3 py-2 border rounded-lg">${data.description || ''}</textarea></div>
${getImageUploadComponent(currentImages, 'gpu')} ${getImageUploadComponent(currentImages, 'gpu')}
</form>`; </form>`;
} }
@@ -1864,32 +1938,13 @@
function getCpuForm(data = {}) { function getCpuForm(data = {}) {
currentImages = data.images || []; currentImages = data.images || [];
const subcategorySelect = getSubcategorySelect('cpus', data.subcategory_id); const subcategorySelect = getSubcategorySelect('cpus', data.subcategory_id);
const formFields = generateFormFields('cpus', data, 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} ${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> ${formFields}
<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="number" name="cores" value="${data.cores || ''}" 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="number" name="threads" value="${data.threads || ''}" required class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">基础频率(GHz)</label><input type="number" name="base_clock_ghz" value="${data.base_clock_ghz || ''}" step="0.1" class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">加速频率(GHz)</label><input type="number" name="boost_clock_ghz" value="${data.boost_clock_ghz || ''}" step="0.1" class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">L3缓存(MB)</label><input type="number" name="l3_cache_mb" value="${data.l3_cache_mb || ''}" class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">TDP功耗(W)</label><input type="number" name="tdp_watts" value="${data.tdp_watts || ''}" class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">币种</label><select name="currency" class="w-full px-3 py-2 border rounded-lg">
<option value="USD" ${data.currency === 'USD' || !data.currency ? 'selected' : ''}>美元 (USD)</option>
<option value="CNY" ${data.currency === 'CNY' ? 'selected' : ''}>人民币 (CNY)</option>
<option value="EUR" ${data.currency === 'EUR' ? 'selected' : ''}>欧元 (EUR)</option>
</select></div>
<div><label class="text-sm text-gray-600 mb-1 block">价格</label><input type="number" name="price_usd" value="${data.price_usd || ''}" 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="min_price" value="${data.min_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="max_price" value="${data.max_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="text" name="price_unit" value="${data.price_unit || ''}" placeholder="如: 万" 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="date" name="publish_date" value="${data.publish_date || ''}" 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="views" value="${data.views || 0}" class="w-full px-3 py-2 border rounded-lg"></div>
</div> </div>
<div><label class="text-sm text-gray-600 mb-1 block">描述</label><textarea name="description" rows="3" class="w-full px-3 py-2 border rounded-lg">${data.description || ''}</textarea></div>
${getImageUploadComponent(currentImages, 'cpu')} ${getImageUploadComponent(currentImages, 'cpu')}
</form>`; </form>`;
} }
@@ -2137,7 +2192,6 @@
text: text, text: text,
images: smartAddImages, images: smartAddImages,
subcategory_id: subcategoryId subcategory_id: subcategoryId
images: smartAddImages
}) })
}); });