Files
c/miniprogram/pages/batch/index.js
刘正航 5279816452 feat: 批量识别结果导出CSV
- 新增导出CSV文件功能,包含文本、判定结果、置信度、风险关键词
- 新增复制CSV内容到剪贴板功能
- CSV字段:文本、判定结果、置信度、垃圾概率、正常概率、风险关键词

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 23:31:49 +08:00

162 lines
4.7 KiB
JavaScript
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.
const { request } = require('../../utils/request')
Page({
data: {
inputText: '',
loading: false,
summary: null,
items: []
},
formatPercent(value, digits = 2) {
const num = Number(value || 0)
return `${(num * 100).toFixed(digits)}%`
},
onInput(e) {
this.setData({ inputText: e.detail.value || '' })
},
parseLines() {
return (this.data.inputText || '')
.split('\n')
.map((line) => line.trim())
.filter((line) => line.length >= 2)
},
async submit() {
if (this.data.loading) return
const items = this.parseLines()
if (!items.length) {
wx.showToast({ title: '请至少输入一条有效文本', icon: 'none' })
return
}
this.setData({ loading: true })
try {
const data = await request({
url: '/spam/predict/batch',
method: 'POST',
data: { items }
})
const summary = {
...(data.summary || {}),
spam_ratio_text: this.formatPercent((data.summary || {}).spam_ratio, 2),
blocked_ratio_text: this.formatPercent((data.summary || {}).blocked_ratio, 2)
}
const normalizedItems = (data.items || []).map((item) => ({
...item,
confidence_text: this.formatPercent(item.confidence, 2)
}))
this.setData({ summary, items: normalizedItems })
} finally {
this.setData({ loading: false })
}
},
fillDemo() {
this.setData({
inputText: [
'点击链接领取购物补贴,名额有限。',
'明天下午三点上线前演练。',
'高薪兼职日结,扫码进群。',
'测试报告我已经同步到项目群。'
].join('\n')
})
},
showTokenWeight(e) {
const token = e.currentTarget.dataset.token
const weight = e.currentTarget.dataset.weight
const weightNum = Number(weight || 0)
const direction = weightNum >= 0 ? '倾向垃圾判定' : '倾向正常判定'
wx.showModal({
title: '关键词权重',
content: `关键词"${token}"\n权重贡献:${weightNum >= 0 ? '+' : ''}${weightNum.toFixed(4)}\n(${direction})`,
showCancel: false,
confirmText: '关闭'
})
},
generateCSV() {
const items = this.data.items
if (!items.length) return ''
const headers = ['文本', '判定结果', '置信度', '垃圾概率', '正常概率', '风险关键词']
const rows = items.map((item) => {
const prediction = item.prediction === 'spam' ? '垃圾信息' : '正常信息'
const confidence = item.confidence_text || '0%'
const spamProb = this.formatPercent(item.spam_probability, 4)
const hamProb = this.formatPercent(item.ham_probability, 4)
const tokens = (item.reason_tokens || []).map((t) => t.token || t).join('; ')
// CSV 转义:文本中的逗号和换行需要处理
const text = (item.text || '').replace(/"/g, '""')
const tokensEscaped = tokens.replace(/"/g, '""')
return `"${text}","${prediction}","${confidence}","${spamProb}","${hamProb}","${tokensEscaped}"`
})
return [headers.join(','), ...rows].join('\n')
},
exportCSV() {
const items = this.data.items
if (!items.length) {
wx.showToast({ title: '暂无识别结果可导出', icon: 'none' })
return
}
const csvContent = this.generateCSV()
const timestamp = new Date().toISOString().slice(0, 19).replace(/[T:]/g, '-')
const filename = `batch_detect_${timestamp}.csv`
// 写入临时文件
const fs = wx.getFileSystemManager()
const tempPath = `${wx.env.USER_DATA_PATH}/${filename}`
try {
fs.writeFileSync(tempPath, csvContent, 'utf8')
wx.showModal({
title: '导出成功',
content: `CSV文件已生成是否打开查看\n文件名:${filename}`,
confirmText: '打开',
cancelText: '关闭',
success: (res) => {
if (res.confirm) {
wx.openDocument({
filePath: tempPath,
fileType: 'csv',
showMenu: true,
fail: (err) => {
console.error('打开文件失败', err)
wx.showToast({ title: '打开失败,请检查文件管理器', icon: 'none' })
}
})
}
}
})
} catch (err) {
console.error('写入文件失败', err)
wx.showToast({ title: '导出失败', icon: 'none' })
}
},
copyCSVToClipboard() {
const items = this.data.items
if (!items.length) {
wx.showToast({ title: '暂无识别结果可复制', icon: 'none' })
return
}
const csvContent = this.generateCSV()
wx.setClipboardData({
data: csvContent,
success: () => {
wx.showToast({ title: 'CSV内容已复制到剪贴板', icon: 'success' })
}
})
}
})