feat: 修改员工APP咨询业务流程图

This commit is contained in:
冯普
2026-04-17 08:35:38 +08:00
parent 104ddd47cf
commit da61152de7
+294 -274
View File
@@ -3,292 +3,312 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>健康咨询 - 员工咨询流程图 | 健康长庆原型</title>
<title>健康咨询 - 员工咨询业务流程图</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: #f5f7fa;
font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif;
color: #1a1a1a;
padding: 32px;
min-width: 1100px;
}
.page-header {
text-align: center; margin-bottom: 32px;
}
.page-header h1 { font-size: 24px; font-weight: 700; color: #1a1a1a; }
.page-header p { font-size: 14px; color: #8c8c8c; margin-top: 6px; }
.legend {
display: flex; justify-content: center; gap: 24px; margin-bottom: 28px; flex-wrap: wrap;
}
.legend-item { display: flex; align-items: center; gap: 6px; font-size: 13px; color: #595959; }
.legend-dot { width: 14px; height: 14px; border-radius: 4px; flex-shrink: 0; }
/* 流程图容器 */
.flowchart-wrap {
display: flex; gap: 28px; justify-content: center; align-items: flex-start; flex-wrap: nowrap;
}
.flow-column {
background: #fff; border-radius: 16px; padding: 24px 20px 28px;
box-shadow: 0 2px 12px rgba(0,0,0,0.06); min-width: 320px; max-width: 360px; flex: 1;
}
.flow-column-title {
font-size: 16px; font-weight: 700; text-align: center; padding: 8px 0 16px;
border-bottom: 2px solid #f0f0f0; margin-bottom: 20px;
}
/* 节点样式 */
.node {
position: relative; border-radius: 10px; padding: 10px 14px; margin: 0 auto;
text-align: center; font-size: 13px; font-weight: 500; width: 90%;
transition: transform 0.15s;
}
.node:hover { transform: scale(1.03); }
.node-entry { background: #e6f4ff; border: 1.5px solid #91caff; color: #0958d9; }
.node-page { background: #f6ffed; border: 1.5px solid #b7eb8f; color: #389e0d; }
.node-action { background: #fff7e6; border: 1.5px solid #ffd591; color: #d46b08; }
.node-state { border-radius: 20px; padding: 7px 14px; font-size: 12px; }
.node-state.s-progress { background: #e6f4ff; border: 1.5px solid #91caff; color: #1677ff; }
.node-state.s-done { background: #f6ffed; border: 1.5px solid #b7eb8f; color: #52c41a; }
.node-state.s-timeout { background: #fff7e6; border: 1.5px solid #ffd591; color: #fa8c16; }
.node-state.s-cancel { background: #f5f5f5; border: 1.5px solid #d9d9d9; color: #8c8c8c; }
.node-state.s-reject { background: #fff2f0; border: 1.5px solid #ffccc7; color: #ff4d4f; }
.node-state.s-confirm { background: #f9f0ff; border: 1.5px solid #d3adf7; color: #722ed1; }
.node-state.s-wait { background: #fff7e6; border: 1.5px solid #ffd591; color: #fa8c16; }
.node-dialog { background: #fff0f6; border: 1.5px dashed #ffadd2; color: #c41d7f; border-radius: 10px; }
.node-end { background: #f5f5f5; border: 1.5px solid #d9d9d9; color: #595959; border-radius: 20px; }
.file-tag {
display: block; font-size: 10px; color: #8c8c8c; font-weight: 400; margin-top: 3px;
word-break: break-all;
}
/* 箭头连接线 */
.arrow-down {
display: flex; flex-direction: column; align-items: center; padding: 6px 0;
}
.arrow-down .line { width: 2px; height: 18px; background: #d9d9d9; }
.arrow-down .head { width: 0; height: 0; border-left: 5px solid transparent; border-right: 5px solid transparent; border-top: 6px solid #d9d9d9; }
.arrow-label {
font-size: 10px; color: #8c8c8c; background: #fff; padding: 0 4px; margin: -2px 0;
}
/* 分支容器 */
.branch-wrap {
display: flex; gap: 10px; justify-content: center; margin: 0 -6px;
}
.branch { flex: 1; display: flex; flex-direction: column; align-items: center; }
.branch .node { width: 100%; font-size: 12px; padding: 8px 6px; }
.branch .file-tag { font-size: 9px; }
/* 分支线 */
.branch-line {
display: flex; align-items: flex-start; justify-content: center; height: 24px; position: relative; width: 100%;
}
.branch-line::before {
content: ''; position: absolute; top: 0; left: 25%; right: 25%; height: 2px; background: #d9d9d9;
}
.branch-line .bl { position: absolute; top: 0; width: 2px; height: 24px; background: #d9d9d9; }
.branch-line .bl-l { left: 25%; }
.branch-line .bl-r { right: 25%; }
.branch-line .bl-c { left: 50%; transform: translateX(-50%); }
.branch-line-3::before { left: 16.6%; right: 16.6%; }
.branch-line-3 .bl-l { left: 16.6%; }
.branch-line-3 .bl-r { right: 16.6%; }
.branch-line-5::before { left: 10%; right: 10%; }
.branch-line-5 .bl-1 { left: 10%; }
.branch-line-5 .bl-2 { left: 30%; }
.branch-line-5 .bl-3 { left: 50%; transform: translateX(-50%); }
.branch-line-5 .bl-4 { right: 30%; }
.branch-line-5 .bl-5 { right: 10%; }
/* 状态分支 - 更紧凑 */
.state-branches { display: flex; gap: 6px; justify-content: center; flex-wrap: wrap; margin: 0 -4px; }
.state-branches .node-state { flex-shrink: 0; }
.section-label {
font-size: 11px; color: #8c8c8c; text-align: center; margin: 12px 0 6px;
border-top: 1px dashed #e8e8e8; padding-top: 10px;
}
/* 起始大节点 */
.node-start {
background: linear-gradient(135deg, #1677ff, #0958d9); color: #fff;
border: none; font-size: 15px; font-weight: 700; padding: 14px; border-radius: 12px;
width: 60%; margin: 0 auto;
}
@media (max-width: 1100px) {
.flowchart-wrap { flex-wrap: wrap; }
.flow-column { max-width: 100%; min-width: 0; }
}
*{margin:0;padding:0;box-sizing:border-box}
body{background:#f5f7fa;font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Helvetica Neue',sans-serif;color:#1a1a1a;padding:40px 20px}
.page-header{text-align:center;margin-bottom:24px}
.page-header h1{font-size:24px;font-weight:700}
.page-header p{font-size:13px;color:#8c8c8c;margin-top:6px}
.legend{display:flex;justify-content:center;gap:24px;margin-bottom:24px;flex-wrap:wrap}
.legend-item{display:flex;align-items:center;gap:6px;font-size:12px;color:#595959}
.legend-shape{width:32px;height:20px;flex-shrink:0}
.canvas-wrap{overflow-x:auto;padding-bottom:40px}
svg text{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Helvetica Neue',sans-serif}
</style>
</head>
<body>
<div class="page-header">
<h1>员工健康咨询 — 完整业务流程图</h1>
<p>覆盖咨询专家图文 / 视频)与咨询小助手两大路径,标注页面文件与状态流转</p>
<h1>员工健康咨询 — 业务流程图</h1>
<p>涵盖找专家图文咨询、视频咨询、咨询小助手、我的咨询五条业务线</p>
</div>
<div class="legend">
<div class="legend-item"><div class="legend-dot" style="background:#e6f4ff;border:1px solid #91caff"></div>入口 / 导航页</div>
<div class="legend-item"><div class="legend-dot" style="background:#f6ffed;border:1px solid #b7eb8f"></div>功能页面</div>
<div class="legend-item"><div class="legend-dot" style="background:#fff7e6;border:1px solid #ffd591"></div>操作 / 选择</div>
<div class="legend-item"><div class="legend-dot" style="background:#fff0f6;border:1px dashed #ffadd2"></div>弹窗 / 对话框</div>
<div class="legend-item"><div class="legend-dot" style="border-radius:10px;background:#f5f5f5;border:1px solid #d9d9d9"></div>终态</div>
<div class="legend-item"><div class="legend-dot" style="border-radius:10px;background:#e6f4ff;border:1px solid #91caff"></div>进行中</div>
<div class="legend-item"><div class="legend-dot" style="border-radius:10px;background:#f6ffed;border:1px solid #b7eb8f"></div>已完成</div>
<div class="legend-item"><div class="legend-dot" style="border-radius:10px;background:#fff7e6;border:1px solid #ffd591"></div>已超时/待开始</div>
<div class="legend-item"><div class="legend-dot" style="border-radius:10px;background:#fff2f0;border:1px solid #ffccc7"></div>已拒绝</div>
<div class="legend-item"><div class="legend-dot" style="border-radius:10px;background:#f9f0ff;border:1px solid #d3adf7"></div>待确认</div>
</div>
<!-- 起始节点 -->
<div style="text-align:center;margin-bottom:8px;">
<div class="node node-start">健康咨询首页<span class="file-tag" style="color:rgba(255,255,255,.7)">index.html</span></div>
</div>
<div class="arrow-down"><div class="line"></div><div class="head"></div></div>
<!-- 三列流程 -->
<div class="flowchart-wrap">
<!-- ========== 图文咨询 ========== -->
<div class="flow-column">
<div class="flow-column-title" style="color:#0958d9">📝 咨询专家 — 图文咨询</div>
<div class="node node-entry">找专家<span class="file-tag">find-expert.html</span></div>
<div class="arrow-down"><div class="line"></div><div class="arrow-label">按医院/科室/疾病</div><div class="line"></div><div class="head"></div></div>
<div class="node node-page">专家列表<span class="file-tag">expert-list.html</span></div>
<div class="arrow-down"><div class="line"></div><div class="head"></div></div>
<div class="node node-page">专家详情<span class="file-tag">expert-profile.html</span></div>
<div class="arrow-down"><div class="line"></div><div class="arrow-label">点击「图文咨询」</div><div class="line"></div><div class="head"></div></div>
<div class="node node-action">选择咨询人 &amp; 健康档案<span class="file-tag">select-person.html</span></div>
<div class="arrow-down"><div class="line"></div><div class="head"></div></div>
<div class="node node-state s-progress">进行中 — 图文聊天<span class="file-tag">text-chat.html</span></div>
<div class="section-label">咨询结束后的状态分支</div>
<div class="branch-line"><div class="bl bl-l"></div><div class="bl bl-c"></div><div class="bl bl-r"></div></div>
<div class="branch-wrap">
<div class="branch">
<div class="node node-state s-timeout">已超时<span class="file-tag">record-detail-timeout.html</span></div>
<div class="arrow-down"><div class="line"></div><div class="head"></div></div>
<div class="node node-end">再次咨询</div>
</div>
<div class="branch">
<div class="node node-state s-done">已完成·未评价<span class="file-tag">record-detail-unrated.html</span></div>
<div class="arrow-down"><div class="line"></div><div class="arrow-label">评价</div><div class="line"></div><div class="head"></div></div>
<div class="node node-dialog">评价医生<span class="file-tag">rate-doctor.html</span></div>
<div class="arrow-down"><div class="line"></div><div class="head"></div></div>
<div class="node node-state s-done">已完成·已评价<span class="file-tag">record-detail-rated.html</span></div>
</div>
<div class="branch">
<div class="node node-dialog">结束咨询确认<span class="file-tag">end-consult-dialog.html</span></div>
<div class="arrow-down"><div class="line"></div><div class="arrow-label">确认结束</div><div class="line"></div><div class="head"></div></div>
<div class="node node-dialog">评价医生<span class="file-tag">rate-doctor.html</span></div>
</div>
</div>
<div class="legend-item">
<svg class="legend-shape" viewBox="0 0 32 20"><rect x="1" y="1" width="30" height="18" rx="9" fill="#e8f4ff" stroke="#1677ff" stroke-width="1.5"/></svg>起止框
</div>
<!-- ========== 视频咨询 ========== -->
<div class="flow-column">
<div class="flow-column-title" style="color:#722ed1">🎥 咨询专家 — 视频咨询</div>
<div class="node node-entry">找专家<span class="file-tag">find-expert.html</span></div>
<div class="arrow-down"><div class="line"></div><div class="arrow-label">按医院/科室/疾病</div><div class="line"></div><div class="head"></div></div>
<div class="node node-page">专家列表<span class="file-tag">expert-list.html</span></div>
<div class="arrow-down"><div class="line"></div><div class="head"></div></div>
<div class="node node-page">专家详情<span class="file-tag">expert-profile.html</span></div>
<div class="arrow-down"><div class="line"></div><div class="arrow-label">点击「视频咨询」</div><div class="line"></div><div class="head"></div></div>
<div class="node node-action">预约视频咨询<span class="file-tag">video-booking.html</span></div>
<div class="arrow-down"><div class="line"></div><div class="arrow-label">选择日期时段</div><div class="line"></div><div class="head"></div></div>
<div class="node node-action">选择咨询人 &amp; 健康档案<span class="file-tag">select-person.html</span></div>
<div class="arrow-down"><div class="line"></div><div class="head"></div></div>
<div class="node node-page">预约成功<span class="file-tag">video-success.html</span></div>
<div class="section-label">视频咨询状态流转</div>
<div class="arrow-down"><div class="line"></div><div class="head"></div></div>
<div class="node node-state s-confirm">待确认<span class="file-tag">video-detail-confirming.html</span></div>
<div class="arrow-down"><div class="line"></div><div class="arrow-label">医生确认</div><div class="line"></div><div class="head"></div></div>
<div class="node node-state s-wait">待开始<span class="file-tag">video-detail-pending.html</span></div>
<div class="arrow-down"><div class="line"></div><div class="arrow-label">进入视频</div><div class="line"></div><div class="head"></div></div>
<div class="node node-state s-done">已完成<span class="file-tag">video-detail-done.html</span></div>
<div class="section-label">异常状态分支</div>
<div class="branch-line"><div class="bl bl-l"></div><div class="bl bl-r"></div></div>
<div class="branch-wrap">
<div class="branch">
<div class="node node-state s-cancel">已取消<span class="file-tag">video-detail-cancelled.html</span></div>
<div class="arrow-down"><div class="line"></div><div class="head"></div></div>
<div class="node node-end">重新预约</div>
</div>
<div class="branch">
<div class="node node-state s-reject">已拒绝<span class="file-tag">video-detail-rejected.html</span></div>
<div class="arrow-down"><div class="line"></div><div class="head"></div></div>
<div class="node node-end">重新预约</div>
</div>
</div>
<div class="legend-item">
<svg class="legend-shape" viewBox="0 0 32 20"><rect x="1" y="1" width="30" height="18" rx="3" fill="#fff" stroke="#333" stroke-width="1.5"/></svg>处理框
</div>
<!-- ========== 小助手咨询 ========== -->
<div class="flow-column" style="max-width:300px;min-width:260px">
<div class="flow-column-title" style="color:#389e0d">💬 咨询小助手</div>
<div class="node node-entry">首页点击「小助手」<span class="file-tag">index.html</span></div>
<div class="arrow-down"><div class="line"></div><div class="head"></div></div>
<div class="node node-state s-progress">进行中 — 小助手聊天<span class="file-tag">assistant-chat.html</span></div>
<div class="arrow-down"><div class="line"></div><div class="arrow-label">结束咨询</div><div class="line"></div><div class="head"></div></div>
<div class="node node-state s-done">已完成 — 历史记录<span class="file-tag">assistant-chat-history.html</span></div>
<div class="section-label">咨询记录入口</div>
<div class="arrow-down"><div class="line"></div><div class="head"></div></div>
<div class="node node-entry">我的咨询<span class="file-tag">my-records.html</span></div>
<div style="font-size:11px;color:#8c8c8c;text-align:center;margin-top:10px;line-height:1.8;">
一级Tab:咨询专家 / 小助手<br>
二级Tab(专家):图文咨询 / 视频咨询<br>
列表按状态跳转到对应详情页
</div>
<div class="section-label">辅助页面</div>
<div style="display:flex;flex-direction:column;gap:8px;margin-top:8px;">
<div class="node node-dialog" style="width:100%">选择咨询人弹窗<span class="file-tag">select-person-dialog.html</span></div>
<div class="node node-dialog" style="width:100%">选择体检报告弹窗<span class="file-tag">select-report-dialog.html</span></div>
<div class="node node-dialog" style="width:100%">结束咨询确认弹窗<span class="file-tag">end-consult-dialog.html</span></div>
<div class="node node-page" style="width:100%">专家评价列表<span class="file-tag">expert-reviews.html</span></div>
<div class="node node-page" style="width:100%">医院详情<span class="file-tag">hospital-detail.html</span></div>
<div class="node node-page" style="width:100%">搜索页<span class="file-tag">search.html</span></div>
<div class="node node-page" style="width:100%">选择咨询记录<span class="file-tag">select-record.html</span></div>
<div class="node node-page" style="width:100%">咨询人详情<span class="file-tag">consult-person-detail.html</span></div>
<div class="node node-page" style="width:100%">添加健康档案<span class="file-tag">add-health-record.html</span></div>
</div>
<div class="legend-item">
<svg class="legend-shape" viewBox="0 0 32 20"><polygon points="16,1 31,10 16,19 1,10" fill="#fffbe6" stroke="#d48806" stroke-width="1.5"/></svg>判断框
</div>
<div class="legend-item">
<svg class="legend-shape" viewBox="0 0 32 20"><rect x="1" y="1" width="30" height="18" rx="3" fill="#fff" stroke="#999" stroke-width="1" stroke-dasharray="4 2"/></svg>注释框
</div>
<div class="legend-item">
<svg class="legend-shape" viewBox="0 0 32 20"><line x1="2" y1="10" x2="28" y2="10" stroke="#666" stroke-width="1.5"/><polygon points="28,7 32,10 28,13" fill="#666"/></svg>流程线
</div>
</div>
<!-- 底部说明 -->
<div style="text-align:center;margin-top:36px;font-size:12px;color:#bfbfbf;">
员工端 · 健康咨询模块 · 业务流程图 &nbsp;|&nbsp;<?php echo count(glob('*.html')); ?> 个页面
<div class="canvas-wrap">
<svg id="fc"></svg>
</div>
<script>
/* ===== 流程图数据定义 ===== */
/* 节点类型: T=起止框, P=处理框, D=判断框, N=注释框 */
const W=160,H=42,DW=130,DH=72; // 处理框宽高、菱形宽高
const nodes=[
// ---- 入口 ----
{id:'start',t:'T',l:'开始',x:700,y:30},
{id:'home',t:'P',l:'健康咨询首页',x:700,y:110},
{id:'d_entry',t:'D',l:'选择入口',x:700,y:200},
// ---- 找专家主线 ----
{id:'find',t:'P',l:'找专家页面',x:380,y:320},
{id:'d_search',t:'D',l:'搜索方式',x:380,y:410},
{id:'s_hosp',t:'P',l:'按医院→医院详情',x:120,y:520,w:150},
{id:'s_dept',t:'P',l:'按科室',x:290,y:520,w:80},
{id:'s_dis',t:'P',l:'按疾病',x:470,y:520,w:80},
{id:'s_kw',t:'P',l:'综合搜索→搜索结果',x:640,y:520,w:160},
{id:'expert_list',t:'P',l:'专家列表',x:380,y:620},
{id:'expert_page',t:'P',l:'专家主页',x:380,y:700},
{id:'d_type',t:'D',l:'咨询类型',x:380,y:790},
// ---- 图文咨询 ----
{id:'tw_notice',t:'P',l:'咨询须知弹窗',x:180,y:910},
{id:'tw_person',t:'P',l:'选择咨询人',x:180,y:1000},
{id:'tw_note1',t:'N',l:'可新增咨询人\n可新增健康档案\n可查看档案详情\n可快速咨询跳过',x:0,y:1000,w:150,h:70},
{id:'tw_chat',t:'P',l:'图文咨询聊天',x:180,y:1090},
{id:'tw_note2',t:'N',l:'可发送图片/拍摄\n体检报告/健康档案\n文件/位置',x:0,y:1090,w:140,h:50},
{id:'d_tw_st',t:'D',l:'咨询状态',x:180,y:1190},
{id:'tw_timeout',t:'T',l:'已超时',x:0,y:1320,c:'#fff1f0',sc:'#cf1322'},
{id:'tw_end_dlg',t:'P',l:'结束咨询确认弹窗',x:180,y:1370,w:160},
{id:'tw_auto',t:'P',l:'已完成(待评价)',x:380,y:1320,w:150},
{id:'tw_done',t:'P',l:'已完成(待评价)',x:180,y:1460,w:150},
{id:'tw_rate',t:'P',l:'评价医生',x:180,y:1550},
{id:'tw_rated',t:'T',l:'已评价',x:180,y:1640,c:'#f6ffed',sc:'#52c41a'},
// ---- 视频咨询 ----
{id:'vd_notice',t:'P',l:'咨询须知弹窗',x:600,y:910},
{id:'vd_person',t:'P',l:'选择咨询人',x:600,y:1000},
{id:'vd_book',t:'P',l:'视频咨询预约',x:600,y:1090},
{id:'vd_ok',t:'P',l:'预约成功',x:600,y:1170},
{id:'vd_confirm',t:'P',l:'待确认',x:600,y:1250,c:'#f9f0ff',sc:'#722ed1'},
{id:'d_vd_resp',t:'D',l:'医生响应',x:600,y:1340},
{id:'vd_cancel',t:'T',l:'已取消',x:410,y:1450,c:'#f5f5f5',sc:'#999'},
{id:'vd_pending',t:'P',l:'待开始',x:600,y:1450,c:'#fff7e6',sc:'#d46b08'},
{id:'vd_reject',t:'T',l:'已拒绝',x:790,y:1450,c:'#f5f5f5',sc:'#999'},
{id:'d_vd_time',t:'D',l:'到达预约时间',x:600,y:1550,w:140},
{id:'vd_cancel2',t:'T',l:'已取消',x:410,y:1680,c:'#f5f5f5',sc:'#999'},
{id:'vd_ing',t:'P',l:'进行中',x:600,y:1680,c:'#e6f4ff',sc:'#1677ff'},
{id:'vd_done',t:'P',l:'已完成',x:600,y:1770,c:'#f6ffed',sc:'#52c41a'},
{id:'vd_rate',t:'P',l:'评价医生',x:600,y:1860},
{id:'vd_rated',t:'T',l:'已评价',x:600,y:1950,c:'#f6ffed',sc:'#52c41a'},
// ---- 咨询小助手 ----
{id:'ai_chat',t:'P',l:'小助手聊天页',x:980,y:320},
{id:'ai_note',t:'N',l:'可发送图片/拍摄\n文件/位置',x:980,y:230,w:120,h:36},
{id:'d_ai_st',t:'D',l:'咨询状态',x:980,y:430},
{id:'ai_timeout',t:'T',l:'已超时',x:820,y:540,c:'#fff1f0',sc:'#cf1322'},
{id:'ai_end_dlg',t:'P',l:'结束咨询确认弹窗',x:980,y:540,w:160},
{id:'ai_auto',t:'P',l:'已完成',x:1150,y:540,w:140},
{id:'ai_done',t:'P',l:'已完成',x:980,y:640,c:'#f6ffed',sc:'#52c41a'},
// ---- 我的咨询 ----
{id:'my_rec',t:'P',l:'我的咨询页面',x:1500,y:320},
{id:'d_my_tab',t:'D',l:'选择类型',x:1500,y:430},
{id:'my_tw',t:'P',l:'图文咨询记录',x:1330,y:550,w:130},
{id:'my_vd',t:'P',l:'视频咨询记录',x:1500,y:550,w:130},
{id:'my_ai',t:'P',l:'小助手记录',x:1670,y:550,w:120},
{id:'d_my_tw',t:'D',l:'咨询状态',x:1330,y:660},
{id:'my_tw_chat',t:'P',l:'继续聊天',x:1220,y:770,w:100},
{id:'my_tw_detail',t:'P',l:'咨询详情',x:1430,y:770,w:100},
{id:'my_tw_hist',t:'P',l:'历史对话(只读)',x:1430,y:860,w:140},
{id:'my_vd_detail',t:'P',l:'视频咨询详情',x:1500,y:660,w:130},
{id:'my_vd_note',t:'N',l:'按状态展示:\n待确认/待开始\n已完成/已取消/已拒绝',x:1650,y:770,w:140,h:50},
{id:'my_ai_hist',t:'P',l:'小助手历史对话',x:1670,y:660,w:130},
{id:'my_end',t:'T',l:'返回',x:1500,y:960,c:'#f6ffed',sc:'#52c41a'},
];
/* ===== 连线定义 [from, to, label?, waypoints?] ===== */
const edges=[
// 入口
['start','home'],['home','d_entry'],
['d_entry','find','找专家'],['d_entry','ai_chat','咨询小助手'],['d_entry','my_rec','我的咨询'],
// 找专家
['find','d_search'],
['d_search','s_hosp','按医院'],['d_search','s_dept','按科室'],['d_search','s_dis','按疾病'],['d_search','s_kw','综合搜索'],
['s_hosp','expert_list'],['s_dept','expert_list'],['s_dis','expert_list'],['s_kw','expert_list'],
['expert_list','expert_page'],['expert_page','d_type'],
['d_type','tw_notice','图文咨询'],['d_type','vd_notice','视频咨询'],
// 图文咨询
['tw_notice','tw_person'],['tw_person','tw_chat'],['tw_chat','d_tw_st'],
['d_tw_st','tw_end_dlg','主动结束'],['d_tw_st','tw_timeout','未回复且超7天'],['d_tw_st','tw_auto','7天无回复\n自动完成'],
['tw_end_dlg','tw_done'],['tw_done','tw_rate'],['tw_auto','tw_rate',null,[380,1460,380,1550]],
['tw_rate','tw_rated'],
// 视频咨询
['vd_notice','vd_person'],['vd_person','vd_book'],['vd_book','vd_ok'],['vd_ok','vd_confirm'],
['vd_confirm','d_vd_resp'],
['d_vd_resp','vd_cancel','用户取消'],['d_vd_resp','vd_pending','医生确认'],['d_vd_resp','vd_reject','医生拒绝'],
['vd_pending','d_vd_time'],
['d_vd_time','vd_cancel2','用户取消'],['d_vd_time','vd_ing','到达时间'],
['vd_ing','vd_done'],['vd_done','vd_rate'],['vd_rate','vd_rated'],
// 小助手
['ai_chat','d_ai_st'],
['d_ai_st','ai_timeout','7天未回复'],['d_ai_st','ai_end_dlg','主动结束'],['d_ai_st','ai_auto','7天无回复\n自动完成'],
['ai_end_dlg','ai_done'],['ai_done','ai_end'],
// 我的咨询
['my_rec','d_my_tab'],
['d_my_tab','my_tw','图文'],['d_my_tab','my_vd','视频'],['d_my_tab','my_ai','小助手'],
['my_tw','d_my_tw'],
['d_my_tw','my_tw_chat','进行中'],['d_my_tw','my_tw_detail','已完成'],
['my_tw_detail','my_tw_hist'],['my_tw_hist','my_end'],
['my_vd','my_vd_detail'],['my_vd_detail','my_end'],
['my_ai','my_ai_hist'],['my_ai_hist','my_end'],
];
<script>
// 统计当前目录页面数(静态标注)
document.querySelector('[style*="margin-top:36px"]').innerHTML =
'员工端 · 健康咨询模块 · 业务流程图';
</script>
/* ===== 注释连线(虚线)[from, to] ===== */
const noteEdges=[
['tw_note1','tw_person'],['tw_note2','tw_chat'],
['ai_note','ai_chat'],['my_vd_note','my_vd_detail'],
];
/* ===== 渲染引擎 ===== */
const NS='http://www.w3.org/2000/svg';
const svg=document.getElementById('fc');
const nMap={};
nodes.forEach(n=>{nMap[n.id]=n;if(!n.w)n.w=W;if(!n.h)n.h=H;});
/* 创建SVG元素 */
function el(tag,attrs){
const e=document.createElementNS(NS,tag);
for(const k in attrs)e.setAttribute(k,attrs[k]);
return e;
}
/* 多行文本 */
function addText(g,txt,cx,cy,fs,fill,fw){
const lines=txt.split('\n');
const lh=fs*1.4;
const startY=cy-((lines.length-1)*lh)/2;
lines.forEach((line,i)=>{
g.appendChild(el('text',{x:cx,y:startY+i*lh,'text-anchor':'middle','dominant-baseline':'central','font-size':fs,'fill':fill||'#333','font-weight':fw||'normal'})).textContent=line;
});
}
/* 获取节点锚点 */
function anchor(n,side){
const cx=n.x,cy=n.y,hw=n.w/2,hh=n.h/2;
if(n.t==='D'){
const dhw=Math.max(n.w||DW,DW)/2,dhh=DH/2;
if(side==='t')return[cx,cy-dhh];if(side==='b')return[cx,cy+dhh];
if(side==='l')return[cx-dhw,cy];if(side==='r')return[cx+dhw,cy];
}
if(side==='t')return[cx,cy-hh];if(side==='b')return[cx,cy+hh];
if(side==='l')return[cx-hw,cy];if(side==='r')return[cx+hw,cy];
return[cx,cy];
}
/* 自动选择锚点方向 */
function autoAnchors(fn,tn){
const dx=tn.x-fn.x,dy=tn.y-fn.y;
const adx=Math.abs(dx),ady=Math.abs(dy);
if(ady>=adx){return dy>0?['b','t']:['t','b'];}
else{return dx>0?['r','l']:['l','r'];}
}
/* 绘制箭头连线 */
function drawEdge(fid,tid,label,waypoints){
const fn=nMap[fid],tn=nMap[tid];
if(!fn||!tn)return;
const[fs,ts]=autoAnchors(fn,tn);
const[x1,y1]=anchor(fn,fs);
const[x2,y2]=anchor(tn,ts);
const g=el('g',{});
const mid=`fc-arrow-${fid}-${tid}`;
// 箭头标记
if(!document.getElementById('ah')){
const defs=el('defs',{});
const mk=el('marker',{id:'ah',viewBox:'0 0 10 10',refX:'9',refY:'5',markerWidth:'8',markerHeight:'8',orient:'auto-start-reverse'});
mk.appendChild(el('path',{d:'M 0 0 L 10 5 L 0 10 z',fill:'#999'}));
defs.appendChild(mk);svg.appendChild(defs);
}
if(waypoints&&waypoints.length>=4){
let d=`M${x1},${y1}`;
for(let i=0;i<waypoints.length;i+=2)d+=` L${waypoints[i]},${waypoints[i+1]}`;
d+=` L${x2},${y2}`;
g.appendChild(el('path',{d:d,fill:'none',stroke:'#999','stroke-width':'1.5','marker-end':'url(#ah)'}));
}else{
// 折线连接
if(fs==='b'&&ts==='t'){
if(Math.abs(x1-x2)<2){
g.appendChild(el('line',{x1,y1,x2,y2,stroke:'#999','stroke-width':'1.5','marker-end':'url(#ah)'}));
}else{
const my=(y1+y2)/2;
g.appendChild(el('path',{d:`M${x1},${y1} L${x1},${my} L${x2},${my} L${x2},${y2}`,fill:'none',stroke:'#999','stroke-width':'1.5','marker-end':'url(#ah)'}));
}
}else if(fs==='r'&&ts==='l'){
const mx=(x1+x2)/2;
g.appendChild(el('path',{d:`M${x1},${y1} L${mx},${y1} L${mx},${y2} L${x2},${y2}`,fill:'none',stroke:'#999','stroke-width':'1.5','marker-end':'url(#ah)'}));
}else if(fs==='l'&&ts==='r'){
const mx=(x1+x2)/2;
g.appendChild(el('path',{d:`M${x1},${y1} L${mx},${y1} L${mx},${y2} L${x2},${y2}`,fill:'none',stroke:'#999','stroke-width':'1.5','marker-end':'url(#ah)'}));
}else if(fs==='l'&&ts==='t'){
g.appendChild(el('path',{d:`M${x1},${y1} L${x2},${y1} L${x2},${y2}`,fill:'none',stroke:'#999','stroke-width':'1.5','marker-end':'url(#ah)'}));
}else if(fs==='r'&&ts==='t'){
g.appendChild(el('path',{d:`M${x1},${y1} L${x2},${y1} L${x2},${y2}`,fill:'none',stroke:'#999','stroke-width':'1.5','marker-end':'url(#ah)'}));
}else{
g.appendChild(el('line',{x1,y1,x2,y2,stroke:'#999','stroke-width':'1.5','marker-end':'url(#ah)'}));
}
}
// 标签 - 放在靠近目标节点的1/3处,避免遮挡源节点
if(label){
const lx=x1+(x2-x1)*0.5, ly=y1+(y2-y1)*0.7;
const lines=label.split('\n');
const lh=11*1.3;
const bw=Math.max(...lines.map(l=>l.length*11))+8;
const bh=lines.length*lh+4;
g.appendChild(el('rect',{x:lx-bw/2,y:ly-bh/2,width:bw,height:bh,rx:'3',fill:'#fff',stroke:'none',opacity:'0.9'}));
addText(g,label,lx,ly,10,'#666');
}
svg.appendChild(g);
}
/* 绘制注释虚线 */
function drawNoteEdge(fid,tid){
const fn=nMap[fid],tn=nMap[tid];
if(!fn||!tn)return;
const[fs,ts]=autoAnchors(fn,tn);
const[x1,y1]=anchor(fn,fs==='b'?'r':fs==='t'?'r':fs);
const[x2,y2]=anchor(tn,ts==='b'?'l':ts==='t'?'l':ts);
svg.appendChild(el('line',{x1,y1,x2,y2,stroke:'#bbb','stroke-width':'1','stroke-dasharray':'4 3'}));
}
/* 绘制节点 */
function drawNode(n){
const g=el('g',{});
const cx=n.x,cy=n.y;
if(n.t==='T'){
const w=n.w,h=n.h;
const fill=n.c||'#e8f4ff',sc=n.sc||'#1677ff';
g.appendChild(el('rect',{x:cx-w/2,y:cy-h/2,width:w,height:h,rx:h/2,fill:fill,stroke:sc,'stroke-width':'2'}));
addText(g,n.l,cx,cy,13,sc==='#52c41a'?'#389e0d':sc==='#cf1322'?'#cf1322':'#1677ff','600');
}else if(n.t==='P'){
const w=n.w,h=n.h;
const fill=n.c||'#fff',sc=n.sc||'#595959';
g.appendChild(el('rect',{x:cx-w/2,y:cy-h/2,width:w,height:h,rx:6,fill:fill,stroke:sc,'stroke-width':'1.5'}));
addText(g,n.l,cx,cy,12,'#333','500');
}else if(n.t==='D'){
const dw=Math.max(n.w||DW,DW),dh=DH;
const pts=`${cx},${cy-dh/2} ${cx+dw/2},${cy} ${cx},${cy+dh/2} ${cx-dw/2},${cy}`;
g.appendChild(el('polygon',{points:pts,fill:'#fffbe6',stroke:'#d48806','stroke-width':'1.5'}));
addText(g,n.l,cx,cy,12,'#8B6914','600');
}else if(n.t==='N'){
const w=n.w||140,h=n.h||50;
g.appendChild(el('rect',{x:cx-w/2,y:cy-h/2,width:w,height:h,rx:4,fill:'#fafafa',stroke:'#ccc','stroke-width':'1','stroke-dasharray':'4 2'}));
addText(g,n.l,cx,cy,10,'#999');
}
svg.appendChild(g);
}
/* ===== 执行渲染 ===== */
// 先画连线再画节点(节点覆盖连线)
edges.forEach(e=>drawEdge(e[0],e[1],e[2]||null,e[3]||null));
noteEdges.forEach(e=>drawNoteEdge(e[0],e[1]));
nodes.forEach(n=>drawNode(n));
// 自动计算画布大小
let maxX=0,maxY=0;
nodes.forEach(n=>{
const hw=(n.w||W)/2+20,hh=(n.h||H)/2+20;
if(n.x+hw>maxX)maxX=n.x+hw;
if(n.y+hh>maxY)maxY=n.y+hh;
});
svg.setAttribute('width',maxX+40);
svg.setAttribute('height',maxY+40);
svg.setAttribute('viewBox',`-100 0 ${maxX+160} ${maxY+40}`);
</script>
</body>
</html>