Files
docs/悦/货代SAAS平台后台/流程图/费用流转流程图.html
T

297 lines
23 KiB
HTML
Raw Normal View History

2026-08-21 09:20:26 +08:00
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>费用流转流程图</title>
<style>
:root{--ink:#1f2937;--muted:#64748b;--line:#d8e1eb;--blue:#2563eb;--blue-soft:#eff6ff;--orange:#d97706;--orange-soft:#fff7ed;--green:#15803d;--green-soft:#f0fdf4;--red:#dc2626;--red-soft:#fef2f2;--gray:#64748b;--gray-soft:#f8fafc;--lane:#f7f9fc;--lane-alt:#fbfdff}
*{box-sizing:border-box}
html,body{margin:0;min-height:100%;font-family:"Microsoft YaHei","PingFang SC",Arial,sans-serif;color:var(--ink);background:#eef2f6}
body{padding:24px}
.page{max-width:1800px;margin:0 auto}
.toolbar{display:flex;align-items:flex-start;justify-content:space-between;gap:18px;margin-bottom:16px;padding:20px 22px;background:#fff;border:1px solid #dbe3ed;border-radius:10px;box-shadow:0 6px 18px rgba(30,41,59,.06)}
.title-wrap h1{margin:0;font-size:24px;letter-spacing:0;font-weight:760;color:#172033}
.title-wrap p{margin:8px 0 0;color:var(--muted);font-size:13px;line-height:1.55}
.toolbar-actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}
button{border:1px solid #cbd5e1;border-radius:6px;background:#fff;color:#334155;padding:9px 14px;font:inherit;font-size:13px;cursor:pointer;transition:background .15s,border-color .15s,color .15s}
button:hover{border-color:#94a3b8;background:#f8fafc}
button.primary{border-color:#2563eb;background:#2563eb;color:#fff}
button.primary:hover{background:#1d4ed8;border-color:#1d4ed8}
button.danger{color:#b91c1c;border-color:#fecaca;background:#fffafa}
.legend{display:flex;align-items:center;flex-wrap:wrap;gap:10px 18px;margin-bottom:14px;padding:12px 16px;background:#fff;border:1px solid #dbe3ed;border-radius:8px;color:#475569;font-size:12px}
.legend-title{font-weight:700;color:#334155;margin-right:2px}
.legend-item{display:inline-flex;align-items:center;gap:7px;white-space:nowrap}
.legend-dot{width:12px;height:12px;border-radius:4px;border:1px solid currentColor;display:inline-block}
.legend-dot.blue{background:var(--blue-soft);color:var(--blue)}
.legend-dot.orange{background:var(--orange-soft);color:var(--orange)}
.legend-dot.green{background:var(--green-soft);color:var(--green)}
.legend-dot.red{background:var(--red-soft);color:var(--red)}
.legend-dot.gray{background:var(--gray-soft);color:var(--gray);border-style:dashed}
.legend-line{width:30px;border-top:2px solid #64748b;display:inline-block}
.legend-line.red{border-top-color:var(--red);border-top-style:dashed}
.legend-line.gray{border-top-color:#94a3b8;border-top-style:dashed}
.scroll-frame{max-width:1602px;margin:0 auto;overflow:auto;border:1px solid #cfd9e5;border-radius:10px;background:#fff;box-shadow:0 8px 22px rgba(30,41,59,.06)}
.canvas{position:relative;width:1600px;height:3300px;background:#fff}
.lane{position:absolute;top:0;bottom:0;width:400px;border-left:1px solid #dbe4ee;border-right:1px solid #dbe4ee}
.lane:nth-of-type(1){left:0;background:var(--lane-alt)}
.lane:nth-of-type(2){left:400px;background:var(--lane)}
.lane:nth-of-type(3){left:800px;background:var(--lane-alt)}
.lane:nth-of-type(4){left:1200px;background:var(--lane)}
.lane-label{position:absolute;left:0;right:0;top:0;height:76px;display:flex;align-items:center;justify-content:center;border-bottom:1px solid #d6e0eb;background:rgba(241,245,249,.88);font-size:16px;font-weight:760;color:#334155;writing-mode:horizontal-tb;letter-spacing:1px}
.lane-note{position:absolute;left:24px;right:24px;top:48px;text-align:center;font-size:10px;line-height:1.4;color:#94a3b8;white-space:nowrap;letter-spacing:0}
.node{position:absolute;width:220px;height:74px;overflow:hidden;padding:12px 13px;display:flex;align-items:center;justify-content:center;text-align:center;border:1px solid #cbd5e1;border-radius:8px;background:#fff;box-shadow:0 3px 8px rgba(15,23,42,.07);font-size:13px;line-height:1.45;z-index:3}
.node .node-text{display:block;width:100%;max-height:50px;overflow:auto;outline:0;white-space:pre-line;overflow-wrap:anywhere;scrollbar-width:thin}
.node.system{border-color:#93c5fd;background:var(--blue-soft);color:#1e40af}
.node.pending{border-color:#f6c778;background:var(--orange-soft);color:#92400e}
.node.success{border-color:#86d39d;background:var(--green-soft);color:#166534}
.node.reject{border-color:#f3a4a4;background:var(--red-soft);color:#991b1b}
.node.decision{border-style:dashed;border-color:#f59e0b;background:#fffdf5;color:#854d0e;border-radius:8px}
.node.note{border-style:dashed;border-color:#b8c3d1;background:var(--gray-soft);color:#64748b;font-size:12px}
.node.editing{box-shadow:0 0 0 3px rgba(37,99,235,.16),0 3px 8px rgba(15,23,42,.07)}
.node.editing .node-text{cursor:text;background:rgba(255,255,255,.6);border-radius:4px;padding:2px 3px}
.branch-tag{position:absolute;z-index:4;padding:6px 12px;border-radius:4px;font-size:11px;font-weight:700;letter-spacing:.2px;background:#fff}
.branch-tag.ar{left:24px;top:1000px;color:#1d4ed8;border-left:3px solid #2563eb}
.branch-tag.ap{left:24px;top:2130px;color:#7c3aed;border-left:3px solid #7c3aed}
.canvas-caption{display:flex;justify-content:space-between;gap:12px;padding:11px 16px;border-top:1px solid #dbe4ee;background:#fff;color:#64748b;font-size:12px}
.canvas-caption strong{color:#334155}
.status{min-height:18px;color:#64748b;font-size:12px}
svg.connections{position:absolute;inset:0;width:100%;height:100%;z-index:2;pointer-events:none}
.connections path{fill:none;stroke:#64748b;stroke-width:2.2;stroke-linecap:round;stroke-linejoin:round}
.connections path.red{stroke:var(--red);stroke-dasharray:7 6}
.connections path.gray{stroke:#94a3b8;stroke-dasharray:5 6}
.connections text{font-family:"Microsoft YaHei","PingFang SC",Arial,sans-serif;font-size:12px;fill:#64748b}
.connections text.red{fill:var(--red)}
.connections text.gray{fill:#64748b}
@media (max-width:900px){body{padding:12px}.toolbar{display:block}.toolbar-actions{justify-content:flex-start;margin-top:14px}.title-wrap h1{font-size:20px}}
@media print{body{padding:0;background:#fff}.toolbar,.legend,.canvas-caption{display:none}.page{max-width:none}.scroll-frame{border:0;overflow:visible;box-shadow:none}.canvas{transform-origin:top left;transform:scale(.48);margin-bottom:-1716px}}
</style>
</head>
<body data-storage-key="fee-flow-node-texts-v1">
<main class="page">
<header class="toolbar">
<div class="title-wrap">
<h1>费用流转流程图</h1>
<p>依据后台订单与财务原型整理:费用生成 → 财务审核 → 应收 / 应付结算 → 收付款结清。</p>
</div>
<div class="toolbar-actions" aria-label="流程图操作">
<button type="button" class="primary" id="edit-toggle">编辑文字</button>
<button type="button" id="reset-flow">恢复默认</button>
<button type="button" id="download-flow">下载 HTML</button>
<button type="button" id="print-flow">打印</button>
</div>
</header>
<section class="legend" aria-label="图例">
<span class="legend-title">图例</span>
<span class="legend-item"><i class="legend-dot blue"></i>系统动作 / 单据</span>
<span class="legend-item"><i class="legend-dot orange"></i>待处理 / 待确认</span>
<span class="legend-item"><i class="legend-dot green"></i>完成 / 结清</span>
<span class="legend-item"><i class="legend-dot red"></i>驳回 / 异常</span>
<span class="legend-item"><i class="legend-dot gray"></i>原型未定义 / 留档</span>
<span class="legend-item"><i class="legend-line"></i>正常流</span>
<span class="legend-item"><i class="legend-line red"></i>驳回回路</span>
<span class="legend-item"><i class="legend-line gray"></i>旁路</span>
</section>
<div class="scroll-frame">
<section class="canvas" aria-label="费用流转竖向泳道图" data-flow-direction="vertical" data-department-layout="columns" data-canvas-width="1600" data-canvas-height="3300">
<div class="lane" data-department="业务订单"><div class="lane-label">业务订单</div><div class="lane-note">国内仓 / 海外仓 / 派送 / 货柜等业务单据</div></div>
<div class="lane" data-department="系统"><div class="lane-label">系统</div><div class="lane-note">记录费用、账单、结算单和余额状态</div></div>
<div class="lane" data-department="财务"><div class="lane-label">财务</div><div class="lane-note">审核、对账、审批、收付款登记</div></div>
<div class="lane" data-department="客户与供应商"><div class="lane-label">客户与供应商</div><div class="lane-note">客户确认已实现;供应商确认未在原型定义</div></div>
<span class="branch-tag ar">应收分支 · 客户结算</span>
<span class="branch-tag ap">应付分支 · 供应商结算</span>
<svg class="connections" viewBox="0 0 1600 3300" role="img" aria-label="费用流转连线">
<defs>
<marker id="arrow" markerWidth="9" markerHeight="9" refX="7" refY="4.5" orient="auto"><path d="M0,0 L9,4.5 L0,9 Z" fill="#64748b"></path></marker>
<marker id="arrow-red" markerWidth="9" markerHeight="9" refX="7" refY="4.5" orient="auto"><path d="M0,0 L9,4.5 L0,9 Z" fill="#dc2626"></path></marker>
<marker id="arrow-gray" markerWidth="9" markerHeight="9" refX="7" refY="4.5" orient="auto"><path d="M0,0 L9,4.5 L0,9 Z" fill="#94a3b8"></path></marker>
</defs>
<path d="M200 194 V224 H600 V250" marker-end="url(#arrow)"></path>
<path d="M600 324 V352 H1000 V380" marker-end="url(#arrow)"></path>
<path d="M1000 454 V510" marker-end="url(#arrow)"></path>
<path d="M890 547 H600 V650" marker-end="url(#arrow)"></path>
<text x="730" y="535">通过</text>
<path d="M1000 584 V610 H200 V650" class="red" marker-end="url(#arrow-red)"></path>
<text x="925" y="603" class="red">驳回</text>
<path d="M90 687 H40 V287 H490" class="red" marker-end="url(#arrow-red)"></path>
<text x="48" y="472" class="red">编辑后重新提交</text>
<path d="M600 724 V790" marker-end="url(#arrow)"></path>
<path d="M710 827 H1000 V900" class="gray" marker-end="url(#arrow-gray)"></path>
<text x="822" y="815" class="gray">否:留档</text>
<path d="M600 864 V1040" marker-end="url(#arrow)"></path>
<text x="612" y="963">是:应收</text>
<path d="M490 827 H430 V2217 H490" marker-end="url(#arrow)"></path>
<text x="440" y="2106">是:应付</text>
<path d="M600 1114 V1142 H1000 V1170" marker-end="url(#arrow)"></path>
<path d="M1000 1244 V1300" marker-end="url(#arrow)"></path>
<path d="M1000 1374 V1402 H1400 V1430" marker-end="url(#arrow)"></path>
<path d="M1400 1504 V1560" class="red" marker-end="url(#arrow-red)"></path>
<text x="1412" y="1540" class="red">否</text>
<path d="M1290 1467 H1180 V1518 H600 V1560" marker-end="url(#arrow)"></path>
<text x="1200" y="1454">是:已确认</text>
<path d="M1510 1597 H1560 V1337 H1110" class="red" marker-end="url(#arrow-red)"></path>
<text x="1470" y="1392" class="red">重新推送</text>
<path d="M600 1634 V1662 H1000 V1690" marker-end="url(#arrow)"></path>
<path d="M1000 1764 V1820" marker-end="url(#arrow)"></path>
<path d="M1000 1894 V1950" marker-end="url(#arrow)"></path>
<path d="M600 1634 V1820" class="gray" marker-end="url(#arrow-gray)"></path>
<text x="612" y="1754" class="gray">原型未定义</text>
<path d="M600 2254 V2310" marker-end="url(#arrow)"></path>
<path d="M600 2384 V2412 H1000 V2440" marker-end="url(#arrow)"></path>
<path d="M1000 2514 V2570" class="red" marker-end="url(#arrow-red)"></path>
<text x="1012" y="2552" class="red">驳回</text>
<path d="M1110 2607 H1150 V2477 H1110" class="red" marker-end="url(#arrow-red)"></path>
<text x="1160" y="2548" class="red">重新审批</text>
<path d="M890 2477 H600 V2700" marker-end="url(#arrow)"></path>
<text x="730" y="2465">通过</text>
<path d="M710 2737 H1290" class="gray" marker-end="url(#arrow-gray)"></path>
<text x="1008" y="2724" class="gray">供应商确认未定义</text>
<path d="M600 2774 V2802 H1000 V2830" marker-end="url(#arrow)"></path>
<path d="M1000 2904 V2960" marker-end="url(#arrow)"></path>
<path d="M1000 3034 V3090" marker-end="url(#arrow)"></path>
</svg>
<div class="node" style="left:90px;top:120px" data-node-id="order-fee-source" data-default-text="业务订单产生费用"><span class="node-text" contenteditable="false">业务订单产生费用</span></div>
<div class="node system" style="left:490px;top:250px" data-node-id="fee-generated" data-default-text="系统生成应收/应付费用记录"><span class="node-text" contenteditable="false">系统生成应收/应付费用记录</span></div>
<div class="node pending" style="left:890px;top:380px" data-node-id="fee-pending-audit" data-default-text="费用待审核"><span class="node-text" contenteditable="false">费用待审核</span></div>
<div class="node decision" style="left:890px;top:510px" data-node-id="fee-audit-decision" data-default-text="财务审核通过?"><span class="node-text" contenteditable="false">财务审核通过?</span></div>
<div class="node reject" style="left:90px;top:650px" data-node-id="fee-rejected" data-default-text="审核驳回:费用可编辑"><span class="node-text" contenteditable="false">审核驳回:费用可编辑</span></div>
<div class="node success" style="left:490px;top:650px" data-node-id="fee-approved" data-default-text="审核通过:记录锁定"><span class="node-text" contenteditable="false">审核通过:记录锁定</span></div>
<div class="node decision" style="left:490px;top:790px" data-node-id="join-settlement-decision" data-default-text="参与结算?"><span class="node-text" contenteditable="false">参与结算?</span></div>
<div class="node note" style="left:890px;top:900px" data-node-id="fee-archived" data-default-text="不参与结算 / 禁止结算\n留档,不生成账单"><span class="node-text" contenteditable="false">不参与结算 / 禁止结算<br>留档,不生成账单</span></div>
<div class="node system" style="left:490px;top:1040px" data-node-id="ar-bill" data-default-text="客户对账单\n按费用明细汇总"><span class="node-text" contenteditable="false">客户对账单<br>按费用明细汇总</span></div>
<div class="node pending" style="left:890px;top:1170px" data-node-id="ar-bill-audit" data-default-text="对账单审核通过"><span class="node-text" contenteditable="false">对账单审核通过</span></div>
<div class="node" style="left:890px;top:1300px" data-node-id="ar-push" data-default-text="推送客户"><span class="node-text" contenteditable="false">推送客户</span></div>
<div class="node decision" style="left:1290px;top:1430px" data-node-id="customer-confirm" data-default-text="客户确认?"><span class="node-text" contenteditable="false">客户确认?</span></div>
<div class="node pending" style="left:1290px;top:1560px" data-node-id="ar-confirm-wait" data-default-text="待确认 / 可重新推送"><span class="node-text" contenteditable="false">待确认 / 可重新推送</span></div>
<div class="node system" style="left:490px;top:1560px" data-node-id="ar-settlement" data-default-text="生成应收结算单"><span class="node-text" contenteditable="false">生成应收结算单</span></div>
<div class="node pending" style="left:890px;top:1690px" data-node-id="ar-approve" data-default-text="应收结算单\n草稿 → 待审批 → 生效"><span class="node-text" contenteditable="false">应收结算单<br>草稿 → 待审批 → 生效</span></div>
<div class="node pending" style="left:890px;top:1820px" data-node-id="ar-payment" data-default-text="收款登记\n部分收款可重复登记"><span class="node-text" contenteditable="false">收款登记<br>部分收款可重复登记</span></div>
<div class="node success" style="left:890px;top:1950px" data-node-id="ar-paid" data-default-text="已结清"><span class="node-text" contenteditable="false">已结清</span></div>
<div class="node note" style="left:490px;top:1820px" data-node-id="ar-undefined" data-default-text="自动开票 / 自动核销:原型未定义"><span class="node-text" contenteditable="false">自动开票 / 自动核销:原型未定义</span></div>
<div class="node system" style="left:490px;top:2180px" data-node-id="ap-bill" data-default-text="供应商应付对账\n按费用明细汇总"><span class="node-text" contenteditable="false">供应商应付对账<br>按费用明细汇总</span></div>
<div class="node system" style="left:490px;top:2310px" data-node-id="ap-settlement" data-default-text="生成应付结算单"><span class="node-text" contenteditable="false">生成应付结算单</span></div>
<div class="node pending" style="left:890px;top:2440px" data-node-id="ap-approval" data-default-text="应付结算审批\n草稿 → 待审批"><span class="node-text" contenteditable="false">应付结算审批<br>草稿 → 待审批</span></div>
<div class="node reject" style="left:890px;top:2570px" data-node-id="ap-rejected" data-default-text="审批驳回\n可重新审批"><span class="node-text" contenteditable="false">审批驳回<br>可重新审批</span></div>
<div class="node success" style="left:490px;top:2700px" data-node-id="ap-effective" data-default-text="审批通过:生效"><span class="node-text" contenteditable="false">审批通过:生效</span></div>
<div class="node note" style="left:1290px;top:2700px" data-node-id="supplier-undefined" data-default-text="供应商确认:原型未定义"><span class="node-text" contenteditable="false">供应商确认:原型未定义</span></div>
<div class="node" style="left:890px;top:2830px" data-node-id="ap-voucher" data-default-text="上传付款凭证"><span class="node-text" contenteditable="false">上传付款凭证</span></div>
<div class="node pending" style="left:890px;top:2960px" data-node-id="ap-payment" data-default-text="付款登记\n部分付款可重复登记"><span class="node-text" contenteditable="false">付款登记<br>部分付款可重复登记</span></div>
<div class="node success" style="left:890px;top:3090px" data-node-id="ap-paid" data-default-text="已结清"><span class="node-text" contenteditable="false">已结清</span></div>
</section>
<div class="canvas-caption"><span><strong>原型已确认:</strong>费用审核、客户确认门槛、结算审批、部分收付款与结清。</span><span><strong>原型未定义:</strong>供应商确认、自动开票、自动核销。</span></div>
</div>
<p class="status" id="status" role="status" aria-live="polite">当前为只读模式。点击“编辑文字”后可修改节点内容。</p>
</main>
<script>
(() => {
const body = document.body;
const storageKey = body.dataset.storageKey || 'fee-flow-node-texts-v1';
const nodes = [...document.querySelectorAll('[data-node-id]')];
const textEls = nodes.map(node => node.querySelector('.node-text')).filter(Boolean);
const defaults = Object.fromEntries(nodes.map(node => [node.dataset.nodeId, node.dataset.defaultText.replace(/\\n/g, '\n')]));
const editToggle = document.getElementById('edit-toggle');
const resetButton = document.getElementById('reset-flow');
const downloadButton = document.getElementById('download-flow');
const printButton = document.getElementById('print-flow');
const status = document.getElementById('status');
const safeStorage = {
get() { try { return localStorage.getItem(storageKey); } catch { return null; } },
set(value) { try { localStorage.setItem(storageKey, value); return true; } catch { return false; } },
remove() { try { localStorage.removeItem(storageKey); return true; } catch { return false; } }
};
function setStatus(message) { status.textContent = message; }
function readNodeText(textEl) {
return String(textEl.innerText || textEl.textContent || '').replace(/\r\n/g, '\n').replace(/\u00a0/g, ' ');
}
function applyTexts(values) {
nodes.forEach(node => {
const value = values[node.dataset.nodeId];
if (value !== undefined) node.querySelector('.node-text').textContent = String(value).replace(/\\n/g, '\n');
});
}
function currentTexts() {
return Object.fromEntries(nodes.map(node => [node.dataset.nodeId, readNodeText(node.querySelector('.node-text'))]));
}
function persist() {
if (!safeStorage.set(JSON.stringify(currentTexts()))) setStatus('浏览器未允许本地保存,仍可下载当前 HTML。');
}
function setEditing(enabled) {
body.classList.toggle('editing', enabled);
textEls.forEach(textEl => {
textEl.contentEditable = enabled ? 'true' : 'false';
textEl.parentElement.classList.toggle('editing', enabled);
textEl.spellcheck = false;
});
editToggle.textContent = enabled ? '完成编辑' : '编辑文字';
editToggle.classList.toggle('primary', !enabled);
setStatus(enabled ? '编辑模式:点击任意节点文字进行修改,修改会自动保存。' : '当前为只读模式。点击“编辑文字”后可修改节点内容。');
if (enabled && textEls[0]) textEls[0].focus();
}
function loadSaved() {
try {
const saved = JSON.parse(safeStorage.get() || '{}');
applyTexts(saved && typeof saved === 'object' ? saved : {});
} catch { safeStorage.remove(); }
}
function resetFlow() {
if (!window.confirm('确定恢复流程图默认文字吗?')) return;
safeStorage.remove();
applyTexts(defaults);
setEditing(false);
setStatus('已恢复默认文字。');
}
function downloadHtml() {
const exportedTexts = currentTexts();
const clone = document.documentElement.cloneNode(true);
const cloneBody = clone.querySelector('body');
cloneBody.classList.remove('editing');
cloneBody.dataset.storageKey = 'fee-flow-node-texts-export-' + Date.now();
clone.querySelectorAll('[data-node-id]').forEach(node => {
const textEl = node.querySelector('.node-text');
node.dataset.defaultText = exportedTexts[node.dataset.nodeId];
textEl.textContent = exportedTexts[node.dataset.nodeId];
textEl.setAttribute('contenteditable', 'false');
textEl.removeAttribute('spellcheck');
node.classList.remove('editing');
});
const blob = new Blob(['<!doctype html>\n' + clone.outerHTML], { type: 'text/html;charset=utf-8' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = '费用流转流程图-已编辑.html';
document.body.appendChild(link);
link.click();
link.remove();
setTimeout(() => URL.revokeObjectURL(link.href), 0);
setStatus('已下载当前编辑版本 HTML。');
}
loadSaved();
setEditing(false);
editToggle.addEventListener('click', () => setEditing(!body.classList.contains('editing')));
resetButton.addEventListener('click', resetFlow);
downloadButton.addEventListener('click', downloadHtml);
printButton.addEventListener('click', () => window.print());
textEls.forEach(textEl => {
textEl.addEventListener('input', persist);
textEl.addEventListener('paste', event => {
event.preventDefault();
document.execCommand('insertText', false, event.clipboardData.getData('text/plain'));
});
});
})();
</script>
</body>
</html>