Files
klp-oa/klp-ui/public/冷轧厂业务流程泳道图(1).html
砂糖 791be3e1a5 feat: 新增多类业务功能并优化页面展示
1. 新增钢卷周期对比查询API,增加冷轧卷、花纹板物料类型
2. 优化库存积压统计逻辑,支持成品和原料数据合并计算
3. 新增告警统计功能,实现长度/厚度告警的数量和重量统计
4. 替换岗位管理页面为冷轧厂业务流程泳道图页面
2026-06-17 11:01:47 +08:00

431 lines
24 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

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

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>冷轧厂业务流程泳道图</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:"Microsoft YaHei","SimHei",sans-serif;background:#f0f4f8;color:#1e293b;overflow-x:hidden}
.header{background:linear-gradient(135deg,#0f2b47 0%,#1a4a7a 50%,#2c6fad 100%);color:#fff;padding:14px 28px;display:flex;align-items:center;gap:16px;box-shadow:0 3px 16px rgba(0,0,0,.2)}
.header h1{font-size:20px;letter-spacing:2px;font-weight:700}
.header .tag{background:rgba(255,255,255,.1);border:1px solid rgba(255,255,255,.2);border-radius:12px;padding:3px 14px;font-size:11.5px}
.tabs-wrap{background:#dde3ec;padding:8px 16px 0;position:sticky;top:0;z-index:200;box-shadow:0 2px 6px rgba(0,0,0,.05)}
.tabs{display:flex;gap:3px;overflow-x:auto;padding-bottom:0}
.tabs::-webkit-scrollbar{height:0}
.tab{flex-shrink:0;padding:9px 16px;font-size:12px;border:none;background:transparent;cursor:pointer;border-radius:7px 7px 0 0;color:#5a6a7a;font-weight:500;transition:all .2s;white-space:nowrap}
.tab:hover{background:rgba(255,255,255,.5)}
.tab.on{background:#fff;color:#0f2b47;font-weight:700;box-shadow:0 -1px 4px rgba(0,0,0,.05)}
.tab .tn{display:inline-block;background:#3b6ea5;color:#fff;border-radius:50%;width:17px;height:17px;line-height:17px;font-size:9.5px;text-align:center;margin-right:4px}
.tab.on .tn{background:#1a4a7a}
.panel{display:none;padding:18px 20px 36px}
.panel.on{display:block}
/* ========== 公司架构图 ========== */
.org-outer{background:#fff;border-radius:10px;box-shadow:0 3px 16px rgba(0,0,0,.06);overflow-x:auto;padding:24px 16px 32px;position:relative}
.org-row{display:flex;justify-content:center;gap:10px;flex-wrap:wrap;position:relative;margin-bottom:2px}
.org-node{text-align:center;padding:8px 16px;border-radius:9px;font-weight:700;cursor:pointer;transition:all .2s;position:relative;z-index:2;white-space:nowrap}
.org-node:hover{transform:translateY(-2px);box-shadow:0 6px 20px rgba(0,0,0,.15)}
.org-node.lv0{background:linear-gradient(135deg,#0f2b47,#1a4a7a);color:#fff;font-size:15px;padding:13px 28px;min-width:220px}
.org-node.lv1{background:linear-gradient(135deg,#1e3a5f,#2563eb);color:#fff;font-size:13px;padding:9px 20px;min-width:160px}
.org-node.lv2{background:linear-gradient(135deg,#1d4ed8,#3b82f6);color:#fff;font-size:12px;padding:8px 14px;min-width:155px}
.org-node.lv3{background:#dbeafe;border:1.5px solid #60a5fa;color:#1e40af;font-size:11.5px;padding:6px 11px;min-width:120px;font-weight:600}
.org-node .nd-sub{font-size:9.5px;font-weight:400;opacity:.7;display:block;margin-top:2px}
.org-vline{width:2px;height:16px;background:#93c5fd;margin:0 auto}
.org-tip{text-align:center;font-size:11px;color:#94a3b8;margin-top:10px}
/* ========== 岗位职责 ========== */
.resp-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(370px,1fr));gap:16px}
.resp-card{background:#fff;border-radius:10px;box-shadow:0 2px 12px rgba(0,0,0,.05);overflow:hidden;transition:all .2s}
.resp-card:hover{box-shadow:0 4px 20px rgba(0,0,0,.1)}
.resp-hd{padding:14px 18px;cursor:pointer;display:flex;align-items:center;gap:12px;user-select:none;transition:background .2s}
.resp-hd:hover{background:#f0f5ff}
.resp-icon{width:42px;height:42px;border-radius:10px;display:flex;align-items:center;justify-content:center;font-size:18px;flex-shrink:0}
.resp-title{font-size:14px;font-weight:700;color:#1e293b}
.resp-sub{font-size:11px;color:#64748b;margin-top:2px}
.resp-arrow{margin-left:auto;font-size:12px;color:#94a3b8;transition:transform .3s}
.resp-card.open .resp-arrow{transform:rotate(90deg)}
.resp-bd{display:none;padding:0 18px 16px;border-top:1px solid #f1f5f9}
.resp-card.open .resp-bd{display:block}
.resp-item{padding:8px 0;border-bottom:1px solid #f8fafc;display:flex;gap:8px;font-size:12.5px;line-height:1.6}
.resp-item:last-child{border-bottom:none}
.resp-bullet{color:#3b82f6;font-weight:700;flex-shrink:0;margin-top:1px}
.resp-tag{display:inline-block;font-size:10px;padding:2px 8px;border-radius:4px;margin-left:8px;font-weight:500}
.resp-tag.core{background:#dbeafe;color:#1e40af}
.resp-tag.support{background:#dcfce7;color:#166534}
.resp-tag.check{background:#fef3c7;color:#92400e}
/* SVG连线动画 */
.sw-svg path{transition:opacity .3s}
.sw-svg path.line-walk{opacity:.85!important;stroke-width:2.8!important}
.sw-svg path.line-walk-done{opacity:.5!important}
.sw-svg path.line-dimmed{opacity:.08!important}
.footer{text-align:center;padding:18px;color:#94a3b8;font-size:10.5px}
</style>
</head>
<body>
<!-- <div class="header">
<h1>⚙️ 冷轧厂业务流程泳道图</h1>
<span class="tag">架构 · 岗位 · 10 条流程 · 步进回放</span>
</div> -->
<div class="tabs-wrap"><div class="tabs" id="tabs"></div></div>
<div id="panels"></div>
<!-- <div class="footer">冷轧厂业务流程泳道图 · 冶金自动化工程</div> -->
<script>
// ═══════════════════════════════════
// 数据定义
// ═══════════════════════════════════
const ORG_TREE = {
name:"股东大会 / 董事会", lv:0, sub:"最高决策层",
children:[
{name:"监事会", lv:1, sub:"监督审计", children:[]},
{name:"总经理", lv:1, sub:"全面经营管理", children:[
{name:"副总经理(生产/设备)", lv:2, sub:"生产运营+设备保障", children:[
{name:"生产部", lv:3, sub:"计划·调度·酸轧·连退·精整"},
{name:"设备部", lv:3, sub:"机械·电气·液压·点检"},
{name:"安全环保部", lv:3, sub:"安监·环保·培训"},
{name:"工厂/分厂厂长", lv:3, sub:"酸轧·连退·镀锌等车间"}
]},
{name:"副总经理(技术/质量)", lv:2, sub:"技术+质量管控", children:[
{name:"技术中心/研发部", lv:3, sub:"工艺标准·新产品开发"},
{name:"质量检验部", lv:3, sub:"来料检·过程检·成品检·实验室"},
{name:"理化检测中心", lv:3, sub:"力学·化学·金相分析"}
]},
{name:"副总经理(营销/供应链)", lv:2, sub:"销售+采购+物流", children:[
{name:"销售部", lv:3, sub:"国内·海外·大客户"},
{name:"采购部", lv:3, sub:"热轧卷·辅料·备件"},
{name:"物流仓储部", lv:3, sub:"原料库·成品库·运输调度"},
{name:"市场部", lv:3, sub:"市场分析·行业研究"}
]},
{name:"总工程师", lv:2, sub:"技术总负责", children:[
{name:"工艺技术科", lv:3, sub:"工艺规程·技术攻关"},
{name:"自动化信息科", lv:3, sub:"L2/MES·信息化"}
]},
{name:"财务总监", lv:2, sub:"资金+成本管控", children:[
{name:"财务部", lv:3, sub:"成本·预算·资金·税务"}
]},
{name:"职能支撑部门", lv:2, sub:"行政+IT+管理", children:[
{name:"综合管理部/办公室", lv:3, sub:"行政·人资·法务"},
{name:"企管部/运营改善部", lv:3, sub:"KPI·流程优化"},
{name:"IT信息化部", lv:3, sub:"ERP·网络·信息安全"}
]}
]}
]
};
const JOBS = [
{name:"生产部",icon:"🏭",color:"#3b82f6",bg:"#dbeafe",lead:"副总经理(生产/设备)",
duties:[
{t:"主生产计划编制与下达",d:"根据销售订单、产能、原料到货编制月/周主生产计划,分解至各产线",tag:"core"},
{t:"车间日/班次排产",d:"编制各车间日计划、班次排产表,协调产线切换与规格衔接",tag:"core"},
{t:"生产调度与异常协调",d:"实时监控产线运行状态,处理停机、改规、换辊等生产异常",tag:"core"},
{t:"订单进度跟踪与反馈",d:"跟踪订单从排产到完工全流程,向销售部反馈进度",tag:"core"},
{t:"产能平衡与瓶颈分析",d:"分析各产线产能利用率,识别瓶颈并提出改善建议",tag:"support"},
{t:"原料需求提报",d:"根据排产计划向采购部提报原料需求计划",tag:"support"}
]},
{name:"设备部",icon:"🔧",color:"#059669",bg:"#d1fae5",lead:"副总经理(生产/设备)",
duties:[
{t:"设备日常/专业点检",d:"执行日常点检、专业点检,记录设备状态数据",tag:"core"},
{t:"维修计划编制与实施",d:"编制定修/年修计划,组织实施维修作业",tag:"core"},
{t:"故障应急响应",d:"处理突发设备故障,组织抢修恢复生产",tag:"core"},
{t:"备件需求提报与管理",d:"提报备件采购需求,管理备件库存与台账",tag:"support"},
{t:"设备技术改造方案",d:"提出设备技改需求,配合总工程师评审方案",tag:"support"},
{t:"维修后验收与归档",d:"组织维修后设备验收,更新设备档案",tag:"check"}
]},
{name:"技术中心/研发部",icon:"🔬",color:"#7c3aed",bg:"#ede9fe",lead:"副总经理(技术/质量)",
duties:[
{t:"工艺标准/工艺卡制定",d:"制定各钢种、各产线工艺标准,编制工艺卡",tag:"core"},
{t:"新产品/新工艺开发",d:"研发新产品、新工艺路线,组织试制与验证",tag:"core"},
{t:"工艺参数下发与交底",d:"向车间下发工艺参数MES/纸质),组织新工艺交底",tag:"core"},
{t:"工艺异常分析与处置",d:"分析工艺异常原因,制定处置方案",tag:"core"},
{t:"工艺变更管理",d:"评审并管理工艺变更,确保变更受控可追溯",tag:"check"},
{t:"封锁品评审",d:"参与封锁品/待判品技术评审,给出处置建议",tag:"check"}
]},
{name:"质量检验部",icon:"✅",color:"#dc2626",bg:"#fee2e2",lead:"副总经理(技术/质量)",
duties:[
{t:"来料取样与检测",d:"对进厂原料/辅料取样检验,出具检验报告",tag:"core"},
{t:"来料合格/不合格判定",d:"依据标准判定来料是否合格,通知采购和仓储",tag:"core"},
{t:"过程检验",d:"按产线执行过程检验(酸洗、轧制、退火、镀锌等)",tag:"core"},
{t:"成品检验与判定",d:"对成品进行力学/表面/尺寸检验,做出合格/不合格判定",tag:"core"},
{t:"合格品放行",d:"确认成品检验合格后签发放行单,通知仓储发货",tag:"core"},
{t:"封锁品/待判品管理",d:"管理封锁品标识与隔离,组织技术中心评审",tag:"check"},
{t:"质量异议现场确认",d:"参与客户质量异议的实物确认与复检",tag:"check"}
]},
{name:"理化检测中心",icon:"🧪",color:"#b45309",bg:"#fef3c7",lead:"副总经理(技术/质量)",
duties:[
{t:"力学性能检测",d:"拉伸、硬度、冲击等力学性能测试",tag:"core"},
{t:"化学成分分析",d:"直读光谱、碳硫分析、化学成分测定",tag:"core"},
{t:"金相组织检验",d:"金相制样、显微组织观察与评级",tag:"core"},
{t:"镀层/涂层检测",d:"镀锌量、涂层厚度、附着力检测",tag:"core"},
{t:"检测报告出具",d:"出具标准检测报告,支持质量判定",tag:"support"}
]},
{name:"销售部",icon:"📊",color:"#2563eb",bg:"#dbeafe",lead:"副总经理(营销/供应链)",
duties:[
{t:"合同评审与签订",d:"组织合同交期/技术/产能评审,签订销售合同",tag:"core"},
{t:"下达销售订单",d:"先交技术中心确认工艺可行性,再下达生产部排产",tag:"core"},
{t:"发货计划编制与跟踪",d:"编制发货计划,跟踪发货确认与签收",tag:"core"},
{t:"质量异议接收与处理",d:"接收客户质量异议,协调技术/质检处理",tag:"core"},
{t:"客户关系维护与回访",d:"维护大客户关系,异议关闭后组织回访",tag:"support"},
{t:"市场信息反馈",d:"收集市场需求、价格走势信息,反馈至生产/技术",tag:"support"}
]},
{name:"采购部",icon:"📦",color:"#ca8a04",bg:"#fef9c3",lead:"副总经理(营销/供应链)",
duties:[
{t:"采购计划编制",d:"根据生产部/设备部需求编制原料/辅料/备件采购计划",tag:"core"},
{t:"采购订单下达与跟踪",d:"下达采购订单,跟踪交期与物流",tag:"core"},
{t:"到货通知与协调",d:"到货前通知物流仓储部准备接收",tag:"core"},
{t:"不合格品退货处理",d:"质量检验部判定不合格后协调退货",tag:"check"},
{t:"质量索赔",d:"对不合格来料向供应商发起质量索赔",tag:"check"},
{t:"供应商管理",d:"供应商资质评审、绩效评价、目录维护",tag:"support"}
]},
{name:"物流仓储部",icon:"🚛",color:"#0d9488",bg:"#ccfbf1",lead:"副总经理(营销/供应链)",
duties:[
{t:"原料入库与投料管理",d:"原料验收入库,按生产计划投料至各车间",tag:"core"},
{t:"成品入库与出库",d:"成品验收入库,按发货计划备货出库",tag:"core"},
{t:"到货接收与暂存",d:"到货暂存待检区,通知质检检验",tag:"core"},
{t:"发货/运输调度",d:"组织发货装车,调度运输",tag:"core"},
{t:"日常出入库记录",d:"记录每日出入库明细,维护库存台账",tag:"support"},
{t:"库存盘点与差异处理",d:"配合财务部进行定期盘点,处理账实差异",tag:"check"},
{t:"呆滞/封锁品报告",d:"定期报告呆滞品与封锁品,通知质检/财务",tag:"check"}
]},
{name:"安全环保部",icon:"🛡️",color:"#dc2626",bg:"#fee2e2",lead:"副总经理(生产/设备)",
duties:[
{t:"安全/环保培训",d:"组织全员安全培训、特种作业培训、环保培训",tag:"core"},
{t:"日常安全检查",d:"执行日常安全巡查,发现隐患并督促整改",tag:"core"},
{t:"环保监测",d:"监测废水、废气排放,确保达标",tag:"core"},
{t:"事故报告与调查",d:"安全事故报告、调查与处理",tag:"core"},
{t:"隐患整改跟踪",d:"跟踪车间隐患整改进度,闭环管理",tag:"check"},
{t:"外部检查对接",d:"对接政府安监/环保检查",tag:"support"}
]},
{name:"总工程师",icon:"🏗️",color:"#7c3aed",bg:"#ede9fe",lead:"总经理",
duties:[
{t:"技改方案评审",d:"评审技术改造方案的技术可行性",tag:"core"},
{t:"定修/年修计划审批",d:"审批定修/年修计划的技术方案",tag:"core"},
{t:"重大工艺变更审批",d:"审批重大工艺变更方案",tag:"core"},
{t:"项目实施协调",d:"协调技改项目实施中的停机窗口",tag:"core"},
{t:"验收与移交",d:"组织技改项目验收与设备移交",tag:"check"},
{t:"资料归档管理",d:"督促技改资料归档至综合管理部",tag:"support"}
]},
{name:"财务部",icon:"💰",color:"#059669",bg:"#d1fae5",lead:"财务总监",
duties:[
{t:"成本核算与分析",d:"各产线/产品成本核算,差异分析",tag:"core"},
{t:"预算编制与管控",d:"年度/月度预算编制,预算执行监控",tag:"core"},
{t:"开票与结算",d:"销售开票、采购付款结算",tag:"core"},
{t:"应收账款跟踪",d:"跟踪应收账款回收,预警逾期",tag:"core"},
{t:"定期盘点组织",d:"组织月/季/年度库存盘点",tag:"check"},
{t:"盘点差异处理",d:"盘点差异审核与账务调整",tag:"check"}
]},
{name:"综合管理部/办公室",icon:"📋",color:"#64748b",bg:"#f1f5f9",lead:"总经理",
duties:[
{t:"行政管理",d:"公司行政事务、会议组织、文件管理",tag:"core"},
{t:"人力资源管理",d:"招聘、培训、薪酬、绩效考核",tag:"core"},
{t:"法务管理",d:"合同审查、法律风险防控",tag:"core"},
{t:"事故调查配合",d:"配合安全环保部事故调查的行政工作",tag:"support"},
{t:"资料归档接收",d:"接收技改/项目归档资料",tag:"support"}
]},
{name:"IT信息化部",icon:"💻",color:"#2563eb",bg:"#dbeafe",lead:"总经理",
duties:[
{t:"MES/L2系统运维",d:"维护MES、L2系统稳定运行故障响应",tag:"core"},
{t:"ERP系统管理",d:"ERP系统配置、数据维护、用户权限管理",tag:"core"},
{t:"网络与信息安全",d:"网络基础设施运维,信息安全防护",tag:"core"},
{t:"系统开发与集成",d:"新系统开发、系统集成、报表定制",tag:"support"},
{t:"数据备份与容灾",d:"数据库备份、容灾方案实施",tag:"support"}
]},
{name:"各车间",icon:"⚙️",color:"#475569",bg:"#f1f5f9",lead:"工厂/分厂厂长",
duties:[
{t:"产线生产执行",d:"按排产计划执行生产作业,确保产量/质量达标",tag:"core"},
{t:"工艺纪律执行",d:"严格执行工艺参数,接受技术中心抽查",tag:"core"},
{t:"设备日常点检",d:"执行设备日常点检,上报隐患与故障",tag:"core"},
{t:"过程质量自检",d:"执行生产过程自检,配合质量检验部过程检",tag:"check"},
{t:"完工入库操作",d:"成品完工后办理入库/转库手续",tag:"support"},
{t:"安全与5S管理",d:"执行安全操作规程保持5S现场",tag:"check"}
]}
];
// ═══════════════════════════════════
// Tab 0: 公司架构图
// ═══════════════════════════════════
(function(){
const tb = document.createElement('button');
tb.className = 'tab on';
tb.innerHTML = '<span class="tn">🏢</span>公司架构';
document.getElementById('tabs').appendChild(tb);
const pn = document.createElement('div');
pn.className = 'panel on';
pn.id = 'org';
let h = '<div class="org-outer" id="org-outer">';
h += '<div class="org-row" id="org-r0" style="margin-bottom:10px">';
h += '<div class="org-node lv0" data-name="股东大会/董事会">股东大会 / 董事会<span class="nd-sub">最高决策层</span></div>';
h += '</div>';
const gmNode = ORG_TREE.children[1];
h += '<div class="org-row" id="org-r1" style="gap:40px;margin-bottom:10px">';
h += '<div class="org-branch"><div class="org-node lv1" data-name="监事会">监事会<span class="nd-sub">监督审计</span></div></div>';
h += '<div class="org-branch"><div class="org-node lv1" id="org-gm" data-name="总经理">总经理<span class="nd-sub">全面经营管理</span></div></div>';
h += '</div>';
h += '<div class="org-row" id="org-r2" style="gap:12px 6px;margin-bottom:10px">';
gmNode.children.forEach((vp,vi) => {
h += '<div class="org-branch" id="org-vp-'+vi+'">';
h += '<div class="org-node lv2" data-name="'+vp.name+'">'+vp.name+'<span class="nd-sub">'+vp.sub+'</span></div>';
if(vp.children && vp.children.length){
h += '<div class="org-vline"></div>';
h += '<div style="display:flex;gap:5px;flex-wrap:wrap;justify-content:center">';
vp.children.forEach(dp => {
h += '<div class="org-node lv3" data-name="'+dp.name+'">'+dp.name+'<span class="nd-sub">'+dp.sub+'</span></div>';
});
h += '</div>';
}
h += '</div>';
});
h += '</div></div>';
h += '<p class="org-tip">💡 点击节点可跳转至岗位职责</p>';
pn.innerHTML = h;
pn.addEventListener('click', function(e){
const node = e.target.closest('.org-node');
if(node && node.dataset.name) jumpToJob(node.dataset.name);
});
document.getElementById('panels').appendChild(pn);
tb.onclick = () => {
document.querySelectorAll('.tab').forEach(x => x.classList.remove('on'));
document.querySelectorAll('.panel').forEach(x => x.classList.remove('on'));
tb.classList.add('on');
pn.classList.add('on');
setTimeout(drawOrgLines, 100);
};
})();
function jumpToJob(name){
const tabs = document.querySelectorAll('.tab');
if(tabs[1]) tabs[1].click();
setTimeout(() => {
const cards = document.querySelectorAll('.resp-card');
cards.forEach(c => {
if(c.dataset.name === name && !c.classList.contains('open')){
c.querySelector('.resp-hd').click();
}
});
const target = [...cards].find(c => c.dataset.name === name);
if(target) target.scrollIntoView({behavior:'smooth',block:'center'});
}, 280);
}
function drawOrgLines(){
const outer = document.getElementById('org-outer');
if(!outer) return;
let svg = document.getElementById('org-svg');
if(svg) svg.remove();
const w = outer.scrollWidth, h = outer.scrollHeight;
svg = document.createElementNS('http://www.w3.org/2000/svg','svg');
svg.id = 'org-svg';
svg.setAttribute('width', w);
svg.setAttribute('height', h);
svg.setAttribute('viewBox','0 0 '+w+' '+h);
svg.style.cssText = 'position:absolute;top:0;left:0;pointer-events:none;overflow:visible;z-index:1';
outer.appendChild(svg);
const or = outer.getBoundingClientRect();
function pos(el){
const r = el.getBoundingClientRect();
return {cx:r.left-or.left+r.width/2, cy:r.top-or.top+r.height/2, l:r.left-or.left, t:r.top-or.top, R:r.left-or.left+r.width, b:r.top-or.top+r.height};
}
function mkCurve(x1,y1,x2,y2,color){
const dy = Math.abs(y2-y1)*0.35;
const path = document.createElementNS('http://www.w3.org/2000/svg','path');
path.setAttribute('d','M'+x1+' '+y1+' C'+x1+' '+(y1<y2?y1+dy:y1-dy)+' '+x2+' '+(y2>dy?y2-dy:y2+dy)+' '+x2+' '+y2);
path.setAttribute('fill','none');
path.setAttribute('stroke',color||'#93c5fd');
path.setAttribute('stroke-width','2');
svg.appendChild(path);
}
const r0n = document.querySelector('#org-r0 .org-node');
const r1ns = document.querySelectorAll('#org-r1 .org-node');
if(r0n && r1ns.length>=2){
const p0 = pos(r0n);
r1ns.forEach(n => { const p1=pos(n); mkCurve(p0.cx,p0.b,p1.cx,p1.t,'#93c5fd'); });
}
const gm = document.getElementById('org-gm');
const vpBranches = document.querySelectorAll('#org-r2 > .org-branch');
if(gm && vpBranches.length){
const pGm = pos(gm);
vpBranches.forEach(br => {
const vpn = br.querySelector(':scope > .org-node');
if(!vpn) return;
const pVp = pos(vpn);
mkCurve(pGm.cx,pGm.b,pVp.cx,pVp.t,'#93c5fd');
});
}
vpBranches.forEach(br => {
const vpn = br.querySelector(':scope > .org-node');
if(!vpn) return;
const pVp = pos(vpn);
const depts = br.querySelectorAll(':scope > div:last-child .org-node');
depts.forEach(dn => { const pD=pos(dn); mkCurve(pVp.cx,pVp.b,pD.cx,pD.t,'#93c5fd'); });
});
}
// ═══════════════════════════════════
// Tab 1: 岗位职责
// ═══════════════════════════════════
(function(){
const tb = document.createElement('button');
tb.className = 'tab';
tb.innerHTML = '<span class="tn">👤</span>岗位职责';
document.getElementById('tabs').appendChild(tb);
const pn = document.createElement('div');
pn.className = 'panel';
pn.id = 'jobs';
let h = '<div class="resp-grid">';
JOBS.forEach(j => {
h += '<div class="resp-card" data-name="'+j.name+'">';
h += '<div class="resp-hd" onclick="this.parentElement.classList.toggle(\'open\')">';
h += '<div class="resp-icon" style="background:'+j.bg+';color:'+j.color+'">'+j.icon+'</div>';
h += '<div><div class="resp-title">'+j.name+'</div><div class="resp-sub">上级:'+j.lead+'</div></div>';
h += '<div class="resp-arrow">▶</div>';
h += '</div>';
h += '<div class="resp-bd">';
j.duties.forEach(d => {
const tc = d.tag==='core'?'core':(d.tag==='check'?'check':'support');
const tl = d.tag==='core'?'核心':(d.tag==='check'?'验收':'支撑');
h += '<div class="resp-item"><span class="resp-bullet">•</span><div><b>'+d.t+'</b><span class="resp-tag '+tc+'">'+tl+'</span><br><span style="color:#64748b;font-size:11.5px">'+d.d+'</span></div></div>';
});
h += '</div></div>';
});
h += '</div>';
pn.innerHTML = h;
document.getElementById('panels').appendChild(pn);
tb.onclick = () => {
document.querySelectorAll('.tab').forEach(x => x.classList.remove('on'));
document.querySelectorAll('.panel').forEach(x => x.classList.remove('on'));
tb.classList.add('on');
pn.classList.add('on');
};
})();
// ═══════════════════════════════════
// 初始化
// ═══════════════════════════════════
window.addEventListener('load', () => {
setTimeout(() => drawOrgLines(), 350);
});
window.addEventListener('resize', () => {
const ap = document.querySelector('.panel.on');
if(!ap) return;
if(ap.id === 'org') drawOrgLines();
else if(ap.id !== 'jobs') drawLines(ap.id);
});
</script>
</body>
</html>