Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4b5e70a3bf | |||
| ea60d4b4c6 |
@@ -185,23 +185,8 @@
|
||||
<div id="model-subcategory-filters" class="flex gap-2"></div>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl shadow-sm overflow-x-auto">
|
||||
<table class="w-full min-w-[1200px]">
|
||||
<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>
|
||||
<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 class="w-full" id="admin-models-table">
|
||||
<tbody><tr><td colspan="20" class="text-center text-gray-400 py-8">加载中...</td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
@@ -221,23 +206,8 @@
|
||||
<div id="gpu-subcategory-filters" class="flex gap-2"></div>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl shadow-sm overflow-x-auto">
|
||||
<table class="w-full min-w-[1200px]">
|
||||
<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>
|
||||
<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 class="w-full" id="admin-gpus-table">
|
||||
<tbody><tr><td colspan="20" class="text-center text-gray-400 py-8">加载中...</td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
@@ -257,23 +227,8 @@
|
||||
<div id="cpu-subcategory-filters" class="flex gap-2"></div>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl shadow-sm overflow-x-auto">
|
||||
<table class="w-full min-w-[1200px]">
|
||||
<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>
|
||||
<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 class="w-full" id="admin-cpus-table">
|
||||
<tbody><tr><td colspan="20" class="text-center text-gray-400 py-8">加载中...</td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
@@ -676,8 +631,11 @@
|
||||
// 显示动态分类数据
|
||||
async function showDynamicCategory(categoryId) {
|
||||
dynamicCategoryId = categoryId;
|
||||
dynamicSubcategoryFilter = ''; // 重置筛选
|
||||
dynamicSubcategoryFilter = '';
|
||||
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.getElementById('section-dynamic').classList.remove('hidden');
|
||||
@@ -694,7 +652,6 @@
|
||||
|
||||
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');
|
||||
@@ -702,42 +659,50 @@
|
||||
document.getElementById('dynamic-filter-area').classList.add('hidden');
|
||||
}
|
||||
|
||||
// 加载该分类的数据(后台显示全部,包括隐藏的)
|
||||
const res = await fetch(`/api/items/${categoryId}?all=1`);
|
||||
let items = await res.json();
|
||||
|
||||
// 子类别筛选
|
||||
if (dynamicSubcategoryFilter) {
|
||||
items = items.filter(i => i.subcategory_id === dynamicSubcategoryFilter);
|
||||
}
|
||||
|
||||
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 {
|
||||
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>`;
|
||||
// 添加子类别列
|
||||
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>`; });
|
||||
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></tr></thead><tbody>`;
|
||||
|
||||
items.forEach(item => {
|
||||
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>`;
|
||||
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>`;
|
||||
}
|
||||
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">
|
||||
<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>
|
||||
</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>`;
|
||||
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>
|
||||
@@ -928,6 +893,12 @@
|
||||
// 加载模型列表
|
||||
async function loadAdminModels() {
|
||||
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');
|
||||
let models = await res.json();
|
||||
|
||||
@@ -936,36 +907,80 @@
|
||||
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 => `
|
||||
<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" title="${m.is_pinned ? '取消置顶' : '置顶'}">
|
||||
<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 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 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 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-center">
|
||||
<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>
|
||||
${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>` : ''}
|
||||
</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>
|
||||
`).join('');
|
||||
if (models.length === 0) {
|
||||
document.getElementById('admin-models-table').innerHTML = '<tr><td colspan="20" class="text-center text-gray-400 py-8">暂无数据</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
// 动态生成表头
|
||||
const fixedFields = ['subcategory_id', 'id', 'images']; // 固定字段
|
||||
const displayFields = fields.filter(f => !fixedFields.includes(f.key));
|
||||
|
||||
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>`;
|
||||
|
||||
// 添加动态字段列(只显示前6个关键字段)
|
||||
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>';
|
||||
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) {
|
||||
@@ -976,44 +991,68 @@
|
||||
// 加载GPU列表
|
||||
async function loadAdminGpus() {
|
||||
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');
|
||||
let gpus = await res.json();
|
||||
|
||||
// 子类别筛选
|
||||
if (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; }
|
||||
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' : ''}">
|
||||
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>';
|
||||
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">
|
||||
<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>
|
||||
</button>
|
||||
</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 font-medium text-gray-800">${g.name || '-'}</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 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-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-center">
|
||||
<button onclick="toggleVisible('gpu', '${g.id}')" class="${g.visible === false ? 'text-gray-400' : 'text-green-600'} hover:opacity-80" title="${g.visible === false ? '点击显示' : '点击隐藏'}">
|
||||
</td>`;
|
||||
displayFields.slice(0, 6).forEach(f => {
|
||||
const value = g[f.key];
|
||||
let displayValue = '-';
|
||||
if (value !== null && value !== undefined && value !== '') {
|
||||
displayValue = f.type === 'boolean' ? (value ? '<span class="text-green-600">是</span>' : '<span class="text-gray-400">否</span>') : value;
|
||||
}
|
||||
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>
|
||||
</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 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="deleteItem('gpu', '${g.id}')" class="text-red-600 hover:text-red-800"><i class="ri-delete-bin-line"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
</td></tr>`;
|
||||
});
|
||||
bodyHtml += '</tbody>';
|
||||
|
||||
document.getElementById('admin-gpus-table').innerHTML = headerHtml + bodyHtml;
|
||||
}
|
||||
|
||||
function filterGpusBySubcategory(subId) {
|
||||
@@ -1024,44 +1063,68 @@
|
||||
// 加载CPU列表
|
||||
async function loadAdminCpus() {
|
||||
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');
|
||||
let cpus = await res.json();
|
||||
|
||||
// 子类别筛选
|
||||
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 => `
|
||||
<tr class="border-b hover:bg-gray-50 ${c.visible === false ? 'bg-gray-100 opacity-60' : ''} ${c.is_pinned ? 'bg-yellow-50' : ''}">
|
||||
if (cpus.length === 0) {
|
||||
document.getElementById('admin-cpus-table').innerHTML = '<tr><td colspan="20" class="text-center text-gray-400 py-8">暂无数据</td></tr>';
|
||||
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">
|
||||
<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>
|
||||
</button>
|
||||
</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 font-medium text-gray-800">${c.name || '-'}</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 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-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-center">
|
||||
<button onclick="toggleVisible('cpu', '${c.id}')" class="${c.visible === false ? 'text-gray-400' : 'text-green-600'} hover:opacity-80" title="${c.visible === false ? '点击显示' : '点击隐藏'}">
|
||||
</td>`;
|
||||
displayFields.slice(0, 6).forEach(f => {
|
||||
const value = c[f.key];
|
||||
let displayValue = '-';
|
||||
if (value !== null && value !== undefined && value !== '') {
|
||||
displayValue = f.type === 'boolean' ? (value ? '<span class="text-green-600">是</span>' : '<span class="text-gray-400">否</span>') : value;
|
||||
}
|
||||
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>
|
||||
</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 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="deleteItem('cpu', '${c.id}')" class="text-red-600 hover:text-red-800"><i class="ri-delete-bin-line"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
</td></tr>`;
|
||||
});
|
||||
bodyHtml += '</tbody>';
|
||||
|
||||
document.getElementById('admin-cpus-table').innerHTML = headerHtml + bodyHtml;
|
||||
}
|
||||
|
||||
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>`;
|
||||
}
|
||||
|
||||
// 根据类别字段配置动态生成表单字段
|
||||
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 = {}) {
|
||||
const cat = categories.find(c => c.id === dynamicCategoryId);
|
||||
currentImages = data.images || [];
|
||||
const subcategorySelect = getSubcategorySelect(dynamicCategoryId, data.subcategory_id);
|
||||
const formFields = generateFormFields(dynamicCategoryId, data, data.subcategory_id);
|
||||
|
||||
return `<form id="itemForm" class="space-y-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="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>
|
||||
${formFields}
|
||||
</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')}
|
||||
</form>`;
|
||||
}
|
||||
@@ -1806,25 +1910,13 @@
|
||||
function getModelForm(data = {}) {
|
||||
currentImages = data.images || [];
|
||||
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">
|
||||
<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="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>
|
||||
${formFields}
|
||||
</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')}
|
||||
</form>`;
|
||||
}
|
||||
@@ -1832,31 +1924,13 @@
|
||||
function getGpuForm(data = {}) {
|
||||
currentImages = data.images || [];
|
||||
const subcategorySelect = getSubcategorySelect('gpus', data.subcategory_id);
|
||||
const formFields = generateFormFields('gpus', data, data.subcategory_id);
|
||||
|
||||
return `<form id="itemForm" class="space-y-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="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>
|
||||
${formFields}
|
||||
</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')}
|
||||
</form>`;
|
||||
}
|
||||
@@ -1864,32 +1938,13 @@
|
||||
function getCpuForm(data = {}) {
|
||||
currentImages = data.images || [];
|
||||
const subcategorySelect = getSubcategorySelect('cpus', data.subcategory_id);
|
||||
const formFields = generateFormFields('cpus', data, data.subcategory_id);
|
||||
|
||||
return `<form id="itemForm" class="space-y-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="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>
|
||||
${formFields}
|
||||
</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')}
|
||||
</form>`;
|
||||
}
|
||||
@@ -2137,7 +2192,6 @@
|
||||
text: text,
|
||||
images: smartAddImages,
|
||||
subcategory_id: subcategoryId
|
||||
images: smartAddImages
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user