<!DOCTYPE html> <html lang="zh"> <head> <meta ch...
创建于:2025年4月12日
使用 O3 Mini High 回答 Chat01
创建于:2025年4月12日
使用 O3 Mini High 回答 Chat01
</div> <div id="loadingModal"> <div id="loadingContent">⏱请稍等,资料海量,正在查询中…… <span id="timer">0</span> 秒</div> </div> <script> const queryForm = document.getElementById('queryForm'); const queryInput = document.getElementById('query'); const responseDiv = document.getElementById('response'); const answerElement = document.getElementById('answer'); const loadingModal = document.getElementById('loadingModal'); const timerElement = document.getElementById('timer'); let timerInterval; let seconds = 0; queryForm.addEventListener('submit', function(event) { event.preventDefault(); loadingModal.style.display = 'block'; seconds = 0; timerElement.textContent = seconds; timerInterval = setInterval(function() { seconds++; timerElement.textContent = seconds; }, 1000); let query = queryInput.value; fetch('/ask', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: 'query=' + encodeURIComponent(query) }) .then(response => response.json()) .then(data => { loadingModal.style.display = 'none'; clearInterval(timerInterval); if (data.error) { answerElement.innerHTML = '错误: ' + data.error; } else { answerElement.innerHTML = data.answer; } responseDiv.style.display = 'block'; }) .catch(error => { loadingModal.style.display = 'none'; clearInterval(timerInterval); answerElement.innerHTML = '发生错误,请稍后再试。'; responseDiv.style.display = 'block'; }); }); // 搜索框功能:过滤知识库文件和表单文件列表 const searchRow = document.getElementById('searchRow'); const searchInput = document.getElementById('searchInput'); searchInput.addEventListener('input', function() { const filter = searchInput.value.toLowerCase(); document.querySelectorAll('#fileList li').forEach(li => { const text = li.textContent.toLowerCase(); li.style.display = text.includes(filter) ? 'list-item' : 'none'; }); document.querySelectorAll('#formFileList li').forEach(li => { const text = li.textContent.toLowerCase(); li.style.display = text.includes(filter) ? 'list-item' : 'none'; }); }); // 更新搜索框显示状态:只要任一文件容器展开即显示 function updateSearchRowVisibility() { const knowledgeVisible = knowledgeContainer.style.display === 'block'; const formVisible = formContainer.style.display === 'block'; if (knowledgeVisible || formVisible) { searchRow.style.display = 'block'; } else { searchRow.style.display = 'none'; searchInput.value = ''; // 重置列表显示所有项 document.querySelectorAll('#fileList li, #formFileList li').forEach(li => { li.style.display = 'list-item'; }); } } // 知识库文件相关逻辑 const knowledgeToggle = document.getElementById('knowledgeToggle'); const knowledgeContainer = document.getElementById('knowledgeContainer'); const closeKnowledgeBtn = document.getElementById('closeKnowledgeBtn'); const fileList = document.getElementById('fileList'); knowledgeToggle.addEventListener('click', function() { if (knowledgeContainer.style.display === 'none' || knowledgeContainer.style.display === '') { knowledgeContainer.style.display = 'block'; fetch('/static/jsons/combined_faiss_index.json') .then(res => res.json()) .then(jsonData => { const fileLinkMap = {}; jsonData.forEach(item => { if (item.filename && item.web) { fileLinkMap[item.filename] = item.web; } }); fileList.innerHTML = ''; Object.keys(fileLinkMap).forEach(filename => { const li = document.createElement('li'); const a = document.createElement('a'); a.href = fileLinkMap[filename]; a.textContent = filename; a.target = '_blank'; li.appendChild(a); fileList.appendChild(li); }); }) .catch(err => { console.error('获取 JSON 文件出错:', err); fileList.innerHTML = '<li>获取 JSON 文件失败,请检查路径或服务器配置。</li>'; }); } else { knowledgeContainer.style.display = 'none'; } updateSearchRowVisibility(); }); closeKnowledgeBtn.addEventListener('click', function() { knowledgeContainer.style.display = 'none'; updateSearchRowVisibility(); }); // 表单文件相关逻辑 const formToggle = document.getElementById('formToggle'); const formContainer = document.getElementById('formContainer'); const closeFormBtn = document.getElementById('closeFormBtn'); const formFileList = document.getElementById('formFileList'); formToggle.addEventListener('click', function() { if (formContainer.style.display === 'none' || formContainer.style.display === '') { formContainer.style.display = 'block'; fetch('/static/jsons/table_list_web.json') .then(res => res.json()) .then(jsonData => { const fileLinkMap = {}; jsonData.forEach(item => { if (item.filename && item.web) { fileLinkMap[item.filename] = item.web; } }); formFileList.innerHTML = ''; Object.keys(fileLinkMap).forEach(filename => { const li = document.createElement('li'); const a = document.createElement('a'); a.href = fileLinkMap[filename]; a.textContent = filename; a.target = '_blank'; li.appendChild(a); formFileList.appendChild(li); }); }) .catch(err => { console.error('获取 JSON 文件出错:', err); formFileList.innerHTML = '<li>获取 JSON 文件失败,请检查路径或服务器配置。</li>'; }); } else { formContainer.style.display = 'none'; } updateSearchRowVisibility(); }); closeFormBtn.addEventListener('click', function() { formContainer.style.display = 'none'; updateSearchRowVisibility(); }); </script> </body> </html> 修改代码,要求,在页面最下方的左侧,添加一小行字体较小的“其他功能”标题,标题下方设立1个“报工数据核对”按钮,点击“报工数据核对”后,会自动会弹出1个新的data_check.html界面。这个html可以实现xlsx文件的拖拽上传,然后后端自动运行check_data.py代码,将上传的文件的第2列数据进行读取,将≥8000的的所在的行的全部数据展示在html界面上,并将≥8000的数据标红加粗。 帮我修改现有html代码,并且给出新的data_check.html的代码,以及check_data.py代码,并将代码在目录结构中应该存放的位置规划清楚。图片中是我现有的结构。text<!-- 搜索框,显示条件:只要有任一文件容器展开就显示 --> <div id="searchRow" style="display: none;"> <input type="text" id="searchInput" placeholder="搜索文件..." /> </div> <!-- 知识库文件展开区域 --> <div id="knowledgeContainer" style="display: none;"> <h3>知识库文件如下:</h3> <ul id="fileList"></ul> <button id="closeKnowledgeBtn">收起</button> </div> <!-- 表单文件展开区域 --> <div id="formContainer" style="display: none;"> <h3>表单文件如下:</h3> <ul id="formFileList"></ul> <button id="closeFormBtn">收起</button> </div> <form id="queryForm"> <div class="form-group"> <label for="query">🤖请输入您的问题,我会根据知识库文件进行回答:</label> <input type="text" id="query" name="query" required /> </div> <button type="submit">提交问题</button> </form> <div class="response" id="response" style="display: none;"> <h3>回答:</h3> <pre id="answer"></pre> </div>
下面给出的是一个示例实现,包含以下内容:
index.html
data_check.html
check_data.py
请根据自身后端框架(Flask/FastAPI/Django等)进行适当调整。此示例以 Flask 为例,演示大致的思路。
假设您现在的项目根目录仍然是 AI_ASSISTANT
,并且主要 Python 后端脚本是 main_assistant_flask.py
。根据您的截图和需求,推荐的目录结构如下(仅展示关键文件及对应位置):
textAI_ASSISTANT [WSL: UBUNTU] ├── 0_pre_codes_2.25.04.12 ├── functions │ └── check_data.py # 新增的检查报工数据的后端逻辑 ├── models ├── static │ ├── jsons │ └── knowledgefiles ├── templates │ ├── index.html # 修改后的index.html │ └── data_check.html # 新增的数据核对页面 ├── main_assistant_flask.py └── main_assistant_gunicorn.py
您可以根据需要自行微调,但常见的 Flask 项目会将 HTML 放在
templates
目录,Python 辅助函数放在独立的目录(如functions
)中。
index.html
在 templates/index.html
的页面底部,添加一个“其他功能”区域(字体较小),并在其中放一个“报工数据核对”按钮。该按钮点击后,通过 JavaScript 打开 data_check.html
(可在同窗口或新窗口打开,这里示例为新窗口 _blank
)。
下面是示例的完整页面(在您现有的 index.html
基础上仅在尾部添加了一个 <div class="footer-utilities">...</div>
块和对应的样式及脚本):
html<!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>智能助手</title> <style> body { font-family: Arial, sans-serif; background-color: #f4f4f4; margin: 0; padding: 0; } .container { width: 80%; margin: 50px auto; padding: 20px; background-color: #fff; border-radius: 8px; box-shadow: 0 0 15px rgba(0, 0, 0, 0.1); position: relative; } /* 头部区域:标题居中显示,两侧分别展示表单文件和知识库文件 */ .header { position: relative; text-align: center; margin-bottom: 20px; } .header h1 { margin: 0; font-size: 2em; } .header .title-right { font-size: 1em; position: absolute; right: 20px; top: 50%; transform: translateY(-50%); cursor: pointer; color: #333; } .header .title-right:hover { text-decoration: underline; } .header .title-left { font-size: 1em; position: absolute; left: 20px; top: 50%; transform: translateY(-50%); cursor: pointer; color: #333; } .header .title-left:hover { text-decoration: underline; } .form-group { margin-bottom: 20px; } label { font-size: 18px; } input[type="text"] { width: 100%; padding: 10px; font-size: 16px; border-radius: 5px; border: 1px solid #ddd; } button { padding: 10px 15px; font-size: 16px; background-color: #4CAF50; color: white; border: none; border-radius: 5px; cursor: pointer; } button:hover { background-color: #45a049; } .response { margin-top: 20px; padding: 20px; background-color: #f9f9f9; border-left: 5px solid #4CAF50; } #answer { white-space: pre-wrap; font-size: 16px; line-height: 1.5; } #loadingModal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.4); display: none; z-index: 9999; } #loadingContent { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: #fff; padding: 30px 50px; border-radius: 8px; font-size: 20px; } /* 知识库文件和表单文件容器样式 */ #knowledgeContainer, #formContainer { margin-top: 20px; padding: 15px; background-color: #f9f9f9; border-left: 5px solid #999; } #fileList, #formFileList { margin: 0; padding: 0; list-style-type: none; } #fileList li, #formFileList li { margin: 5px 0; } #closeKnowledgeBtn, #closeFormBtn { margin-top: 10px; } /* 搜索框样式 */ #searchRow { margin-top: 20px; margin-bottom: 10px; } #searchInput { width: 100%; padding: 10px; font-size: 16px; border-radius: 5px; border: 1px solid #ddd; } /* 新增的“其他功能”区域 */ .footer-utilities { position: relative; margin-top: 30px; font-size: 12px; /* 字体较小 */ text-align: left; /* 左对齐 */ } .footer-utilities .footer-title { font-size: 13px; /* 可以比正文略大一点或随需求 */ font-weight: bold; margin-bottom: 5px; } .footer-utilities button { font-size: 12px; /* 按钮也要比较小 */ padding: 5px 10px; background-color: #2196F3; } .footer-utilities button:hover { background-color: #0b7dda; } </style> </head> <body> <div class="container"> <!-- 头部区域:标题居中显示,两侧分别展示表单文件和知识库文件 --> <div class="header"> <span class="title-left" id="formToggle">📝表单文件</span> <h1>智能助手</h1> <span class="title-right" id="knowledgeToggle">📖知识库文件</span> </div> <!-- 搜索框,显示条件:只要有任一文件容器展开就显示 --> <div id="searchRow" style="display: none;"> <input type="text" id="searchInput" placeholder="搜索文件..." /> </div> <!-- 知识库文件展开区域 --> <div id="knowledgeContainer" style="display: none;"> <h3>知识库文件如下:</h3> <ul id="fileList"></ul> <button id="closeKnowledgeBtn">收起</button> </div> <!-- 表单文件展开区域 --> <div id="formContainer" style="display: none;"> <h3>表单文件如下:</h3> <ul id="formFileList"></ul> <button id="closeFormBtn">收起</button> </div> <form id="queryForm"> <div class="form-group"> <label for="query">🤖请输入您的问题,我会根据知识库文件进行回答:</label> <input type="text" id="query" name="query" required /> </div> <button type="submit">提交问题</button> </form> <div class="response" id="response" style="display: none;"> <h3>回答:</h3> <pre id="answer"></pre> </div> <!-- 新增“其他功能”区域 --> <div class="footer-utilities"> <div class="footer-title">其他功能</div> <button id="dataCheckBtn">报工数据核对</button> </div> </div> <div id="loadingModal"> <div id="loadingContent">⏱请稍等,资料海量,正在查询中…… <span id="timer">0</span> 秒</div> </div> <script> const queryForm = document.getElementById('queryForm'); const queryInput = document.getElementById('query'); const responseDiv = document.getElementById('response'); const answerElement = document.getElementById('answer'); const loadingModal = document.getElementById('loadingModal'); const timerElement = document.getElementById('timer'); let timerInterval; let seconds = 0; queryForm.addEventListener('submit', function(event) { event.preventDefault(); loadingModal.style.display = 'block'; seconds = 0; timerElement.textContent = seconds; timerInterval = setInterval(function() { seconds++; timerElement.textContent = seconds; }, 1000); let query = queryInput.value; fetch('/ask', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: 'query=' + encodeURIComponent(query) }) .then(response => response.json()) .then(data => { loadingModal.style.display = 'none'; clearInterval(timerInterval); if (data.error) { answerElement.innerHTML = '错误: ' + data.error; } else { answerElement.innerHTML = data.answer; } responseDiv.style.display = 'block'; }) .catch(error => { loadingModal.style.display = 'none'; clearInterval(timerInterval); answerElement.innerHTML = '发生错误,请稍后再试。'; responseDiv.style.display = 'block'; }); }); // 搜索框功能:过滤知识库文件和表单文件列表 const searchRow = document.getElementById('searchRow'); const searchInput = document.getElementById('searchInput'); searchInput.addEventListener('input', function() { const filter = searchInput.value.toLowerCase(); document.querySelectorAll('#fileList li').forEach(li => { const text = li.textContent.toLowerCase(); li.style.display = text.includes(filter) ? 'list-item' : 'none'; }); document.querySelectorAll('#formFileList li').forEach(li => { const text = li.textContent.toLowerCase(); li.style.display = text.includes(filter) ? 'list-item' : 'none'; }); }); function updateSearchRowVisibility() { const knowledgeContainer = document.getElementById('knowledgeContainer'); const formContainer = document.getElementById('formContainer'); const knowledgeVisible = knowledgeContainer.style.display === 'block'; const formVisible = formContainer.style.display === 'block'; if (knowledgeVisible || formVisible) { searchRow.style.display = 'block'; } else { searchRow.style.display = 'none'; searchInput.value = ''; document.querySelectorAll('#fileList li, #formFileList li').forEach(li => { li.style.display = 'list-item'; }); } } // 知识库文件相关逻辑 const knowledgeToggle = document.getElementById('knowledgeToggle'); const knowledgeContainer = document.getElementById('knowledgeContainer'); const closeKnowledgeBtn = document.getElementById('closeKnowledgeBtn'); const fileList = document.getElementById('fileList'); knowledgeToggle.addEventListener('click', function() { if (knowledgeContainer.style.display === 'none' || knowledgeContainer.style.display === '') { knowledgeContainer.style.display = 'block'; fetch('/static/jsons/combined_faiss_index.json') .then(res => res.json()) .then(jsonData => { const fileLinkMap = {}; jsonData.forEach(item => { if (item.filename && item.web) { fileLinkMap[item.filename] = item.web; } }); fileList.innerHTML = ''; Object.keys(fileLinkMap).forEach(filename => { const li = document.createElement('li'); const a = document.createElement('a'); a.href = fileLinkMap[filename]; a.textContent = filename; a.target = '_blank'; li.appendChild(a); fileList.appendChild(li); }); }) .catch(err => { console.error('获取 JSON 文件出错:', err); fileList.innerHTML = '<li>获取 JSON 文件失败,请检查路径或服务器配置。</li>'; }); } else { knowledgeContainer.style.display = 'none'; } updateSearchRowVisibility(); }); closeKnowledgeBtn.addEventListener('click', function() { knowledgeContainer.style.display = 'none'; updateSearchRowVisibility(); }); // 表单文件相关逻辑 const formToggle = document.getElementById('formToggle'); const formContainer = document.getElementById('formContainer'); const closeFormBtn = document.getElementById('closeFormBtn'); const formFileList = document.getElementById('formFileList'); formToggle.addEventListener('click', function() { if (formContainer.style.display === 'none' || formContainer.style.display === '') { formContainer.style.display = 'block'; fetch('/static/jsons/table_list_web.json') .then(res => res.json()) .then(jsonData => { const fileLinkMap = {}; jsonData.forEach(item => { if (item.filename && item.web) { fileLinkMap[item.filename] = item.web; } }); formFileList.innerHTML = ''; Object.keys(fileLinkMap).forEach(filename => { const li = document.createElement('li'); const a = document.createElement('a'); a.href = fileLinkMap[filename]; a.textContent = filename; a.target = '_blank'; li.appendChild(a); formFileList.appendChild(li); }); }) .catch(err => { console.error('获取 JSON 文件出错:', err); formFileList.innerHTML = '<li>获取 JSON 文件失败,请检查路径或服务器配置。</li>'; }); } else { formContainer.style.display = 'none'; } updateSearchRowVisibility(); }); closeFormBtn.addEventListener('click', function() { formContainer.style.display = 'none'; updateSearchRowVisibility(); }); // 新增的报工数据核对按钮 const dataCheckBtn = document.getElementById('dataCheckBtn'); dataCheckBtn.addEventListener('click', function() { // 方式1:在新窗口/标签页打开 data_check.html window.open('/data_check', '_blank'); // 如果需要在同一页面中跳转,可换成: // window.location.href = '/data_check'; }); </script> </body> </html>
以上修改仅在原有代码底部增加了一个小区域(
.footer-utilities
)和一个按钮 (#dataCheckBtn
)。其余与您原先的代码保持一致,便于整合。
data_check.html
在 templates/data_check.html
中实现简单的拖拽上传 XLSX 文件,并将文件通过 fetch
或 form
形式提交到后端(假定后端的上传处理接口是 /upload_data_check
)。后端完成处理后,以 JSON 返回出问题的行数据,前端再进行渲染并对 ≥8000 的数据进行红色加粗。
注意:是否要在前端就进行红色加粗,或后端直接标记好,均可。此处演示由后端返回普通文本,前端再进行判断高亮。也可以后端直接给出 HTML 标记返回给前端。
示例 data_check.html
:
html<!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <title>报工数据核对</title> <style> body { font-family: Arial, sans-serif; margin: 20px; } h1 { margin-bottom: 20px; } #dropzone { width: 80%; height: 150px; border: 2px dashed #ccc; border-radius: 8px; display: flex; align-items: center; justify-content: center; color: #999; margin-bottom: 20px; cursor: pointer; transition: background-color 0.3s; } #dropzone.hover { background-color: #f0f0f0; } #result-table { border-collapse: collapse; width: 80%; } #result-table th, #result-table td { border: 1px solid #ccc; padding: 8px; text-align: left; } #result-table th { background-color: #f8f8f8; } .highlight { color: red; font-weight: bold; } </style> </head> <body> <h1>报工数据核对</h1> <div id="dropzone">将 XLSX 文件拖拽到此处,或点击选择文件</div> <input type="file" id="fileInput" accept=".xlsx" style="display: none;" /> <table id="result-table" style="display:none;"> <thead id="table-head"></thead> <tbody id="table-body"></tbody> </table> <script> const dropzone = document.getElementById('dropzone'); const fileInput = document.getElementById('fileInput'); const table = document.getElementById('result-table'); const tableHead = document.getElementById('table-head'); const tableBody = document.getElementById('table-body'); // 点击打开文件选择 dropzone.addEventListener('click', () => { fileInput.click(); }); // 文件选择事件 fileInput.addEventListener('change', handleFiles); // 拖拽进入 dropzone.addEventListener('dragenter', (e) => { e.preventDefault(); dropzone.classList.add('hover'); }); // 拖拽离开 dropzone.addEventListener('dragleave', (e) => { e.preventDefault(); dropzone.classList.remove('hover'); }); // 拖拽到目标上方 dropzone.addEventListener('dragover', (e) => { e.preventDefault(); }); // 放下文件 dropzone.addEventListener('drop', (e) => { e.preventDefault(); dropzone.classList.remove('hover'); if (e.dataTransfer.files.length) { handleFiles({ target: { files: e.dataTransfer.files } }); } }); function handleFiles(event) { const file = event.target.files[0]; if (!file) return; // 使用 FormData 提交到后端 const formData = new FormData(); formData.append('file', file); fetch('/upload_data_check', { method: 'POST', body: formData }) .then(res => res.json()) .then(data => { if (data.error) { alert('出错了:' + data.error); return; } // data.rows 是返回的所有符合条件行及整个表头等信息 // 这里假设 data 结构是 { header: [...], rows: [ [...], [...], ... ] } if (!data.header || !data.rows) { alert('返回数据格式不正确或没有符合条件的行。'); return; } // 清空现有表格 tableHead.innerHTML = ''; tableBody.innerHTML = ''; // 构建表头 const trHead = document.createElement('tr'); data.header.forEach(h => { const th = document.createElement('th'); th.textContent = h; trHead.appendChild(th); }); tableHead.appendChild(trHead); // 构建表体 data.rows.forEach(row => { const tr = document.createElement('tr'); row.forEach((cell, index) => { const td = document.createElement('td'); // 如果这一列正好是大于等于8000的列,则加红加粗 // 假定前端根据 row[index] 来判断 if (!isNaN(cell) && Number(cell) >= 8000) { td.textContent = cell; td.classList.add('highlight'); } else { td.textContent = cell; } tr.appendChild(td); }); tableBody.appendChild(tr); }); // 显示表格 table.style.display = 'table'; }) .catch(err => { alert('上传或处理失败:' + err); }); } </script> </body> </html>
.xlsx
文件拖拽到 #dropzone
区域,或点击选择文件后,使用 fetch
向后端 /upload_data_check
路由提交。header
: XLSX 文件的表头字段列表rows
: 所有符合“第2列 ≥ 8000”条件的完整行数据check_data.py
将此文件放在 functions/check_data.py
(或您的其他逻辑目录)下,用于处理 Excel 的读取与检查逻辑。可以使用 pandas
或 openpyxl
等库。下面以 pandas
为例:
python# functions/check_data.py import pandas as pd import os def check_data(file_path): """ 读取上传的 xlsx 文件,检查其第二列数据是否 >= 8000, 返回表头信息和所有符合条件的行,供前端渲染。 """ try: df = pd.read_excel(file_path, header=0) # 读取Excel,默认第一行为列名 # 判断第2列的列名,一般是 df.columns[1](如果只关心第一行做表头) second_col_name = df.columns[1] # 找到符合条件的行 filtered_df = df[df[second_col_name] >= 8000] # 如果没有符合条件的行,则可以返回空结构 if filtered_df.empty: return { "header": list(df.columns), "rows": [] } # 将结果 DataFrame 转为二维列表 header = list(df.columns) rows = filtered_df.values.tolist() return { "header": header, "rows": rows } except Exception as e: return { "error": str(e) } finally: # 删除临时文件,避免堆积(可选) if os.path.exists(file_path): os.remove(file_path)
注意:
check_data
函数会在读完Excel后,删除临时文件(如有需要保留原文件,可移除对应逻辑)。- 如果您的 Excel 文件没有表头,或结构有变化,需要相应调整读取方式。
在您的 main_assistant_flask.py
中,为了能够接收 /upload_data_check
的 Post 请求,并调用 check_data.py
中的函数,需要添加一个路由。例如:
python# main_assistant_flask.py (节选示例) from flask import Flask, request, jsonify, render_template import os from functions.check_data import check_data from werkzeug.utils import secure_filename app = Flask(__name__) @app.route('/') def index(): return render_template('index.html') @app.route('/data_check') def data_check_page(): # 直接渲染 data_check.html return render_template('data_check.html') @app.route('/upload_data_check', methods=['POST']) def upload_data_check(): # 接收 XLSX 文件 file = request.files.get('file') if not file: return jsonify({"error": "No file uploaded"}) filename = secure_filename(file.filename) save_path = os.path.join("/tmp", filename) # 保存到临时路径,也可以放到别的路径 file.save(save_path) # 调用函数进行检查 result = check_data(save_path) return jsonify(result) if __name__ == '__main__': app.run(debug=True)
以上仅为演示主要逻辑。请根据您现有项目实际情况(如:使用 gunicorn、docker 等)去做相应的改动。
index.html
:在底部左侧区域新增“其他功能”以及“报工数据核对”按钮,点击后跳转到 data_check.html
。data_check.html
:实现拖拽上传 XLSX 文件,并将文件通过 POST 提交至 /upload_data_check
路由,获取后端返回的符合条件的行,并展示到页面表格。对数值 ≥ 8000 的单元格进行红色加粗。check_data.py
:负责读取 Excel 并筛选第二列 ≥ 8000 的数据行,然后返回结构化 JSON 给前端。/data_check
(渲染页面)和 /upload_data_check
(处理上传)的接口。这样即可实现您所需的“报工数据核对”功能,并将 ≥8000 的行数据标红加粗地展示给用户。
Traceback (most recent call last):
File "/root/miniconda3/envs/ai_assis_3.10p/lib/python3.10/site-packages/flask/app.py", line 1511, in wsgi_app
response = self.full_dispatch_request()
File "/root/miniconda3/envs/ai_assis_3.10p/lib/python3.10/site-packages/flask/app.py", line 919, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/root/miniconda3/envs/ai_assis_3.10p/lib/python3.10/site-packages/flask/app.py", line 917, in full_dispatch_request
rv = self.dispatch_request()
File "/root/miniconda3/envs/ai_assis_3.10p/lib/python3.10/site-packages/flask/app.py", line 902, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args) # type: ignore[no-any-return]
File "/root/Myprojects/AI_assistant/main_assistant_flask .py", line 258, in upload_data_check
return jsonify(result)
File "/root/miniconda3/envs/ai_assis_3.10p/lib/python3.10/site-packages/flask/json/init.py", line 170, in jsonify
return current_app.json.response(*args, **kwargs) # type: ignore[return-value]
File "/root/miniconda3/envs/ai_assis_3.10p/lib/python3.10/site-packages/flask/json/provider.py", line 214, in response
f"{self.dumps(obj, **dump_args)}\n", mimetype=self.mimetype
File "/root/miniconda3/envs/ai_assis_3.10p/lib/python3.10/site-packages/flask/json/provider.py", line 179, in dumps
return json.dumps(obj, **kwargs)
File "/root/miniconda3/envs/ai_assis_3.10p/lib/python3.10/json/init.py", line 238, in dumps
**kw).encode(obj)
File "/root/miniconda3/envs/ai_assis_3.10p/lib/python3.10/json/encoder.py", line 199, in encode
chunks = self.iterencode(o, _one_shot=True)
File "/root/miniconda3/envs/ai_assis_3.10p/lib/python3.10/json/encoder.py", line 257, in iterencode
return _iterencode(o, 0)
File "/root/miniconda3/envs/ai_assis_3.10p/lib/python3.10/site-packages/flask/json/provider.py", line 110, in _default
return http_date(o)
File "/root/miniconda3/envs/ai_assis_3.10p/lib/python3.10/site-packages/werkzeug/http.py", line 1037, in http_date
return email.utils.format_datetime(timestamp, usegmt=True)
File "/root/miniconda3/envs/ai_assis_3.10p/lib/python3.10/email/utils.py", line 278, in format_datetime
now = dt.timetuple()
File "nattype.pyx", line 54, in pandas._libs.tslibs.nattype._make_error_func.f
上传xlsx文件时遇到这样的报错
根据报错栈信息来看,根本原因是 返回给前端的 JSON 数据里,包含了 Pandas 的 NaT
(Not a Time) 或者其他无法被默认 JSON 编码器序列化的对象。这会导致 jsonify()
在序列化时失败,进而抛出类似:
File "nattype.pyx", line 54, in pandas._libs.tslibs.nattype._make_error_func.f
或
TypeError: Object of type Timestamp is not JSON serializable
等报错。
这是一个相对常见的问题。当 DataFrame 中包含了日期时间列,且这些列存在空值(Pandas 会将空日期记为 NaT
),Flask/Python 默认的 json.dumps()
就无法处理它们。
Excel 某一列是日期类型
当 Pandas 读取该列时会解析成 datetime64 或者 Timestamp,如果单元格为空,则对应为 NaT
。
返回 DataFrame 里包含空值
Pandas 用 NaN
/NaT
表示空数值/空时间,默认不支持直接 JSON 序列化。
DataFrame 中包含对象类型的列
比如带有自定义对象或非纯文本、数字的列,也可能导致序列化异常。
在将 DataFrame 转成列表或字典前,先将所有 NaT
/ NaN
替换为 None(或者字符串)即可。示例:
pythonimport pandas as pd import numpy as np df = pd.read_excel(file_path) # 让 df 中的 NaN、NaT、Infinity 等都变成 None df = df.where(df.notnull(), None) # 或者只针对时间列进行格式化 # 假设 'time_col' 是日期列,则可以转成字符串 # df['time_col'] = df['time_col'].dt.strftime('%Y-%m-%d %H:%M:%S') # 然后把 DataFrame 转成 2D 列表 rows = df.values.tolist()
此时,rows
不会再包含 NaT
之类的特殊值,从而顺利被 jsonify(rows)
序列化。
注意:如果存在日期列,且需要在前端以字符串形式展示,可以自行决定格式化。比如:
pythondf['time_col'] = df['time_col'].apply(lambda x: x.strftime('%Y-%m-%d') if pd.notnull(x) else None)
DataFrame.to_json()
里指定引擎和处理方式如果想一次性把整个 DataFrame 序列化为 JSON,可直接使用 Pandas 的 to_json()
方法,并指定 date_format
等参数:
pythonjson_str = df.to_json(orient='records', date_format='iso', force_ascii=False) return jsonify(json.loads(json_str))
其中:
orient='records'
会将行转换为列表的形式date_format='iso'
会将日期格式化为 ISO8601,如 "2025-04-12T10:00:00"
同样,如果 DataFrame 中有空值,会自动序列化为 null
,不会出现 Python 原生 NaT
。
Flask >=2.2 默认使用了新的 JSON Provider。可以通过重载 Flask.json_provider_class
或自定义 app.json_encoder
来处理时间、空值等特殊类型。但这对新手而言相对复杂,且只适合在你需要全局处理各种自定义类型时使用。对于单一场景,前两种方法更简单、直接。
以下以您之前的 check_data.py
示例为例,说明如何在筛选完 DataFrame 后,处理 NaT
问题并返回可被 jsonify
序列化的对象。
check_data.py
中转换 NaT
/ NaN
python# functions/check_data.py import pandas as pd import os def check_data(file_path): try: df = pd.read_excel(file_path, header=0) second_col_name = df.columns[1] # 第2列列名 # 筛选第2列 >= 8000 的行 filtered_df = df[df[second_col_name] >= 8000] # 转换 df 里的 NaN/NaT 为 None,以便 JSON 序列化 filtered_df = filtered_df.where(filtered_df.notnull(), None) # 如果需要对所有日期列进行格式化,可在此处进一步处理 # for col in filtered_df.select_dtypes(include=['datetime']): # filtered_df[col] = filtered_df[col].apply( # lambda x: x.strftime('%Y-%m-%d %H:%M:%S') if x else None # ) header = list(df.columns) rows = filtered_df.values.tolist() return { "header": header, "rows": rows } except Exception as e: return {"error": str(e)} finally: if os.path.exists(file_path): os.remove(file_path)
jsonify(result)
python@app.route('/upload_data_check', methods=['POST']) def upload_data_check(): file = request.files.get('file') if not file: return jsonify({"error": "No file uploaded"}) filename = secure_filename(file.filename) save_path = os.path.join("/tmp", filename) file.save(save_path) result = check_data(save_path) # 这里的 result 已经是可序列化的 dict 结构。直接返回 return jsonify(result)
这样就不会再出现 NaT
或其他无法被 JSON 序列化的对象了。
NaT
/NaN
/时间戳等特殊值,而 Flask/Python 默认的 jsonify
无法编码它们。df = df.where(df.notnull(), None)
将空值替换为 None。strftime
将其转换为字符串。df.to_json()
,指定 orient='records'
和合适的 date_format
。完成这些转换后,即可顺利通过 jsonify()
返回结果给前端。
上传或处理失败:SyntaxError: Unexpected token '<', "<!doctype "... is not valid JSON
这个错误通常意味着前端尝试用 JSON 解析从服务器返回的数据,但实际返回的是 HTML 文档(例如一个错误页面或者 404 页面),而 HTML 以 “<!doctype” 开头,自然不能被解析为合法 JSON。
可能的原因和解决方案如下:
路由未命中或返回 HTML
请确认上传文件的请求确实命中了后端的 /upload_data_check
路由。若路由名称或方法不匹配,Flask 可能会返回默认的 404 页面(HTML 格式),导致 JSON 解析失败。
methods=['POST']
。后端处理出错
如果在后端处理过程中出现错误(比如读取文件、解析 Excel 等发生异常),而没有捕获或者返回 JSON 格式错误信息,Flask 可能会生成一个错误页面(HTML格式)。
python@app.route('/upload_data_check', methods=['POST']) def upload_data_check(): try: file = request.files.get('file') if not file: return jsonify({"error": "No file uploaded"}) # 保存文件等操作…… result = check_data(save_path) return jsonify(result) except Exception as e: return jsonify({"error": str(e)})
调试步骤
总结:
“Unexpected token '<'” 错误通常说明服务器返回的是 HTML(例如错误页面),而前端代码期望 JSON 数据。请确认上传接口的 URL、HTTP 方法和后端处理逻辑都正确,确保在所有情况下返回的都是有效的 JSON 格式数据。
@app.route('/upload_data_check', methods=['POST'])
def upload_data_check():
# 接收 XLSX 文件
file = request.files.get('file')
if not file:
return jsonify({"error": "No file uploaded"})
filename = secure_filename(file.filename)
save_path = os.path.join("/tmp", filename) # 保存到临时路径,也可以放到别的路径
file.save(save_path)
text# 调用函数进行检查 result = check_data(save_path) return jsonify(result)
再此代码上进行修改
下面给出的是修改后的代码示例,增加了异常捕获、HTTP 状态码返回,并在 finally 块中清理临时保存的文件,以确保返回数据始终为合法的 JSON 格式。请根据您的项目环境确保其它依赖(如 secure_filename 的导入、check_data 函数等)均已正确配置:
pythonfrom flask import Flask, request, jsonify, render_template import os from werkzeug.utils import secure_filename from functions.check_data import check_data app = Flask(__name__) @app.route('/upload_data_check', methods=['POST']) def upload_data_check(): save_path = None try: # 接收 XLSX 文件 file = request.files.get('file') if not file: return jsonify({"error": "No file uploaded"}), 400 filename = secure_filename(file.filename) save_path = os.path.join("/tmp", filename) # 保存到临时路径,也可以放到其他路径 file.save(save_path) # 调用函数进行检查(函数内部建议已转换所有 NaN/NaT 为 None 或字符串) result = check_data(save_path) # 若 check_data 函数返回了包含错误信息的字典,则设置错误状态码 if "error" in result: return jsonify(result), 500 return jsonify(result) except Exception as e: # 捕获所有异常,返回 JSON 格式的错误信息 return jsonify({"error": str(e)}), 500 finally: # 清理临时文件,避免文件堆积(仅当 save_path 已被赋值且文件存在时执行) if save_path and os.path.exists(save_path): os.remove(save_path) if __name__ == '__main__': app.run(debug=True)
异常捕获
HTTP 状态码
临时文件清理
save_path
是否存在,若存在则删除上传的临时文件,防止临时目录堆积文件。请确保 check_data 函数内对 Excel 数据进行必要的处理(比如将 NaT/NaN 转换为 None 或字符串),以避免 JSON 序列化时仍出现异常。
源码如下:
import pandas as pd
import os
def check_data(file_path):
try:
df = pd.read_excel(file_path, header=0)
print(df.head())
second_col_name = df.columns[1] # 第2列列名
text# 筛选第2列 >= 8000 的行 filtered_df = df[df[second_col_name] >= 8000] # 转换 df 里的 NaN/NaT 为 None,以便 JSON 序列化 filtered_df = filtered_df.where(filtered_df.notnull(), None) # 如果需要对所有日期列进行格式化,可在此处进一步处理 # for col in filtered_df.select_dtypes(include=['datetime']): # filtered_df[col] = filtered_df[col].apply( # lambda x: x.strftime('%Y-%m-%d %H:%M:%S') if x else None # ) header = list(df.columns) rows = filtered_df.values.tolist() return { "header": header, "rows": rows } except Exception as e: return {"error": str(e)} finally: if os.path.exists(file_path): os.remove(file_path)
终端显示如下:
炉号 铸锭总重量 ID 看板计划ID MES计划ID 设备 锭号 牌号 实际投料量 熔炼总时长 日期 开始时间 全熔时间 ... 检查人2 耗电量 最大漏电电流 真空感应浮渣级别 评价人 送电前漏率单位 底垫砖1烘烤日期 底垫砖2烘烤日期 底垫砖3烘烤日期 底垫砖4烘烤日期 底垫砖5烘烤日期 底垫砖6烘烤日期 填写状态
0 4800-24000651 0 822 48678 271591 4#VIM 4800-24000651 GH5188 6013.981 18.88 2025-03-04 2025-03-04 04:23:17 NaT ... 郑凯 7416.0 0.0 NaN NaN Pa/min NaT NaT NaT NaT NaN NaN 编辑中
1 4016-25000090 7936 821 49305 272436 1#VIM 4016-25000090 GH4169G 8066.052 14.78 2025-03-04 2025-03-04 07:33:44 NaT ... 王少勋 6327.0 0.0 NaN NaN Pa/min NaT NaT NaT NaT NaN NaN 已完成
2 4028-25009021 1342 820 49258 272428 2#VIM 4028-25009021 NiCr 2099.060 9.00 2025-03-04 2025-03-04 15:20:08 NaT ... NaN 2058.0 17.6 NaN NaN Pa/min 2025-03-05 2025-03-05 NaT NaT NaN NaN 已完成
3 4547-25007013 0 819 49115 272219 2#VIM 4547-25007013 SUS316L-A 1032.950 19.32 2025-03-03 2025-03-03 18:54:01 NaT ... NaN 2486.0 8.3 NaN NaN Pa/min 2025-02-26 NaT NaT NaT NaN NaN 编辑中
4 4002-25000074 7970 818 49075 272177 1#VIM 4002-25000074 GH4169 8057.189 11.83 2025-03-03 2025-03-03 19:00:11 NaT ... 柴超杰 5737.0 0.0 NaN NaN Pa/min NaT NaT NaT NaT NaN NaN 已完成
[5 rows x 360 columns]
依然如图报错,如何进行修改?
从您提供的日志和打印结果来看,主要问题并不在于空值 NaT
本身(因为 where(filtered_df.notnull(), None)
已经把空值替换为 None
),而是 DataFrame 里那些有效的日期时间值 依旧是 Pandas 的 Timestamp
对象,Flask 默认的 JSON 序列化器无法直接处理它们,从而抛出类似 “NaTType does not support timetuple” 或 “Object of type Timestamp is not JSON serializable” 的错误。
换句话说:
Timestamp
类型存在;Timestamp
对象,Flask 默认的 jsonify()
无法直接处理;df.to_json()
)。下面给出两种常见的修改方案,您可根据需求任选其一或结合使用。
在 check_data.py
里,筛选完之后加一段转换日期列的逻辑(解开您原先注释的那部分)。示例如下:
python# functions/check_data.py import pandas as pd import os def check_data(file_path): try: df = pd.read_excel(file_path, header=0) print(df.head()) second_col_name = df.columns[1] # 第2列列名 # 筛选第2列 >= 8000 的行 filtered_df = df[df[second_col_name] >= 8000] # 将 NaN/NaT 替换为 None filtered_df = filtered_df.where(filtered_df.notnull(), None) # 关键:将所有 datetime 类型的列转成字符串,避免 Timestamp 导致的 JSON 序列化错误 for col in filtered_df.select_dtypes(include=["datetime64[ns]", "datetimetz"]): filtered_df[col] = filtered_df[col].apply( lambda x: x.strftime('%Y-%m-%d %H:%M:%S') if x else None ) # 也可以一次性将所有日期列都做统一格式化: # filtered_df[col] = filtered_df[col].dt.strftime('%Y-%m-%d %H:%M:%S') # 准备返回 header = list(df.columns) # 若列名中不会出现时间格式,一般都是字符串,这里问题不大 rows = filtered_df.values.tolist() return { "header": header, "rows": rows } except Exception as e: return {"error": str(e)} finally: # 删除临时文件 if os.path.exists(file_path): os.remove(file_path)
filtered_df.where(filtered_df.notnull(), None)
仅能把空值替换成 None
;不会 把非空的 Timestamp
转成字符串。select_dtypes(include=["datetime64[ns]", "datetimetz"])
找出所有日期时间列,逐行或一次性 strftime()
,最终把 Timestamp
变成普通字符串。rows
内部就只有普通的 Python 基本类型(字符串、数字、None…),不会再触发 “NaTType does not support timetuple” 之类的错误。to_json()
格式化另一种思路是 完全交给 Pandas 的 to_json()
。例如:
pythondef check_data(file_path): try: df = pd.read_excel(file_path, header=0) second_col_name = df.columns[1] filtered_df = df[df[second_col_name] >= 8000] # 将空值替换为 None 并做日期格式化,可通过 date_format 参数控制 # orient='records' -> 每行是一个对象 # date_format='iso' -> 把 Timestamp 转为 ISO8601 字符串,如 "2025-03-04T04:23:17" json_str = filtered_df.to_json(orient='records', date_format='iso', force_ascii=False) # json_str 是一个合法的 JSON 字符串,再套一层 dict 以保持之前返回结构 return { "header": list(df.columns), "rows": pd.read_json(json_str, orient='records').values.tolist() } except Exception as e: return {"error": str(e)} finally: if os.path.exists(file_path): os.remove(file_path)
filtered_df.to_json()
会自动把 Pandas 的空值、时间戳等转换成可以被 JSON 接受的内容,例如 null
、ISO8601 字符串等。
由于您似乎想在返回的数据结构中保留 ["header", "rows"]
这样的形式,这里又用 pd.read_json(json_str)
解析回 DataFrame,再 values.tolist()
成二维数组。比较绕,但能省掉手动循环的麻烦。
如果您希望直接将整段 JSON 返回给前端,也可以写成:
pythonreturn { "header": list(df.columns), "rows_json": json_str # 前端自己 parse }
这样前端拿到 "rows_json"
后再自行 JSON.parse(...)
。
不过对大多数场景而言,第一种方案(手动遍历日期列)更直观、更可控。
Timestamp
或 NaT
在 Flask 的默认 JSON 序列化中会报错,需要先转成字符串或 None。strftime
转换成字符串。DataFrame.to_json(date_format='iso')
统一处理,让 Pandas 替我们完成序列化。rows
数组中只有普通类型(字符串、数字、None),不会再出现 Timestamp
或 NaT
导致的错误。main函数相关代码如下:
@app.route('/upload_data_check', methods=['POST'])
def upload_data_check():
save_path = None
try:
# 接收 XLSX 文件
file = request.files.get('file')
if not file:
return jsonify({"error": "No file uploaded"}), 400
textfilename = secure_filename(file.filename) save_path = os.path.join("/tmp", filename) # 保存到临时路径,也可以放到其他路径 file.save(save_path) # 调用函数进行检查(函数内部建议已转换所有 NaN/NaT 为 None 或字符串) result = check_data(save_path) # 若 check_data 函数返回了包含错误信息的字典,则设置错误状态码 if "error" in result: return jsonify(result), 500 return jsonify(result) except Exception as e: # 捕获所有异常,返回 JSON 格式的错误信息 return jsonify({"error": str(e)}), 500 finally: # 清理临时文件,避免文件堆积(仅当 save_path 已被赋值且文件存在时执行) if save_path and os.path.exists(save_path): os.remove(save_path)
功能函数如下:
import pandas as pd
import os
def check_data(file_path):
try:
df = pd.read_excel(file_path, header=0)
second_col_name = df.columns[1]
filtered_df = df[df[second_col_name] >= 8000]
text# 将空值替换为 None 并做日期格式化,可通过 date_format 参数控制 # orient='records' -> 每行是一个对象 # date_format='iso' -> 把 Timestamp 转为 ISO8601 字符串,如 "2025-03-04T04:23:17" json_str = filtered_df.to_json(orient='records', date_format='iso', force_ascii=False) # json_str 是一个合法的 JSON 字符串,再套一层 dict 以保持之前返回结构 return { "header": list(df.columns), "rows": pd.read_json(json_str, orient='records').values.tolist() } except Exception as e: return {"error": str(e)} finally: if os.path.exists(file_path): os.remove(file_path)
修改检查逻辑,将上传的文件的第2列数据进行读取,将第2列数字>8000的所在的行的第1列列名,和第1列对应的内容,以及第2列列名,和第2列对应的>8000数字保存为1个json格式,进行输出。即只输出检查出的数据的列名和错误数据即可。
下面给出一个修改后的示例代码,满足您所说的:
只读取上传的 Excel 文件的 第2列 数据,筛选出 数值 > 8000 的行,然后只输出:
- 第1列列名与对应的值
- 第2列列名与对应的值
以 JSON 的形式返回给前端,即 “只输出检查出的数据的列名和错误数据”。
check_data.py
您可以将原先的 check_data
函数修改为下面这样(保留原先的异常处理和文件删除逻辑):
python# functions/check_data.py import pandas as pd import os def check_data(file_path): try: # 1. 读取 Excel,并获取列名 df = pd.read_excel(file_path, header=0) # 2. 获取第1列和第2列的列名 col1_name = df.columns[0] # 第1列列名 col2_name = df.columns[1] # 第2列列名 # 3. 从第2列中筛选出 > 8000 的行 # 注意:如果第2列不是纯数字,可能需要进一步转换或做异常处理 filtered_df = df.loc[df[col2_name] > 8000, [col1_name, col2_name]] # 4. 将筛选得到的 DataFrame 转为 JSON 字符串(仅包含2列) # 由于只包含第1列和第2列,不涉及时间类型,因此无需特别 date_format json_str = filtered_df.to_json(orient='records', force_ascii=False) # 5. 解析回 Python 列表;格式为 [[col1_val, col2_val], [col1_val, col2_val], ...] rows = pd.read_json(json_str, orient='records').values.tolist() # 6. 返回结果时,只包含这2列的列名和对应值 return { "header": [col1_name, col2_name], "rows": rows } except Exception as e: return {"error": str(e)} finally: # 删除临时文件,防止堆积 if os.path.exists(file_path): os.remove(file_path)
col2_name = df.columns[1]
取第2列列名。filtered_df = df.loc[df[col2_name] > 8000, [col1_name, col2_name]]
只保留行满足“第2列 > 8000”的条件,并只选择第1列和第2列。"header"
返回 [第一列列名, 第二列列名]
,例如 ["炉号", "铸锭总重量"]
。"rows"
返回一个二维列表,其中每个元素(行)只有第1列和第2列的值。例如 [[4800-24000651, 10000], [....], ...]
。to_json()
再 read_json()
values.tolist()
也行。to_json()
的写法是为了兼容可能的中文和确保序列化过程不会因为某些特殊值(如空字符串、特殊字符)出问题。一样可行。pythonrows = filtered_df.values.tolist() return { "header": [col1_name, col2_name], "rows": rows }
您在 main_assistant_flask.py
中的 /upload_data_check
路由无需特别调整,跟原来一样即可。该路由会调用 check_data()
函数,并返回 JSON 数据给前端。
示例:
python@app.route('/upload_data_check', methods=['POST']) def upload_data_check(): save_path = None try: file = request.files.get('file') if not file: return jsonify({"error": "No file uploaded"}), 400 filename = secure_filename(file.filename) save_path = os.path.join("/tmp", filename) file.save(save_path) result = check_data(save_path) if "error" in result: return jsonify(result), 500 return jsonify(result) except Exception as e: return jsonify({"error": str(e)}), 500 finally: if save_path and os.path.exists(save_path): os.remove(save_path)
前端拿到返回数据
data["header"]
-> [第1列列名, 第2列列名]
data["rows"]
-> [[row1_col1, row1_col2], [row2_col1, row2_col2], ...]
渲染到页面
只需遍历 data["rows"]
并按照需要显示即可。例如:
js.then(data => { if (data.error) { alert("出错了:" + data.error); return; } console.log("header:", data.header); console.log("rows:", data.rows); // 只包含两列 -> data.header[0], data.header[1] // -> data.rows[i][0], data.rows[i][1] }) .catch(err => console.error(err));
这样就实现了 只输出第1列和第2列的列名,以及 只筛选出第2列 > 8000 的行对应的值。
import pandas as pd
import os
def check_data(file_path):
try:
# 读取 Excel,并获取列名
df = pd.read_excel(file_path, header=0)
text# 获取第1列和第2列的列名 col1_name = df.columns[0] # 第1列列名,炉号 col2_name = df.columns[1] # 第2列列名,铸锭总重量 col9_name = df.columns[8] # 第8列列名,熔炼总时长 # 以下开始进行错误数据筛选 # 错误1,铸锭重量为0 wrong_1 = df.loc[df[col2_name] <= 0, [col1_name, col2_name]] # 错误2,熔炼总时长为负数或0 wrong_2 = df.loc[df[col9_name] <= 0, [col1_name, col9_name]] # 将筛选得到的 DataFrame 转为 JSON 字符串(仅包含2列) json_str1 = wrong_1.to_json(orient='records', force_ascii=False) json_str2 = wrong_2.to_json(orient='records', force_ascii=False) # 解析回 Python 列表;格式为 [[col1_val, col2_val], [col1_val, col2_val], ...] rows1 = pd.read_json(json_str1, orient='records').values.tolist() rows2 = pd.read_json(json_str2, orient='records').values.tolist() # 返回结果时,只包含这2列的列名和对应值 return { "header": [col1_name, col2_name, col9_name], "rows": rows1, rows2 } except Exception as e: return {"error": str(e)} finally: # 删除临时文件,防止堆积 if os.path.exists(file_path): os.remove(file_path)
我需要筛选并展示不同列的错误数据,同时只展示错误列的第1列炉号,以及错误数据本身和列名即可。如何修改代码进行展示?
下面给出修改后的函数示例,满足如下要求:
json{ "errors": [ { "错误列": "铸锭总重量", "炉号": "4800-24000651", "错误数据": 0 }, { "错误列": "熔炼总时长", "炉号": "4016-25000090", "错误数据": -5 } ] }
修改后的代码如下:
python# functions/check_data.py import pandas as pd import os def check_data(file_path): try: # 1. 读取 Excel,并获取所有列名 df = pd.read_excel(file_path, header=0) # 2. 获取需要用到的列名 col1_name = df.columns[0] # 第1列,炉号 col2_name = df.columns[1] # 第2列,铸锭总重量 col9_name = df.columns[8] # 第9列,熔炼总时长 # 3. 筛选错误数据 # 错误1:铸锭总重量 ≤ 0 wrong_1 = df.loc[df[col2_name] <= 0, [col1_name, col2_name]] # 错误2:熔炼总时长 ≤ 0 wrong_2 = df.loc[df[col9_name] <= 0, [col1_name, col9_name]] # 4. 整理返回数据:仅展示“炉号”、错误列名及错误数据 errors = [] # 处理错误1数据 for _, row in wrong_1.iterrows(): errors.append({ "错误列": col2_name, "炉号": row[col1_name], "错误数据": row[col2_name] }) # 处理错误2数据 for _, row in wrong_2.iterrows(): errors.append({ "错误列": col9_name, "炉号": row[col1_name], "错误数据": row[col9_name] }) return { "errors": errors } except Exception as e: return {"error": str(e)} finally: # 删除临时文件,避免文件堆积 if os.path.exists(file_path): os.remove(file_path)
列名获取
通过 df.columns[0]
、df.columns[1]
和 df.columns[8]
分别获取“炉号”、“铸锭总重量”和“熔炼总时长”的列名,请确保 Excel 文件中这三列位置正确。
错误数据筛选
< 0
或其他条件。整理输出
对每个筛选出来的错误行,仅构造包含三个字段的字典:
"错误列"
:对应错误数据所属的列名"炉号"
:第一列的值"错误数据"
:错误列中的数据"errors"
数组中返回。文件清理
finally
块中删除上传的临时文件,确保文件不会在服务器上堆积。
将此函数与前端上传接口结合,即可返回只包含所需检查信息的 JSON 数据。
返回数据格式不正确或没有符合条件的行。
出现报错,重新修改代码,使其正确展示
下面给出修改后的完整代码,满足要求:
修改后的代码如下:
python# functions/check_data.py import pandas as pd import os def check_data(file_path): try: # 读取 Excel,并获取所有列名 df = pd.read_excel(file_path, header=0) # 获取所需的列名 col1_name = df.columns[0] # 第1列,炉号 col2_name = df.columns[1] # 第2列,铸锭总重量 col9_name = df.columns[8] # 第9列,熔炼总时长 # 筛选错误数据: # 错误1:铸锭总重量 ≤ 0(注意:条件可根据实际需求调整) wrong_1 = df.loc[df[col2_name] <= 0, [col1_name, col2_name]] # 错误2:熔炼总时长 ≤ 0 wrong_2 = df.loc[df[col9_name] <= 0, [col1_name, col9_name]] # 整合错误记录为列表,每一记录形式为 [炉号, 错误列, 错误数据] rows = [] for _, row in wrong_1.iterrows(): rows.append([row[col1_name], col2_name, row[col2_name]]) for _, row in wrong_2.iterrows(): rows.append([row[col1_name], col9_name, row[col9_name]]) # 如果没有符合条件的错误记录,也返回空列表 return { "header": [col1_name, "错误列", "错误数据"], "rows": rows } except Exception as e: return {"error": str(e)} finally: # 删除临时文件,防止文件堆积 if os.path.exists(file_path): os.remove(file_path)
列名获取
使用 df.columns[0]
、df.columns[1]
和 df.columns[8]
分别获取“炉号”、“铸锭总重量”和“熔炼总时长”的列名,请确保 Excel 文件中这几列的顺序正确。
错误数据筛选
返回数据格式
最终返回一个 JSON 对象,键值说明:
"header"
为固定格式,即 "rows"
为所有错误记录组成的二维列表,每条记录只包含对应的炉号、错误所在的列名以及错误数据"rows"
返回空列表,这样前端处理时也不会报“格式不正确”的错异常处理与文件清理
如果发生异常,将返回 { "error": "异常信息" }
;同时在 finally 块中删除上传的临时文件,避免堆积。
将此函数与您在 Flask 路由中的调用相结合,即可正确展示错误数据。