7 changed files with 469 additions and 199 deletions
-
103src/main/resources/static/components/AuditLogManager.js
-
10src/main/resources/static/components/FaqManager.js
-
99src/main/resources/static/components/ModelConfigManager.js
-
65src/main/resources/static/components/SensitiveWordManager.js
-
75src/main/resources/static/css/main.css
-
202src/main/resources/static/js/app.js
-
114src/main/resources/static/js/store.js
@ -0,0 +1,103 @@ |
|||||
|
/** |
||||
|
* 内容审计日志组件 |
||||
|
* 展示敏感词命中记录,支持分页浏览 |
||||
|
*/ |
||||
|
import { listAuditLogs } from '../js/api.js' |
||||
|
import { toast } from '../js/utils.js' |
||||
|
|
||||
|
export default { |
||||
|
template: `
|
||||
|
<div class="card"> |
||||
|
<h2>📋 内容审计日志</h2> |
||||
|
<p style="font-size:13px;color:var(--sub);margin-bottom:16px;">记录所有敏感词命中事件,包含输入/输出方向、违规内容、命中词及处理方式。</p> |
||||
|
|
||||
|
<!-- 表格 --> |
||||
|
<div style="overflow-x:auto;"> |
||||
|
<table class="data-table"> |
||||
|
<thead> |
||||
|
<tr> |
||||
|
<th>时间</th> |
||||
|
<th>方向</th> |
||||
|
<th>违规内容</th> |
||||
|
<th>命中词</th> |
||||
|
<th>处理方式</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody> |
||||
|
<tr v-if="loading"><td colspan="5" style="text-align:center;padding:20px;">加载中...</td></tr> |
||||
|
<tr v-else-if="logs.length === 0"><td colspan="5" style="text-align:center;padding:20px;color:var(--sub);">暂无审计日志</td></tr> |
||||
|
<tr v-for="log in logs" :key="log.id"> |
||||
|
<td style="white-space:nowrap;">{{ formatTime(log.createTime) }}</td> |
||||
|
<td style="text-align:center;"> |
||||
|
<span :style="{color: log.direction === 'INPUT' ? '#007bff' : '#28a745'}"> |
||||
|
{{ log.direction === 'INPUT' ? '输入' : '输出' }} |
||||
|
</span> |
||||
|
</td> |
||||
|
<td style="max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" :title="log.originalText">{{ log.originalText }}</td> |
||||
|
<td style="font-size:12px;">{{ formatHitWords(log.hitWords) }}</td> |
||||
|
<td style="text-align:center;"> |
||||
|
<span :style="{color: log.actionTaken === 'BLOCK' ? '#dc3545' : log.actionTaken === 'MASK' ? '#ffc107' : '#28a745'}"> |
||||
|
{{ log.actionTaken === 'BLOCK' ? '拦截' : log.actionTaken === 'MASK' ? '脱敏' : '通过' }} |
||||
|
</span> |
||||
|
</td> |
||||
|
</tr> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 分页 --> |
||||
|
<div v-if="total > pageSize" style="display:flex;justify-content:center;gap:8px;margin-top:12px;"> |
||||
|
<button @click="page > 1 && (page--, loadLogs())" :disabled="page <= 1" style="padding:4px 10px;">上一页</button> |
||||
|
<span style="line-height:32px;">第 {{ page }} / {{ Math.ceil(total / pageSize) }} 页(共 {{ total }} 条)</span> |
||||
|
<button @click="page < Math.ceil(total / pageSize) && (page++, loadLogs())" :disabled="page >= Math.ceil(total / pageSize)" style="padding:4px 10px;">下一页</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
`,
|
||||
|
|
||||
|
data() { |
||||
|
return { |
||||
|
logs: [], |
||||
|
loading: false, |
||||
|
page: 1, |
||||
|
pageSize: 30, |
||||
|
total: 0 |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
mounted() { |
||||
|
this.loadLogs() |
||||
|
}, |
||||
|
|
||||
|
methods: { |
||||
|
async loadLogs() { |
||||
|
this.loading = true |
||||
|
try { |
||||
|
const res = await listAuditLogs(this.page, this.pageSize) |
||||
|
if (res.success) { |
||||
|
this.logs = res.data?.records || res.data || [] |
||||
|
this.total = res.data?.total || 0 |
||||
|
} else { |
||||
|
toast('加载审计日志失败: ' + (res.message || '未知错误'), 'error') |
||||
|
} |
||||
|
} catch (e) { |
||||
|
toast('加载审计日志失败: ' + e.message, 'error') |
||||
|
} |
||||
|
this.loading = false |
||||
|
}, |
||||
|
|
||||
|
formatTime(t) { return t ? new Date(t).toLocaleString('zh-CN') : '' }, |
||||
|
|
||||
|
formatHitWords(hw) { |
||||
|
if (!hw) return '' |
||||
|
if (hw.type === 'jsonb' && hw.value) { |
||||
|
try { hw = JSON.parse(hw.value) } catch { return hw.value } |
||||
|
} |
||||
|
if (typeof hw === 'string') { try { hw = JSON.parse(hw) } catch { return hw } } |
||||
|
if (hw && hw.hits && Array.isArray(hw.hits)) { |
||||
|
return hw.hits.map(h => `${h.word || ''}(${h.category || ''})`).join('、') |
||||
|
} |
||||
|
if (Array.isArray(hw)) return hw.map(h => typeof h === 'string' ? h : h.word || '').join('、') |
||||
|
return String(hw) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue