清理示例文件和敏感数据

This commit is contained in:
wandering
2026-05-25 13:51:57 +08:00
parent 02e2ca90a7
commit 70f61be3aa
14 changed files with 1 additions and 425 deletions

1
.gitignore vendored
View File

@@ -23,6 +23,7 @@ images/
# IDE
.vscode/
.idea/
.cursor/
# OS
.DS_Store

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 KiB

View File

@@ -1,11 +0,0 @@
{
"username": "202407021",
"password": "wang!1624155937",
"sso_login_url": "https://tyrz.fynu.edu.cn/sso/login",
"portal_url": "https://tyrz.fynu.edu.cn/oshall",
"reimburse_url": "http://210.45.32.214:8081",
"reimburse_page": "/expen/common/common?v=4.0",
"default_name": "王建锋",
"default_card_no": "6282880139161682",
"default_person_id": "202407021"
}

View File

@@ -1,5 +0,0 @@
序号,发票号码,开票日期,项目名称,规格型号,价税合计,销售方名称,人员姓名,刷卡日期,公务卡号,刷卡金额,备注,工号
1,26442000005432755951,2026/05/18,*电子元件*电容器 电容一批 个 500000 0.0057425742574 2871.29 1% 28.71,电容器 电容一批 个 500000 0.0057425742574 2871.29 1% 28.71,2900.00,佛山市泓宇芯科技有限公司,陈陈,2026/04/28,,2850.00,,
2,26442000005432652421,2026/05/18,*电子元件*电阻 电阻一批 个 500000 0.0057425742574 2871.29 1% 28.71,电阻 电阻一批 个 500000 0.0057425742574 2871.29 1% 28.71,2900.00,佛山市泓宇芯科技有限公司,陈陈,2026/04/28,,2880.00,,
3,26442000005432937661,2026/05/18,*集成电路*集成电路 LED一批 个 2638.92 1% 26.39,集成电路 LED一批 个 2638.92 1% 26.39,2665.31,佛山市泓宇芯科技有限公司,陈陈,2026/04/28,,2661.61,,
4,26442000005468940571,2026/05/18,*电子工业设备*元件盒 1# 个 1 47.5247524752475 47.52 1% 0.48,元件盒 1# 个 1 47.5247524752475 47.52 1% 0.48,96.00,东莞市长安顺淘电子工具经营部,陈陈,2026/05/07,,96.00,,
1 序号 发票号码 开票日期 项目名称 规格型号 价税合计 销售方名称 人员姓名 刷卡日期 公务卡号 刷卡金额 备注 工号
2 1 26442000005432755951 2026/05/18 *电子元件*电容器 电容一批 个 500000 0.0057425742574 2871.29 1% 28.71 电容器 电容一批 个 500000 0.0057425742574 2871.29 1% 28.71 2900.00 佛山市泓宇芯科技有限公司 陈陈 2026/04/28 2850.00
3 2 26442000005432652421 2026/05/18 *电子元件*电阻 电阻一批 个 500000 0.0057425742574 2871.29 1% 28.71 电阻 电阻一批 个 500000 0.0057425742574 2871.29 1% 28.71 2900.00 佛山市泓宇芯科技有限公司 陈陈 2026/04/28 2880.00
4 3 26442000005432937661 2026/05/18 *集成电路*集成电路 LED一批 个 2638.92 1% 26.39 集成电路 LED一批 个 2638.92 1% 26.39 2665.31 佛山市泓宇芯科技有限公司 陈陈 2026/04/28 2661.61
5 4 26442000005468940571 2026/05/18 *电子工业设备*元件盒 1# 个 1 47.5247524752475 47.52 1% 0.48 元件盒 1# 个 1 47.5247524752475 47.52 1% 0.48 96.00 东莞市长安顺淘电子工具经营部 陈陈 2026/05/07 96.00

View File

