@@ -3,7 +3,9 @@
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
< title > 后台管理 - ParamHub< / title >
< title > ParamHub - 参数百科 < / title >
< link rel = "icon" type = "image/svg+xml" href = "/static/favicon.svg" >
< link rel = "icon" type = "image/svg+xml" href = "/static/favicon.svg" >
< script src = "https://cdn.tailwindcss.com" > < / script >
< link href = "https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel = "stylesheet" >
< style >
@@ -65,7 +67,7 @@
< 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-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-center text-sm font-medium text-gray-600" > 操作< / th >
< / tr >
< / thead >
@@ -78,7 +80,10 @@
< section id = "section-dynamic" class = "hidden" >
< div class = "flex justify-between items-center mb-6" >
< h1 class = "text-2xl font-bold text-gray-800" id = "dynamic-title" > 数据管理< / h1 >
< button onclick = "openAddModal('dynamic')" class = "px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700" > < i class = "ri-add-line mr-2" > < / i > 添加数据< / button >
< div class = "flex gap-2" >
< button onclick = "openSmartAddModal('dynamic')" class = "px-4 py-2 bg-orange-600 text-white rounded-lg hover:bg-orange-700" > < i class = "ri-magic-line mr-2" > < / i > 智能添加< / button >
< button onclick = "openAddModal('dynamic')" class = "px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700" > < i class = "ri-add-line mr-2" > < / i > 手动添加< / button >
< / div >
< / div >
< div class = "bg-white rounded-xl shadow-sm overflow-hidden" >
< table class = "w-full" > < tbody id = "admin-dynamic-table" > < tr > < td class = "text-center text-gray-400 py-8" > 加载中...< / td > < / tr > < / tbody > < / table >
@@ -89,7 +94,10 @@
< section id = "section-models" class = "hidden" >
< div class = "flex justify-between items-center mb-6" >
< h1 class = "text-2xl font-bold text-gray-800" > 模型管理< / h1 >
< button onclick = "openAddModal('model')" class = "px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700" > < i class = "ri-add-line mr-2" > < / i > 添加模型< / button >
< div class = "flex gap-2" >
< button onclick = "openSmartAddModal('model')" class = "px-4 py-2 bg-orange-600 text-white rounded-lg hover:bg-orange-700" > < i class = "ri-magic-line mr-2" > < / i > 智能添加< / button >
< button onclick = "openAddModal('model')" class = "px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700" > < i class = "ri-add-line mr-2" > < / i > 手动添加< / button >
< / div >
< / div >
< div class = "bg-white rounded-xl shadow-sm overflow-hidden" >
< table class = "w-full" >
@@ -100,10 +108,11 @@
< 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-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-center text-sm font-medium text-gray-600" > 操作< / th >
< / tr >
< / thead >
< tbody id = "admin-models-table" > < tr > < td colspan = "6 " class = "text-center text-gray-400 py-8" > 加载中...< / td > < / tr > < / tbody >
< tbody id = "admin-models-table" > < tr > < td colspan = "7 " class = "text-center text-gray-400 py-8" > 加载中...< / td > < / tr > < / tbody >
< / table >
< / div >
< / section >
@@ -112,7 +121,10 @@
< section id = "section-gpus" class = "hidden" >
< div class = "flex justify-between items-center mb-6" >
< h1 class = "text-2xl font-bold text-gray-800" > GPU管理< / h1 >
< button onclick = "openAddModal('gpu')" class = "px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700" > < i class = "ri-add-line mr-2" > < / i > 添加GPU< / button >
< div class = "flex gap-2" >
< button onclick = "openSmartAddModal('gpu')" class = "px-4 py-2 bg-orange-600 text-white rounded-lg hover:bg-orange-700" > < i class = "ri-magic-line mr-2" > < / i > 智能添加< / button >
< button onclick = "openAddModal('gpu')" class = "px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700" > < i class = "ri-add-line mr-2" > < / i > 手动添加< / button >
< / div >
< / div >
< div class = "bg-white rounded-xl shadow-sm overflow-hidden" >
< table class = "w-full" >
@@ -123,10 +135,11 @@
< 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-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-center text-sm font-medium text-gray-600" > 操作< / th >
< / tr >
< / thead >
< tbody id = "admin-gpus-table" > < tr > < td colspan = "6 " class = "text-center text-gray-400 py-8" > 加载中...< / td > < / tr > < / tbody >
< tbody id = "admin-gpus-table" > < tr > < td colspan = "7 " class = "text-center text-gray-400 py-8" > 加载中...< / td > < / tr > < / tbody >
< / table >
< / div >
< / section >
@@ -135,7 +148,10 @@
< section id = "section-cpus" class = "hidden" >
< div class = "flex justify-between items-center mb-6" >
< h1 class = "text-2xl font-bold text-gray-800" > CPU管理< / h1 >
< button onclick = "openAddModal('cpu')" class = "px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700" > < i class = "ri-add-line mr-2" > < / i > 添加CPU< / button >
< div class = "flex gap-2" >
< button onclick = "openSmartAddModal('cpu')" class = "px-4 py-2 bg-orange-600 text-white rounded-lg hover:bg-orange-700" > < i class = "ri-magic-line mr-2" > < / i > 智能添加< / button >
< button onclick = "openAddModal('cpu')" class = "px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700" > < i class = "ri-add-line mr-2" > < / i > 手动添加< / button >
< / div >
< / div >
< div class = "bg-white rounded-xl shadow-sm overflow-hidden" >
< table class = "w-full" >
@@ -146,10 +162,11 @@
< 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-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-center text-sm font-medium text-gray-600" > 操作< / th >
< / tr >
< / thead >
< tbody id = "admin-cpus-table" > < tr > < td colspan = "6 " class = "text-center text-gray-400 py-8" > 加载中...< / td > < / tr > < / tbody >
< tbody id = "admin-cpus-table" > < tr > < td colspan = "7 " class = "text-center text-gray-400 py-8" > 加载中...< / td > < / tr > < / tbody >
< / table >
< / div >
< / section >
@@ -200,6 +217,44 @@
< / div >
< / div >
<!-- 智能添加弹窗 -->
< div id = "smartAddModal" class = "fixed inset-0 bg-black/50 z-50 hidden flex items-center justify-center" >
< div class = "bg-white rounded-xl max-w-3xl w-full mx-4 max-h-[90vh] overflow-auto" >
< div class = "p-6 border-b flex justify-between items-center sticky top-0 bg-white z-10" >
< h2 class = "text-xl font-bold text-gray-800" > < i class = "ri-magic-line mr-2 text-orange-600" > < / i > 智能添加< / h2 >
< button onclick = "closeSmartAddModal()" class = "text-gray-400 hover:text-gray-600" > < i class = "ri-close-line text-2xl" > < / i > < / button >
< / div >
< div class = "p-6" >
< p class = "text-sm text-gray-500 mb-4" > 粘贴产品信息文本, AI将自动解析并提取结构化数据。支持各种格式的产品介绍、规格参数、价格信息等。< / p >
< textarea id = "smartAddText" rows = "8" class = "w-full p-4 border border-gray-200 rounded-lg focus:outline-none focus:border-orange-400 text-gray-700" placeholder = "粘贴产品信息文本...
示例:
GPT-4是OpenAI发布的大语言模型, 参数量约1.8万亿, 支持128K上下文, MMLU分数86.4,输入价格$0.03/1K tokens, 输出价格$0.06/1K tokens, 商业许可。" > < / textarea >
< div id = "smartAddPreview" class = "mt-4 hidden" >
< h3 class = "text-sm font-semibold text-gray-700 mb-2" > 解析结果预览:< / h3 >
< div class = "bg-gray-50 rounded-lg p-4 text-sm text-gray-600" id = "smartAddResult" > < / div >
< / div >
< / div >
< div class = "p-6 border-t flex justify-end gap-4 sticky bottom-0 bg-white" >
< button onclick = "closeSmartAddModal()" class = "px-4 py-2 bg-gray-200 text-gray-600 rounded-lg hover:bg-gray-300" > 取消< / button >
< button onclick = "smartAddSubmit()" id = "smartAddBtn" class = "px-4 py-2 bg-orange-600 text-white rounded-lg hover:bg-orange-700" > < i class = "ri-magic-line mr-1" > < / i > 智能解析并添加< / button >
< / div >
< / div >
< / div >
<!-- 原始数据查看弹窗 -->
< div id = "rawDataModal" class = "fixed inset-0 bg-black/50 z-50 hidden flex items-center justify-center" >
< div class = "bg-white rounded-xl max-w-3xl w-full mx-4 max-h-[90vh] overflow-auto" >
< div class = "p-6 border-b flex justify-between items-center sticky top-0 bg-white z-10" >
< h2 class = "text-xl font-bold text-gray-800" > < i class = "ri-file-text-line mr-2" > < / i > 原始数据< / h2 >
< button onclick = "closeRawDataModal()" class = "text-gray-400 hover:text-gray-600" > < i class = "ri-close-line text-2xl" > < / i > < / button >
< / div >
< div id = "rawDataContent" class = "p-6" >
< div class = "bg-gray-50 rounded-lg p-4 text-sm text-gray-700 whitespace-pre-wrap" id = "rawDataText" > < / div >
< / div >
< / div >
< / div >
< script >
let currentType = '' ;
let currentId = '' ;
@@ -217,6 +272,43 @@
red : 'bg-red-100 text-red-600'
} ;
// 价格格式化函数
function formatPrice ( item ) {
// 支持多种价格格式
const currency = item . currency || 'USD' ;
const symbols = { USD : '$' , CNY : '¥' , EUR : '€' , JPY : '¥' , GBP : '£' } ;
const symbol = symbols [ currency ] || currency ;
// 价格区间支持
const minPrice = item . min _price || item . price _usd _min || item . price _min ;
const maxPrice = item . max _price || item . price _usd _max || item . price _max ;
const singlePrice = item . price _usd || item . price _cny || item . price ;
// 单位处理
const unit = item . price _unit || '' ;
if ( minPrice && maxPrice ) {
// 价格区间
return ` ${ symbol } ${ formatNumber ( minPrice ) } - ${ formatNumber ( maxPrice ) } ${ unit ? ' ' + unit : '' } ` ;
} else if ( singlePrice ) {
return ` ${ symbol } ${ formatNumber ( singlePrice ) } ${ unit ? ' ' + unit : '' } ` ;
} else {
return '-' ;
}
}
// 数字格式化
function formatNumber ( num ) {
if ( ! num ) return '0' ;
if ( num >= 10000 ) {
return ( num / 10000 ) . toFixed ( 1 ) + '万' ;
}
if ( num >= 1000 ) {
return ( num / 1000 ) . toFixed ( 1 ) + 'K' ;
}
return num . toLocaleString ( ) ;
}
// 初始化
async function init ( ) {
await loadCategories ( ) ;
@@ -242,7 +334,7 @@
const builtinMap = {
'ai-models' : { id : 'models' , name : '模型管理' , icon : 'ri-robot-line' } ,
'gpus' : { id : 'gpus' , name : 'GPU管理' , icon : 'ri-cpu-line' } ,
'cpus' : { id : 'cpus' , name : 'CPU管理' , icon : 'ri-memory -line' }
'cpus' : { id : 'cpus' , name : 'CPU管理' , icon : 'ri-cpu -line' }
} ;
let html = fixedItems . map ( item => `
@@ -377,7 +469,7 @@
</div>
<div class="bg-white rounded-xl p-5 shadow-sm">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-lg bg-purple-100 flex items-center justify-center"><i class="ri-memory -line text-xl text-purple-600"></i></div>
<div class="w-10 h-10 rounded-lg bg-purple-100 flex items-center justify-center"><i class="ri-cpu -line text-xl text-purple-600"></i></div>
<div><div class="text-2xl font-bold text-gray-800"> ${ data . cpus _count } </div><div class="text-xs text-gray-500">CPU数量</div></div>
</div>
</div>
@@ -417,12 +509,16 @@
}
document . getElementById ( 'admin-categories-table' ) . innerHTML = categories . map ( c => `
<tr class="border-b hover:bg-gray-50">
<tr class="border-b hover:bg-gray-50 ${ c . visible === false ? 'bg-gray-100 opacity-60' : '' } ">
<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"> ${ c . order || 0 } </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">
<i class=" ${ c . visible === false ? 'ri-eye-off-line' : 'ri-eye-line' } "></i>
</button>
</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>
@@ -433,16 +529,22 @@
// 加载模型列表
async function loadAdminModels ( ) {
const res = await fetch ( '/api/models' ) ;
const res = await fetch ( '/api/models?all=1 ' ) ;
const models = await res . json ( ) ;
if ( models . length === 0 ) { document . getElementById ( 'admin-models-table' ) . innerHTML = '<tr><td colspan="6 " class="text-center text-gray-400 py-8">暂无数据</td></tr>' ; return ; }
if ( models . length === 0 ) { document . getElementById ( 'admin-models-table' ) . innerHTML = '<tr><td colspan="7 " 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">
<tr class="border-b hover:bg-gray-50 ${ m . visible === false ? 'bg-gray-100 opacity-60' : '' } ">
<td class="px-4 py-3 font-medium text-gray-800"> ${ m . name } </td>
<td class="px-4 py-3 text-gray-600"> ${ m . organization } </td>
<td class="px-4 py-3"> ${ m . parameters } B</td>
<td class="px-4 py-3 text-gray-600"> ${ m . context _length || '-' } </td>
<td class="px-4 py-3"> ${ m . is _open _source ? '<span class="text-green-600">开源</span>' : '<span class="text-gray-600">商业</span>' } </td>
<td class="px-4 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" title="查看原始数据"><i class="ri-file-text-line"></i></button> ` : '' }
</td>
<td class="px-4 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>
@@ -453,16 +555,22 @@
// 加载GPU列表
async function loadAdminGpus ( ) {
const res = await fetch ( '/api/gpus' ) ;
const res = await fetch ( '/api/gpus?all=1 ' ) ;
const gpus = await res . json ( ) ;
if ( gpus . length === 0 ) { document . getElementById ( 'admin-gpus-table' ) . innerHTML = '<tr><td colspan="6 " class="text-center text-gray-400 py-8">暂无数据</td></tr>' ; return ; }
if ( gpus . length === 0 ) { document . getElementById ( 'admin-gpus-table' ) . innerHTML = '<tr><td colspan="7 " 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">
<tr class="border-b hover:bg-gray-50 ${ g . visible === false ? 'bg-gray-100 opacity-60' : '' } ">
<td class="px-4 py-3 font-medium text-gray-800"> ${ g . name } </td>
<td class="px-4 py-3 text-gray-600"> ${ g . manufacturer } </td>
<td class="px-4 py-3"> ${ g . memory _gb } GB</td>
<td class="px-4 py-3 text-gray-600"> ${ g . architecture || '-' } </td>
<td class="px-4 py-3 text-gray-600"> $ ${ g . price _usd || '-' } </td>
<td class="px-4 py-3 text-gray-600"> ${ formatPrice ( g ) } </td>
<td class="px-4 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> ` : '' }
</td>
<td class="px-4 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>
@@ -473,16 +581,22 @@
// 加载CPU列表
async function loadAdminCpus ( ) {
const res = await fetch ( '/api/cpus' ) ;
const res = await fetch ( '/api/cpus?all=1 ' ) ;
const cpus = await res . json ( ) ;
if ( cpus . length === 0 ) { document . getElementById ( 'admin-cpus-table' ) . innerHTML = '<tr><td colspan="6 " class="text-center text-gray-400 py-8">暂无数据</td></tr>' ; return ; }
if ( cpus . length === 0 ) { document . getElementById ( 'admin-cpus-table' ) . innerHTML = '<tr><td colspan="7 " 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">
<tr class="border-b hover:bg-gray-50 ${ c . visible === false ? 'bg-gray-100 opacity-60' : '' } ">
<td class="px-4 py-3 font-medium text-gray-800"> ${ c . name } </td>
<td class="px-4 py-3 text-gray-600"> ${ c . manufacturer } </td>
<td class="px-4 py-3"> ${ c . cores } / ${ c . threads } </td>
<td class="px-4 py-3 text-gray-600"> ${ c . base _clock _ghz || '-' } - ${ c . boost _clock _ghz || '-' } GHz</td>
<td class="px-4 py-3 text-gray-600"> $ ${ c . price _usd || '-' } </td>
<td class="px-4 py-3 text-gray-600"> ${ formatPrice ( c ) } </td>
<td class="px-4 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> ` : '' }
</td>
<td class="px-4 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>
@@ -583,9 +697,9 @@
const data = { } ;
formData . forEach ( ( value , key ) => {
if ( value ) {
const numFields = [ 'parameters' , 'context_length' , 'mmlu' , 'humaneval' , 'input_price' , 'output_price' , 'memory_gb' , 'cuda_cores' , 'tensor_cores' , 'memory_bandwidth_gbs' , 'fp32_tflops' , 'fp16_tflops' , 'int8_perf_tops' , 'price_usd' , 'release_year' , 'cores' , 'threads' , 'base_clock_ghz' , 'boost_clock_ghz' , 'l3_cache_mb' , 'tdp_watts' , 'order' ] ;
const numFields = [ 'parameters' , 'context_length' , 'mmlu' , 'humaneval' , 'input_price' , 'output_price' , 'memory_gb' , 'cuda_cores' , 'tensor_cores' , 'memory_bandwidth_gbs' , 'fp32_tflops' , 'fp16_tflops' , 'int8_perf_tops' , 'price_usd' , 'min_price' , 'max_price' , 'release_year' , 'cores' , 'threads' , 'base_clock_ghz' , 'boost_clock_ghz' , 'l3_cache_mb' , 'tdp_watts' , 'order' , 'price' ];
if ( numFields . includes ( key ) ) data [ key ] = parseFloat ( value ) ;
else if ( key === 'is_open_source' ) data [ key ] = value === 'true' ;
else if ( key === 'is_open_source' || key === 'visible' ) data [ key ] = value === 'true' ;
else data [ key ] = value ;
}
} ) ;
@@ -635,6 +749,16 @@
<option value="red" ${ data . color === 'red' ? 'selected' : '' } >红色</option>
</select></div>
<div><label class="text-sm text-gray-600 mb-1 block">排序</label><input type="number" name="order" value=" ${ data . order || 0 } " 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="visible" class="w-full px-3 py-2 border rounded-lg">
<option value="true" ${ data . visible !== false ? 'selected' : '' } >显示</option>
<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>
</form> ` ;
}
< / s e l e c t > < / d i v >
< div > < label class = "text-sm text-gray-600 mb-1 block" > 排序 < / l a b e l > < i n p u t t y p e = " n u m b e r " n a m e = " o r d e r " v a l u e = " $ { d a t a . o r d e r | | 0 } " c l a s s = " w - f u l l p x - 3 p y - 2 b o r d e r r o u n d e d - l g " > < / d i v >
< / d i v >
< div > < label class = "text-sm text-gray-600 mb-1 block" > 描述 < / l a b e l > < t e x t a r e a n a m e = " d e s c r i p t i o n " r o w s = " 2 " c l a s s = " w - f u l l p x - 3 p y - 2 b o r d e r r o u n d e d - l g " > $ { d a t a . d e s c r i p t i o n | | ' ' } < / t e x t a r e a > < / d i v >
< / f o r m > ` ;
@@ -697,7 +821,15 @@
<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><input type="number" name="price_usd" value=" ${ data . price _usd || '' } " 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="number" name="release_year" value=" ${ data . release _year || '' } " class="w-full px-3 py-2 border rounded-lg"></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>
@@ -716,13 +848,145 @@
<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><input type="number" name="price_usd" value=" ${ data . price _usd || '' } " 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>
<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>
</form> ` ;
}
document . getElementById ( 'editModal' ) . addEventListener ( 'click' , function ( e ) { if ( e . target === this ) closeModal ( ) ; } ) ;
document . getElementById ( 'smartAddModal' ) . addEventListener ( 'click' , function ( e ) { if ( e . target === this ) closeSmartAddModal ( ) ; } ) ;
document . getElementById ( 'rawDataModal' ) . addEventListener ( 'click' , function ( e ) { if ( e . target === this ) closeRawDataModal ( ) ; } ) ;
// ============ 智能添加功能 ============
let smartAddType = '' ;
function openSmartAddModal ( type ) {
smartAddType = type ;
document . getElementById ( 'smartAddText' ) . value = '' ;
document . getElementById ( 'smartAddPreview' ) . classList . add ( 'hidden' ) ;
document . getElementById ( 'smartAddModal' ) . classList . remove ( 'hidden' ) ;
}
function closeSmartAddModal ( ) {
document . getElementById ( 'smartAddModal' ) . classList . add ( 'hidden' ) ;
}
async function smartAddSubmit ( ) {
const text = document . getElementById ( 'smartAddText' ) . value . trim ( ) ;
if ( ! text ) {
alert ( '请粘贴产品信息文本' ) ;
return ;
}
const btn = document . getElementById ( 'smartAddBtn' ) ;
btn . disabled = true ;
btn . innerHTML = '<i class="ri-loader-4-line animate-spin mr-1"></i>解析中...' ;
try {
let endpoint ;
if ( smartAddType === 'model' ) endpoint = '/api/models/smart-add' ;
else if ( smartAddType === 'gpu' ) endpoint = '/api/gpus/smart-add' ;
else if ( smartAddType === 'cpu' ) endpoint = '/api/cpus/smart-add' ;
else if ( smartAddType === 'dynamic' ) endpoint = ` /api/items/ ${ dynamicCategoryId } /smart-add ` ;
const res = await fetch ( endpoint , {
method : 'POST' ,
headers : { 'Content-Type' : 'application/json' } ,
body : JSON . stringify ( { text } )
} ) ;
const data = await res . json ( ) ;
if ( data . error ) {
alert ( '解析失败: ' + data . error ) ;
} else {
// 显示解析结果
document . getElementById ( 'smartAddPreview' ) . classList . remove ( 'hidden' ) ;
document . getElementById ( 'smartAddResult' ) . innerHTML = ` <pre> ${ JSON . stringify ( data , null , 2 ) } </pre> ` ;
// 关闭弹窗并刷新列表
closeSmartAddModal ( ) ;
if ( smartAddType === 'dynamic' ) showDynamicCategory ( dynamicCategoryId ) ;
else {
const loaders = { model : loadAdminModels , gpu : loadAdminGpus , cpu : loadAdminCpus } ;
loaders [ smartAddType ] ( ) ;
}
loadOverview ( ) ;
alert ( '智能添加成功!数据已自动解析并保存。' ) ;
}
} catch ( e ) {
alert ( '请求失败: ' + e . message ) ;
}
btn . disabled = false ;
btn . innerHTML = '<i class="ri-magic-line mr-1"></i>智能解析并添加' ;
}
// ============ 显示切换功能 ============
async function toggleVisible ( type , id ) {
let endpoint ;
if ( type === 'category' ) endpoint = ` /api/categories/ ${ id } /visible ` ;
else if ( type === 'model' ) endpoint = ` /api/models/ ${ id } /visible ` ;
else if ( type === 'gpu' ) endpoint = ` /api/gpus/ ${ id } /visible ` ;
else if ( type === 'cpu' ) endpoint = ` /api/cpus/ ${ id } /visible ` ;
else if ( type === 'dynamic' ) endpoint = ` /api/items/ ${ dynamicCategoryId } / ${ id } /visible ` ;
try {
await fetch ( endpoint , { method : 'POST' } ) ;
// 刷新列表
if ( type === 'dynamic' ) showDynamicCategory ( dynamicCategoryId ) ;
else {
const loaders = { category : loadAdminCategories , model : loadAdminModels , gpu : loadAdminGpus , cpu : loadAdminCpus } ;
loaders [ type ] ( ) ;
}
} catch ( e ) {
alert ( '切换失败: ' + e . message ) ;
}
}
// ============ 原始数据查看 ============
async function showRawData ( id , type ) {
let endpoint ;
if ( type === 'model' ) endpoint = ` /api/models/ ${ id } ` ;
else if ( type === 'gpu' ) endpoint = ` /api/gpus/ ${ id } ` ;
else if ( type === 'cpu' ) endpoint = ` /api/cpus/ ${ id } ` ;
try {
const res = await fetch ( endpoint ) ;
const data = await res . json ( ) ;
let content = '' ;
if ( data . raw _text ) {
content = ` 【原始文本】 \n ${ data . raw _text } \n \n 【解析数据】 \n ${ JSON . stringify ( data , null , 2 ) } ` ;
} else {
content = JSON . stringify ( data , null , 2 ) ;
}
document . getElementById ( 'rawDataText' ) . textContent = content ;
document . getElementById ( 'rawDataModal' ) . classList . remove ( 'hidden' ) ;
} catch ( e ) {
alert ( '获取数据失败' ) ;
}
}
function closeRawDataModal ( ) {
document . getElementById ( 'rawDataModal' ) . classList . add ( 'hidden' ) ;
}
init ( ) ;
< / script >