Files
c/miniprogram/pages/batch/index.js
刘正航 00ead01cb8 feat: 批量检测支持上传TXT文件
- 新增文件选择功能,支持TXT格式
- 自动读取文件内容并逐行拆解
- 显示已选文件名和文本条数
- 保留手动输入方式作为备选

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-01 17:02:08 +08:00

202 lines
5.9 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: '',
fileName: '',
lineCount: 0,
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)
},
chooseFile() {
wx.chooseMessageFile({
count: 1,
type: 'file',
extension: ['txt'],
success: (res) => {
const file = res.tempFiles[0]
const fs = wx.getFileSystemManager()
try {
const content = fs.readFileSync(file.path, 'utf8')
const lines = content
.split('\n')
.map((line) => line.trim())
.filter((line) => line.length >= 2)
this.setData({
inputText: lines.join('\n'),
fileName: file.name,
lineCount: lines.length
})
wx.showToast({ title: `已读取 ${lines.length} 条文本`, icon: 'success' })
} catch (err) {
console.error('读取文件失败', err)
wx.showToast({ title: '文件读取失败', icon: 'none' })
}
},
fail: (err) => {
console.error('选择文件失败', err)
if (err.errMsg !== 'chooseMessageFile:fail cancel') {
wx.showToast({ title: '请选择TXT文件', icon: 'none' })
}
}
})
},
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 categoryLabel = item.category_label || ''
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}","${categoryLabel}","${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' })
}
})
}
})