改成导excel
This commit is contained in:
@@ -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 app.extensions import db
|
||||
@@ -367,3 +367,56 @@ def import_samples():
|
||||
|
||||
db.session.commit()
|
||||
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",
|
||||
)
|
||||
|
||||
@@ -11,3 +11,4 @@ joblib==1.4.2
|
||||
python-dotenv==1.0.1
|
||||
requests==2.32.3
|
||||
Werkzeug==3.1.3
|
||||
openpyxl==3.1.5
|
||||
|
||||
@@ -141,40 +141,53 @@ Page({
|
||||
return [headers.join(','), ...rows].join('\n')
|
||||
},
|
||||
|
||||
exportCSV() {
|
||||
exportXLSX() {
|
||||
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`
|
||||
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, csvContent, 'utf8')
|
||||
wx.showModal({
|
||||
title: '导出成功',
|
||||
content: `CSV文件已生成,是否打开查看?\n文件名:${filename}`,
|
||||
confirmText: '打开',
|
||||
cancelText: '关闭',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
fs.writeFileSync(tempPath, res.data)
|
||||
wx.openDocument({
|
||||
filePath: tempPath,
|
||||
fileType: 'csv',
|
||||
fileType: 'xlsx',
|
||||
showMenu: true,
|
||||
success: () => {
|
||||
wx.showToast({ title: '导出成功', icon: 'success' })
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('打开文件失败', err)
|
||||
wx.showToast({ title: '打开失败,请检查文件管理器', icon: 'none' })
|
||||
}
|
||||
})
|
||||
}
|
||||
wx.showToast({ title: '打开失败', icon: 'none' })
|
||||
}
|
||||
})
|
||||
} catch (err) {
|
||||
@@ -182,6 +195,13 @@ Page({
|
||||
wx.showToast({ title: '导出失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
fail(err) {
|
||||
wx.hideLoading()
|
||||
console.error('导出请求失败', err)
|
||||
wx.showToast({ title: '导出失败,请检查网络', icon: 'none' })
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
copyCSVToClipboard() {
|
||||
const items = this.data.items
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
<view class="card fade-up fade-up-delay-3" wx:if="{{items.length}}">
|
||||
<view class="card-title">明细结果</view>
|
||||
<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>
|
||||
</view>
|
||||
<view class="list-item" wx:for="{{items}}" wx:key="index">
|
||||
|
||||
Reference in New Issue
Block a user