feat: 小程序移除管理后台入口,新增admin-web前端项目

将管理后台功能从微信小程序中剥离,独立为Vue.js前端项目admin-web

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
刘正航
2026-05-14 13:49:07 +08:00
parent f342fdc9b4
commit 49c946dd55
39 changed files with 10760 additions and 257 deletions

View File

@@ -0,0 +1,187 @@
<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')
} 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>