Files
c/miniprogram/pages/batch/index.js
2026-05-11 16:09:44 +08:00

222 lines
6.5 KiB
JavaScript

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')
},
exportXLSX() {
const items = this.data.items
if (!items.length) {
wx.showToast({ title: '暂无识别结果可导出', icon: 'none' })
return
}
wx.showLoading({ title: '生成文件中...' })
const app = getApp()
const token = app.globalData.token || wx.getStorageSync('token') || ''
const baseURL = app.globalData.baseURL || 'http://127.0.0.1:5000/api'
wx.request({
url: `${baseURL}/spam/export/xlsx`,
method: 'POST',
data: { items },
header: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
},
responseType: 'arraybuffer',
success(res) {
wx.hideLoading()
if (res.statusCode !== 200) {
wx.showToast({ title: '导出失败', icon: 'none' })
return
}
const timestamp = new Date().toISOString().slice(0, 19).replace(/[T:]/g, '-')
const filename = `batch_detect_${timestamp}.xlsx`
const fs = wx.getFileSystemManager()
const tempPath = `${wx.env.USER_DATA_PATH}/${filename}`
try {
fs.writeFileSync(tempPath, res.data)
wx.openDocument({
filePath: tempPath,
fileType: 'xlsx',
showMenu: true,
success: () => {
wx.showToast({ title: '导出成功', icon: 'success' })
},
fail: (err) => {
console.error('打开文件失败', err)
wx.showToast({ title: '打开失败', icon: 'none' })
}
})
} catch (err) {
console.error('写入文件失败', err)
wx.showToast({ title: '导出失败', icon: 'none' })
}
},
fail(err) {
wx.hideLoading()
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' })
}
})
}
})