<!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个新的html网页。同时给出这个新的网页的设计代码,要求可以拖脱入csv或者xlsx文件,进行数据检查。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
:
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; } /* 其他功能区域 */ .other-functions { margin-top: 30px; text-align: center; border-top: 1px solid #ddd; padding-top: 20px; } .other-functions h2 { margin: 10px 0; font-size: 1.8em; } .other-functions p { font-size: 18px; cursor: pointer; color: #4CAF50; } .other-functions p:hover { text-decoration: underline; } </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="other-functions"> <h2>其他功能</h2> <p id="workReportCheck">报工数据核对</p> </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 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(); }); // 其他功能——报工数据核对 document.getElementById('workReportCheck').addEventListener('click', function() { // 在新窗口中打开报工数据核对页面 window.open('report_check.html', '_blank', 'width=800,height=600'); }); </script> </body> </html>
将下面的代码保存为 report_check.html
:
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; display: flex; flex-direction: column; align-items: center; } .container { margin-top: 50px; width: 80%; max-width: 600px; background-color: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 0 15px rgba(0,0,0,0.1); text-align: center; } .drop-area { margin-top: 20px; padding: 40px; border: 2px dashed #bbb; border-radius: 8px; background-color: #f9f9f9; color: #333; font-size: 18px; cursor: pointer; } .drop-area.dragover { border-color: #4CAF50; background-color: #e8f5e9; } #fileInput { display: none; } .result { margin-top: 20px; text-align: left; font-size: 16px; } </style> </head> <body> <div class="container"> <h1>报工数据核对</h1> <div class="drop-area" id="drop-area"> 拖拽 CSV 或 XLSX 文件到此处,或点击选择文件 </div> <input type="file" id="fileInput" accept=".csv, .xlsx, .xls"> <div class="result" id="result"></div> </div> <script> const dropArea = document.getElementById('drop-area'); const fileInput = document.getElementById('fileInput'); const resultDiv = document.getElementById('result'); // 点击拖拽区域时触发文件选择 dropArea.addEventListener('click', () => { fileInput.click(); }); // 处理文件选择事件 fileInput.addEventListener('change', (event) => { const file = event.target.files[0]; handleFile(file); }); // 拖拽相关事件 dropArea.addEventListener('dragover', (event) => { event.preventDefault(); dropArea.classList.add('dragover'); }); dropArea.addEventListener('dragleave', (event) => { dropArea.classList.remove('dragover'); }); dropArea.addEventListener('drop', (event) => { event.preventDefault(); dropArea.classList.remove('dragover'); const file = event.dataTransfer.files[0]; handleFile(file); }); // 处理拖拽或选择的文件 function handleFile(file) { if (!file) return; const fileName = file.name; const fileExtension = fileName.split('.').pop().toLowerCase(); if (fileExtension !== 'csv' && fileExtension !== 'xlsx' && fileExtension !== 'xls') { resultDiv.textContent = '只支持 CSV 和 XLSX 文件格式。'; return; } // 这里仅展示文件基本信息,如需进一步数据检查,可集成解析库如 PapaParse 或 SheetJS resultDiv.innerHTML = '<p>文件名称: ' + fileName + '</p>' + '<p>文件大小: ' + file.size + ' 字节</p>' + '<p>文件类型: ' + file.type + '</p>' + '<p>数据检查功能待开发...</p>'; } </script> </body> </html>
主页面
用户在主页面可以通过“报工数据核对”点击打开新页面,该页面将以新窗口弹出。
报工数据核对页面
用户可在弹出页面中拖拽或点击选择 CSV 或 XLSX 文件,页面会显示选中文件的基本信息。后续可根据需求扩展数据解析和检查功能。
以上代码即满足了增加其他功能标题及数据核对页面的要求。
从你提供的截图以及文件结构来看,最主要的原因是:你的 Flask 或其他后端服务器并没有“自动”去处理和返回 report_check.html
这个静态页面,因此访问 127.0.0.1:5000/report_check.html
就会出现 404 Not Found。
在一个典型的 Flask 项目中,若你想要直接通过 http://127.0.0.1:5000/something.html
访问某个静态页面,需要满足以下任意一种情况:
将该页面放到 Flask 的静态文件夹(默认是 static/
)里
static/
文件夹下的资源映射到 /static/...
路径。例如:
textproject/ ├── static/ │ └── report_check.html └── app.py
http://127.0.0.1:5000/static/report_check.html
来访问它。http://127.0.0.1:5000/report_check.html
来访问,也可以在创建 Flask 实例时指定 static_url_path=''
来让它从根路径开始提供静态文件,但这需要额外的配置,不是默认行为。将该页面放到 Flask 的模板文件夹(默认是 templates/
)里,并通过路由返回
render_template
来渲染(哪怕只是纯静态 HTML 文件),就通常会把它放到 templates/
下,然后编写一个路由,比如:
python# app.py from flask import Flask, render_template app = Flask(__name__) @app.route('/report_check') def report_check(): return render_template('report_check.html')
http://127.0.0.1:5000/report_check
访问到 templates/report_check.html
。手动编写一个自定义路由,返回这个 HTML 文件
templates/
里,也不想用默认的 static/
,可以写一个自定义路由,通过 send_from_directory
或 send_file
返回该文件。例如:
pythonfrom flask import Flask, send_from_directory app = Flask(__name__) @app.route('/report_check.html') def serve_report_check(): # 假设 report_check.html 在项目根目录下 return send_from_directory('.', 'report_check.html')
http://127.0.0.1:5000/report_check.html
即可返回根目录下的 report_check.html
文件。static/
文件夹里的文件会映射到 /static/
路径,只有 templates/
文件夹里的文件可以被 render_template()
自动找到。report_check.html
放在了项目根目录或其他不在 Flask 默认配置的地方,就必须显示地告诉 Flask:
“这个文件要怎么对外提供?是放到 static 里还是通过路由返回?”
把 report_check.html
文件挪进 static/
文件夹(最简单的方式)
textAI_ASSISTANT ├── static │ └── report_check.html ├── templates │ └── index.html ├── index.html # 如果这是要当静态首页的话,也可放到 static ├── ... └── app.py
window.open('/static/report_check.html', '_blank', 'width=800,height=600');
http://127.0.0.1:5000/static/report_check.html
用模板方式:写一个路由返回它
report_check.html
放到 templates/
目录下,然后在 app.py
里写:
python@app.route('/report_check') def report_check(): return render_template('report_check.html')
jswindow.open('/report_check', '_blank', 'width=800,height=600');
templates/report_check.html
。自定义路由 + 发送静态文件
report_check.html
,可以手动写一个路由:
pythonfrom flask import send_from_directory @app.route('/report_check.html') def serve_report_check(): return send_from_directory('.', 'report_check.html')
http://127.0.0.1:5000/report_check.html
就能打开你放在根目录的文件。总结:
你现在看到的 404 提示说明 Flask 并不知道如何去取 report_check.html
。只要按照以上三种方式之一去配置,把这个文件放到 Flask 能识别和自动提供的地方,或者写明要如何提供它,问题就能解决。
后期我需要在智能助手下增加更多的html网页,去扩充智能助手的功能,并在后端新增更多的py代码,去执行新增的html网页功能。你推荐怎么去构建这个目录和实现方法?请详细叙述。
为了方便扩展你的智能助手项目并增加更多的 HTML 页面及后端功能,推荐按照以下的结构来组织目录,并制定合理的实现方法。
为了管理多个功能模块并使得代码更具可维护性和扩展性,建议采用模块化的目录结构。下面是一个典型的 Flask 项目结构:
textAI_ASSISTANT/ │ ├── app.py # Flask 后端主程序,管理路由和应用逻辑 ├── models/ # 存放模型相关代码 │ ├── model1.py # 模型1相关代码 │ ├── model2.py # 模型2相关代码 │ └── ... ├── static/ # 存放静态资源,如JS、CSS、图片、HTML等 │ ├── css/ # 存放CSS样式文件 │ ├── js/ # 存放JavaScript文件 │ ├── images/ # 存放图片 │ └── html/ # 存放新增的 HTML 页面 ├── templates/ # 存放前端页面模板(Jinja2 模板) │ ├── index.html # 主页面模板 │ ├── report_check.html # 报工数据核对页面模板 │ └── other_feature.html # 其他功能页面模板 ├── static/jsons/ # 存放json数据文件 ├── utils/ # 存放工具类、函数等 │ ├── file_utils.py # 用于处理文件上传、下载、读取等的工具 │ └── data_utils.py # 用于数据处理的工具 ├── 1_createfiles.py # 脚本,创建静态文件 ├── 2_assistant_flask.py # 启动 Flask 后端服务 ├── 3_assistant_gunicorn.py # 使用 Gunicorn 启动后端服务 └── requirements.txt # 项目所需依赖包
app.py
这是你的 Flask 应用的主文件,负责管理应用的配置、路由、后端逻辑等内容。每个新增的功能页面和后端逻辑需要在这里添加相应的路由。
models/
存放与人工智能模型相关的代码。比如,模型加载、数据预处理、推理过程等逻辑可以放在这里。这有助于后期扩展和维护。
static/
用于存放静态资源。比如 CSS 文件、JavaScript 文件、图片以及 HTML 页面。如果你有新的页面功能,建议将新的 HTML 页面放在 static/html/
文件夹中。
static/css/
:放置 CSS 样式文件。static/js/
:放置 JS 文件和前端逻辑。static/images/
:放置图片资源。templates/
存放 Flask 的 Jinja2 模板,这些模板文件将用于动态渲染页面。Flask 后端通过 render_template()
方法将数据传递给这些模板进行渲染。每个新增的 HTML 页面都可以创建一个新的模板文件。
static/jsons/
用来存放静态的 JSON 数据文件。如果你的应用需要加载一些静态数据或者配置文件,这些可以存放在此目录。
utils/
存放一些工具类和函数模块,帮助处理常见的任务,比如文件上传、数据预处理、数据存储等。这里的模块可以被其他 Python 文件复用。
1_createfiles.py
脚本用于创建静态文件,比如生成一些动态生成的 HTML 文件或其他类型的文件。
2_assistant_flask.py
这个文件用于启动 Flask 服务。在开发阶段,使用 flask run
来启动后端服务。
3_assistant_gunicorn.py
这个文件用于通过 Gunicorn 启动 Flask 服务,通常在生产环境中使用。
requirements.txt
记录项目所需的 Python 依赖包,可以通过 pip install -r requirements.txt
安装所有依赖。
当你需要为智能助手增加新的功能时,遵循以下步骤:
static/html/
文件夹下创建新的 HTML 文件,比如 new_feature.html
。templates/
文件夹下创建与该功能相关的模板文件,比如 new_feature_template.html
。该文件应该使用 Jinja2 语法,能够接收从后端传递的数据。在 app.py
中增加新的路由,处理前端请求。
python@app.route('/new_feature') def new_feature(): # 执行新功能的后端逻辑 return render_template('new_feature_template.html', data=data)
在路由中,可能需要执行一些后端操作(如数据库查询、调用模型推理等),然后将结果传递到模板中渲染。
models/
文件夹下增加新模块,实现与新功能相关的后端逻辑。
new_feature_model.py
负责新功能的核心逻辑,或者增加一个 API 接口进行数据处理。utils/file_utils.py
中增加相关函数。在 static/js/
中,增加与后端交互的 JavaScript 文件。例如,使用 AJAX 向后端发送请求并获取结果。
javascript$.ajax({ url: '/new_feature', type: 'POST', data: { 'input_data': userInput }, success: function(response) { $('#result').html(response.data); } });
前端与后端交互后,将结果渲染在页面上。
static/css/
和 static/js/
文件夹下创建新的文件。html<link rel="stylesheet" href="{{ url_for('static', filename='css/new_feature.css') }}"> <script src="{{ url_for('static', filename='js/new_feature.js') }}"></script>
假设你要添加一个新的功能 "数据统计":
创建 HTML 文件
在 static/html/
文件夹下,创建 data_statistics.html
,该文件将用于展示数据统计功能的前端页面。
创建模板
在 templates/
文件夹下,创建 data_statistics_template.html
,并使用 Jinja2 渲染数据:
html<html> <head> <title>数据统计</title> </head> <body> <h1>数据统计</h1> <p>统计结果:{{ result }}</p> </body> </html>
创建路由
在 app.py
中,增加一个新的路由:
python@app.route('/data_statistics') def data_statistics(): # 调用后端处理函数获取数据统计结果 result = calculate_statistics() return render_template('data_statistics_template.html', result=result)
添加后端逻辑
在 models/data_statistics.py
中,添加处理统计的代码:
pythondef calculate_statistics(): # 执行数据统计的逻辑 return "统计结果示例"
前端交互
在 static/js/data_statistics.js
中,添加前端与后端交互的逻辑:
javascriptfunction getDataStatistics() { $.get('/data_statistics', function(data) { $('#result').text(data.result); }); }
调用新功能 通过在前端页面中添加一个链接或按钮来调用新功能页面:
html<a href="/data_statistics">点击查看数据统计</a>
app.py
中为每个新页面增加对应的路由。这种结构可以使你在后续扩展功能时更加高效,并且容易维护。
请将添加一个新的功能 "数据统计"的涉及到的全部代码和代码存放位置结构进行详细的展示。
好的,下面我将详细展示如何增加一个新的功能 "数据统计",涉及的所有代码和文件存放位置。
假设你已经有了下面的目录结构,我们将基于这个结构来组织代码:
textAI_ASSISTANT/ │ ├── app.py # Flask 后端主程序,管理路由和应用逻辑 ├── models/ # 存放模型相关代码 │ ├── data_statistics.py # 数据统计的后端逻辑 │ └── ... ├── static/ # 存放静态资源,如JS、CSS、图片、HTML等 │ ├── css/ # 存放CSS样式文件 │ ├── js/ # 存放JavaScript文件 │ ├── html/ # 存放新增的 HTML 页面 │ └── ... ├── templates/ # 存放前端页面模板(Jinja2 模板) │ ├── index.html # 主页面模板 │ ├── data_statistics_template.html # 数据统计页面模板 │ └── ... ├── utils/ # 存放工具类、函数等 │ └── ... ├── 1_createfiles.py # 脚本,创建静态文件 ├── 2_assistant_flask.py # 启动 Flask 后端服务 └── requirements.txt # 项目所需依赖包
首先,我们需要在 models/
文件夹中创建一个文件 data_statistics.py
,该文件包含了数据统计的核心逻辑。
文件:models/data_statistics.py
python# models/data_statistics.py def calculate_statistics(): # 示例:计算一个简单的数据统计,实际可以根据你的需求进行修改 # 这里简单返回一个模拟的数据统计结果 data = [10, 20, 30, 40, 50] total = sum(data) average = total / len(data) return f"总和: {total}, 平均值: {average:.2f}"
这个函数 calculate_statistics
简单地计算了一组数字的总和和平均值,实际应用中,你可以根据需要更改这个函数来进行更复杂的数据统计。
接下来,我们需要在 templates/
文件夹下创建一个新的 HTML 模板文件,来显示数据统计结果。
文件:templates/data_statistics_template.html
html<!-- templates/data_statistics_template.html --> <html> <head> <title>数据统计</title> <link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}"> </head> <body> <h1>数据统计结果</h1> <p>{{ result }}</p> </body> </html>
这里我们展示了通过 {{ result }}
渲染的数据统计结果,后端会传递 result
字段到这个模板进行显示。
如果你需要额外的样式或者交互功能,可以在 static/css/
和 static/js/
文件夹下创建新的 CSS 和 JavaScript 文件。
文件:static/css/styles.css
css/* static/css/styles.css */ body { font-family: Arial, sans-serif; background-color: #f4f4f4; padding: 20px; } h1 { color: #333; } p { font-size: 18px; color: #555; }
这个 CSS 文件为数据统计页面提供了基本的样式。
文件:static/js/data_statistics.js
(如果需要)
javascript// static/js/data_statistics.js // 你可以在这里添加前端与后端交互的功能,比如获取数据统计结果 function fetchStatistics() { fetch('/data_statistics') .then(response => response.text()) .then(data => { document.getElementById('result').innerHTML = data; }); }
目前这个 JavaScript 文件只是一个示例,后期你可以用它来增加动态加载数据等功能。
app.py
中添加数据统计路由在后端的 app.py
中添加一个新的路由,用来处理数据统计功能。
文件:app.py
pythonfrom flask import Flask, render_template from models.data_statistics import calculate_statistics app = Flask(__name__) @app.route('/') def index(): return render_template('index.html') @app.route('/data_statistics') def data_statistics(): # 获取数据统计结果 result = calculate_statistics() # 渲染并返回数据统计页面模板 return render_template('data_statistics_template.html', result=result) if __name__ == '__main__': app.run(debug=True)
在这个路由中,调用了 calculate_statistics()
函数获取数据统计结果,并将结果传递给 data_statistics_template.html
模板进行渲染。
index.html
)中的链接,指向数据统计页面在你的主页面(index.html
)中,可以增加一个按钮或链接,点击后跳转到数据统计页面。
文件:templates/index.html
html<!-- templates/index.html --> <html> <head> <title>智能助手</title> </head> <body> <h1>欢迎使用智能助手</h1> <a href="/data_statistics">点击查看数据统计</a> </body> </html>
安装依赖:
如果你还没有安装项目依赖,先运行下面的命令来安装所需的 Python 包(确保你已经创建并激活了一个虚拟环境):
bashpip install -r requirements.txt
启动 Flask 后端:
启动 Flask 后端服务:
bashpython 2_assistant_flask.py
访问网页:
打开浏览器,访问 http://127.0.0.1:5000/
,点击“点击查看数据统计”链接,就可以访问到数据统计功能页面。
models/data_statistics.py
文件中封装了数据统计的核心逻辑。templates/
文件夹中创建了 data_statistics_template.html
,用于显示数据统计结果。static/css/styles.css
提供了页面样式,使得数据统计页面看起来更加美观。app.py
中添加了一个新的路由 /data_statistics
,将计算结果传递到前端页面进行展示。index.html
页面中添加了链接,用户点击后跳转到数据统计页面。通过这种结构,你可以很容易地在后续添加新的功能模块。每个新功能都可以对应一个 Python 后端逻辑文件、一个 HTML 页面模板以及可能的 CSS/JS 文件。
</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个新的html网页。同时给出这个新的网页的设计代码,要求可以拖脱入csv或者xlsx文件,对第2列数据进行数据检查,如果第2列数据>8000,则需要给出第二列数值>8000的所在行,并将大于8000的这个数字标红加粗显示。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>
好的,下面我将根据您的需求修改前端代码,并提供一个新的 HTML 网页设计代码,以支持“报工数据核对”的功能,包括拖拽文件、检查第二列数据大于 8000 的行,并将这些数据标红加粗。
在您的现有 HTML 页面中,最下方增加“其他功能”标题以及“报工数据核对”的小标题,并且点击后弹出新的 HTML 页面。
修改后的 HTML 代码:
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; } /* 其他功能区域 */ .other-functions { margin-top: 30px; text-align: center; border-top: 1px solid #ddd; padding-top: 20px; } .other-functions h2 { margin: 10px 0; font-size: 1.8em; } .other-functions p { font-size: 18px; cursor: pointer; color: #4CAF50; } .other-functions p:hover { text-decoration: underline; } </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="other-functions"> <h2>其他功能</h2> <p id="workReportCheck">报工数据核对</p> </div> </div> <div id="loadingModal"> <div id="loadingContent">⏱请稍等,资料海量,正在查询中…… <span id="timer">0</span> 秒</div> </div> <script> // 其他功能 - 报工数据核对 document.getElementById('workReportCheck').addEventListener('click', function() { // 在新窗口中打开报工数据核对页面 window.open('report_check.html', '_blank', 'width=800,height=600'); }); </script> </body> </html>
report_check.html
)此页面允许用户拖拽 CSV 或 XLSX 文件,读取第二列数据,并将大于 8000 的值标红加粗。
文件:report_check.html
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; display: flex; justify-content: center; align-items: center; height: 100vh; } .container { width: 80%; max-width: 700px; background-color: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); text-align: center; } .drop-area { padding: 40px; border: 2px dashed #bbb; border-radius: 8px; background-color: #f9f9f9; color: #333; font-size: 18px; cursor: pointer; margin-bottom: 20px; } .drop-area.dragover { border-color: #4CAF50; background-color: #e8f5e9; } table { width: 100%; border-collapse: collapse; margin-top: 20px; } table, th, td { border: 1px solid #ddd; } th, td { padding: 10px; text-align: center; } .highlight { background-color: #ffcccc; font-weight: bold; color: red; } </style> </head> <body> <div class="container"> <h1>报工数据核对</h1> <div class="drop-area" id="drop-area"> 拖拽 CSV 或 XLSX 文件到此处,或点击选择文件 </div> <input type="file" id="fileInput" accept=".csv, .xlsx, .xls" style="display: none;"> <table id="dataTable" style="display: none;"> <thead> <tr> <th>第1列</th> <th>第2列</th> </tr> </thead> <tbody></tbody> </table> </div> <script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.3.0/papaparse.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.17.2/xlsx.full.min.js"></script> <script> const dropArea = document.getElementById('drop-area'); const fileInput = document.getElementById('fileInput'); const dataTable = document.getElementById('dataTable'); const tbody = dataTable.querySelector('tbody'); // 点击拖拽区域时触发文件选择 dropArea.addEventListener('click', () => { fileInput.click(); }); // 处理文件选择事件 fileInput.addEventListener('change', (event) => { const file = event.target.files[0]; handleFile(file); }); // 拖拽相关事件 dropArea.addEventListener('dragover', (event) => { event.preventDefault(); dropArea.classList.add('dragover'); }); dropArea.addEventListener('dragleave', () => { dropArea.classList.remove('dragover'); }); dropArea.addEventListener('drop', (event) => { event.preventDefault(); dropArea.classList.remove('dragover'); const file = event.dataTransfer.files[0]; handleFile(file); }); function handleFile(file) { const fileName = file.name; const fileExtension = fileName.split('.').pop().toLowerCase(); if (fileExtension === 'csv') { parseCSV(file); } else if (fileExtension === 'xlsx' || fileExtension === 'xls') { parseXLSX(file); } else { alert('只支持 CSV 或 XLSX 文件'); } } // 解析 CSV 文件 function parseCSV(file) { Papa.parse(file, { complete: function(results) { displayData(results.data); } }); } // 解析 XLSX 文件 function parseXLSX(file) { const reader = new FileReader(); reader.onload = function(event) { const data = new Uint8Array(event.target.result); const workbook = XLSX.read(data, { type: 'array' }); const sheetName = workbook.SheetNames[0]; const sheet = workbook.Sheets[sheetName]; const rows = XLSX.utils.sheet_to_json(sheet, { header: 1 }); displayData(rows); }; reader.readAsArrayBuffer(file); } // 显示数据并检查第二列 function displayData(rows) { tbody.innerHTML = ''; rows.forEach((row, index) => { const tr = document.createElement('tr'); const td1 = document.createElement('td'); const td2 = document.createElement('td'); td1.textContent = row[0]; td2.textContent = row[1]; if (row[1] > 8000) { td2.classList.add('highlight'); } tr.appendChild(td1); tr.appendChild(td2); tbody.appendChild(tr); }); dataTable.style.display = 'table'; } </script> </body> </html>
HTML 页面:
CSV
或 XLSX
文件到指定区域,或者通过点击选择文件。主要功能:
PapaParse
库解析 CSV 文件。xlsx
库解析 XLSX 文件。report_check.html
。这样,你就能实现报工数据核对的功能,支持 CSV 和 XLSX 文件的拖拽上传和数据检查。