const { request } = require('../../utils/request') Page({ data: { loading: false, stats: null, kpis: [], bars: [], sourceDist: [], topKeywords: [], report: null, reportLoading: false }, formatPercent(value, digits = 2) { const num = Number(value || 0) return `${(num * 100).toFixed(digits)}%` }, onShow() { this.fetchStats() }, onPullDownRefresh() { this.fetchStats(true) }, normalizeKpis(stats) { return [ { label: '系统用户', value: stats.user_count || 0 }, { label: '发布总量', value: stats.post_count || 0 }, { label: '拦截总量', value: stats.blocked_count || 0 }, { label: '待处理申诉', value: stats.pending_appeal_count || 0 }, { label: '训练样本', value: stats.sample_count || 0 }, { label: '近7天拦截率', value: this.formatPercent(stats.blocked_ratio_7d, 2) } ] }, normalizeBars(trend) { const rows = Array.isArray(trend) ? trend : [] const maxVal = Math.max(1, ...rows.map((r) => Number(r.post_count || 0))) return rows.map((row) => { const value = Number(row.post_count || 0) const ratio = value / maxVal return { ...row, value, percent_text: `${Math.max(6, Math.round(ratio * 100))}%` } }) }, async fetchStats(fromPullDown = false) { this.setData({ loading: true }) try { const stats = await request({ url: '/admin/stats' }) const normalizedStats = { ...stats, threshold_text: stats && stats.threshold ? this.formatPercent(stats.threshold.spam_threshold, 1) : '--' } this.setData({ stats: normalizedStats, kpis: this.normalizeKpis(normalizedStats), bars: this.normalizeBars(normalizedStats.trend_7d || []), sourceDist: normalizedStats.source_distribution || [], topKeywords: normalizedStats.top_keywords || [] }) } finally { this.setData({ loading: false }) if (fromPullDown) wx.stopPullDownRefresh() } }, async generateReport() { this.setData({ reportLoading: true }) try { const report = await request({ url: '/admin/stats/report' }) // 处理趋势数据,计算进度条宽度 const spamTrend = (report.spam_trend || []).map((item) => { const maxBlocked = Math.max(...(report.spam_trend || []).map((r) => r.blocked || 0), 1) return { ...item, blocked_percent: `${Math.max(4, Math.round((item.blocked || 0) / maxBlocked * 100))}%` } }) const misjudgeTrend = (report.misjudge_trend || []).map((item) => ({ ...item, rate_percent: `${Math.round((item.misjudge_rate || 0) * 100)}%` })) this.setData({ report: { ...report, spam_trend: spamTrend, misjudge_trend: misjudgeTrend } }) wx.showToast({ title: '报告已生成', icon: 'success' }) } finally { this.setData({ reportLoading: false }) } }, closeReport() { this.setData({ report: null }) }, copyReportText() { const report = this.data.report if (!report) return const summary = report.summary || {} const lines = [ `【垃圾信息运营报告】`, `报告周期:${report.period}`, `生成时间:${(report.report_date || '').replace('T', ' ').slice(0, 19)}`, '', `【汇总统计】`, `总发布量:${summary.total_posts || 0} 条`, `拦截量:${summary.total_blocked || 0} 条`, `正常发布:${summary.total_published || 0} 条`, `拦截率:${this.formatPercent(summary.blocked_ratio, 2)}`, `复核总数:${summary.total_reviews || 0} 次`, `误判放行:${summary.total_approved || 0} 次`, `平均误判率:${summary.avg_misjudge_rate_text || '0%'}`, '', `【高频风险词 Top10】`, (report.top_keywords || []).slice(0, 10).map((k) => `${k.token}(${k.count}次)`).join('、'), '', `【近7日趋势】`, (report.spam_trend || []).slice(-7).map((t) => `${t.label}: 拦截${t.blocked}条,发布${t.published}条`).join('\n') ] wx.setClipboardData({ data: lines.join('\n'), success: () => wx.showToast({ title: '报告已复制', icon: 'success' }) }) } })