Showing
6 changed files
with
0 additions
and
978 deletions
BCAT_front/app.py
deleted
100644 → 0
| 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) |
BCAT_front/predict.py
deleted
100644 → 0
| 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) |
BCAT_front/templates/failure.html
deleted
100644 → 0
| 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> |
BCAT_front/templates/main.html
deleted
100644 → 0
| 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> |
BCAT_front/templates/success.html
deleted
100644 → 0
| 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> |
BCAT_front/templates/waiting.html
deleted
100644 → 0
| 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> |
-
Please register or login to post a comment