@@ -1,11 +0,0 @@
# 发票信息汇总表
| 序号 | 发票号码 | 开票日期 | 项目名称 | 规格型号 | 价税合计 | 销售方名称 | 人员姓名 | 刷卡日期 | 公务卡号 | 刷卡金额 | 备注 | 工号 |
|------|------|------|------|------|------|------|------|------|------|------|------|------|
| 1 | 26442000005432755951 | 2026/05/18 | *电子元件*电容器 电容一批 个 500000 0.0057425742574 2871.29 1% 28.71 | 电容器 电容一批 个 500000 0.0057425742574 2871.29 1% 28.71 | ¥2,900.00 | 佛山市泓宇芯科技有限公司 | 陈陈 | 2026/04/28 | | ¥2,850.00 | | |
| 2 | 26442000005432652421 | 2026/05/18 | *电子元件*电阻 电阻一批 个 500000 0.0057425742574 2871.29 1% 28.71 | 电阻 电阻一批 个 500000 0.0057425742574 2871.29 1% 28.71 | ¥2,900.00 | 佛山市泓宇芯科技有限公司 | 陈陈 | 2026/04/28 | | ¥2,880.00 | | |
| 3 | 26442000005432937661 | 2026/05/18 | *集成电路*集成电路 LED一批 个 2638.92 1% 26.39 | 集成电路 LED一批 个 2638.92 1% 26.39 | ¥2,665.31 | 佛山市泓宇芯科技有限公司 | 陈陈 | 2026/04/28 | | ¥2,661.61 | | |
| 4 | 26442000005468940571 | 2026/05/18 | *电子工业设备*元件盒 1# 个 1 47.5247524752475 47.52 1% 0.48 | 元件盒 1# 个 1 47.5247524752475 47.52 1% 0.48 | ¥96.00 | 东莞市长安顺淘电子工具经营部 | 陈陈 | 2026/05/07 | | ¥96.00 | | |
**价税合计总计: ¥8,561.31**
**刷卡金额总计: ¥8,487.61**

View File

