2 Commits

View File

@@ -130,6 +130,9 @@
<h1 class="text-2xl font-bold text-gray-800">分类管理</h1>
<button onclick="openAddModal('category')" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700"><i class="ri-add-line mr-2"></i>添加分类</button>
</div>
<div class="bg-blue-50 rounded-lg p-4 mb-4">
<p class="text-sm text-blue-700"><i class="ri-information-line mr-1"></i>内置分类AI模型、GPU、CPU的子类别配置可在此编辑其数据管理入口在左侧导航栏的独立页面。</p>
</div>
<div class="bg-white rounded-xl shadow-sm overflow-hidden">
<table class="w-full">
<thead class="bg-gray-50 border-b">
@@ -137,8 +140,8 @@
<th class="px-4 py-3 text-left text-sm font-medium text-gray-600">图标</th>
<th class="px-4 py-3 text-left text-sm font-medium text-gray-600">ID</th>
<th class="px-4 py-3 text-left text-sm font-medium text-gray-600">名称</th>
<th class="px-4 py-3 text-left text-sm font-medium text-gray-600">描述</th>
<th class="px-4 py-3 text-center text-sm font-medium text-gray-600">显示</th>
<th class="px-4 py-3 text-left text-sm font-medium text-gray-600">类型</th>
<th class="px-4 py-3 text-left text-sm font-medium text-gray-600">子类别</th>
<th class="px-4 py-3 text-center text-sm font-medium text-gray-600">操作</th>
</tr>
</thead>
@@ -446,6 +449,11 @@
let currentFilter = '';
let dynamicCategoryId = '';
// 生成随机ID12位十六进制
function generateId() {
return Math.random().toString(16).slice(2, 8) + Math.random().toString(16).slice(2, 8);
}
const colorMap = {
blue: 'bg-blue-100 text-blue-600',
green: 'bg-green-100 text-green-600',
@@ -754,6 +762,9 @@
: '<div class="text-gray-400">暂无数据</div>';
}
// 内置分类列表
const builtinCategories = ['ai-models', 'gpus', 'cpus'];
// 加载分类列表
async function loadAdminCategories() {
const res = await fetch('/api/categories?all=1');
@@ -764,23 +775,27 @@
return;
}
document.getElementById('admin-categories-table').innerHTML = categories.map(c => `
<tr class="border-b hover:bg-gray-50 ${c.visible === false ? 'bg-gray-100 opacity-60' : ''}">
document.getElementById('admin-categories-table').innerHTML = categories.map(c => {
const isBuiltin = builtinCategories.includes(c.id);
const subcatCount = (c.subcategories || []).length;
return `
<tr class="border-b hover:bg-gray-50 ${c.visible === false ? 'bg-gray-100 opacity-60' : ''} ${isBuiltin ? 'bg-indigo-50' : ''}">
<td class="px-4 py-3"><div class="w-10 h-10 rounded-lg ${colorMap[c.color] || 'bg-gray-100 text-gray-600'} flex items-center justify-center"><i class="${c.icon} text-xl"></i></div></td>
<td class="px-4 py-3 text-gray-500 text-sm font-mono">${c.id}</td>
<td class="px-4 py-3 font-medium text-gray-800">${c.name}</td>
<td class="px-4 py-3 text-gray-600 text-sm">${c.description || '-'}</td>
<td class="px-4 py-3 text-center">
<button onclick="toggleVisible('category', '${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>
</button>
<td class="px-4 py-3 text-sm">
${isBuiltin ? '<span class="px-2 py-1 bg-indigo-100 text-indigo-600 rounded text-xs">内置</span>' : '<span class="text-gray-500">自定义</span>'}
</td>
<td class="px-4 py-3 text-sm">
${subcatCount > 0 ? `<span class="px-2 py-1 bg-green-100 text-green-600 rounded text-xs">${subcatCount} 个</span>` : '<span class="text-gray-400">无</span>'}
</td>
<td class="px-4 py-3 text-center">
<button onclick="editItem('category', '${c.id}')" class="text-blue-600 hover:text-blue-800 mr-2"><i class="ri-edit-line"></i></button>
<button onclick="deleteItem('category', '${c.id}')" class="text-red-600 hover:text-red-800"><i class="ri-delete-bin-line"></i></button>
<button onclick="editItem('category', '${c.id}')" class="text-blue-600 hover:text-blue-800 mr-2" title="编辑子类别"><i class="ri-edit-line"></i></button>
${!isBuiltin ? `<button onclick="deleteItem('category', '${c.id}')" class="text-red-600 hover:text-red-800" title="删除"><i class="ri-delete-bin-line"></i></button>` : '<span class="text-gray-300 cursor-not-allowed"><i class="ri-delete-bin-line"></i></span>'}
</td>
</tr>
`).join('');
`;
}).join('');
}
// 加载模型列表
@@ -1176,14 +1191,53 @@
// 表单模板
function getCategoryForm(data = {}) {
const subcategories = data.subcategories || [];
const isBuiltin = builtinCategories.includes(data.id);
// 存储到全局变量,便于管理
window.currentEditingSubcategories = JSON.parse(JSON.stringify(subcategories));
// 内置类别只显示子类别管理
if (isBuiltin) {
return `<form id="itemForm" class="space-y-4">
<div class="bg-indigo-50 rounded-lg p-4 mb-4">
<p class="text-sm text-indigo-700"><i class="ri-information-line mr-1"></i>内置分类的基础信息不可修改,只可编辑子类别配置。</p>
</div>
<div class="grid grid-cols-2 gap-4 bg-gray-50 p-4 rounded-lg">
<div><label class="text-sm text-gray-500 mb-1 block">ID</label><div class="text-gray-700 font-mono">${data.id}</div></div>
<div><label class="text-sm text-gray-500 mb-1 block">名称</label><div class="text-gray-700">${data.name}</div></div>
<div><label class="text-sm text-gray-500 mb-1 block">图标</label><div class="text-gray-700"><i class="${data.icon} mr-1"></i>${data.icon}</div></div>
<div><label class="text-sm text-gray-500 mb-1 block">颜色</label><div class="text-gray-700">${data.color}</div></div>
</div>
<input type="hidden" name="id" value="${data.id}">
<input type="hidden" name="name" value="${data.name}">
<input type="hidden" name="icon" value="${data.icon}">
<input type="hidden" name="color" value="${data.color}">
<input type="hidden" name="order" value="${data.order || 0}">
<input type="hidden" name="visible" value="${data.visible !== false ? 'true' : 'false'}">
<input type="hidden" name="description" value="${data.description || ''}">
<!-- 子类别管理 -->
<div class="border-t pt-4">
<div class="flex justify-between items-center mb-3">
<label class="text-sm font-medium text-gray-700"><i class="ri-folder-line mr-1"></i>子类别管理</label>
<button onclick="openSubcategoryAddModal()" class="px-3 py-1.5 bg-green-600 text-white rounded-lg text-sm hover:bg-green-700">
<i class="ri-add-line mr-1"></i>添加子类别
</button>
</div>
<div id="subcategoriesList" class="space-y-2">
${renderSubcategoriesList(subcategories)}
</div>
<input type="hidden" name="subcategories" id="subcategoriesHidden" value='${JSON.stringify(subcategories)}'>
</div>
</form>`;
}
// 自定义类别完整编辑表单
const autoId = data.id || generateId();
return `<form id="itemForm" class="space-y-4">
<div class="grid grid-cols-2 gap-4">
<div><label class="text-sm text-gray-600 mb-1 block">ID *</label><input type="text" name="id" value="${data.id || ''}" ${data.id ? 'readonly' : ''} required class="w-full px-3 py-2 border rounded-lg ${data.id ? 'bg-gray-100' : ''}"></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="icon" value="${data.icon || 'ri-folder-line'}" class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">ID</label><input type="text" name="id" value="${autoId}" readonly class="w-full px-3 py-2 border rounded-lg bg-gray-100 text-gray-500 font-mono text-xs"><p class="text-xs text-gray-400 mt-1">自动生成,无需填写</p></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" placeholder="如:手机、电脑"></div>
<div><label class="text-sm text-gray-600 mb-1 block">图标</label><input type="text" name="icon" value="${data.icon || 'ri-folder-line'}" class="w-full px-3 py-2 border rounded-lg" placeholder="ri-folder-line"></div>
<div><label class="text-sm text-gray-600 mb-1 block">颜色</label><select name="color" class="w-full px-3 py-2 border rounded-lg">
<option value="blue" ${data.color === 'blue' ? 'selected' : ''}>蓝色</option>
<option value="green" ${data.color === 'green' ? 'selected' : ''}>绿色</option>
@@ -1198,7 +1252,7 @@
<option value="false" ${data.visible === false ? 'selected' : ''}>隐藏</option>
</select></div>
</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>
<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" placeholder="分类描述">${data.description || ''}</textarea></div>
<!-- 子类别管理 -->
<div class="border-t pt-4">
@@ -1275,11 +1329,12 @@
const keyFeatures = (data.key_features || []).join(', ');
const featureLabels = data.feature_labels || {};
const featureLabelsStr = Object.entries(featureLabels).map(([k, v]) => `${k}:${v}`).join(', ');
const autoSubId = data.id || generateId();
return `<div class="space-y-4">
<div class="grid grid-cols-2 gap-4">
<div><label class="text-sm text-gray-600 mb-1 block">ID *</label><input type="text" id="sub_id" value="${data.id || ''}" 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" id="sub_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">ID</label><input type="text" id="sub_id" value="${autoSubId}" readonly class="w-full px-3 py-2 border rounded-lg bg-gray-100 text-gray-500 font-mono text-xs"><p class="text-xs text-gray-400 mt-1">自动生成</p></div>
<div><label class="text-sm text-gray-600 mb-1 block">名称 *</label><input type="text" id="sub_name" value="${data.name || ''}" required class="w-full px-3 py-2 border rounded-lg" placeholder="如:旗舰手机"></div>
<div><label class="text-sm text-gray-600 mb-1 block">图标</label><input type="text" id="sub_icon" value="${data.icon || 'ri-folder-line'}" class="w-full px-3 py-2 border rounded-lg" placeholder="ri-folder-line"></div>
</div>
<div>
@@ -1303,11 +1358,13 @@
const keyFeaturesStr = document.getElementById('sub_key_features').value.trim();
const featureLabelsStr = document.getElementById('sub_feature_labels').value.trim();
if (!id || !name) {
alert('ID和名称不能为空');
if (!name) {
alert('名称不能为空');
return;
}
// ID自动生成无需校验
// 解析 key_features
const key_features = keyFeaturesStr ? keyFeaturesStr.split(',').map(s => s.trim()).filter(s => s) : [];