fujian_water_biz_doc/output/08_planb_canvas_diagrams.html

263 lines
9.8 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>08 PlanB Canvas Diagrams</title>
<style>
:root {
--bg: #f8fafc;
--ink: #0f172a;
--muted: #475569;
--outer: #fff7ed;
--outer-border: #ea580c;
--inner: #eff6ff;
--inner-border: #2563eb;
--svc: #f5f3ff;
--svc-border: #7c3aed;
--data: #ecfdf5;
--data-border: #059669;
--bank: #fef2f2;
--bank-border: #dc2626;
--backup: #fffbeb;
--backup-border: #d97706;
}
body {
margin: 0;
padding: 24px;
background: var(--bg);
color: var(--ink);
font: 14px/1.5 -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", sans-serif;
}
h1 {
margin: 0 0 16px;
font-size: 22px;
}
h2 {
margin: 24px 0 8px;
font-size: 18px;
}
p {
margin: 0 0 12px;
color: var(--muted);
}
.card {
background: white;
border: 1px solid #e2e8f0;
border-radius: 16px;
padding: 16px;
margin-bottom: 20px;
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.06);
}
canvas {
width: 100%;
height: auto;
display: block;
background: #ffffff;
border-radius: 12px;
}
.tip {
margin-top: 8px;
color: var(--muted);
font-size: 13px;
}
</style>
</head>
<body>
<h1>福建水务营收系统 Canvas 图示</h1>
<p>该文件用于替代当前 Mermaid 版本,便于你审阅更接近正式交付效果的网络图与访问图。</p>
<div class="card">
<h2>图 A 访问与应用软件拓扑图</h2>
<canvas id="access" width="1500" height="720"></canvas>
<div class="tip">公网域与内部网络域已明确拆分PC 端走内网,移动端与第三方走公网接入代理。</div>
</div>
<div class="card">
<h2>图 B 网络连接图</h2>
<canvas id="network" width="1600" height="980"></canvas>
<div class="tip">该图更偏网络工程师评审视角,强调边界、链路和跨域访问关系。</div>
</div>
<script>
const COLORS = {
ink: "#0f172a",
muted: "#475569",
outer: "#fff7ed",
outerBorder: "#ea580c",
inner: "#eff6ff",
innerBorder: "#2563eb",
svc: "#f5f3ff",
svcBorder: "#7c3aed",
data: "#ecfdf5",
dataBorder: "#059669",
bank: "#fef2f2",
bankBorder: "#dc2626",
backup: "#fffbeb",
backupBorder: "#d97706",
line: "#334155"
};
function roundRect(ctx, x, y, w, h, r, fill, stroke, lw = 2) {
ctx.beginPath();
ctx.moveTo(x + r, y);
ctx.arcTo(x + w, y, x + w, y + h, r);
ctx.arcTo(x + w, y + h, x, y + h, r);
ctx.arcTo(x, y + h, x, y, r);
ctx.arcTo(x, y, x + w, y, r);
ctx.closePath();
ctx.fillStyle = fill;
ctx.fill();
ctx.lineWidth = lw;
ctx.strokeStyle = stroke;
ctx.stroke();
}
function text(ctx, str, x, y, opts = {}) {
const { size = 18, color = COLORS.ink, align = "center", weight = "600" } = opts;
ctx.fillStyle = color;
ctx.font = `${weight} ${size}px -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", sans-serif`;
ctx.textAlign = align;
ctx.textBaseline = "middle";
const lines = Array.isArray(str) ? str : [str];
lines.forEach((line, i) => ctx.fillText(line, x, y + i * (size + 6)));
}
function arrow(ctx, x1, y1, x2, y2, label = "") {
ctx.strokeStyle = COLORS.line;
ctx.fillStyle = COLORS.line;
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
const angle = Math.atan2(y2 - y1, x2 - x1);
const len = 10;
ctx.beginPath();
ctx.moveTo(x2, y2);
ctx.lineTo(x2 - len * Math.cos(angle - Math.PI / 6), y2 - len * Math.sin(angle - Math.PI / 6));
ctx.lineTo(x2 - len * Math.cos(angle + Math.PI / 6), y2 - len * Math.sin(angle + Math.PI / 6));
ctx.closePath();
ctx.fill();
if (label) {
const mx = (x1 + x2) / 2;
const my = (y1 + y2) / 2 - 10;
roundRect(ctx, mx - 72, my - 16, 144, 28, 8, "#ffffff", "#cbd5e1", 1);
text(ctx, label, mx, my - 1, { size: 12, weight: "500", color: COLORS.muted });
}
}
function drawAccess() {
const c = document.getElementById("access");
const ctx = c.getContext("2d");
ctx.clearRect(0, 0, c.width, c.height);
roundRect(ctx, 40, 50, 500, 210, 20, COLORS.outer, COLORS.outerBorder);
text(ctx, "公网域", 290, 80, { size: 22 });
roundRect(ctx, 80, 115, 160, 60, 14, "#ffffff", COLORS.outerBorder);
roundRect(ctx, 290, 115, 160, 60, 14, "#ffffff", COLORS.outerBorder);
roundRect(ctx, 170, 190, 220, 60, 14, "#ffffff", COLORS.outerBorder);
text(ctx, "移动端用户", 160, 145);
text(ctx, "第三方系统", 370, 145);
text(ctx, "公网接入代理", 280, 220);
roundRect(ctx, 600, 50, 280, 210, 20, COLORS.inner, COLORS.innerBorder);
text(ctx, "内部网络域", 740, 80, { size: 22 });
roundRect(ctx, 645, 115, 190, 60, 14, "#ffffff", COLORS.innerBorder);
roundRect(ctx, 645, 190, 190, 60, 14, "#ffffff", COLORS.innerBorder);
text(ctx, "PC 端用户", 740, 145);
text(ctx, "内网 Nginx", 740, 220);
roundRect(ctx, 930, 80, 240, 220, 20, COLORS.inner, COLORS.innerBorder);
roundRect(ctx, 1220, 80, 240, 220, 20, COLORS.inner, COLORS.innerBorder);
text(ctx, "业务应用节点 1", 1050, 110, { size: 20 });
text(ctx, "业务应用节点 2", 1340, 110, { size: 20 });
roundRect(ctx, 980, 145, 140, 54, 12, "#ffffff", COLORS.innerBorder);
roundRect(ctx, 1270, 145, 140, 54, 12, "#ffffff", COLORS.innerBorder);
roundRect(ctx, 980, 220, 140, 54, 12, "#ffffff", COLORS.innerBorder);
roundRect(ctx, 1270, 220, 140, 54, 12, "#ffffff", COLORS.innerBorder);
text(ctx, "Gateway", 1050, 172);
text(ctx, "Gateway", 1340, 172);
text(ctx, "业务服务", 1050, 247);
text(ctx, "业务服务", 1340, 247);
arrow(ctx, 390, 220, 645, 220, "跨公网/内网边界转发");
arrow(ctx, 740, 220, 980, 172, "负载到节点 1");
arrow(ctx, 740, 220, 1270, 172, "负载到节点 2");
arrow(ctx, 1050, 199, 1050, 220, "");
arrow(ctx, 1340, 199, 1340, 220, "");
}
function drawNetwork() {
const c = document.getElementById("network");
const ctx = c.getContext("2d");
ctx.clearRect(0, 0, c.width, c.height);
roundRect(ctx, 40, 40, 440, 180, 18, COLORS.outer, COLORS.outerBorder);
text(ctx, "公网域", 260, 72, { size: 22 });
roundRect(ctx, 70, 105, 120, 54, 12, "#fff", COLORS.outerBorder);
roundRect(ctx, 215, 105, 120, 54, 12, "#fff", COLORS.outerBorder);
roundRect(ctx, 125, 170, 180, 54, 12, "#fff", COLORS.outerBorder);
text(ctx, "移动端用户", 130, 132, { size: 15 });
text(ctx, "第三方系统", 275, 132, { size: 15 });
text(ctx, "公网接入代理", 215, 197, { size: 15 });
roundRect(ctx, 540, 40, 230, 180, 18, COLORS.inner, COLORS.innerBorder);
text(ctx, "内网接入区", 655, 72, { size: 22 });
roundRect(ctx, 575, 130, 160, 60, 12, "#fff", COLORS.innerBorder);
text(ctx, "内网 Nginx", 655, 160);
roundRect(ctx, 830, 40, 360, 220, 18, COLORS.inner, COLORS.innerBorder);
text(ctx, "应用区", 1010, 72, { size: 22 });
roundRect(ctx, 865, 115, 140, 95, 12, "#fff", COLORS.innerBorder);
roundRect(ctx, 1015, 115, 140, 95, 12, "#fff", COLORS.innerBorder);
text(ctx, ["应用节点 1", "Gateway + Biz"], 935, 145, { size: 15 });
text(ctx, ["应用节点 2", "Gateway + Biz"], 1085, 145, { size: 15 });
roundRect(ctx, 1240, 40, 300, 220, 18, COLORS.svc, COLORS.svcBorder);
text(ctx, "综合服务区", 1390, 72, { size: 22 });
roundRect(ctx, 1275, 115, 110, 54, 12, "#fff", COLORS.svcBorder);
roundRect(ctx, 1395, 115, 110, 54, 12, "#fff", COLORS.svcBorder);
roundRect(ctx, 1335, 185, 110, 54, 12, "#fff", COLORS.svcBorder);
text(ctx, "缓存服务", 1330, 142, { size: 14 });
text(ctx, "文件服务", 1450, 142, { size: 14 });
text(ctx, "数据库控制", 1390, 212, { size: 14 });
roundRect(ctx, 40, 310, 300, 180, 18, COLORS.bank, COLORS.bankBorder);
text(ctx, "银行文件交换区", 190, 342, { size: 22 });
roundRect(ctx, 90, 390, 200, 60, 12, "#fff", COLORS.bankBorder);
text(ctx, "SFTP / FTP 文件交换服务器", 190, 420, { size: 15 });
roundRect(ctx, 430, 310, 360, 180, 18, COLORS.data, COLORS.dataBorder);
text(ctx, "数据区", 610, 342, { size: 22 });
roundRect(ctx, 470, 390, 120, 60, 12, "#fff", COLORS.dataBorder);
roundRect(ctx, 630, 390, 120, 60, 12, "#fff", COLORS.dataBorder);
text(ctx, "主库", 530, 420);
text(ctx, "热备", 690, 420);
roundRect(ctx, 860, 310, 260, 180, 18, COLORS.backup, COLORS.backupBorder);
text(ctx, "备份归档区", 990, 342, { size: 22 });
roundRect(ctx, 900, 390, 180, 60, 12, "#fff", COLORS.backupBorder);
text(ctx, "备份归档存储", 990, 420);
arrow(ctx, 305, 197, 575, 160, "公网转内网");
arrow(ctx, 655, 190, 935, 115, "API / 网关端口");
arrow(ctx, 655, 190, 1085, 115, "API / 网关端口");
arrow(ctx, 935, 210, 1330, 142, "6379 / 8848 / 9000");
arrow(ctx, 1085, 210, 1390, 212, "6432 / 5000");
arrow(ctx, 935, 210, 190, 390, "21 或 22");
arrow(ctx, 1390, 239, 530, 390, "5432");
arrow(ctx, 1390, 239, 690, 390, "5432 状态探测");
arrow(ctx, 590, 420, 630, 420, "主备同步");
arrow(ctx, 1390, 239, 990, 390, "归档");
arrow(ctx, 690, 450, 990, 450, "备份副本");
}
drawAccess();
drawNetwork();
</script>
</body>
</html>