Files
c/admin-web/src/views/Detect.vue
2026-05-14 21:15:27 +08:00

190 lines
6.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="container">
<section class="hero fade-up">
<div class="hero-badge">REAL-TIME CHECK</div>
<h1 class="hero-title">文本信息发布</h1>
<p class="hero-sub">支持公开发布私有发布与用户私信提交时自动执行垃圾信息识别</p>
</section>
<section class="card fade-up fade-up-delay-1">
<div class="card-title">发布内容</div>
<div class="field">
<label class="field-label">内容文本</label>
<textarea class="textarea" v-model="text" placeholder="请输入要发布的文本信息" rows="4"></textarea>
<div class="field-help">当前字数{{ text.length }}建议不少于 2 个字符</div>
</div>
<div class="grid-2">
<div class="field">
<label class="field-label">发布类型</label>
<select class="select" v-model="visibility">
<option v-for="opt in visibilityOptions" :key="opt.value" :value="opt.value">{{ opt.label }}</option>
</select>
</div>
<div class="field" v-if="visibility === 'direct'">
<label class="field-label">接收人用户名</label>
<input class="input" v-model.trim="recipientUsername" placeholder="私信发送时必填" />
</div>
</div>
<button class="btn btn-primary" :disabled="loading" @click="publish">
{{ loading ? '检测中...' : '提交发布' }}
</button>
</section>
<section class="card fade-up fade-up-delay-2">
<div class="card-title">快捷示例</div>
<div class="chip-group">
<span
v-for="(item, idx) in quickTexts"
:key="idx"
class="chip"
@click="text = item"
>{{ item }}</span>
</div>
</section>
<section class="card fade-up fade-up-delay-3" v-if="result">
<div class="card-title">识别反馈</div>
<div class="row">
<span class="label">发布结果</span>
<span :class="result.publish_allowed ? 'status-ham' : 'status-spam'">
{{ result.publish_allowed ? '发布成功' : '已拦截,需申诉' }}
</span>
</div>
<div class="row" v-if="result.detect && result.detect.category_label">
<span class="label">分类标签</span>
<span class="status-spam">{{ result.detect.category_label }}</span>
</div>
<div class="row">
<span class="label">模型判断</span>
<span class="value">{{ result.detect && result.detect.prediction_text }}</span>
</div>
<div class="row">
<span class="label">垃圾概率</span>
<span class="value">{{ result.detect_spam_probability_text }}</span>
</div>
<div class="progress-track">
<div class="progress-fill" :style="{ width: result.detect_spam_probability_text }"></div>
</div>
<div class="row">
<span class="label">检测置信度</span>
<span class="value">{{ result.detect.confidence_text }}</span>
</div>
<div class="progress-track">
<div class="progress-fill-safe" :style="{ width: result.detect.confidence_text }"></div>
</div>
<div class="row">
<span class="label">本次阈值</span>
<span class="value">{{ result.post_threshold_text }}</span>
</div>
<div class="field" v-if="result.detect.reason_tokens && result.detect.reason_tokens.length">
<span class="field-label">风险关键词</span>
<div class="chip-group">
<span
v-for="(tk, i) in result.detect.reason_tokens"
:key="i"
class="tag tag-danger"
@click="showTokenWeight(tk)"
>{{ tk.token }}</span>
</div>
</div>
<button class="btn btn-ghost" @click="$router.push('/history')">查看发布历史</button>
</section>
</div>
</template>
<script>
import { request } from '@/utils/request'
import { toast, confirm } from '@/utils/feedback'
const QUICK_TEXTS = [
'大家好,今晚 8 点社区线上读书会,欢迎参加。',
'恭喜中奖领取大额现金,点击链接立即到账。',
'本周活动报名已开放,请在群里接龙。',
'高薪兼职日结,扫码进群立刻赚钱。'
]
const VISIBILITY_OPTIONS = [
{ value: 'public', label: '公开信息发布' },
{ value: 'private', label: '私有信息发布' },
{ value: 'direct', label: '用户私信发布' }
]
export default {
name: 'DetectView',
data() {
return {
text: '',
loading: false,
result: null,
quickTexts: QUICK_TEXTS,
visibilityOptions: VISIBILITY_OPTIONS,
visibility: 'public',
recipientUsername: ''
}
},
methods: {
formatPercent(value, digits = 2) {
const num = Number(value || 0)
return `${(num * 100).toFixed(digits)}%`
},
async publish() {
if (this.loading) return
const text = (this.text || '').trim()
if (text.length < 2) {
toast('请输入至少 2 个字符', 'error')
return
}
const payload = { text, visibility: this.visibility }
if (this.visibility === 'direct') {
const receiver = (this.recipientUsername || '').trim()
if (!receiver) {
toast('私信请填写接收人用户名', 'error')
return
}
payload.recipient_username = receiver
}
this.loading = true
try {
const result = await request({ url: '/content/publish', method: 'POST', data: payload })
this.result = {
...result,
detect: {
...(result.detect || {}),
confidence_text: this.formatPercent((result.detect || {}).confidence, 2)
},
post_threshold_text: this.formatPercent((result.post || {}).threshold, 1),
detect_spam_probability_text: this.formatPercent((result.detect || {}).spam_probability, 2)
}
toast(result.publish_allowed ? '发布成功' : '已拦截,可申诉', result.publish_allowed ? 'success' : 'error')
} catch (err) {
console.error('[detect] 发布失败', err)
} finally {
this.loading = false
}
},
showTokenWeight(tk) {
const weight = Number(tk.weight || 0)
const direction = weight >= 0 ? '倾向垃圾判定' : '倾向正常判定'
confirm({
title: '关键词权重',
content: `关键词「${tk.token}\n权重贡献${weight >= 0 ? '+' : ''}${weight.toFixed(4)}\n${direction}`,
showCancel: false,
confirmText: '关闭'
})
}
}
}
</script>