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