7 changed files with 469 additions and 199 deletions
-
103src/main/resources/static/components/AuditLogManager.js
-
10src/main/resources/static/components/FaqManager.js
-
63src/main/resources/static/components/ModelConfigManager.js
-
65src/main/resources/static/components/SensitiveWordManager.js
-
75src/main/resources/static/css/main.css
-
152src/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