改成导excel

This commit is contained in:
刘正航
2026-05-11 16:09:44 +08:00
parent 45bfa93e85
commit 25fd25005a
4 changed files with 107 additions and 33 deletions

View File

@@ -1,4 +1,4 @@
from flask import Blueprint, current_app, request from flask import Blueprint, current_app, request, send_file, after_this_request
from flask_jwt_extended import jwt_required from flask_jwt_extended import jwt_required
from app.extensions import db from app.extensions import db
@@ -367,3 +367,56 @@ def import_samples():
db.session.commit() db.session.commit()
return ok({"created": created, "updated": updated}, "样本导入完成") return ok({"created": created, "updated": updated}, "样本导入完成")
@spam_bp.post("/export/xlsx")
@jwt_required()
def export_xlsx():
user = current_user()
if not user:
return fail("用户不存在", 404)
payload = request.get_json(silent=True) or {}
items = payload.get("items") or []
if not isinstance(items, list) or not items:
return fail("items 必须是非空数组", 400)
import os
import tempfile
import pandas as pd
rows = []
for item in items:
tokens = item.get("reason_tokens") or []
token_str = "; ".join(t.get("token", "") for t in tokens) if isinstance(tokens, list) else ""
prediction_text = "垃圾信息" if item.get("prediction") == "spam" else "正常信息"
rows.append({
"文本": item.get("text", ""),
"判定结果": prediction_text,
"分类标签": item.get("category_label", ""),
"置信度": f"{float(item.get("confidence", 0) or 0) * 100:.2f}%",
"垃圾概率": f"{float(item.get("spam_probability", 0) or 0) * 100:.2f}%",
"正常概率": f"{float(item.get("ham_probability", 0) or 0) * 100:.2f}%",
"风险关键词": token_str,
})
df = pd.DataFrame(rows)
tmp = tempfile.NamedTemporaryFile(suffix=".xlsx", delete=False)
tmp.close()
df.to_excel(tmp.name, index=False, engine="openpyxl")
@after_this_request
def cleanup(response):
try:
os.unlink(tmp.name)
except Exception:
pass
return response
return send_file(
tmp.name,
mimetype="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
as_attachment=True,
download_name="batch_detect.xlsx",
)

View File

@@ -11,3 +11,4 @@ joblib==1.4.2
python-dotenv==1.0.1 python-dotenv==1.0.1
requests==2.32.3 requests==2.32.3
Werkzeug==3.1.3 Werkzeug==3.1.3
openpyxl==3.1.5

View File

@@ -141,46 +141,66 @@ Page({
return [headers.join(','), ...rows].join('\n') return [headers.join(','), ...rows].join('\n')
}, },
exportCSV() { exportXLSX() {
const items = this.data.items const items = this.data.items
if (!items.length) { if (!items.length) {
wx.showToast({ title: '暂无识别结果可导出', icon: 'none' }) wx.showToast({ title: '暂无识别结果可导出', icon: 'none' })
return return
} }
const csvContent = this.generateCSV() wx.showLoading({ title: '生成文件中...' })
const timestamp = new Date().toISOString().slice(0, 19).replace(/[T:]/g, '-')
const filename = `batch_detect_${timestamp}.csv`
// 写入临时文件 const app = getApp()
const fs = wx.getFileSystemManager() const token = app.globalData.token || wx.getStorageSync('token') || ''
const tempPath = `${wx.env.USER_DATA_PATH}/${filename}` const baseURL = app.globalData.baseURL || 'http://127.0.0.1:5000/api'
try { wx.request({
fs.writeFileSync(tempPath, csvContent, 'utf8') url: `${baseURL}/spam/export/xlsx`,
wx.showModal({ method: 'POST',
title: '导出成功', data: { items },
content: `CSV文件已生成是否打开查看\n文件名:${filename}`, header: {
confirmText: '打开', 'Content-Type': 'application/json',
cancelText: '关闭', Authorization: `Bearer ${token}`
success: (res) => { },
if (res.confirm) { responseType: 'arraybuffer',
wx.openDocument({ success(res) {
filePath: tempPath, wx.hideLoading()
fileType: 'csv',
showMenu: true, if (res.statusCode !== 200) {
fail: (err) => { wx.showToast({ title: '导出失败', icon: 'none' })
console.error('打开文件失败', err) return
wx.showToast({ title: '打开失败,请检查文件管理器', icon: 'none' })
}
})
}
} }
})
} catch (err) { const timestamp = new Date().toISOString().slice(0, 19).replace(/[T:]/g, '-')
console.error('写入文件失败', err) const filename = `batch_detect_${timestamp}.xlsx`
wx.showToast({ title: '导出失败', icon: 'none' }) 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() { copyCSVToClipboard() {

View File

@@ -77,7 +77,7 @@
<view class="card fade-up fade-up-delay-3" wx:if="{{items.length}}"> <view class="card fade-up fade-up-delay-3" wx:if="{{items.length}}">
<view class="card-title">明细结果</view> <view class="card-title">明细结果</view>
<view class="btn-row" style="margin-bottom: 12rpx;"> <view class="btn-row" style="margin-bottom: 12rpx;">
<button class="btn btn-ghost" bindtap="exportCSV">导出CSV文件</button> <button class="btn btn-ghost" bindtap="exportXLSX">导出Excel文件</button>
<button class="btn btn-ghost" bindtap="copyCSVToClipboard">复制CSV内容</button> <button class="btn btn-ghost" bindtap="copyCSVToClipboard">复制CSV内容</button>
</view> </view>
<view class="list-item" wx:for="{{items}}" wx:key="index"> <view class="list-item" wx:for="{{items}}" wx:key="index">