1564 lines
66 KiB
HTML
1564 lines
66 KiB
HTML
|
|
<!DOCTYPE html>
|
|||
|
|
<html lang="zh-CN">
|
|||
|
|
<head>
|
|||
|
|
<meta charset="UTF-8">
|
|||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|||
|
|
<title>iloom × muyu-apiserver 系统架构图</title>
|
|||
|
|
<style>
|
|||
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|||
|
|
body {
|
|||
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|||
|
|
background: #0a0e27;
|
|||
|
|
color: #e0e6ed;
|
|||
|
|
min-height: 100vh;
|
|||
|
|
overflow-x: hidden;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* ── Header ── */
|
|||
|
|
.header {
|
|||
|
|
text-align: center;
|
|||
|
|
padding: 32px 20px 16px;
|
|||
|
|
background: linear-gradient(180deg, #111640 0%, #0a0e27 100%);
|
|||
|
|
border-bottom: 1px solid rgba(99,102,241,0.2);
|
|||
|
|
}
|
|||
|
|
.header h1 {
|
|||
|
|
font-size: 28px;
|
|||
|
|
font-weight: 700;
|
|||
|
|
background: linear-gradient(135deg, #818cf8, #6ee7b7);
|
|||
|
|
-webkit-background-clip: text;
|
|||
|
|
-webkit-text-fill-color: transparent;
|
|||
|
|
margin-bottom: 6px;
|
|||
|
|
}
|
|||
|
|
.header p { font-size: 14px; color: #64748b; }
|
|||
|
|
|
|||
|
|
/* ── Legend ── */
|
|||
|
|
.legend {
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: center;
|
|||
|
|
gap: 24px;
|
|||
|
|
padding: 12px;
|
|||
|
|
flex-wrap: wrap;
|
|||
|
|
}
|
|||
|
|
.legend-item {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 8px;
|
|||
|
|
font-size: 12px;
|
|||
|
|
color: #94a3b8;
|
|||
|
|
}
|
|||
|
|
.legend-dot {
|
|||
|
|
width: 12px; height: 12px;
|
|||
|
|
border-radius: 50%;
|
|||
|
|
}
|
|||
|
|
.legend-line {
|
|||
|
|
width: 32px; height: 3px;
|
|||
|
|
border-radius: 2px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* ── Canvas ── */
|
|||
|
|
.canvas {
|
|||
|
|
position: relative;
|
|||
|
|
max-width: 1440px;
|
|||
|
|
margin: 0 auto;
|
|||
|
|
padding: 20px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* ── SVG Connections ── */
|
|||
|
|
.connections-svg {
|
|||
|
|
position: absolute;
|
|||
|
|
top: 0; left: 0;
|
|||
|
|
width: 100%; height: 100%;
|
|||
|
|
pointer-events: none;
|
|||
|
|
z-index: 1;
|
|||
|
|
}
|
|||
|
|
.conn-line {
|
|||
|
|
fill: none;
|
|||
|
|
stroke-width: 2;
|
|||
|
|
opacity: 0.5;
|
|||
|
|
transition: opacity 0.3s, stroke-width 0.3s;
|
|||
|
|
}
|
|||
|
|
.conn-line.highlight {
|
|||
|
|
opacity: 1;
|
|||
|
|
stroke-width: 3;
|
|||
|
|
}
|
|||
|
|
.flow-particle {
|
|||
|
|
r: 4;
|
|||
|
|
opacity: 0.9;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* ── Grid Layout ── */
|
|||
|
|
.arch-grid {
|
|||
|
|
display: grid;
|
|||
|
|
grid-template-columns: 200px 1fr 320px;
|
|||
|
|
grid-template-rows: auto auto auto;
|
|||
|
|
gap: 20px;
|
|||
|
|
position: relative;
|
|||
|
|
z-index: 2;
|
|||
|
|
min-height: 800px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* ── Service Card ── */
|
|||
|
|
.service-card {
|
|||
|
|
background: rgba(30, 35, 60, 0.85);
|
|||
|
|
border: 1px solid rgba(99, 102, 241, 0.15);
|
|||
|
|
border-radius: 14px;
|
|||
|
|
padding: 16px;
|
|||
|
|
cursor: pointer;
|
|||
|
|
transition: all 0.3s ease;
|
|||
|
|
position: relative;
|
|||
|
|
backdrop-filter: blur(8px);
|
|||
|
|
}
|
|||
|
|
.service-card:hover {
|
|||
|
|
border-color: rgba(99, 102, 241, 0.5);
|
|||
|
|
transform: translateY(-2px);
|
|||
|
|
box-shadow: 0 8px 32px rgba(99, 102, 241, 0.15);
|
|||
|
|
}
|
|||
|
|
.service-card.active {
|
|||
|
|
border-color: #818cf8;
|
|||
|
|
box-shadow: 0 0 24px rgba(99, 102, 241, 0.3);
|
|||
|
|
}
|
|||
|
|
.card-header {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 10px;
|
|||
|
|
margin-bottom: 10px;
|
|||
|
|
}
|
|||
|
|
.card-icon {
|
|||
|
|
width: 36px; height: 36px;
|
|||
|
|
border-radius: 10px;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
font-size: 18px;
|
|||
|
|
flex-shrink: 0;
|
|||
|
|
}
|
|||
|
|
.card-title {
|
|||
|
|
font-size: 14px;
|
|||
|
|
font-weight: 600;
|
|||
|
|
color: #e2e8f0;
|
|||
|
|
}
|
|||
|
|
.card-port {
|
|||
|
|
font-size: 11px;
|
|||
|
|
color: #64748b;
|
|||
|
|
font-family: 'SF Mono', 'Fira Code', monospace;
|
|||
|
|
}
|
|||
|
|
.card-tag {
|
|||
|
|
display: inline-block;
|
|||
|
|
font-size: 10px;
|
|||
|
|
padding: 2px 8px;
|
|||
|
|
border-radius: 20px;
|
|||
|
|
font-weight: 500;
|
|||
|
|
margin-top: 4px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* ── Color Themes ── */
|
|||
|
|
.theme-frontend .card-icon { background: rgba(251,191,36,0.15); color: #fbbf24; }
|
|||
|
|
.theme-frontend { border-color: rgba(251,191,36,0.2); }
|
|||
|
|
.theme-frontend:hover { border-color: rgba(251,191,36,0.5); }
|
|||
|
|
|
|||
|
|
.theme-gateway .card-icon { background: rgba(129,140,248,0.15); color: #818cf8; }
|
|||
|
|
.theme-gateway { border-color: rgba(129,140,248,0.2); }
|
|||
|
|
|
|||
|
|
.theme-iloom .card-icon { background: rgba(52,211,153,0.15); color: #34d399; }
|
|||
|
|
.theme-iloom { border-color: rgba(52,211,153,0.2); }
|
|||
|
|
.theme-iloom:hover { border-color: rgba(52,211,153,0.5); }
|
|||
|
|
|
|||
|
|
.theme-muyu .card-icon { background: rgba(251,113,133,0.15); color: #fb7185; }
|
|||
|
|
.theme-muyu { border-color: rgba(251,113,133,0.2); }
|
|||
|
|
.theme-muyu:hover { border-color: rgba(251,113,133,0.5); }
|
|||
|
|
|
|||
|
|
.theme-db .card-icon { background: rgba(96,165,250,0.15); color: #60a5fa; }
|
|||
|
|
.theme-db { border-color: rgba(96,165,250,0.2); }
|
|||
|
|
.theme-db:hover { border-color: rgba(96,165,250,0.5); }
|
|||
|
|
|
|||
|
|
.theme-infra .card-icon { background: rgba(168,85,247,0.15); color: #a855f7; }
|
|||
|
|
.theme-infra { border-color: rgba(168,85,247,0.2); }
|
|||
|
|
|
|||
|
|
/* ── Section Titles ── */
|
|||
|
|
.section-title {
|
|||
|
|
font-size: 13px;
|
|||
|
|
font-weight: 600;
|
|||
|
|
text-transform: uppercase;
|
|||
|
|
letter-spacing: 1.5px;
|
|||
|
|
padding: 8px 0;
|
|||
|
|
margin-bottom: 8px;
|
|||
|
|
}
|
|||
|
|
.section-title.iloom { color: #34d399; border-bottom: 2px solid rgba(52,211,153,0.3); }
|
|||
|
|
.section-title.muyu { color: #fb7185; border-bottom: 2px solid rgba(251,113,133,0.3); }
|
|||
|
|
.section-title.data { color: #60a5fa; border-bottom: 2px solid rgba(96,165,250,0.3); }
|
|||
|
|
|
|||
|
|
/* ── Zone Boxes ── */
|
|||
|
|
.zone {
|
|||
|
|
border: 1.5px dashed rgba(99,102,241,0.2);
|
|||
|
|
border-radius: 16px;
|
|||
|
|
padding: 16px;
|
|||
|
|
position: relative;
|
|||
|
|
}
|
|||
|
|
.zone-label {
|
|||
|
|
position: absolute;
|
|||
|
|
top: -10px; left: 16px;
|
|||
|
|
background: #0a0e27;
|
|||
|
|
padding: 0 8px;
|
|||
|
|
font-size: 11px;
|
|||
|
|
font-weight: 600;
|
|||
|
|
letter-spacing: 1px;
|
|||
|
|
text-transform: uppercase;
|
|||
|
|
}
|
|||
|
|
.zone.iloom { border-color: rgba(52,211,153,0.25); }
|
|||
|
|
.zone.iloom .zone-label { color: #34d399; }
|
|||
|
|
.zone.muyu { border-color: rgba(251,113,133,0.25); }
|
|||
|
|
.zone.muyu .zone-label { color: #fb7185; }
|
|||
|
|
.zone.db { border-color: rgba(96,165,250,0.25); }
|
|||
|
|
.zone.db .zone-label { color: #60a5fa; }
|
|||
|
|
|
|||
|
|
/* ── Detail Panel ── */
|
|||
|
|
.detail-panel {
|
|||
|
|
position: fixed;
|
|||
|
|
right: -480px;
|
|||
|
|
top: 0;
|
|||
|
|
width: 460px;
|
|||
|
|
height: 100vh;
|
|||
|
|
background: #111640;
|
|||
|
|
border-left: 1px solid rgba(99,102,241,0.2);
|
|||
|
|
z-index: 100;
|
|||
|
|
transition: right 0.35s cubic-bezier(0.4, 0, 0.2, 1);
|
|||
|
|
overflow-y: auto;
|
|||
|
|
padding: 24px;
|
|||
|
|
}
|
|||
|
|
.detail-panel.open { right: 0; }
|
|||
|
|
.detail-panel .close-btn {
|
|||
|
|
position: absolute;
|
|||
|
|
top: 16px; right: 16px;
|
|||
|
|
background: rgba(255,255,255,0.05);
|
|||
|
|
border: 1px solid rgba(255,255,255,0.1);
|
|||
|
|
color: #94a3b8;
|
|||
|
|
width: 32px; height: 32px;
|
|||
|
|
border-radius: 8px;
|
|||
|
|
cursor: pointer;
|
|||
|
|
font-size: 18px;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
transition: all 0.2s;
|
|||
|
|
}
|
|||
|
|
.detail-panel .close-btn:hover { background: rgba(255,255,255,0.1); color: #fff; }
|
|||
|
|
.detail-title {
|
|||
|
|
font-size: 20px;
|
|||
|
|
font-weight: 700;
|
|||
|
|
margin-bottom: 4px;
|
|||
|
|
}
|
|||
|
|
.detail-subtitle {
|
|||
|
|
font-size: 13px;
|
|||
|
|
color: #64748b;
|
|||
|
|
margin-bottom: 20px;
|
|||
|
|
}
|
|||
|
|
.detail-section {
|
|||
|
|
margin-bottom: 20px;
|
|||
|
|
}
|
|||
|
|
.detail-section h3 {
|
|||
|
|
font-size: 12px;
|
|||
|
|
font-weight: 600;
|
|||
|
|
text-transform: uppercase;
|
|||
|
|
letter-spacing: 1px;
|
|||
|
|
color: #818cf8;
|
|||
|
|
margin-bottom: 10px;
|
|||
|
|
}
|
|||
|
|
.endpoint-list {
|
|||
|
|
list-style: none;
|
|||
|
|
font-size: 12px;
|
|||
|
|
font-family: 'SF Mono', 'Fira Code', monospace;
|
|||
|
|
}
|
|||
|
|
.endpoint-list li {
|
|||
|
|
padding: 5px 0;
|
|||
|
|
border-bottom: 1px solid rgba(255,255,255,0.04);
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 8px;
|
|||
|
|
}
|
|||
|
|
.method {
|
|||
|
|
display: inline-block;
|
|||
|
|
padding: 1px 6px;
|
|||
|
|
border-radius: 4px;
|
|||
|
|
font-size: 10px;
|
|||
|
|
font-weight: 700;
|
|||
|
|
min-width: 38px;
|
|||
|
|
text-align: center;
|
|||
|
|
}
|
|||
|
|
.method.get { background: rgba(52,211,153,0.15); color: #34d399; }
|
|||
|
|
.method.post { background: rgba(251,191,36,0.15); color: #fbbf24; }
|
|||
|
|
.method.put { background: rgba(96,165,250,0.15); color: #60a5fa; }
|
|||
|
|
.method.delete { background: rgba(251,113,133,0.15); color: #fb7185; }
|
|||
|
|
.method.patch { background: rgba(168,85,247,0.15); color: #a855f7; }
|
|||
|
|
.method.grpc { background: rgba(129,140,248,0.15); color: #818cf8; }
|
|||
|
|
.method.ws { background: rgba(6,182,212,0.15); color: #06b6d4; }
|
|||
|
|
.table-badge {
|
|||
|
|
display: inline-block;
|
|||
|
|
padding: 2px 8px;
|
|||
|
|
border-radius: 6px;
|
|||
|
|
font-size: 11px;
|
|||
|
|
font-family: 'SF Mono', 'Fira Code', monospace;
|
|||
|
|
margin: 2px;
|
|||
|
|
}
|
|||
|
|
.table-badge.ilm { background: rgba(52,211,153,0.1); color: #34d399; border: 1px solid rgba(52,211,153,0.2); }
|
|||
|
|
.table-badge.sys { background: rgba(251,113,133,0.1); color: #fb7185; border: 1px solid rgba(251,113,133,0.2); }
|
|||
|
|
.table-badge.inv { background: rgba(251,191,36,0.1); color: #fbbf24; border: 1px solid rgba(251,191,36,0.2); }
|
|||
|
|
|
|||
|
|
.flow-badge {
|
|||
|
|
display: inline-flex;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 4px;
|
|||
|
|
padding: 3px 10px;
|
|||
|
|
border-radius: 20px;
|
|||
|
|
font-size: 11px;
|
|||
|
|
margin: 2px;
|
|||
|
|
}
|
|||
|
|
.flow-badge.read { background: rgba(52,211,153,0.1); color: #6ee7b7; }
|
|||
|
|
.flow-badge.write { background: rgba(251,113,133,0.1); color: #fca5a5; }
|
|||
|
|
.flow-badge.grpc { background: rgba(129,140,248,0.1); color: #a5b4fc; }
|
|||
|
|
.flow-badge.http { background: rgba(251,191,36,0.1); color: #fcd34d; }
|
|||
|
|
|
|||
|
|
/* ── Overlay ── */
|
|||
|
|
.overlay {
|
|||
|
|
position: fixed;
|
|||
|
|
inset: 0;
|
|||
|
|
background: rgba(0,0,0,0.4);
|
|||
|
|
z-index: 99;
|
|||
|
|
opacity: 0;
|
|||
|
|
pointer-events: none;
|
|||
|
|
transition: opacity 0.3s;
|
|||
|
|
}
|
|||
|
|
.overlay.show { opacity: 1; pointer-events: auto; }
|
|||
|
|
|
|||
|
|
/* ── Animations ── */
|
|||
|
|
@keyframes pulse {
|
|||
|
|
0%, 100% { opacity: 0.6; }
|
|||
|
|
50% { opacity: 1; }
|
|||
|
|
}
|
|||
|
|
@keyframes flowDash {
|
|||
|
|
to { stroke-dashoffset: -20; }
|
|||
|
|
}
|
|||
|
|
.animated-flow {
|
|||
|
|
stroke-dasharray: 8 6;
|
|||
|
|
animation: flowDash 0.8s linear infinite;
|
|||
|
|
}
|
|||
|
|
@keyframes fadeIn {
|
|||
|
|
from { opacity: 0; transform: translateY(10px); }
|
|||
|
|
to { opacity: 1; transform: translateY(0); }
|
|||
|
|
}
|
|||
|
|
.service-card { animation: fadeIn 0.5s ease both; }
|
|||
|
|
.service-card:nth-child(1) { animation-delay: 0.05s; }
|
|||
|
|
.service-card:nth-child(2) { animation-delay: 0.1s; }
|
|||
|
|
.service-card:nth-child(3) { animation-delay: 0.15s; }
|
|||
|
|
.service-card:nth-child(4) { animation-delay: 0.2s; }
|
|||
|
|
.service-card:nth-child(5) { animation-delay: 0.25s; }
|
|||
|
|
|
|||
|
|
/* ── Sub layout ── */
|
|||
|
|
.services-col { display: flex; flex-direction: column; gap: 12px; }
|
|||
|
|
.frontend-col {
|
|||
|
|
grid-row: 1 / 3;
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
gap: 16px;
|
|||
|
|
padding-top: 40px;
|
|||
|
|
}
|
|||
|
|
.iloom-zone { grid-column: 2; grid-row: 1 / 2; }
|
|||
|
|
.muyu-zone { grid-column: 3; grid-row: 1 / 2; }
|
|||
|
|
.db-zone { grid-column: 1 / 4; grid-row: 3; }
|
|||
|
|
|
|||
|
|
.iloom-services {
|
|||
|
|
display: grid;
|
|||
|
|
grid-template-columns: 1fr 1fr;
|
|||
|
|
gap: 12px;
|
|||
|
|
}
|
|||
|
|
.iloom-gateway { grid-column: 1 / 3; }
|
|||
|
|
|
|||
|
|
.muyu-services { display: flex; flex-direction: column; gap: 12px; }
|
|||
|
|
|
|||
|
|
.db-grid {
|
|||
|
|
display: grid;
|
|||
|
|
grid-template-columns: 1fr 1fr 1fr;
|
|||
|
|
gap: 16px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* ── Flow Arrows between zones ── */
|
|||
|
|
.flow-label {
|
|||
|
|
position: absolute;
|
|||
|
|
font-size: 10px;
|
|||
|
|
font-weight: 600;
|
|||
|
|
padding: 3px 10px;
|
|||
|
|
border-radius: 12px;
|
|||
|
|
white-space: nowrap;
|
|||
|
|
z-index: 5;
|
|||
|
|
pointer-events: none;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* ── Data flow diagram ── */
|
|||
|
|
.dataflow-section {
|
|||
|
|
max-width: 1440px;
|
|||
|
|
margin: 40px auto;
|
|||
|
|
padding: 0 20px;
|
|||
|
|
}
|
|||
|
|
.dataflow-title {
|
|||
|
|
font-size: 22px;
|
|||
|
|
font-weight: 700;
|
|||
|
|
text-align: center;
|
|||
|
|
margin-bottom: 24px;
|
|||
|
|
background: linear-gradient(135deg, #818cf8, #6ee7b7);
|
|||
|
|
-webkit-background-clip: text;
|
|||
|
|
-webkit-text-fill-color: transparent;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.flow-diagram {
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
gap: 2px;
|
|||
|
|
background: rgba(30,35,60,0.5);
|
|||
|
|
border-radius: 16px;
|
|||
|
|
padding: 24px;
|
|||
|
|
border: 1px solid rgba(99,102,241,0.15);
|
|||
|
|
}
|
|||
|
|
.flow-row {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
padding: 10px 0;
|
|||
|
|
border-bottom: 1px solid rgba(255,255,255,0.03);
|
|||
|
|
}
|
|||
|
|
.flow-row:last-child { border-bottom: none; }
|
|||
|
|
.flow-row-header {
|
|||
|
|
display: contents;
|
|||
|
|
}
|
|||
|
|
.flow-row-head {
|
|||
|
|
font-weight: 700;
|
|||
|
|
color: #64748b;
|
|||
|
|
font-size: 11px;
|
|||
|
|
text-transform: uppercase;
|
|||
|
|
letter-spacing: 1px;
|
|||
|
|
}
|
|||
|
|
.flow-from {
|
|||
|
|
width: 160px;
|
|||
|
|
flex-shrink: 0;
|
|||
|
|
font-size: 13px;
|
|||
|
|
font-weight: 600;
|
|||
|
|
color: #e2e8f0;
|
|||
|
|
}
|
|||
|
|
.flow-arrow {
|
|||
|
|
width: 80px;
|
|||
|
|
flex-shrink: 0;
|
|||
|
|
text-align: center;
|
|||
|
|
position: relative;
|
|||
|
|
}
|
|||
|
|
.flow-arrow::before {
|
|||
|
|
content: '';
|
|||
|
|
position: absolute;
|
|||
|
|
top: 50%;
|
|||
|
|
left: 10px;
|
|||
|
|
right: 10px;
|
|||
|
|
height: 2px;
|
|||
|
|
border-radius: 1px;
|
|||
|
|
}
|
|||
|
|
.flow-arrow::after {
|
|||
|
|
content: '';
|
|||
|
|
position: absolute;
|
|||
|
|
top: 50%; right: 8px;
|
|||
|
|
width: 0; height: 0;
|
|||
|
|
border-left: 6px solid;
|
|||
|
|
border-top: 4px solid transparent;
|
|||
|
|
border-bottom: 4px solid transparent;
|
|||
|
|
transform: translateY(-50%);
|
|||
|
|
}
|
|||
|
|
.flow-arrow.green::before { background: #34d399; }
|
|||
|
|
.flow-arrow.green::after { border-left-color: #34d399; }
|
|||
|
|
.flow-arrow.pink::before { background: #fb7185; }
|
|||
|
|
.flow-arrow.pink::after { border-left-color: #fb7185; }
|
|||
|
|
.flow-arrow.blue::before { background: #60a5fa; }
|
|||
|
|
.flow-arrow.blue::after { border-left-color: #60a5fa; }
|
|||
|
|
.flow-arrow.purple::before { background: #a855f7; }
|
|||
|
|
.flow-arrow.purple::after { border-left-color: #a855f7; }
|
|||
|
|
.flow-arrow.yellow::before { background: #fbbf24; }
|
|||
|
|
.flow-arrow.yellow::after { border-left-color: #fbbf24; }
|
|||
|
|
|
|||
|
|
.flow-to {
|
|||
|
|
width: 160px;
|
|||
|
|
flex-shrink: 0;
|
|||
|
|
font-size: 13px;
|
|||
|
|
font-weight: 600;
|
|||
|
|
color: #e2e8f0;
|
|||
|
|
}
|
|||
|
|
.flow-desc {
|
|||
|
|
flex: 1;
|
|||
|
|
font-size: 12px;
|
|||
|
|
color: #94a3b8;
|
|||
|
|
padding-left: 16px;
|
|||
|
|
}
|
|||
|
|
.flow-protocol {
|
|||
|
|
width: 70px;
|
|||
|
|
flex-shrink: 0;
|
|||
|
|
text-align: center;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* ── JWT Flow ── */
|
|||
|
|
.jwt-flow {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: stretch;
|
|||
|
|
gap: 0;
|
|||
|
|
margin: 32px 0;
|
|||
|
|
overflow-x: auto;
|
|||
|
|
padding-bottom: 10px;
|
|||
|
|
}
|
|||
|
|
.jwt-step {
|
|||
|
|
flex: 1;
|
|||
|
|
min-width: 180px;
|
|||
|
|
background: rgba(30,35,60,0.85);
|
|||
|
|
border: 1px solid rgba(99,102,241,0.15);
|
|||
|
|
border-radius: 12px;
|
|||
|
|
padding: 16px;
|
|||
|
|
position: relative;
|
|||
|
|
text-align: center;
|
|||
|
|
}
|
|||
|
|
.jwt-step:not(:last-child)::after {
|
|||
|
|
content: '→';
|
|||
|
|
position: absolute;
|
|||
|
|
right: -14px;
|
|||
|
|
top: 50%;
|
|||
|
|
transform: translateY(-50%);
|
|||
|
|
font-size: 20px;
|
|||
|
|
color: #818cf8;
|
|||
|
|
z-index: 3;
|
|||
|
|
}
|
|||
|
|
.jwt-step-num {
|
|||
|
|
width: 28px; height: 28px;
|
|||
|
|
border-radius: 50%;
|
|||
|
|
background: rgba(129,140,248,0.2);
|
|||
|
|
color: #818cf8;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
font-size: 13px;
|
|||
|
|
font-weight: 700;
|
|||
|
|
margin: 0 auto 8px;
|
|||
|
|
}
|
|||
|
|
.jwt-step-title {
|
|||
|
|
font-size: 13px;
|
|||
|
|
font-weight: 600;
|
|||
|
|
color: #e2e8f0;
|
|||
|
|
margin-bottom: 6px;
|
|||
|
|
}
|
|||
|
|
.jwt-step-desc {
|
|||
|
|
font-size: 11px;
|
|||
|
|
color: #64748b;
|
|||
|
|
line-height: 1.5;
|
|||
|
|
}
|
|||
|
|
.jwt-step code {
|
|||
|
|
background: rgba(129,140,248,0.1);
|
|||
|
|
padding: 1px 5px;
|
|||
|
|
border-radius: 4px;
|
|||
|
|
font-size: 10px;
|
|||
|
|
color: #a5b4fc;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* ── Tables section ── */
|
|||
|
|
.tables-grid {
|
|||
|
|
display: grid;
|
|||
|
|
grid-template-columns: repeat(3, 1fr);
|
|||
|
|
gap: 16px;
|
|||
|
|
margin-top: 16px;
|
|||
|
|
}
|
|||
|
|
.table-group {
|
|||
|
|
background: rgba(30,35,60,0.6);
|
|||
|
|
border-radius: 12px;
|
|||
|
|
padding: 16px;
|
|||
|
|
border: 1px solid rgba(99,102,241,0.1);
|
|||
|
|
}
|
|||
|
|
.table-group h4 {
|
|||
|
|
font-size: 13px;
|
|||
|
|
font-weight: 600;
|
|||
|
|
margin-bottom: 10px;
|
|||
|
|
padding-bottom: 6px;
|
|||
|
|
border-bottom: 1px solid rgba(255,255,255,0.06);
|
|||
|
|
}
|
|||
|
|
.table-group ul {
|
|||
|
|
list-style: none;
|
|||
|
|
font-size: 11px;
|
|||
|
|
font-family: 'SF Mono', 'Fira Code', monospace;
|
|||
|
|
color: #94a3b8;
|
|||
|
|
}
|
|||
|
|
.table-group ul li {
|
|||
|
|
padding: 3px 0;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 6px;
|
|||
|
|
}
|
|||
|
|
.table-group ul li::before {
|
|||
|
|
content: '';
|
|||
|
|
width: 6px; height: 6px;
|
|||
|
|
border-radius: 2px;
|
|||
|
|
flex-shrink: 0;
|
|||
|
|
}
|
|||
|
|
.table-group.ilm ul li::before { background: #34d399; }
|
|||
|
|
.table-group.sys ul li::before { background: #fb7185; }
|
|||
|
|
.table-group.inv ul li::before { background: #fbbf24; }
|
|||
|
|
|
|||
|
|
/* ── Responsive: Tablet ── */
|
|||
|
|
@media (max-width: 1100px) {
|
|||
|
|
.arch-grid {
|
|||
|
|
grid-template-columns: 1fr 1fr;
|
|||
|
|
grid-template-rows: auto;
|
|||
|
|
min-height: auto;
|
|||
|
|
}
|
|||
|
|
.frontend-col {
|
|||
|
|
grid-row: auto;
|
|||
|
|
grid-column: 1 / 3;
|
|||
|
|
flex-direction: row;
|
|||
|
|
padding-top: 0;
|
|||
|
|
gap: 12px;
|
|||
|
|
}
|
|||
|
|
.frontend-col .service-card { flex: 1; }
|
|||
|
|
.iloom-zone { grid-column: 1; grid-row: auto; }
|
|||
|
|
.muyu-zone { grid-column: 2; grid-row: auto; }
|
|||
|
|
.db-zone { grid-column: 1 / 3; grid-row: auto; }
|
|||
|
|
.db-grid { grid-template-columns: 1fr 1fr; }
|
|||
|
|
.detail-panel { width: 420px; right: -420px; }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* ── Responsive: Mobile ── */
|
|||
|
|
@media (max-width: 768px) {
|
|||
|
|
html { scroll-behavior: smooth; }
|
|||
|
|
body { -webkit-text-size-adjust: 100%; }
|
|||
|
|
|
|||
|
|
.header { padding: 20px 16px 12px; }
|
|||
|
|
.header h1 { font-size: 20px; }
|
|||
|
|
.header p { font-size: 12px; }
|
|||
|
|
|
|||
|
|
.legend {
|
|||
|
|
gap: 10px 16px;
|
|||
|
|
padding: 10px 16px;
|
|||
|
|
justify-content: flex-start;
|
|||
|
|
overflow-x: auto;
|
|||
|
|
flex-wrap: nowrap;
|
|||
|
|
-webkit-overflow-scrolling: touch;
|
|||
|
|
scrollbar-width: none;
|
|||
|
|
}
|
|||
|
|
.legend::-webkit-scrollbar { display: none; }
|
|||
|
|
.legend-item { flex-shrink: 0; font-size: 11px; }
|
|||
|
|
|
|||
|
|
.canvas { padding: 12px; }
|
|||
|
|
|
|||
|
|
/* ── Hide SVG on mobile ── */
|
|||
|
|
.connections-svg { display: none; }
|
|||
|
|
|
|||
|
|
.arch-grid {
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
gap: 16px;
|
|||
|
|
min-height: auto;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.frontend-col {
|
|||
|
|
flex-direction: row;
|
|||
|
|
gap: 10px;
|
|||
|
|
padding-top: 0;
|
|||
|
|
}
|
|||
|
|
.frontend-col .service-card { flex: 1; min-width: 0; }
|
|||
|
|
|
|||
|
|
.zone { padding: 12px; border-radius: 12px; }
|
|||
|
|
.zone-label { font-size: 10px; top: -9px; }
|
|||
|
|
|
|||
|
|
.iloom-services {
|
|||
|
|
grid-template-columns: 1fr;
|
|||
|
|
}
|
|||
|
|
.iloom-gateway { grid-column: 1; }
|
|||
|
|
|
|||
|
|
.service-card {
|
|||
|
|
padding: 12px;
|
|||
|
|
border-radius: 10px;
|
|||
|
|
min-height: 44px;
|
|||
|
|
}
|
|||
|
|
.card-header { gap: 8px; margin-bottom: 6px; }
|
|||
|
|
.card-icon { width: 32px; height: 32px; font-size: 16px; border-radius: 8px; }
|
|||
|
|
.card-title { font-size: 13px; }
|
|||
|
|
.card-port { font-size: 10px; }
|
|||
|
|
.card-tag { font-size: 9px; padding: 2px 6px; }
|
|||
|
|
|
|||
|
|
.db-grid { grid-template-columns: 1fr; gap: 10px; }
|
|||
|
|
.table-group { padding: 12px; }
|
|||
|
|
.table-group h4 { font-size: 12px; }
|
|||
|
|
|
|||
|
|
/* ── Mobile flow arrows between zones ── */
|
|||
|
|
.zone::after {
|
|||
|
|
content: '▼';
|
|||
|
|
display: block;
|
|||
|
|
text-align: center;
|
|||
|
|
color: rgba(99,102,241,0.3);
|
|||
|
|
font-size: 20px;
|
|||
|
|
margin-top: 8px;
|
|||
|
|
}
|
|||
|
|
.zone:last-child::after,
|
|||
|
|
.db-zone::after { display: none; }
|
|||
|
|
|
|||
|
|
/* ── Detail Panel: Bottom Sheet ── */
|
|||
|
|
.detail-panel {
|
|||
|
|
width: 100%;
|
|||
|
|
height: 85vh;
|
|||
|
|
max-height: 85vh;
|
|||
|
|
top: auto;
|
|||
|
|
bottom: 0;
|
|||
|
|
right: 0;
|
|||
|
|
left: 0;
|
|||
|
|
transform: translateY(100%);
|
|||
|
|
transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1);
|
|||
|
|
border-radius: 20px 20px 0 0;
|
|||
|
|
border-left: none;
|
|||
|
|
border-top: 1px solid rgba(99,102,241,0.3);
|
|||
|
|
padding: 16px;
|
|||
|
|
padding-top: 8px;
|
|||
|
|
}
|
|||
|
|
.detail-panel.open {
|
|||
|
|
transform: translateY(0);
|
|||
|
|
right: 0;
|
|||
|
|
}
|
|||
|
|
.detail-panel::before {
|
|||
|
|
content: '';
|
|||
|
|
display: block;
|
|||
|
|
width: 40px;
|
|||
|
|
height: 4px;
|
|||
|
|
background: rgba(255,255,255,0.2);
|
|||
|
|
border-radius: 2px;
|
|||
|
|
margin: 0 auto 12px;
|
|||
|
|
}
|
|||
|
|
.detail-panel .close-btn {
|
|||
|
|
top: 12px; right: 12px;
|
|||
|
|
width: 36px; height: 36px;
|
|||
|
|
}
|
|||
|
|
.detail-title { font-size: 17px; padding-right: 44px; }
|
|||
|
|
.detail-subtitle { font-size: 12px; margin-bottom: 14px; }
|
|||
|
|
.detail-section { margin-bottom: 14px; }
|
|||
|
|
.endpoint-list { font-size: 11px; }
|
|||
|
|
.endpoint-list li {
|
|||
|
|
padding: 6px 0;
|
|||
|
|
flex-wrap: wrap;
|
|||
|
|
gap: 4px 8px;
|
|||
|
|
line-height: 1.5;
|
|||
|
|
}
|
|||
|
|
.method { font-size: 9px; min-width: 32px; padding: 1px 4px; }
|
|||
|
|
.table-badge { font-size: 10px; padding: 2px 6px; }
|
|||
|
|
.flow-badge { font-size: 10px; padding: 2px 8px; }
|
|||
|
|
|
|||
|
|
/* ── Dataflow section ── */
|
|||
|
|
.dataflow-section { padding: 0 12px; margin: 24px auto; }
|
|||
|
|
.dataflow-title { font-size: 18px; margin-bottom: 16px; }
|
|||
|
|
|
|||
|
|
/* ── JWT Flow: Vertical ── */
|
|||
|
|
.jwt-flow {
|
|||
|
|
flex-direction: column;
|
|||
|
|
gap: 8px;
|
|||
|
|
padding-bottom: 0;
|
|||
|
|
}
|
|||
|
|
.jwt-step {
|
|||
|
|
min-width: auto;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: flex-start;
|
|||
|
|
gap: 12px;
|
|||
|
|
text-align: left;
|
|||
|
|
padding: 12px;
|
|||
|
|
}
|
|||
|
|
.jwt-step:not(:last-child)::after {
|
|||
|
|
content: '↓';
|
|||
|
|
position: static;
|
|||
|
|
transform: none;
|
|||
|
|
font-size: 16px;
|
|||
|
|
color: #818cf8;
|
|||
|
|
position: absolute;
|
|||
|
|
bottom: -16px;
|
|||
|
|
left: 50%;
|
|||
|
|
transform: translateX(-50%);
|
|||
|
|
}
|
|||
|
|
.jwt-step-num {
|
|||
|
|
margin: 0;
|
|||
|
|
flex-shrink: 0;
|
|||
|
|
width: 32px;
|
|||
|
|
height: 32px;
|
|||
|
|
}
|
|||
|
|
.jwt-step-body { flex: 1; min-width: 0; }
|
|||
|
|
.jwt-step-title { font-size: 13px; margin-bottom: 4px; }
|
|||
|
|
.jwt-step-desc { font-size: 11px; }
|
|||
|
|
.jwt-step code { font-size: 9px; word-break: break-all; }
|
|||
|
|
|
|||
|
|
/* ── Flow Diagram: Card layout ── */
|
|||
|
|
.flow-diagram { padding: 12px; border-radius: 12px; gap: 0; }
|
|||
|
|
.flow-row {
|
|||
|
|
flex-direction: column;
|
|||
|
|
align-items: stretch;
|
|||
|
|
padding: 0;
|
|||
|
|
gap: 0;
|
|||
|
|
border-bottom: none;
|
|||
|
|
}
|
|||
|
|
.flow-row-head { display: none; }
|
|||
|
|
.flow-row-header {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 6px;
|
|||
|
|
flex-wrap: wrap;
|
|||
|
|
}
|
|||
|
|
.flow-from, .flow-to {
|
|||
|
|
width: auto;
|
|||
|
|
font-size: 12px;
|
|||
|
|
}
|
|||
|
|
.flow-arrow {
|
|||
|
|
width: 32px;
|
|||
|
|
height: auto;
|
|||
|
|
flex-shrink: 0;
|
|||
|
|
}
|
|||
|
|
.flow-arrow::before {
|
|||
|
|
top: 50%;
|
|||
|
|
left: 2px;
|
|||
|
|
right: 8px;
|
|||
|
|
height: 2px;
|
|||
|
|
}
|
|||
|
|
.flow-arrow::after {
|
|||
|
|
top: 50%;
|
|||
|
|
right: 2px;
|
|||
|
|
left: auto;
|
|||
|
|
transform: translateY(-50%);
|
|||
|
|
}
|
|||
|
|
.flow-protocol { width: auto; text-align: left; }
|
|||
|
|
.flow-desc {
|
|||
|
|
padding-left: 0;
|
|||
|
|
padding-top: 4px;
|
|||
|
|
font-size: 11px;
|
|||
|
|
line-height: 1.5;
|
|||
|
|
}
|
|||
|
|
.flow-desc code { font-size: 10px; word-break: break-all; }
|
|||
|
|
|
|||
|
|
/* ── Flow Row as mini-card ── */
|
|||
|
|
.flow-row:not(.flow-row-head) {
|
|||
|
|
background: rgba(255,255,255,0.02);
|
|||
|
|
border-radius: 10px;
|
|||
|
|
padding: 12px;
|
|||
|
|
margin-bottom: 8px;
|
|||
|
|
border-bottom: none;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.section-title { font-size: 12px; letter-spacing: 1px; }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* ── Small phones ── */
|
|||
|
|
@media (max-width: 380px) {
|
|||
|
|
.header h1 { font-size: 17px; }
|
|||
|
|
.frontend-col { flex-direction: column; }
|
|||
|
|
.card-title { font-size: 12px; }
|
|||
|
|
.detail-panel { height: 90vh; max-height: 90vh; }
|
|||
|
|
}
|
|||
|
|
</style>
|
|||
|
|
</head>
|
|||
|
|
<body>
|
|||
|
|
|
|||
|
|
<div class="header">
|
|||
|
|
<h1>iloom × muyu-apiserver 系统架构</h1>
|
|||
|
|
<p>纺织供应链管理 · 微服务架构 · 点击服务卡片查看详情</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="legend">
|
|||
|
|
<div class="legend-item"><div class="legend-dot" style="background:#fbbf24"></div>Frontend</div>
|
|||
|
|
<div class="legend-item"><div class="legend-dot" style="background:#818cf8"></div>Gateway</div>
|
|||
|
|
<div class="legend-item"><div class="legend-dot" style="background:#34d399"></div>iloom 服务</div>
|
|||
|
|
<div class="legend-item"><div class="legend-dot" style="background:#fb7185"></div>muyu 服务</div>
|
|||
|
|
<div class="legend-item"><div class="legend-dot" style="background:#60a5fa"></div>数据层</div>
|
|||
|
|
<div class="legend-item"><div class="legend-line animated-flow" style="background:none;border-top:2px dashed #34d399;"></div>HTTP</div>
|
|||
|
|
<div class="legend-item"><div class="legend-line" style="background:#818cf8;"></div>gRPC</div>
|
|||
|
|
<div class="legend-item"><div class="legend-line animated-flow" style="background:none;border-top:2px dashed #60a5fa;"></div>SQL</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- ════════════════════════════════════════ -->
|
|||
|
|
<!-- MAIN ARCHITECTURE DIAGRAM -->
|
|||
|
|
<!-- ════════════════════════════════════════ -->
|
|||
|
|
<div class="canvas" id="canvas">
|
|||
|
|
<svg class="connections-svg" id="connSvg"></svg>
|
|||
|
|
|
|||
|
|
<div class="arch-grid">
|
|||
|
|
<!-- ── Column 1: Frontend ── -->
|
|||
|
|
<div class="frontend-col">
|
|||
|
|
<div class="service-card theme-frontend" data-service="frontend" id="card-frontend">
|
|||
|
|
<div class="card-header">
|
|||
|
|
<div class="card-icon">⚛</div>
|
|||
|
|
<div>
|
|||
|
|
<div class="card-title">React SPA</div>
|
|||
|
|
<div class="card-port">:3015</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="card-tag" style="background:rgba(251,191,36,0.12);color:#fbbf24;">Frontend</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="service-card theme-frontend" data-service="muyu-portal" id="card-muyu-portal" style="opacity:0.65">
|
|||
|
|
<div class="card-header">
|
|||
|
|
<div class="card-icon" style="font-size:14px">🖥</div>
|
|||
|
|
<div>
|
|||
|
|
<div class="card-title">muyu-portal</div>
|
|||
|
|
<div class="card-port">:8080 (Nginx)</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="card-tag" style="background:rgba(251,113,133,0.12);color:#fb7185;">muyu 前端</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- ── Column 2: iloom Services ── -->
|
|||
|
|
<div class="zone iloom" id="zone-iloom">
|
|||
|
|
<span class="zone-label">iloom 微服务集群</span>
|
|||
|
|
<div class="iloom-services">
|
|||
|
|
<div class="service-card theme-gateway iloom-gateway" data-service="gateway" id="card-gateway">
|
|||
|
|
<div class="card-header">
|
|||
|
|
<div class="card-icon">🔀</div>
|
|||
|
|
<div>
|
|||
|
|
<div class="card-title">API Gateway</div>
|
|||
|
|
<div class="card-port">:8080</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div style="display:flex;gap:6px;flex-wrap:wrap;margin-top:4px;">
|
|||
|
|
<div class="card-tag" style="background:rgba(129,140,248,0.12);color:#818cf8;">JWT 验证</div>
|
|||
|
|
<div class="card-tag" style="background:rgba(129,140,248,0.12);color:#818cf8;">CORS</div>
|
|||
|
|
<div class="card-tag" style="background:rgba(129,140,248,0.12);color:#818cf8;">路由分发</div>
|
|||
|
|
<div class="card-tag" style="background:rgba(129,140,248,0.12);color:#818cf8;">限流</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="service-card theme-iloom" data-service="auth" id="card-auth">
|
|||
|
|
<div class="card-header">
|
|||
|
|
<div class="card-icon">🔐</div>
|
|||
|
|
<div>
|
|||
|
|
<div class="card-title">Auth Service</div>
|
|||
|
|
<div class="card-port">:8081</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="card-tag" style="background:rgba(52,211,153,0.12);color:#34d399;">认证 · 公司 · 成员</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="service-card theme-iloom" data-service="purchaser" id="card-purchaser">
|
|||
|
|
<div class="card-header">
|
|||
|
|
<div class="card-icon">🛒</div>
|
|||
|
|
<div>
|
|||
|
|
<div class="card-title">Purchaser Service</div>
|
|||
|
|
<div class="card-port">:8082</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="card-tag" style="background:rgba(52,211,153,0.12);color:#34d399;">采购 · 计划 · 产品</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="service-card theme-iloom" data-service="textile" id="card-textile">
|
|||
|
|
<div class="card-header">
|
|||
|
|
<div class="card-icon">🧵</div>
|
|||
|
|
<div>
|
|||
|
|
<div class="card-title">Textile Service</div>
|
|||
|
|
<div class="card-port">:8083</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="card-tag" style="background:rgba(52,211,153,0.12);color:#34d399;">纺织 · 纱线 · 工序</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="service-card theme-iloom" data-service="washing" id="card-washing">
|
|||
|
|
<div class="card-header">
|
|||
|
|
<div class="card-icon">💧</div>
|
|||
|
|
<div>
|
|||
|
|
<div class="card-title">Washing Service</div>
|
|||
|
|
<div class="card-port">:8084</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="card-tag" style="background:rgba(52,211,153,0.12);color:#34d399;">水洗 · 成品 · 出库</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- ── Column 3: muyu Services ── -->
|
|||
|
|
<div class="zone muyu" id="zone-muyu">
|
|||
|
|
<span class="zone-label">muyu-apiserver</span>
|
|||
|
|
<div class="muyu-services">
|
|||
|
|
<div class="service-card theme-muyu" data-service="muyu-gateway" id="card-muyu-gw">
|
|||
|
|
<div class="card-header">
|
|||
|
|
<div class="card-icon">🌐</div>
|
|||
|
|
<div>
|
|||
|
|
<div class="card-title">muyu Gateway</div>
|
|||
|
|
<div class="card-port">:8888 (REST)</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="card-tag" style="background:rgba(251,113,133,0.12);color:#fb7185;">go-zero API</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="service-card theme-muyu" data-service="system-rpc" id="card-system-rpc">
|
|||
|
|
<div class="card-header">
|
|||
|
|
<div class="card-icon">👤</div>
|
|||
|
|
<div>
|
|||
|
|
<div class="card-title">System RPC</div>
|
|||
|
|
<div class="card-port">:9001 (gRPC)</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div style="display:flex;gap:4px;flex-wrap:wrap;margin-top:4px;">
|
|||
|
|
<div class="card-tag" style="background:rgba(251,113,133,0.12);color:#fb7185;">用户</div>
|
|||
|
|
<div class="card-tag" style="background:rgba(251,113,133,0.12);color:#fb7185;">角色</div>
|
|||
|
|
<div class="card-tag" style="background:rgba(251,113,133,0.12);color:#fb7185;">菜单</div>
|
|||
|
|
<div class="card-tag" style="background:rgba(251,113,133,0.12);color:#fb7185;">权限</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="service-card theme-muyu" data-service="inventory-rpc" id="card-inv-rpc">
|
|||
|
|
<div class="card-header">
|
|||
|
|
<div class="card-icon">📦</div>
|
|||
|
|
<div>
|
|||
|
|
<div class="card-title">Inventory RPC</div>
|
|||
|
|
<div class="card-port">:9002 (gRPC)</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div style="display:flex;gap:4px;flex-wrap:wrap;margin-top:4px;">
|
|||
|
|
<div class="card-tag" style="background:rgba(251,113,133,0.12);color:#fb7185;">产品</div>
|
|||
|
|
<div class="card-tag" style="background:rgba(251,113,133,0.12);color:#fb7185;">库存</div>
|
|||
|
|
<div class="card-tag" style="background:rgba(251,113,133,0.12);color:#fb7185;">盘点</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div style="display:flex;gap:10px;">
|
|||
|
|
<div class="service-card theme-infra" style="flex:1" id="card-redis">
|
|||
|
|
<div class="card-header">
|
|||
|
|
<div class="card-icon" style="width:28px;height:28px;font-size:14px;">⚡</div>
|
|||
|
|
<div>
|
|||
|
|
<div class="card-title" style="font-size:12px;">Redis</div>
|
|||
|
|
<div class="card-port">:6379</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="service-card theme-infra" style="flex:1" id="card-etcd">
|
|||
|
|
<div class="card-header">
|
|||
|
|
<div class="card-icon" style="width:28px;height:28px;font-size:14px;">🔧</div>
|
|||
|
|
<div>
|
|||
|
|
<div class="card-title" style="font-size:12px;">etcd</div>
|
|||
|
|
<div class="card-port">:2379</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- ── Row 3: Database ── -->
|
|||
|
|
<div class="zone db db-zone" id="zone-db">
|
|||
|
|
<span class="zone-label">MySQL 8.0 · muyu_wms · :3306</span>
|
|||
|
|
<div class="db-grid">
|
|||
|
|
<div class="table-group ilm">
|
|||
|
|
<h4 style="color:#34d399;">ilm_* 表 (iloom 拥有 · 直接 SQL 读写)</h4>
|
|||
|
|
<ul>
|
|||
|
|
<li>ilm_company <span style="color:#475569">— 公司</span></li>
|
|||
|
|
<li>ilm_company_member <span style="color:#475569">— 成员</span></li>
|
|||
|
|
<li>ilm_company_relationship <span style="color:#475569">— 公司关系</span></li>
|
|||
|
|
<li>ilm_product_ext <span style="color:#475569">— 产品扩展</span></li>
|
|||
|
|
<li>ilm_production_plan <span style="color:#475569">— 生产计划</span></li>
|
|||
|
|
<li>ilm_plan_factory <span style="color:#475569">— 计划工厂</span></li>
|
|||
|
|
<li>ilm_yarn_ratio <span style="color:#475569">— 纱线配比</span></li>
|
|||
|
|
<li>ilm_process_step <span style="color:#475569">— 生产工序</span></li>
|
|||
|
|
<li>ilm_warehouse <span style="color:#475569">— 仓库</span></li>
|
|||
|
|
<li>ilm_inventory_record <span style="color:#475569">— 库存记录</span></li>
|
|||
|
|
</ul>
|
|||
|
|
</div>
|
|||
|
|
<div class="table-group ilm">
|
|||
|
|
<h4 style="color:#34d399;">ilm_* 表 (续)</h4>
|
|||
|
|
<ul>
|
|||
|
|
<li>ilm_product_inventory <span style="color:#475569">— 产品出入库</span></li>
|
|||
|
|
<li>ilm_product_outbound <span style="color:#475569">— 产品出库</span></li>
|
|||
|
|
<li>ilm_washing_plan <span style="color:#475569">— 水洗计划</span></li>
|
|||
|
|
<li>ilm_washing_step <span style="color:#475569">— 水洗工序</span></li>
|
|||
|
|
<li>ilm_washing_completion <span style="color:#475569">— 水洗完工</span></li>
|
|||
|
|
<li>ilm_finished_product <span style="color:#475569">— 成品</span></li>
|
|||
|
|
<li>ilm_finished_product_inventory <span style="color:#475569">— 成品出入库</span></li>
|
|||
|
|
<li>ilm_yarn_stock <span style="color:#475569">— 纱线库存</span></li>
|
|||
|
|
<li>ilm_yarn_stock_record <span style="color:#475569">— 纱线记录</span></li>
|
|||
|
|
<li>ilm_accounts_payable <span style="color:#475569">— 应付账款</span></li>
|
|||
|
|
<li>ilm_ap_item <span style="color:#475569">— 账款明细</span></li>
|
|||
|
|
<li>ilm_payment <span style="color:#475569">— 收付款</span></li>
|
|||
|
|
<li>ilm_notification <span style="color:#475569">— 通知</span></li>
|
|||
|
|
<li>ilm_share_link <span style="color:#475569">— 分享链接</span></li>
|
|||
|
|
<li>ilm_audit_log <span style="color:#475569">— 审计日志</span></li>
|
|||
|
|
<li>ilm_product_price_history <span style="color:#475569">— 产品价格历史</span></li>
|
|||
|
|
<li>ilm_production_price_history <span style="color:#475569">— 生产价格历史</span></li>
|
|||
|
|
</ul>
|
|||
|
|
</div>
|
|||
|
|
<div class="table-group sys">
|
|||
|
|
<h4 style="color:#fb7185;">sys_* / inv_* 表 (muyu 拥有 · 只读, 写走 gRPC)</h4>
|
|||
|
|
<ul>
|
|||
|
|
<li>sys_user <span style="color:#475569">— 用户</span></li>
|
|||
|
|
<li>sys_role <span style="color:#475569">— 角色</span></li>
|
|||
|
|
<li>sys_user_role <span style="color:#475569">— 用户角色绑定</span></li>
|
|||
|
|
<li>sys_menu <span style="color:#475569">— 菜单</span></li>
|
|||
|
|
<li>sys_role_menu <span style="color:#475569">— 角色菜单权限</span></li>
|
|||
|
|
<li>sys_operation_log <span style="color:#475569">— 操作日志</span></li>
|
|||
|
|
<li>sys_config <span style="color:#475569">— 系统配置</span></li>
|
|||
|
|
<li style="margin-top:8px;padding-top:8px;border-top:1px solid rgba(255,255,255,0.06);">inv_product <span style="color:#475569">— 产品</span></li>
|
|||
|
|
<li>inv_stock_check <span style="color:#475569">— 盘点</span></li>
|
|||
|
|
<li>inv_stock_adjust <span style="color:#475569">— 调整</span></li>
|
|||
|
|
<li style="margin-top:8px;padding-top:8px;border-top:1px solid rgba(255,255,255,0.06);">casbin_rule <span style="color:#475569">— RBAC 策略</span></li>
|
|||
|
|
</ul>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- ════════════════════════════════════════ -->
|
|||
|
|
<!-- DATA FLOW DIAGRAM -->
|
|||
|
|
<!-- ════════════════════════════════════════ -->
|
|||
|
|
<div class="dataflow-section">
|
|||
|
|
<div class="dataflow-title">数据流向 · 接口调用一览</div>
|
|||
|
|
|
|||
|
|
<!-- JWT Flow -->
|
|||
|
|
<div style="margin-bottom:32px;">
|
|||
|
|
<div class="section-title iloom" style="text-align:center;">JWT 认证流程</div>
|
|||
|
|
<div class="jwt-flow">
|
|||
|
|
<div class="jwt-step">
|
|||
|
|
<div class="jwt-step-num">1</div>
|
|||
|
|
<div class="jwt-step-body">
|
|||
|
|
<div class="jwt-step-title">用户登录</div>
|
|||
|
|
<div class="jwt-step-desc">Frontend → Gateway<br><code>POST /api/v1/auth/login</code><br>{username, password}</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="jwt-step">
|
|||
|
|
<div class="jwt-step-num">2</div>
|
|||
|
|
<div class="jwt-step-body">
|
|||
|
|
<div class="jwt-step-title">gRPC 验证</div>
|
|||
|
|
<div class="jwt-step-desc">Auth Service → System RPC<br><code>SystemService.Login()</code><br>muyu 验证密码</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="jwt-step">
|
|||
|
|
<div class="jwt-step-num">3</div>
|
|||
|
|
<div class="jwt-step-body">
|
|||
|
|
<div class="jwt-step-title">查询公司</div>
|
|||
|
|
<div class="jwt-step-desc">Auth Service → MySQL<br><code>sys_user JOIN ilm_company_member</code><br>获取 company_id, role</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="jwt-step">
|
|||
|
|
<div class="jwt-step-num">4</div>
|
|||
|
|
<div class="jwt-step-body">
|
|||
|
|
<div class="jwt-step-title">签发 Token</div>
|
|||
|
|
<div class="jwt-step-desc">JWT Claims:<br><code>{uid, cid, role, master, uname}</code><br>Secret: <code>muyu-wms-jwt-secret-key-2026</code></div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="jwt-step">
|
|||
|
|
<div class="jwt-step-num">5</div>
|
|||
|
|
<div class="jwt-step-body">
|
|||
|
|
<div class="jwt-step-title">请求鉴权</div>
|
|||
|
|
<div class="jwt-step-desc">Gateway 验证 JWT → 注入 Headers<br><code>X-User-ID</code> <code>X-Company-ID</code><br><code>X-User-Role</code> <code>X-Is-Master</code></div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- Main Data Flow -->
|
|||
|
|
<div class="section-title iloom" style="text-align:center;">服务间调用 · 数据流向</div>
|
|||
|
|
<div class="flow-diagram">
|
|||
|
|
<div class="flow-row flow-row-head">
|
|||
|
|
<div class="flow-from">调用方</div>
|
|||
|
|
<div class="flow-arrow" style="visibility:hidden"></div>
|
|||
|
|
<div class="flow-to">目标</div>
|
|||
|
|
<div class="flow-protocol">协议</div>
|
|||
|
|
<div class="flow-desc">说明</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="flow-row">
|
|||
|
|
<div class="flow-row-header">
|
|||
|
|
<div class="flow-from" style="color:#fbbf24;">React SPA</div>
|
|||
|
|
<div class="flow-arrow green"></div>
|
|||
|
|
<div class="flow-to" style="color:#818cf8;">Gateway :8080</div>
|
|||
|
|
<div class="flow-protocol"><span class="method get">HTTP</span></div>
|
|||
|
|
</div>
|
|||
|
|
<div class="flow-desc">所有 API 请求 + WebSocket 升级,<code>Authorization: Bearer <token></code></div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="flow-row">
|
|||
|
|
<div class="flow-row-header">
|
|||
|
|
<div class="flow-from" style="color:#818cf8;">Gateway</div>
|
|||
|
|
<div class="flow-arrow green"></div>
|
|||
|
|
<div class="flow-to" style="color:#34d399;">Auth Service</div>
|
|||
|
|
<div class="flow-protocol"><span class="method post">HTTP</span></div>
|
|||
|
|
</div>
|
|||
|
|
<div class="flow-desc"><code>/api/v1/auth/*</code>, <code>/api/v1/common/*</code> → 反向代理到 :8081</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="flow-row">
|
|||
|
|
<div class="flow-row-header">
|
|||
|
|
<div class="flow-from" style="color:#818cf8;">Gateway</div>
|
|||
|
|
<div class="flow-arrow green"></div>
|
|||
|
|
<div class="flow-to" style="color:#34d399;">Purchaser Svc</div>
|
|||
|
|
<div class="flow-protocol"><span class="method post">HTTP</span></div>
|
|||
|
|
</div>
|
|||
|
|
<div class="flow-desc"><code>/api/v1/purchaser/*</code> → :8082 (role=purchaser)</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="flow-row">
|
|||
|
|
<div class="flow-row-header">
|
|||
|
|
<div class="flow-from" style="color:#818cf8;">Gateway</div>
|
|||
|
|
<div class="flow-arrow green"></div>
|
|||
|
|
<div class="flow-to" style="color:#34d399;">Textile Svc</div>
|
|||
|
|
<div class="flow-protocol"><span class="method post">HTTP</span></div>
|
|||
|
|
</div>
|
|||
|
|
<div class="flow-desc"><code>/api/v1/textile/*</code> → :8083 (role=textile)</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="flow-row">
|
|||
|
|
<div class="flow-row-header">
|
|||
|
|
<div class="flow-from" style="color:#818cf8;">Gateway</div>
|
|||
|
|
<div class="flow-arrow green"></div>
|
|||
|
|
<div class="flow-to" style="color:#34d399;">Washing Svc</div>
|
|||
|
|
<div class="flow-protocol"><span class="method post">HTTP</span></div>
|
|||
|
|
</div>
|
|||
|
|
<div class="flow-desc"><code>/api/v1/washing/*</code> → :8084 (role=washing)</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="flow-row" style="background:rgba(129,140,248,0.04);">
|
|||
|
|
<div class="flow-row-header">
|
|||
|
|
<div class="flow-from" style="color:#34d399;">Auth Service</div>
|
|||
|
|
<div class="flow-arrow purple"></div>
|
|||
|
|
<div class="flow-to" style="color:#fb7185;">System RPC</div>
|
|||
|
|
<div class="flow-protocol"><span class="method grpc">gRPC</span></div>
|
|||
|
|
</div>
|
|||
|
|
<div class="flow-desc"><code>Login()</code> 密码验证, <code>CreateUser()</code> 注册, <code>GetUser()</code> 查询</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="flow-row" style="background:rgba(129,140,248,0.04);">
|
|||
|
|
<div class="flow-row-header">
|
|||
|
|
<div class="flow-from" style="color:#34d399;">Purchaser Svc</div>
|
|||
|
|
<div class="flow-arrow purple"></div>
|
|||
|
|
<div class="flow-to" style="color:#fb7185;">Inventory RPC</div>
|
|||
|
|
<div class="flow-protocol"><span class="method grpc">gRPC</span></div>
|
|||
|
|
</div>
|
|||
|
|
<div class="flow-desc"><code>CreateProduct()</code>, <code>GetProduct()</code>, <code>ListProduct()</code> 产品CRUD</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="flow-row">
|
|||
|
|
<div class="flow-row-header">
|
|||
|
|
<div class="flow-from" style="color:#34d399;">Textile Svc</div>
|
|||
|
|
<div class="flow-arrow yellow"></div>
|
|||
|
|
<div class="flow-to" style="color:#34d399;">Purchaser Svc</div>
|
|||
|
|
<div class="flow-protocol"><span class="method get">HTTP</span></div>
|
|||
|
|
</div>
|
|||
|
|
<div class="flow-desc">内部服务调用 — 查询采购商计划、工厂信息</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="flow-row">
|
|||
|
|
<div class="flow-row-header">
|
|||
|
|
<div class="flow-from" style="color:#34d399;">Washing Svc</div>
|
|||
|
|
<div class="flow-arrow yellow"></div>
|
|||
|
|
<div class="flow-to" style="color:#34d399;">Purchaser Svc</div>
|
|||
|
|
<div class="flow-protocol"><span class="method get">HTTP</span></div>
|
|||
|
|
</div>
|
|||
|
|
<div class="flow-desc">内部服务调用 — 查询采购商计划、工厂信息</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="flow-row" style="background:rgba(96,165,250,0.04);">
|
|||
|
|
<div class="flow-row-header">
|
|||
|
|
<div class="flow-from" style="color:#34d399;">iloom 全部服务</div>
|
|||
|
|
<div class="flow-arrow blue"></div>
|
|||
|
|
<div class="flow-to" style="color:#60a5fa;">MySQL</div>
|
|||
|
|
<div class="flow-protocol"><span class="method" style="background:rgba(96,165,250,0.15);color:#60a5fa;">SQL</span></div>
|
|||
|
|
</div>
|
|||
|
|
<div class="flow-desc"><code>ilm_*</code> 表直接读写;<code>sys_user</code> 只读查询 (JOIN)</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="flow-row" style="background:rgba(96,165,250,0.04);">
|
|||
|
|
<div class="flow-row-header">
|
|||
|
|
<div class="flow-from" style="color:#fb7185;">muyu RPC 服务</div>
|
|||
|
|
<div class="flow-arrow blue"></div>
|
|||
|
|
<div class="flow-to" style="color:#60a5fa;">MySQL</div>
|
|||
|
|
<div class="flow-protocol"><span class="method" style="background:rgba(96,165,250,0.15);color:#60a5fa;">SQL</span></div>
|
|||
|
|
</div>
|
|||
|
|
<div class="flow-desc"><code>sys_*</code>, <code>inv_*</code>, <code>casbin_rule</code> 全权读写</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="flow-row" style="background:rgba(168,85,247,0.04);">
|
|||
|
|
<div class="flow-row-header">
|
|||
|
|
<div class="flow-from" style="color:#fb7185;">muyu RPC 服务</div>
|
|||
|
|
<div class="flow-arrow" style="visibility:hidden"></div>
|
|||
|
|
<div class="flow-to" style="color:#a855f7;">Redis / etcd</div>
|
|||
|
|
<div class="flow-protocol"><span class="method" style="background:rgba(168,85,247,0.15);color:#a855f7;">TCP</span></div>
|
|||
|
|
</div>
|
|||
|
|
<div class="flow-desc">Redis: 缓存 + 会话 | etcd: 服务注册发现</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- ════════════════════════════════════════ -->
|
|||
|
|
<!-- DETAIL PANEL -->
|
|||
|
|
<!-- ════════════════════════════════════════ -->
|
|||
|
|
<div class="overlay" id="overlay"></div>
|
|||
|
|
<div class="detail-panel" id="detailPanel">
|
|||
|
|
<button class="close-btn" id="closeBtn">✕</button>
|
|||
|
|
<div id="detailContent"></div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<script>
|
|||
|
|
// ── Service Detail Data ──
|
|||
|
|
const serviceData = {
|
|||
|
|
frontend: {
|
|||
|
|
title: 'React SPA (iloom Frontend)',
|
|||
|
|
subtitle: ':3015 · React 18 + TailwindCSS + Zustand',
|
|||
|
|
sections: [
|
|||
|
|
{ title: 'Tech Stack', content: '<div style="display:flex;gap:6px;flex-wrap:wrap"><span class="table-badge ilm">React 18.3</span><span class="table-badge ilm">Webpack 5</span><span class="table-badge ilm">TailwindCSS</span><span class="table-badge ilm">Zustand</span><span class="table-badge ilm">React Router v6</span><span class="table-badge ilm">Recharts</span><span class="table-badge ilm">Framer Motion</span></div>' },
|
|||
|
|
{ title: 'API 模块', content: '<ul class="endpoint-list"><li><span class="method post">AUTH</span> authApi — 登录/注册/刷新/用户信息</li><li><span class="method get">API</span> purchaserApi — 采购商全部接口</li><li><span class="method get">API</span> textileApi — 纺织厂全部接口</li><li><span class="method get">API</span> washingApi — 水洗厂全部接口</li><li><span class="method get">API</span> commonApi — 公司/成员/通知</li><li><span class="method ws">WS</span> WebSocket 实时推送</li></ul>' },
|
|||
|
|
{ title: '认证机制', content: '<div style="font-size:12px;color:#94a3b8;line-height:1.8">• Access Token 存内存<br>• Refresh Token 存 Cookie (credentials: include)<br>• 401 自动刷新 + 重试<br>• Token 过期触发 auth:expired 事件 → 跳转登录</div>' }
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
gateway: {
|
|||
|
|
title: 'API Gateway',
|
|||
|
|
subtitle: ':8080 · Gin + 反向代理',
|
|||
|
|
sections: [
|
|||
|
|
{ title: '中间件链', content: '<ul class="endpoint-list"><li>RequestID() — 请求追踪</li><li>Logger() — 请求日志</li><li>Recovery() — Panic 恢复</li><li>CORS() — 跨域 (ALLOW_ORIGINS)</li><li>RateLimit() — 限流 100 req/s</li><li>JWTAuth() — JWT 验证 + Claims 提取</li></ul>' },
|
|||
|
|
{ title: '路由规则 (JWT 验证后注入 Headers)', content: '<ul class="endpoint-list"><li><span class="method post">ANY</span>/api/v1/auth/* → auth-service:8081 <span style="color:#475569">(免 JWT)</span></li><li><span class="method post">ANY</span>/api/v1/common/* → auth-service:8081</li><li><span class="method post">ANY</span>/api/v1/purchaser/* → purchaser:8082 <span style="color:#475569">(role=purchaser)</span></li><li><span class="method post">ANY</span>/api/v1/textile/* → textile:8083 <span style="color:#475569">(role=textile)</span></li><li><span class="method post">ANY</span>/api/v1/washing/* → washing:8084 <span style="color:#475569">(role=washing)</span></li><li><span class="method ws">WS</span>/ws/v1/* → 对应服务 WebSocket</li></ul>' },
|
|||
|
|
{ title: '注入的 Headers', content: '<div style="font-size:12px;font-family:monospace;color:#a5b4fc;line-height:2">X-User-ID: {claims.UserID}<br>X-Company-ID: {claims.CompanyID}<br>X-User-Role: {claims.Role}<br>X-Is-Master: {claims.IsMaster}<br>X-Username: {claims.Username}</div>' }
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
auth: {
|
|||
|
|
title: 'Auth Service',
|
|||
|
|
subtitle: ':8081 · 认证 + 公司管理 + 成员管理 + 通知',
|
|||
|
|
sections: [
|
|||
|
|
{ title: 'Auth 端点', content: '<ul class="endpoint-list"><li><span class="method post">POST</span>/auth/login — gRPC→SystemRPC.Login()→签发JWT</li><li><span class="method post">POST</span>/auth/register — gRPC→CreateUser()→建公司→建成员</li><li><span class="method post">POST</span>/auth/refresh — 刷新 access_token</li><li><span class="method get">GET</span>/auth/me — GetWithCompany() 用户+公司信息</li><li><span class="method post">POST</span>/auth/logout — 登出</li></ul>' },
|
|||
|
|
{ title: 'Common 端点', content: '<ul class="endpoint-list"><li><span class="method get">GET</span>/common/companies — 公司关系列表</li><li><span class="method get">GET</span>/common/companies/:id — 公司详情</li><li><span class="method get">GET</span>/common/members — 公司成员列表</li><li><span class="method post">POST</span>/common/members — gRPC→CreateUser()+建成员</li><li><span class="method delete">DEL</span>/common/members/:id — 删除成员关系</li><li><span class="method get">GET</span>/common/notifications — 通知列表</li><li><span class="method patch">PATCH</span>/common/notifications/:id — 标记已读</li></ul>' },
|
|||
|
|
{ title: 'gRPC 调用', content: '<div style="display:flex;gap:6px;flex-wrap:wrap"><span class="flow-badge grpc">→ SystemRPC.Login()</span><span class="flow-badge grpc">→ SystemRPC.CreateUser()</span><span class="flow-badge grpc">→ SystemRPC.GetUser()</span></div>' },
|
|||
|
|
{ title: '数据库访问', content: '<div style="display:flex;gap:6px;flex-wrap:wrap"><span class="flow-badge read">读 sys_user</span><span class="flow-badge write">写 ilm_company</span><span class="flow-badge write">写 ilm_company_member</span><span class="flow-badge read">读 ilm_company_relationship</span><span class="flow-badge write">写 ilm_notification</span></div>' }
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
purchaser: {
|
|||
|
|
title: 'Purchaser Service',
|
|||
|
|
subtitle: ':8082 · 采购商:计划 + 产品 + 应付账款',
|
|||
|
|
sections: [
|
|||
|
|
{ title: '计划管理', content: '<ul class="endpoint-list"><li><span class="method get">GET</span>/plans — 计划列表</li><li><span class="method post">POST</span>/plans — 创建生产计划</li><li><span class="method get">GET</span>/plans/:id — 计划详情 (含工序/库存)</li><li><span class="method put">PUT</span>/plans/:id — 更新计划</li><li><span class="method delete">DEL</span>/plans/:id — 删除计划</li><li><span class="method post">POST</span>/plans/:id/factories — 分配工厂</li><li><span class="method get">GET</span>/plans/:id/steps — 工序步骤</li><li><span class="method get">GET</span>/plans/:id/inventory — 库存记录</li></ul>' },
|
|||
|
|
{ title: '产品管理', content: '<ul class="endpoint-list"><li><span class="method get">GET</span>/products — 产品列表</li><li><span class="method post">POST</span>/products — 创建产品 (gRPC→inv_product + ilm_product_ext)</li><li><span class="method put">PUT</span>/products/:id — 更新产品</li><li><span class="method post">POST</span>/products/:id/inbound — 入库</li><li><span class="method post">POST</span>/products/:id/outbound — 出库</li><li><span class="method put">PUT</span>/products/:id/price — 更新价格</li><li><span class="method get">GET</span>/products/:id/price-history — 价格历史</li></ul>' },
|
|||
|
|
{ title: '其他', content: '<ul class="endpoint-list"><li><span class="method get">GET</span>/dashboard/stats — 仪表盘统计</li><li><span class="method get">GET</span>/factories — 工厂列表</li><li><span class="method get">GET</span>/accounts-payable — 应付账款</li><li><span class="method post">POST</span>/accounts-payable/:id/pay — 付款</li><li><span class="method post">POST</span>/washing-plans — 创建水洗计划</li><li><span class="method ws">WS</span>/ws/v1/purchaser — 实时推送</li></ul>' },
|
|||
|
|
{ title: 'gRPC 调用', content: '<div style="display:flex;gap:6px;flex-wrap:wrap"><span class="flow-badge grpc">→ InventoryRPC.CreateProduct()</span><span class="flow-badge grpc">→ InventoryRPC.GetProduct()</span><span class="flow-badge grpc">→ InventoryRPC.ListProduct()</span></div>' },
|
|||
|
|
{ title: '数据库表', content: '<div style="display:flex;gap:6px;flex-wrap:wrap"><span class="table-badge ilm">ilm_production_plan</span><span class="table-badge ilm">ilm_plan_factory</span><span class="table-badge ilm">ilm_yarn_ratio</span><span class="table-badge ilm">ilm_process_step</span><span class="table-badge ilm">ilm_product_ext</span><span class="table-badge ilm">ilm_product_inventory</span><span class="table-badge ilm">ilm_product_outbound</span><span class="table-badge ilm">ilm_product_price_history</span><span class="table-badge ilm">ilm_accounts_payable</span><span class="table-badge ilm">ilm_ap_item</span><span class="table-badge ilm">ilm_washing_plan</span></div>' }
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
textile: {
|
|||
|
|
title: 'Textile Service',
|
|||
|
|
subtitle: ':8083 · 纺织厂:工序 + 纱线 + 库存 + 收款',
|
|||
|
|
sections: [
|
|||
|
|
{ title: 'API 端点', content: '<ul class="endpoint-list"><li><span class="method get">GET</span>/dashboard/stats — 仪表盘</li><li><span class="method get">GET</span>/plans — 生产计划 (只读)</li><li><span class="method get">GET</span>/plans/:id — 计划详情</li><li><span class="method post">POST</span>/plans/:id/steps/:stepId/complete — 完成工序</li><li><span class="method post">POST</span>/plans/:id/steps/:stepId/reject — 驳回工序</li><li><span class="method post">POST</span>/plans/:id/inventory — 入库记录</li><li><span class="method get">GET</span>/yarn-stock — 纱线库存</li><li><span class="method post">POST</span>/yarn-stock — 创建纱线</li><li><span class="method post">POST</span>/yarn-stock/:id/records — 纱线出入库</li><li><span class="method get">GET</span>/payments — 收款列表</li><li><span class="method post">POST</span>/payments/:id/confirm — 确认收款</li><li><span class="method ws">WS</span>/ws/v1/textile — 实时推送</li></ul>' },
|
|||
|
|
{ title: '服务间调用', content: '<div style="display:flex;gap:6px;flex-wrap:wrap"><span class="flow-badge http">→ Purchaser :8082 (HTTP)</span></div><div style="font-size:11px;color:#64748b;margin-top:4px">查询采购商计划、工厂信息</div>' },
|
|||
|
|
{ title: '数据库表', content: '<div style="display:flex;gap:6px;flex-wrap:wrap"><span class="table-badge ilm">ilm_production_plan</span><span class="table-badge ilm">ilm_process_step</span><span class="table-badge ilm">ilm_inventory_record</span><span class="table-badge ilm">ilm_warehouse</span><span class="table-badge ilm">ilm_yarn_stock</span><span class="table-badge ilm">ilm_yarn_stock_record</span><span class="table-badge ilm">ilm_payment</span></div>' }
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
washing: {
|
|||
|
|
title: 'Washing Service',
|
|||
|
|
subtitle: ':8084 · 水洗厂:水洗计划 + 成品 + 出库 + 收款',
|
|||
|
|
sections: [
|
|||
|
|
{ title: 'API 端点', content: '<ul class="endpoint-list"><li><span class="method get">GET</span>/dashboard/stats — 仪表盘</li><li><span class="method get">GET</span>/plans — 水洗计划列表</li><li><span class="method get">GET</span>/plans/pending — 待处理计划</li><li><span class="method get">GET</span>/plans/completed — 已完成计划</li><li><span class="method get">GET</span>/plans/:id — 计划详情</li><li><span class="method patch">PATCH</span>/plans/:id/status — 更新状态</li><li><span class="method post">POST</span>/plans/:id/complete — 标记完成</li><li><span class="method get">GET</span>/finished-products — 成品列表</li><li><span class="method post">POST</span>/finished-products — 创建成品</li><li><span class="method post">POST</span>/finished-products/:id/outbound — 成品出库</li><li><span class="method get">GET</span>/payments — 收款列表</li><li><span class="method post">POST</span>/payments/:id/confirm — 确认收款</li><li><span class="method ws">WS</span>/ws/v1/washing — 实时推送</li></ul>' },
|
|||
|
|
{ title: '服务间调用', content: '<div style="display:flex;gap:6px;flex-wrap:wrap"><span class="flow-badge http">→ Purchaser :8082 (HTTP)</span></div><div style="font-size:11px;color:#64748b;margin-top:4px">查询采购商计划、工厂信息</div>' },
|
|||
|
|
{ title: '数据库表', content: '<div style="display:flex;gap:6px;flex-wrap:wrap"><span class="table-badge ilm">ilm_washing_plan</span><span class="table-badge ilm">ilm_washing_step</span><span class="table-badge ilm">ilm_washing_completion</span><span class="table-badge ilm">ilm_finished_product</span><span class="table-badge ilm">ilm_finished_product_inventory</span><span class="table-badge ilm">ilm_payment</span></div>' }
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
'system-rpc': {
|
|||
|
|
title: 'System RPC (muyu-apiserver)',
|
|||
|
|
subtitle: ':9001 · go-zero gRPC · 用户/角色/权限管理',
|
|||
|
|
sections: [
|
|||
|
|
{ title: 'gRPC 方法', content: '<ul class="endpoint-list"><li><span class="method grpc">RPC</span>Login(username, password, ip) → LoginResp</li><li><span class="method grpc">RPC</span>CreateUser(username, password, realName, phone, roleID) → IdResp</li><li><span class="method grpc">RPC</span>GetUser(userID) → UserInfo</li><li><span class="method grpc">RPC</span>UpdateUser / DeleteUser / ListUser</li><li><span class="method grpc">RPC</span>UpdateUserStatus(userID, status)</li><li><span class="method grpc">RPC</span>CreateRole / GetRole / ListRole / DeleteRole</li><li><span class="method grpc">RPC</span>SetRolePermissions(roleID, menuIDs)</li><li><span class="method grpc">RPC</span>CreateMenu / GetMenu / ListMenu / DeleteMenu</li><li><span class="method grpc">RPC</span>CreateOperationLog / ListOperationLog</li></ul>' },
|
|||
|
|
{ title: '调用者', content: '<div style="display:flex;gap:6px;flex-wrap:wrap"><span class="flow-badge grpc">← iloom Auth Service</span><span class="flow-badge grpc">← muyu Gateway :8888</span></div>' },
|
|||
|
|
{ title: '数据库表', content: '<div style="display:flex;gap:6px;flex-wrap:wrap"><span class="table-badge sys">sys_user</span><span class="table-badge sys">sys_role</span><span class="table-badge sys">sys_user_role</span><span class="table-badge sys">sys_menu</span><span class="table-badge sys">sys_role_menu</span><span class="table-badge sys">sys_operation_log</span><span class="table-badge sys">sys_config</span><span class="table-badge sys">casbin_rule</span></div>' }
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
'inventory-rpc': {
|
|||
|
|
title: 'Inventory RPC (muyu-apiserver)',
|
|||
|
|
subtitle: ':9002 · go-zero gRPC · 产品/库存管理',
|
|||
|
|
sections: [
|
|||
|
|
{ title: 'gRPC 方法', content: '<ul class="endpoint-list"><li><span class="method grpc">RPC</span>CreateProduct(name, sku, ...) → IdResp</li><li><span class="method grpc">RPC</span>GetProduct(productID) → ProductInfo</li><li><span class="method grpc">RPC</span>ListProduct(page, pageSize, ...) → ListProductResp</li><li><span class="method grpc">RPC</span>UpdateProduct / DeleteProduct</li><li><span class="method grpc">RPC</span>ImportProducts(batch) → ImportResp</li><li><span class="method grpc">RPC</span>GetStockSummary / GetStockGroup</li><li><span class="method grpc">RPC</span>CreateStockCheck / ConfirmStockCheck</li><li><span class="method grpc">RPC</span>CreateStockAdjust / ApproveStockAdjust</li></ul>' },
|
|||
|
|
{ title: '调用者', content: '<div style="display:flex;gap:6px;flex-wrap:wrap"><span class="flow-badge grpc">← iloom Purchaser Service</span><span class="flow-badge grpc">← muyu Gateway :8888</span></div>' },
|
|||
|
|
{ title: '数据库表', content: '<div style="display:flex;gap:6px;flex-wrap:wrap"><span class="table-badge inv">inv_product</span><span class="table-badge inv">inv_stock_check</span><span class="table-badge inv">inv_stock_adjust</span></div>' }
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
'muyu-gateway': {
|
|||
|
|
title: 'muyu Gateway',
|
|||
|
|
subtitle: ':8888 · go-zero REST API · 管理后台接口',
|
|||
|
|
sections: [
|
|||
|
|
{ title: '端点 (JWT + Casbin RBAC)', content: '<ul class="endpoint-list"><li><span class="method post">POST</span>/api/v1/auth/login — 登录</li><li><span class="method get">GET</span>/api/v1/auth/info — 用户信息</li><li><span class="method get">GET</span>/api/v1/system/users — 用户管理</li><li><span class="method get">GET</span>/api/v1/system/roles — 角色管理</li><li><span class="method get">GET</span>/api/v1/system/menus — 菜单管理</li><li><span class="method get">GET</span>/api/v1/inventory/products — 产品管理</li><li><span class="method get">GET</span>/api/v1/inventory/stock-check — 盘点</li><li><span class="method get">GET</span>/api/v1/inventory/stock-adjust — 调整</li></ul>' },
|
|||
|
|
{ title: '下游 gRPC', content: '<div style="display:flex;gap:6px;flex-wrap:wrap"><span class="flow-badge grpc">→ System RPC :9001</span><span class="flow-badge grpc">→ Inventory RPC :9002</span></div>' }
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// ── Detail Panel Logic ──
|
|||
|
|
const panel = document.getElementById('detailPanel');
|
|||
|
|
const overlay = document.getElementById('overlay');
|
|||
|
|
const detailContent = document.getElementById('detailContent');
|
|||
|
|
|
|||
|
|
function openDetail(key) {
|
|||
|
|
const data = serviceData[key];
|
|||
|
|
if (!data) return;
|
|||
|
|
|
|||
|
|
let html = `<div class="detail-title">${data.title}</div><div class="detail-subtitle">${data.subtitle}</div>`;
|
|||
|
|
data.sections.forEach(s => {
|
|||
|
|
html += `<div class="detail-section"><h3>${s.title}</h3>${s.content}</div>`;
|
|||
|
|
});
|
|||
|
|
detailContent.innerHTML = html;
|
|||
|
|
panel.classList.add('open');
|
|||
|
|
overlay.classList.add('show');
|
|||
|
|
|
|||
|
|
document.querySelectorAll('.service-card').forEach(c => c.classList.remove('active'));
|
|||
|
|
const activeCard = document.querySelector(`[data-service="${key}"]`);
|
|||
|
|
if (activeCard) activeCard.classList.add('active');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function closeDetail() {
|
|||
|
|
panel.classList.remove('open');
|
|||
|
|
overlay.classList.remove('show');
|
|||
|
|
document.querySelectorAll('.service-card').forEach(c => c.classList.remove('active'));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
document.getElementById('closeBtn').addEventListener('click', closeDetail);
|
|||
|
|
overlay.addEventListener('click', closeDetail);
|
|||
|
|
|
|||
|
|
// Swipe down to close on mobile
|
|||
|
|
let touchStartY = 0;
|
|||
|
|
panel.addEventListener('touchstart', (e) => {
|
|||
|
|
if (panel.scrollTop <= 0) {
|
|||
|
|
touchStartY = e.touches[0].clientY;
|
|||
|
|
}
|
|||
|
|
}, { passive: true });
|
|||
|
|
panel.addEventListener('touchmove', (e) => {
|
|||
|
|
if (panel.scrollTop <= 0 && touchStartY > 0) {
|
|||
|
|
const dy = e.touches[0].clientY - touchStartY;
|
|||
|
|
if (dy > 80) {
|
|||
|
|
closeDetail();
|
|||
|
|
touchStartY = 0;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}, { passive: true });
|
|||
|
|
panel.addEventListener('touchend', () => { touchStartY = 0; }, { passive: true });
|
|||
|
|
|
|||
|
|
document.querySelectorAll('.service-card[data-service]').forEach(card => {
|
|||
|
|
card.addEventListener('click', () => openDetail(card.dataset.service));
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// ── Draw SVG Connections ──
|
|||
|
|
function drawConnections() {
|
|||
|
|
const svg = document.getElementById('connSvg');
|
|||
|
|
if (window.innerWidth <= 768) {
|
|||
|
|
svg.innerHTML = '';
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const canvas = document.getElementById('canvas');
|
|||
|
|
const canvasRect = canvas.getBoundingClientRect();
|
|||
|
|
|
|||
|
|
svg.setAttribute('width', canvas.scrollWidth);
|
|||
|
|
svg.setAttribute('height', canvas.scrollHeight);
|
|||
|
|
svg.innerHTML = '';
|
|||
|
|
|
|||
|
|
const defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
|
|||
|
|
|
|||
|
|
// Gradient: green
|
|||
|
|
const gGreen = document.createElementNS('http://www.w3.org/2000/svg', 'linearGradient');
|
|||
|
|
gGreen.setAttribute('id', 'gGreen'); gGreen.setAttribute('x1','0%'); gGreen.setAttribute('y1','0%'); gGreen.setAttribute('x2','100%'); gGreen.setAttribute('y2','0%');
|
|||
|
|
gGreen.innerHTML = '<stop offset="0%" style="stop-color:#34d399"/><stop offset="100%" style="stop-color:#6ee7b7"/>';
|
|||
|
|
defs.appendChild(gGreen);
|
|||
|
|
|
|||
|
|
// Gradient: purple (gRPC)
|
|||
|
|
const gPurple = document.createElementNS('http://www.w3.org/2000/svg', 'linearGradient');
|
|||
|
|
gPurple.setAttribute('id', 'gPurple'); gPurple.setAttribute('x1','0%'); gPurple.setAttribute('y1','0%'); gPurple.setAttribute('x2','100%'); gPurple.setAttribute('y2','0%');
|
|||
|
|
gPurple.innerHTML = '<stop offset="0%" style="stop-color:#818cf8"/><stop offset="100%" style="stop-color:#a855f7"/>';
|
|||
|
|
defs.appendChild(gPurple);
|
|||
|
|
|
|||
|
|
// Gradient: blue (DB)
|
|||
|
|
const gBlue = document.createElementNS('http://www.w3.org/2000/svg', 'linearGradient');
|
|||
|
|
gBlue.setAttribute('id', 'gBlue'); gBlue.setAttribute('x1','0%'); gBlue.setAttribute('y1','0%'); gBlue.setAttribute('x2','0%'); gBlue.setAttribute('y2','100%');
|
|||
|
|
gBlue.innerHTML = '<stop offset="0%" style="stop-color:#60a5fa"/><stop offset="100%" style="stop-color:#3b82f6"/>';
|
|||
|
|
defs.appendChild(gBlue);
|
|||
|
|
|
|||
|
|
// Gradient: yellow
|
|||
|
|
const gYellow = document.createElementNS('http://www.w3.org/2000/svg', 'linearGradient');
|
|||
|
|
gYellow.setAttribute('id', 'gYellow'); gYellow.setAttribute('x1','0%'); gYellow.setAttribute('y1','0%'); gYellow.setAttribute('x2','100%'); gYellow.setAttribute('y2','0%');
|
|||
|
|
gYellow.innerHTML = '<stop offset="0%" style="stop-color:#fbbf24"/><stop offset="100%" style="stop-color:#fcd34d"/>';
|
|||
|
|
defs.appendChild(gYellow);
|
|||
|
|
|
|||
|
|
svg.appendChild(defs);
|
|||
|
|
|
|||
|
|
function getCenter(id) {
|
|||
|
|
const el = document.getElementById(id);
|
|||
|
|
if (!el) return null;
|
|||
|
|
const r = el.getBoundingClientRect();
|
|||
|
|
return {
|
|||
|
|
x: r.left + r.width / 2 - canvasRect.left,
|
|||
|
|
y: r.top + r.height / 2 - canvasRect.top,
|
|||
|
|
top: r.top - canvasRect.top,
|
|||
|
|
bottom: r.bottom - canvasRect.top,
|
|||
|
|
left: r.left - canvasRect.left,
|
|||
|
|
right: r.right - canvasRect.left,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function drawCurve(from, to, stroke, dashArray, fromSide, toSide) {
|
|||
|
|
const a = getCenter(from);
|
|||
|
|
const b = getCenter(to);
|
|||
|
|
if (!a || !b) return;
|
|||
|
|
|
|||
|
|
let x1, y1, x2, y2;
|
|||
|
|
if (fromSide === 'right') { x1 = a.right; y1 = (a.top + a.bottom) / 2; }
|
|||
|
|
else if (fromSide === 'bottom') { x1 = (a.left + a.right) / 2; y1 = a.bottom; }
|
|||
|
|
else { x1 = (a.left + a.right) / 2; y1 = (a.top + a.bottom) / 2; }
|
|||
|
|
|
|||
|
|
if (toSide === 'left') { x2 = b.left; y2 = (b.top + b.bottom) / 2; }
|
|||
|
|
else if (toSide === 'top') { x2 = (b.left + b.right) / 2; y2 = b.top; }
|
|||
|
|
else { x2 = (b.left + b.right) / 2; y2 = (b.top + b.bottom) / 2; }
|
|||
|
|
|
|||
|
|
const dx = x2 - x1;
|
|||
|
|
const dy = y2 - y1;
|
|||
|
|
let cx1, cy1, cx2, cy2;
|
|||
|
|
|
|||
|
|
if (fromSide === 'right' && toSide === 'left') {
|
|||
|
|
cx1 = x1 + dx * 0.4; cy1 = y1;
|
|||
|
|
cx2 = x2 - dx * 0.4; cy2 = y2;
|
|||
|
|
} else if (fromSide === 'bottom' && toSide === 'top') {
|
|||
|
|
cx1 = x1; cy1 = y1 + dy * 0.4;
|
|||
|
|
cx2 = x2; cy2 = y2 - dy * 0.4;
|
|||
|
|
} else {
|
|||
|
|
cx1 = x1 + dx * 0.3; cy1 = y1;
|
|||
|
|
cx2 = x2 - dx * 0.3; cy2 = y2;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|||
|
|
path.setAttribute('d', `M${x1},${y1} C${cx1},${cy1} ${cx2},${cy2} ${x2},${y2}`);
|
|||
|
|
path.setAttribute('stroke', stroke);
|
|||
|
|
path.setAttribute('class', 'conn-line');
|
|||
|
|
if (dashArray) {
|
|||
|
|
path.setAttribute('stroke-dasharray', dashArray);
|
|||
|
|
path.classList.add('animated-flow');
|
|||
|
|
}
|
|||
|
|
svg.appendChild(path);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Frontend → Gateway
|
|||
|
|
drawCurve('card-frontend', 'card-gateway', 'url(#gYellow)', '6 4', 'right', 'left');
|
|||
|
|
|
|||
|
|
// Gateway → Services
|
|||
|
|
drawCurve('card-gateway', 'card-auth', 'url(#gGreen)', '6 4', 'bottom', 'top');
|
|||
|
|
drawCurve('card-gateway', 'card-purchaser', 'url(#gGreen)', '6 4', 'bottom', 'top');
|
|||
|
|
drawCurve('card-gateway', 'card-textile', 'url(#gGreen)', '6 4', 'bottom', 'top');
|
|||
|
|
drawCurve('card-gateway', 'card-washing', 'url(#gGreen)', '6 4', 'bottom', 'top');
|
|||
|
|
|
|||
|
|
// Auth → System RPC (gRPC)
|
|||
|
|
drawCurve('card-auth', 'card-system-rpc', 'url(#gPurple)', null, 'right', 'left');
|
|||
|
|
|
|||
|
|
// Purchaser → Inventory RPC (gRPC)
|
|||
|
|
drawCurve('card-purchaser', 'card-inv-rpc', 'url(#gPurple)', null, 'right', 'left');
|
|||
|
|
|
|||
|
|
// Textile → Purchaser (internal HTTP)
|
|||
|
|
drawCurve('card-textile', 'card-purchaser', 'url(#gYellow)', '4 3', 'right', 'left');
|
|||
|
|
|
|||
|
|
// Washing → Purchaser (internal HTTP)
|
|||
|
|
drawCurve('card-washing', 'card-purchaser', 'url(#gYellow)', '4 3', 'right', 'left');
|
|||
|
|
|
|||
|
|
// Services → DB zone
|
|||
|
|
drawCurve('card-auth', 'zone-db', 'url(#gBlue)', '4 3', 'bottom', 'top');
|
|||
|
|
drawCurve('card-purchaser', 'zone-db', 'url(#gBlue)', '4 3', 'bottom', 'top');
|
|||
|
|
drawCurve('card-textile', 'zone-db', 'url(#gBlue)', '4 3', 'bottom', 'top');
|
|||
|
|
drawCurve('card-washing', 'zone-db', 'url(#gBlue)', '4 3', 'bottom', 'top');
|
|||
|
|
drawCurve('card-system-rpc', 'zone-db', 'url(#gBlue)', '4 3', 'bottom', 'top');
|
|||
|
|
drawCurve('card-inv-rpc', 'zone-db', 'url(#gBlue)', '4 3', 'bottom', 'top');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Run after layout
|
|||
|
|
window.addEventListener('load', () => { setTimeout(drawConnections, 100); });
|
|||
|
|
window.addEventListener('resize', drawConnections);
|
|||
|
|
</script>
|
|||
|
|
</body>
|
|||
|
|
</html>
|