Delete the BCAT frontend page.
This commit is contained in:
@@ -1,144 +0,0 @@
|
||||
import os
|
||||
import subprocess
|
||||
import threading
|
||||
from flask import Flask, render_template, request, redirect, url_for, flash
|
||||
from werkzeug.utils import secure_filename
|
||||
import json
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['UPLOAD_FOLDER'] = 'data/' # 上传文件的保存目录
|
||||
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 文件大小限制为16MB
|
||||
app.secret_key = 'secret_key' # 用于Flash消息的密钥
|
||||
ALLOWED_EXTENSIONS = {'csv'} # 允许的文件扩展名
|
||||
processing_status = {} # 全局字典用于存储处理状态和统计信息
|
||||
|
||||
def allowed_file(filename):
|
||||
"""检查文件是否是允许的类型"""
|
||||
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
||||
|
||||
@app.route('/')
|
||||
def upload_form():
|
||||
"""显示文件上传表单"""
|
||||
return render_template('main.html')
|
||||
|
||||
@app.route('/upload', methods=['POST'])
|
||||
def upload_file():
|
||||
"""处理文件上传和启动异步处理"""
|
||||
if 'file' not in request.files:
|
||||
flash('没有文件部分', 'error')
|
||||
return redirect(url_for('upload_form'))
|
||||
|
||||
file = request.files['file']
|
||||
|
||||
if file.filename == '':
|
||||
flash('未选择文件', 'error')
|
||||
return redirect(url_for('upload_form'))
|
||||
|
||||
if file and allowed_file(file.filename):
|
||||
filename = secure_filename(file.filename)
|
||||
filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
|
||||
filepath = os.path.abspath(filepath) # 转换为绝对路径
|
||||
|
||||
try:
|
||||
file.save(filepath)
|
||||
print(f'文件已保存到 {filepath}')
|
||||
|
||||
# 初始化处理状态
|
||||
processing_status[filename] = {'status': 'processing', 'stats': None}
|
||||
|
||||
# 启动后台线程处理文件
|
||||
thread = threading.Thread(target=handle_file_processing, args=(filepath, filename))
|
||||
thread.start()
|
||||
|
||||
# 重定向到等待页面,并传递文件名以跟踪状态
|
||||
return redirect(url_for('waiting_page', filename=filename))
|
||||
except Exception as e:
|
||||
flash(f'文件上传失败: {str(e)}', 'error')
|
||||
return redirect(url_for('upload_failure'))
|
||||
else:
|
||||
flash('文件类型不允许', 'error')
|
||||
return redirect(url_for('upload_form'))
|
||||
|
||||
|
||||
@app.route('/waiting/<filename>')
|
||||
def waiting_page(filename):
|
||||
"""显示等待页面,并传递文件名"""
|
||||
return render_template('waiting.html', filename=filename)
|
||||
|
||||
|
||||
@app.route('/status/<filename>')
|
||||
def check_status(filename):
|
||||
"""检查文件处理状态,并返回状态和统计信息"""
|
||||
status_info = processing_status.get(filename, {'status': 'processing', 'stats': None})
|
||||
return json.dumps(status_info)
|
||||
|
||||
@app.route('/upload-success')
|
||||
def upload_success():
|
||||
"""文件处理成功页面"""
|
||||
filename = request.args.get('filename')
|
||||
stats = processing_status.get(filename, {}).get('stats', {})
|
||||
return render_template('success.html', stats=stats)
|
||||
|
||||
@app.route('/upload-failure')
|
||||
def upload_failure():
|
||||
"""文件处理失败页面"""
|
||||
filename = request.args.get('filename')
|
||||
stats = processing_status.get(filename, {}).get('stats', {})
|
||||
return render_template('failure.html', stats=stats)
|
||||
|
||||
def handle_file_processing(filepath, filename):
|
||||
"""异步处理文件并根据统计结果设置处理状态"""
|
||||
try:
|
||||
script_path = r'E:\ICTfront\BCAT\using_example.py' # 请根据实际路径更新
|
||||
stats_output_path = os.path.join(app.config['UPLOAD_FOLDER'], f'stats_{filename}.json')
|
||||
|
||||
# 执行外部脚本,传递文件路径和统计信息文件路径作为参数
|
||||
result = subprocess.run(
|
||||
['python', script_path, filepath, stats_output_path],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
encoding='utf-8'
|
||||
)
|
||||
|
||||
print(f"脚本标准输出: {result.stdout}")
|
||||
print(f"脚本标准错误: {result.stderr}")
|
||||
|
||||
if result.returncode == 0:
|
||||
# 读取统计信息
|
||||
with open(stats_output_path, 'r', encoding='utf-8') as f:
|
||||
stats = json.load(f)
|
||||
|
||||
# 获取“不良”标签的占比
|
||||
bad_percentage = float(stats.get("不良", {}).get("percentage", "0%").strip('%'))
|
||||
|
||||
if bad_percentage > 5.0:
|
||||
# 失败占比超过5%,标记为失败
|
||||
processing_status[filename] = {
|
||||
'status': 'failure',
|
||||
'stats': stats
|
||||
}
|
||||
else:
|
||||
# 成功
|
||||
processing_status[filename] = {
|
||||
'status': 'success',
|
||||
'stats': stats
|
||||
}
|
||||
else:
|
||||
# 脚本执行失败
|
||||
processing_status[filename] = {
|
||||
'status': 'failure',
|
||||
'stats': None
|
||||
}
|
||||
except Exception as e:
|
||||
print(f"运行脚本时出错: {str(e)}")
|
||||
processing_status[filename] = {
|
||||
'status': 'failure',
|
||||
'stats': None
|
||||
}
|
||||
|
||||
if __name__ == '__main__':
|
||||
# 如果上传文件夹不存在,则创建
|
||||
if not os.path.exists(app.config['UPLOAD_FOLDER']):
|
||||
os.makedirs(app.config['UPLOAD_FOLDER'])
|
||||
app.run(debug=True)
|
||||
@@ -1,241 +0,0 @@
|
||||
import torch
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from torch.utils.data import DataLoader, TensorDataset
|
||||
from tqdm import tqdm
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import chardet
|
||||
|
||||
# 导入改进版模型的组件
|
||||
from model_pro.MHA import MultiHeadAttentionLayer
|
||||
from model_pro.classifier import FinalClassifier
|
||||
from model_pro.BERT_CTM import BERT_CTM_Model
|
||||
|
||||
class ModelManager:
|
||||
_instance = None
|
||||
_initialized = False
|
||||
|
||||
def __new__(cls):
|
||||
if cls._instance is None:
|
||||
cls._instance = super(ModelManager, cls).__new__(cls)
|
||||
return cls._instance
|
||||
|
||||
def __init__(self):
|
||||
if not self._initialized:
|
||||
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
||||
self.classifier_model = None
|
||||
self.attention_model = None
|
||||
self.bert_ctm_model = None
|
||||
self._initialized = True
|
||||
|
||||
def load_models(self, model_save_path, bert_model_path, ctm_tokenizer_path):
|
||||
"""加载所有需要的模型"""
|
||||
try:
|
||||
if self.classifier_model is None:
|
||||
self.classifier_model = torch.load(model_save_path, map_location=self.device)
|
||||
self.classifier_model.eval()
|
||||
|
||||
if self.attention_model is None:
|
||||
self.attention_model = MultiHeadAttentionLayer(embed_size=768, num_heads=8)
|
||||
self.attention_model.to(self.device)
|
||||
self.attention_model.eval()
|
||||
|
||||
if self.bert_ctm_model is None:
|
||||
self.bert_ctm_model = BERT_CTM_Model(
|
||||
bert_model_path=bert_model_path,
|
||||
ctm_tokenizer_path=ctm_tokenizer_path
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"模型加载失败: {e}")
|
||||
return False
|
||||
|
||||
def predict_batch(self, texts, batch_size=32):
|
||||
"""批量预测文本情感"""
|
||||
try:
|
||||
all_predictions = []
|
||||
all_probabilities = []
|
||||
|
||||
# 分批处理文本
|
||||
for i in range(0, len(texts), batch_size):
|
||||
batch_texts = texts[i:i + batch_size]
|
||||
|
||||
# 获取文本嵌入
|
||||
embeddings = self.bert_ctm_model.get_bert_embeddings(batch_texts)
|
||||
|
||||
# 转换为tensor
|
||||
batch_x = torch.tensor(embeddings, dtype=torch.float32).to(self.device)
|
||||
batch_x = torch.mean(batch_x, dim=1)
|
||||
|
||||
with torch.no_grad():
|
||||
# 使用注意力机制
|
||||
attention_output = self.attention_model(batch_x, batch_x, batch_x)
|
||||
# 获取分类结果
|
||||
outputs = self.classifier_model(attention_output)
|
||||
outputs = torch.mean(outputs, dim=1)
|
||||
# 获取预测概率
|
||||
probabilities = torch.softmax(outputs, dim=1)
|
||||
# 获取预测标签
|
||||
_, predicted = torch.max(outputs, 1)
|
||||
|
||||
all_predictions.extend(predicted.cpu().numpy())
|
||||
all_probabilities.extend(probabilities.cpu().numpy())
|
||||
|
||||
return all_predictions, all_probabilities
|
||||
except Exception as e:
|
||||
print(f"预测过程中出现错误: {e}")
|
||||
return None, None
|
||||
|
||||
# 创建全局的模型管理器实例
|
||||
model_manager = ModelManager()
|
||||
|
||||
def detect_file_encoding(file_path, num_bytes=10000):
|
||||
"""
|
||||
使用 chardet 检测文件的编码。
|
||||
|
||||
:param file_path: 文件路径
|
||||
:param num_bytes: 用于检测的字节数
|
||||
:return: 检测到的编码
|
||||
"""
|
||||
with open(file_path, 'rb') as f:
|
||||
rawdata = f.read(num_bytes)
|
||||
result = chardet.detect(rawdata)
|
||||
encoding = result['encoding']
|
||||
confidence = result['confidence']
|
||||
print(f"检测到的编码: {encoding}, 置信度: {confidence}")
|
||||
return encoding
|
||||
|
||||
|
||||
def get_bert_ctm_embeddings(texts, bert_model_path, ctm_tokenizer_path, n_components=12, num_epochs=20):
|
||||
# 创建BERT_CTM_Model实例
|
||||
bert_ctm_model = BERT_CTM_Model(
|
||||
bert_model_path=bert_model_path,
|
||||
ctm_tokenizer_path=ctm_tokenizer_path,
|
||||
n_components=n_components,
|
||||
num_epochs=num_epochs
|
||||
)
|
||||
# 获取嵌入
|
||||
embeddings = bert_ctm_model.get_bert_embeddings(texts)
|
||||
return embeddings
|
||||
|
||||
|
||||
def prepare_dataloader(features, batch_size):
|
||||
tensor_x = torch.tensor(features, dtype=torch.float32)
|
||||
dataset = TensorDataset(tensor_x)
|
||||
return DataLoader(dataset, batch_size=batch_size, shuffle=False)
|
||||
|
||||
|
||||
def predict(model_save_path, input_data_path, output_path, bert_model_path, ctm_tokenizer_path, stats_output_path,
|
||||
batch_size=128,
|
||||
num_classes=2):
|
||||
try:
|
||||
# 加载模型
|
||||
print("加载模型...")
|
||||
if not model_manager.load_models(model_save_path, bert_model_path, ctm_tokenizer_path):
|
||||
return False
|
||||
|
||||
# 检测文件编码
|
||||
encoding = detect_file_encoding(input_data_path)
|
||||
|
||||
# 读取输入数据
|
||||
print("读取输入数据...")
|
||||
data = pd.read_csv(input_data_path, encoding=encoding)
|
||||
texts = data['TEXT'].tolist()
|
||||
|
||||
# 生成嵌入
|
||||
print("生成文本嵌入...")
|
||||
embeddings = get_bert_ctm_embeddings(texts, bert_model_path, ctm_tokenizer_path)
|
||||
|
||||
# 准备DataLoader
|
||||
data_loader = prepare_dataloader(embeddings, batch_size)
|
||||
|
||||
# 存储预测结果
|
||||
all_predictions = []
|
||||
all_probabilities = []
|
||||
|
||||
print("开始预测...")
|
||||
with torch.no_grad():
|
||||
for batch in tqdm(data_loader, desc="预测进度"):
|
||||
batch_x = batch[0].to(model_manager.device)
|
||||
batch_x = torch.mean(batch_x, dim=1)
|
||||
|
||||
# 使用注意力机制
|
||||
attention_output = model_manager.attention_model(batch_x, batch_x, batch_x)
|
||||
|
||||
# 获取分类结果
|
||||
outputs = model_manager.classifier_model(attention_output)
|
||||
outputs = torch.mean(outputs, dim=1)
|
||||
|
||||
# 获取预测概率
|
||||
probabilities = torch.softmax(outputs, dim=1)
|
||||
|
||||
# 获取预测标签
|
||||
_, predicted = torch.max(outputs, 1)
|
||||
|
||||
all_predictions.extend(predicted.cpu().numpy())
|
||||
all_probabilities.extend(probabilities.cpu().numpy())
|
||||
|
||||
# 添加预测结果和概率到数据框
|
||||
data['Predicted_Label'] = all_predictions
|
||||
data['Confidence'] = [prob[pred] for prob, pred in zip(all_probabilities, all_predictions)]
|
||||
|
||||
# 保存预测结果
|
||||
data.to_csv(output_path, index=False, encoding='utf-8')
|
||||
print(f"预测结果已保存到 {output_path}")
|
||||
|
||||
# 统计标签的个数和占比
|
||||
label_counts = data['Predicted_Label'].value_counts()
|
||||
total_count = len(data)
|
||||
stats = {
|
||||
'统计信息': {
|
||||
'总样本数': total_count,
|
||||
'各类别统计': {}
|
||||
}
|
||||
}
|
||||
|
||||
for label, count in label_counts.items():
|
||||
label_name = "良好" if label == 0 else "不良"
|
||||
percentage = (count / total_count) * 100
|
||||
confidence_mean = data[data['Predicted_Label'] == label]['Confidence'].mean()
|
||||
|
||||
stats['统计信息']['各类别统计'][label_name] = {
|
||||
'数量': int(count),
|
||||
'占比': f"{percentage:.2f}%",
|
||||
'平均置信度': f"{confidence_mean:.2f}"
|
||||
}
|
||||
print(f"标签: {label_name}, 数量: {count}, 占比: {percentage:.2f}%, 平均置信度: {confidence_mean:.2f}")
|
||||
|
||||
# 将统计信息保存到 JSON 文件
|
||||
with open(stats_output_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(stats, f, ensure_ascii=False, indent=4)
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"预测过程中出现错误: {e}")
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != 3:
|
||||
print("使用方法: python predict.py <input_data_path> <stats_output_path>")
|
||||
sys.exit(1)
|
||||
|
||||
input_data_path = sys.argv[1]
|
||||
stats_output_path = sys.argv[2]
|
||||
|
||||
# 定义路径
|
||||
model_save_path = 'model_pro/final_model.pt'
|
||||
output_path = 'model_pro/predictions.csv'
|
||||
bert_model_path = 'model_pro/bert_model'
|
||||
ctm_tokenizer_path = 'model_pro/sentence_bert_model'
|
||||
|
||||
# 执行预测
|
||||
success = predict(model_save_path, input_data_path, output_path, bert_model_path, ctm_tokenizer_path,
|
||||
stats_output_path)
|
||||
|
||||
if success:
|
||||
sys.exit(0)
|
||||
else:
|
||||
sys.exit(1)
|
||||
@@ -1,98 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>检测到不良言论!</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f4f4f4;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
}
|
||||
.container {
|
||||
text-align: center;
|
||||
width: 600px;
|
||||
background-color: #fff;
|
||||
padding: 50px;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 0 15px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
h1 {
|
||||
font-size: 28px; /* 与 main 的标题一致 */
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.status-box {
|
||||
margin-top: 20px;
|
||||
padding: 20px;
|
||||
font-size: 18px; /* 与 main 的字体大小一致 */
|
||||
background-color: #ffebee;
|
||||
color: #c62828;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #c62828;
|
||||
}
|
||||
.stats-table {
|
||||
margin-top: 30px;
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.stats-table th, .stats-table td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 12px;
|
||||
}
|
||||
.stats-table th {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
a {
|
||||
display: inline-block;
|
||||
margin-top: 30px;
|
||||
padding: 15px 40px;
|
||||
font-size: 18px;
|
||||
background-color: #000;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 30px;
|
||||
font-weight: bold;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
a:hover {
|
||||
background-color: #333;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>数据分析完毕!</h1>
|
||||
<div class="status-box">
|
||||
检测到不良言论!
|
||||
</div>
|
||||
{% if stats %}
|
||||
<h3>统计信息:</h3>
|
||||
<table class="stats-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>标签</th>
|
||||
<th>个数</th>
|
||||
<th>占比</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for label, info in stats.items() %}
|
||||
<tr>
|
||||
<td>{{ label }}</td>
|
||||
<td>{{ info.count }}</td>
|
||||
<td>{{ info.percentage }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p>没有统计信息可显示。</p>
|
||||
{% endif %}
|
||||
<a href="/">返回首页</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,311 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>文件上传</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f4f4f4;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 全局遮罩层 */
|
||||
#global-blur {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.2); /* 半透明遮罩 */
|
||||
z-index: 2; /* 在 container 上面 */
|
||||
display: none; /* 默认隐藏,拖拽时显示 */
|
||||
pointer-events: none; /* 允许事件穿透模糊层 */
|
||||
}
|
||||
|
||||
/* main 容器 */
|
||||
.container {
|
||||
z-index: 1; /* 在模糊层之下 */
|
||||
text-align: center;
|
||||
width: 600px;
|
||||
background-color: #fff;
|
||||
padding: 50px;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 0 15px rgba(0, 0, 0, 0.1);
|
||||
transition: filter 0.3s ease; /* 添加过渡效果 */
|
||||
}
|
||||
|
||||
/* 当模糊时,应用到 container */
|
||||
.container.blurred {
|
||||
filter: blur(10px); /* 对 main 容器应用模糊效果 */
|
||||
}
|
||||
|
||||
.upload-box {
|
||||
border: 2px dashed #4caf50;
|
||||
padding: 40px;
|
||||
cursor: pointer;
|
||||
border-radius: 15px;
|
||||
margin-top: 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.upload-box input[type="file"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.upload-box label {
|
||||
color: #4caf50;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.file-display {
|
||||
display: none;
|
||||
margin-top: 20px;
|
||||
padding: 20px;
|
||||
background-color: #f9f9f9;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 15px;
|
||||
text-align: left;
|
||||
position: relative; /* 修正删除按钮错位问题 */
|
||||
}
|
||||
|
||||
.file-display img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
vertical-align: middle;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.file-display span {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.file-display .file-size {
|
||||
font-size: 16px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.file-display .remove-btn {
|
||||
position: absolute;
|
||||
right: 10px; /* 调整删除按钮的位置 */
|
||||
top: 50%;
|
||||
transform: translateY(-50%); /* 垂直居中 */
|
||||
background-color: red;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.submit-button {
|
||||
display: inline-block;
|
||||
background-color: #000;
|
||||
color: white;
|
||||
padding: 15px 40px;
|
||||
border-radius: 30px;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.submit-button:hover {
|
||||
background-color: #333;
|
||||
}
|
||||
|
||||
.submit-button::after {
|
||||
content: '→';
|
||||
font-size: 18px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
/* 弹窗样式 */
|
||||
.popup {
|
||||
position: fixed;
|
||||
top: 35%;
|
||||
left: 30%;
|
||||
width: 40%;
|
||||
height: 30%;
|
||||
background-color: rgba(0, 0, 0, 0.8); /* 半透明黑色背景 */
|
||||
border-radius: 15px;
|
||||
display: none;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
z-index: 9999; /* 确保弹窗在最上层 */
|
||||
color: white;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
padding: 20px;
|
||||
flex-direction: column; /* 让文字上下排列 */
|
||||
backdrop-filter: blur(5px); /* 添加背景模糊效果 */
|
||||
}
|
||||
|
||||
.popup h2 {
|
||||
margin-bottom: 10px;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.popup p {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
/* 美化弹窗内容 */
|
||||
.popup .upload-icon {
|
||||
font-size: 60px;
|
||||
margin-bottom: 20px;
|
||||
color: #4caf50;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- 全局模糊层 -->
|
||||
<div id="global-blur"></div>
|
||||
|
||||
<div class="container" id="container">
|
||||
<h1 style="font-size: 28px;">文件上传</h1>
|
||||
<p style="font-size: 18px;">请选择一个 CSV 文件上传,我们将为您分析数据</p>
|
||||
|
||||
<!-- 文件上传区域 -->
|
||||
<form id="upload-form" action="/upload" method="post" enctype="multipart/form-data">
|
||||
<div class="upload-box" id="upload-box">
|
||||
<label>点击这里或拖拽文件上传</label>
|
||||
<input id="file-upload" type="file" name="file" accept=".csv" required>
|
||||
</div>
|
||||
|
||||
<!-- 文件展示区域 -->
|
||||
<div id="file-display" class="file-display">
|
||||
<img src="https://img.icons8.com/color/48/000000/csv.png" alt="CSV">
|
||||
<span id="file-name">文件名</span>
|
||||
<span class="file-size" id="file-size">大小</span>
|
||||
<button type="button" class="remove-btn" id="remove-btn">×</button>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="submit-button">Start now</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- 弹窗 -->
|
||||
<div class="popup" id="popup">
|
||||
<h2>文件拖拽到此处即可上传</h2>
|
||||
<p>支持的文件格式:CSV</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const fileInput = document.getElementById('file-upload');
|
||||
const fileDisplay = document.getElementById('file-display');
|
||||
const fileNameDisplay = document.getElementById('file-name');
|
||||
const fileSizeDisplay = document.getElementById('file-size');
|
||||
const removeBtn = document.getElementById('remove-btn');
|
||||
const uploadBox = document.getElementById('upload-box');
|
||||
const popup = document.getElementById('popup');
|
||||
const globalBlur = document.getElementById('global-blur');
|
||||
const container = document.getElementById('container');
|
||||
|
||||
// 点击上传区域时,触发文件选择框
|
||||
uploadBox.addEventListener('click', function() {
|
||||
fileInput.click();
|
||||
});
|
||||
|
||||
// 监听文件选择事件
|
||||
fileInput.addEventListener('change', function () {
|
||||
const file = fileInput.files[0];
|
||||
if (file) {
|
||||
if (file.name.endsWith('.csv')) {
|
||||
fileNameDisplay.textContent = file.name;
|
||||
fileSizeDisplay.textContent = `, ${Math.round(file.size / 1024)} KB`;
|
||||
fileDisplay.style.display = 'block';
|
||||
} else {
|
||||
alert('只允许上传 CSV 文件');
|
||||
fileInput.value = ''; // 清除文件
|
||||
}
|
||||
// 隐藏模糊层和弹窗
|
||||
globalBlur.style.display = 'none';
|
||||
popup.style.display = 'none';
|
||||
container.classList.remove('blurred'); // 移除模糊效果
|
||||
}
|
||||
});
|
||||
|
||||
// 删除按钮逻辑
|
||||
removeBtn.addEventListener('click', function () {
|
||||
fileDisplay.style.display = 'none';
|
||||
fileInput.value = '';
|
||||
// 隐藏模糊层和弹窗
|
||||
globalBlur.style.display = 'none';
|
||||
popup.style.display = 'none';
|
||||
container.classList.remove('blurred'); // 移除模糊效果
|
||||
});
|
||||
|
||||
// 监听整个页面的拖拽进入事件,显示弹窗和模糊层
|
||||
document.addEventListener('dragenter', function (e) {
|
||||
e.preventDefault();
|
||||
globalBlur.style.display = 'block'; // 显示全局模糊层
|
||||
popup.style.display = 'flex'; // 显示弹出框
|
||||
container.classList.add('blurred'); // 对 container 添加模糊效果
|
||||
});
|
||||
|
||||
// 监听拖拽离开弹窗区域,隐藏弹窗和模糊层
|
||||
document.addEventListener('dragleave', function (e) {
|
||||
e.preventDefault();
|
||||
// 如果鼠标离开整个窗口,则隐藏模糊层和弹窗
|
||||
if (e.clientX <= 0 || e.clientY <= 0 || e.clientX >= window.innerWidth || e.clientY >= window.innerHeight) {
|
||||
globalBlur.style.display = 'none';
|
||||
popup.style.display = 'none';
|
||||
container.classList.remove('blurred');
|
||||
}
|
||||
});
|
||||
|
||||
// 在弹窗上允许拖拽
|
||||
popup.addEventListener('dragover', function (e) {
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
// 在弹窗上接收文件
|
||||
popup.addEventListener('drop', function (e) {
|
||||
e.preventDefault();
|
||||
globalBlur.style.display = 'none';
|
||||
popup.style.display = 'none';
|
||||
container.classList.remove('blurred');
|
||||
|
||||
const file = e.dataTransfer.files[0];
|
||||
if (file && file.name.endsWith('.csv')) {
|
||||
fileInput.files = e.dataTransfer.files;
|
||||
const event = new Event('change');
|
||||
fileInput.dispatchEvent(event);
|
||||
} else {
|
||||
alert('只允许上传 CSV 文件');
|
||||
}
|
||||
});
|
||||
|
||||
// 在页面其他地方的 drop 事件,隐藏模糊层和弹窗
|
||||
document.addEventListener('drop', function (e) {
|
||||
e.preventDefault();
|
||||
globalBlur.style.display = 'none';
|
||||
popup.style.display = 'none';
|
||||
container.classList.remove('blurred');
|
||||
// 不处理文件上传
|
||||
});
|
||||
|
||||
// 防止在页面其他位置的拖拽行为
|
||||
document.addEventListener('dragover', function (e) {
|
||||
e.preventDefault();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,98 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>一切正常!</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f4f4f4;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
}
|
||||
.container {
|
||||
text-align: center;
|
||||
width: 600px;
|
||||
background-color: #fff;
|
||||
padding: 50px;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 0 15px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
h1 {
|
||||
font-size: 28px; /* 和 main 的标题一致 */
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.status-box {
|
||||
margin-top: 20px;
|
||||
padding: 20px;
|
||||
font-size: 18px; /* 和 main 的字体大小一致 */
|
||||
background-color: #e0f7e9;
|
||||
color: #2e7d32;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #2e7d32;
|
||||
}
|
||||
.stats-table {
|
||||
margin-top: 30px;
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.stats-table th, .stats-table td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 12px;
|
||||
}
|
||||
.stats-table th {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
a {
|
||||
display: inline-block;
|
||||
margin-top: 30px;
|
||||
padding: 15px 40px;
|
||||
font-size: 18px;
|
||||
background-color: #000;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 30px;
|
||||
font-weight: bold;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
a:hover {
|
||||
background-color: #333;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>数据分析完毕!</h1>
|
||||
<div class="status-box">
|
||||
一切正常!
|
||||
</div>
|
||||
{% if stats %}
|
||||
<h3>统计信息:</h3>
|
||||
<table class="stats-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>标签</th>
|
||||
<th>个数</th>
|
||||
<th>占比</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for label, info in stats.items() %}
|
||||
<tr>
|
||||
<td>{{ label }}</td>
|
||||
<td>{{ info.count }}</td>
|
||||
<td>{{ info.percentage }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p>没有统计信息可显示。</p>
|
||||
{% endif %}
|
||||
<a href="/">返回首页</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,86 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>处理中</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f4f4f4;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
}
|
||||
.container {
|
||||
text-align: center;
|
||||
width: 600px; /* 调整容器宽度与 main.html 一致 */
|
||||
background-color: #fff;
|
||||
padding: 50px;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 0 15px rgba(0, 0, 0, 0.1); /* 与 main.html 相同的阴影效果 */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center; /* 水平居中 */
|
||||
}
|
||||
h1 {
|
||||
font-size: 28px; /* 与 main.html 标题保持一致 */
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
p {
|
||||
font-size: 18px; /* 与 main.html 副标题大小一致 */
|
||||
color: #888;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.loading-icon {
|
||||
margin-top: 20px;
|
||||
width: 50px; /* 调整图标大小 */
|
||||
height: 50px;
|
||||
border: 5px solid #f3f3f3;
|
||||
border-radius: 50%;
|
||||
border-top: 5px solid #4caf50;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>处理中,请稍候...</h1>
|
||||
<div class="loading-icon"></div>
|
||||
<p>您的文件正在分析中,请稍等片刻。</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function checkStatus() {
|
||||
fetch('{{ url_for('check_status', filename=filename) }}')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.status === 'success') {
|
||||
// 成功,跳转到成功页面,并传递文件名以获取统计信息
|
||||
window.location.href = "{{ url_for('upload_success') }}?filename=" + encodeURIComponent('{{ filename }}');
|
||||
} else if (data.status === 'failure') {
|
||||
// 失败,跳转到失败页面,并传递文件名以获取统计信息
|
||||
window.location.href = "{{ url_for('upload_failure') }}?filename=" + encodeURIComponent('{{ filename }}');
|
||||
} else {
|
||||
// 继续等待
|
||||
setTimeout(checkStatus, 2000);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
setTimeout(checkStatus, 2000);
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
checkStatus();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user