戒酒的李白

Delete the BCAT frontend page.

1 -import os  
2 -import subprocess  
3 -import threading  
4 -from flask import Flask, render_template, request, redirect, url_for, flash  
5 -from werkzeug.utils import secure_filename  
6 -import json  
7 -  
8 -app = Flask(__name__)  
9 -app.config['UPLOAD_FOLDER'] = 'data/' # 上传文件的保存目录  
10 -app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 文件大小限制为16MB  
11 -app.secret_key = 'secret_key' # 用于Flash消息的密钥  
12 -ALLOWED_EXTENSIONS = {'csv'} # 允许的文件扩展名  
13 -processing_status = {} # 全局字典用于存储处理状态和统计信息  
14 -  
15 -def allowed_file(filename):  
16 - """检查文件是否是允许的类型"""  
17 - return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS  
18 -  
19 -@app.route('/')  
20 -def upload_form():  
21 - """显示文件上传表单"""  
22 - return render_template('main.html')  
23 -  
24 -@app.route('/upload', methods=['POST'])  
25 -def upload_file():  
26 - """处理文件上传和启动异步处理"""  
27 - if 'file' not in request.files:  
28 - flash('没有文件部分', 'error')  
29 - return redirect(url_for('upload_form'))  
30 -  
31 - file = request.files['file']  
32 -  
33 - if file.filename == '':  
34 - flash('未选择文件', 'error')  
35 - return redirect(url_for('upload_form'))  
36 -  
37 - if file and allowed_file(file.filename):  
38 - filename = secure_filename(file.filename)  
39 - filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)  
40 - filepath = os.path.abspath(filepath) # 转换为绝对路径  
41 -  
42 - try:  
43 - file.save(filepath)  
44 - print(f'文件已保存到 {filepath}')  
45 -  
46 - # 初始化处理状态  
47 - processing_status[filename] = {'status': 'processing', 'stats': None}  
48 -  
49 - # 启动后台线程处理文件  
50 - thread = threading.Thread(target=handle_file_processing, args=(filepath, filename))  
51 - thread.start()  
52 -  
53 - # 重定向到等待页面,并传递文件名以跟踪状态  
54 - return redirect(url_for('waiting_page', filename=filename))  
55 - except Exception as e:  
56 - flash(f'文件上传失败: {str(e)}', 'error')  
57 - return redirect(url_for('upload_failure'))  
58 - else:  
59 - flash('文件类型不允许', 'error')  
60 - return redirect(url_for('upload_form'))  
61 -  
62 -  
63 -@app.route('/waiting/<filename>')  
64 -def waiting_page(filename):  
65 - """显示等待页面,并传递文件名"""  
66 - return render_template('waiting.html', filename=filename)  
67 -  
68 -  
69 -@app.route('/status/<filename>')  
70 -def check_status(filename):  
71 - """检查文件处理状态,并返回状态和统计信息"""  
72 - status_info = processing_status.get(filename, {'status': 'processing', 'stats': None})  
73 - return json.dumps(status_info)  
74 -  
75 -@app.route('/upload-success')  
76 -def upload_success():  
77 - """文件处理成功页面"""  
78 - filename = request.args.get('filename')  
79 - stats = processing_status.get(filename, {}).get('stats', {})  
80 - return render_template('success.html', stats=stats)  
81 -  
82 -@app.route('/upload-failure')  
83 -def upload_failure():  
84 - """文件处理失败页面"""  
85 - filename = request.args.get('filename')  
86 - stats = processing_status.get(filename, {}).get('stats', {})  
87 - return render_template('failure.html', stats=stats)  
88 -  
89 -def handle_file_processing(filepath, filename):  
90 - """异步处理文件并根据统计结果设置处理状态"""  
91 - try:  
92 - script_path = r'E:\ICTfront\BCAT\using_example.py' # 请根据实际路径更新  
93 - stats_output_path = os.path.join(app.config['UPLOAD_FOLDER'], f'stats_{filename}.json')  
94 -  
95 - # 执行外部脚本,传递文件路径和统计信息文件路径作为参数  
96 - result = subprocess.run(  
97 - ['python', script_path, filepath, stats_output_path],  
98 - stdout=subprocess.PIPE,  
99 - stderr=subprocess.PIPE,  
100 - text=True,  
101 - encoding='utf-8'  
102 - )  
103 -  
104 - print(f"脚本标准输出: {result.stdout}")  
105 - print(f"脚本标准错误: {result.stderr}")  
106 -  
107 - if result.returncode == 0:  
108 - # 读取统计信息  
109 - with open(stats_output_path, 'r', encoding='utf-8') as f:  
110 - stats = json.load(f)  
111 -  
112 - # 获取“不良”标签的占比  
113 - bad_percentage = float(stats.get("不良", {}).get("percentage", "0%").strip('%'))  
114 -  
115 - if bad_percentage > 5.0:  
116 - # 失败占比超过5%,标记为失败  
117 - processing_status[filename] = {  
118 - 'status': 'failure',  
119 - 'stats': stats  
120 - }  
121 - else:  
122 - # 成功  
123 - processing_status[filename] = {  
124 - 'status': 'success',  
125 - 'stats': stats  
126 - }  
127 - else:  
128 - # 脚本执行失败  
129 - processing_status[filename] = {  
130 - 'status': 'failure',  
131 - 'stats': None  
132 - }  
133 - except Exception as e:  
134 - print(f"运行脚本时出错: {str(e)}")  
135 - processing_status[filename] = {  
136 - 'status': 'failure',  
137 - 'stats': None  
138 - }  
139 -  
140 -if __name__ == '__main__':  
141 - # 如果上传文件夹不存在,则创建  
142 - if not os.path.exists(app.config['UPLOAD_FOLDER']):  
143 - os.makedirs(app.config['UPLOAD_FOLDER'])  
144 - app.run(debug=True)  
1 -import torch  
2 -import pandas as pd  
3 -import numpy as np  
4 -from torch.utils.data import DataLoader, TensorDataset  
5 -from tqdm import tqdm  
6 -import os  
7 -import sys  
8 -import json  
9 -import chardet  
10 -  
11 -# 导入改进版模型的组件  
12 -from model_pro.MHA import MultiHeadAttentionLayer  
13 -from model_pro.classifier import FinalClassifier  
14 -from model_pro.BERT_CTM import BERT_CTM_Model  
15 -  
16 -class ModelManager:  
17 - _instance = None  
18 - _initialized = False  
19 -  
20 - def __new__(cls):  
21 - if cls._instance is None:  
22 - cls._instance = super(ModelManager, cls).__new__(cls)  
23 - return cls._instance  
24 -  
25 - def __init__(self):  
26 - if not self._initialized:  
27 - self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")  
28 - self.classifier_model = None  
29 - self.attention_model = None  
30 - self.bert_ctm_model = None  
31 - self._initialized = True  
32 -  
33 - def load_models(self, model_save_path, bert_model_path, ctm_tokenizer_path):  
34 - """加载所有需要的模型"""  
35 - try:  
36 - if self.classifier_model is None:  
37 - self.classifier_model = torch.load(model_save_path, map_location=self.device)  
38 - self.classifier_model.eval()  
39 -  
40 - if self.attention_model is None:  
41 - self.attention_model = MultiHeadAttentionLayer(embed_size=768, num_heads=8)  
42 - self.attention_model.to(self.device)  
43 - self.attention_model.eval()  
44 -  
45 - if self.bert_ctm_model is None:  
46 - self.bert_ctm_model = BERT_CTM_Model(  
47 - bert_model_path=bert_model_path,  
48 - ctm_tokenizer_path=ctm_tokenizer_path  
49 - )  
50 - return True  
51 - except Exception as e:  
52 - print(f"模型加载失败: {e}")  
53 - return False  
54 -  
55 - def predict_batch(self, texts, batch_size=32):  
56 - """批量预测文本情感"""  
57 - try:  
58 - all_predictions = []  
59 - all_probabilities = []  
60 -  
61 - # 分批处理文本  
62 - for i in range(0, len(texts), batch_size):  
63 - batch_texts = texts[i:i + batch_size]  
64 -  
65 - # 获取文本嵌入  
66 - embeddings = self.bert_ctm_model.get_bert_embeddings(batch_texts)  
67 -  
68 - # 转换为tensor  
69 - batch_x = torch.tensor(embeddings, dtype=torch.float32).to(self.device)  
70 - batch_x = torch.mean(batch_x, dim=1)  
71 -  
72 - with torch.no_grad():  
73 - # 使用注意力机制  
74 - attention_output = self.attention_model(batch_x, batch_x, batch_x)  
75 - # 获取分类结果  
76 - outputs = self.classifier_model(attention_output)  
77 - outputs = torch.mean(outputs, dim=1)  
78 - # 获取预测概率  
79 - probabilities = torch.softmax(outputs, dim=1)  
80 - # 获取预测标签  
81 - _, predicted = torch.max(outputs, 1)  
82 -  
83 - all_predictions.extend(predicted.cpu().numpy())  
84 - all_probabilities.extend(probabilities.cpu().numpy())  
85 -  
86 - return all_predictions, all_probabilities  
87 - except Exception as e:  
88 - print(f"预测过程中出现错误: {e}")  
89 - return None, None  
90 -  
91 -# 创建全局的模型管理器实例  
92 -model_manager = ModelManager()  
93 -  
94 -def detect_file_encoding(file_path, num_bytes=10000):  
95 - """  
96 - 使用 chardet 检测文件的编码。  
97 -  
98 - :param file_path: 文件路径  
99 - :param num_bytes: 用于检测的字节数  
100 - :return: 检测到的编码  
101 - """  
102 - with open(file_path, 'rb') as f:  
103 - rawdata = f.read(num_bytes)  
104 - result = chardet.detect(rawdata)  
105 - encoding = result['encoding']  
106 - confidence = result['confidence']  
107 - print(f"检测到的编码: {encoding}, 置信度: {confidence}")  
108 - return encoding  
109 -  
110 -  
111 -def get_bert_ctm_embeddings(texts, bert_model_path, ctm_tokenizer_path, n_components=12, num_epochs=20):  
112 - # 创建BERT_CTM_Model实例  
113 - bert_ctm_model = BERT_CTM_Model(  
114 - bert_model_path=bert_model_path,  
115 - ctm_tokenizer_path=ctm_tokenizer_path,  
116 - n_components=n_components,  
117 - num_epochs=num_epochs  
118 - )  
119 - # 获取嵌入  
120 - embeddings = bert_ctm_model.get_bert_embeddings(texts)  
121 - return embeddings  
122 -  
123 -  
124 -def prepare_dataloader(features, batch_size):  
125 - tensor_x = torch.tensor(features, dtype=torch.float32)  
126 - dataset = TensorDataset(tensor_x)  
127 - return DataLoader(dataset, batch_size=batch_size, shuffle=False)  
128 -  
129 -  
130 -def predict(model_save_path, input_data_path, output_path, bert_model_path, ctm_tokenizer_path, stats_output_path,  
131 - batch_size=128,  
132 - num_classes=2):  
133 - try:  
134 - # 加载模型  
135 - print("加载模型...")  
136 - if not model_manager.load_models(model_save_path, bert_model_path, ctm_tokenizer_path):  
137 - return False  
138 -  
139 - # 检测文件编码  
140 - encoding = detect_file_encoding(input_data_path)  
141 -  
142 - # 读取输入数据  
143 - print("读取输入数据...")  
144 - data = pd.read_csv(input_data_path, encoding=encoding)  
145 - texts = data['TEXT'].tolist()  
146 -  
147 - # 生成嵌入  
148 - print("生成文本嵌入...")  
149 - embeddings = get_bert_ctm_embeddings(texts, bert_model_path, ctm_tokenizer_path)  
150 -  
151 - # 准备DataLoader  
152 - data_loader = prepare_dataloader(embeddings, batch_size)  
153 -  
154 - # 存储预测结果  
155 - all_predictions = []  
156 - all_probabilities = []  
157 -  
158 - print("开始预测...")  
159 - with torch.no_grad():  
160 - for batch in tqdm(data_loader, desc="预测进度"):  
161 - batch_x = batch[0].to(model_manager.device)  
162 - batch_x = torch.mean(batch_x, dim=1)  
163 -  
164 - # 使用注意力机制  
165 - attention_output = model_manager.attention_model(batch_x, batch_x, batch_x)  
166 -  
167 - # 获取分类结果  
168 - outputs = model_manager.classifier_model(attention_output)  
169 - outputs = torch.mean(outputs, dim=1)  
170 -  
171 - # 获取预测概率  
172 - probabilities = torch.softmax(outputs, dim=1)  
173 -  
174 - # 获取预测标签  
175 - _, predicted = torch.max(outputs, 1)  
176 -  
177 - all_predictions.extend(predicted.cpu().numpy())  
178 - all_probabilities.extend(probabilities.cpu().numpy())  
179 -  
180 - # 添加预测结果和概率到数据框  
181 - data['Predicted_Label'] = all_predictions  
182 - data['Confidence'] = [prob[pred] for prob, pred in zip(all_probabilities, all_predictions)]  
183 -  
184 - # 保存预测结果  
185 - data.to_csv(output_path, index=False, encoding='utf-8')  
186 - print(f"预测结果已保存到 {output_path}")  
187 -  
188 - # 统计标签的个数和占比  
189 - label_counts = data['Predicted_Label'].value_counts()  
190 - total_count = len(data)  
191 - stats = {  
192 - '统计信息': {  
193 - '总样本数': total_count,  
194 - '各类别统计': {}  
195 - }  
196 - }  
197 -  
198 - for label, count in label_counts.items():  
199 - label_name = "良好" if label == 0 else "不良"  
200 - percentage = (count / total_count) * 100  
201 - confidence_mean = data[data['Predicted_Label'] == label]['Confidence'].mean()  
202 -  
203 - stats['统计信息']['各类别统计'][label_name] = {  
204 - '数量': int(count),  
205 - '占比': f"{percentage:.2f}%",  
206 - '平均置信度': f"{confidence_mean:.2f}"  
207 - }  
208 - print(f"标签: {label_name}, 数量: {count}, 占比: {percentage:.2f}%, 平均置信度: {confidence_mean:.2f}")  
209 -  
210 - # 将统计信息保存到 JSON 文件  
211 - with open(stats_output_path, 'w', encoding='utf-8') as f:  
212 - json.dump(stats, f, ensure_ascii=False, indent=4)  
213 -  
214 - return True  
215 - except Exception as e:  
216 - print(f"预测过程中出现错误: {e}")  
217 - return False  
218 -  
219 -  
220 -if __name__ == "__main__":  
221 - if len(sys.argv) != 3:  
222 - print("使用方法: python predict.py <input_data_path> <stats_output_path>")  
223 - sys.exit(1)  
224 -  
225 - input_data_path = sys.argv[1]  
226 - stats_output_path = sys.argv[2]  
227 -  
228 - # 定义路径  
229 - model_save_path = 'model_pro/final_model.pt'  
230 - output_path = 'model_pro/predictions.csv'  
231 - bert_model_path = 'model_pro/bert_model'  
232 - ctm_tokenizer_path = 'model_pro/sentence_bert_model'  
233 -  
234 - # 执行预测  
235 - success = predict(model_save_path, input_data_path, output_path, bert_model_path, ctm_tokenizer_path,  
236 - stats_output_path)  
237 -  
238 - if success:  
239 - sys.exit(0)  
240 - else:  
241 - sys.exit(1)  
1 -<!DOCTYPE html>  
2 -<html lang="zh">  
3 -<head>  
4 - <meta charset="UTF-8">  
5 - <title>检测到不良言论!</title>  
6 - <style>  
7 - body {  
8 - font-family: Arial, sans-serif;  
9 - background-color: #f4f4f4;  
10 - display: flex;  
11 - justify-content: center;  
12 - align-items: center;  
13 - height: 100vh;  
14 - margin: 0;  
15 - }  
16 - .container {  
17 - text-align: center;  
18 - width: 600px;  
19 - background-color: #fff;  
20 - padding: 50px;  
21 - border-radius: 15px;  
22 - box-shadow: 0 0 15px rgba(0, 0, 0, 0.1);  
23 - }  
24 - h1 {  
25 - font-size: 28px; /* 与 main 的标题一致 */  
26 - margin-bottom: 20px;  
27 - }  
28 - .status-box {  
29 - margin-top: 20px;  
30 - padding: 20px;  
31 - font-size: 18px; /* 与 main 的字体大小一致 */  
32 - background-color: #ffebee;  
33 - color: #c62828;  
34 - border-radius: 10px;  
35 - border: 1px solid #c62828;  
36 - }  
37 - .stats-table {  
38 - margin-top: 30px;  
39 - width: 100%;  
40 - border-collapse: collapse;  
41 - }  
42 - .stats-table th, .stats-table td {  
43 - border: 1px solid #ddd;  
44 - padding: 12px;  
45 - }  
46 - .stats-table th {  
47 - background-color: #f2f2f2;  
48 - }  
49 - a {  
50 - display: inline-block;  
51 - margin-top: 30px;  
52 - padding: 15px 40px;  
53 - font-size: 18px;  
54 - background-color: #000;  
55 - color: white;  
56 - text-decoration: none;  
57 - border-radius: 30px;  
58 - font-weight: bold;  
59 - transition: background-color 0.3s ease;  
60 - }  
61 - a:hover {  
62 - background-color: #333;  
63 - }  
64 - </style>  
65 -</head>  
66 -<body>  
67 - <div class="container">  
68 - <h1>数据分析完毕!</h1>  
69 - <div class="status-box">  
70 - 检测到不良言论!  
71 - </div>  
72 - {% if stats %}  
73 - <h3>统计信息:</h3>  
74 - <table class="stats-table">  
75 - <thead>  
76 - <tr>  
77 - <th>标签</th>  
78 - <th>个数</th>  
79 - <th>占比</th>  
80 - </tr>  
81 - </thead>  
82 - <tbody>  
83 - {% for label, info in stats.items() %}  
84 - <tr>  
85 - <td>{{ label }}</td>  
86 - <td>{{ info.count }}</td>  
87 - <td>{{ info.percentage }}</td>  
88 - </tr>  
89 - {% endfor %}  
90 - </tbody>  
91 - </table>  
92 - {% else %}  
93 - <p>没有统计信息可显示。</p>  
94 - {% endif %}  
95 - <a href="/">返回首页</a>  
96 - </div>  
97 -</body>  
98 -</html>  
1 -<!DOCTYPE html>  
2 -<html lang="zh">  
3 -<head>  
4 - <meta charset="UTF-8">  
5 - <meta name="viewport" content="width=device-width, initial-scale=1.0">  
6 - <title>文件上传</title>  
7 - <style>  
8 - body {  
9 - font-family: Arial, sans-serif;  
10 - background-color: #f4f4f4;  
11 - display: flex;  
12 - justify-content: center;  
13 - align-items: center;  
14 - height: 100vh;  
15 - margin: 0;  
16 - position: relative;  
17 - overflow: hidden;  
18 - }  
19 -  
20 - /* 全局遮罩层 */  
21 - #global-blur {  
22 - position: fixed;  
23 - top: 0;  
24 - left: 0;  
25 - width: 100%;  
26 - height: 100%;  
27 - background-color: rgba(0, 0, 0, 0.2); /* 半透明遮罩 */  
28 - z-index: 2; /* 在 container 上面 */  
29 - display: none; /* 默认隐藏,拖拽时显示 */  
30 - pointer-events: none; /* 允许事件穿透模糊层 */  
31 - }  
32 -  
33 - /* main 容器 */  
34 - .container {  
35 - z-index: 1; /* 在模糊层之下 */  
36 - text-align: center;  
37 - width: 600px;  
38 - background-color: #fff;  
39 - padding: 50px;  
40 - border-radius: 15px;  
41 - box-shadow: 0 0 15px rgba(0, 0, 0, 0.1);  
42 - transition: filter 0.3s ease; /* 添加过渡效果 */  
43 - }  
44 -  
45 - /* 当模糊时,应用到 container */  
46 - .container.blurred {  
47 - filter: blur(10px); /* 对 main 容器应用模糊效果 */  
48 - }  
49 -  
50 - .upload-box {  
51 - border: 2px dashed #4caf50;  
52 - padding: 40px;  
53 - cursor: pointer;  
54 - border-radius: 15px;  
55 - margin-top: 20px;  
56 - position: relative;  
57 - }  
58 -  
59 - .upload-box input[type="file"] {  
60 - display: none;  
61 - }  
62 -  
63 - .upload-box label {  
64 - color: #4caf50;  
65 - font-size: 20px;  
66 - cursor: pointer;  
67 - }  
68 -  
69 - .file-display {  
70 - display: none;  
71 - margin-top: 20px;  
72 - padding: 20px;  
73 - background-color: #f9f9f9;  
74 - border: 1px solid #ddd;  
75 - border-radius: 15px;  
76 - text-align: left;  
77 - position: relative; /* 修正删除按钮错位问题 */  
78 - }  
79 -  
80 - .file-display img {  
81 - width: 40px;  
82 - height: 40px;  
83 - vertical-align: middle;  
84 - margin-right: 10px;  
85 - }  
86 -  
87 - .file-display span {  
88 - font-size: 18px;  
89 - font-weight: bold;  
90 - }  
91 -  
92 - .file-display .file-size {  
93 - font-size: 16px;  
94 - color: #888;  
95 - }  
96 -  
97 - .file-display .remove-btn {  
98 - position: absolute;  
99 - right: 10px; /* 调整删除按钮的位置 */  
100 - top: 50%;  
101 - transform: translateY(-50%); /* 垂直居中 */  
102 - background-color: red;  
103 - color: white;  
104 - border: none;  
105 - border-radius: 50%;  
106 - width: 25px;  
107 - height: 25px;  
108 - cursor: pointer;  
109 - font-size: 16px;  
110 - line-height: 22px;  
111 - text-align: center;  
112 - }  
113 -  
114 - .submit-button {  
115 - display: inline-block;  
116 - background-color: #000;  
117 - color: white;  
118 - padding: 15px 40px;  
119 - border-radius: 30px;  
120 - font-size: 18px;  
121 - font-weight: bold;  
122 - border: none;  
123 - cursor: pointer;  
124 - margin-top: 20px;  
125 - }  
126 -  
127 - .submit-button:hover {  
128 - background-color: #333;  
129 - }  
130 -  
131 - .submit-button::after {  
132 - content: '→';  
133 - font-size: 18px;  
134 - margin-left: 8px;  
135 - }  
136 -  
137 - /* 弹窗样式 */  
138 - .popup {  
139 - position: fixed;  
140 - top: 35%;  
141 - left: 30%;  
142 - width: 40%;  
143 - height: 30%;  
144 - background-color: rgba(0, 0, 0, 0.8); /* 半透明黑色背景 */  
145 - border-radius: 15px;  
146 - display: none;  
147 - justify-content: center;  
148 - align-items: center;  
149 - text-align: center;  
150 - z-index: 9999; /* 确保弹窗在最上层 */  
151 - color: white;  
152 - font-size: 24px;  
153 - font-weight: bold;  
154 - padding: 20px;  
155 - flex-direction: column; /* 让文字上下排列 */  
156 - backdrop-filter: blur(5px); /* 添加背景模糊效果 */  
157 - }  
158 -  
159 - .popup h2 {  
160 - margin-bottom: 10px;  
161 - font-size: 32px;  
162 - }  
163 -  
164 - .popup p {  
165 - font-size: 20px;  
166 - }  
167 -  
168 - /* 美化弹窗内容 */  
169 - .popup .upload-icon {  
170 - font-size: 60px;  
171 - margin-bottom: 20px;  
172 - color: #4caf50;  
173 - }  
174 - </style>  
175 -</head>  
176 -<body>  
177 -  
178 - <!-- 全局模糊层 -->  
179 - <div id="global-blur"></div>  
180 -  
181 - <div class="container" id="container">  
182 - <h1 style="font-size: 28px;">文件上传</h1>  
183 - <p style="font-size: 18px;">请选择一个 CSV 文件上传,我们将为您分析数据</p>  
184 -  
185 - <!-- 文件上传区域 -->  
186 - <form id="upload-form" action="/upload" method="post" enctype="multipart/form-data">  
187 - <div class="upload-box" id="upload-box">  
188 - <label>点击这里或拖拽文件上传</label>  
189 - <input id="file-upload" type="file" name="file" accept=".csv" required>  
190 - </div>  
191 -  
192 - <!-- 文件展示区域 -->  
193 - <div id="file-display" class="file-display">  
194 - <img src="https://img.icons8.com/color/48/000000/csv.png" alt="CSV">  
195 - <span id="file-name">文件名</span>  
196 - <span class="file-size" id="file-size">大小</span>  
197 - <button type="button" class="remove-btn" id="remove-btn">×</button>  
198 - </div>  
199 -  
200 - <button type="submit" class="submit-button">Start now</button>  
201 - </form>  
202 - </div>  
203 -  
204 - <!-- 弹窗 -->  
205 - <div class="popup" id="popup">  
206 - <h2>文件拖拽到此处即可上传</h2>  
207 - <p>支持的文件格式:CSV</p>  
208 - </div>  
209 -  
210 - <script>  
211 - const fileInput = document.getElementById('file-upload');  
212 - const fileDisplay = document.getElementById('file-display');  
213 - const fileNameDisplay = document.getElementById('file-name');  
214 - const fileSizeDisplay = document.getElementById('file-size');  
215 - const removeBtn = document.getElementById('remove-btn');  
216 - const uploadBox = document.getElementById('upload-box');  
217 - const popup = document.getElementById('popup');  
218 - const globalBlur = document.getElementById('global-blur');  
219 - const container = document.getElementById('container');  
220 -  
221 - // 点击上传区域时,触发文件选择框  
222 - uploadBox.addEventListener('click', function() {  
223 - fileInput.click();  
224 - });  
225 -  
226 - // 监听文件选择事件  
227 - fileInput.addEventListener('change', function () {  
228 - const file = fileInput.files[0];  
229 - if (file) {  
230 - if (file.name.endsWith('.csv')) {  
231 - fileNameDisplay.textContent = file.name;  
232 - fileSizeDisplay.textContent = `, ${Math.round(file.size / 1024)} KB`;  
233 - fileDisplay.style.display = 'block';  
234 - } else {  
235 - alert('只允许上传 CSV 文件');  
236 - fileInput.value = ''; // 清除文件  
237 - }  
238 - // 隐藏模糊层和弹窗  
239 - globalBlur.style.display = 'none';  
240 - popup.style.display = 'none';  
241 - container.classList.remove('blurred'); // 移除模糊效果  
242 - }  
243 - });  
244 -  
245 - // 删除按钮逻辑  
246 - removeBtn.addEventListener('click', function () {  
247 - fileDisplay.style.display = 'none';  
248 - fileInput.value = '';  
249 - // 隐藏模糊层和弹窗  
250 - globalBlur.style.display = 'none';  
251 - popup.style.display = 'none';  
252 - container.classList.remove('blurred'); // 移除模糊效果  
253 - });  
254 -  
255 - // 监听整个页面的拖拽进入事件,显示弹窗和模糊层  
256 - document.addEventListener('dragenter', function (e) {  
257 - e.preventDefault();  
258 - globalBlur.style.display = 'block'; // 显示全局模糊层  
259 - popup.style.display = 'flex'; // 显示弹出框  
260 - container.classList.add('blurred'); // 对 container 添加模糊效果  
261 - });  
262 -  
263 - // 监听拖拽离开弹窗区域,隐藏弹窗和模糊层  
264 - document.addEventListener('dragleave', function (e) {  
265 - e.preventDefault();  
266 - // 如果鼠标离开整个窗口,则隐藏模糊层和弹窗  
267 - if (e.clientX <= 0 || e.clientY <= 0 || e.clientX >= window.innerWidth || e.clientY >= window.innerHeight) {  
268 - globalBlur.style.display = 'none';  
269 - popup.style.display = 'none';  
270 - container.classList.remove('blurred');  
271 - }  
272 - });  
273 -  
274 - // 在弹窗上允许拖拽  
275 - popup.addEventListener('dragover', function (e) {  
276 - e.preventDefault();  
277 - });  
278 -  
279 - // 在弹窗上接收文件  
280 - popup.addEventListener('drop', function (e) {  
281 - e.preventDefault();  
282 - globalBlur.style.display = 'none';  
283 - popup.style.display = 'none';  
284 - container.classList.remove('blurred');  
285 -  
286 - const file = e.dataTransfer.files[0];  
287 - if (file && file.name.endsWith('.csv')) {  
288 - fileInput.files = e.dataTransfer.files;  
289 - const event = new Event('change');  
290 - fileInput.dispatchEvent(event);  
291 - } else {  
292 - alert('只允许上传 CSV 文件');  
293 - }  
294 - });  
295 -  
296 - // 在页面其他地方的 drop 事件,隐藏模糊层和弹窗  
297 - document.addEventListener('drop', function (e) {  
298 - e.preventDefault();  
299 - globalBlur.style.display = 'none';  
300 - popup.style.display = 'none';  
301 - container.classList.remove('blurred');  
302 - // 不处理文件上传  
303 - });  
304 -  
305 - // 防止在页面其他位置的拖拽行为  
306 - document.addEventListener('dragover', function (e) {  
307 - e.preventDefault();  
308 - });  
309 - </script>  
310 -</body>  
311 -</html>  
1 -<!DOCTYPE html>  
2 -<html lang="zh">  
3 -<head>  
4 - <meta charset="UTF-8">  
5 - <title>一切正常!</title>  
6 - <style>  
7 - body {  
8 - font-family: Arial, sans-serif;  
9 - background-color: #f4f4f4;  
10 - display: flex;  
11 - justify-content: center;  
12 - align-items: center;  
13 - height: 100vh;  
14 - margin: 0;  
15 - }  
16 - .container {  
17 - text-align: center;  
18 - width: 600px;  
19 - background-color: #fff;  
20 - padding: 50px;  
21 - border-radius: 15px;  
22 - box-shadow: 0 0 15px rgba(0, 0, 0, 0.1);  
23 - }  
24 - h1 {  
25 - font-size: 28px; /* 和 main 的标题一致 */  
26 - margin-bottom: 20px;  
27 - }  
28 - .status-box {  
29 - margin-top: 20px;  
30 - padding: 20px;  
31 - font-size: 18px; /* 和 main 的字体大小一致 */  
32 - background-color: #e0f7e9;  
33 - color: #2e7d32;  
34 - border-radius: 10px;  
35 - border: 1px solid #2e7d32;  
36 - }  
37 - .stats-table {  
38 - margin-top: 30px;  
39 - width: 100%;  
40 - border-collapse: collapse;  
41 - }  
42 - .stats-table th, .stats-table td {  
43 - border: 1px solid #ddd;  
44 - padding: 12px;  
45 - }  
46 - .stats-table th {  
47 - background-color: #f2f2f2;  
48 - }  
49 - a {  
50 - display: inline-block;  
51 - margin-top: 30px;  
52 - padding: 15px 40px;  
53 - font-size: 18px;  
54 - background-color: #000;  
55 - color: white;  
56 - text-decoration: none;  
57 - border-radius: 30px;  
58 - font-weight: bold;  
59 - transition: background-color 0.3s ease;  
60 - }  
61 - a:hover {  
62 - background-color: #333;  
63 - }  
64 - </style>  
65 -</head>  
66 -<body>  
67 - <div class="container">  
68 - <h1>数据分析完毕!</h1>  
69 - <div class="status-box">  
70 - 一切正常!  
71 - </div>  
72 - {% if stats %}  
73 - <h3>统计信息:</h3>  
74 - <table class="stats-table">  
75 - <thead>  
76 - <tr>  
77 - <th>标签</th>  
78 - <th>个数</th>  
79 - <th>占比</th>  
80 - </tr>  
81 - </thead>  
82 - <tbody>  
83 - {% for label, info in stats.items() %}  
84 - <tr>  
85 - <td>{{ label }}</td>  
86 - <td>{{ info.count }}</td>  
87 - <td>{{ info.percentage }}</td>  
88 - </tr>  
89 - {% endfor %}  
90 - </tbody>  
91 - </table>  
92 - {% else %}  
93 - <p>没有统计信息可显示。</p>  
94 - {% endif %}  
95 - <a href="/">返回首页</a>  
96 - </div>  
97 -</body>  
98 -</html>  
1 -<!DOCTYPE html>  
2 -<html lang="zh">  
3 -<head>  
4 - <meta charset="UTF-8">  
5 - <meta name="viewport" content="width=device-width, initial-scale=1.0">  
6 - <title>处理中</title>  
7 - <style>  
8 - body {  
9 - font-family: Arial, sans-serif;  
10 - background-color: #f4f4f4;  
11 - display: flex;  
12 - justify-content: center;  
13 - align-items: center;  
14 - height: 100vh;  
15 - margin: 0;  
16 - }  
17 - .container {  
18 - text-align: center;  
19 - width: 600px; /* 调整容器宽度与 main.html 一致 */  
20 - background-color: #fff;  
21 - padding: 50px;  
22 - border-radius: 15px;  
23 - box-shadow: 0 0 15px rgba(0, 0, 0, 0.1); /* 与 main.html 相同的阴影效果 */  
24 - display: flex;  
25 - flex-direction: column;  
26 - align-items: center; /* 水平居中 */  
27 - }  
28 - h1 {  
29 - font-size: 28px; /* 与 main.html 标题保持一致 */  
30 - margin-bottom: 20px;  
31 - }  
32 - p {  
33 - font-size: 18px; /* 与 main.html 副标题大小一致 */  
34 - color: #888;  
35 - margin-top: 20px;  
36 - }  
37 - .loading-icon {  
38 - margin-top: 20px;  
39 - width: 50px; /* 调整图标大小 */  
40 - height: 50px;  
41 - border: 5px solid #f3f3f3;  
42 - border-radius: 50%;  
43 - border-top: 5px solid #4caf50;  
44 - animation: spin 1s linear infinite;  
45 - }  
46 - @keyframes spin {  
47 - 0% { transform: rotate(0deg); }  
48 - 100% { transform: rotate(360deg); }  
49 - }  
50 - </style>  
51 -</head>  
52 -<body>  
53 - <div class="container">  
54 - <h1>处理中,请稍候...</h1>  
55 - <div class="loading-icon"></div>  
56 - <p>您的文件正在分析中,请稍等片刻。</p>  
57 - </div>  
58 -  
59 - <script>  
60 - function checkStatus() {  
61 - fetch('{{ url_for('check_status', filename=filename) }}')  
62 - .then(response => response.json())  
63 - .then(data => {  
64 - if (data.status === 'success') {  
65 - // 成功,跳转到成功页面,并传递文件名以获取统计信息  
66 - window.location.href = "{{ url_for('upload_success') }}?filename=" + encodeURIComponent('{{ filename }}');  
67 - } else if (data.status === 'failure') {  
68 - // 失败,跳转到失败页面,并传递文件名以获取统计信息  
69 - window.location.href = "{{ url_for('upload_failure') }}?filename=" + encodeURIComponent('{{ filename }}');  
70 - } else {  
71 - // 继续等待  
72 - setTimeout(checkStatus, 2000);  
73 - }  
74 - })  
75 - .catch(error => {  
76 - console.error('Error:', error);  
77 - setTimeout(checkStatus, 2000);  
78 - });  
79 - }  
80 -  
81 - document.addEventListener('DOMContentLoaded', function() {  
82 - checkStatus();  
83 - });  
84 - </script>  
85 -</body>  
86 -</html>