@@ -1,84 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
财务报销全流程编排脚本
依次执行:
1. extract_invoice.py — 从 PDF 发票提取信息,生成 invoice_summary.csv
2. extract_image_ocr.py — 从支付截图 OCR 识别刷卡信息,更新 CSV
3. reimburse.py — 打开浏览器登录财务系统并自动填报
用法:
python run_all.py # 默认执行全部三步
python run_all.py --step invoice # 仅执行第 1 步
python run_all.py --step ocr # 仅执行第 2 步
python run_all.py --step submit # 仅执行第 3 步
"""
import subprocess
import sys
from pathlib import Path
PROJECT_DIR = Path(__file__).parent.resolve()
PYTHON = sys.executable
def step(name: str, module: str, args: list[str]) -> bool:
"""运行单个步骤,返回是否成功"""
cmd = [PYTHON, str(PROJECT_DIR / module), *args]
print(f"\n{'=' * 60}")
print(f" [{name}] {module}")
print(f"{'=' * 60}")
result = subprocess.run(cmd, cwd=str(PROJECT_DIR))
ok = result.returncode == 0
if ok:
print(f"\n[{name}] 完成")
else:
print(f"\n[错误] {name} 失败 (exit code: {result.returncode})")
return ok
def run_all() -> int:
print("=" * 60)
print(" 财务报销自动化流程")
print(f" 工作目录: {PROJECT_DIR}")
print(f" Python: {PYTHON}")
print("=" * 60)
if not step("发票提取", "extract_invoice.py", []):
return 1
if not step("OCR 识别", "extract_image_ocr.py", []):
return 1
if not step("报销提交", "reimburse.py", ["--data", str(PROJECT_DIR / "invoice_summary.csv")]):
return 1
print("\n" + "=" * 60)
print(" 全流程执行完毕")
print("=" * 60)
return 0
def main() -> int:
if len(sys.argv) >= 3 and sys.argv[1] == "--step":
name = sys.argv[2]
steps = {
"invoice": ("发票提取", "extract_invoice.py", []),
"ocr": ("OCR 识别", "extract_image_ocr.py", []),
"submit": ("报销提交", "reimburse.py", ["--data", str(PROJECT_DIR / "invoice_summary.csv")]),
}
if name not in steps:
print(f"未知步骤: {name},可选: {', '.join(steps)}")
return 1
label, module, args = steps[name]
return 0 if step(label, module, args) else 1
return run_all()
if __name__ == "__main__":
sys.exit(main())

View File

@@ -1,314 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>财务报销自动化</title>
<style>
:root {
--bg: #f5f5f5;
--card: #fff;
--text: #333;
--muted: #888;
--primary: #4f46e5;
--primary-hover: #4338ca;
--success: #16a34a;
--danger: #dc2626;
--warning: #d97706;
--border: #e5e7eb;
--radius: 8px;
--shadow: 0 1px 3px rgba(0,0,0,.08);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: var(--bg); color: var(--text); line-height: 1.6; }
.container { max-width: 960px; margin: 0 auto; padding: 24px 16px; }
.card { background: var(--card); border-radius: var(--radius); box-shadow: var(--shadow); padding: 24px; margin-bottom: 16px; }
.card h2 { font-size: 18px; margin-bottom: 12px; display: flex; align-items: center; gap: 8px; }
.card h2 .badge { font-size: 12px; padding: 2px 8px; border-radius: 12px; background: var(--primary); color: #fff; }
.upload-zone { border: 2px dashed var(--border); border-radius: var(--radius); padding: 40px 20px; text-align: center; cursor: pointer; transition: border-color .2s; }
.upload-zone:hover, .upload-zone.dragover { border-color: var(--primary); background: #f8f8ff; }
.upload-zone p { color: var(--muted); margin-top: 8px; }
.file-list { margin-top: 12px; }
.file-item { display: flex; align-items: center; gap: 8px; padding: 6px 10px; background: #f9fafb; border-radius: 6px; margin-bottom: 4px; font-size: 14px; }
.file-item .remove { margin-left: auto; cursor: pointer; color: var(--danger); font-size: 16px; }
label { display: block; font-size: 14px; font-weight: 500; margin-bottom: 4px; }
input, textarea { width: 100%; padding: 8px 12px; border: 1px solid var(--border); border-radius: 6px; font-size: 14px; outline: none; transition: border-color .2s; }
input:focus, textarea:focus { border-color: var(--primary); }
textarea { font-family: "SF Mono", "Fira Code", monospace; font-size: 13px; height: 160px; resize: vertical; }
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 12px; }
.btn { display: inline-flex; align-items: center; gap: 6px; padding: 10px 20px; border: none; border-radius: 6px; font-size: 14px; cursor: pointer; transition: background .2s; }
.btn-primary { background: var(--primary); color: #fff; }
.btn-primary:hover { background: var(--primary-hover); }
.btn-primary:disabled { opacity: .5; cursor: not-allowed; }
.btn-success { background: var(--success); color: #fff; }
.btn-success:hover { background: #15803d; }
.btn-success:disabled { opacity: .5; cursor: not-allowed; }
.actions { display: flex; gap: 8px; margin-top: 12px; flex-wrap: wrap; }
.log-panel { background: #1e1e1e; color: #d4d4d4; border-radius: var(--radius); padding: 16px; max-height: 380px; overflow-y: auto; font-family: "SF Mono", "Fira Code", monospace; font-size: 13px; }
.log-panel .entry { margin-bottom: 2px; white-space: pre-wrap; word-break: break-all; }
.log-panel .INFO { color: #6a9955; }
.log-panel .WARNING { color: #d97706; }
.log-panel .ERROR { color: #f87171; }
.log-panel .DEBUG { color: #8b8b8b; }
.status-badge { display: inline-block; padding: 4px 12px; border-radius: 12px; font-size: 12px; font-weight: 500; }
.status-badge.uploaded { background: #e5e7eb; color: #6b7280; }
.status-badge.extracting { background: #dbeafe; color: #2563eb; }
.status-badge.done { background: #dcfce7; color: #16a34a; }
.status-badge.submitting { background: #fef3c7; color: #d97706; }
.status-badge.submitted { background: #dcfce7; color: #16a34a; }
.status-badge.error { background: #fee2e2; color: #dc2626; }
.result-table { width: 100%; border-collapse: collapse; font-size: 13px; margin-top: 8px; }
.result-table th, .result-table td { padding: 8px 10px; border-bottom: 1px solid var(--border); text-align: left; }
.result-table th { background: #f9fafb; font-weight: 500; }
.hidden { display: none !important; }
</style>
</head>
<body>
<div class="container">
<div class="card">
<h2>财务报销自动化</h2>
<p style="color:var(--muted);font-size:14px;">上传 PDF 发票和支付截图,自动提取信息并填报报销系统</p>
</div>
<!-- 文件上传 -->
<div class="card">
<h2>1. 上传文件</h2>
<div class="upload-zone" id="uploadZone">
<div style="font-size:36px;margin-bottom:4px;">&#128194;</div>
<p>拖拽 PDF 和图片到此处,或点击选择文件</p>
<input type="file" id="fileInput" multiple accept=".pdf,.png,.jpg,.jpeg,.bmp,.webp" style="display:none">
</div>
<div class="file-list" id="fileList"></div>
</div>
<!-- 配置 -->
<div class="card">
<h2>2. 配置</h2>
<div class="form-row">
<div><label>用户名</label><input id="cfgUser" placeholder="工号/学号"></div>
<div><label>密码</label><input id="cfgPass" type="password" placeholder="登录密码"></div>
</div>
<div class="form-row">
<div><label>默认姓名</label><input id="cfgName" placeholder="报销人姓名"></div>
<div><label>默认公务卡号</label><input id="cfgCard" placeholder="公务卡号后四位或完整号"></div>
</div>
<div><label>默认人员编号</label><input id="cfgPid" placeholder="人员编号"></div>
<details style="margin-top:8px;">
<summary style="font-size:14px;color:var(--muted);cursor:pointer;">高级配置 (JSON)</summary>
<textarea id="cfgJson" style="margin-top:8px;">{}</textarea>
</details>
</div>
<!-- 进度 -->
<div class="card">
<h2>3. 处理 <span class="status-badge" id="statusBadge" style="display:none"></span></h2>
<div class="log-panel" id="logPanel">
<div class="entry" style="color:var(--muted)">等待上传文件...</div>
</div>
<div class="actions">
<button class="btn btn-primary" id="btnStart" disabled>开始处理</button>
<button class="btn btn-success hidden" id="btnSubmit">提交报销</button>
<button class="btn hidden" id="btnDownload" style="background:#6b7280;color:#fff;">下载 CSV</button>
</div>
<div id="resultArea" class="hidden" style="margin-top:12px;"></div>
</div>
</div>
<script>
const $ = s => document.querySelector(s);
const $$ = s => document.querySelectorAll(s);
let sessionId = null;
let files = [];
let evtSource = null;
// ---- 文件选择 ----
const uploadZone = $('#uploadZone');
const fileInput = $('#fileInput');
const fileList = $('#fileList');
uploadZone.addEventListener('click', () => fileInput.click());
uploadZone.addEventListener('dragover', e => { e.preventDefault(); uploadZone.classList.add('dragover'); });
uploadZone.addEventListener('dragleave', () => uploadZone.classList.remove('dragover'));
uploadZone.addEventListener('drop', e => {
e.preventDefault();
uploadZone.classList.remove('dragover');
addFiles(Array.from(e.dataTransfer.files));
});
fileInput.addEventListener('change', () => addFiles(Array.from(fileInput.files)));
function addFiles(newFiles) {
for (const f of newFiles) {
if (!files.find(x => x.name === f.name && x.size === f.size)) {
files.push(f);
}
}
renderFiles();
checkReady();
}
function removeFile(idx) {
files.splice(idx, 1);
renderFiles();
checkReady();
}
function renderFiles() {
fileList.innerHTML = files.length === 0 ? '' : files.map((f, i) =>
`<div class="file-item">
<span>${['.pdf','.PDF'].some(e => f.name.endsWith(e)) ? '&#128196;' : '&#128444;'}</span>
<span>${f.name}</span>
<span style="color:var(--muted);font-size:12px;">${(f.size/1024).toFixed(1)} KB</span>
<span class="remove" onclick="removeFile(${i})">&times;</span>
</div>`
).join('');
}
// ---- 配置 ----
const cfgUser = $('#cfgUser');
const cfgPass = $('#cfgPass');
const cfgName = $('#cfgName');
const cfgCard = $('#cfgCard');
const cfgPid = $('#cfgPid');
const cfgJson = $('#cfgJson');
[cfgUser, cfgPass, cfgName, cfgCard, cfgPid, cfgJson].forEach(el =>
el.addEventListener('input', checkReady)
);
function getConfig() {
let base = {};
try { base = JSON.parse(cfgJson.value || '{}'); } catch (e) {}
if (cfgUser.value) base.username = cfgUser.value;
if (cfgPass.value) base.password = cfgPass.value;
if (cfgName.value) base.default_name = cfgName.value;
if (cfgCard.value) base.default_card_no = cfgCard.value;
if (cfgPid.value) base.default_person_id = cfgPid.value;
return base;
}
function checkReady() {
const hasFiles = files.length > 0;
const hasUser = !!(cfgUser.value && cfgPass.value);
$('#btnStart').disabled = !(hasFiles && hasUser);
}
// ---- 开始处理 ----
const btnStart = $('#btnStart');
const btnSubmit = $('#btnSubmit');
const btnDownload = $('#btnDownload');
const statusBadge = $('#statusBadge');
const logPanel = $('#logPanel');
const resultArea = $('#resultArea');
btnStart.addEventListener('click', async () => {
btnStart.disabled = true;
statusBadge.style.display = 'inline-block';
statusBadge.textContent = '上传中...';
statusBadge.className = 'status-badge extracting';
const formData = new FormData();
for (const f of files) formData.append('files', f);
const config = getConfig();
formData.append('config', JSON.stringify(config));
try {
const resp = await fetch('/api/start', { method: 'POST', body: formData });
const data = await resp.json();
if (data.error) { alert(data.error); btnStart.disabled = false; return; }
sessionId = data.session_id;
startStream();
btnSubmit.classList.remove('hidden');
btnDownload.classList.remove('hidden');
} catch (e) {
alert('请求失败: ' + e.message);
btnStart.disabled = false;
}
});
function startStream() {
logPanel.innerHTML = '';
if (evtSource) evtSource.close();
evtSource = new EventSource(`/api/stream/${sessionId}`);
evtSource.addEventListener('message', e => {
const data = JSON.parse(e.data);
if (data.type === 'log') {
const lvl = (data.msg.match(/\[(\w+)\]/) || [])[1] || '';
logPanel.innerHTML += `<div class="entry ${lvl}">${escHtml(data.msg)}</div>`;
logPanel.scrollTop = logPanel.scrollHeight;
}
if (data.type === 'heartbeat' || data.type === 'status') {
statusBadge.textContent = data.status;
statusBadge.className = `status-badge ${data.status}`;
}
if (data.type === 'complete') {
statusBadge.textContent = data.status;
statusBadge.className = `status-badge ${data.status}`;
evtSource.close();
if (data.status === 'done') {
btnSubmit.classList.remove('hidden');
loadResults();
}
btnStart.disabled = data.status !== 'done';
}
});
}
function escHtml(s) {
return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
}
async function loadResults() {
try {
const resp = await fetch(`/api/status/${sessionId}`);
const data = await resp.json();
if (!data.preview) return;
let html = '<table class="result-table"><thead><tr>';
const keys = Object.keys(data.preview[0]);
for (const k of keys) html += `<th>${k}</th>`;
html += '</tr></thead><tbody>';
for (const row of data.preview) {
html += '<tr>';
for (const k of keys) html += `<td>${row[k] || ''}</td>`;
html += '</tr>';
}
html += '</tbody></table>';
html += `<p style="margin-top:8px;color:var(--muted);">共 ${data.total} 条记录</p>`;
resultArea.innerHTML = html;
resultArea.classList.remove('hidden');
} catch (e) {}
}
// ---- 浏览器填报 ----
btnSubmit.addEventListener('click', async () => {
if (!sessionId) return;
btnSubmit.disabled = true;
statusBadge.textContent = 'submitting';
statusBadge.className = 'status-badge submitting';
try {
await fetch(`/api/submit/${sessionId}`, { method: 'POST' });
startStream();
} catch (e) {
alert('提交失败: ' + e.message);
btnSubmit.disabled = false;
}
});
// ---- 下载 CSV ----
btnDownload.addEventListener('click', () => {
if (sessionId) window.open(`/api/download/${sessionId}/csv`, '_blank');
});
</script>
</body>
</html>