Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 35df07725e | |||
| 867a0a3eaf |
@@ -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 = '';
|
||||
|
||||
// 生成随机ID(12位十六进制)
|
||||
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) : [];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user