本地 RAG 知识库
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

/**
* 内容审计日志组件
* 展示敏感词命中记录,支持分页浏览
*/
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)
}
}
}