@@ -382,6 +382,13 @@
< input id = "cfgTaskMaxRetries" type = "number" class = "flex h-9 w-full rounded-md border border-input bg-background px-3 py-2 text-sm" placeholder = "3" min = "1" max = "10" >
< p class = "text-xs text-muted-foreground mt-1" > 任务失败后最多重试的次数( 1-10次) < / p >
< / div >
< div >
< label class = "inline-flex items-center gap-2 cursor-pointer" >
< input type = "checkbox" id = "cfgAutoDisableOn401" class = "h-4 w-4 rounded border-input" >
< span class = "text-sm font-medium" > 遇到401错误自动禁用Token< / span >
< / label >
< p class = "text-xs text-muted-foreground mt-1" > 当Token返回401错误时, 自动禁用该Token并使用其他Token重试< / p >
< / div >
< button onclick = "saveAdminConfig()" class = "inline-flex items-center justify-center rounded-md bg-primary text-primary-foreground hover:bg-primary/90 h-9 px-4 w-full" > 保存配置< / button >
< / div >
< / div >
@@ -489,7 +496,7 @@
< option value = "default" > 随机轮询< / option >
< option value = "polling" > 逐个轮询< / option >
< / select >
< p class = "text-xs text-muted-foreground mt-2" > 随机轮询:随机选择可用账号;逐个轮询:每个活跃账号只调 用一次,全部轮询 后再开始下一轮< / p >
< p class = "text-xs text-muted-foreground mt-2" > 随机轮询:随机选择可用账号;逐个轮询:每个活跃账号只使 用一次,全部使用过 后再开始下一轮< / p >
< / div >
< button onclick = "saveCallLogicConfig()" class = "inline-flex items-center justify-center rounded-md bg-primary text-primary-foreground hover:bg-primary/90 h-9 px-4 w-full" > 保存配置< / button >
< / div >
@@ -978,8 +985,8 @@
batchDisableSelected = async ( ) => { if ( selectedTokenIds . size === 0 ) { showToast ( '请先选择要禁用的Token' , 'info' ) ; return } if ( ! confirm ( ` 确定要禁用选中的 ${ selectedTokenIds . size } 个Token吗? ` ) ) { return } showToast ( '正在批量禁用Token...' , 'info' ) ; try { const r = await apiRequest ( '/api/tokens/batch/disable-selected' , { method : 'POST' , body : JSON . stringify ( { token _ids : Array . from ( selectedTokenIds ) } ) } ) ; if ( ! r ) return ; const d = await r . json ( ) ; if ( d . success ) { selectedTokenIds . clear ( ) ; await refreshTokens ( ) ; showToast ( d . message , 'success' ) } else { showToast ( '批量禁用失败: ' + ( d . detail || '未知错误' ) , 'error' ) } } catch ( e ) { showToast ( '批量禁用失败: ' + e . message , 'error' ) } } ,
updateImportModeHint = ( ) => { const mode = $ ( 'importMode' ) . value , hint = $ ( 'importModeHint' ) , hints = { at : '使用AT更新账号状态( 订阅信息、Sora2次数等) ' , offline : '离线导入,不更新账号状态,动态字段显示为-' , st : '自动将ST转换为AT, 然后更新账号状态' , rt : '自动将RT转换为AT( 并刷新RT) , 然后更新账号状态' } ; hint . textContent = hints [ mode ] || '' } ,
submitImportTokens = async ( ) => { const fileInput = $ ( 'importFile' ) ; if ( ! fileInput . files || fileInput . files . length === 0 ) { showToast ( '请选择文件' , 'error' ) ; return } const file = fileInput . files [ 0 ] ; if ( ! file . name . endsWith ( '.json' ) ) { showToast ( '请选择JSON文件' , 'error' ) ; return } const mode = $ ( 'importMode' ) . value ; try { const fileContent = await file . text ( ) ; const importData = JSON . parse ( fileContent ) ; if ( ! Array . isArray ( importData ) ) { showToast ( 'JSON格式错误: 应为数组' , 'error' ) ; return } if ( importData . length === 0 ) { showToast ( 'JSON文件为空' , 'error' ) ; return } for ( let item of importData ) { if ( ! item . email ) { showToast ( '导入数据缺少必填字段: email' , 'error' ) ; return } if ( mode === 'offline' || mode === 'at' ) { if ( ! item . access _token ) { showToast ( ` ${ item . email } 缺少必填字段: access_token ` , 'error' ) ; return } } else if ( mode === 'st' ) { if ( ! item . session _token ) { showToast ( ` ${ item . email } 缺少必填字段: session_token ` , 'error' ) ; return } } else if ( mode === 'rt' ) { if ( ! item . refresh _token ) { showToast ( ` ${ item . email } 缺少必填字段: refresh_token ` , 'error' ) ; return } } } const btn = $ ( 'importBtn' ) , btnText = $ ( 'importBtnText' ) , btnSpinner = $ ( 'importBtnSpinner' ) ; btn . disabled = true ; btnText . textContent = '导入中...' ; btnSpinner . classList . remove ( 'hidden' ) ; try { const r = await apiRequest ( '/api/tokens/import' , { method : 'POST' , body : JSON . stringify ( { tokens : importData , mode : mode } ) } ) ; if ( ! r ) { btn . disabled = false ; btnText . textContent = '导入' ; btnSpinner . classList . add ( 'hidden' ) ; return } const d = await r . json ( ) ; if ( d . success ) { closeImportModal ( ) ; await refreshTokens ( ) ; showImportProgress ( d . results || [ ] , d . added || 0 , d . updated || 0 , d . failed || 0 ) } else { showToast ( '导入失败: ' + ( d . detail || d . message || '未知错误' ) , 'error' ) } } catch ( e ) { showToast ( '导入失败: ' + e . message , 'error' ) } finally { btn . disabled = false ; btnText . textContent = '导入' ; btnSpinner . classList . add ( 'hidden' ) } } catch ( e ) { showToast ( '文件解析失败: ' + e . message , 'error' ) } } ,
loadAdminConfig = async ( ) => { try { const r = await apiRequest ( '/api/admin/config' ) ; if ( ! r ) return ; const d = await r . json ( ) ; $ ( 'cfgErrorBan' ) . value = d . error _ban _threshold || 3 ; $ ( 'cfgTaskRetryEnabled' ) . checked = d . task _retry _enabled || false ; $ ( 'cfgTaskMaxRetries' ) . value = d . task _max _retries || 3 ; $ ( 'cfgAdminUsername' ) . value = d . admin _username || 'admin' ; $ ( 'cfgCurrentAPIKey' ) . value = d . api _key || '' ; $ ( 'cfgDebugEnabled' ) . checked = d . debug _enabled || false } catch ( e ) { console . error ( '加载配置失败:' , e ) } } ,
saveAdminConfig = async ( ) => { try { const r = await apiRequest ( '/api/admin/config' , { method : 'POST' , body : JSON . stringify ( { error _ban _threshold : parseInt ( $ ( 'cfgErrorBan' ) . value ) || 3 , task _retry _enabled : $ ( 'cfgTaskRetryEnabled' ) . checked , task _max _retries : parseInt ( $ ( 'cfgTaskMaxRetries' ) . value ) || 3 } ) } ) ; if ( ! r ) return ; const d = await r . json ( ) ; d . success ? showToast ( '配置保存成功' , 'success' ) : showToast ( '保存失败' , 'error' ) } catch ( e ) { showToast ( '保存失败: ' + e . message , 'error' ) } } ,
loadAdminConfig = async ( ) => { try { const r = await apiRequest ( '/api/admin/config' ) ; if ( ! r ) return ; const d = await r . json ( ) ; $ ( 'cfgErrorBan' ) . value = d . error _ban _threshold || 3 ; $ ( 'cfgTaskRetryEnabled' ) . checked = d . task _retry _enabled || false ; $ ( 'cfgTaskMaxRetries' ) . value = d . task _max _retries || 3 ; $ ( 'cfgAutoDisableOn401' ) . checked = d . auto _disable _on _401 || false ; $ ( ' cfgAdminUsername' ) . value = d . admin _username || 'admin' ; $ ( 'cfgCurrentAPIKey' ) . value = d . api _key || '' ; $ ( 'cfgDebugEnabled' ) . checked = d . debug _enabled || false } catch ( e ) { console . error ( '加载配置失败:' , e ) } } ,
saveAdminConfig = async ( ) => { try { const r = await apiRequest ( '/api/admin/config' , { method : 'POST' , body : JSON . stringify ( { error _ban _threshold : parseInt ( $ ( 'cfgErrorBan' ) . value ) || 3 , task _retry _enabled : $ ( 'cfgTaskRetryEnabled' ) . checked , task _max _retries : parseInt ( $ ( 'cfgTaskMaxRetries' ) . value ) || 3 , auto _disable _on _401 : $ ( 'cfgAutoDisableOn401' ) . checked }) } ) ; if ( ! r ) return ; const d = await r . json ( ) ; d . success ? showToast ( '配置保存成功' , 'success' ) : showToast ( '保存失败' , 'error' ) } catch ( e ) { showToast ( '保存失败: ' + e . message , 'error' ) } } ,
updateAdminPassword = async ( ) => { const username = $ ( 'cfgAdminUsername' ) . value . trim ( ) , oldPwd = $ ( 'cfgOldPassword' ) . value . trim ( ) , newPwd = $ ( 'cfgNewPassword' ) . value . trim ( ) ; if ( ! oldPwd || ! newPwd ) return showToast ( '请输入旧密码和新密码' , 'error' ) ; if ( newPwd . length < 4 ) return showToast ( '新密码至少4个字符' , 'error' ) ; try { const r = await apiRequest ( '/api/admin/password' , { method : 'POST' , body : JSON . stringify ( { username : username || undefined , old _password : oldPwd , new _password : newPwd } ) } ) ; if ( ! r ) return ; const d = await r . json ( ) ; if ( d . success ) { showToast ( '密码修改成功,请重新登录' , 'success' ) ; setTimeout ( ( ) => { localStorage . removeItem ( 'adminToken' ) ; location . href = '/login' } , 2000 ) } else { showToast ( '修改失败: ' + ( d . detail || '未知错误' ) , 'error' ) } } catch ( e ) { showToast ( '修改失败: ' + e . message , 'error' ) } } ,
updateAPIKey = async ( ) => { const newKey = $ ( 'cfgNewAPIKey' ) . value . trim ( ) ; if ( ! newKey ) return showToast ( '请输入新的 API Key' , 'error' ) ; if ( newKey . length < 6 ) return showToast ( 'API Key 至少6个字符' , 'error' ) ; if ( ! confirm ( '确定要更新 API Key 吗?更新后需要通知所有客户端使用新密钥。' ) ) return ; try { const r = await apiRequest ( '/api/admin/apikey' , { method : 'POST' , body : JSON . stringify ( { new _api _key : newKey } ) } ) ; if ( ! r ) return ; const d = await r . json ( ) ; if ( d . success ) { showToast ( 'API Key 更新成功' , 'success' ) ; $ ( 'cfgCurrentAPIKey' ) . value = newKey ; $ ( 'cfgNewAPIKey' ) . value = '' } else { showToast ( '更新失败: ' + ( d . detail || '未知错误' ) , 'error' ) } } catch ( e ) { showToast ( '更新失败: ' + e . message , 'error' ) } } ,
toggleDebugMode = async ( ) => { const enabled = $ ( 'cfgDebugEnabled' ) . checked ; try { const r = await apiRequest ( '/api/admin/debug' , { method : 'POST' , body : JSON . stringify ( { enabled : enabled } ) } ) ; if ( ! r ) return ; const d = await r . json ( ) ; if ( d . success ) { showToast ( enabled ? '调试模式已开启' : '调试模式已关闭' , 'success' ) } else { showToast ( '操作失败: ' + ( d . detail || '未知错误' ) , 'error' ) ; $ ( 'cfgDebugEnabled' ) . checked = ! enabled } } catch ( e ) { showToast ( '操作失败: ' + e . message , 'error' ) ; $ ( 'cfgDebugEnabled' ) . checked = ! enabled } } ,
@@ -999,7 +1006,7 @@
saveGenerationTimeout = async ( ) => { const imageTimeout = parseInt ( $ ( 'cfgImageTimeout' ) . value ) || 300 , videoTimeout = parseInt ( $ ( 'cfgVideoTimeout' ) . value ) || 1500 ; console . log ( '保存生成超时配置:' , { imageTimeout , videoTimeout } ) ; if ( imageTimeout < 60 || imageTimeout > 3600 ) return showToast ( '图片超时时间必须在 60-3600 秒之间' , 'error' ) ; if ( videoTimeout < 60 || videoTimeout > 7200 ) return showToast ( '视频超时时间必须在 60-7200 秒之间' , 'error' ) ; try { const r = await apiRequest ( '/api/generation/timeout' , { method : 'POST' , body : JSON . stringify ( { image _timeout : imageTimeout , video _timeout : videoTimeout } ) } ) ; if ( ! r ) { console . error ( '保存请求失败' ) ; return } const d = await r . json ( ) ; console . log ( '保存结果:' , d ) ; if ( d . success ) { showToast ( '生成超时配置保存成功' , 'success' ) ; await new Promise ( r => setTimeout ( r , 200 ) ) ; await loadGenerationTimeout ( ) } else { console . error ( '保存失败:' , d ) ; showToast ( '保存失败' , 'error' ) } } catch ( e ) { console . error ( '保存失败:' , e ) ; showToast ( '保存失败: ' + e . message , 'error' ) } } ,
toggleATAutoRefresh = async ( ) => { try { const enabled = $ ( 'atAutoRefreshToggle' ) . checked ; const r = await apiRequest ( '/api/token-refresh/enabled' , { method : 'POST' , body : JSON . stringify ( { enabled : enabled } ) } ) ; if ( ! r ) { $ ( 'atAutoRefreshToggle' ) . checked = ! enabled ; return } const d = await r . json ( ) ; if ( d . success ) { showToast ( enabled ? 'AT自动刷新已启用' : 'AT自动刷新已禁用' , 'success' ) } else { showToast ( '操作失败: ' + ( d . detail || '未知错误' ) , 'error' ) ; $ ( 'atAutoRefreshToggle' ) . checked = ! enabled } } catch ( e ) { showToast ( '操作失败: ' + e . message , 'error' ) ; $ ( 'atAutoRefreshToggle' ) . checked = ! enabled } } ,
loadATAutoRefreshConfig = async ( ) => { try { const r = await apiRequest ( '/api/token-refresh/config' ) ; if ( ! r ) return ; const d = await r . json ( ) ; if ( d . success && d . config ) { $ ( 'atAutoRefreshToggle' ) . checked = d . config . at _auto _refresh _enabled || false } else { console . error ( 'AT自动刷新配置数据格式错误:' , d ) } } catch ( e ) { console . error ( '加载AT自动刷新配置失败:' , e ) } } ,
loadLogs = async ( ) => { try { const r = await apiRequest ( '/api/logs?limit=100' ) ; if ( ! r ) return ; const logs = await r . json ( ) ; window . allLogs = logs ; const tb = $ ( 'logsTableBody' ) ; tb . innerHTML = logs . map ( l => { const isProcessing = l . status _code === - 1 ; const statusText = isProcessing ? '处理中' : l . status _code ; const statusClass = isProcessing ? 'bg-blue-50 text-blue-700' : l . status _code === 200 ? 'bg-green-50 text-green-700' : 'bg-red-50 text-red-700' ; let progressHtml = '<span class="text-xs text-muted-foreground">-</span>' ; if ( isProcessing && l . task _status ) { const taskStatusMap = { processing : '生成中' , completed : '已完成' , failed : '失败' } ; const taskStatusText = taskStatusMap [ l . task _status ] || l . task _status ; const progress = l . progress || 0 ; progressHtml = ` <div class="flex flex-col gap-1"><div class="flex items-center gap-2"><div class="flex-1 h-2 bg-gray-200 rounded-full overflow-hidden"><div class="h-full bg-blue-500 transition-all" style="width: ${ progress } %"></div></div><span class="text-xs text-blue-600"> ${ progress . toFixed ( 0 ) } %</span></div><span class="text-xs text-muted-foreground"> ${ taskStatusText } </span></div> ` } let actionHtml = '<button onclick="showLogDetail(' + l . id + ')" class="inline-flex items-center justify-center rounded-md hover:bg-blue-50 hover:text-blue-700 h-7 px-2 text-xs">查看</button>' ; if ( isProcessing && l . task _id ) { actionHtml = '<div class="flex gap-1"><button onclick="showLogDetail(' + l . id + ')" class="inline-flex items-center justify-center rounded-md hover:bg-blue-50 hover:text-blue-700 h-7 px-2 text-xs">查看</button><button onclick="cancelTask(\'' + l . task _id + '\')" class="inline-flex items-center justify-center rounded-md hover:bg-red-50 hover:text-red-700 h-7 px-2 text-xs">终止</button></div>' } return ` <tr><td class="py-2.5 px-3"> ${ l . operation } </td><td class="py-2.5 px-3"><span class="text-xs ${ l . token _email ? 'text-blue-600' : 'text-muted-foreground' } "> ${ l . token _email || '未知' } </span></td><td class="py-2.5 px-3"><span class="inline-flex items-center rounded px-2 py-0.5 text-xs ${ statusClass } "> ${ statusText } </span></td><td class="py-2.5 px-3"> ${ progressHtml } </td><td class="py-2.5 px-3"> ${ l . duration === - 1 ? '处理中' : l . duration . toFixed ( 2 ) + '秒' } </td><td class="py-2.5 px-3 text-xs text-muted-foreground"> ${ l . created _at ? new Date ( l . created _at ) . toLocaleString ( 'zh-CN' ) : '-' } </td><td class="py-2.5 px-3"> ${ actionHtml } </td></tr> ` } ) . join ( '' ) } catch ( e ) { console . error ( '加载日志失败:' , e ) } } ,
loadLogs = async ( ) => { try { const r = await apiRequest ( '/api/logs?limit=100' ) ; if ( ! r ) return ; const logs = await r . json ( ) ; window . allLogs = logs ; const tb = $ ( 'logsTableBody' ) ; tb . innerHTML = logs . map ( l => { const isProcessing = l . status _code === - 1 && l . task _status === 'processing' ; const isFailed = l . task _status === 'failed' ; const isCompleted = l . task _status === 'completed' ; const statusText = isProcessing ? '处理中' : l . status _code ; const statusClass = isProcessing ? 'bg-blue-50 text-blue-700' : l . status _code === 200 ? 'bg-green-50 text-green-700' : 'bg-red-50 text-red-700' ; let progressHtml = '<span class="text-xs text-muted-foreground">-</span>' ; if ( isProcessing && l . task _status ) { const taskStatusMap = { processing : '生成中' , completed : '已完成' , failed : '失败' } ; const taskStatusText = taskStatusMap [ l . task _status ] || l . task _status ; const progress = l . progress || 0 ; progressHtml = ` <div class="flex flex-col gap-1"><div class="flex items-center gap-2"><div class="flex-1 h-2 bg-gray-200 rounded-full overflow-hidden"><div class="h-full bg-blue-500 transition-all" style="width: ${ progress } %"></div></div><span class="text-xs text-blue-600"> ${ progress . toFixed ( 0 ) } %</span></div><span class="text-xs text-muted-foreground"> ${ taskStatusText } </span></div> ` } else if ( isFailed ) { progressHtml = '<span class="text-xs text-red-600">失败</span>' } else if ( isCompleted && l . status _code === 200 ) { progressHtml = '<span class="text-xs text-green-600">已完成</span>' } let actionHtml = '<button onclick="showLogDetail(' + l . id + ')" class="inline-flex items-center justify-center rounded-md hover:bg-blue-50 hover:text-blue-700 h-7 px-2 text-xs">查看</button>' ; if ( isProcessing && l . task _id ) { actionHtml = '<div class="flex gap-1"><button onclick="showLogDetail(' + l . id + ')" class="inline-flex items-center justify-center rounded-md hover:bg-blue-50 hover:text-blue-700 h-7 px-2 text-xs">查看</button><button onclick="cancelTask(\'' + l . task _id + '\')" class="inline-flex items-center justify-center rounded-md hover:bg-red-50 hover:text-red-700 h-7 px-2 text-xs">终止</button></div>' } return ` <tr><td class="py-2.5 px-3"> ${ l . operation } </td><td class="py-2.5 px-3"><span class="text-xs ${ l . token _email ? 'text-blue-600' : 'text-muted-foreground' } "> ${ l . token _email || '未知' } </span></td><td class="py-2.5 px-3"><span class="inline-flex items-center rounded px-2 py-0.5 text-xs ${ statusClass } "> ${ statusText } </span></td><td class="py-2.5 px-3"> ${ progressHtml } </td><td class="py-2.5 px-3"> ${ l . duration === - 1 ? '处理中' : l . duration . toFixed ( 2 ) + '秒' } </td><td class="py-2.5 px-3 text-xs text-muted-foreground"> ${ l . created _at ? new Date ( l . created _at ) . toLocaleString ( 'zh-CN' ) : '-' } </td><td class="py-2.5 px-3"> ${ actionHtml } </td></tr> ` } ) . join ( '' ) } catch ( e ) { console . error ( '加载日志失败:' , e ) } } ,
refreshLogs = async ( ) => { await loadLogs ( ) } ,
showLogDetail = ( logId ) => { const log = window . allLogs . find ( l => l . id === logId ) ; if ( ! log ) { showToast ( '日志不存在' , 'error' ) ; return } const content = $ ( 'logDetailContent' ) ; let detailHtml = '' ; if ( log . status _code === - 1 ) { detailHtml += ` <div class="space-y-2"><h4 class="font-medium text-sm text-blue-600">生成进度</h4><div class="rounded-md border border-blue-200 p-3 bg-blue-50"><p class="text-sm text-blue-700">任务正在生成中...</p> ${ log . task _status ? ` <p class="text-xs text-blue-600 mt-1">状态: ${ log . task _status } </p> ` : '' } </div></div> ` } else if ( log . status _code === 200 ) { try { const responseBody = log . response _body ? JSON . parse ( log . response _body ) : null ; if ( responseBody ) { if ( responseBody . data && responseBody . data . length > 0 ) { const item = responseBody . data [ 0 ] ; if ( item . url ) { detailHtml += ` <div class="space-y-2"><h4 class="font-medium text-sm">生成结果</h4><div class="rounded-md border border-border p-3 bg-muted/30"><p class="text-sm mb-2"><span class="font-medium">文件URL:</span></p><a href=" ${ item . url } " target="_blank" class="text-blue-600 hover:underline text-xs break-all"> ${ item . url } </a></div></div> ` } else { detailHtml += ` <div class="space-y-2"><h4 class="font-medium text-sm">响应数据</h4><pre class="rounded-md border border-border p-3 bg-muted/30 text-xs overflow-x-auto"> ${ JSON . stringify ( responseBody , null , 2 ) } </pre></div> ` } } else { detailHtml += ` <div class="space-y-2"><h4 class="font-medium text-sm">响应数据</h4><pre class="rounded-md border border-border p-3 bg-muted/30 text-xs overflow-x-auto"> ${ JSON . stringify ( responseBody , null , 2 ) } </pre></div> ` } } else { detailHtml += ` <div class="space-y-2"><h4 class="font-medium text-sm">响应信息</h4><p class="text-sm text-muted-foreground">无响应数据</p></div> ` } } catch ( e ) { detailHtml += ` <div class="space-y-2"><h4 class="font-medium text-sm">响应数据</h4><pre class="rounded-md border border-border p-3 bg-muted/30 text-xs overflow-x-auto"> ${ log . response _body || '无' } </pre></div> ` } } else { try { const responseBody = log . response _body ? JSON . parse ( log . response _body ) : null ; if ( responseBody && responseBody . error ) { detailHtml += ` <div class="space-y-2"><h4 class="font-medium text-sm text-red-600">错误原因</h4><div class="rounded-md border border-red-200 p-3 bg-red-50"><p class="text-sm text-red-700"> ${ responseBody . error . message || responseBody . error || '未知错误' } </p></div></div> ` } else if ( log . response _body && log . response _body !== '{}' ) { detailHtml += ` <div class="space-y-2"><h4 class="font-medium text-sm text-red-600">错误信息</h4><pre class="rounded-md border border-red-200 p-3 bg-red-50 text-xs overflow-x-auto"> ${ log . response _body } </pre></div> ` } } catch ( e ) { if ( log . response _body && log . response _body !== '{}' ) { detailHtml += ` <div class="space-y-2"><h4 class="font-medium text-sm text-red-600">错误信息</h4><pre class="rounded-md border border-red-200 p-3 bg-red-50 text-xs overflow-x-auto"> ${ log . response _body } </pre></div> ` } } } detailHtml += ` <div class="space-y-2 pt-4 border-t border-border"><h4 class="font-medium text-sm">基本信息</h4><div class="grid grid-cols-2 gap-2 text-sm"><div><span class="text-muted-foreground">操作:</span> ${ log . operation } </div><div><span class="text-muted-foreground">状态码:</span> <span class="inline-flex items-center rounded px-2 py-0.5 text-xs ${ log . status _code === - 1 ? 'bg-blue-50 text-blue-700' : log . status _code === 200 ? 'bg-green-50 text-green-700' : 'bg-red-50 text-red-700' } "> ${ log . status _code === - 1 ? '生成中' : log . status _code } </span></div><div><span class="text-muted-foreground">耗时:</span> ${ log . duration === - 1 ? '生成中' : log . duration . toFixed ( 2 ) + '秒' } </div><div><span class="text-muted-foreground">时间:</span> ${ log . created _at ? new Date ( log . created _at ) . toLocaleString ( 'zh-CN' ) : '-' } </div></div></div> ` ; content . innerHTML = detailHtml ; $ ( 'logDetailModal' ) . classList . remove ( 'hidden' ) } ,
closeLogDetailModal = ( ) => { $ ( 'logDetailModal' ) . classList . add ( 'hidden' ) } ,