You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
103 lines
3.8 KiB
103 lines
3.8 KiB
/**
|
|
* 内容审计日志组件
|
|
* 展示敏感词命中记录,支持分页浏览
|
|
*/
|
|
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)
|
|
}
|
|
}
|
|
}
|