<think>我们将在前端(algorithmselection.vue)和后端(app.py)进行修改,以实现连接。下面是完整的修改后的文件内容。###修改后的algorithmselection.vue文件主要修改点:1.在`uploadFiles`方法中,将上传请求的URL改为`http://localhost:5000/upload`(根据实际部署情况,可能需要调整)2.在`analyzeData`方法中,将分析请求的URL改为`http://localhost:5000/analyze`3.在上传请求中,添加对错误响应的处理(检查response.ok)4.更新数据概览的显示(使用后端返回的气体类型和浓度)注意:由于后端返回的`gas_types`是一个数组,我们需要将其显示出来。同时,我们使用后端返回的`sample_count`更新样本数量。```vue<template><divclass="layout"><!--顶部导航栏--><navclass="navbarnavbar-expand-lgnavbar-darkbg-primarymb-4"><divclass="container"><aclass="navbar-brand"href="#"><iclass="fafa-line-chartme-2"></i>气体传感器数据分析平台</a><spanclass="logout"@click="handleLogout">退出</span></div></nav><divclass="main-layout"><!--左侧边栏--><asideclass="sidebar"><ulclass="menu"><li@click="navigateTo('algorithms')">上传并选择算法</li><li@click="navigateTo('gas-info')">气体信息查询</li></ul></aside><!--右侧主内容区--><divclass="main-content"><divclass="container-fluid"><divclass="row"><!--左侧面板:数据上传和算法设置--><divclass="col-md-4"><!--数据上传板块--><divclass="cardmb-4"><divclass="card-headerbg-primarytext-white"><h5class="mb-0">数据上传</h5></div><divclass="card-body"><formid="uploadForm"enctype="multipart/form-data"@submit.prevent="uploadFiles"><divclass="mb-3"><labelfor="sensorType1"class="form-label">传感器类型1</label><selectclass="form-select"id="sensorType1"v-model="sensorType1"><optionvalue="MP2">MP2</option><optionvalue="MP3B">MP3B</option><optionvalue="MP503">MP503</option><optionvalue="MP801">MP801</option><optionvalue="MQ2">MQ2</option><optionvalue="MQ7B">MQ7B</option></select></div><divclass="mb-3"><labelfor="gasType1"class="form-label">气体类型1</label><selectclass="form-select"id="gasType1"v-model="gasType1"><optionvalue="acetone">丙酮(Acetone)</option><optionvalue="toluene">甲苯(Toluene)</option><optionvalue="methanol">甲醇(Methanol)</option><optionvalue="formaldehyde">甲醛(Formaldehyde)</option><optionvalue="ethanol">乙醇(Ethanol)</option><optionvalue="toluene+formaldehyde">甲苯+甲醛(toluene+formaldehyde)</option><optionvalue="methanol+toluene">甲醇+甲苯(methanol+toluene)</option></select></div><divclass="mb-3"><labelfor="concentration1"class="form-label">浓度1(ppm)</label><inputtype="number"class="form-control"id="concentration1"v-model="concentration1"min="1"></div><divclass="mb-3"><labelfor="sensorType2"class="form-label">传感器类型2</label><selectclass="form-select"id="sensorType2"v-model="sensorType2"><optionvalue="MP2">MP2</option><optionvalue="MP3B">MP3B</option><optionvalue="MP503">MP503</option><optionvalue="MP801">MP801</option><optionvalue="MQ2">MQ2</option><optionvalue="MQ7B">MQ7B</option></select></div><divclass="mb-3"><labelfor="gasType2"class="form-label">气体类型2</label><selectclass="form-select"id="gasType2"v-model="gasType2"><optionvalue="acetone">丙酮(Acetone)</option><optionvalue="toluene">甲苯(Toluene)</option><optionvalue="methanol">甲醇(Methanol)</option><optionvalue="formaldehyde">甲醛(Formaldehyde)</option><optionvalue="ethanol">乙醇(Ethanol)</option><optionvalue="toluene+formaldehyde">甲苯+甲醛(toluene+formaldehyde)</option><optionvalue="methanol+toluene">甲醇+甲苯(methanol+toluene)</option></select></div><divclass="mb-3"><labelfor="concentration2"class="form-label">浓度2(ppm)</label><inputtype="number"class="form-control"id="concentration2"v-model="concentration2"min="1"></div><divclass="mb-3"><labelclass="form-label">选择数据文件</label><divclass="row"><divclass="col-md-6"><labelclass="form-labelsmall">选择单个或多个文件</label><inputclass="form-control"type="file"id="fileUpload"multiple></div><divclass="col-md-6"><labelclass="form-labelsmall">选择整个文件夹</label><inputclass="form-control"type="file"id="folderUpload"webkitdirectorydirectorymultiple></div></div></div><buttontype="submit"class="btnbtn-primaryw-100"><iclass="fafa-uploadme-2"></i>上传数据</button></form><divid="uploadStatus"class="mt-3":class="uploadStatusClass">{{uploadStatusMessage}}</div></div></div><!--算法参数设置板块--><divclass="card"><divclass="card-headerbg-primarytext-white"><h5class="mb-0">算法参数设置</h5></div><divclass="card-body"><divclass="mb-3"><labelclass="form-label">K-近邻算法(KNN)</label><divclass="row"><divclass="col-6"><labelclass="form-label">邻居数量</label><inputtype="range"class="form-range"min="1"max="20"v-model="knnNeighbors"id="knnNeighbors"><spanid="knnNeighborsValue"class="badgebg-secondary">{{knnNeighbors}}</span></div><divclass="col-6"><labelclass="form-label">距离度量</label><selectclass="form-select"id="knnMetric"v-model="knnMetric"><optionvalue="euclidean">欧氏距离</option><optionvalue="manhattan">曼哈顿距离</option></select></div></div></div><divclass="mb-3"><labelclass="form-label">支持向量机(SVM)</label><divclass="row"><divclass="col-6"><labelclass="form-label">正则化参数$C$</label><inputtype="range"class="form-range"min="0.1"max="10"step="0.1"v-model="svmC"id="svmC"><spanid="svmCValue"class="badgebg-secondary">{{svmC}}</span></div><divclass="col-6"><labelclass="form-label">核函数</label><selectclass="form-select"id="svmKernel"v-model="svmKernel"><optionvalue="rbf">RBF</option><optionvalue="linear">线性</option><optionvalue="poly">多项式</option></select></div></div></div><divclass="mb-3"><labelclass="form-label">随机森林(RandomForest)</label><divclass="row"><divclass="col-6"><labelclass="form-label">树的数量</label><inputtype="range"class="form-range"min="10"max="200"step="10"v-model="rfEstimators"id="rfEstimators"><spanid="rfEstimatorsValue"class="badgebg-secondary">{{rfEstimators}}</span></div><divclass="col-6"><labelclass="form-label">最大深度</label><selectclass="form-select"id="rfMaxDepth"v-model="rfMaxDepth"><optionvalue="None">无限制</option><optionvalue="5">5</option><optionvalue="10">10</option></select></div></div></div><buttonid="analyzeBtn"class="btnbtn-successw-100mt-3"@click="analyzeData":disabled="analyzing"><iclass="fa":class="analyzing?'fa-spinnerfa-spin':'fa-play'"></i>{{analyzing?'分析中...':'开始分析'}}</button></div></div></div><!--右侧面板:数据概览、性能比较和详细结果--><divclass="col-md-8"><!--数据概览板块--><divclass="cardmb-4"><divclass="card-headerbg-primarytext-white"><h5class="mb-0">数据概览</h5></div><divclass="card-body"><divclass="rowtext-center"><divclass="col-md-4"><divclass="p-3bg-lightrounded"><h6>气体类型</h6><h4id="gasTypeDisplay">{{gasTypeDisplay}}</h4></div></div><divclass="col-md-4"><divclass="p-3bg-lightrounded"><h6>浓度(ppm)</h6><h4id="concentrationDisplay">{{concentrationDisplay}}</h4></div></div><divclass="col-md-4"><divclass="p-3bg-lightrounded"><h6>样本数量</h6><h4id="sampleCountDisplay">{{sampleCountDisplay}}</h4></div></div></div></div></div><!--算法性能比较板块--><divclass="cardmb-4"><divclass="card-headerbg-primarytext-white"><h5class="mb-0">算法性能比较</h5></div><divclass="card-body"><canvasid="accuracyChart"height="300"></canvas></div></div><!--详细结果板块--><divclass="card"><divclass="card-headerbg-primarytext-white"><h5class="mb-0">详细结果</h5></div><divclass="card-body"><divclass="table-responsive"><tableclass="tabletable-striped"><thead><tr><th>算法</th><th>训练集准确率</th><th>测试集准确率</th><th>操作</th></tr></thead><tbodyid="resultsTable"><templatev-for="(result,algoName)inresults":key="algoName"><!--主行--><trclass="main-row"><td>{{result.name}}</td><td>{{(result.train_accuracy*100).toFixed(2)}}%</td><td>{{(result.test_accuracy*100).toFixed(2)}}%</td><td><buttonclass="btnbtn-smbtn-outline-primaryview-details"@click="toggleDetails(algoName)">{{showDetails[algoName]?'隐藏详情':'查看详情'}}</button></td></tr><!--详情行--><trv-if="showDetails[algoName]"class="detail-row"><tdcolspan="4"><divclass="report-container"><pre>{{result.classification_report}}</pre></div></td></tr></template></tbody></table></div></div></div></div></div></div></div></div></div></template><script>importChartfrom'chart.js/auto';exportdefault{data(){return{//数据上传相关sensorType1:'MP2',gasType1:'acetone',concentration1:20,sensorType2:'MP2',gasType2:'acetone',concentration2:20,uploadStatusMessage:'',uploadStatusClass:'',//数据概览gasTypeDisplay:'-',concentrationDisplay:'-',sampleCountDisplay:'-',//算法参数knnNeighbors:5,knnMetric:'euclidean',svmC:1.0,svmKernel:'rbf',rfEstimators:100,rfMaxDepth:'None',//分析结果analyzing:false,results:{},showDetails:{},//图表实例accuracyChart:null};},mounted(){//初始化图表this.initAccuracyChart();},methods:{navigateTo(page){this.$router.push(`/${page}`);},handleLogout(){this.$router.push('/login');},//初始化准确率图表initAccuracyChart(){constctx=document.getElementById('accuracyChart').getContext('2d');this.accuracyChart=newChart(ctx,{type:'bar',data:{labels:[],datasets:[{label:'测试集准确率',data:[],backgroundColor:'rgba(54,162,235,0.7)',borderColor:'rgba(54,162,235,1)',borderWidth:1}]},options:{responsive:true,scales:{y:{beginAtZero:true,max:1,ticks:{callback:function(value){return(value*100).toFixed(0)+'%';}}}},plugins:{tooltip:{callbacks:{label:function(context){return'准确率:'+(context.raw*100).toFixed(2)+'%';}}}}}});},//上传文件asyncuploadFiles(){constfileInput=document.getElementById('fileUpload');constfolderInput=document.getElementById('folderUpload');//合并所有选择的文件constfiles=Array.from(fileInput.files).concat(Array.from(folderInput.files));if(files.length===0){this.showUploadStatus('请选择至少一个文件或文件夹','error');return;}//显示上传文件信息this.showUploadStatus(`正在上传${files.length}个文件...`,'info');constformData=newFormData();for(constfileoffiles){formData.append('files',file);}formData.append('gas_type1',this.gasType1);formData.append('concentration1',this.concentration1);formData.append('sensor_type1',this.sensorType1);formData.append('gas_type2',this.gasType2);formData.append('concentration2',this.concentration2);formData.append('sensor_type2',this.sensorType2);try{constresponse=awaitfetch('http://localhost:5000/upload',{method:'POST',body:formData});//检查响应状态if(!response.ok){consterrorData=awaitresponse.json();thrownewError(errorData.error||'上传失败');}constdata=awaitresponse.json();if(data.error){this.showUploadStatus(data.error,'error');}else{this.showUploadStatus(`成功上传${files.length}个文件,加载了${data.sample_count}个样本`,'success');//更新数据概览//使用后端返回的气体类型和浓度this.gasTypeDisplay=data.gas_types.join(',');this.concentrationDisplay=data.concentrations.join(',')+'ppm';this.sampleCountDisplay=data.sample_count;//清空输入框fileInput.value='';folderInput.value='';}}catch(error){this.showUploadStatus('上传失败:'+error.message,'error');}},//显示上传状态showUploadStatus(message,type){this.uploadStatusMessage=message;this.uploadStatusClass=type==='error'?'alertalert-danger':type==='success'?'alertalert-success':'alertalert-info';},//分析数据asyncanalyzeData(){this.analyzing=true;//获取算法参数constparams={knn:{n_neighbors:this.knnNeighbors,metric:this.knnMetric},svm:{C:this.svmC,kernel:this.svmKernel},random_forest:{n_estimators:this.rfEstimators,max_depth:this.rfMaxDepth==='None'?null:parseInt(this.rfMaxDepth)}};try{constresponse=awaitfetch('http://localhost:5000/analyze',{method:'POST',headers:{'Content-Type':'application/json',},body:JSON.stringify({params:params,gas_type1:this.gasType1,concentration1:this.concentration1,gas_type2:this.gasType2,concentration2:this.concentration2})});//检查响应状态if(!response.ok){consterrorData=awaitresponse.json();thrownewError(errorData.error||'分析失败');}constdata=awaitresponse.json();if(data.error){thrownewError(data.error);}//更新图表和结果this.updateAccuracyChart(data.results);this.results=data.results;//重置详情展开状态this.showDetails={};for(constalgoNameindata.results){this.$set(this.showDetails,algoName,false);}}catch(error){this.showUploadStatus('分析失败:'+error.message,'error');}finally{this.analyzing=false;}},//更新准确率图表updateAccuracyChart(results){constlabels=[];constaccuracies=[];for(constalgoNameinresults){labels.push(results[algoName].name);accuracies.push(results[algoName].test_accuracy);}this.accuracyChart.data.labels=labels;this.accuracyChart.data.datasets[0].data=accuracies;this.accuracyChart.update();},//切换详情显示toggleDetails(algoName){this.$set(this.showDetails,algoName,!this.showDetails[algoName]);}}};</script><stylescoped>/*页面框架样式*/.layout{display:flex;flex-direction:column;height:100vh;background-color:#f8f9fa;}.navbar{padding:0.5rem1rem;box-shadow:02px4pxrgba(0,0,0,0.1);}.logout{color:white;cursor:pointer;padding:0.5rem;margin-left:auto;}.main-layout{display:flex;flex:1;overflow:hidden;}.sidebar{width:220px;background-color:#d5d5d5;padding:30px20px;box-shadow:2px010pxrgba(0,0,0,0.05);}.menuli{list-style:none;padding:12px;margin:10px0;cursor:pointer;border-radius:8px;transition:background0.2s;font-size:16px;}.menuli:hover{background-color:#dcdcdc;}/*主内容区样式*/.main-content{flex:1;background-color:#f6f9ff;overflow-y:auto;padding:15px;}/*卡片样式*/.card{box-shadow:04px6pxrgba(0,0,0,0.1);transition:all0.3sease;border-radius:8px;border:1pxsolid#dee2e6;margin-bottom:1.5rem;}.card:hover{box-shadow:010px15pxrgba(0,0,0,0.1);}.card-header{padding:0.75rem1.25rem;font-size:1rem;}.badge{font-size:0.9em;padding:0.35em0.65em;}#uploadStatus{min-height:50px;padding:10px;border-radius:5px;}.alert{padding:10px;margin-bottom:10px;border-radius:5px;}.alert-success{background-color:#d1e7dd;color:#0f5132;}.alert-danger{background-color:#f8d7da;color:#842029;}.alert-info{background-color:#cff4fc;color:#055160;}.tableth{background-color:#f8f9fa;}/*详情行样式*/.detail-row{background-color:#f8f9fa;border-left:4pxsolid#0d6efd;transition:all0.3sease;}.detail-row:hover{background-color:#e9ecef;}.detail-row.report-container{padding:15px;}.detail-rowpre{white-space:pre-wrap;word-wrap:break-word;font-size:0.9rem;padding:10px;margin:0;max-height:300px;overflow-y:auto;background-color:#fff;border-radius:4px;border:1pxsolid#dee2e6;font-family:'CourierNew',monospace;line-height:1.4;}/*表格行悬停效果*/#resultsTabletr.main-row{cursor:pointer;transition:background-color0.2s;}#resultsTabletr.main-row:hover{background-color:rgba(13,110,253,0.05);}/*按钮样式优化*/.btn{padding:0.375rem0.75rem;font-size:0.9rem;}.view-details{transition:all0.3s;padding:3px8px;font-size:0.85rem;}.view-details:hover{background-color:#0d6efd;color:white;}/*响应式调整*/@media(max-width:992px){.main-layout{flex-direction:column;}.sidebar{width:100%;padding:15px;height:auto;}.main-content{padding:10px;}}@media(max-width:768px){.col-md-4,.col-md-8{width:100%;max-width:100%;flex:00100%;}.detail-rowpre{max-height:200px;font-size:0.8rem;}.view-details{font-size:0.75rem;}}</style>```###修改后的app.py文件主要修改点:1.添加CORS支持,允许跨域请求2.修改上传路由,返回气体类型、浓度和样本数量3.在分析路由中,从临时文件中读取数据4.添加错误处理```pythonfromflaskimportFlask,render_template,request,jsonify,send_from_directoryfromflask_corsimportCORS#导入CORSfromnew_algorithmimportGasSensorDataAnalyzer,AlgorithmSelector,detect_dataset_typeimporttempfileimportosimportpandasaspdimportnumpyasnpimportjsonimportshutilimporttracebackimportloggingimportrefromsklearn.metricsimportclassification_reportapp=Flask(__name__)CORS(app)#允许跨域请求app.config['UPLOAD_FOLDER']=tempfile.mkdtemp()#配置日志logging.basicConfig(level=logging.DEBUG,format='%(asctime)s-%(name)s-%(levelname)s-%(message)s')logger=logging.getLogger('gas_sensor_app')#初始化数据加载器和算法选择器data_loader=GasSensorDataAnalyzer()selector=AlgorithmSelector(use_chinese=True)defsanitize_filename(filename):"""清理文件名中的非法字符和路径分隔符保留中文、字母、数字、点、连字符和下划线"""#替换所有路径分隔符为下划线filename=re.sub(r'[\\/]','_',filename)#保留中文、字母、数字、点、连字符和下划线filename=re.sub(r'[^\w\u4e00-\u9fff\.-]','_',filename)returnfilenamedefallowed_file(filename):"""检查文件扩展名是否允许"""ALLOWED_EXTENSIONS={'xlsx','csv','txt'}return'.'infilenameand\filename.rsplit('.',1)[1].lower()inALLOWED_EXTENSIONS@app.route('/')defindex():"""主页面"""returnrender_template('index.html')@app.route('/upload',methods=['POST'])defupload_file():"""处理文件上传并自动检测数据集类型"""temp_dir=Nonetry:logger.info("收到文件上传请求")if'files'notinrequest.files:logger.warning("请求中缺少文件部分")returnjsonify({'error':'Nofilepart'}),400files=request.files.getlist('files')ifnotfilesorfiles[0].filename=='':logger.warning("未选择任何文件")returnjsonify({'error':'Noselectedfile'}),400#创建临时目录来保存上传的文件temp_dir=tempfile.mkdtemp(dir=app.config['UPLOAD_FOLDER'])logger.debug(f"创建临时目录:{temp_dir}")file_paths=[]forfileinfiles:iffileandallowed_file(file.filename):#清理文件名中的非法字符safe_filename=sanitize_filename(file.filename)#保存临时文件file_path=os.path.join(temp_dir,safe_filename)logger.debug(f"保存文件:{file_path}")#确保目录存在os.makedirs(os.path.dirname(file_path),exist_ok=True)file.save(file_path)file_paths.append(file_path)logger.info(f"成功保存{len(file_paths)}个文件")#获取表单数据gas_type1=request.form.get('gas_type1','acetone')concentration1=request.form.get('concentration1','20')sensor_type1=request.form.get('sensor_type1','MP2')gas_type2=request.form.get('gas_type2','acetone')concentration2=request.form.get('concentration2','20')sensor_type2=request.form.get('sensor_type2','MP2')#使用detect_dataset_type分析文件夹logger.debug("开始检测数据集类型...")dataset_type,detected_paths,gas_types,concentrations,sensor_types=detect_dataset_type(temp_dir,gas_types=[gas_type1,gas_type2],concentrations=[concentration1,concentration2],sensor_types=[sensor_type1,sensor_type2])logger.info(f"检测到数据集类型:{dataset_type}")logger.debug(f"气体类型:{gas_types}")logger.debug(f"浓度:{concentrations}")logger.debug(f"传感器类型:{sensor_types}")#使用数据加载器加载数据logger.debug("开始加载数据...")X,y=data_loader.load_multiple_gas_data(detected_paths,gas_types,concentrations,sensor_types)ifXisNoneorlen(X)==0:logger.error("加载数据失败:没有有效数据")returnjsonify({'error':'Novaliddataloaded'}),400logger.info(f"成功加载数据:{len(X)}个样本,{X.shape[1]}个特征")#保存合并后的数据df=pd.DataFrame(X)df['label']=yfile_path=os.path.join(app.config['UPLOAD_FOLDER'],'temp_data.xlsx')logger.debug(f"保存合并数据到:{file_path}")df.to_excel(file_path,index=False)#获取气体名称映射gas_names={'acetone':'丙酮(Acetone)','toluene':'甲苯(Toluene)','methanol':'甲醇(Methanol)','formaldehyde':'甲醛(Formaldehyde)','ethanol':'乙醇(Ethanol)','toluene+formaldehyde':'甲苯+甲醛(Toluene+Formaldehyde)','methanol+toluene':'甲醇+甲苯(methanol+toluene)','methanol+toluene+formaldehyde':'甲醇+甲苯+甲醛(Methanol+Toluene+Formaldehyde)'}#准备返回结果response={'status':'success','sample_count':len(X),'feature_count':X.shape[1],'gas_types':[gas_names.get(gt,gt)forgtingas_types],'concentrations':concentrations,'sensor_types':sensor_types,'dataset_type':dataset_type}logger.info("文件上传处理成功")returnjsonify(response)exceptExceptionase:logger.error(f"文件上传出错:{str(e)}")logger.error(traceback.format_exc())returnjsonify({'error':'Internalservererror','message':str(e),'traceback':traceback.format_exc()}),500finally:#清理临时文件iftemp_dir:try:logger.debug(f"清理临时目录:{temp_dir}")shutil.rmtree(temp_dir,ignore_errors=True)exceptExceptionase:logger.warning(f"清理临时目录时出错:{str(e)}")@app.route('/analyze',methods=['POST'])defanalyze_data():"""执行数据分析"""try:logger.info("收到数据分析请求")data=request.get_json()ifnotdata:logger.warning("请求中没有数据")returnjsonify({'error':'Nodataprovided'}),400#从请求中获取参数params=data.get('params',{})logger.debug(f"算法参数:{params}")#设置算法参数foralgo_name,algo_paramsinparams.items():selector.set_algorithm_params(algo_name,algo_params)#加载数据file_path=os.path.join(app.config['UPLOAD_FOLDER'],'temp_data.xlsx')logger.debug(f"从{file_path}加载数据")ifnotos.path.exists(file_path):logger.error(f"数据文件不存在:{file_path}")returnjsonify({'error':'Datafilenotfound'}),404df=pd.read_excel(file_path)if'label'notindf.columns:logger.error("数据文件中缺少'label'列")returnjsonify({'error':"Datafilemissing'label'column"}),400X=df.drop('label',axis=1).valuesy=df['label'].valuesifXisNoneorlen(X)==0:logger.error("没有可用于分析的数据")returnjsonify({'error':'Nodataavailableforanalysis'}),400logger.info(f"开始训练模型,样本数:{len(X)}")#训练模型results=selector.train_models(X,y)logger.info("模型训练完成")#准备返回结果response={'status':'success','results':{},'sample_count':len(X),}foralgo_name,resultinresults.items():#生成文本格式的分类报告report_text=""if'y_test'inresultand'y_pred'inresult:try:report_text=classification_report(result['y_test'],result['y_pred'])exceptExceptionase:report_text=f"生成分类报告出错:{str(e)}"elif'error'inresult:report_text=result['error']response['results'][algo_name]={'name':result['name'],'train_accuracy':result.get('train_accuracy',0),'test_accuracy':result.get('test_accuracy',0),'classification_report':report_text}returnjsonify(response)exceptExceptionase:logger.error(f"数据分析出错:{str(e)}")logger.error(traceback.format_exc())returnjsonify({'error':'Internalservererror','message':str(e),'traceback':traceback.format_exc()}),500@app.route('/static/<path:filename>')defserve_static(filename):"""提供静态文件"""returnsend_from_directory('static',filename)#全局错误处理@app.errorhandler(404)defnot_found_error(error):returnjsonify({'error':'Resourcenotfound'}),404@app.errorhandler(500)definternal_error(error):returnjsonify({'error':'Internalservererror','message':str(error),'traceback':traceback.format_exc()}),500if__name__=='__main__':app.run(debug=True,port=5000)```###注意事项:1.后端需要安装`flask-cors`:`pipinstallflask-cors`2.确保`new_algorithm.py`中的`GasSensorDataAnalyzer`和`AlgorithmSelector`类已经实现,并且`detect_dataset_type`函数可用。3.前端和后端运行在不同的端口(前端默认`5173`,后端`5000`),所以需要配置跨域。###运行步骤:1.启动后端:`pythonapp.py`2.启动前端:在`gas-classification-frontend`目录下运行`npmrundev`这样,前端就可以与后端进行通信了。§§相关问题§§:1.如何解决Vue和Flask开发时的跨域问题?2.在生产环境中如何部署Vue+Flask应用?3.如何优化大文件上传的性能和用户体验?4.Vue中如何处理FormData文件上传的最佳实践?</think>以下是修改后的完整 `algorithmselection.vue` 和 `app.py` 文件,已实现前后端连接:
### 修改后的完整 algorithmselection.vue
```vue
<template>
<div class="layout">
<!-- 顶部导航栏 -->
<nav class="navbar navbar-expand-lg navbar-dark bg-primary mb-4">
<div class="container">
<a class="navbar-brand" href="#">
<i class="fa fa-line-chart me-2"></i>气体传感器数据分析平台
</a>
<span class="logout" @click="handleLogout">退出</span>
</div>
</nav>
<div class="main-layout">
<!-- 左侧边栏 -->
<aside class="sidebar">
<ul class="menu">
<li @click="navigateTo('algorithms')">上传并选择算法</li>
<li @click="navigateTo('gas-info')">气体信息查询</li>
</ul>
</aside>
<!-- 右侧主内容区 -->
<div class="main-content">
<div class="container-fluid">
<div class="row">
<!-- 左侧面板:数据上传和算法设置 -->
<div class="col-md-4">
<!-- 数据上传板块 -->
<div class="card mb-4">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">数据上传</h5>
</div>
<div class="card-body">
<form id="uploadForm" enctype="multipart/form-data" @submit.prevent="uploadFiles">
<div class="mb-3">
<label for="sensorType1" class="form-label">传感器类型 1</label>
<select class="form-select" id="sensorType1" v-model="sensorType1">
<option value="MP2">MP2</option>
<option value="MP3B">MP3B</option>
<option value="MP503">MP503</option>
<option value="MP801">MP801</option>
<option value="MQ2">MQ2</option>
<option value="MQ7B">MQ7B</option>
</select>
</div>
<div class="mb-3">
<label for="gasType1" class="form-label">气体类型 1</label>
<select class="form-select" id="gasType1" v-model="gasType1">
<option value="acetone">丙酮 (Acetone)</option>
<option value="toluene">甲苯 (Toluene)</option>
<option value="methanol">甲醇 (Methanol)</option>
<option value="formaldehyde">甲醛 (Formaldehyde)</option>
<option value="ethanol">乙醇 (Ethanol)</option>
<option value="toluene+formaldehyde">甲苯+甲醛 (toluene+formaldehyde)</option>
<option value="methanol+toluene">甲醇+甲苯 (methanol+toluene)</option>
</select>
</div>
<div class="mb-3">
<label for="concentration1" class="form-label">浓度 1 (ppm)</label>
<input type="number" class="form-control" id="concentration1" v-model="concentration1" min="1">
</div>
<div class="mb-3">
<label for="sensorType2" class="form-label">传感器类型 2</label>
<select class="form-select" id="sensorType2" v-model="sensorType2">
<option value="MP2">MP2</option>
<option value="MP3B">MP3B</option>
<option value="MP503">MP503</option>
<option value="MP801">MP801</option>
<option value="MQ2">MQ2</option>
<option value="MQ7B">MQ7B</option>
</select>
</div>
<div class="mb-3">
<label for="gasType2" class="form-label">气体类型 2</label>
<select class="form-select" id="gasType2" v-model="gasType2">
<option value="acetone">丙酮 (Acetone)</option>
<option value="toluene">甲苯 (Toluene)</option>
<option value="methanol">甲醇 (Methanol)</option>
<option value="formaldehyde">甲醛 (Formaldehyde)</option>
<option value="ethanol">乙醇 (Ethanol)</option>
<option value="toluene+formaldehyde">甲苯+甲醛 (toluene+formaldehyde)</option>
<option value="methanol+toluene">甲醇+甲苯 (methanol+toluene)</option>
</select>
</div>
<div class="mb-3">
<label for="concentration2" class="form-label">浓度 2 (ppm)</label>
<input type="number" class="form-control" id="concentration2" v-model="concentration2" min="1">
</div>
<div class="mb-3">
<label class="form-label">选择数据文件</label>
<div class="row">
<div class="col-md-6">
<label class="form-label small">选择单个或多个文件</label>
<input class="form-control" type="file" id="fileUpload" multiple>
</div>
<div class="col-md-6">
<label class="form-label small">选择整个文件夹</label>
<input class="form-control" type="file" id="folderUpload" webkitdirectory directory multiple>
</div>
</div>
</极
<button type="submit" class="btn btn-primary w-100">
<i class="fa fa-upload me-2"></i>上传数据
</button>
</form>
<div id="uploadStatus" class="mt-3" :class="uploadStatusClass">{{ uploadStatusMessage }}</div>
</div>
</div>
<!-- 算法参数设置板块 -->
<div class="card">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">算法参数设置</h极
</div>
<div class="card-body">
<div class="mb-3">
<label class="form-label">K-近邻算法 (KNN)</label>
<div class="row">
<div class="col-6">
<label class="form-label">邻居数量</label>
<input type="range" class="form-range" min="1" max="20" v-model="knnNeighbors" id="knnNeighbors">
<span id="knnNeighborsValue" class="badge bg-secondary">{{ knnNeighbors }}</span>
</div>
<div class="col-6">
<label class="form-label">距离度量</label>
<select class="form-select" id="knnMetric" v-model="knnMetric">
<option value="euclidean">欧氏距离</option>
<option value="manhattan">曼哈顿距离</option>
</select>
</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">支持向量机 (SVM)</label>
<div class="row">
<div class="col-6">
<label class="form-label">正则化参数 $ C$</label>
<input type="range" class="form-range" min="0.1" max="10" step="0.1" v-model="svmC" id="svmC">
<span id="svmCValue" class="bad极 bg-secondary">{{ svmC }}</span>
</div>
<div class="col-6">
<label class="form-label">核函数</label>
<select class="form-select" id="svmKernel" v-model="svmKernel">
<option value="rbf">RBF</option>
<option value="linear">线性</option>
<option value="poly">多项式</option>
</select>
</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">随机森林 (Random Forest)</label>
<div class="row">
<div class="col-6">
<label class="form-label">树的数量</label>
<input type="range" class="form-range" min="10" max="200" step="10" v-model="rfEstimators" id="rfEstimators">
<span id="rfEstimatorsValue" class="badge bg-secondary">{{ rfEstimators }}</span>
</div>
<div class="col-6">
<label class="form-label">最大深度</label>
<select class="form-select" id="rfMaxDepth" v-model="rfMaxDepth">
<option value="None">无限制</option>
<option value="5">5</option>
<option value="10">10</option>
</select>
</div>
</div>
</div>
<button id="analyzeBtn" class="btn btn-success w-100 mt-3" @click="analyzeData" :disabled="analyzing">
<i class="fa" :class="analyzing ? 'fa-spinner fa-spin' : 'fa-play'"></i>
{{ analyzing ? '分析中...' : '开始分析' }}
</button>
</div>
</div>
</div>
<!-- 右侧面板:数据概览、性能比较和详细结果 -->
<div class="col-md-8">
<!-- 数据概览板块 -->
<div class="card mb-4">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">数据概览</h5>
</div>
<div class="card-body">
<div class="row text-center">
<div class="col-md-4">
<div class="p-3 bg-light rounded">
<h6>气体类型</h6>
<h4 id="gasTypeDisplay">{{ gasTypeDisplay }}</h4>
</div>
</div>
<div class="col-md-4">
<div class="p-3 bg-light rounded">
<h6>浓度 (ppm)</h6>
<h4 id="concentrationDisplay">{{ concentrationDisplay }}</h4>
</div>
</div>
<div class="col-md-4">
<div class="p-3 bg-light rounded">
<h6>样本数量</h6>
<h4 id="sampleCountDisplay">{{ sampleCountDisplay }}</h4>
</div>
</div>
</div>
</div>
</div>
<!-- 算法性能比较板块 -->
<div class="card mb-4">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">算法性能比较</h5>
</div>
<div class="card-body">
<canvas id="accuracyChart" height="300"></canvas>
</div>
</div>
<!-- 详细结果板块 -->
<div class="card">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">详细结果</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>算法</th>
<th>训练集准确率</th>
<th>测试集准确率</th>
<th>操作</th>
</tr>
</thead>
<tbody id="resultsTable">
<template v-for="(result, algoName) in results" :key="algoName">
<!-- 主行 -->
<tr class="main-row">
<td>{{ result.name }}</td>
<td>{{ (result.train_accuracy * 100).toFixed(2) }}%</td>
<td>{{ (result.test_accuracy * 100).toFixed(2) }}%</td>
<td>
<button class="btn btn-sm btn-outline-primary view-details" @click="toggleDetails(algoName)">
{{ showDetails[algoName] ? '隐藏详情' : '查看详情' }}
</button>
</td>
</tr>
<!-- 详情行 -->
<tr v-if="showDetails[algoName]" class="detail-row">
<td colspan="4">
<div class="report-container">
<pre>{{ result.classification_report }}</pre>
</div>
</td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import Chart from 'chart.js/auto';
export default {
data() {
return {
// 数据上传相关
sensorType1: 'MP2',
gasType1: 'acetone',
concentration1: 20,
sensorType2: 'MP2',
gasType2: 'acetone',
concentration2: 20,
uploadStatusMessage: '',
uploadStatusClass: '',
// 数据概览
gasTypeDisplay: '-',
concentrationDisplay: '-',
sampleCountDisplay: '-',
// 算法参数
knnNeighbors: 5,
knnMetric: 'euclidean',
svmC: 1.0,
svmKernel: 'r极',
rfEstimators: 100,
rfMaxDepth: 'None',
// 分析结果
analyzing: false,
results: {},
showDetails: {},
// 图表实例
accuracyChart: null
};
},
mounted() {
// 初始化图表
this.initAccuracyChart();
},
methods: {
navigateTo(page) {
this.$router.push(`/${page}`);
},
handleLogout() {
this.$router.push('/login');
},
// 初始化准确率图表
initAccuracyChart() {
const ctx = document.getElementById('accuracyChart').getContext('2d');
this.accuracyChart = new Chart(ctx, {
type: 'bar',
data: {
labels: [],
datasets: [{
label: '测试集准确率',
data: [],
backgroundColor: 'rgba(54, 162, 235, 0.7)',
borderColor: 'rgba(54, 162, 235, 1)',
borderWidth: 1
}]
},
options: {
responsive: true,
scales: {
y: {
beginAtZero: true,
max: 1,
ticks: {
callback: function(value) {
return (value * 100).toFixed(0) + '%';
}
}
}
},
plugins: {
tooltip: {
callbacks: {
label: function(context) {
return '准确率: ' + (context.raw * 100).toFixed(2) + '%';
}
}
}
}
}
});
},
// 上传文件
async uploadFiles() {
const fileInput = document.getElementById('fileUpload');
const folderInput = document.getElementById('folderUpload');
// 合并所有选择的文件
const files = Array.from(fileInput.files).concat(Array.from(folderInput.files));
if (files.length === 0) {
this.showUploadStatus('请选择至少一个文件或文件夹', 'error');
return;
}
this.showUploadStatus(`正在上传 ${files.length} 个文件...`, 'info');
const formData = new FormData();
for (const file of files) {
formData.append('files', file);
}
formData.append('gas_type1', this.gasType1);
formData.append('concentration1', this.concentration1);
formData.append('sensor_type1', this.sensorType1);
formData.append('gas_type2', this.gasType2);
formData.append('concentration2', this.concentration2);
formData.append('sensor_type2', this.sensorType2);
try {
const response = await fetch('http://localhost:5000/upload', {
method: 'POST',
body: formData
});
// 检查响应状态
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || '上传失败');
}
const data = await response.json();
if (data.error) {
this.showUploadStatus(data.error, 'error');
} else {
this.showUploadStatus(`成功上传 ${files.length} 个文件,加载了 ${data.sample_count} 个样本`, 'success');
// 更新数据概览
this.gasTypeDisplay = data.gas_types.join(', ');
this.concentrationDisplay = data.concentrations.join(', ') + ' ppm';
this.sampleCountDisplay = data.sample_count;
// 清空输入框
fileInput.value = '';
folderInput.value = '';
}
} catch (error) {
this.showUploadStatus('上传失败: ' + error.message, 'error');
}
},
// 显示上传状态
showUploadStatus(message, type) {
this.uploadStatusMessage = message;
this.uploadStatusClass = type === 'error' ? 'alert alert-danger' :
type === 'success' ? 'alert alert-success' :
'alert alert-info';
},
// 分析数据
async analyzeData() {
this.analyzing = true;
// 获取算法参数
const params = {
knn: {
n_neighbors: this.knnNeighbors,
metric: this.knnMetric
},
svm: {
C: this.svmC,
kernel: this.svmKernel
},
random_forest: {
n_estimators: this.rfEstimators,
max_depth: this.rfMaxDepth === 'None' ? null : parseInt(this.rfMaxDepth)
}
};
try {
const response = await fetch('http://localhost:5000/analyze', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
params: params,
gas_type1: this.gasType1,
concentration1: this.concentration1,
gas_type2: this.gasType2,
concentration2: this.concentration2
})
});
// 检查响应状态
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || '分析失败');
}
const data = await response.json();
if (data.error) {
throw new Error(data.error);
}
// 更新图表和结果
this.updateAccuracyChart(data.results);
this.results = data.results;
// 重置详情展开状态
this.showDetails = {};
for (const algoName in data.results) {
this.$set(this.showDetails, algoName, false);
}
} catch (error) {
this.showUploadStatus('分析失败: ' + error.message, 'error');
} finally {
this.analyzing = false;
}
},
// 更新准确率图表
updateAccuracyChart(results) {
const labels = [];
const accuracies = [];
for (const algoName in results) {
labels.push(results[algoName].name);
accuracies.push(results[algoName].test_accuracy);
}
this.accuracyChart.data.labels = labels;
this.accuracyChart.data.datasets[0].data = accuracies;
this.accuracyChart.update();
},
// 切换详情显示
toggleDetails(algoName) {
this.$set(this.showDetails, algoName, !this.showDetails[algoName]);
}
}
};
</script>
<style scoped>
/* 页面框架样式 */
.layout {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #f8f9fa;
}
.navbar {
padding: 0.5rem 1rem;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.logout {
color: white;
cursor: pointer;
padding: 0.5rem;
margin-left: auto;
}
.main-layout {
display: flex;
flex: 1;
overflow: hidden;
}
.sidebar {
width: 220px;
background-color: #d5d5d5;
padding: 30px 20px;
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.05);
}
.menu li {
list-style: none;
padding: 12px;
margin: 10px 0;
cursor: pointer;
border-radius: 8px;
transition: background 0.2s;
font-size: 16px;
}
.menu li:hover {
background-color: #dcdcdc;
}
/* 主内容区样式 */
.main-content {
flex: 1;
background-color: #f6f9ff;
overflow-y: auto;
padding: 15px;
}
/* 卡片样式 */
.card {
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
border-radius: 8px;
border: 1px solid #dee2e6;
margin-bottom: 1.5rem;
}
.card:hover {
box-shadow: 0 10px 15px rgba(0, 0, 0, 0.1);
}
.card-header {
padding: 0.75rem 1.25rem;
font-size: 1rem;
}
.badge {
font-size: 0.9em;
padding: 0.35em 0.65em;
}
#uploadStatus {
min-height: 50px;
padding: 10px;
border-radius: 5px;
}
.alert {
padding: 10px;
margin-bottom: 10px;
border-radius: 5px;
}
.alert-success {
background-color: #d1e7dd;
color: #0f5132;
}
.alert-danger {
background-color: #f8d7da;
color: #842029;
}
.alert-info {
background-color: #cff4fc;
color: #055160;
}
.table th {
background-color: #f8f9fa;
}
/* 详情行样式 */
.detail-row {
background-color: #f8f9fa;
border-left: 4px solid #0d6efd;
transition: all 0.3s ease;
}
.detail-row:hover {
background-color: #e9ecef;
}
.detail-row .report-container {
padding: 15px;
}
.detail-row pre {
white-space: pre-wrap;
word-wrap: break-word;
font-size: 0.9rem;
padding: 10px;
margin: 0;
max-height: 300px;
overflow-y: auto;
background-color: #fff;
border-radius: 4px;
border: 1px solid #dee2e6;
font-family: 'Courier New', monospace;
line-height: 1.4;
}
/* 表格行悬停效果 */
#resultsTable tr.main-row {
cursor: pointer;
transition: background-color 0.2s;
}
#resultsTable tr.main-row:hover {
background-color: rgba(13, 110, 253, 0.05);
}
/* 按钮样式优化 */
.btn {
padding: 0.375rem 0.75rem;
font-size: 0.9rem;
}
.view-details {
transition: all 0.3s;
padding: 3px 8px;
font-size: 0.85rem;
}
.view-details:hover {
background-color: #0d6efd;
color: white;
}
/* 响应式调整 */
@media (max-width: 992px) {
.main-layout {
flex-direction: column;
}
.sidebar {
width: 100%;
padding: 15px;
height: auto;
}
.main-content {
padding: 10px;
}
}
@media (max-width: 768px) {
.col-md-4, .col-md-8 {
width: 100%;
max-width: 100%;
flex: 0 0 100%;
}
.detail-row pre {
max-height: 200极
font-size: 0.8rem;
}
.view-details {
font-size: 0.75rem;
}
}
</style>
```
### 修改后的完整 app.py
```python
from flask import Flask, render_template, request, jsonify, send_from_directory
from flask_cors import CORS
from new_algorithm import GasSensorDataAnalyzer, AlgorithmSelector, detect_dataset_type
import tempfile
import os
import pandas as pd
import numpy as np
import json
import shutil
import traceback
import logging
import re
from sklearn.metrics import classification_report
app = Flask(__name__)
CORS(app) # 添加CORS支持
app.config['UPLOAD_FOLDER'] = tempfile.mkdtemp()
# 配置日志
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger('gas_sensor_app')
# 初始化数据加载器和算法选择器
data_loader = GasSensorDataAnalyzer()
selector = AlgorithmSelector(use_chinese=True)
def sanitize_filename(filename):
"""
清理文件名中的非法字符和路径分隔符
保留中文、字母、数字、点、连字符和下划线
"""
# 替换所有路径分隔符为下划线
filename = re.sub(r'[\\/]', '_', filename)
# 保留中文、字母、数字、点、连字符和下划线
filename = re.sub(r'[^\w\u4e00-\u9fff\.-]', '_', filename)
return filename
def allowed_file(filename):
"""检查文件扩展名是否允许"""
ALLOWED_EXTENSIONS = {'xlsx', 'csv', 'txt'}
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@app.route('/')
def index():
"""主页面"""
return render_template('index.html')
@app.route('/upload', methods=['POST'])
def upload_file():
"""处理文件上传并自动检测数据集类型"""
temp_dir = None
try:
logger.info("收到文件上传请求")
if 'files' not in request.files:
logger.warning("请求中缺少文件部分")
return jsonify({'error': 'No file part'}), 400
files = request.files.getlist('files')
if not files or files[0].filename == '':
logger.warning("未选择任何文件")
return jsonify({'error': 'No selected file'}), 400
# 创建临时目录来保存上传的文件
temp_dir = tempfile.mkdtemp(dir=app.config['UPLOAD_FOLDER'])
logger.debug(f"创建临时目录: {temp_dir}")
file_paths = []
for file in files:
if file and allowed_file(file.filename):
# 清理文件名中的非法字符
safe_filename = sanitize_filename(file.filename)
# 保存临时文件
file_path = os.path.join(temp_dir, safe_filename)
logger.debug(f"保存文件: {file_path}")
# 确保目录存在
os.makedirs(os.path.dirname(file_path), exist_ok=True)
file.save(file_path)
file_paths.append(file_path)
logger.info(f"成功保存 {len(file_paths)} 个文件")
# 获取表单数据
gas_type1 = request.form.get('gas_type1', 'acetone')
concentration1 = request.form.get('concentration1', '20')
sensor_type1 = request.form.get('sensor_type1', 'MP2')
gas_type2 = request.form.get('gas_type2', 'acetone')
concentration2 = request.form.get('concentration2', '20')
sensor_type2 = request.form.get('sensor_type2', 'MP2')
# 使用detect_dataset_type分析文件夹
logger.debug("开始检测数据集类型...")
dataset_type, detected_paths, gas_types, concentrations, sensor_types = detect_dataset_type(
temp_dir,
gas_types=[gas_type1, gas_type2],
concentrations=[concentration1, concentration2],
sensor_types=[sensor_type1, sensor_type2]
)
logger.info(f"检测到数据集类型: {dataset_type}")
logger.debug(f"气体类型: {gas_types}")
logger.debug(f"浓度: {concentrations}")
logger.debug(f"传感器类型: {sensor_types}")
# 使用数据加载器加载数据
logger.debug("开始加载数据...")
X, y = data_loader.load_multiple_gas_data(detected_paths, gas_types, concentrations, sensor_types)
if X is None or len(X) == 0:
logger.error("加载数据失败: 没有有效数据")
return jsonify({'error': 'No valid data loaded'}), 400
logger.info(f"成功加载数据: {len(X)} 个样本, {X.shape[1]} 个特征")
# 保存合并后的数据
df = pd.DataFrame(X)
df['label'] = y
file_path = os.path.join(app.config['UPLOAD_FOLDER'], 'temp_data.xlsx')
logger.debug(f"保存合并数据到: {file_path}")
df.to_excel(file_path, index=False)
# 获取气体名称映射
gas_names = {
'acetone': '丙酮 (Acetone)',
'toluene': '甲苯 (Toluene)',
'methanol': '甲醇 (Methanol)',
'formaldehyde': '甲醛 (Formaldehyde)',
'ethanol': '乙醇 (Ethanol)',
'toluene+formaldehyde': '甲苯+甲醛 (Toluene+Formaldehyde)',
'methanol+toluene':'甲醇+甲苯 (methanol+toluene)',
'methanol+toluene+formaldehyde': '甲醇+甲苯+甲醛 (Methanol+Toluene+Formaldehyde)'
}
# 准备返回结果
response = {
'status': 'success',
'sample_count': len(X),
'feature_count': X.shape[1],
'gas_types': [gas_names.get(gt, gt) for gt in gas_types],
'concentrations': concentrations,
'sensor_types': sensor_types,
'dataset_type': dataset_type
}
logger.info("文件上传处理成功")
return jsonify(response)
except Exception as e:
logger.error(f"文件上传出错: {str(e)}")
logger.error(traceback.format_exc())
return jsonify({
'error': 'Internal server error',
'message': str(e),
'traceback': traceback.format_exc()
}), 500
finally:
# 清理临时文件
if temp_dir:
try:
logger.debug(f"清理临时目录: {temp_dir}")
shutil.rmtree(temp_dir, ignore_errors=True)
except Exception as e:
logger.warning(f"清理临时目录时出错: {str(e)}")
@app.route('/analyze', methods=['POST'])
def analyze_data():
"""执行数据分析"""
try:
logger.info("收到数据分析请求")
data = request.get_json()
if not data:
logger.warning("请求中没有数据")
return jsonify({'error': 'No data provided'}), 400
# 从请求中获取参数
params = data.get('params', {})
logger.debug(f"算法参数: {params}")
# 设置算法参数
for algo_name, algo_params in params.items():
selector.set_algorithm_params(algo_name, algo_params)
# 加载数据
file_path = os.path.join(app.config['UPLOAD_FOLDER'], 'temp_data.xlsx')
logger.debug(f"从 {file_path} 加载数据")
if not os.path.exists(file_path):
logger.error(f"数据文件不存在: {file_path}")
return jsonify({'error': 'Data file not found'}), 404
df = pd.read_excel(file_path)
if 'label' not in df.columns:
logger.error("数据文件中缺少 'label' 列")
return jsonify({'error': "Data file missing 'label' column"}), 400
X = df.drop('label', axis=1).values
y = df['label'].values
if X is None or len(X) == 0:
logger.error("没有可用于分析的数据")
return jsonify({'error': 'No data available for analysis'}), 400
logger.info(f"开始训练模型, 样本数: {len(X)}")
# 训练模型
results = selector.train_models(X, y)
logger.info("模型训练完成")
# 准备返回结果
response = {
'status': 'success',
'results': {},
'sample_count': len(X),
}
for algo_name, result in results.items():
# 生成文本格式的分类报告
report_text = ""
if 'y_test' in result and 'y_pred' in result:
try:
report_text = classification_report(result['y_test'], result['y_pred'])
except Exception as e:
report_text = f"生成分类报告出错: {str(e)}"
elif 'error' in result:
report_text = result['error']
response['results'][algo_name] = {
'name': result['name'],
'train_accuracy': result.get('train_accuracy', 0),
'test_accuracy': result.get('test_accuracy', 0),
'classification_report': report_text
}
return jsonify(response)
except Exception as e:
logger.error(f"数据分析出错: {str(e)}")
logger.error(traceback.format_exc())
return jsonify({
'error': 'Internal server error',
'message': str(e),
'traceback': traceback.format_exc()
}), 500
@app.route('/static/<path:filename>')
def serve_static(filename):
"""提供静态文件"""
return send_from_directory('static', filename)
# 全局错误处理
@app.errorhandler(404)
def not_found_error(error):
return jsonify({'error': 'Resource not found'}), 404
@app.errorhandler(500)
def internal_error(error):
return jsonify({
'error': 'Internal server error',
'message': str(error),
'traceback': traceback.format_exc()
}), 500
if __name__ == '__main__':
app.run(debug=True, port=5000)
```
### 关键修改点总结:
#### 前端修改:
1. **API端点更新**:
- 上传接口:`fetch('http://localhost:5000/upload')`
- 分析接口:`fetch('http://localhost:5000/analyze')`
2. **错误处理增强**:
```javascript
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || '上传失败');
}
```
3. **数据概览显示**:
```javascript
// 使用后端返回的数据
this.gasTypeDisplay = data.gas_types.join(', ');
this.concentrationDisplay = data.concentrations.join(', ') + ' ppm';
this.sampleCountDisplay = data.sample_count;
```
4. **文件上传优化**:
- 简化状态消息
- 清空文件输入框
#### 后端修改:
1. **CORS支持**:
```python
from flask_cors import CORS
CORS(app)
```
2. **表单数据处理**:
```python
gas_type1 = request.form.get('gas_type1', 'acetone')
concentration1 = request.form.get('concentration1', '20')
```
3. **数据集检测参数传递**:
```python
dataset_type, detected_paths, gas_types, concentrations, sensor_types = detect_dataset_type(
temp_dir,
gas_types=[gas_type1, gas_type2],
concentrations=[concentration1, concentration2],
sensor_types=[sensor_type1, sensor_type2]
)
```
4. **响应格式优化**:
```python
response = {
'status': 'success',
'sample_count': len(X),
'feature_count': X.shape[1],
'gas_types': [gas_names.get(gt, gt) for gt in gas_types],
'concentrations': concentrations,
'sensor_types': sensor_types,
'dataset_type': dataset_type
}
```
### 使用说明:
1. 安装依赖:
```bash
pip install flask flask-cors pandas openpyxl
```
2. 启动后端:
```bash
python app.py
```
3. 启动前端:
```bash
npm run dev
```
4. 确保 `new_algorithm.py` 模块存在并包含 `GasSensorDataAnalyzer`, `AlgorithmSelector`, `detect_dataset_type` 等类和方法