You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

4910 lines
1.0 MiB

<!-- S2-gpt 当前7项增量修复交付版:2026-07-10 09:52 -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1" name="viewport"/>
<title>保伦CRM系统demo-重构版-S1</title>
<style>
:root{
--neo-logo:url("data:image/png;base64,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");
--blue:#1677ff;
--primary:#1677ff;
--line:#dbe4ef;
--muted:#64748b;
--text:#0f172a;
--bg:#f3f6fb;
--radius:8px;
--shadow:0 8px 24px rgba(15,23,42,.08);
}
*{box-sizing:border-box}
html,body{height:100%;margin:0;font-family:"Microsoft YaHei","PingFang SC",Arial,sans-serif;color:var(--text);font-size:14px;background:var(--bg);overflow:hidden}
button,input,textarea,select{font:inherit}
button{cursor:pointer}
.neo-shell{height:100vh;display:grid;grid-template-rows:58px 52px 40px minmax(0,1fr);background:var(--bg)}
.topbar{height:58px;background:#fff;border-bottom:1px solid #dbe4ef;box-shadow:0 1px 8px rgba(15,23,42,.04);display:flex;align-items:center;padding:0 26px;gap:14px;position:relative;z-index:5}
.system-home-logo{width:224px;height:34px;border:0;background:var(--neo-logo) left center/contain no-repeat;padding:0;flex:0 0 auto}
.topbar-spacer{flex:1}
.ai-pill{height:32px;padding:0 16px;border:0;border-radius:18px;background:linear-gradient(135deg,#8db8ff,#5b8df6);box-shadow:0 2px 6px rgba(37,99,235,.16);color:#fff;display:flex;align-items:center;gap:8px;font-weight:700;cursor:pointer;font:inherit}
.ai-pill:hover{background:linear-gradient(135deg,#7fafff,#4f82ed)}
.topbar-icon{width:30px;height:30px;border:0;background:transparent;border-radius:7px;color:#475569;display:inline-flex;align-items:center;justify-content:center;padding:0}
.topbar-icon:hover{background:#f2f6fb;color:#1677ff}
.topbar-icon .ui-icon,.bell .ui-icon{width:18px;height:18px}
.bell{position:relative;font-size:0;display:flex;align-items:center;justify-content:center;width:30px;height:30px;border:0;background:transparent;padding:0;border-radius:7px;color:#475569;cursor:pointer}
.bell:hover,.bell.is-open{background:#f2f6fb;color:#1677ff}
.notice-badge{position:absolute;right:-9px;top:-8px;min-width:14px;box-sizing:border-box;background:#e11d48;color:#fff;border-radius:9px;font-size:10px;line-height:14px;padding:1px 4px;text-align:center}
.ai-pill:focus,.topbar-icon:focus,.bell:focus{outline:none}
.user{display:flex;align-items:center;gap:8px;white-space:nowrap}
.avatar{width:26px;height:26px;border-radius:50%;background:#fbe2df}
.neo-global-nav{height:52px;display:flex;align-items:center;gap:18px;background:#fff;border-bottom:1px solid #dbe4ef;padding:0 38px;position:relative;z-index:100}
.neo-tab{height:36px;border:0;background:transparent;color:#0f172a;border-radius:7px;padding:0 12px;display:inline-flex;align-items:center;gap:8px;font-weight:700;white-space:nowrap}
.neo-tab:hover,.neo-tab.active{background:#eef6ff;color:#1677ff}
.neo-tab .ui-icon{width:16px;height:16px;color:#6b7890}
.neo-tab.active .ui-icon{color:#1677ff}
.neo-menu{position:relative;height:52px;display:flex;align-items:center}
.neo-menu-panel{display:none;position:absolute;top:45px;left:0;min-width:150px;background:#fff;border:1px solid #dbe4ef;border-radius:8px;box-shadow:0 12px 32px rgba(15,23,42,.14);padding:6px;z-index:110}
.neo-menu:hover .neo-menu-panel{display:grid}
.neo-menu-panel button{height:32px;border:0;background:transparent;text-align:left;border-radius:6px;padding:0 10px;color:#334155}
.neo-menu-panel button:hover{background:#eef6ff;color:#1677ff}
.nav-ref-icon{width:16px;height:16px;display:inline-flex;align-items:center;justify-content:center;color:#6b7890;flex:0 0 auto}
.nav-ref-icon svg{width:16px;height:16px}
.nav-ref-icon--opp{border:1.6px solid currentColor;border-radius:50%;font-size:11px;font-weight:800;line-height:1}
.neo-tab.active .nav-ref-icon{color:#1677ff}
.location-bar{height:40px;display:flex;align-items:center;padding:0 38px;background:#fff;border-bottom:1px solid #dbe4ef;font-weight:700;position:relative;z-index:1}
.location-current{color:#0f172a}
.side-nav-v7{display:none;background:#eef3f8;border-right:1px solid #ccd8e5;flex-direction:column;min-width:0}
.side-nav-v7 .side-menu{padding:10px 0;overflow:auto}
.side-item{width:100%;height:44px;border:0;border-left:4px solid transparent;background:transparent;color:#223044;display:flex;align-items:center;gap:12px;padding:0 18px;text-align:left}
.side-item:hover{background:#fff}
.side-item.active{background:#fff;border-left-color:#1677ff;color:#1677ff;font-weight:700}
.side-item .ui-icon{width:16px;height:16px;opacity:.75}
.side-group{font-weight:700}
.side-group:after{content:"⌄";margin-left:auto;color:#334155}
.side-group.collapsed:after{content:"›"}
.side-sub{height:38px;padding-left:36px;font-size:13px;overflow:hidden;transition:max-height .2s ease,opacity .2s ease;max-height:44px;opacity:1}
.side-sub.is-hidden{max-height:0;opacity:0;pointer-events:none}
.side-collapse{margin-top:auto;height:44px;border:0;background:transparent;border-top:1px solid #ccd8e5;padding:0 18px;text-align:right;color:#111}
.neo-shell.side-mode{grid-template-columns:232px minmax(0,1fr);grid-template-rows:58px 40px minmax(0,1fr)}
.neo-shell.side-mode .topbar{grid-column:1 / -1;grid-row:1}
.neo-shell.side-mode .side-nav-v7{display:flex;grid-column:1;grid-row:2 / 4}
.neo-shell.side-mode .neo-global-nav{display:none}
.neo-shell.side-mode .location-bar{grid-column:2;grid-row:2}
.neo-shell.side-mode .content{grid-column:2;grid-row:3}
.neo-shell.side-nav-collapsed{grid-template-columns:64px minmax(0,1fr)}
.neo-shell.side-nav-collapsed .side-item{justify-content:center;padding:0;gap:0;font-size:0}
.neo-shell.side-nav-collapsed .side-item .ui-icon,.neo-shell.side-nav-collapsed .side-item .nav-ref-icon{width:18px;height:18px;font-size:13px}
.neo-shell.side-nav-collapsed .side-group:after{display:none}
.neo-shell.side-nav-collapsed .side-sub{display:none!important}
.neo-shell.side-nav-collapsed .side-collapse{font-size:0;text-align:center;padding:0}
.neo-shell.side-nav-collapsed .side-collapse:before{content:'>>';font-size:12px}
.content{min-width:0;overflow:auto;padding:18px 26px 28px}
.page{display:none}
.page.active{display:block}
.hero{min-height:76px;border:1px solid #d8e8fb;border-radius:8px;background:#f8fbff;display:flex;align-items:center;justify-content:space-between;gap:16px;padding:14px 16px;margin-bottom:10px}
.hero h1{font-size:24px;margin:0 0 6px;letter-spacing:0}
.hero p{margin:0;color:#334e75}
.hero-actions{display:flex;gap:8px}
.btn{height:32px;border:1px solid #d9e2ec;background:#fff;border-radius:6px;padding:0 12px}
.btn:hover,.mini-btn:hover,.ai-btn:hover{background:#f4f8fd}
.btn.primary{background:#1677ff;border-color:#1677ff;color:#fff}
.btn.ghost{background:#f5f9ff;color:#1554c0;border-color:#b7d2ff}
.more{font-size:12px;color:#475569;cursor:pointer}
.more:hover{color:#1677ff}
.kpis{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;margin-bottom:14px}
.kpi{min-height:104px;background:#fff;border:1px solid #d9e2ec;border-radius:8px;box-shadow:0 1px 2px rgba(15,23,42,.025);padding:13px 14px;overflow:hidden;position:relative;transition:box-shadow .18s,transform .18s;cursor:pointer}
.kpi:hover{box-shadow:0 4px 16px rgba(15,23,42,.10);transform:translateY(-1px)}
.kpi b{font-size:12px;color:#334155;display:flex;align-items:center;gap:7px}
.kpi .num{font-size:28px;line-height:1;font-weight:700;color:#174ea6;margin-top:6px}
.kpi.good .num{color:#059669}
.kpi.warn .num{color:#d97706}
.kpi small{font-size:12px;color:#64748b;line-height:1.45}
.kpi small b{display:inline;font-size:12px;color:#0f172a}
.kpi-delta{font-weight:600}
.kpi-delta.up{color:#059669}
.kpi-delta.down{color:#dc2626}
.kpi-delta.neutral{color:#64748b}
.kpi-trend-bar{position:absolute;bottom:0;left:0;right:0;height:3px;background:#e8edf4;overflow:hidden}
.kpi-trend-bar i{display:block;height:100%;background:#1677ff;border-radius:0 2px 2px 0;transition:width .6s ease}
.kpi.good .kpi-trend-bar i{background:#059669}
.kpi.warn .kpi-trend-bar i{background:#f59e0b}
.ui-icon--kpi{width:15px;height:15px;color:#2563eb}
.section-title{display:flex;align-items:center;justify-content:space-between;margin:2px 0 10px}
.section-title h2{margin:0;font-size:16px;color:#1f2937}
.section-title span{font-size:12px;color:#64748b}
.layout-grid,.management-grid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px;margin-bottom:10px}
.priority-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:0}
.card{background:#fff;border:1px solid #d9e2ec;border-radius:8px;box-shadow:0 1px 2px rgba(15,23,42,.025);min-width:0;overflow:hidden;cursor:pointer}
.card-head{height:42px;padding:0 14px;border-bottom:1px solid #e8eef5;display:flex;align-items:center;justify-content:space-between;font-weight:800;color:#1f2937}
.card-head>span{display:flex;align-items:center;gap:8px}
.card-body{padding:12px 14px}
.ui-icon--section{width:17px;height:17px;color:#2563eb}
.todo ul,.risk-list,.notice ul{margin:0;padding:0;list-style:none}
.todo li{display:flex;align-items:center;border-bottom:1px solid #edf2f7;gap:8px;padding:8px 0;list-style:none;cursor:pointer}
.todo li:last-child{border-bottom:0}
.dot{width:5px;height:5px;border-radius:50%;background:#b7c8df;display:inline-block;flex:0 0 auto}
.badge{margin-left:auto;background:#f1f5f9;color:#334155;border-radius:10px;padding:1px 8px}
.todo-arrow{color:#94a3b8;margin-left:4px}
.funnel-visual{display:grid;gap:8px;padding:4px 0}
.funnel-step{display:grid;grid-template-columns:54px minmax(0,1fr) 24px;gap:8px;align-items:center;font-size:12px}
.funnel-bar-wrap{height:20px;position:relative;background:#e8edf4;border-radius:3px;overflow:hidden}
.funnel-bar-fill{height:100%;background:linear-gradient(90deg,#60a5fa,#2563eb);border-radius:3px}
.funnel-step:nth-child(2) .funnel-bar-fill{background:linear-gradient(90deg,#34d399,#059669)}
.funnel-step:nth-child(3) .funnel-bar-fill{background:linear-gradient(90deg,#fbbf24,#d97706)}
.funnel-step:nth-child(4) .funnel-bar-fill{background:linear-gradient(90deg,#fb7185,#dc2626)}
.funnel-step:nth-child(5) .funnel-bar-fill{background:linear-gradient(90deg,#a78bfa,#7c3aed)}
.funnel-bar-label{position:absolute;left:8px;top:2px;font-size:12px;font-weight:800;color:#0f172a}
.funnel-step-num{text-align:right;font-weight:800}
.ai-card{border-color:#cfe1ff;background:#fff}
.ai-box{background:#f5f9ff;border-radius:6px;padding:9px;margin-bottom:8px;line-height:1.55;font-size:12px;cursor:pointer}
.assistant-actions{display:grid;grid-template-columns:repeat(3,1fr);gap:6px}
.assistant-actions .btn{height:30px;padding:0 6px;font-size:12px}
.table{width:100%;border-collapse:collapse;font-size:12px}
.table th,.table td{height:29px;border-bottom:1px solid #edf2f7;text-align:left;padding:0 6px}
.table th{color:#334155;font-weight:700}
.tag{display:inline-block;background:#eef5ff;color:#1554c0;border-radius:4px;padding:2px 7px;font-size:12px}
.customer{display:grid;grid-template-columns:30px 1fr;gap:8px;align-items:center;padding:8px 0;border-bottom:1px solid #edf2f7}
.customer:last-child{border-bottom:0}
.customer small{color:#64748b}
.customer-icon{color:#5b7fb8;width:28px;height:28px;padding:5px;border:1px solid #d9e2ec;border-radius:6px;background:#f8fbff}
.double-five-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px}
.double-five-item{border:1px solid #d9e2ec;border-radius:6px;padding:9px;text-align:center}
.double-five-item span{font-size:12px;color:#64748b}
.double-five-item strong{font-size:12px}
.ring{--value:50;--ring-color:#2580f7;width:42px;height:42px;border-radius:50%;margin:6px auto;background:conic-gradient(var(--ring-color) calc(var(--value)*1%),#e8edf4 0);display:grid;place-items:center;position:relative}
.ring:after{content:"";position:absolute;inset:6px;border-radius:50%;background:#fff}
.ring b{position:relative;z-index:1;color:var(--ring-color);font-size:11px}
.risk-list{display:grid;grid-template-columns:1fr 1fr;gap:8px 18px}
.risk-list li{font-size:13px;display:flex;align-items:center;gap:8px}
.risk-dot{width:8px;height:8px;border-radius:50%;display:inline-block}
.risk-dot.high{background:#ef4444}.risk-dot.mid{background:#f59e0b}.risk-dot.low{background:#2563eb}
.risk-count{font-size:18px}
.quick-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}
.ai-btn{height:32px;border:1px solid #d9e8ff;background:#f0f6ff;color:#1554c0;border-radius:6px;padding:0 10px;font-size:12px}
.metric-unit{font-size:13px;font-weight:700;margin-left:3px}
.today-work-list{display:grid;gap:0}
.today-work-item{display:grid;grid-template-columns:44px minmax(0,1fr) auto;align-items:center;gap:9px;min-height:39px;border-bottom:1px solid #edf2f7;color:#334155}
.today-work-item:last-child{border-bottom:0}
.today-work-item time{font-size:12px;color:#64748b}
.today-work-item b{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px}
.today-work-status{border-radius:4px;padding:2px 7px;font-size:11px;background:#fff4e6;color:#b45309}
.today-work-status.overdue{background:#fff1f2;color:#dc2626}
.project-progress{display:grid;grid-template-columns:minmax(54px,1fr) 32px;align-items:center;gap:6px}
.project-progress span{height:6px;border-radius:4px;background:#e8edf4;overflow:hidden}
.project-progress i{display:block;height:100%;border-radius:4px;background:#6b8fca}
.project-progress b{font-size:11px}
.planning-placeholder{height:100%;display:grid;place-content:center;justify-items:center;gap:9px;text-align:center;color:#64748b;padding:18px}
.planning-placeholder .ui-icon{width:30px;height:30px;color:#94a3b8}
.planning-placeholder b{font-size:16px;color:#334155}
.planning-placeholder p{margin:0;line-height:1.6}
.planning-tags{display:flex;flex-wrap:wrap;justify-content:center;gap:6px}
.planning-tags span,.planning-state{border-radius:10px;background:#f1f5f9;color:#64748b;padding:2px 8px;font-size:11px;font-weight:600}
.risk-list li{cursor:pointer}
.home-notice{position:fixed;left:50%;top:72px;z-index:1200;transform:translate(-50%,-8px);padding:10px 16px;border-radius:7px;background:#1f2937;color:#fff;box-shadow:0 8px 24px rgba(15,23,42,.18);opacity:0;pointer-events:none;transition:.18s}
.home-notice.show{opacity:1;transform:translate(-50%,0)}
.modal-mask,.ai-panel-mask{position:fixed;inset:0;background:rgba(15,23,42,.32);display:none;z-index:300}
.modal-mask.show{display:flex;align-items:center;justify-content:center}
.modal{width:560px;max-width:calc(100vw - 32px);background:#fff;border-radius:10px;box-shadow:var(--shadow);overflow:hidden}
.modal h3{margin:0;padding:16px;border-bottom:1px solid var(--line)}
.modal .m-body{padding:16px}
.modal .m-foot{padding:12px 16px;border-top:1px solid var(--line);text-align:right}
.ai-panel-mask{backdrop-filter:blur(1px)}
.ai-panel-mask.show{display:flex;align-items:stretch;justify-content:flex-end}
.ai-panel-drawer{width:min(480px,calc(100vw - 48px));height:100%;background:#fff;display:grid;grid-template-rows:auto auto 1fr auto;box-shadow:-12px 0 40px rgba(15,23,42,.14);animation:slideInRight .22s ease}
@keyframes slideInRight{from{transform:translateX(60px);opacity:0}to{transform:translateX(0);opacity:1}}
.ai-panel-head{padding:16px 18px 0;display:flex;align-items:flex-start;justify-content:space-between;gap:12px}
.ai-panel-head-title{display:flex;align-items:center;gap:10px}
.ai-panel-head-title h2{margin:0;font-size:18px;font-weight:700}
.ai-panel-badge{display:inline-flex;align-items:center;gap:5px;background:linear-gradient(135deg,#eff6ff,#e0f2fe);border:1px solid #bfdbfe;color:#1d4ed8;padding:2px 10px;border-radius:10px;font-size:11px;font-weight:700}
.ai-panel-shortcuts{padding:12px 18px;display:flex;gap:7px;flex-wrap:wrap;border-bottom:1px solid #edf2f7}
.ai-shortcut-btn{border:1px solid #d7e2ee;background:#f8fbff;border-radius:14px;padding:4px 11px;font-size:12px;color:#334155;transition:all .15s}
.ai-shortcut-btn:hover{border-color:#93c5fd;background:#eff6ff;color:#1d4ed8}
.ai-chat-body{overflow:auto;padding:14px 18px;display:flex;flex-direction:column;gap:12px}
.ai-chat-msg{display:grid;gap:4px}
.ai-chat-msg.user{text-align:right}
.ai-chat-bubble{display:inline-block;padding:9px 13px;border-radius:12px;font-size:13px;line-height:1.6;max-width:88%;text-align:left}
.ai-chat-msg.user .ai-chat-bubble{background:linear-gradient(135deg,#2563eb,#1d4ed8);color:#fff;border-radius:12px 12px 2px 12px;margin-left:auto}
.ai-chat-msg.assistant .ai-chat-bubble{background:#f3f7fc;color:#1f2937;border:1px solid #e0e8f5;border-radius:2px 12px 12px 12px}
.ai-chat-msg.assistant.loading .ai-chat-bubble{display:flex;align-items:center;gap:5px;color:#94a3b8}
.ai-typing-dot{width:6px;height:6px;border-radius:50%;background:#94a3b8;animation:typingBounce 1s infinite}
.ai-typing-dot:nth-child(2){animation-delay:.15s}
.ai-typing-dot:nth-child(3){animation-delay:.3s}
@keyframes typingBounce{0%,80%,100%{transform:translateY(0)}40%{transform:translateY(-5px)}}
.ai-chat-meta{font-size:11px;color:#98a2b3;padding:0 4px}
.ai-panel-input-area{padding:12px 18px;border-top:1px solid #e8eef5;background:#fbfdff}
.ai-input-wrap{display:flex;gap:8px;align-items:flex-end}
.ai-input-wrap textarea{flex:1;border:1px solid #d0dae8;border-radius:8px;padding:9px 12px;font-size:13px;font-family:inherit;resize:none;height:38px;max-height:120px;overflow:auto;line-height:1.5;transition:border-color .15s,height .1s;background:#fff}
.ai-input-wrap textarea:focus{outline:none;border-color:#93c5fd;box-shadow:0 0 0 3px rgba(147,197,253,.25)}
.ai-send-btn{width:36px;height:36px;border-radius:8px;background:var(--blue);border:none;color:#fff;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:background .15s}
.ai-send-btn:hover{background:#1554c0}
.ai-send-btn:disabled{background:#b7c8df;cursor:default}
.ai-send-btn svg{width:16px;height:16px;stroke:#fff;stroke-width:2;fill:none}
.notice-panel{position:fixed;inset:0;display:none;z-index:320;background:transparent}
.notice-panel.show{display:block}
.notice-popover{position:absolute;top:50px;right:112px;width:400px;max-width:calc(100vw - 28px);overflow:hidden;background:#fff;border:1px solid #d9e3ef;border-radius:10px;box-shadow:0 14px 32px rgba(15,23,42,.18);animation:noticePopIn .16s ease}
@keyframes noticePopIn{from{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:translateY(0)}}
.notice-popover-head{height:52px;padding:0 16px;border-bottom:1px solid #e8eef5;display:flex;align-items:center;justify-content:space-between}
.notice-popover-head h3{margin:0;font-size:15px;color:#172033}.notice-popover-head h3 small{margin-left:7px;color:#64748b;font-size:12px;font-weight:500}
.notice-more-btn{height:28px;border:0;border-radius:4px;background:#1677ff;color:#fff;padding:0 10px;font:inherit;font-size:12px;cursor:pointer}.notice-more-btn:hover{background:#155fbd}
.notice-popover-list{padding:0 16px}.notice-popover-item{position:relative;width:100%;display:block;border:0;border-bottom:1px solid #edf2f7;background:#fff;padding:12px 20px 11px 0;text-align:left;color:#172033;font:inherit;cursor:pointer}.notice-popover-item:last-child{border-bottom:0}.notice-popover-item b{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px}.notice-popover-item small{display:block;margin-top:5px;color:#8a99ab;font-size:11px}.notice-popover-item.unread:after{content:"";position:absolute;right:3px;top:20px;width:8px;height:8px;border-radius:50%;background:#f5222d}.notice-popover-item:hover{color:#1677ff}
.notice-page{height:100%;min-height:0;box-sizing:border-box;padding:16px;overflow:auto;background:#f5f7fa}.notice-page-shell{min-height:100%;max-width:1180px;margin:0 auto;border:1px solid #dbe4ef;border-radius:8px;background:#fff}.notice-page-head{min-height:64px;padding:0 18px;border-bottom:1px solid #e5ebf2;display:flex;align-items:center;justify-content:space-between;gap:16px}.notice-page-head h1{margin:0;font-size:18px;color:#172033}.notice-page-head p{margin:4px 0 0;color:#64748b;font-size:12px}.notice-page-tools{padding:12px 18px;border-bottom:1px solid #edf2f7;display:flex;align-items:center;justify-content:space-between;gap:12px}.notice-filter-group{display:flex;align-items:center;gap:7px}.notice-filter-group button{height:30px;border:1px solid #d5dfeb;border-radius:4px;background:#fff;padding:0 12px;color:#475569;cursor:pointer}.notice-filter-group button.active,.notice-filter-group button:hover{border-color:#93c5fd;background:#eaf2ff;color:#1677ff}.notice-page-tools input{width:240px;height:32px;box-sizing:border-box;border:1px solid #cfd9e5;border-radius:4px;padding:0 10px;font:inherit;color:#334155}.notice-history-list{padding:0 18px}.notice-history-item{position:relative;display:grid;grid-template-columns:104px minmax(0,1fr) 144px 58px;align-items:center;gap:14px;min-height:68px;border-bottom:1px solid #edf2f7}.notice-history-item.unread:before{content:"";position:absolute;left:-10px;width:7px;height:7px;border-radius:50%;background:#f5222d}.notice-history-type{display:inline-flex;justify-content:center;width:max-content;min-width:54px;padding:3px 8px;border-radius:10px;background:#eef5ff;color:#3975c4;font-size:11px}.notice-history-copy{min-width:0}.notice-history-copy b{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#24364f;font-size:13px}.notice-history-copy small{display:block;margin-top:4px;color:#74849a;font-size:11px}.notice-history-time{color:#8a99ab;font-size:12px}.notice-history-item button{border:0;background:transparent;color:#1769d2;font:inherit;font-size:12px;cursor:pointer;text-align:right}.notice-history-empty{padding:54px 12px;text-align:center;color:#94a3b8}
.ui-icon{width:16px;height:16px;display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto}
.ui-icon svg{width:100%;height:100%;stroke:currentColor;stroke-width:1.8;fill:none;stroke-linecap:round;stroke-linejoin:round}
.placeholder-page{height:100%;display:grid;place-items:center;color:#64748b;border:1px dashed #cbd5e1;border-radius:8px;background:#fff}
@media(max-width:1180px){
.kpis,.layout-grid,.management-grid{grid-template-columns:1fr 1fr}
.priority-grid{grid-template-columns:1fr}
.neo-global-nav{overflow:auto;padding:0 18px}
.notice-popover{right:76px}
}
@media(max-width:760px){
.neo-shell{grid-template-rows:58px auto 38px minmax(0,1fr)}
.topbar{padding:0 14px}.system-home-logo{width:170px}.user span{display:none}
.kpis,.layout-grid,.management-grid{grid-template-columns:1fr}
.hero{align-items:flex-start;flex-direction:column}
}
/* S2-gpt · 商机模块(V8.2视觉基准 + Axure工具栏增量) */
#myOpportunity{height:calc(100% + 28px);min-height:0;margin-bottom:-28px}
#oppModuleRoot{height:100%;min-height:0}
.location-bar{font-weight:400;color:#64748b;gap:0}
.location-bar .crumb-btn{border:0;background:transparent;color:#64748b;padding:0;cursor:pointer;font:inherit}
.location-bar .crumb-btn:hover{color:#1677ff}
.location-bar .crumb-sep{padding:0 10px;color:#94a3b8}
.location-bar .crumb-current{color:#172033;font-weight:700}
.opp-app{height:100%;min-height:0;display:grid;grid-template-rows:auto auto minmax(0,1fr);gap:0;color:#172033}
.opp-control-stack{min-width:0;background:#f8fbff}
.opp-app button,.opp-app input,.opp-app select,.opp-app textarea{font-family:inherit}
.opp-top{position:relative;min-height:50px;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 12px;background:#fff;border:1px solid #d6e0ec;border-radius:8px 8px 0 0}
.opp-top-left{display:flex;align-items:center;gap:8px;min-width:0}
.opp-mode-group{display:flex;align-items:center;gap:6px;flex:0 0 auto}
.opp-mode-btn{width:30px;height:30px;padding:0;border:1px solid #d7e2ee;border-radius:6px;background:#fff;color:#64748b;display:grid;place-items:center;cursor:pointer;transition:background .15s,border-color .15s,color .15s,transform .15s,box-shadow .15s}
.opp-mode-btn svg{width:17px;height:17px;fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
.opp-mode-btn:hover{border-color:#93c5fd;background:#eff6ff;color:#1677ff;transform:translateY(-1px);box-shadow:0 4px 10px rgba(37,99,235,.10)}
.opp-mode-btn.active{border-color:#bfdbfe;background:#eaf2ff;color:#1677ff}
.opp-view-title{position:relative;display:flex;align-items:center;gap:5px;height:32px;font-size:17px;font-weight:800;color:#172033;cursor:pointer;white-space:nowrap;user-select:none}
.opp-view-title .chev{font-size:11px;color:#64748b;transition:transform .15s}.opp-view-title.open .chev{transform:rotate(180deg)}
.opp-view-menu{position:absolute;left:0;top:38px;z-index:35;width:420px;min-height:255px;display:none;grid-template-columns:116px 1fr;background:#fff;border:1px solid #d5dfeb;border-radius:8px;box-shadow:0 14px 34px rgba(15,23,42,.16);overflow:hidden;font-size:14px;font-weight:400;cursor:default;transition:width .18s ease}
.opp-view-menu.show{display:grid}.opp-view-menu.is-search{width:744px;min-height:336px}
.opp-view-tabs{display:flex;flex-direction:column;background:#f6f8fb;border-right:1px solid #e1e8f0;padding:8px 0}
.opp-view-tabs button{width:100%;height:40px;padding:0 14px;border:0;background:transparent;text-align:left;color:#334155;cursor:pointer;font-weight:700}
.opp-view-tabs button:hover{background:#eaf2ff;color:#1677ff}.opp-view-tabs button.active{background:#1677ff;color:#fff}
.opp-view-panels{min-width:0}.opp-view-panel{display:none;padding:10px 12px}.opp-view-panel.active{display:block}
.opp-view-row{height:35px;display:grid;grid-template-columns:minmax(0,1fr) 28px;align-items:center;border-radius:5px}
.opp-view-row:hover{background:#f4f8ff}.opp-view-link{height:35px;border:0;background:transparent;text-align:left;padding:0 4px;color:#172033;cursor:pointer;font-size:14px}
.opp-pin{width:26px;height:26px;border:0;background:transparent;color:#1677ff;display:grid;place-items:center;cursor:pointer;border-radius:5px;transition:background .15s,transform .15s}
.opp-pin:hover{background:#eaf2ff;transform:rotate(-8deg)}.opp-pin.active{color:#0b65db}.opp-pin svg{width:17px;height:17px;fill:none;stroke:currentColor;stroke-width:1.9;stroke-linecap:round;stroke-linejoin:round}.opp-pin.active svg path:first-child{fill:#1677ff33}
.opp-view-menu.custom-actions-open{overflow:visible}.opp-view-row.is-custom{position:relative;grid-template-columns:minmax(0,1fr) 28px 28px}.opp-more-action{width:26px;height:26px;border:0;border-radius:5px;background:transparent;color:#64748b;display:grid;place-items:center;cursor:pointer;font-size:18px;line-height:1;opacity:0;transition:opacity .15s,background .15s,color .15s}.opp-view-row.is-custom:hover .opp-more-action,.opp-more-action.active,.opp-more-action:focus-visible{opacity:1}.opp-more-action:hover,.opp-more-action.active{background:#eaf2ff;color:#1677ff}.opp-saved-search-menu{position:absolute;right:28px;top:32px;z-index:45;display:block!important;width:132px;box-sizing:border-box;padding:5px;background:#fff;border:1px solid #d5dfeb;border-radius:6px;box-shadow:0 10px 24px rgba(15,23,42,.16);white-space:normal!important}.opp-saved-search-menu>button{position:static!important;display:block!important;float:none!important;width:100%!important;height:32px!important;margin:0!important;padding:0 10px!important;border:0!important;border-radius:4px!important;background:transparent!important;text-align:left!important;color:#334155!important;cursor:pointer}.opp-saved-search-menu>button:hover{background:#f3f8ff!important;color:#1677ff!important}.opp-saved-search-menu>button.danger{color:#dc2626!important}.opp-view-row.is-renaming{grid-template-columns:minmax(0,1fr) 60px}.opp-rename-input{width:100%;height:29px;box-sizing:border-box;border:1px solid #1677ff;border-radius:4px;padding:0 7px;font-size:14px;outline:none}.opp-rename-actions{display:flex;align-items:center;gap:2px}.opp-rename-actions button{width:28px;height:28px;border:0;border-radius:4px;background:transparent;cursor:pointer}.opp-rename-actions .confirm{color:#1677ff}.opp-rename-actions .cancel{color:#64748b}.opp-rename-actions button:hover{background:#eaf2ff}
.opp-search-panel{display:grid;grid-template-columns:102px minmax(0,1fr);gap:13px 10px;align-items:center;padding:2px 0 4px}.opp-search-panel>span:first-child,.opp-search-panel>span:nth-of-type(2),.opp-search-panel>span:nth-of-type(4){font-weight:700;color:#334155}
.opp-search-panel input,.opp-search-panel select{height:34px;border:1px solid #ccd8e5;border-radius:4px;background:#fff;padding:0 10px}.opp-search-panel .wide{width:100%}.opp-search-hint{color:#64748b}.opp-search-panel .is-invalid{border-color:#e11d48!important;box-shadow:0 0 0 2px rgba(225,29,72,.08)}.opp-custom-error{min-height:18px;color:#e11d48;font-size:12px;line-height:18px}
.opp-condition-row{display:grid;grid-template-columns:24px 150px 100px minmax(120px,1fr) 26px;gap:8px;align-items:center}.opp-condition-row .remove{height:32px;border:1px solid #d7e2ee;background:#fff;border-radius:4px;color:#ef4444;cursor:pointer}.opp-condition-row .remove:hover{background:#fff1f2}
.opp-more-cond{border:0;background:transparent;color:#1677ff;text-align:left;padding:0;cursor:pointer;font-weight:700}.opp-advanced{display:flex;align-items:center;gap:7px;color:#64748b}.opp-advanced input{width:15px;height:15px}
.opp-sort-row{display:grid;grid-template-columns:minmax(220px,1fr) 92px 92px;gap:10px}.opp-sort-row button{height:34px;border:1px solid #ccd8e5;background:#fff;border-radius:4px;color:#334155}.opp-sort-row button.active{background:#94a3b8;color:#fff;border-color:#94a3b8}
.opp-search-actions{grid-column:1/3;display:flex;justify-content:flex-end;gap:10px;padding-top:12px;border-top:1px solid #edf2f7}.opp-search-actions .save{border-radius:18px}
.opp-toolbar{min-height:48px;display:flex;align-items:center;gap:8px;padding:7px 12px;background:#f8fbff;border:1px solid #d6e0ec;border-top:0}
.opp-toolbar-left{display:flex;align-items:center;gap:8px;min-width:0;flex:1}.opp-toolbar-right{display:flex;align-items:center;gap:5px;margin-left:auto;flex:0 0 auto}
.opp-toolbar input,.opp-toolbar select{height:32px;border:1px solid #cfd9e5;border-radius:4px;background:#fff;padding:0 10px;color:#334155}
.opp-toolbar .opp-query-input{width:270px;border-radius:16px;padding:0 14px}.opp-toolbar select{min-width:96px}
.opp-stats{display:flex;align-items:stretch;gap:8px;padding:9px 12px 3px;background:#f8fbff;border-left:1px solid #d6e0ec;border-right:1px solid #d6e0ec;overflow-x:auto}
.opp-stat-card{min-width:128px;min-height:46px;flex:1;display:flex;align-items:center;justify-content:center;gap:7px;padding:6px 12px;border:1px solid #c9d5e3;border-radius:6px;background:#fff;color:#475569;white-space:nowrap}
.opp-stat-card span{font-size:12px}.opp-stat-card b{font-size:18px;color:#124ecb}
.opp-tool-icon{position:relative;width:32px;height:32px;padding:0;border:1px solid transparent;border-radius:5px;background:transparent;color:#475569;display:grid;place-items:center;cursor:pointer;transition:all .15s}
.opp-tool-icon svg{width:17px;height:17px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}.opp-tool-icon:hover,.opp-tool-icon.active{background:#eaf2ff;border-color:#bfdbfe;color:#1677ff;transform:translateY(-1px)}
.opp-tool-icon.refreshing svg{animation:oppSpin .55s linear}@keyframes oppSpin{to{transform:rotate(360deg)}}
.opp-popover{position:absolute;z-index:30;top:38px;right:0;display:none;background:#fff;border:1px solid #d5dfeb;border-radius:7px;box-shadow:0 12px 28px rgba(15,23,42,.15);padding:8px}.opp-popover.show{display:block}
.opp-density-pop{width:154px}.opp-density-pop button{width:100%;height:34px;border:0;background:#fff;border-radius:5px;text-align:left;padding:0 10px;color:#334155;cursor:pointer}.opp-density-pop button:hover,.opp-density-pop button.active{background:#eaf2ff;color:#1677ff}
.opp-fields-pop{width:265px;padding:10px}.opp-fields-pop h4{margin:0 0 8px;font-size:14px}.opp-field-list{max-height:280px;overflow:auto;display:grid;grid-template-columns:1fr 1fr;gap:2px}.opp-field-list label{height:30px;display:flex;align-items:center;gap:7px;padding:0 5px;border-radius:4px;color:#334155}.opp-field-list label:hover{background:#f4f8ff}.opp-fields-foot{display:flex;justify-content:flex-end;gap:7px;padding-top:9px;border-top:1px solid #edf2f7;margin-top:8px}
.opp-stage-chips{display:flex;align-items:center;gap:5px;overflow:auto}.opp-stage-chip{height:26px;border:1px solid #b9d5ff;background:#eff6ff;color:#1677ff;border-radius:13px;padding:0 10px;white-space:nowrap;cursor:pointer}.opp-stage-chip.active{background:#1677ff;color:#fff}
.opp-main{min-height:0;background:#fff;border:1px solid #d6e0ec;border-top:0;border-radius:0 0 8px 8px;overflow:hidden}
.opp-list-shell{height:100%;min-height:0;display:grid;grid-template-rows:minmax(0,1fr) 44px}
.opp-table-wrap{min-height:0;overflow:auto;scrollbar-color:#8b8b8b #e8ebef;scrollbar-width:auto}.opp-table{width:100%;min-width:1515px;border-collapse:collapse;font-size:13px}
.opp-table th,.opp-table td{height:58px;padding:8px 12px;border-bottom:1px solid #e5ecf3;text-align:left;white-space:nowrap;vertical-align:middle}.opp-table th{height:39px;position:sticky;top:0;z-index:3;background:#f6f9fd;color:#334155;font-weight:800}
.opp-table tbody tr{transition:background .12s}.opp-table tbody tr:hover td{background:#f8fbff}.opp-table tbody tr:hover .opp-name-link{text-decoration:underline}
.opp-app.density-compact .opp-table td{height:44px;padding-top:5px;padding-bottom:5px}.opp-app.density-compact .opp-table th{height:34px}.opp-app.density-relaxed .opp-table td{height:66px;padding-top:12px;padding-bottom:12px}.opp-app.density-relaxed .opp-table th{height:44px}
.opp-name-link,.opp-text-link{border:0;background:transparent;padding:0;color:#0563e8;cursor:pointer;font:inherit}.opp-name-link{font-weight:800}.opp-name-link:hover,.opp-text-link:hover{text-decoration:underline}
.opp-industry{display:inline-flex;align-items:center;height:25px;border:1px solid #8fc3ff;border-radius:3px;padding:0 7px;background:#eff6ff;color:#1677ff}.opp-node-pill{display:inline-flex;align-items:center;height:25px;border-radius:13px;padding:0 9px;background:#eef2f7;color:#667085}
.opp-row-actions{display:flex;gap:5px}.opp-pagination{height:44px;border-top:1px solid #e5ecf3;display:flex;align-items:center;justify-content:flex-end;gap:11px;padding:0 16px;color:#475569;font-size:13px}.opp-pagination select{height:30px}.opp-page-buttons{display:flex;align-items:center;gap:2px}.opp-page-buttons button{min-width:28px;height:28px;border:0;background:#fff;border-radius:4px;color:#475569;cursor:pointer}.opp-page-buttons button:hover{background:#eff6ff;color:#1677ff}.opp-page-buttons button.active{background:#1677ff;color:#fff}.opp-jump{display:flex;align-items:center;gap:6px}.opp-jump input{width:40px;height:28px;text-align:center;padding:0}
.opp-split-shell{height:100%;min-height:0;display:grid;grid-template-columns:330px 9px minmax(650px,1fr);overflow:hidden}.opp-split-shell.collapsed{grid-template-columns:0 0 minmax(650px,1fr)}
.opp-split-list{min-width:0;overflow:auto;background:#fff}.opp-split-shell.collapsed .opp-split-list{visibility:hidden;pointer-events:none}.opp-split-table-head{height:38px;position:sticky;top:0;z-index:2;display:grid;grid-template-columns:44px minmax(0,1fr) 76px;align-items:center;padding:0 9px;background:#f6f9fd;border-bottom:1px solid #dfe7f1;font-size:12px;font-weight:800;color:#475569}
.opp-split-row{min-height:72px;display:grid;grid-template-columns:44px minmax(0,1fr) 76px;align-items:start;padding:10px 9px;border-bottom:1px solid #edf2f7;cursor:pointer;transition:background .13s,box-shadow .13s}.opp-split-row:hover{background:#f8fbff}.opp-split-row.active{background:#eaf2ff;box-shadow:inset 3px 0 #1677ff}.opp-split-row b{display:block;color:#0563e8;margin-bottom:4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.opp-split-row small{display:block;color:#667085;line-height:1.45;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.opp-split-divider{position:relative;background:#eef2f7;border-left:1px solid #d8e1eb;border-right:1px solid #d8e1eb}.opp-split-toggle{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:18px;height:46px;padding:0;border:1px solid #cbd5e1;border-radius:0 6px 6px 0;background:#fff;color:#1677ff;cursor:pointer;z-index:3}.opp-split-shell.collapsed .opp-split-divider{border:0;background:transparent}.opp-split-shell.collapsed .opp-split-toggle{left:0;transform:translate(0,-50%)}
.opp-split-detail{min-width:0;overflow:auto;background:#fff}.opp-split-detail .opp-detail{border:0;border-radius:0;min-height:100%}.opp-split-detail .opp-detail-head{padding:14px 16px 10px}.opp-split-detail .opp-detail-title h1{font-size:20px}.opp-split-detail .opp-detail-back{display:none}.opp-split-detail .opp-detail-meta{gap:0 22px}.opp-split-detail .opp-detail-tabs button{padding:0 12px}.opp-split-detail .opp-detail-body{padding:12px 16px 18px}
.opp-board-shell{height:100%;min-height:0;overflow:auto;background:#edf2f7;padding:10px}.opp-board-type{height:30px;border:1px solid #cbd5e1;background:#fff;border-radius:5px;padding:0 12px;margin-bottom:8px;color:#334155;cursor:pointer}.opp-board-scope{min-width:1100px;display:flex;align-items:center;justify-content:space-between;gap:16px;margin-bottom:8px;padding:8px 11px;border:1px solid #cbdcf0;border-radius:5px;background:#f8fbff;color:#475569;font-size:12px}.opp-board-scope b{color:#173b64}.opp-board-scope em{font-style:normal;color:#64748b}
.opp-board-pipeline{min-width:1100px;display:grid;grid-template-columns:repeat(5,minmax(190px,1fr));gap:0;margin-bottom:8px}.opp-pipe-stage{height:42px;position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;background:#0f9f85;color:#fff;font-weight:800;font-size:13px;clip-path:polygon(0 0,93% 0,100% 50%,93% 100%,0 100%,7% 50%)}.opp-pipe-stage:first-child{clip-path:polygon(0 0,93% 0,100% 50%,93% 100%,0 100%)}.opp-pipe-stage small{font-size:11px;font-weight:500;color:#d8fff7;margin-top:2px}
.opp-board-lanes{min-width:1100px;min-height:calc(100% - 80px);display:grid;grid-template-columns:repeat(5,minmax(190px,1fr));gap:10px;align-items:start}.opp-board-lane{min-height:430px;padding:0 0 20px}.opp-board-card{margin-bottom:10px;padding:11px 12px;background:#fff;border:1px solid #dce5ef;border-radius:3px;box-shadow:0 1px 2px rgba(15,23,42,.04);cursor:pointer;transition:border-color .15s,box-shadow .15s,transform .15s}.opp-board-card:hover{border-color:#60a5fa;box-shadow:0 8px 20px rgba(15,23,42,.12);transform:translateY(-2px)}.opp-board-card b{display:block;color:#0563e8;font-size:13px;margin-bottom:5px}.opp-board-card span{display:block;color:#475569;font-size:12px;line-height:1.45}.opp-board-card .client{color:#1677ff;margin-top:4px}.opp-board-more{width:100%;min-height:34px;border:1px dashed #9bb9df;border-radius:4px;background:#f8fbff;color:#1565c0;cursor:pointer;font-size:12px}.opp-board-more:hover{border-color:#1677ff;background:#eef6ff}.opp-board-loaded{margin:1px 0 8px;color:#64748b;text-align:center;font-size:12px}.opp-board-empty{padding:18px 10px;color:#94a3b8;text-align:center;font-size:12px}
.opp-detail{min-height:100%;background:#fff;border:1px solid #d6e0ec;border-radius:8px;overflow:hidden}.opp-detail-head{padding:18px 20px 10px;border-bottom:1px solid #e5edf7;background:#fff}.opp-detail-title{display:flex;justify-content:space-between;align-items:flex-start;gap:12px}.opp-detail-title h1{margin:0 0 7px;font-size:24px;color:#15385f}.opp-detail-tags{display:flex;gap:6px}.opp-detail-tag{display:inline-flex;height:25px;align-items:center;padding:0 11px;border:1px solid #0ea5e9;border-radius:13px;color:#0369a1;background:#f0f9ff;font-size:12px}.opp-detail-back{height:32px;border:1px solid #d7e2ee;background:#fff;border-radius:5px;padding:0 12px;cursor:pointer}.opp-detail-back:hover{border-color:#93c5fd;background:#f8fbff;color:#1677ff}
.opp-detail-meta{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:0 28px;margin-top:14px;font-size:13px;color:#6a788b}.opp-detail-meta div{padding:6px 0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.opp-detail-meta span{color:#263b55;font-weight:500}.opp-detail-actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:13px}.opp-detail-actions .danger{border-color:#fca5a5;color:#dc2626}.opp-detail-actions .warn{border-color:#fdba74;color:#ea580c;background:#fff7ed}
.opp-stage-wrap{padding:0;background:#fff}.opp-stage-arrow{display:grid;grid-template-columns:repeat(5,minmax(140px,1fr));gap:8px;padding:10px 0 8px}.opp-stage-node{height:38px;display:flex;align-items:center;justify-content:center;gap:8px;background:#ddd;color:#111;font-size:13px;clip-path:polygon(0 0,92% 0,100% 50%,92% 100%,0 100%,8% 50%)}.opp-stage-node.done,.opp-stage-node.active{background:#dffaff}.opp-stage-node i{width:18px;height:18px;border-radius:50%;background:#111;color:#fff;display:inline-flex;align-items:center;justify-content:center;font-style:normal;font-size:12px}
.opp-guide-title{padding:8px 0 10px;font-weight:700;color:#163b65}.opp-guide-title small{font-weight:400;color:#718096;margin-left:10px}
.opp-detail-tabs{height:44px;display:flex;gap:0;border-top:1px solid #dbe4ef;border-bottom:1px solid #dbe4ef;padding:0 10px;background:#fff;overflow:auto}.opp-detail-tabs button{height:43px;padding:0 16px;background:#fff;border:0;border-bottom:3px solid transparent;white-space:nowrap;cursor:pointer;color:#172033}.opp-detail-tabs button:hover{color:#1677ff}.opp-detail-tabs button.active{border-bottom-color:#1677ff;color:#1677ff;font-weight:700}
.opp-detail-body{padding:16px 20px 24px;background:#fff}.opp-tab-panel{display:none}.opp-tab-panel.active{display:block}.opp-overview{display:grid;grid-template-columns:minmax(0,1.8fr) minmax(300px,.8fr);gap:18px}.opp-overview aside{border-left:1px solid #e5ebf2;padding-left:18px}
.opp-section{margin-bottom:14px}.opp-section-title{height:32px;display:flex;align-items:center;gap:7px;font-weight:800;border-bottom:1px solid #e7ecf2}.opp-section-title:before{content:'☒';font-weight:400;color:#64748b}.opp-section-title a{margin-left:auto;color:#0284c7;font-weight:500;cursor:pointer}.opp-section-title a:hover{text-decoration:underline}
.opp-data-table{width:100%;border-collapse:collapse;font-size:13px}.opp-data-table th,.opp-data-table td{border:1px solid #dbe4ef;padding:9px 11px;text-align:left}.opp-data-table th{background:#f7f9fc;color:#63748a;font-weight:500}.opp-data-table td{color:#1f344d}.opp-data-table tr:hover td{background:#fbfdff}.opp-info-grid{display:grid;grid-template-columns:130px 1fr 130px 1fr;border-top:1px solid #dbe4ef;border-left:1px solid #dbe4ef}.opp-info-grid label,.opp-info-grid span{min-height:39px;padding:9px 11px;border-right:1px solid #dbe4ef;border-bottom:1px solid #dbe4ef}.opp-info-grid label{background:#f7f9fc;color:#63748a;text-align:center}.opp-info-grid span{background:#fff;color:#1f344d}.opp-wide{grid-column:2/5}
.opp-timeline{border-left:2px solid #d7e5fb;margin:6px 0 0 8px;padding-left:18px}.opp-time-item{position:relative;margin-bottom:22px;font-size:13px;line-height:1.55}.opp-time-item:before{content:'';position:absolute;left:-24px;top:3px;width:10px;height:10px;border-radius:50%;background:#3b82f6}.opp-time-item b{display:block;margin-bottom:3px}.opp-time-item p{margin:0;color:#475569}
.opp-cards{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.opp-card-item{border:1px solid #dbe4ef;border-radius:7px;padding:12px;background:#fff;transition:border-color .15s,box-shadow .15s}.opp-card-item:hover{border-color:#93c5fd;box-shadow:0 8px 18px rgba(15,23,42,.07)}.opp-card-item h4{margin:0 0 9px;color:#173b64}.opp-card-meta{display:grid;grid-template-columns:repeat(3,1fr);gap:7px}.opp-card-meta span{padding:7px;background:#f8fafc;border:1px solid #edf2f7;border-radius:4px;font-size:12px;color:#475569}
.opp-attachment-card{border:1px solid #dbe4ef;border-radius:8px;background:#fff;padding:12px 14px;margin-bottom:10px}.opp-attachment-card:hover{border-color:#93c5fd}.opp-attachment-title{font-weight:800;color:#475569;margin-bottom:9px}.opp-status{display:inline-flex;height:22px;align-items:center;border-radius:4px;padding:0 8px;font-size:12px;font-weight:700}.opp-status.done{background:#ecfdf3;color:#047857}.opp-status.wait{background:#fff7ed;color:#c2410c}.opp-status.todo{background:#f1f5f9;color:#475569}
.opp-member-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}.opp-member{border:1px solid #dbe4ef;border-radius:7px;padding:11px;display:grid;grid-template-columns:38px 1fr;gap:10px;align-items:center}.opp-member-avatar{width:38px;height:38px;border-radius:50%;background:#eaf2ff;color:#1677ff;display:grid;place-items:center;font-weight:800}.opp-member b{display:block;margin-bottom:3px}.opp-member small{color:#64748b}
.opp-modal-wide{width:min(900px,calc(100vw - 64px))!important}.opp-modal-form{display:grid;grid-template-columns:120px minmax(0,1fr) 120px minmax(0,1fr);gap:12px 10px;align-items:center}.opp-modal-form label{color:#475569}.opp-modal-form input,.opp-modal-form select,.opp-modal-form textarea{width:100%;height:34px;border:1px solid #d2dce8;border-radius:5px;padding:0 10px;background:#fff}.opp-modal-form textarea{height:76px;padding:8px 10px;resize:vertical}.opp-modal-form input[type="checkbox"]{width:15px;height:15px;padding:0;margin:0 6px 0 0;vertical-align:middle}.opp-modal-form .full{grid-column:2/5}.opp-form-section{grid-column:1/5;font-weight:800;color:#173b64;padding:7px 0;border-bottom:1px solid #e5ebf2}.opp-modal-note{grid-column:1/5;padding:9px 11px;background:#fff8e8;color:#7a5b16;border-radius:6px}.opp-modal-foot{display:flex;justify-content:flex-end;gap:8px}
.opp-toast{position:fixed;left:50%;top:72px;z-index:330;transform:translate(-50%,-10px);padding:9px 14px;border-radius:6px;background:#172033;color:#fff;box-shadow:0 10px 24px rgba(15,23,42,.18);opacity:0;pointer-events:none;transition:opacity .18s,transform .18s}.opp-toast.show{opacity:1;transform:translate(-50%,0)}
@media(max-width:1180px){.opp-toolbar .opp-query-input{width:235px}.opp-toolbar-left{overflow:auto}.opp-detail-meta{grid-template-columns:repeat(2,minmax(0,1fr))}.opp-overview{grid-template-columns:1fr}.opp-overview aside{border-left:0;border-top:1px solid #e5ebf2;padding:16px 0 0}.opp-view-menu.is-search{width:min(744px,calc(100vw - 120px))}.opp-split-shell{grid-template-columns:290px 9px minmax(590px,1fr)}.opp-split-shell.collapsed{grid-template-columns:0 0 minmax(590px,1fr)}}
/* S2-gpt verified repair batch: 2026-07-10 */
.crumb-parent{color:#64748b;font-weight:400;cursor:default}.crumb-current{color:#172033;font-weight:700}.crumb-btn{border:0;background:transparent;padding:0;color:#64748b;cursor:pointer;font:inherit;font-weight:400}.crumb-btn:hover{color:#1677ff}.crumb-sep{margin:0 9px;color:#94a3b8}
.opp-view-menu{width:430px;min-height:278px;grid-template-columns:120px 1fr;font-size:16px}.opp-view-menu.is-search{width:760px;min-height:340px}.opp-view-tabs button{height:48px;padding:0 18px;font-size:16px}.opp-view-panel{padding:11px 18px}.opp-view-row{height:43px;grid-template-columns:minmax(0,1fr) 36px}.opp-view-link{height:43px;font-size:16px;padding:0 8px}.opp-pin{width:32px;height:32px;color:#1677ff!important}.opp-pin .classic-pushpin{width:20px;height:20px;overflow:visible;fill:none;stroke:currentColor;stroke-width:1.9;stroke-linecap:round;stroke-linejoin:round}.opp-pin .pin-body{fill:#fff}.opp-pin.active .pin-body{fill:currentColor}.opp-pin .pin-needle{fill:none;stroke-width:2}.opp-pin:hover{transform:none;background:#f3f8ff}.opp-view-row:hover{background:#f3f8ff}
.opp-stage-node{border:0;font:inherit;cursor:pointer;transition:filter .16s,transform .16s,box-shadow .16s}.opp-stage-node:hover{filter:saturate(1.15);transform:translateY(-1px);box-shadow:0 4px 12px rgba(37,99,235,.16)}.opp-stage-node:focus-visible{outline:2px solid #1677ff;outline-offset:2px}
.opp-bygpt-overview{display:grid;grid-template-columns:minmax(0,1fr) 290px;gap:16px}.opp-bygpt-main{min-width:0}.opp-bygpt-side{border-left:1px solid #e5ebf2;padding-left:16px}.opp-bygpt-table{width:100%;border-collapse:collapse;font-size:12px;table-layout:fixed}.opp-bygpt-table th,.opp-bygpt-table td{border:1px solid #cbd5e1;padding:7px 9px;line-height:1.5;vertical-align:middle}.opp-bygpt-table th{width:18%;background:#f3f6fa;color:#64748b;font-weight:500;text-align:center}.opp-bygpt-table td{background:#fff}.opp-bygpt-table .danger{color:#e11d48;font-weight:700}.opp-bygpt-chip{display:inline-block;border:1px solid #60a5fa;background:#eff6ff;color:#1d4ed8;padding:1px 8px}.opp-bygpt-follow{display:grid;gap:14px;border-left:2px solid #d7e5fb;margin:6px 0 0 8px;padding-left:16px}.opp-bygpt-follow>div{position:relative}.opp-bygpt-follow>div:before{content:'';position:absolute;left:-22px;top:3px;width:8px;height:8px;border-radius:50%;background:#3b82f6}.opp-bygpt-follow p{margin:4px 0;color:#334155}.opp-bygpt-follow small{color:#64748b}
@media(max-width:1180px){.opp-bygpt-overview{grid-template-columns:1fr}.opp-bygpt-side{border-left:0;border-top:1px solid #e5ebf2;padding:14px 0 0}.opp-setting-grid{grid-template-columns:1fr}}
@media(min-width:1280px) and (min-height:760px){
.neo-shell.home-active .content{overflow:hidden;padding:10px 16px 12px}
#home.active{height:100%;min-height:0;display:grid;grid-template-rows:64px 82px 22px minmax(148px,1fr) minmax(148px,1fr) 82px;gap:8px}
#home .hero,#home .kpis,#home .section-title,#home .layout-grid,#home .management-grid,#home .priority-grid{margin:0;min-height:0}
#home .hero{height:64px;padding:8px 14px}.hero h1{font-size:20px;margin-bottom:3px}.hero p{font-size:12px}
#home .kpis{height:82px;gap:9px}.kpi{min-height:0;height:82px;padding:8px 11px}.kpi .num{font-size:23px;margin-top:3px}.kpi small{font-size:11px}.kpi b{font-size:11px}
#home .section-title{height:22px}.section-title h2{font-size:15px}
#home .layout-grid,#home .management-grid,#home .priority-grid{height:100%;gap:8px}
#home .card{height:100%;display:grid;grid-template-rows:34px minmax(0,1fr)}#home .card-head{height:34px;padding:0 10px;font-size:13px}#home .card-body{min-height:0;overflow:hidden;padding:7px 10px}
#home .todo li{padding:4px 0;font-size:12px}.funnel-visual{gap:4px;padding:0}.funnel-step{grid-template-columns:48px minmax(0,1fr) 20px;gap:6px}.funnel-bar-wrap{height:17px}.ai-box{padding:6px;margin-bottom:5px;line-height:1.38;font-size:11px}.assistant-actions .btn{height:25px;font-size:11px}
#home .table th,#home .table td{height:24px;font-size:11px;padding:0 5px}.customer{padding:4px 0;font-size:12px}.double-five-grid{gap:5px}.double-five-item{padding:4px}.ring{width:35px;height:35px;margin:2px auto}.ring:after{inset:5px}.risk-list{gap:4px 12px}.risk-list li{font-size:12px}.quick-grid{grid-template-columns:repeat(6,minmax(0,1fr));gap:5px}.ai-btn{height:28px;padding:0 8px;font-size:12px}
}
/* S2-gpt · A3 商机模块完整合并 2026-07-10 */
.opp-view-menu{width:420px!important;min-height:255px!important;grid-template-columns:116px 1fr!important;font-size:14px!important}
.opp-view-menu.is-search{width:744px!important;min-height:336px!important}
.opp-view-tabs button{height:40px!important;padding:0 14px!important;font-size:14px!important}
.opp-view-panel{padding:10px 12px!important}.opp-view-row{height:35px!important;grid-template-columns:minmax(0,1fr) 28px!important}
.opp-view-link{height:35px!important;font-size:14px!important;padding:0 4px!important}
.opp-pin{width:24px!important;height:24px!important;color:#1677ff!important}
.opp-pin .classic-pushpin{width:15px!important;height:15px!important;overflow:visible;fill:none;stroke:currentColor;stroke-width:1.9;stroke-linecap:round;stroke-linejoin:round}
.opp-pin .pin-body{fill:#fff!important}.opp-pin.active .pin-body{fill:currentColor!important}.opp-pin .pin-needle{fill:none!important;stroke-width:2!important}
.opp-pin:hover{transform:none!important;background:#f3f8ff!important}
.opp-view-row.is-custom{grid-template-columns:minmax(0,1fr) 28px 28px!important}.opp-view-row.is-renaming{grid-template-columns:minmax(0,1fr) 60px!important}
.opp-detail{min-height:100%;background:#fff;border:1px solid #d6e0ec;border-radius:8px;overflow:hidden}
.opp-detail-head{padding:14px 16px 10px;background:#fff;border-bottom:0}.opp-detail-title h1{font-size:20px;color:#1f2937}
.opp-detail-meta{grid-template-columns:repeat(4,minmax(0,1fr));font-size:12px;gap:0 32px;margin-top:10px}
.opp-detail-actions{gap:10px;margin-top:10px}.opp-detail-actions .btn{height:30px;min-width:72px}
.opp-guide-title{display:flex;align-items:center;gap:8px;padding:10px 16px 0;font-weight:800;color:#334155}.opp-guide-title small{font-weight:400;color:#64748b}
.opp-stage-track{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:8px;padding:10px 16px 12px}
.opp-stage-node{height:36px;display:flex;align-items:center;justify-content:center;gap:7px;background:#e5e7eb;color:#334155;clip-path:polygon(0 0,92% 0,100% 50%,92% 100%,0 100%,8% 50%);border:0;cursor:pointer;font:inherit}
.opp-stage-node.done,.opp-stage-node.active{background:#dff6ff;color:#075985}.opp-stage-node.active{font-weight:800;box-shadow:inset 0 0 0 1px #38bdf8}.opp-stage-node i{width:18px;height:18px;border-radius:50%;background:#111827;color:#fff;display:grid;place-items:center;font-style:normal;font-size:11px}
.opp-detail-tabs{height:43px;padding:0 16px}.opp-detail-tabs button{height:42px;padding:0 17px;font-size:13px}.opp-detail-body{padding:12px 16px 20px}
.a3-grid{display:grid;gap:12px}.a3-two-col{display:grid;grid-template-columns:minmax(0,1fr) 280px;gap:16px}.a3-side{border-left:1px solid #e5ebf2;padding-left:16px}
.a3-section{margin-bottom:14px}.a3-section-head{height:34px;display:flex;align-items:center;gap:7px;font-weight:800;color:#1f2937}.a3-section-head:before{content:'☑';font-weight:400;color:#64748b}.a3-section-head .spacer{margin-left:auto}.a3-section-head button,.a3-section-head a{margin-left:auto;border:0;background:transparent;color:#0284c7;cursor:pointer;font-weight:500}
.a3-table{width:100%;border-collapse:collapse;font-size:12px;table-layout:fixed}.a3-table th,.a3-table td{border:1px solid #cbd5e1;padding:7px 9px;line-height:1.5;vertical-align:middle;word-break:break-word}.a3-table th{background:#f3f4f6;color:#64748b;font-weight:500;text-align:center}.a3-table td{background:#fff}.a3-table thead th{background:#f7f9fc;color:#475569;font-weight:700;text-align:left}.a3-table .danger{color:#e11d48;font-weight:700}.a3-table .link{color:#1677ff;cursor:pointer}.a3-table .nowrap{white-space:nowrap}.a3-table .center{text-align:center}.a3-table .muted{color:#64748b}
.a3-plan-list{border:1px solid #cbd5e1}.a3-plan-item{display:grid;grid-template-columns:minmax(0,1fr) 150px 180px 70px 220px;gap:10px;padding:9px 12px;border-bottom:1px solid #dbe4ef;align-items:center;font-size:12px}.a3-plan-item:last-child{border-bottom:0}.a3-plan-item b{font-weight:500}.a3-plan-item .danger{color:#e11d48;font-weight:700}
.a3-timeline{border-left:2px solid #d7e5fb;margin:6px 0 0 10px;padding-left:18px}.a3-time-item{position:relative;margin-bottom:20px;font-size:12px;line-height:1.55}.a3-time-item:before{content:'';position:absolute;left:-24px;top:2px;width:10px;height:10px;border-radius:50%;background:#3b82f6}.a3-time-item b{display:block;margin-bottom:3px}.a3-time-item small{color:#64748b}.a3-time-item p{margin:4px 0;color:#334155}
.a3-tab-tools{display:flex;align-items:center;gap:8px;margin-bottom:10px}.a3-tab-tools .grow{flex:1}.a3-tab-tools input,.a3-tab-tools select{height:32px;border:1px solid #cfd9e5;border-radius:5px;padding:0 9px}.a3-subtabs{display:flex;gap:6px;margin-bottom:10px}.a3-subtabs button{height:30px;border:1px solid #d7e2ee;background:#fff;border-radius:15px;padding:0 12px;color:#475569;cursor:pointer}.a3-subtabs button.active{background:#eaf2ff;border-color:#93c5fd;color:#1677ff;font-weight:700}
.a3-card-grid{display:grid;grid-template-columns:minmax(0,1fr);gap:12px;align-items:start}.a3-card{border:1px solid #dbe4ef;border-radius:7px;background:#fff;overflow:hidden}.a3-card-head{height:40px;padding:0 12px;background:#f8fafc;border-bottom:1px solid #e5ebf2;display:flex;align-items:center;justify-content:space-between;font-weight:800}.a3-card-body{padding:12px}.a3-kv{display:grid;grid-template-columns:120px minmax(0,1fr);border-top:1px solid #dbe4ef;border-left:1px solid #dbe4ef}.a3-kv label,.a3-kv span{padding:8px 10px;border-right:1px solid #dbe4ef;border-bottom:1px solid #dbe4ef;font-size:12px}.a3-kv label{background:#f8fafc;color:#64748b}.a3-kv span{background:#fff}
.scheme-overview{margin-bottom:12px;border:1px solid #dbe4ef;border-radius:7px;background:#fff;overflow:auto}.scheme-overview .a3-table{min-width:880px}.scheme-overview tbody tr{cursor:pointer}.scheme-overview tbody tr:hover td,.scheme-overview tbody tr.active td{background:#eef6ff}.scheme-overview .scheme-overview-name{color:#1677ff;font-weight:700}.scheme-card-title{display:flex;align-items:center;gap:8px}.scheme-card-focused{border-color:#8bbcff;box-shadow:0 0 0 2px rgba(22,119,255,.08)}.scheme-card-actions{margin:0;display:flex;align-items:center;gap:10px}.scheme-card-actions button{border:0;background:transparent;color:#1677ff;padding:0;cursor:pointer}.scheme-card-empty{padding:4px 0;color:#8a94a6;font-size:12px}
.a3-status{display:inline-flex;height:24px;align-items:center;padding:0 8px;border-radius:12px;background:#eef2f7;color:#475569;font-size:12px}.a3-status.blue{background:#eaf2ff;color:#1677ff}.a3-status.green{background:#ecfdf3;color:#047857}.a3-status.orange{background:#fff7ed;color:#c2410c}.a3-status.red{background:#fff1f2;color:#be123c}
.a3-file{display:grid;grid-template-columns:34px minmax(0,1fr) 120px 100px 130px;gap:10px;align-items:center;padding:10px 12px;border-bottom:1px solid #edf2f7}.a3-file:last-child{border-bottom:0}.a3-file-icon{width:30px;height:34px;border:1px solid #cbd5e1;border-radius:4px;background:#f8fafc;display:grid;place-items:center;color:#64748b;font-size:10px;font-weight:800}.a3-file b{display:block}.a3-file small{color:#64748b}
.a3-member-perms{max-width:310px;white-space:normal;line-height:1.5;color:#475569}.a3-actions{display:flex;gap:7px;flex-wrap:wrap}.a3-actions button{border:0;background:transparent;color:#1677ff;cursor:pointer;padding:0}
.a3-pagination{height:42px;padding:0 12px;display:flex;align-items:center;justify-content:flex-end;gap:8px;border-top:1px solid #edf2f7}.a3-pagination button{height:26px;min-width:26px;border:1px solid #d7e2ee;background:#fff;border-radius:4px}.a3-pagination button.active{background:#1677ff;color:#fff;border-color:#1677ff}
.a3-empty{min-height:180px;border:1px dashed #cbd5e1;border-radius:8px;display:grid;place-items:center;color:#64748b;background:#fbfdff}
.opp-modal-wide{width:min(940px,calc(100vw - 48px))!important}.opp-modal-form{display:grid;grid-template-columns:140px minmax(0,1fr) 140px minmax(0,1fr);gap:12px 10px;align-items:center}.opp-modal-form label{font-size:12px;color:#64748b;text-align:right}.opp-modal-form input,.opp-modal-form select,.opp-modal-form textarea{width:100%;height:34px;border:1px solid #cfd9e5;border-radius:5px;padding:0 10px}.opp-modal-form textarea{height:76px;padding:8px 10px;resize:none}.opp-modal-form .full{grid-column:2/5}.opp-form-section{grid-column:1/5;background:#f8fafc;border-left:3px solid #1677ff;padding:8px 10px;font-weight:800}.opp-modal-foot{display:flex;justify-content:flex-end;gap:8px}.opp-upload-box{grid-column:2/5;border:1px dashed #94a3b8;border-radius:7px;background:#f8fbff;min-height:80px;display:grid;place-items:center;color:#64748b;text-align:center;padding:12px}.opp-check-grid{grid-column:2/5;display:grid;grid-template-columns:repeat(3,1fr);gap:8px}.opp-check-grid label{text-align:left;border:1px solid #e2e8f0;border-radius:5px;padding:8px;color:#334155}.opp-push-steps{grid-column:1/5;display:grid;grid-template-columns:repeat(4,1fr);gap:8px}.opp-push-steps span{height:34px;display:grid;place-items:center;border:1px solid #dbe4ef;background:#f8fafc;border-radius:5px;color:#475569}.opp-push-steps span.done{background:#ecfdf3;border-color:#a7f3d0;color:#047857}.opp-business-note{grid-column:1/5;background:#f8fafc;border:1px solid #e2e8f0;border-radius:6px;padding:10px;color:#475569;line-height:1.6}
@media(max-width:1180px){.a3-two-col{grid-template-columns:1fr}.a3-side{border-left:0;border-top:1px solid #e5ebf2;padding:14px 0 0}.a3-card-grid{grid-template-columns:1fr}.a3-plan-item{grid-template-columns:1fr}.opp-modal-form{grid-template-columns:110px minmax(0,1fr)}.opp-modal-form .full,.opp-form-section,.opp-upload-box,.opp-check-grid,.opp-push-steps,.opp-business-note{grid-column:1/3}.opp-modal-form label{text-align:left}.opp-detail-meta{grid-template-columns:repeat(2,1fr)}}
/* 首页紧凑版:保留信息层级,收窄模块之间的纵向留白。 */
.neo-shell.home-active .content{overflow:auto!important;padding:8px 16px 12px!important}
#home.active{display:block!important;height:auto!important;min-height:0!important}
#home .workbench{display:block!important}
#home .hero{height:auto!important;min-height:78px!important;padding:12px 18px!important;margin-bottom:8px!important}
#home .hero h1{font-size:23px!important;margin:0 0 6px!important}#home .hero p{font-size:13px!important}
#home .kpis{height:auto!important;gap:8px!important;margin-bottom:6px!important}#home .kpi{height:auto!important;min-height:96px!important;padding:11px 14px!important}#home .kpi .num{font-size:27px!important;margin-top:5px!important}#home .kpi b{font-size:13px!important}#home .kpi small{font-size:12px!important}
#home .section-title{height:auto!important;margin:0 0 6px!important}#home .section-title h2{font-size:16px!important}
#home .layout-grid,#home .management-grid{height:auto!important;grid-template-columns:repeat(3,minmax(0,1fr))!important;gap:10px!important;margin-bottom:10px!important}
#home .priority-grid{height:auto!important;grid-template-columns:1fr 1fr!important;gap:10px!important;margin-bottom:10px!important}
#home .card{height:auto!important;display:block!important}#home .card-head{height:40px!important;padding:0 12px!important;font-size:14px!important}#home .card-body{overflow:visible!important;padding:10px 12px!important}
#home .todo li{padding:7px 0!important;font-size:13px!important}#home .funnel-visual{gap:7px!important;padding:3px 0!important}#home .funnel-step{grid-template-columns:54px minmax(0,1fr) 24px!important;gap:8px!important}#home .funnel-bar-wrap{height:20px!important}
#home .ai-box{padding:10px!important;margin-bottom:8px!important;line-height:1.55!important;font-size:12px!important}#home .assistant-actions .btn{height:30px!important;font-size:12px!important}
#home .table th,#home .table td{height:29px!important;font-size:12px!important;padding:0 6px!important}#home .customer{padding:8px 0!important;font-size:13px!important}#home .double-five-grid{gap:8px!important}#home .double-five-item{padding:9px!important}#home .ring{width:42px!important;height:42px!important;margin:6px auto!important}#home .ring:after{inset:6px!important}#home .risk-list{gap:8px 18px!important}#home .risk-list li{font-size:13px!important}#home .quick-grid{grid-template-columns:repeat(3,1fr)!important;gap:8px!important}#home .ai-btn{height:32px!important;padding:0 10px!important;font-size:12px!important}
/* 首页最终排布与紧凑密度。 */
#home.active{display:block!important;height:auto!important;min-height:0!important}
#home .workbench{display:block!important}
#home .main-col{display:flex!important;flex-direction:column!important}
#home .hero{order:1;height:auto!important;min-height:78px!important;padding:12px 18px!important;margin-bottom:8px!important}
#home .hero h1{font-size:23px!important;margin:0 0 6px!important}#home .hero p{font-size:13px!important}
#home .kpis{order:2;height:auto!important;gap:8px!important;margin-bottom:6px!important}
#home .kpi{height:auto!important;min-height:96px!important;padding:11px 14px!important}
#home .kpi .num{font-size:27px!important;margin-top:5px!important}#home .kpi b{font-size:13px!important}#home .kpi small{font-size:12px!important}
#home .section-title{order:3;height:auto!important;margin:0 0 6px!important}#home .section-title h2{font-size:16px!important}
#home .layout-grid{order:4!important;height:auto!important;grid-template-columns:repeat(3,minmax(0,1fr))!important;gap:10px!important;margin-bottom:10px!important;align-items:stretch!important}
#home .management-grid{order:5!important;display:grid!important;height:auto!important;grid-template-columns:repeat(3,minmax(0,1fr))!important;gap:10px!important;margin-bottom:10px!important;align-items:stretch!important}
#home .priority-grid{order:6!important;display:grid!important;height:auto!important;grid-template-columns:1fr 1fr!important;gap:10px!important;margin-bottom:0!important}
#home .layout-grid>.card,#home .management-grid>.card{height:252px!important;display:grid!important;grid-template-rows:38px minmax(0,1fr)!important}
#home .priority-grid>.card{height:auto!important;min-height:108px!important;display:grid!important;grid-template-rows:38px minmax(0,1fr)!important}
#home .card-head{height:38px!important;padding:0 12px!important;font-size:14px!important}
#home .card-body{min-height:0!important;overflow:hidden!important;padding:8px 12px!important}
#home .todo li{padding:7px 0!important;font-size:13px!important}
#home .funnel-visual{gap:7px!important;padding:3px 0!important}
#home .funnel-step{grid-template-columns:54px minmax(0,1fr) 24px!important;gap:8px!important}
#home .funnel-bar-wrap{height:20px!important}
#home .ai-box{padding:10px!important;margin-bottom:8px!important;line-height:1.55!important;font-size:12px!important}
#home .assistant-actions .btn{height:30px!important;font-size:12px!important}
#home .table th,#home .table td{height:29px!important;font-size:12px!important;padding:0 6px!important}
#home .customer{padding:8px 0!important;font-size:13px!important}
#home .double-five-grid{gap:8px!important}#home .double-five-item{padding:9px!important}
#home .ring{width:42px!important;height:42px!important;margin:6px auto!important}#home .ring:after{inset:6px!important}
#home .priority-grid .risk-list{display:grid!important;grid-template-columns:1fr 1fr!important;gap:8px 18px!important}
#home .risk-list li{font-size:13px!important}
#home .quick-grid{grid-template-columns:repeat(3,1fr)!important;gap:8px!important}
#home .ai-btn{height:32px!important;padding:0 10px!important;font-size:12px!important}
@media(max-width:1180px){#home .layout-grid,#home .management-grid{grid-template-columns:1fr!important}#home .priority-grid{grid-template-columns:1fr!important}}
/* 销售机会列表:固定序号、选择、商机名称及操作列 */
.opp-row-check,.opp-table .col-select input,.crm-check-col input,.lead2-check input{width:15px;height:15px;margin:0;accent-color:#1677ff}.opp-table tr.selected td{background:#f1f7ff}.opp-table .col-no{position:sticky;left:0;z-index:4;width:50px;min-width:50px;max-width:50px;background:inherit}.opp-table .col-select{position:sticky;left:50px;z-index:4;width:42px;min-width:42px;max-width:42px;text-align:center;background:inherit}.opp-table .col-name{position:sticky;left:92px;z-index:4;min-width:210px;background:inherit}.opp-table.no-select .col-name{left:50px}.opp-table .col-source{min-width:112px}.opp-table .col-lastVisited{min-width:118px}.opp-table .col-scheme{min-width:110px}.opp-table .col-closeDate{min-width:120px}.opp-table .col-actions{position:sticky;right:0;z-index:4;width:170px;min-width:170px;max-width:170px;background:#fff}.opp-table.has-wide-actions .col-actions{width:250px;min-width:250px;max-width:250px}.opp-table thead .col-no,.opp-table thead .col-select,.opp-table thead .col-name,.opp-table thead .col-actions{z-index:6;background:#f6f9fd}.opp-table tbody tr:hover .col-no,.opp-table tbody tr:hover .col-select,.opp-table tbody tr:hover .col-name,.opp-table tbody tr:hover .col-actions{background:#f8fbff}.opp-table tbody tr.selected .col-no,.opp-table tbody tr.selected .col-select,.opp-table tbody tr.selected .col-name,.opp-table tbody tr.selected .col-actions{background:#f1f7ff}
.a3-plan-actions{display:flex;justify-content:flex-end;gap:6px}.a3-plan-actions button{height:25px;padding:0 9px;border:1px solid #7dd3fc;border-radius:4px;background:#fff;color:#0284c7;cursor:pointer}.a3-plan-actions button:nth-child(2){border-color:#fda4af;color:#e11d48}.a3-plan-actions button:nth-child(3){border-color:#2dd4bf;color:#0f766e;background:#f0fdfa}
/* 商机功能弹窗:长表单固定头尾,内容区域滚动 */
#modal .modal{max-height:calc(100vh - 40px);display:grid;grid-template-rows:auto minmax(0,1fr) auto}#modal .modal .m-body{min-height:0;overflow:auto}#modal .modal .m-foot{flex:0 0 auto}
/* 2026-07-10 fix3: repair detail-tab tables */
.a3-table-wrap{width:100%;overflow-x:auto}
.a3-table td.a3-actions{display:table-cell!important;white-space:nowrap;vertical-align:middle}
.a3-table td.a3-actions button{display:inline-block;margin-right:8px}
.a3-table td.a3-actions button:last-child{margin-right:0}
.a3-row-actions{display:flex;align-items:center;gap:8px;white-space:nowrap}
.a3-row-actions button{border:0;background:transparent;color:#1677ff;cursor:pointer;padding:0}
.a3-attachment-table .a3-file-cell{display:flex;align-items:center;gap:10px;min-width:260px}
.a3-attachment-table .a3-file-cell b{display:block;color:#172033}
.a3-attachment-table .a3-file-icon{flex:0 0 auto}
.a3-attachment-table col.file{width:38%}
.a3-attachment-table col.category{width:14%}
.a3-attachment-table col.uploader{width:12%}
.a3-attachment-table col.time{width:20%}
.a3-attachment-table col.action{width:16%}
/* 首页收口:首页不显示无意义面包屑,内容区直接承接主导航。 */
.neo-shell.home-active{grid-template-rows:58px 52px minmax(0,1fr)}
.neo-shell.home-active>.location-bar{display:none}
.neo-shell.home-active>.content{grid-row:3}
.neo-shell.side-mode.home-active{grid-template-rows:58px minmax(0,1fr)}
.neo-shell.side-mode.home-active .side-nav-v7{grid-row:2}
.neo-shell.side-mode.home-active>.content{grid-column:2;grid-row:2}
</style>
<style id="s2gpt-fix10">
.opp-split-shell{grid-template-columns:300px 9px minmax(650px,1fr)!important}
.opp-split-table-head{height:36px!important;grid-template-columns:minmax(0,1fr) 76px!important;padding:0 12px!important}
.opp-split-row{width:100%;min-height:58px!important;grid-template-columns:minmax(0,1fr) 76px!important;align-items:center!important;padding:8px 10px 8px 12px!important;border:0!important;border-bottom:1px solid #edf2f7!important;background:#fff;text-align:left;box-sizing:border-box}
.opp-split-left.has-select .opp-split-table-head,.opp-split-left.has-select .opp-split-row{grid-template-columns:30px minmax(0,1fr) 76px!important}
.opp-split-check{display:flex;align-items:center;justify-content:center}.opp-split-check input{width:14px;height:14px;margin:0;accent-color:#1677ff}
.opp-split-row.selected{background:#f1f7ff!important}
.opp-split-row:hover{background:#f6faff!important}.opp-split-row.active{background:#eaf2ff!important;box-shadow:inset 3px 0 #1677ff!important}
.opp-split-row-main{min-width:0;display:block}.opp-split-row b{font-size:13px!important;line-height:1.45!important;margin-bottom:3px!important;color:#0563e8!important}.opp-split-row small{font-size:12px!important;color:#667085!important;line-height:1.45!important}
.opp-split-stage{justify-self:end;display:inline-flex;align-items:center;height:24px;padding:0 8px;border-radius:12px;background:#eef2f7;color:#667085;font-size:12px;white-space:nowrap}
.opp-guide-title{margin-top:10px!important;padding:0!important;font-size:14px!important;color:#263b55!important}.opp-stage-track{margin-top:8px!important}.opp-guide-desc{padding:7px 0 10px;color:#6b7280;font-size:12px;line-height:1.5}
.opp-detail-tabs{overflow-x:auto!important;overflow-y:hidden!important;scrollbar-width:none!important}.opp-detail-tabs::-webkit-scrollbar{display:none!important}
#modal .modal{width:760px;max-width:calc(100vw - 36px);max-height:calc(100vh - 36px);border-radius:8px!important;box-shadow:0 16px 42px rgba(15,23,42,.24)!important;display:grid;grid-template-rows:auto minmax(0,1fr) auto;overflow:hidden;background:#fff}
#modal .modal.opp-modal-wide{width:min(1080px,calc(100vw - 48px))!important}#modal .modal.opp-modal-medium{width:min(860px,calc(100vw - 48px))!important}#modal .modal.opp-modal-small{width:min(620px,calc(100vw - 48px))!important}
#modal .modal h3{height:54px;display:flex;align-items:center;padding:0 18px!important;margin:0!important;border-bottom:1px solid #dbe4ef!important;font-size:16px!important;color:#172033}#modal .modal .m-body{padding:18px!important;overflow:auto!important;background:#fff}#modal .modal .m-foot{padding:12px 18px!important;border-top:1px solid #dbe4ef!important;background:#fff!important}
.opp-modal-foot{display:flex;justify-content:flex-end;gap:8px}.opp-modal-foot .btn{min-width:64px;height:32px}
.opp-modal-form{display:grid!important;grid-template-columns:130px minmax(0,1fr) 130px minmax(0,1fr)!important;gap:12px 12px!important;align-items:center!important}.opp-modal-form--relation{margin-top:14px}.opp-form-section{grid-column:1/5!important;padding:9px 12px!important;background:#f6f9fd!important;border-left:3px solid #1677ff!important;color:#173b64!important;font-size:14px!important;font-weight:700!important}
.opp-modal-form>label{font-size:14px!important;color:#334155!important;text-align:right!important;font-weight:400!important;line-height:1.4!important}.opp-required-star{color:#ef4444!important;margin-right:2px;font-weight:700}
.opp-modal-form input,.opp-modal-form select,.opp-modal-form textarea,.opp-search-bar input,.opp-search-bar select{width:100%;box-sizing:border-box;border:1px solid #cbd7e5!important;border-radius:5px!important;background:#fff!important;color:#172033!important;font-size:14px!important;outline:none}.opp-modal-form input,.opp-modal-form select,.opp-search-bar input,.opp-search-bar select{height:34px!important;padding:0 10px!important}.opp-modal-form textarea{min-height:74px!important;padding:8px 10px!important;line-height:1.55!important;resize:vertical!important}.opp-modal-form input[disabled]{background:#f5f7fa!important;color:#64748b!important}
.opp-modal-form .full{grid-column:2/5!important}.opp-inline-selects{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}.opp-inline-action{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:8px}.opp-upload-box.full{grid-column:2/5!important;display:flex!important;flex-direction:column;justify-content:center;gap:5px}.opp-upload-box span{font-size:12px;color:#64748b}.opp-long-text{min-height:88px!important}
.opp-search-bar{display:grid;grid-template-columns:minmax(0,1fr) 180px auto auto;gap:8px;align-items:center}.opp-picker-title{display:flex;align-items:center;justify-content:space-between;margin:14px 0 8px}.opp-select-table-wrap{max-height:300px;overflow:auto;border:1px solid #d8e1eb}.opp-select-table{width:100%;border-collapse:collapse;font-size:13px}.opp-select-table th,.opp-select-table td{height:38px;padding:6px 10px;border-bottom:1px solid #e5ebf2;text-align:left;white-space:nowrap}.opp-select-table th{position:sticky;top:0;background:#f6f9fd;color:#475569;font-weight:700}.opp-select-table tbody tr:hover{background:#f8fbff}.opp-picker-pagination{text-align:right;padding:10px 0;color:#64748b;font-size:13px}
.opp-push-tip{padding:10px 12px;background:#fff8e8;color:#7a5b16;border-bottom:1px solid #f1dfb7;margin:-18px -18px 14px}.opp-push-filter{display:flex;gap:8px;margin-bottom:12px}.opp-push-filter button{height:30px;border:1px solid #cbd7e5;background:#fff;border-radius:15px;padding:0 13px;color:#475569}.opp-push-filter button.active{border-color:#1677ff;color:#1677ff;background:#eff6ff}.opp-scheme-choice{display:grid;grid-template-columns:24px minmax(0,1fr) auto;gap:10px;align-items:start;padding:14px;border:1px solid #d8e1eb;border-radius:7px;margin-bottom:10px;cursor:pointer}.opp-scheme-choice.active{border-color:#1677ff;background:#eff6ff}.opp-scheme-choice span{display:grid;gap:5px}.opp-scheme-choice small{color:#64748b}.opp-scheme-choice em{font-style:normal;color:#047857}.opp-scheme-choice em.warn{color:#c2410c}.opp-push-note{padding:10px 12px;background:#f8fafc;color:#64748b;border:1px solid #e5ebf2;border-radius:6px}
.opp-template-picker{display:grid;gap:12px}.opp-template-picker-intro{margin:0;color:#52677d;font-size:13px;line-height:1.7}.opp-template-options{display:grid;gap:10px}.opp-template-option{display:grid;grid-template-columns:22px minmax(0,1fr) auto;gap:11px;align-items:start;border:1px solid #d8e2ed;border-radius:8px;background:#fff;padding:13px 14px;cursor:pointer}.opp-template-option:hover{border-color:#8dbcf2;background:#f8fbff}.opp-template-option.active{border-color:#1677ff;background:#eff7ff;box-shadow:0 0 0 2px rgba(22,119,255,.08)}.opp-template-option input{margin-top:4px}.opp-template-main{display:grid;gap:5px;min-width:0}.opp-template-title{display:flex;align-items:center;gap:8px;flex-wrap:wrap;color:#263b55}.opp-template-title b{font-size:14px}.opp-template-recommend{display:inline-flex;align-items:center;height:20px;padding:0 7px;border-radius:10px;background:#e8f3ff;color:#1677ff;font-size:11px;font-weight:700}.opp-template-main p{margin:0;color:#64748b;font-size:12px;line-height:1.55}.opp-template-version{white-space:nowrap;color:#405871;font-size:12px;padding-top:2px}.opp-template-permission{margin-top:2px;color:#7b8b9e;font-size:12px}.opp-scheme-form-shell{display:grid;gap:14px}.opp-scheme-template-head{display:flex;align-items:center;justify-content:space-between;gap:16px;border:1px solid #cfe0f0;border-radius:7px;background:#f6faff;padding:11px 13px}.opp-scheme-template-head>div{display:grid;gap:4px;min-width:0}.opp-scheme-template-head b{color:#294763;font-size:14px}.opp-scheme-template-head span{color:#718198;font-size:12px}.opp-scheme-template-head .btn{flex:0 0 auto}.opp-scheme-form-shell .opp-modal-form{border:1px solid #e0e8f1;border-radius:7px;background:#fff;padding:14px}.opp-scheme-form-shell .opp-form-section{border-left:0;border-bottom:1px solid #e3eaf2;background:#f8fafc;padding:9px 10px}.opp-template-warning{display:grid;gap:10px}.opp-template-warning p{margin:0;color:#40546b;line-height:1.75}.opp-template-warning strong{color:#b45309}.opp-template-warning .opp-business-note{margin:0}.opp-scheme-preview-shell .opp-modal-form{pointer-events:none}.opp-scheme-preview-shell input,.opp-scheme-preview-shell select,.opp-scheme-preview-shell textarea{background:#fff}
@media(max-width:1100px){.opp-modal-form{grid-template-columns:120px minmax(0,1fr)!important}.opp-modal-form .full,.opp-form-section,.opp-upload-box.full{grid-column:1/3!important}.opp-modal-form>label{text-align:left!important}.opp-inline-selects{grid-template-columns:1fr}}
</style>
<style id="s2gpt-fix10-v2">
#modal .modal.opp-modal-small{width:min(680px,calc(100vw - 48px))!important}
.opp-team-picker{display:grid;grid-template-columns:minmax(0,1fr);gap:12px}.opp-team-picker>*{grid-column:1/-1!important;min-width:0}.opp-team-picker .opp-business-note{margin:0}.opp-selected-members{display:grid;grid-template-columns:110px minmax(0,1fr);gap:12px;align-items:center}.opp-selected-members b{font-size:14px;color:#334155}.opp-selected-members input{height:34px;border:1px solid #cbd7e5;border-radius:5px;padding:0 10px;background:#f7f9fc;color:#475569;font-size:14px}
.opp-modal-form .opp-search-bar.full,.opp-modal-form .opp-select-table-wrap.full{grid-column:1/5!important}.opp-modal-form .opp-select-table-wrap.full{max-height:210px}
.opp-business-note--danger{background:#fff8e8!important;border-color:#ffe2a8!important;color:#d4380d!important}.opp-close-modal{display:grid;gap:14px}.opp-close-modal .opp-business-note{margin:0}.opp-confirm-check{display:flex!important;align-items:flex-start;gap:8px}.opp-confirm-check input{width:16px!important;height:16px!important;margin-top:2px}.opp-confirm-check label{font-size:14px!important;color:#334155!important;text-align:left!important;line-height:1.5}
@media(max-width:1180px){.opp-modal-form .opp-search-bar.full,.opp-modal-form .opp-select-table-wrap.full{grid-column:1/3!important}.opp-selected-members{grid-template-columns:1fr}}
/* 商机团队成员弹窗:成员选择、协作信息和说明分层,避免通用表单造成字段拥挤。 */
.opp-team-dialog{display:grid;grid-template-columns:minmax(0,1fr);gap:14px}.opp-team-dialog>.opp-business-note,.opp-team-dialog>.opp-team-search,.opp-team-dialog>.opp-select-table-wrap,.opp-team-dialog>.opp-team-section,.opp-team-dialog>.opp-team-caption,.opp-team-dialog>.opp-team-check{grid-column:1/-1!important;min-width:0}.opp-team-dialog .opp-business-note{margin:0!important;padding:9px 12px!important;background:#f6f9fd!important;border-color:#dbe7f4!important;color:#52657a!important;font-size:13px!important;line-height:1.55!important}.opp-team-search{display:grid;grid-template-columns:minmax(0,1fr) 170px auto auto;gap:8px;align-items:center}.opp-team-search input,.opp-team-search select,.opp-team-field input,.opp-team-field select{width:100%;height:36px;box-sizing:border-box;border:1px solid #cbd7e5;border-radius:5px;background:#fff;color:#172033;font-size:14px;padding:0 10px}.opp-team-dialog .opp-select-table-wrap{width:100%;max-height:208px;border-color:#d5e0ed}.opp-team-dialog .opp-select-table th,.opp-team-dialog .opp-select-table td{height:37px}.opp-team-section{padding:14px 16px;border:1px solid #dbe5f0;border-radius:7px;background:#fff}.opp-team-section-title{margin:0 0 12px;color:#263b55;font-size:14px;font-weight:700}.opp-team-fields{display:grid;grid-template-columns:minmax(210px,.82fr) minmax(0,1.18fr);gap:16px}.opp-team-field{min-width:0}.opp-team-field label{display:block;margin:0 0 7px;color:#475569;font-size:13px;font-weight:600}.opp-team-field input[disabled],.opp-team-field select[disabled]{background:#f5f7fa;color:#64748b}.opp-team-caption{display:flex;align-items:flex-start;gap:7px;margin:0;color:#64748b;font-size:12px;line-height:1.55}.opp-team-caption:before{content:'i';flex:0 0 16px;width:16px;height:16px;display:grid;place-items:center;border-radius:50%;background:#eaf2ff;color:#1677ff;font-size:11px;font-weight:700}.opp-team-transfer-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px}.opp-team-transfer-grid .opp-team-field.full{grid-column:1/-1}.opp-team-check{display:flex;align-items:center;gap:8px;margin:0;padding:10px 12px;border:1px solid #dbe5f0;border-radius:6px;background:#f8fafc;color:#475569;font-size:13px}.opp-team-check input{width:16px;height:16px;margin:0;accent-color:#1677ff}.opp-team-required{color:#ef4444;margin-right:3px;font-weight:700}@media(max-width:720px){.opp-team-search{grid-template-columns:1fr auto auto}.opp-team-search select{grid-column:1/-1}.opp-team-fields,.opp-team-transfer-grid{grid-template-columns:1fr}.opp-team-transfer-grid .opp-team-field.full{grid-column:auto}}
/* 商机协同群:仅记录当前商机内部协同建群与回写信息,不替代企业微信侧的群管理。 */
.opp-group-dialog{display:grid;gap:14px}.opp-group-layout{display:grid;grid-template-columns:minmax(420px,1.3fr) minmax(280px,.7fr);gap:14px;align-items:start}.opp-group-dialog .opp-team-fields{grid-template-columns:minmax(0,1fr);gap:12px}.opp-group-dialog .opp-team-check{margin-top:12px;align-items:flex-start;line-height:1.45}.opp-group-members{border:1px solid #dbe5f0;border-radius:7px;overflow:hidden}.opp-group-member{display:grid;grid-template-columns:18px 32px minmax(0,1fr) auto;gap:10px;align-items:center;padding:10px 12px;border-bottom:1px solid #edf1f5;color:#334155;font-size:13px}.opp-group-member:last-child{border-bottom:0}.opp-group-member input{width:16px;height:16px;margin:0;accent-color:#1677ff}.opp-group-avatar{width:30px;height:30px;display:grid;place-items:center;border-radius:50%;background:#edf3fb;color:#35618c;font-size:12px;font-weight:700}.opp-group-role{color:#718096;font-size:12px;white-space:nowrap}.opp-group-record{display:flex;align-items:center;justify-content:space-between;gap:16px;margin:0 0 12px;padding:11px 13px;border:1px solid #dbe7f4;border-radius:7px;background:#f6f9fd;color:#40566f;font-size:13px}.opp-group-record b{color:#263b55}.opp-group-record small{display:block;margin-top:3px;color:#718096}@media(max-width:760px){.opp-group-layout{grid-template-columns:1fr}.opp-group-member{grid-template-columns:18px 30px minmax(0,1fr)}.opp-group-role{grid-column:3;white-space:normal}.opp-group-record{align-items:flex-start;flex-direction:column}}
/* S2-gpt fix16: split collapse, guide placement, push flow and compact dialog forms */
.opp-split-shell.collapsed{grid-template-columns:18px minmax(0,1fr)!important}
.opp-split-shell.collapsed .opp-split-list{display:none!important}
.opp-split-shell.collapsed .opp-split-divider{display:block!important;width:18px!important;min-width:18px!important;border-left:1px solid #d8e1eb!important;border-right:1px solid #d8e1eb!important;background:#eef2f7!important}
.opp-split-shell.collapsed .opp-split-toggle{left:50%!important;top:50%!important;transform:translate(-50%,-50%)!important;border-radius:0 6px 6px 0!important}
.opp-split-shell.collapsed .opp-split-detail{grid-column:2!important;min-width:0!important;width:auto!important}
.opp-guide-copy{display:flex;align-items:center;gap:12px;padding:9px 0 3px;color:#52657a}
.opp-guide-copy .opp-guide-title{padding:0;font-weight:700;color:#163b65;white-space:nowrap}
.opp-guide-copy .opp-guide-desc{font-size:13px;line-height:1.5;color:#718096}
.opp-compact-form .opp-note-label,.opp-close-form .opp-note-label{grid-column:1!important;align-self:start!important;padding-top:8px!important;border:0!important;background:none!important;box-shadow:none!important;border-radius:0!important}
.opp-compact-form .opp-note-field,.opp-close-form .opp-note-field{grid-column:2/5!important;min-height:82px!important}
.opp-form-spacer{display:block}
.opp-close-form .opp-confirm-check{grid-column:2/5!important;display:grid!important;grid-template-columns:18px minmax(0,1fr)!important;gap:4px 8px!important;padding:8px 10px;border:1px solid transparent;border-radius:6px}
.opp-confirm-check .opp-confirm-error{grid-column:2;display:none;color:#ef4444;font-size:13px;line-height:1.4}
.opp-confirm-check.invalid{border-color:#fecaca;background:#fff7f7}
.opp-confirm-check.invalid label{color:#dc2626!important}
.opp-confirm-check.shake{animation:opp-confirm-shake .34s ease}
@keyframes opp-confirm-shake{0%,100%{transform:translateX(0)}20%{transform:translateX(-7px)}40%{transform:translateX(7px)}60%{transform:translateX(-5px)}80%{transform:translateX(5px)}}
.opp-push-process{display:grid;gap:14px}.opp-push-process-head{display:flex;justify-content:space-between;align-items:center;padding:10px 12px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:6px}.opp-push-process-head span{color:#1677ff}
.opp-push-step-list{display:grid;gap:8px}.opp-push-step{display:grid;grid-template-columns:28px minmax(0,1fr) auto;gap:10px;align-items:center;padding:11px 12px;border:1px solid #e2e8f0;border-radius:6px;background:#fff}.opp-push-step i{width:24px;height:24px;border-radius:50%;display:grid;place-items:center;background:#eef2f7;color:#64748b;font-style:normal}.opp-push-step em{font-style:normal;color:#94a3b8}.opp-push-step.current{border-color:#93c5fd;background:#eff6ff}.opp-push-step.current i{background:#1677ff;color:#fff}.opp-push-step.current em{color:#1677ff}.opp-push-step.done{border-color:#bbf7d0;background:#f0fdf4}.opp-push-step.done i{background:#16a34a;color:#fff}.opp-push-step.done em{color:#15803d}
.opp-push-result{display:grid;gap:12px;padding:4px}.opp-push-result h3{margin:0;font-size:17px}.opp-push-result p{margin:0;color:#475569}.opp-push-result ul{margin:0;padding-left:22px;color:#b91c1c}.opp-push-result table{width:100%;border-collapse:collapse}.opp-push-result th,.opp-push-result td{border:1px solid #dbe4ef;padding:9px 11px;text-align:left}.opp-push-result th{width:130px;background:#f8fafc;color:#64748b}.opp-push-result.success h3{color:#15803d}.opp-push-result.warning h3{color:#d97706}.opp-push-result.error h3{color:#dc2626}
@media(max-width:1100px){.opp-compact-form .opp-note-field,.opp-close-form .opp-note-field{grid-column:2!important}.opp-close-form .opp-confirm-check{grid-column:1/3!important}.opp-guide-copy{align-items:flex-start;flex-direction:column;gap:2px}}
</style>
<!-- DELIVERY_MARKER: S2GPT_FIX16_20260710_0916 -->
<style id="s2gpt-fix7-current">
/* S2-gpt incremental fix: split workspace, tab headers, BYGPT push flow, compact dialogs */
.opp-split-shell{grid-template-columns:330px 9px minmax(0,1fr)!important;transition:grid-template-columns .24s ease!important}
.opp-split-shell.collapsed{grid-template-columns:0 18px minmax(0,1fr)!important}
.opp-split-left{min-width:0;display:grid;grid-template-rows:minmax(0,1fr);overflow:hidden;background:#fff;opacity:1;transform:translateX(0);transition:opacity .18s ease,transform .24s ease}
.opp-split-shell.collapsed .opp-split-left{opacity:0;transform:translateX(-18px);pointer-events:none;visibility:hidden}
.opp-split-list{min-height:0!important;overflow:auto!important}
.opp-split-divider{transition:width .24s ease,background .24s ease!important}
.opp-split-toggle{transition:transform .24s ease,background .15s ease,color .15s ease!important}
.opp-split-shell.collapsed .opp-split-detail{grid-column:auto!important}
.opp-guide-copy{display:flex!important;align-items:center!important;gap:12px!important;min-height:38px;padding:8px 0 6px!important;margin:0!important}
.opp-guide-copy .opp-guide-title{margin:0!important;padding:0!important;font-size:13px!important;line-height:20px!important;font-weight:700!important;color:#163b65!important;white-space:nowrap!important}
.opp-guide-copy .opp-guide-desc{margin:0!important;padding:0!important;font-size:13px!important;line-height:20px!important;color:#718096!important}
.a3-tab-header{min-height:46px;display:flex;align-items:center;justify-content:space-between;gap:14px;padding:0 0 10px;margin-bottom:10px;border-bottom:1px solid #e5ebf2}
.a3-tab-header-title{display:flex;align-items:center;gap:7px;color:#172033;font-size:14px;font-weight:800;white-space:nowrap}
.a3-tab-header-title:before{content:"⊠";font-size:13px;color:#64748b;font-weight:400}
.a3-tab-header-actions{margin-left:auto;display:flex;align-items:center;justify-content:flex-end;gap:8px;flex-wrap:wrap}
.a3-tab-header-actions input,.a3-tab-header-actions select{height:32px;border:1px solid #cfd9e5;border-radius:5px;padding:0 9px;background:#fff;color:#334155;font-size:13px}
.a3-tab-header-actions input{width:190px}
.a3-tab-header-actions .a3-subtabs{margin:0}
.a3-tab-header-actions .btn{height:32px}
/* BYGPT push workflow visual system */
#modal .modal.opp-push-dialog{width:min(780px,calc(100vw - 48px))!important}
#modal .modal.opp-push-dialog #modalTitle{gap:8px}
.bygpt-push-badge{display:inline-flex;align-items:center;height:20px;padding:0 8px;border-radius:10px;background:#f3e8ff;color:#7c3aed;font-size:11px;font-weight:700}
.bygpt-push-intro{margin:-2px 0 12px;color:#64748b;font-size:13px;line-height:1.55}
.bygpt-push-tabs{display:flex;gap:6px;padding:10px 0 12px;border-bottom:1px solid #e5e7eb;margin-bottom:14px}
.bygpt-push-tabs button{height:30px;border:1px solid #dbe3ee;background:#fff;border-radius:16px;padding:0 12px;cursor:pointer;color:#475569;font-size:13px}
.bygpt-push-tabs button.active{border-color:#2563eb;background:#eff6ff;color:#2563eb;font-weight:700}
.push-scheme-list{display:grid;gap:10px}
.push-scheme-option{border:1px solid #dbe3ee;border-radius:10px;padding:12px;display:grid;grid-template-columns:28px minmax(0,1fr) auto;gap:10px;align-items:flex-start;background:#fff;cursor:pointer}
.push-scheme-option:hover{border-color:#93c5fd;background:#f8fbff}.push-scheme-option.active{border-color:#2563eb;background:#eff6ff}
.push-scheme-option input{margin-top:3px}.push-scheme-option h4{margin:0 0 6px;font-size:14px;color:#172033}.push-scheme-option p{margin:0;color:#64748b;font-size:12px;line-height:1.65}
.push-scheme-status{font-size:12px;border-radius:999px;padding:3px 8px;background:#dcfce7;color:#166534;font-weight:700;white-space:nowrap}.push-scheme-status.draft{background:#fef3c7;color:#92400e}
.push-pick-tip{font-size:12px;color:#64748b;margin:9px 0 0}
.push-steps{display:grid!important;grid-template-columns:repeat(5,minmax(0,1fr))!important;gap:8px;margin-bottom:18px}
.push-step{text-align:center;color:#94a3b8;font-size:12px;line-height:1.4}.push-step i{display:flex;width:30px;height:30px;border-radius:50%;background:#e2e8f0;align-items:center;justify-content:center;margin:0 auto 7px;font-style:normal}
.push-step.done{color:#059669}.push-step.done i{background:#d1fae5}.push-step.loading{color:#2563eb;font-weight:700}.push-step.loading i{background:#dbeafe;animation:pushPulse 1s infinite}.push-step.warn{color:#d97706}.push-step.warn i{background:#fef3c7}.push-step.fail{color:#dc2626}.push-step.fail i{background:#fee2e2}.push-step.wait{color:#94a3b8}.push-step.wait i{background:#e2e8f0}
@keyframes pushPulse{50%{transform:scale(1.12)}}
.push-result{border:1px solid #e2e8f0;border-radius:8px;padding:16px;background:#fbfdff;line-height:1.7}.push-result h4{margin:0 0 6px;font-size:16px}.push-result p{margin:0;color:#64748b}.push-result .push-missing{margin:8px 0 0;padding-left:20px;color:#475569}.push-dup-grid{display:grid;grid-template-columns:110px 1fr;gap:7px;margin-top:10px;font-size:13px}.push-dup-grid b{color:#64748b}.push-result .mini-actions{display:flex;gap:8px;margin-top:12px}.opp-green-btn{border-color:#22c55e!important;color:#16a34a!important;background:#f0fdf4!important}
/* Compact small-dialog form rows: no meaningless left whitespace */
.opp-compact-layout{display:grid;gap:14px}
.opp-compact-top{display:grid;grid-template-columns:1fr 1fr;gap:12px 18px}
.opp-inline-field{display:grid;grid-template-columns:100px minmax(0,1fr);gap:10px;align-items:center;min-width:0}
.opp-inline-field>label{font-size:14px;color:#334155;text-align:right;line-height:1.45}.opp-inline-field>select,.opp-inline-field>input,.opp-inline-field>textarea{width:100%;box-sizing:border-box;border:1px solid #cbd7e5;border-radius:5px;background:#fff;color:#172033;font-size:14px}
.opp-inline-field>select,.opp-inline-field>input{height:34px;padding:0 10px}.opp-inline-field>textarea{min-height:80px;padding:8px 10px;line-height:1.55;resize:vertical}.opp-inline-field.full{grid-template-columns:100px minmax(0,1fr)}
.opp-compact-layout>.opp-business-note{margin:0!important}
.opp-close-layout{display:grid;gap:14px}.opp-close-layout .opp-business-note{margin:0}.opp-close-fields{display:grid;gap:12px}.opp-close-fields .opp-inline-field{grid-template-columns:120px minmax(0,1fr)}
.opp-close-fields .opp-confirm-check{margin-left:130px;width:auto;box-sizing:border-box}
#modal .modal.opp-modal-small .m-body{min-height:0!important}
@media(max-width:1100px){
.opp-split-shell{grid-template-columns:300px 9px minmax(0,1fr)!important}.opp-split-shell.collapsed{grid-template-columns:0 18px minmax(0,1fr)!important}
.opp-guide-copy{align-items:flex-start!important;gap:5px!important;flex-wrap:wrap!important}
.a3-tab-header{align-items:flex-start}.a3-tab-header-actions{max-width:75%}
.opp-compact-top{grid-template-columns:1fr}.opp-inline-field,.opp-inline-field.full,.opp-close-fields .opp-inline-field{grid-template-columns:110px minmax(0,1fr)}.opp-close-fields .opp-confirm-check{margin-left:120px}
}
</style>
<style id="s2gpt-fix7-grid-hotfix">
.opp-compact-layout > .opp-compact-top,
.opp-compact-layout > .opp-inline-field,
.opp-compact-layout > .opp-business-note{
grid-column:1 / -1 !important;
width:100% !important;
min-width:0 !important;
box-sizing:border-box;
}
.opp-close-layout > .opp-business-note,
.opp-close-layout > .opp-close-fields{
grid-column:1 / -1 !important;
width:100% !important;
min-width:0 !important;
box-sizing:border-box;
}
.opp-compact-top{
display:grid !important;
grid-template-columns:minmax(0,1fr) minmax(0,1fr) !important;
gap:14px 22px !important;
}
.opp-compact-top .opp-inline-field{
grid-template-columns:112px minmax(0,1fr) !important;
width:100% !important;
min-width:0 !important;
}
.opp-compact-top .opp-inline-field select,
.opp-compact-top .opp-inline-field input{
width:100% !important;
min-width:0 !important;
}
.opp-compact-layout > .opp-inline-field.full{
display:grid !important;
grid-template-columns:112px minmax(0,1fr) !important;
align-items:start !important;
}
.opp-compact-layout > .opp-inline-field.full textarea{
width:100% !important;
min-height:82px !important;
}
@media(max-width:900px){
.opp-compact-top{grid-template-columns:1fr !important}
}
</style>
<style id="opp-config-draft-styles">
.opp-config-page{height:100%;min-height:0;display:grid;grid-template-rows:auto minmax(0,1fr);gap:12px}.opp-config-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px}.opp-config-head h1{margin:0;font-size:20px;color:#172033}.opp-config-head p{margin:5px 0 0;font-size:13px;color:#64748b}.opp-config-head-actions{display:flex;gap:8px;flex-wrap:wrap}.opp-config-layout{display:grid;grid-template-columns:184px minmax(0,1fr);gap:12px;min-height:0}.opp-config-nav{background:#fff;border:1px solid #dbe4ef;border-radius:7px;padding:8px;display:flex;flex-direction:column;gap:3px}.opp-config-nav button{height:36px;border:0;border-radius:5px;background:transparent;color:#475569;text-align:left;padding:0 10px;cursor:pointer;font-size:13px}.opp-config-nav button:hover{background:#f5f8fc;color:#1677ff}.opp-config-nav button.active{background:#eaf2ff;color:#1677ff;font-weight:700}.opp-config-main{min-width:0;overflow:auto;background:#fff;border:1px solid #dbe4ef;border-radius:7px;padding:16px}.opp-config-section-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:14px}.opp-config-section-head h2{margin:0;font-size:16px;color:#172033}.opp-config-section-head p{margin:5px 0 0;color:#64748b;font-size:12px;line-height:1.5}.opp-config-section-head .actions{display:flex;gap:8px;white-space:nowrap}.opp-config-kpis{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin-bottom:14px}.opp-config-kpi{border:1px solid #dbe4ef;border-radius:6px;padding:12px;background:#fbfdff}.opp-config-kpi b{display:block;font-size:22px;color:#173b64;line-height:1.1}.opp-config-kpi span{display:block;margin-top:6px;font-size:12px;color:#64748b}.opp-config-note{padding:10px 12px;border-left:3px solid #1677ff;background:#f6faff;color:#475569;font-size:13px;line-height:1.6;margin-bottom:14px}.opp-config-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.opp-config-card{border:1px solid #dbe4ef;border-radius:6px;background:#fff;overflow:hidden}.opp-config-card h3{height:40px;display:flex;align-items:center;justify-content:space-between;margin:0;padding:0 12px;border-bottom:1px solid #e5ebf2;background:#f8fafc;color:#263b55;font-size:14px}.opp-config-card h3 button{border:0;background:transparent;color:#1677ff;cursor:pointer;font-size:12px}.opp-config-card .body{padding:12px}.opp-config-list{display:grid;gap:0}.opp-config-list div{display:flex;align-items:center;justify-content:space-between;gap:10px;min-height:34px;border-bottom:1px solid #edf2f7;font-size:13px;color:#334155}.opp-config-list div:last-child{border-bottom:0}.opp-config-list small{color:#64748b}.opp-config-list .tag{font-size:11px;padding:2px 7px;border-radius:10px;background:#eef5ff;color:#1677ff}.opp-track-layout{display:grid;grid-template-columns:190px minmax(420px,1fr) 250px;gap:12px;min-width:820px}.opp-track-list{border:1px solid #dbe4ef;border-radius:6px;overflow:hidden}.opp-track-list h3,.opp-track-detail h3{height:38px;margin:0;padding:0 11px;display:flex;align-items:center;border-bottom:1px solid #e5ebf2;background:#f8fafc;font-size:13px;color:#334155}.opp-track-template{display:block;width:100%;border:0;border-bottom:1px solid #edf2f7;background:#fff;text-align:left;padding:10px 11px;color:#475569;cursor:pointer;font-size:13px}.opp-track-template.active{background:#eaf2ff;color:#1677ff;font-weight:700;border-left:3px solid #1677ff;padding-left:8px}.opp-track-canvas{border:1px solid #dbe4ef;border-radius:6px;padding:16px;background:#fbfdff}.opp-track-canvas-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:18px}.opp-track-canvas-head b{font-size:14px;color:#263b55}.opp-track-canvas-head span{font-size:12px;color:#64748b}.opp-track-flow{display:flex;align-items:stretch;gap:5px}.opp-track-node{position:relative;flex:1;min-width:0;border:1px solid #cfe0f5;border-radius:6px;background:#fff;padding:10px 8px;color:#334155}.opp-track-node.active{border-color:#1677ff;background:#eff6ff}.opp-track-node b{display:block;font-size:13px}.opp-track-node small{display:block;margin-top:5px;color:#64748b;font-size:11px;line-height:1.4}.opp-track-arrow{display:flex;align-items:center;color:#94a3b8}.opp-track-detail{border:1px solid #dbe4ef;border-radius:6px;overflow:hidden;background:#fff}.opp-field-row{padding:9px 11px;border-bottom:1px solid #edf2f7}.opp-field-row:last-child{border-bottom:0}.opp-field-row label{display:block;font-size:11px;color:#94a3b8;margin-bottom:4px}.opp-field-row b,.opp-field-row span{font-size:13px;color:#334155;line-height:1.45}.opp-template-table{width:100%;border-collapse:collapse;font-size:13px}.opp-template-table th,.opp-template-table td{padding:10px 9px;border-bottom:1px solid #e5ebf2;text-align:left}.opp-template-table th{background:#f8fafc;color:#64748b;font-weight:600}.opp-template-table .status{display:inline-block;padding:2px 7px;border-radius:10px;background:#ecfdf3;color:#047857;font-size:11px}.opp-gate-list{display:grid;gap:10px}.opp-gate-row{display:grid;grid-template-columns:150px minmax(0,1fr) 115px;align-items:center;gap:12px;border:1px solid #e2e8f0;border-radius:6px;padding:11px 12px}.opp-gate-row b{font-size:13px;color:#263b55}.opp-gate-row p{margin:0;color:#64748b;font-size:12px;line-height:1.5}.opp-gate-row button{justify-self:end}.opp-rule-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.opp-rule-card{border:1px solid #dbe4ef;border-radius:6px;padding:13px}.opp-rule-card h3{margin:0 0 10px;font-size:14px;color:#263b55}.opp-rule-card label{display:flex;align-items:center;gap:7px;margin:9px 0;color:#475569;font-size:13px}.opp-rule-card input[type=number]{width:58px;height:28px;border:1px solid #cfd9e5;border-radius:4px;padding:0 7px}.opp-state-group{display:flex;flex-wrap:wrap;gap:8px}.opp-state-group span{border:1px solid #dbe4ef;border-radius:4px;padding:6px 9px;background:#fff;font-size:12px;color:#475569}.opp-state-group span.active{border-color:#93c5fd;background:#eff6ff;color:#1677ff}.opp-version-row{display:flex;align-items:center;justify-content:space-between;padding:10px 0;border-bottom:1px solid #edf2f7;font-size:13px}.opp-version-row:last-child{border-bottom:0}.opp-version-row small{color:#64748b}@media(max-width:1180px){.opp-config-layout{grid-template-columns:1fr}.opp-config-nav{flex-direction:row;overflow:auto}.opp-config-nav button{white-space:nowrap}.opp-config-kpis{grid-template-columns:repeat(2,minmax(0,1fr))}.opp-config-grid,.opp-rule-grid{grid-template-columns:1fr}.opp-track-layout{grid-template-columns:1fr;min-width:0}.opp-track-flow{flex-direction:column}.opp-track-arrow{justify-content:center;transform:rotate(90deg)}}
</style>
<style id="opp-config-governance-styles">
.opp-governance{display:grid;grid-template-columns:minmax(0,1.15fr) minmax(340px,.85fr);gap:14px}.opp-governance-stack{display:grid;gap:10px}.opp-governance-layer{position:relative;border:1px solid #dbe4ef;border-radius:7px;background:#fff;padding:13px 14px}.opp-governance-layer.company{border-color:#93c5fd;background:#f5f9ff}.opp-governance-layer.stage{border-color:#c7d2fe;background:#fafaff}.opp-governance-layer.team{border-color:#bae6fd;background:#f4fbff}.opp-governance-layer h3{margin:0 0 6px;font-size:14px;color:#263b55}.opp-governance-layer p{margin:0;color:#64748b;font-size:12px;line-height:1.55}.opp-governance-layer .scope{position:absolute;right:12px;top:12px;padding:2px 7px;border-radius:10px;background:#eaf2ff;color:#1677ff;font-size:11px}.opp-governance-arrow{text-align:center;color:#94a3b8;font-size:18px;line-height:12px}.opp-scenario{border:1px solid #dbe4ef;border-radius:7px;padding:14px;background:#fbfdff}.opp-scenario h3{margin:0 0 10px;font-size:14px;color:#263b55}.opp-scenario-options{display:flex;flex-wrap:wrap;gap:7px;margin-bottom:12px}.opp-scenario-options button{height:29px;border:1px solid #cfd9e5;border-radius:15px;background:#fff;padding:0 10px;color:#475569;cursor:pointer;font-size:12px}.opp-scenario-options button.active{border-color:#1677ff;background:#eaf2ff;color:#1677ff;font-weight:700}.opp-effective-rule{display:grid;gap:8px;border-top:1px solid #e5ebf2;padding-top:11px}.opp-effective-rule div{display:flex;justify-content:space-between;gap:12px;font-size:12px}.opp-effective-rule span{color:#64748b}.opp-effective-rule b{color:#334155;text-align:right}.opp-state-panel{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.opp-state-panel .opp-config-card{min-height:220px}.opp-state-line{display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin:10px 0}.opp-state-line i{font-style:normal;color:#94a3b8}.opp-policy-lock{margin-top:12px;padding:9px 10px;border:1px solid #fde68a;background:#fffbeb;color:#92400e;border-radius:5px;font-size:12px;line-height:1.55}@media(max-width:1180px){.opp-governance,.opp-state-panel{grid-template-columns:1fr}}
</style>
<style id="opp-guide-editor-styles">
.opp-track-template,.opp-track-node{cursor:pointer}.opp-track-node{font:inherit;text-align:left}.opp-track-node:hover{border-color:#93c5fd;background:#f8fbff}.opp-track-node.active{border-color:#1677ff;background:#eff6ff}.opp-guide-draft{display:inline-flex;margin-left:6px;padding:1px 5px;border-radius:8px;background:#fff7ed;color:#c2410c;font-size:10px;font-weight:600}.opp-guide-form{display:grid;grid-template-columns:110px minmax(0,1fr);gap:12px;align-items:center}.opp-guide-form label{font-size:13px;color:#475569;text-align:right}.opp-guide-form input,.opp-guide-form select{height:34px;border:1px solid #cbd7e5;border-radius:5px;padding:0 10px;font-size:13px;box-sizing:border-box}.opp-guide-form .full{grid-column:2}.opp-guide-form-note{grid-column:1/-1;padding:10px 12px;background:#f6faff;border-left:3px solid #1677ff;color:#475569;font-size:12px;line-height:1.55}@media(max-width:800px){.opp-guide-form{grid-template-columns:1fr}.opp-guide-form label{text-align:left}.opp-guide-form .full{grid-column:1}}
</style>
<style id="opp-field-template-demo-styles">
.opp-field-workbench{display:grid;grid-template-columns:195px minmax(390px,1fr) 255px;gap:12px;min-width:840px}.opp-field-library,.opp-field-preview,.opp-field-detail{border:1px solid #dbe4ef;border-radius:7px;background:#fff;overflow:hidden}.opp-field-library h3,.opp-field-preview h3,.opp-field-detail h3{height:40px;margin:0;padding:0 12px;display:flex;align-items:center;border-bottom:1px solid #e5ebf2;background:#f8fafc;color:#263b55;font-size:13px}.opp-field-source{display:block;width:100%;border:0;border-bottom:1px solid #edf2f7;background:#fff;text-align:left;padding:10px 12px;color:#475569;cursor:pointer;font-size:13px}.opp-field-source small{display:block;margin-top:4px;color:#94a3b8;font-size:11px}.opp-field-source.active{background:#eaf2ff;color:#1677ff;border-left:3px solid #1677ff;padding-left:9px;font-weight:700}.opp-template-mode{display:flex;gap:7px;margin-bottom:12px}.opp-template-mode button,.opp-condition-group button{height:30px;border:1px solid #cfd9e5;border-radius:15px;background:#fff;color:#475569;padding:0 11px;cursor:pointer;font-size:12px}.opp-template-mode button.active,.opp-condition-group button.active{border-color:#1677ff;background:#eaf2ff;color:#1677ff;font-weight:700}.opp-condition-bar{display:grid;grid-template-columns:1fr 1fr 1fr;gap:9px;padding:12px;border:1px solid #dbe4ef;border-radius:7px;background:#fbfdff;margin-bottom:12px}.opp-condition-group label{display:block;margin-bottom:6px;color:#64748b;font-size:11px}.opp-condition-group{min-width:0}.opp-condition-group button{margin:0 4px 4px 0;padding:0 8px}.opp-form-preview{padding:12px;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}.opp-preview-field{border:1px solid #e2e8f0;border-radius:5px;padding:9px 10px;background:#fff;text-align:left;cursor:pointer}.opp-preview-field:hover{border-color:#93c5fd}.opp-preview-field.active{border-color:#1677ff;box-shadow:0 0 0 2px #eaf2ff}.opp-preview-field b{display:block;color:#334155;font-size:12px}.opp-preview-field small{display:inline-block;margin-top:5px;padding:2px 6px;border-radius:9px;font-size:10px}.opp-preview-field small.common{background:#eef5ff;color:#1677ff}.opp-preview-field small.stage{background:#f3e8ff;color:#7c3aed}.opp-preview-field small.tender{background:#fff7ed;color:#c2410c}.opp-preview-field small.team{background:#ecfdf3;color:#047857}.opp-field-detail .row{padding:10px 12px;border-bottom:1px solid #edf2f7}.opp-field-detail .row:last-child{border-bottom:0}.opp-field-detail label{display:block;color:#94a3b8;font-size:11px;margin-bottom:4px}.opp-field-detail b,.opp-field-detail span{font-size:13px;color:#334155;line-height:1.45}.opp-field-detail .switch-line{display:flex;align-items:center;justify-content:space-between;padding:7px 0;font-size:12px;color:#475569}.opp-field-detail .switch-line i{width:26px;height:15px;border-radius:10px;background:#1677ff;position:relative}.opp-field-detail .switch-line i:after{content:'';position:absolute;right:2px;top:2px;width:11px;height:11px;border-radius:50%;background:#fff}.opp-scheme-map{padding:12px}.opp-scheme-map-row{display:grid;grid-template-columns:1fr 20px 1fr;gap:8px;align-items:center;padding:9px 0;border-bottom:1px solid #edf2f7;font-size:12px}.opp-scheme-map-row:last-child{border-bottom:0}.opp-scheme-map-row span{color:#64748b}.opp-scheme-map-row b{color:#334155;font-weight:600}.opp-scheme-map-row em{font-style:normal;text-align:center;color:#94a3b8}@media(max-width:1180px){.opp-field-workbench{grid-template-columns:1fr;min-width:0}.opp-condition-bar{grid-template-columns:1fr}.opp-form-preview{grid-template-columns:1fr}}
</style>
<style id="crm-module-styles">
/* A2/A4/A5 modules: clean shared layer */
.crm-host{height:calc(100% + 28px);min-height:0;margin-bottom:-28px}
.crm-host>div{height:100%;min-height:0}
.crm-page{height:100%;min-height:0;display:grid;grid-template-rows:auto auto minmax(0,1fr);color:#172033}
/* A2/A4/A5 shared title bar: match the opportunity title bar exactly */
.crm-head{height:50px;min-height:50px;background:#fff;border:1px solid #d6e0ec;border-radius:8px 8px 0 0;padding:8px 12px;display:flex;align-items:center;justify-content:space-between;gap:12px}
.crm-head-main{display:flex;align-items:center;gap:8px;min-width:0}.crm-head-copy{min-width:0}.crm-head h1{margin:0;font-size:17px;line-height:32px;font-weight:800;color:#172033;white-space:nowrap}.crm-head p{display:none}
.crm-head-actions,.crm-toolbar-actions,.crm-inline-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.crm-head-actions:empty{display:none}
.crm-view-switch{display:flex;align-items:center;gap:6px;flex:0 0 auto}.crm-view-switch button{width:30px;height:30px;padding:0;border:1px solid #d7e2ee;background:#fff;border-radius:6px;color:#64748b;display:grid;place-items:center;cursor:pointer;transition:background .15s,border-color .15s,color .15s}.crm-view-switch button:hover{border-color:#93c5fd;background:#eff6ff;color:#1677ff}.crm-view-switch button.active{background:#eaf2ff;color:#1677ff;border-color:#bfdbfe}.crm-view-switch svg{width:17px;height:17px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
.crm-toolbar{min-height:54px;background:#f8fbff;border:1px solid #d6e0ec;border-top:0;padding:9px 12px;display:flex;align-items:flex-start;gap:8px;flex-wrap:wrap}.crm-toolbar-query{display:flex;align-items:center;gap:8px;flex:1 1 auto;min-width:0;flex-wrap:wrap}.crm-toolbar-actions{margin-left:auto;flex:0 0 auto;justify-content:flex-end}
.crm-toolbar input,.crm-toolbar select,.crm-form input,.crm-form select,.crm-form textarea{height:32px;border:1px solid #cfd9e5;border-radius:5px;background:#fff;padding:0 10px;color:#334155}
.crm-toolbar input{width:260px}.crm-toolbar select{min-width:112px}.crm-toolbar .spacer{flex:1}
.crm-stage-filter{display:flex;align-items:center;gap:6px;overflow:auto;max-width:100%}.crm-stage-filter button{height:27px;border:1px solid #c7d8ed;background:#fff;border-radius:14px;padding:0 11px;color:#475569;white-space:nowrap}.crm-stage-filter button.active{background:#1677ff;color:#fff;border-color:#1677ff}
.crm-main{min-height:0;background:#fff;border:1px solid #d6e0ec;border-top:0;border-radius:0 0 8px 8px;overflow:hidden;display:grid;grid-template-rows:minmax(0,1fr) 44px}
.crm-scroll{min-height:0;overflow:auto}.crm-table{width:100%;min-width:1320px;border-collapse:collapse;font-size:12px}.crm-table.compact{min-width:980px}
.crm-table th,.crm-table td{height:50px;border-bottom:1px solid #e8eef5;padding:8px 11px;text-align:left;white-space:nowrap;vertical-align:middle}.crm-table th{height:39px;background:#f6f9fd;color:#475569;font-weight:800;position:sticky;top:0;z-index:2}.crm-table tbody tr:hover td{background:#f8fbff}
.crm-link{border:0;background:transparent;color:#0563e8;padding:0;font:inherit;cursor:pointer}.crm-link:hover{text-decoration:underline}.crm-link.strong{font-weight:800}
.crm-pill{display:inline-flex;align-items:center;height:24px;border-radius:12px;padding:0 9px;background:#eef2f7;color:#475569}.crm-pill.blue{background:#eff6ff;color:#1677ff}.crm-pill.green{background:#ecfdf3;color:#047857}.crm-pill.orange{background:#fff7ed;color:#c2410c}.crm-pill.red{background:#fff1f2;color:#be123c}
.crm-stars{color:#f59e0b;letter-spacing:1px}.crm-actions{display:flex;gap:9px}
.crm-empty{height:100%;display:grid;place-items:center;color:#94a3b8}
.crm-cards{padding:12px;display:grid;grid-template-columns:repeat(3,minmax(240px,1fr));gap:11px;align-content:start}.crm-card{border:1px solid #dbe4ef;border-radius:7px;padding:12px;background:#fff;cursor:pointer}.crm-card:hover{border-color:#93c5fd;box-shadow:0 7px 18px rgba(15,23,42,.07)}.crm-card h3{margin:0 0 8px;color:#0563e8;font-size:14px}.crm-card-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px;font-size:12px;color:#64748b}.crm-card-grid b{color:#334155}
.crm-board{padding:12px;background:#eef2f7;display:grid;grid-template-columns:repeat(5,minmax(210px,1fr));gap:10px;align-items:start;overflow:auto}.crm-lane{min-width:210px}.crm-lane-head{height:38px;background:#1677ff;color:#fff;border-radius:5px;padding:0 11px;display:flex;align-items:center;justify-content:space-between;font-weight:800}.crm-lane .crm-card{margin-top:9px}
.crm-detail{height:100%;min-height:0;background:#fff;border:1px solid #d6e0ec;border-radius:8px;overflow:auto}.crm-detail-head{padding:16px 18px 11px;border-bottom:1px solid #e5edf7}.crm-detail-title{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.crm-detail-title h1{margin:0 0 7px;font-size:22px;color:#15385f}.crm-detail-sub{color:#64748b;font-size:12px}.crm-detail-actions{display:flex;gap:8px;flex-wrap:wrap}
.crm-kpis{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:9px;margin-top:13px}.crm-kpi{border:1px solid #dbe4ef;border-radius:7px;padding:10px;background:#f9fbfe}.crm-kpi b{display:block;font-size:20px;color:#174ea6}.crm-kpi span{font-size:12px;color:#64748b}
.crm-detail-tabs{height:44px;display:flex;align-items:stretch;border-bottom:1px solid #dbe4ef;padding:0 10px;overflow:auto}.crm-detail-tabs button{border:0;border-bottom:3px solid transparent;background:#fff;padding:0 15px;white-space:nowrap;color:#475569}.crm-detail-tabs button.active{color:#1677ff;border-bottom-color:#1677ff;font-weight:800}
.crm-detail-body{padding:15px 18px 24px}.crm-detail-grid{display:grid;grid-template-columns:minmax(0,1.8fr) minmax(280px,.75fr);gap:16px}.crm-section{margin-bottom:15px}.crm-section h3{margin:0;height:34px;border-bottom:1px solid #e5ebf2;display:flex;align-items:center;font-size:14px}.crm-info{display:grid;grid-template-columns:130px 1fr 130px 1fr;border-left:1px solid #dbe4ef;border-top:1px solid #dbe4ef}.crm-info label,.crm-info span{min-height:39px;padding:9px 11px;border-right:1px solid #dbe4ef;border-bottom:1px solid #dbe4ef}.crm-info label{background:#f7f9fc;color:#64748b;text-align:center}.crm-info span{color:#263b55}.crm-info .wide{grid-column:2/5}
.crm-timeline{border-left:2px solid #d7e5fb;margin:6px 0 0 8px;padding-left:17px}.crm-time{position:relative;margin-bottom:18px;line-height:1.55}.crm-time:before{content:"";position:absolute;left:-23px;top:4px;width:9px;height:9px;border-radius:50%;background:#3b82f6}.crm-time b{display:block}.crm-time p{margin:3px 0;color:#475569}.crm-time small{color:#94a3b8}
.crm-stage-ribbon{display:grid;grid-template-columns:repeat(7,minmax(120px,1fr));gap:6px;margin:13px 0 0;overflow:auto}.crm-stage-ribbon button{height:36px;border:0;background:#e5e7eb;clip-path:polygon(0 0,92% 0,100% 50%,92% 100%,0 100%,8% 50%);white-space:nowrap}.crm-stage-ribbon button.done,.crm-stage-ribbon button.active{background:#dffaff;color:#0369a1}.crm-stage-ribbon button.active{font-weight:800}
.crm-setting-grid{height:100%;overflow:auto;display:grid;grid-template-columns:1fr 1fr;gap:12px;padding:12px}.crm-setting-card{border:1px solid #dbe4ef;border-radius:8px;padding:15px;background:#fff}.crm-setting-card h3{margin:0 0 9px}.crm-setting-card p{color:#64748b;line-height:1.6}.crm-setting-card label{display:block;margin:10px 0}
.crm-form{display:grid;grid-template-columns:112px 1fr 112px 1fr;gap:11px 10px;align-items:center}.crm-form label{color:#475569}.crm-form textarea{height:76px;padding:8px 10px;resize:vertical}.crm-form .full{grid-column:2/5}.crm-form-title{grid-column:1/5;font-weight:800;color:#173b64;border-bottom:1px solid #e5ebf2;padding:6px 0}
.crm-toast{position:fixed;left:50%;top:82px;transform:translate(-50%,-8px);z-index:100;background:#172033;color:#fff;border-radius:6px;padding:9px 14px;opacity:0;pointer-events:none;transition:.18s}.crm-toast.show{opacity:1;transform:translate(-50%,0)}
@media(max-width:1180px){.crm-cards{grid-template-columns:repeat(2,minmax(240px,1fr))}.crm-kpis{grid-template-columns:repeat(3,1fr)}.crm-stat-cards{grid-template-columns:repeat(3,1fr)}.crm-detail-grid,.crm-stat-grid{grid-template-columns:1fr}}
@media(max-width:760px){.crm-head{align-items:flex-start;flex-direction:column}.crm-toolbar input{width:100%}.crm-cards,.crm-setting-grid{grid-template-columns:1fr}.crm-kpis,.crm-stat-cards{grid-template-columns:1fr 1fr}.crm-form{grid-template-columns:1fr}.crm-form label,.crm-form input,.crm-form select,.crm-form textarea,.crm-form .full,.crm-form-title{grid-column:1}.crm-info{grid-template-columns:105px 1fr}.crm-info .wide{grid-column:2}.crm-info label:nth-of-type(n+1){}}
/* A4 customer management: clean prototype-aligned implementation */
.crm-customer-page{grid-template-rows:50px 52px minmax(0,1fr)}
.crm-customer-page.no-toolbar{grid-template-rows:50px minmax(0,1fr)}
.crm-customer-toolbar{min-height:52px;background:#f8fbff;border:1px solid #d6e0ec;border-top:0;padding:8px 12px;display:flex;align-items:center;gap:12px;white-space:nowrap;overflow-x:auto}
.crm-customer-query{display:flex;align-items:center;gap:8px;min-width:max-content;flex:1 1 auto}.crm-customer-query input,.crm-customer-query select{height:32px;border:1px solid #cfd9e5;border-radius:5px;background:#fff;padding:0 10px;color:#334155}.crm-customer-query>input:first-child{width:330px}.crm-customer-query select{min-width:126px}
.crm-customer-actions{display:flex;align-items:center;gap:7px;margin-left:auto;min-width:max-content}
.crm-title-wrap{position:relative;display:flex;align-items:center;gap:7px}.crm-title-trigger{height:32px;border:0;background:transparent;padding:0 3px;display:flex;align-items:center;gap:6px;font-size:17px;font-weight:800;color:#172033}.crm-title-trigger:hover{color:#1677ff}.crm-title-trigger .arrow{font-size:11px;color:#64748b;transition:transform .15s}.crm-title-trigger.open .arrow{transform:rotate(180deg)}
.crm-search-menu{display:none;position:absolute;left:0;top:38px;z-index:40;width:420px;min-height:265px;background:#fff;border:1px solid #d5dfeb;border-radius:7px;box-shadow:0 14px 34px rgba(15,23,42,.16);overflow:hidden;grid-template-columns:116px minmax(0,1fr)}.crm-search-menu.show{display:grid}.crm-search-menu.is-custom{width:610px}.crm-search-tabs{background:#f6f8fb;border-right:1px solid #e1e8f0;padding:7px 0}.crm-search-tabs button{width:100%;height:40px;border:0;background:transparent;padding:0 13px;text-align:left;color:#334155;font-weight:700}.crm-search-tabs button.active{background:#1677ff;color:#fff}.crm-search-panel{display:none;min-width:0}.crm-search-panel.active{display:block}.crm-search-list{padding:8px 10px}.crm-search-list-row{height:38px;display:flex;align-items:center;gap:8px}.crm-search-list-row>button:first-child{flex:1;height:34px;border:0;background:#fff;border-radius:4px;padding:0 8px;text-align:left;color:#334155}.crm-search-list-row>button:first-child:hover{background:#eef6ff;color:#1677ff}.crm-search-pin{width:30px;height:30px;border:0;background:transparent;color:#1677ff;cursor:pointer;display:flex;align-items:center;justify-content:center}.crm-search-pin svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}.crm-search-editor{padding:16px 14px;display:grid;grid-template-columns:88px minmax(0,1fr);gap:12px;align-items:center}.crm-search-editor input,.crm-search-editor select{height:34px;border:1px solid #ccd8e5;border-radius:4px;padding:0 9px;min-width:0}.crm-search-editor .condition{grid-column:1/3;display:grid;grid-template-columns:28px 150px 100px minmax(0,1fr) 32px;gap:8px;align-items:center}.crm-search-editor .condition .remove{height:32px;border:1px solid #fecaca;background:#fff;color:#ef4444;border-radius:4px}.crm-search-editor .hint{color:#64748b}.crm-search-editor .advanced{grid-column:2/3;display:flex;align-items:center;gap:6px;color:#64748b}.crm-search-editor .sort{grid-column:2/3;display:grid;grid-template-columns:minmax(0,1fr) 84px 84px;gap:8px}.crm-search-editor .sort button{height:34px;border:1px solid #ccd8e5;background:#fff;border-radius:4px}.crm-search-editor .sort button.active{background:#94a3b8;color:#fff}.crm-search-editor footer{grid-column:1/3;display:flex;justify-content:flex-end;gap:8px;padding-top:10px;border-top:1px solid #edf2f7}
.crm-table{font-size:13px}.crm-table th,.crm-table td{height:58px;padding:8px 12px}.crm-table th{height:39px}.crm-customer-table{min-width:1580px}.crm-check-col{width:42px;min-width:42px;text-align:center!important}.crm-sticky-action{position:sticky!important;right:0;z-index:5;min-width:238px;background:#fff!important;box-shadow:-7px 0 13px -12px rgba(15,23,42,.8)}.crm-table thead .crm-sticky-action{z-index:8;background:#f6f9fd!important}.crm-table tbody tr:hover .crm-sticky-action{background:#f8fbff!important}.crm-row-actions{display:flex;align-items:center;gap:9px}.crm-row-actions button{border:0;background:transparent;color:#0563e8;padding:0;white-space:nowrap;cursor:pointer}.crm-row-actions button:hover{text-decoration:underline}.crm-danger{color:#dc2626!important}
.crm-customer-split{height:100%;min-height:0;display:grid;grid-template-columns:350px 18px minmax(720px,1fr);background:#eef2f7;gap:0;padding:9px;transition:grid-template-columns .22s ease}.crm-customer-split.collapsed{grid-template-columns:0 18px minmax(720px,1fr)}.crm-customer-split>aside,.crm-customer-split>main{min-height:0;background:#fff;border:1px solid #d6e0ec;overflow:auto}.crm-customer-split>aside{min-width:0;transition:opacity .15s ease,transform .22s ease}.crm-customer-split.collapsed>aside{opacity:0;transform:translateX(-16px);visibility:hidden;pointer-events:none}.crm-customer-split>main{border-left:0}.crm-customer-split-divider{position:relative;min-width:18px;background:#eef2f7;border-top:1px solid #d6e0ec;border-bottom:1px solid #d6e0ec}.crm-customer-split-toggle{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:18px;height:48px;padding:0;border:1px solid #cbd5e1;border-radius:0 6px 6px 0;background:#fff;color:#1677ff;cursor:pointer;z-index:4}.crm-customer-split-toggle:hover{background:#eff6ff}.crm-split-head{height:42px;position:sticky;top:0;z-index:3;background:#f6f9fd;border-bottom:1px solid #dfe7f1;padding:0 11px;display:grid;grid-template-columns:24px minmax(0,1fr) auto;align-items:center;gap:7px;font-weight:800}.crm-split-head span{color:#64748b;font-weight:500}.crm-split-customer{width:100%;min-height:84px;border:0;border-bottom:1px solid #edf2f7;background:#fff;padding:9px 11px;display:grid;grid-template-columns:24px minmax(0,1fr) auto;gap:7px;text-align:left;cursor:pointer}.crm-split-customer:hover{background:#f8fbff}.crm-split-customer.active{background:#eaf2ff;box-shadow:inset 3px 0 #1677ff}.crm-split-customer b{display:block;color:#0563e8;margin-bottom:5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.crm-split-customer .rel-line{display:flex;align-items:center;gap:6px;color:#334155}.crm-split-customer .rel-word{font-weight:700}.crm-split-customer small{display:block;color:#64748b;line-height:1.55;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.crm-split-stage{align-self:start;margin-top:3px;white-space:nowrap}
.crm-card-board{height:100%;min-width:950px;display:grid;grid-template-rows:42px minmax(0,1fr);background:#eef2f7;overflow:hidden}.crm-card-board-toolbar{display:flex;align-items:center;padding:6px 10px;background:#fff;border-bottom:1px solid #d6e0ec}.crm-card-board-toolbar select{height:30px;border:1px solid #cfd9e5;border-radius:4px;padding:0 9px}.crm-card-lanes{min-height:0;display:grid;gap:10px;padding:10px;align-items:start;overflow:auto}.crm-card-lanes.cols-3{grid-template-columns:repeat(3,minmax(270px,1fr))}.crm-card-lanes.cols-5{grid-template-columns:repeat(5,minmax(210px,1fr))}.crm-card-lane{min-height:430px}.crm-card-lane>header{height:38px;background:#8fc8ff;color:#173b64;padding:0 10px;display:flex;align-items:center;justify-content:space-between;font-weight:800}.crm-card-lane>header span{font-weight:600}.crm-customer-card{background:#fff;border:1px solid #cfd8e3;padding:10px;margin-top:7px;cursor:pointer}.crm-customer-card:hover{border-color:#60a5fa;box-shadow:0 6px 15px rgba(15,23,42,.08)}.crm-customer-card>button{display:block;font-size:13px;font-weight:800}.crm-customer-card .business-link{display:block;margin-top:4px;color:#0563e8;font-weight:800}.crm-customer-card footer{margin-top:6px;display:flex;align-items:center;justify-content:space-between;color:#64748b;font-size:12px}.crm-customer-card .mini-stars{font-size:12px;color:#f59e0b}
.crm-customer-summary{padding:15px 17px 12px;background:#fff;border-bottom:1px solid #e5edf7}.crm-summary-top h2{margin:0 0 8px;font-size:20px;color:#15385f}.crm-summary-meta{display:flex;align-items:center;gap:9px 18px;flex-wrap:wrap;color:#64748b;font-size:12px}.crm-summary-actions{display:flex;align-items:center;gap:7px;flex-wrap:wrap;margin-top:12px}.crm-customer-stage{display:grid;grid-template-columns:repeat(3,minmax(130px,1fr));gap:7px;margin:13px 0 10px}.crm-customer-stage span{height:35px;display:flex;align-items:center;justify-content:center;background:#e5e7eb;color:#475569;clip-path:polygon(0 0,92% 0,100% 50%,92% 100%,0 100%,8% 50%)}.crm-customer-stage span.active{background:#dffaff;color:#0369a1;font-weight:800}
.crm-customer-metrics{display:grid;grid-template-columns:repeat(5,minmax(130px,1fr));gap:8px}.crm-customer-metrics>div{border:1px solid #dbe4ef;border-radius:5px;padding:10px;background:#fff}.crm-customer-metrics>div:nth-child(1){border-color:#8ec5ff}.crm-customer-metrics>div:nth-child(2){border-color:#b9df83}.crm-customer-metrics>div:nth-child(3){border-color:#efc682}.crm-customer-metrics>div:nth-child(4){border-color:#c6a5f4}.crm-customer-metrics>div:nth-child(5){border-color:#8ec5ff}.crm-customer-metrics b{display:block;color:#174ea6;font-size:16px}.crm-customer-metrics>div:nth-child(2) b{color:#65a30d}.crm-customer-metrics>div:nth-child(3) b{color:#d97706}.crm-customer-metrics>div:nth-child(4) b{color:#7c3aed}.crm-customer-metrics span{font-size:11px;color:#64748b}.crm-customer-level{display:inline-flex;align-items:center;gap:5px}.crm-customer-level b{color:#172033;font-size:14px}.crm-latest-follow{margin-top:10px;border:1px solid #e0e9f4;background:#fff}.crm-latest-follow .follow-head{display:flex;align-items:center;gap:10px;padding:9px 12px;border-bottom:0;font-size:12px}.crm-latest-follow .follow-head strong{color:#172033}.crm-latest-follow em{font-style:normal;background:#eaf2ff;color:#1677ff;padding:2px 7px;border-radius:10px}.crm-latest-follow a{color:#1677ff}.crm-latest-follow .follow-body{padding:9px 12px;font-size:12px;color:#475569}.crm-latest-follow .follow-person{margin-bottom:5px;color:#64748b}
.crm-customer-detail-tabs{background:#fff}.crm-customer-tab-body{padding:14px 16px 24px;min-width:0}.crm-tab-actions{min-height:38px;display:flex;align-items:center;justify-content:flex-end;gap:8px;margin-bottom:10px}.crm-tab-actions>span:first-child{margin-right:auto;color:#475569}.crm-contact-inline-actions{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;padding:10px 0 2px}.crm-contact-inline-actions .left,.crm-contact-inline-actions .right{display:flex;align-items:center;gap:8px}.crm-contact-list-panel .crm-table input,.crm-contact-list-panel .crm-table select{height:30px;width:100%;border:1px solid #cdd8e5;border-radius:3px;padding:0 7px}.crm-contact-list-panel .crm-table .row-check{width:16px;height:16px}.crm-contact-list-panel .crm-table tr.is-selected{background:#eef6ff}.crm-contact-list-panel .crm-table tr.is-new{background:#f0fdf4}.crm-contact-list-panel .crm-table .crm-danger{color:#ef4444}.crm-contact-list-panel .crm-table .placeholder{color:#94a3b8}.crm-contact-list-panel .crm-table{min-width:1180px}.crm-agreement-list{margin-bottom:12px;border:1px solid #dbe4ef;background:#fff}.crm-agreement-list table{width:100%;border-collapse:collapse}.crm-agreement-list th,.crm-agreement-list td{padding:9px 10px;border-bottom:1px solid #e7edf4;text-align:left;font-size:13px}.crm-agreement-list th{background:#f5f8fc;color:#334155}.crm-agreement-list tr.active{background:#eef6ff}.crm-agreement-actions{display:flex;justify-content:flex-end;gap:8px;margin-bottom:10px}.crm-agreement-actions button:disabled{opacity:.45;cursor:not-allowed}.crm-ocr-context{font-size:13px;color:#64748b;margin-right:4px}.crm-ocr-scenarios{display:flex;align-items:center;justify-content:center;gap:8px;padding:12px}.crm-ocr-scenarios .btn.active{background:#1677ff;border-color:#1677ff;color:#fff;box-shadow:0 0 0 2px rgba(22,119,255,.14)}.crm-ocr-success-banner{margin:0 0 12px;padding:10px 12px;background:#eaf8ef;color:#16803d;border-radius:4px;font-weight:700}.crm-ocr-compare{border:1px solid #dce5ef;border-radius:6px;overflow:hidden;margin-top:10px}.crm-ocr-compare dl{display:grid;grid-template-columns:130px 1fr;margin:0}.crm-ocr-compare dt,.crm-ocr-compare dd{padding:8px 10px;border-bottom:1px solid #edf2f7;margin:0}.crm-ocr-compare dt{background:#f8fafc;color:#64748b}.crm-ocr-choice-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:12px}.crm-ocr-choice{border:1px solid #dbe4ef;border-radius:6px;padding:12px;display:flex;align-items:flex-start;gap:8px}.crm-ocr-choice.active{border-color:#80bfff;background:#eef7ff}.crm-info-note{padding:12px;border-top:1px solid #e5ebf2}.crm-info-note strong{display:block;margin-bottom:6px;color:#475569}.crm-info-attachment{padding:12px;border-top:1px solid #e5ebf2}.crm-info-attachment a{color:#1677ff}
.crm-fold-panel{border:1px solid #dbe4ef;background:#fff;margin-bottom:11px}.crm-fold-panel>header{height:38px;padding:0 12px;background:#f7f9fc;border-bottom:1px solid #e5ebf2;display:flex;align-items:center;justify-content:space-between}.crm-fold-panel>header h3{margin:0;font-size:14px}.crm-fold-panel>header button{border:0;background:transparent;color:#1677ff}.crm-fold-body.hidden{display:none}.crm-info-grid{display:grid;grid-template-columns:130px minmax(180px,1fr) 130px minmax(180px,1fr)}.crm-info-grid label,.crm-info-grid span{min-height:38px;border-right:1px solid #dbe4ef;border-bottom:1px solid #dbe4ef;padding:8px 10px}.crm-info-grid label{background:#fbfcfe;color:#64748b;text-align:center}.crm-info-grid span{color:#263b55}.crm-info-grid .wide{grid-column:2/5}
.crm-contact-tab{display:grid;gap:14px}.crm-contact-list-panel,.crm-contact-network-panel{border:1px solid #dbe4ef;background:#fff}.crm-contact-list-panel>header,.crm-contact-network-panel>header{height:42px;padding:0 11px;border-bottom:1px solid #e5ebf2;display:flex;align-items:center;justify-content:space-between}.crm-contact-list-panel>header h3,.crm-contact-network-panel>header h3{margin:0;font-size:14px}.crm-contact-list-actions,.crm-network-tools{display:flex;align-items:center;gap:7px}.crm-contact-list-panel .crm-table{min-width:1100px}.crm-contact-network-canvas{min-height:360px;position:relative;overflow:auto;background:linear-gradient(#f8fbff 1px,transparent 1px),linear-gradient(90deg,#f8fbff 1px,transparent 1px);background-size:24px 24px}.crm-network-node{position:absolute;min-width:126px;padding:10px 12px;border:1px solid #93c5fd;border-radius:8px;background:#fff;box-shadow:0 5px 14px rgba(15,23,42,.08);text-align:center}.crm-network-node strong{display:block;color:#0563e8}.crm-network-node small{color:#64748b}.crm-network-line{position:absolute;height:2px;background:#60a5fa;transform-origin:left center}.crm-network-root{left:48%;top:45px}.crm-network-a{left:17%;top:215px}.crm-network-b{left:47%;top:240px}.crm-network-c{left:76%;top:195px}
.crm-form-page{height:100%;overflow:auto;background:#fff;border:1px solid #d6e0ec;border-radius:8px;padding-bottom:24px}.crm-form-page-head{min-height:58px;border-bottom:1px solid #dbe4ef;padding:10px 18px;display:flex;align-items:center;justify-content:space-between}.crm-form-page-head h1{margin:0;font-size:18px}.crm-form-page-head p{margin:4px 0 0;color:#64748b}.crm-form-section{margin:16px 18px 0;border:1px solid #dbe4ef}.crm-form-section>header{height:38px;padding:0 12px;background:#f6f9fd;display:flex;align-items:center;justify-content:space-between}.crm-form-section>header h3{margin:0;font-size:14px}.crm-form-grid{padding:14px;display:grid;grid-template-columns:130px minmax(220px,1fr) 130px minmax(220px,1fr);gap:10px;align-items:center}.crm-form-grid label{text-align:right;color:#475569}.crm-form-grid input,.crm-form-grid select,.crm-form-grid textarea{width:100%;height:34px;border:1px solid #cfd9e5;border-radius:4px;padding:0 10px}.crm-form-grid textarea{height:70px;padding:8px 10px;resize:vertical}.crm-form-grid .span3{grid-column:2/5}.crm-contact-editor{padding:0 10px 10px;overflow:auto}.crm-contact-editor table{min-width:1150px}.crm-contact-editor input,.crm-contact-editor select{width:100%;height:34px;border:1px solid #cfd9e5;border-radius:4px;padding:0 8px}.crm-contact-editor-footer{display:flex;justify-content:flex-end;gap:8px;padding:8px 10px 0}.crm-form-foot{display:flex;justify-content:center;gap:28px;padding:24px 18px 0}.crm-attachment-row{padding:14px;display:flex;align-items:center;gap:10px}.crm-file-chip{display:inline-flex;align-items:center;gap:8px;padding:7px 10px;border:1px solid #dbe4ef;border-radius:5px;background:#f8fbff}.crm-file-chip button{border:0;background:transparent;color:#dc2626}
.crm-import-page{height:100%;overflow:auto;background:#fff;border:1px solid #d6e0ec;border-radius:8px}.crm-import-head{height:58px;border-bottom:1px solid #dbe4ef;padding:0 20px;display:flex;align-items:center;justify-content:space-between}.crm-import-head h1{font-size:18px;margin:0}.crm-import-body{max-width:1040px;padding:22px 28px 36px;display:grid;gap:20px}.crm-import-body section{border-bottom:1px solid #edf2f7;padding-bottom:18px}.crm-import-body h3{margin:0 0 13px;font-size:15px}.crm-import-modes{display:flex;gap:10px}.crm-import-modes button{height:36px;border:1px solid #cbd5e1;background:#fff;border-radius:5px;padding:0 18px}.crm-import-modes button.active{border-color:#1677ff;background:#eaf2ff;color:#1677ff}.crm-import-checks{display:flex;gap:18px;margin-top:12px;color:#475569}.crm-upload-zone{min-height:210px;border:1px dashed #94a3b8;border-radius:6px;background:#fbfdff;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:7px;cursor:pointer}.crm-upload-zone:hover{border-color:#1677ff;background:#eff6ff}.crm-upload-zone b{color:#1677ff}.crm-file-rules ol{margin:0;padding-left:20px;color:#475569;line-height:1.8}.crm-import-foot{display:flex;justify-content:center;gap:28px;padding-top:4px}
.crm-ocr-page{height:100%;overflow:auto;background:#fff;border:1px solid #d6e0ec;border-radius:8px}.crm-ocr-head{height:58px;border-bottom:1px solid #dbe4ef;padding:0 18px;display:flex;align-items:center;justify-content:space-between}.crm-ocr-head h1{margin:0;font-size:18px}.crm-ocr-body{max-width:900px;margin:0 auto;padding:28px}.crm-ocr-scenarios{display:flex;gap:7px;justify-content:center;margin-bottom:14px}.crm-ocr-stage{min-height:420px;border:1px solid #dbe4ef;background:#fff;display:grid;place-items:center;padding:22px}.crm-ocr-upload{width:520px;min-height:260px;border:1px dashed #94a3b8;background:#fbfdff;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px}.crm-ocr-upload .icon{font-size:42px;color:#1677ff}.crm-ocr-loading{text-align:center}.crm-ocr-spinner{width:58px;height:58px;border:5px solid #dbeafe;border-top-color:#1677ff;border-radius:50%;margin:0 auto 16px;animation:crmSpin .8s linear infinite}@keyframes crmSpin{to{transform:rotate(360deg)}}.crm-ocr-result{width:min(720px,100%)}.crm-ocr-result h3{text-align:center;margin:0 0 16px}.crm-ocr-alert{padding:12px;border-radius:6px;margin-bottom:12px}.crm-ocr-alert.warn{background:#fff7ed;color:#9a3412}.crm-ocr-alert.fail{background:#fff1f2;color:#be123c}.crm-match-card{border:1px solid #dbe4ef;border-radius:6px;padding:11px;margin-bottom:8px;display:grid;grid-template-columns:22px 1fr;gap:9px}.crm-match-card.active{border-color:#60a5fa;background:#eff6ff}.crm-ocr-result footer{display:flex;justify-content:flex-end;gap:8px;margin-top:14px}.crm-ocr-success-form{width:100%}.crm-ocr-success-form .crm-form-grid{padding:0}
.crm-agreement-upload{padding:14px;display:flex;align-items:center;gap:10px}.crm-agreement-upload .btn{flex:0 0 auto}
@media(max-width:1200px){.crm-customer-split{grid-template-columns:310px 18px minmax(660px,1fr)}.crm-customer-split.collapsed{grid-template-columns:0 18px minmax(660px,1fr)}.crm-customer-metrics{grid-template-columns:repeat(3,1fr)}.crm-search-menu{width:600px}.crm-form-grid{grid-template-columns:120px minmax(200px,1fr)}.crm-form-grid label,.crm-form-grid input,.crm-form-grid select,.crm-form-grid textarea,.crm-form-grid .span3{grid-column:auto}.crm-card-lanes.cols-5{grid-template-columns:repeat(5,minmax(190px,1fr))}}
.crm-selection-summary{height:40px;padding:0 12px;display:flex;align-items:center;border-bottom:1px solid #dbe4ef;color:#334155}.crm-selection-summary b{color:#1677ff;margin:0 4px}
.modal.crm-member-modal{width:632px}.crm-member-search{display:flex;align-items:center;gap:8px}.crm-member-search input{flex:1;height:34px;border:1px solid #cfd9e5;border-radius:4px;padding:0 10px}.crm-member-candidates{margin-top:12px;border:1px solid #dbe4ef;max-height:286px;overflow:auto}.crm-member-candidates label{min-height:58px;padding:8px 12px;border-bottom:1px solid #edf2f7;display:grid;grid-template-columns:18px 36px minmax(0,1fr);gap:10px;align-items:center}.crm-member-avatar,.crm-member-profile>span{width:36px;height:36px;border-radius:50%;display:grid;place-items:center;background:#eaf2ff;color:#1677ff;font-weight:800}.crm-member-candidates b,.crm-member-profile b{display:block}.crm-member-candidates small,.crm-member-profile small{display:block;color:#64748b;margin-top:3px}.crm-member-selected{margin-top:12px;padding:9px 11px;background:#f8fbff;border:1px solid #dbe4ef}.crm-member-field{display:grid;grid-template-columns:72px minmax(0,1fr);gap:10px;align-items:center;margin-top:14px;padding-top:14px;border-top:1px solid #edf2f7}.crm-member-permissions{display:flex;gap:8px}.crm-member-permissions button{height:30px;border:1px solid #cfd9e5;border-radius:4px;background:#fff;padding:0 14px}.crm-member-permissions button.active{border-color:#1677ff;background:#eaf2ff;color:#1677ff}.crm-member-profile{display:grid;grid-template-columns:36px minmax(0,1fr);gap:10px;align-items:center}.crm-member-delete p{color:#64748b}.crm-delete-confirm{background:#ef4444!important;border-color:#ef4444!important;color:#fff!important}
/* 线索列表第一批收口:标题/操作/工具、统计卡、筛选栏、固定列与横向滚动 */
.crm-business-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.crm-control-stack{min-width:0;background:#f8fbff;border-left:1px solid #d6e0ec;border-right:1px solid #d6e0ec}
.crm-list-tools{display:flex;align-items:center;gap:4px;padding-left:4px;border-left:1px solid #e0e7ef}.crm-tool-icon{width:32px;height:32px;padding:0;border:1px solid transparent;border-radius:5px;background:transparent;color:#4b5565;display:grid;place-items:center;transition:all .15s}.crm-tool-icon:hover{background:#eaf2ff;border-color:#bfdbfe;color:#1677ff;transform:translateY(-1px)}.crm-tool-icon .shared-list-icon,.opp-tool-icon .shared-list-icon{width:22px;height:22px;stroke-width:4;stroke-linecap:round;stroke-linejoin:round}.crm-tool-icon .shared-list-icon--settings,.opp-tool-icon .shared-list-icon--settings{width:23px;height:23px}.crm-tool-icon.refreshing svg{animation:oppSpin .55s linear}
@media(max-width:1280px){.crm-head{align-items:flex-start}.crm-head-actions{max-width:68%}.crm-business-actions{justify-content:flex-end}}
/* 线索第二批:真实分屏、检索方案、详情、导入与我的关注 */
.crm-lead-title-wrap{position:relative;min-width:0}.crm-lead-title-btn{height:32px;border:0;background:transparent;padding:0;display:flex;align-items:center;gap:5px;font-size:17px;font-weight:800;color:#172033;cursor:pointer}.crm-lead-title-btn .chev{font-size:11px;color:#64748b;transition:transform .15s}.crm-lead-title-btn.open .chev{transform:rotate(180deg)}
.crm-lead-view-menu{display:none;position:absolute;left:0;top:38px;width:420px;min-height:275px;z-index:40;background:#fff;border:1px solid #d5dfeb;border-radius:8px;box-shadow:0 14px 34px rgba(15,23,42,.16);grid-template-columns:116px 1fr;overflow:hidden}.crm-lead-view-menu.show{display:grid}.crm-lead-view-menu aside{background:#f6f8fb;border-right:1px solid #e1e8f0;padding:8px 0}.crm-lead-view-menu aside button{width:100%;height:42px;border:0;background:transparent;text-align:left;padding:0 14px;font-weight:700;color:#334155}.crm-lead-view-menu aside button.active{background:#1677ff;color:#fff}.crm-lead-view-menu section{padding:12px 14px}.crm-lead-view-menu h4{margin:0 0 8px;font-size:13px;color:#64748b}.crm-lead-view-row{height:38px;display:grid;grid-template-columns:minmax(0,1fr) 30px;align-items:center;border-radius:5px}.crm-lead-view-row:hover{background:#f4f8ff}.crm-lead-view-link,.crm-lead-pin{border:0;background:transparent;cursor:pointer}.crm-lead-view-link{text-align:left;height:38px;color:#172033}.crm-lead-pin{width:28px;height:28px;color:#1677ff;display:grid;place-items:center}.crm-lead-pin svg{width:17px;height:17px;fill:none;stroke:currentColor;stroke-width:1.8}.crm-lead-pin.active svg path:first-child{fill:#1677ff33}
/* S1 · 线索模块第3批统一修复(19项) 2026-07-11 */
.lead2-form-grid input[type="radio"],.lead2-form-grid input[type="checkbox"]{width:14px;height:14px;padding:0;vertical-align:middle}.lead2-form-grid div>label{display:inline-flex;align-items:center;gap:5px;text-align:left}.lead2-modal-grid input[type="checkbox"],.lead2-modal-grid input[type="radio"]{width:14px;height:14px;padding:0;vertical-align:middle}.lead2-modal-grid .lead2-field-grid label{display:flex;align-items:center;gap:7px;text-align:left;white-space:nowrap}
.lead2-page{height:100%;min-height:0;display:grid;grid-template-rows:auto auto minmax(0,1fr);color:#172033}
.lead2-head{height:50px;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 12px;background:#fff;border:1px solid #d6e0ec;border-radius:8px 8px 0 0}
.lead2-head-left,.lead2-head-right,.lead2-view-switch,.lead2-toolbar,.lead2-filters,.lead2-actions,.lead2-tools{display:flex;align-items:center;gap:8px}.lead2-head-left{position:relative}.lead2-head-left .crm-lead-title-wrap{z-index:20}
.lead2-head h1{margin:0;font-size:17px;font-weight:800}.lead2-view-switch button{width:30px;height:30px;padding:0;border:1px solid #d7e2ee;border-radius:6px;background:#fff;color:#64748b;display:grid;place-items:center}.lead2-view-switch button.active{border-color:#bfdbfe;background:#eaf2ff;color:#1677ff}.lead2-view-switch svg{width:17px;height:17px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
.lead2-controls{background:#f8fbff;border-left:1px solid #d6e0ec;border-right:1px solid #d6e0ec}.lead2-stats{display:flex;align-items:center;gap:12px;padding:10px 12px 0;overflow:auto}.lead2-stats button{height:38px;min-width:108px;border:1px solid #b8c4d2;border-radius:7px;background:#fff;padding:0 14px;display:flex;align-items:center;justify-content:center;gap:4px;color:#334155}.lead2-stats button:hover{border-color:#7fb3f4;background:#fff}.lead2-stats span{font-size:12px}.lead2-stats b{font-size:17px;color:#124ecb}
.lead2-toolbar{min-height:54px;align-items:flex-start;padding:9px 12px;overflow-x:auto}.lead2-filters{min-width:max-content;flex-wrap:nowrap}.lead2-filters select,.lead2-filters input{height:32px;border:1px solid #cfd9e5;border-radius:4px;background:#fff;padding:0 9px}.lead2-filters select{min-width:98px}.lead2-filters input{width:210px}.lead2-actions{margin-left:auto;min-width:max-content}.lead2-tools{padding-left:5px;border-left:1px solid #dde5ee}.lead2-tool{width:32px;height:32px;border:1px solid transparent;border-radius:5px;background:transparent;color:#475569;display:grid;place-items:center}.lead2-tool:hover{background:#eaf2ff;border-color:#bfdbfe;color:#1677ff}.lead2-tool svg{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:1.8}
.lead2-main{min-height:0;display:grid;grid-template-rows:minmax(0,1fr) 44px;background:#fff;border:1px solid #d6e0ec;border-top:0;border-radius:0 0 8px 8px;overflow:hidden}.lead2-scroll{min-height:0;overflow:auto}.lead2-table{width:max-content;min-width:100%;border-collapse:collapse;font-size:12px}.lead2-table th,.lead2-table td{height:48px;padding:7px 10px;border-bottom:1px solid #e8eef5;text-align:left;white-space:nowrap;background:#fff}.lead2-table th{height:39px;position:sticky;top:0;z-index:4;background:#f6f9fd;color:#475569;font-weight:800;cursor:grab}.lead2-table tbody tr:hover td{background:#f8fbff}.lead2-table .fixed-no{position:sticky;left:0;z-index:5;width:66px;min-width:66px}.lead2-table .fixed-name{position:sticky;left:66px;z-index:5;width:190px;min-width:190px;box-shadow:6px 0 10px -10px rgba(15,23,42,.65)}.lead2-table .fixed-action{position:sticky;right:0;z-index:5;min-width:180px;box-shadow:-6px 0 10px -10px rgba(15,23,42,.65)}.lead2-table thead .fixed-no,.lead2-table thead .fixed-name,.lead2-table thead .fixed-action{z-index:8;background:#f6f9fd}.lead2-table tbody tr:hover .fixed-no,.lead2-table tbody tr:hover .fixed-name,.lead2-table tbody tr:hover .fixed-action{background:#f8fbff}.lead2-table .long{max-width:190px;overflow:hidden;text-overflow:ellipsis}.lead2-row-actions{display:flex;gap:10px}.lead2-link{border:0;background:transparent;color:#0563e8;padding:0;cursor:pointer;font:inherit}.lead2-link:hover{text-decoration:underline}.lead2-danger{color:#e11d48!important}.lead2-green{color:#16a34a}.lead2-orange{color:#ea580c}.lead2-blue{color:#2563eb}.lead2-status{font-weight:700}.lead2-check{display:flex;align-items:center;gap:6px}.lead2-check input{margin:0}
.lead2-split{height:100%;min-height:0;display:grid;grid-template-columns:330px 18px minmax(0,1fr);gap:0;padding:10px;background:#eef2f7;transition:grid-template-columns .22s ease}.lead2-split.collapsed{grid-template-columns:0 18px minmax(0,1fr)}.lead2-split-left,.lead2-split-detail{min-width:0;min-height:0;background:#fff;border:1px solid #d6e0ec;overflow:auto}.lead2-split-left{transition:opacity .15s ease,transform .22s ease}.lead2-split.collapsed .lead2-split-left{opacity:0;transform:translateX(-16px);visibility:hidden;pointer-events:none}.lead2-split-divider{position:relative;min-width:18px;background:#eef2f7;border-top:1px solid #d6e0ec;border-bottom:1px solid #d6e0ec}.lead2-split-toggle{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:18px;height:48px;padding:0;border:1px solid #cbd5e1;border-radius:0 6px 6px 0;background:#fff;color:#1677ff;cursor:pointer;z-index:4}.lead2-split-detail{border-left:0;padding:0}.lead2-split-head{height:38px;position:sticky;top:0;z-index:3;background:#f6f9fd;border-bottom:1px solid #dfe7f1;padding:0 12px;display:grid;grid-template-columns:28px minmax(0,1fr);gap:4px;align-items:center;font-weight:800}.lead2-split-head .lead2-check{justify-content:center}.lead2-split-row{width:100%;min-height:78px;border:0;border-bottom:1px solid #edf2f7;background:#fff;padding:9px 11px;text-align:left;display:grid;grid-template-columns:28px minmax(0,1fr) auto;gap:5px 8px;align-items:center;cursor:pointer}.lead2-split-row.active{background:#eaf6ff;box-shadow:inset 3px 0 #1677ff}.lead2-split-row .lead2-check{grid-column:1;grid-row:1/3;align-self:center;justify-self:center}.lead2-split-row b{grid-column:2;grid-row:1;color:#0563e8;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.lead2-split-row .lead2-split-status{grid-column:3;grid-row:1;justify-self:end}.lead2-split-row small{grid-column:2/4;grid-row:2;color:#64748b}
.lead2-detail{height:100%;min-height:0;background:#fff;border:1px solid #d6e0ec;border-radius:8px;overflow:auto}.lead2-split-detail .lead2-detail{border:0;border-radius:0}.lead2-detail-top{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;padding:16px 18px 8px}.lead2-detail-top h1{margin:0;font-size:21px;color:#15385f}.lead2-detail-actions{display:flex;align-items:center;gap:9px;flex-wrap:wrap;padding:0 18px 12px;border-bottom:1px solid #e5edf7}.lead2-detail-tabs{height:44px;display:flex;border-bottom:1px solid #dbe4ef;padding:0 12px}.lead2-detail-tabs button{height:44px;border:0;border-bottom:3px solid transparent;background:#fff;padding:0 16px}.lead2-detail-tabs button.active{color:#1677ff;border-bottom-color:#1677ff;font-weight:800}.lead2-detail-body{padding:18px 24px 28px}.lead2-plain-title{font-size:14px;margin:0 0 12px}.lead2-info{display:grid;grid-template-columns:135px minmax(0,1fr);gap:0 16px;max-width:920px}.lead2-info dt,.lead2-info dd{margin:0;padding:6px 0;min-height:31px;line-height:1.55}.lead2-info dt{color:#64748b;text-align:right}.lead2-info dd{color:#172033}.lead2-history-stage{min-height:360px;background:#fff;padding:0 0 26px}.lead2-history-toolbar{display:flex;justify-content:flex-end;max-width:790px;margin:0 0 12px;padding:0}.lead2-history-toolbar .btn{height:30px;padding:0 10px;font-size:12px;color:#475569;background:#fff;border-color:#d8e1eb}.lead2-history{position:relative;display:grid;gap:16px;max-width:850px;margin:0;padding:2px 0 2px 42px}.lead2-history:before{content:'';position:absolute;left:14px;top:26px;bottom:26px;width:1px;background:#aebdce}.lead2-history-item{position:relative;padding:13px 16px 12px;border:1px solid #d9e3ec;border-radius:8px;background:#f8fbfd;box-shadow:0 1px 3px rgba(15,23,42,.04)}.lead2-history-item:before{content:'';position:absolute;left:-34px;top:23px;width:10px;height:10px;border-radius:50%;background:#2f80ed;box-shadow:0 0 0 2px #e9f3ff}.lead2-history-meta{display:flex;align-items:center;gap:16px;min-height:24px;flex-wrap:wrap;font-size:12px;line-height:1.4}.lead2-history-meta .date{color:#64748b}.lead2-history-meta .person,.lead2-history-meta .dept{color:#172033;font-weight:600}.lead2-history-tag{display:inline-flex;align-items:center;justify-content:center;min-width:58px;height:22px;padding:0 12px;border-radius:3px;font-size:12px;font-weight:700;line-height:22px}.lead2-history-tag.release{background:#f7b5c2;color:#7f1d35}.lead2-history-tag.feedback{background:#ffc56f;color:#7a4300}.lead2-history-tag.claim{background:#9edcf7;color:#075985}.lead2-history-tag.default{background:#dbeafe;color:#1d4ed8}.lead2-history-content{margin-top:10px;min-height:38px;padding:9px 12px;background:#dff3fb;color:#26384d;line-height:1.65}.lead2-history-empty{min-height:320px;max-width:790px;display:flex;flex-direction:column;align-items:center;justify-content:center;color:#94a3b8;gap:10px;background:#fff}.lead2-history-empty svg{width:108px;height:86px;stroke:#b8c4d2;fill:#f8fafc;stroke-width:1.5}.lead2-log{width:100%;min-width:880px;border-collapse:collapse;font-size:12px}.lead2-log th,.lead2-log td{height:40px;border-bottom:1px solid #edf2f7;text-align:left;padding:0 9px}.lead2-log th{background:#f6f9fd}
.lead2-form-page{height:100%;overflow:auto;background:#fff;border:1px solid #d6e0ec;border-radius:8px;padding:18px 24px 30px}.lead2-form-page h2{margin:0 0 18px;font-size:17px}.lead2-form-section{max-width:980px;margin-bottom:22px}.lead2-form-section h3{font-size:14px;margin:0 0 13px;padding-bottom:8px;border-bottom:1px solid #e5ebf2}.lead2-form-grid{display:grid;grid-template-columns:130px minmax(220px,1fr) 130px minmax(220px,1fr);gap:10px 12px;align-items:center}.lead2-form-grid label{color:#475569;text-align:right}.lead2-form-grid input,.lead2-form-grid select,.lead2-form-grid textarea{width:100%;height:34px;border:1px solid #cfd9e5;border-radius:4px;background:#fff;padding:0 9px}.lead2-form-grid textarea{height:78px;padding:8px 9px;resize:vertical}.lead2-form-grid .wide{grid-column:2/5}.lead2-required:before{content:'*';color:#e11d48;margin-right:3px}.lead2-form-foot{display:flex;align-items:center;gap:10px;padding-left:142px}.lead2-form-foot .grow{flex:1}.lead2-note{padding:9px 12px;background:#fff8df;border:1px solid #f5df9f;color:#7c5d13;line-height:1.55;border-radius:4px}.lead2-tags{display:flex;gap:6px;flex-wrap:wrap}.lead2-tag{display:inline-flex;align-items:center;gap:5px;padding:4px 8px;background:#f1f5f9;border:1px solid #dbe4ef;border-radius:4px}.lead2-tag button{border:0;background:transparent;color:#64748b;padding:0}
.lead2-setting-tabs{display:flex;gap:24px;border-bottom:1px solid #dbe4ef;margin-bottom:18px}.lead2-setting-tabs button{height:38px;border:0;border-bottom:3px solid transparent;background:#fff}.lead2-setting-tabs button.active{color:#1677ff;border-bottom-color:#1677ff;font-weight:800}.lead2-rule-list{display:grid;gap:20px;max-width:720px}.lead2-rule-item h4{margin:0 0 9px}.lead2-inline-number{display:flex;align-items:center;gap:10px}.lead2-inline-number input{width:80px;height:34px;border:1px solid #cfd9e5;border-radius:4px;padding:0 8px}
.lead2-import{height:100%;overflow:auto;background:#fff;border:1px solid #d6e0ec;border-radius:8px}.lead2-import-head{height:58px;display:flex;align-items:center;padding:0 20px;border-bottom:1px solid #dbe4ef}.lead2-import-head h1{font-size:18px;margin:0}.lead2-import-body{max-width:1060px;padding:22px 28px 36px;display:grid;gap:22px}.lead2-import-body section{padding-bottom:20px;border-bottom:1px solid #edf2f7}.lead2-import-modes{display:flex;gap:9px}.lead2-import-modes button{height:36px;border:1px solid #cbd5e1;background:#fff;border-radius:5px;padding:0 18px}.lead2-import-modes button.active{border-color:#1677ff;background:#eaf2ff;color:#1677ff}.lead2-upload{min-height:220px;border:1px dashed #94a3b8;border-radius:8px;background:#fbfdff;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;cursor:pointer}.lead2-upload:hover,.lead2-upload.dragging{border-color:#1677ff;background:#eff6ff}.lead2-upload strong{color:#1677ff}.lead2-upload-icon{font-size:38px;color:#1677ff}.lead2-demo-upload{min-height:210px;border:1px dashed #94a3b8;border-radius:8px;background:#fbfdff;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:9px;cursor:pointer;text-align:center;padding:20px}.lead2-demo-upload:hover{border-color:#1677ff;background:#eff6ff}.lead2-demo-upload strong{color:#1677ff}.lead2-demo-upload small{color:#64748b}.lead2-progress{width:92px;height:92px;border-radius:50%;display:grid;place-items:center;background:conic-gradient(#1677ff calc(var(--p)*1%),#e5e7eb 0);position:relative}.lead2-progress:after{content:'';position:absolute;inset:8px;border-radius:50%;background:#fff}.lead2-progress b{position:relative;z-index:1;color:#1677ff}.lead2-result{min-height:220px;border:1px dashed #cbd5e1;border-radius:8px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px}.lead2-result-icon{width:64px;height:64px;border-radius:50%;display:grid;place-items:center;font-size:30px;font-weight:800;background:#dcfce7;color:#16a34a}.lead2-result.fail .lead2-result-icon{background:#fee2e2;color:#dc2626}
.lead2-modal-grid{display:grid;grid-template-columns:112px minmax(0,1fr) 112px minmax(0,1fr);gap:11px 10px;align-items:center}.lead2-modal-grid label{color:#475569;text-align:right}.lead2-modal-grid input,.lead2-modal-grid select,.lead2-modal-grid textarea{width:100%;height:34px;border:1px solid #cfd9e5;border-radius:4px;padding:0 9px}.lead2-modal-grid textarea{height:82px;padding:8px 9px;resize:vertical}.lead2-modal-grid .full{grid-column:2/5}.lead2-readonly{color:#172033;min-height:32px;display:flex;align-items:center}.lead2-upload-small{grid-column:2/5;border:1px dashed #94a3b8;padding:12px;min-height:70px;cursor:pointer}.lead2-file-list{grid-column:2/5;display:grid;gap:6px}.lead2-file{display:flex;align-items:center;gap:10px}.lead2-file span{flex:1}.lead2-field-top{display:flex;align-items:center;gap:12px;margin-bottom:12px}.lead2-field-top input[type=text]{flex:1;height:34px;border:1px solid #cfd9e5;border-radius:4px;padding:0 9px}.lead2-field-grid{max-height:360px;overflow:auto;display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:7px;padding:10px;background:#f8fafc}.lead2-field-grid label{height:30px;display:flex;align-items:center;gap:6px}.lead2-field-note{margin-top:12px;color:#475569}.lead2-picker{display:grid;grid-template-columns:1fr 1fr;gap:12px}.lead2-picker-list{border:1px solid #dbe4ef;min-height:260px;padding:10px;overflow:auto}.lead2-picker-list h4{margin:0 0 9px}.lead2-picker-list label{display:flex;align-items:center;gap:7px;height:30px}.lead2-picker-selected{display:flex;gap:6px;flex-wrap:wrap;align-content:flex-start}
@media(max-width:1100px){.lead2-split{grid-template-columns:290px 18px minmax(0,1fr)}.lead2-split.collapsed{grid-template-columns:0 18px minmax(0,1fr)}.lead2-form-grid{grid-template-columns:120px 1fr}.lead2-form-grid label,.lead2-form-grid input,.lead2-form-grid select,.lead2-form-grid textarea,.lead2-form-grid .wide{grid-column:auto}.lead2-form-grid .wide{grid-column:2}.lead2-modal-grid{grid-template-columns:105px 1fr}.lead2-modal-grid .full,.lead2-upload-small,.lead2-file-list{grid-column:2}}
</style>
</head>
<body>
<div class="neo-shell top-mode home-active">
<header class="topbar">
<button aria-label="返回系统首页" class="system-home-logo" onclick="setActivePage('home','首页')" title="返回首页" type="button"></button>
<div class="topbar-spacer"></div>
<div class="ai-pill" onclick="openAiPanel()"><span class="ui-icon" data-icon="sparkles"></span>AI助手</div>
<button class="topbar-icon" onclick="openModal('帮助中心')" title="帮助"><span class="ui-icon" data-icon="circle-help"></span></button>
<button class="topbar-icon" onclick="setActivePage('adminUsers','后台 / 用户管理')" title="管理后台"><span class="ui-icon" data-icon="settings"></span></button>
<button class="bell" id="noticeTrigger" onclick="openNoticePanel()" title="消息通知" aria-label="消息通知" aria-expanded="false"><span class="ui-icon" data-icon="bell"></span><span class="notice-badge" id="noticeUnreadBadge">3</span></button>
<button aria-label="切换至左侧导航模式" class="topbar-icon" data-nav-toggle="" onclick="toggleNavMode()" title="切换至左侧导航模式"><span class="ui-icon" data-icon="grid-2x2"></span></button>
<div class="user"><div class="avatar"></div><span>Panyu10109</span></div>
</header>
<aside class="side-nav-v7" id="sideNav">
<nav class="side-menu">
<button class="side-item active" data-label="首页" data-page="home"><span class="ui-icon" data-icon="home"></span>首页</button>
<button class="side-item side-group" data-collapse-target="workplanSideGroup"><span class="workplan-nav-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><rect x="3" y="5" width="18" height="16" rx="2"></rect><path d="M7 3v4M17 3v4M3 10h18M8 15l2 2 4-4"></path></svg></span>工作计划</button>
<button class="side-item side-sub" data-label="工作计划 / 工作日历" data-page="workplan">工作日历</button>
<button class="side-item side-sub" data-label="工作计划 / 计划管理" data-page="workplanList">计划管理</button>
<button class="side-item side-sub" data-label="工作计划 / 出差与拜访" data-page="workplanTrips">出差与拜访</button>
<button class="side-item side-group" data-collapse-target="leadSideGroup"><span class="nav-ref-icon nav-ref-icon--lead"><svg aria-hidden="true" viewbox="0 0 24 24"><path d="M4 6.5v6.2c0 3 1.9 5 4.6 5s4.6-2 4.6-5V10c0-2.2 1.4-3.8 3.4-3.8s3.4 1.6 3.4 3.8v7.5" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></path><circle cx="4" cy="5" fill="currentColor" r="1.5"></circle><circle cx="20" cy="19" fill="currentColor" r="1.5"></circle></svg></span>线索</button>
<button class="side-item side-sub" data-label="线索 / 线索公海" data-page="lead">线索公海</button>
<button class="side-item side-sub" data-label="线索 / 我的线索" data-page="leadClaim">我的线索</button>
<button class="side-item side-sub" data-label="线索 / 我的关注" data-page="leadFollow">我的关注</button>
<button class="side-item side-sub" data-label="线索 / 线索管理" data-page="leadManage">线索管理</button>
<button class="side-item side-sub" data-label="线索 / 线索管理设置" data-page="leadSetting">线索管理设置</button>
<button class="side-item side-group" data-collapse-target="customerSideGroup"><span class="ui-icon" data-icon="users"></span>客户</button>
<button class="side-item side-sub" data-label="客户 / 客户总览" data-page="customerPool">客户总览</button>
<button class="side-item side-sub" data-label="客户 / 我的客户" data-page="myCustomer">我的客户</button>
<button class="side-item side-sub" data-label="客户 / 客户公海" data-page="customerSea">客户公海</button>
<button class="side-item side-sub" data-label="客户 / 联系人" data-page="contact">联系人</button>
<button class="side-item side-sub" data-label="客户 / 客户查重" data-page="customerDedupe">客户查重</button>
<button class="side-item side-sub" data-label="客户 / 客户交割" data-page="customerHandover">客户交割</button>
<button class="side-item side-group" data-collapse-target="oppSideGroup"><span class="nav-ref-icon nav-ref-icon--opp">$</span>商机</button>
<button class="side-item side-sub" data-label="商机 / 销售机会" data-page="myOpportunity">销售机会</button>
<button class="side-item side-sub" data-label="商机 / 商机管理" data-page="myOpportunity">商机管理</button>
<button class="side-item side-sub" data-label="商机 / 商机公海" data-page="myOpportunity">商机公海</button><button class="side-item side-sub" data-label="商机 / 商机管理设置" data-page="oppSetting">商机管理设置</button>
<button class="side-item" data-label="项目管理 / 项目管理" data-page="project"><span class="ui-icon" data-icon="folder-kanban"></span>项目</button>
<button class="side-item" data-label="数据看板" data-page="board"><span class="ui-icon" data-icon="bar-chart-3"></span>数据看板</button>
<button class="side-item" data-label="后台 / 用户管理" data-page="adminUsers"><span class="ui-icon" data-icon="settings-2"></span>管理后台</button>
</nav>
<button class="side-collapse" onclick="toggleSidebarCollapse()">&lt;&lt;收起菜单</button>
</aside>
<nav aria-label="顶部导航" class="neo-global-nav" id="neoNav">
<button class="neo-tab active" data-label="首页" data-page="home"><span class="ui-icon" data-icon="home"></span>首页</button>
<div class="neo-menu"><button class="neo-tab" data-label="工作计划 / 工作日历" data-page="workplan"><span class="workplan-nav-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><rect x="3" y="5" width="18" height="16" rx="2"></rect><path d="M7 3v4M17 3v4M3 10h18M8 15l2 2 4-4"></path></svg></span>工作计划</button><div class="neo-menu-panel"><button data-label="工作计划 / 工作日历" data-page="workplan">工作日历</button><button data-label="工作计划 / 计划管理" data-page="workplanList">计划管理</button><button data-label="工作计划 / 出差与拜访" data-page="workplanTrips">出差与拜访</button></div></div>
<div class="neo-menu">
<button class="neo-tab" data-label="线索 / 线索公海" data-page="lead"><span class="nav-ref-icon nav-ref-icon--lead"><svg aria-hidden="true" viewbox="0 0 24 24"><path d="M4 6.5v6.2c0 3 1.9 5 4.6 5s4.6-2 4.6-5V10c0-2.2 1.4-3.8 3.4-3.8s3.4 1.6 3.4 3.8v7.5" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></path><circle cx="4" cy="5" fill="currentColor" r="1.5"></circle><circle cx="20" cy="19" fill="currentColor" r="1.5"></circle></svg></span>线索</button>
<div class="neo-menu-panel"><button data-label="线索 / 线索公海" data-page="lead">线索公海</button><button data-label="线索 / 我的线索" data-page="leadClaim">我的线索</button><button data-label="线索 / 我的关注" data-page="leadFollow">我的关注</button><button data-label="线索 / 线索管理" data-page="leadManage">线索管理</button><button data-label="线索 / 线索管理设置" data-page="leadSetting">线索管理设置</button></div>
</div>
<div class="neo-menu">
<button class="neo-tab" data-label="客户 / 客户总览" data-page="customerPool"><span class="ui-icon" data-icon="users"></span>客户</button>
<div class="neo-menu-panel"><button data-label="客户 / 客户总览" data-page="customerPool">客户总览</button><button data-label="客户 / 我的客户" data-page="myCustomer">我的客户</button><button data-label="客户 / 客户公海" data-page="customerSea">客户公海</button><button data-label="客户 / 联系人" data-page="contact">联系人</button><button data-label="客户 / 客户查重" data-page="customerDedupe">客户查重</button><button data-label="客户 / 客户交割" data-page="customerHandover">客户交割</button><button data-label="客户 / 客户管理设置" data-page="customerSetting">客户管理设置</button></div>
</div>
<div class="neo-menu">
<button class="neo-tab" data-label="商机" data-page="myOpportunity"><span class="nav-ref-icon nav-ref-icon--opp">$</span>商机</button>
<div class="neo-menu-panel"><button data-label="商机 / 销售机会" data-page="myOpportunity">销售机会</button><button data-label="商机 / 商机管理" data-page="myOpportunity">商机管理</button><button data-label="商机 / 商机公海" data-page="myOpportunity">商机公海</button><button data-label="商机 / 商机管理设置" data-page="oppSetting">商机管理设置</button></div>
</div>
<div class="neo-menu">
<button class="neo-tab" data-label="项目管理 / 项目管理" data-page="project"><span class="ui-icon" data-icon="folder-kanban"></span>项目</button>
<div class="neo-menu-panel"><button data-label="项目管理 / 项目管理" data-page="project">项目管理</button><button data-label="项目管理 / 我的项目" data-page="myProject">我的项目</button><button data-label="项目管理 / 项目管理设置" data-page="projectSetting">项目管理设置</button><button data-label="项目管理 / 项目统计" data-page="projectStats">项目统计</button></div>
</div>
<button class="neo-tab" data-label="数据看板" data-page="board"><span class="ui-icon" data-icon="bar-chart-3"></span>数据看板</button>
<button class="neo-tab" data-label="后台 / 用户管理" data-page="adminUsers"><span class="ui-icon" data-icon="settings-2"></span>管理后台</button>
</nav>
<nav aria-label="当前位置" class="location-bar"><span class="location-current" id="locationCurrent">首页</span></nav>
<main class="content">
<section class="page active" id="home">
<div class="workbench">
<div class="main-col">
<div class="home-data-bar">
<div class="home-data-context">
<span class="home-data-context-label">经营数据范围</span>
<div class="home-scope-picker" id="homeScopePicker">
<button class="home-scope-trigger" id="homeScopeTrigger" type="button" aria-haspopup="listbox" aria-expanded="false"><span id="homeScopeLabel">华南战队 / 广州一部</span><span class="home-scope-chevron">⌄</span></button>
<div class="home-scope-popover" id="homeScopePopover" role="dialog" aria-label="选择经营数据范围">
<input class="home-scope-search" id="homeScopeSearch" placeholder="搜索战队或部门" autocomplete="off"/>
<div class="home-scope-options" id="homeScopeOptions">
<div class="home-scope-group" data-home-scope-group="个人视角 我的数据"><div class="home-scope-group-title">个人视角</div><button type="button" data-home-scope="mine"><span>我的数据</span><small>本人负责及协同</small></button></div>
<div class="home-scope-group" data-home-scope-group="华南战队"><div class="home-scope-group-title">华南战队</div><button type="button" data-home-scope="south"><span>华南战队</span><small>战队汇总</small></button><button type="button" data-home-scope="gz1"><span>广州一部</span><small>华南战队 / 广州一部</small></button><button type="button" data-home-scope="sz1"><span>深圳一部</span><small>华南战队 / 深圳一部</small></button></div>
</div>
</div>
</div>
<span class="home-data-updated"><svg class="home-data-clock" viewBox="0 0 24 24" fill="none" aria-hidden="true"><circle cx="12" cy="12" r="8.5" stroke="currentColor" stroke-width="1.7"/><path d="M12 7.5v5l3.2 1.9" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg>数据更新至:<b id="homeDataUpdated">2026-08-02 10:30</b></span>
</div>
</div>
<div class="hero">
<div class="hero-copy"><h1>早上好,Panyu10109!</h1><p>今天是 2026年6月10日 星期三。重点关注今日工作、项目推进和客户经营。</p></div>
<div class="home-hero-side">
<div class="hero-actions"><button class="btn primary" onclick="faithfulProject.newProject()">+ 新增项目</button><button class="btn ghost" onclick="customerV2.openHomeCustomer()">+ 新增客户</button><button class="btn ghost" onclick="openHomeWorkplan()">+ 新增工作计划</button></div>
</div>
</div>
<div class="kpis">
<div class="kpi" data-home-kpi="lead" onclick="homeOpenScopedPage('lead','线索 / 线索公海')"><b><span class="ui-icon ui-icon--kpi" data-icon="inbox"></span>线索池</b><div class="num">32<span class="metric-unit">条</span></div><small>今日新增 6 条 · 待领取 26 条</small><div class="kpi-trend-bar"><i style="width:58%"></i></div></div>
<div class="kpi good" data-home-kpi="opportunity" onclick="homeOpenScopedPage('myOpportunity','商机 / 商机管理')"><b><span class="ui-icon ui-icon--kpi" data-icon="briefcase-business"></span>进行中商机</b><div class="num">18</div><small>预计金额 <b>10,000 万元</b></small><div class="kpi-trend-bar"><i style="width:80%"></i></div></div>
<div class="kpi warn" data-home-kpi="project" onclick="homeOpenScopedPage('project','项目管理 / 项目管理')"><b><span class="ui-icon ui-icon--kpi" data-icon="folder-kanban"></span>在途项目</b><div class="num">8</div><small>逾期预警 <span class="kpi-delta down">2 个</span></small><div class="kpi-trend-bar"><i style="width:52%"></i></div></div>
<div class="kpi" data-home-kpi="customer" onclick="homeOpenScopedPage('customerPool','客户 / 客户总览')"><b><span class="ui-icon ui-icon--kpi" data-icon="users"></span>客户数量</b><div class="num">286</div><small>本月新增 <span class="kpi-delta up">+12</span></small><div class="kpi-trend-bar"><i style="width:48%"></i></div></div>
</div>
<div class="layout-grid">
<div class="card" data-home-card="work"><div class="card-head"><span><span class="ui-icon ui-icon--section" data-icon="calendar-check"></span>今日工作</span><button class="more" type="button" onclick="setActivePage('workplan','工作计划 / 工作日历')">查看全部&gt;</button></div><div class="card-body"><div class="today-work-list" id="homeTodayWorkList" data-home-live="work"></div></div></div>
<div class="card" data-home-card="funnel"><div class="card-head"><span><span class="ui-icon ui-icon--section" data-icon="funnel"></span>成交漏斗</span><span class="home-card-head-actions"><small id="homeFunnelTotal">未完结 58 项</small><button class="more" type="button" data-home-action="funnel-details">详情&gt;</button></span></div><div class="card-body"><div class="funnel-visual" id="homeFunnelList" data-home-live="funnel"></div></div></div>
<div class="card ai-card" data-home-card="ai"><div class="card-head"><span><span class="ui-icon ui-icon--section" data-icon="sparkles"></span>AI 智能营销助手</span><span class="more" onclick="openAiPanel()">展开对话&gt;</span></div><div class="card-body"><div class="ai-box" onclick="openAiPanel()">👋 <b>Panyu10109,今日重点提醒</b><br/>☑ 胡总(广州XXXX公司)<b>7天未联系</b>,建议今日跟进<br/>☑ XX地铁监控项目处于<b>商务阶段</b>,建议核对关键推进事项<br/>☑ XX政务功能厅即将进入<b>招投标阶段</b>,方案卡需补充<br/><span style="color:#94a3b8;font-size:11px">点击展开 AI 对话 →</span></div><div class="assistant-actions"><button class="btn" onclick="sendAiShortcut('今天应该优先跟进哪些客户?给我分析一下');openAiPanel()">跟进建议</button><button class="btn" onclick="sendAiShortcut('分析重点客户的近期跟进与风险');openAiPanel()">客户洞察</button><button class="btn" onclick="sendAiShortcut('分析当前商机结构与推进风险');openAiPanel()">商机分析</button></div></div></div>
</div>
<div class="management-grid">
<div class="card" data-home-card="customer"><div class="card-head"><span><span class="ui-icon ui-icon--section" data-icon="building-2"></span>重点客户</span><button class="more" type="button" onclick="homeOpenScopedPage('customerPool','客户 / 客户总览')">更多&gt;</button></div><div class="card-body"><div class="home-customer-list" id="homeCustomerList" data-home-live="customer"></div></div></div>
<div class="card" data-home-card="project"><div class="card-head"><span><span class="ui-icon ui-icon--section" data-icon="folder-kanban"></span>项目进度</span><button class="more" type="button" onclick="homeOpenScopedPage('project','项目管理 / 项目管理')">更多&gt;</button></div><div class="card-body"><div class="home-project-table-wrap"><table class="table home-project-table"><thead><tr><th>项目名称</th><th>阶段</th><th>负责人</th><th>进度</th><th>下一阶段</th></tr></thead><tbody id="homeProjectRows" data-home-live="project"></tbody></table></div></div></div>
<div class="card" data-home-card="double50"><div class="card-head"><span><span class="ui-icon ui-icon--section" data-icon="target"></span>“双五零”战略完成情况</span><span class="planning-state">模块规划中</span></div><div class="card-body"><div class="planning-placeholder"><span class="ui-icon" data-icon="construction"></span><b>模块设计中</b><p>指标口径与数据链路正在规划,暂不展示模拟完成率。</p><div class="planning-tags"><span>指标口径待确认</span><span>数据链路待建设</span></div></div></div></div>
</div>
<div class="priority-grid">
<div class="card" data-home-card="reminder"><div class="card-head"><span><span class="ui-icon ui-icon--section" data-icon="triangle-alert"></span>经营提醒</span></div><div class="card-body"><ul class="risk-list" id="homeReminderList" data-home-live="reminder"></ul></div></div>
<div class="card" data-home-card="quick"><div class="card-head"><span><span class="ui-icon ui-icon--section" data-icon="grid-2x2"></span>AI快捷入口</span><span class="planning-state">功能规划中</span></div><div class="card-body"><div class="quick-grid"><button class="ai-btn" onclick="homeShowNotice('营销对练功能规划中')">营销对练</button><button class="ai-btn" onclick="homeShowNotice('乾坤袋功能规划中')">乾坤袋</button><button class="ai-btn" onclick="homeShowNotice('报备助手功能规划中')">报备助手</button><button class="ai-btn" onclick="homeShowNotice('方案助手功能规划中')">方案助手</button><button class="ai-btn" onclick="homeShowNotice('招投标助手功能规划中')">招投标助手</button><button class="ai-btn" onclick="homeShowNotice('会议纪要功能规划中')">会议纪要</button></div></div></div>
</div>
</div>
</section>
<section class="page workplan-native-page" id="workplan"><div id="workplanRoot"><div class="workplan-native" id="workplanMount"><main class="main">
<section class="view active" id="view-dashboard">
<div class="card workbench-surface">
<div class="double50 double50-legacy"><div class="d50-legacy-card stock"><span class="d50-watermark">50</span><div class="d50-legacy-title">双50 · 存量经营 <small>老客户深耕、存量项目推进</small></div><div class="d50-legacy-number" id="stock-summary">0 / 0 项</div><div class="d50-legacy-desc">已完成 / 本月计划</div><div class="d50-legacy-bar"><i id="stock-progress"></i></div></div><div class="d50-legacy-card growth"><span class="d50-watermark">50</span><div class="d50-legacy-title">双50 · 增量开发 <small>新客户、新项目、行业关系</small></div><div class="d50-legacy-number" id="growth-summary">0 / 0 项</div><div class="d50-legacy-desc">已完成 / 本月计划</div><div class="d50-legacy-bar"><i id="growth-progress"></i></div></div></div>
<div class="calendar-layout">
<div><div class="calendar-tools"><button class="btn small" onclick="wp_showToast('已切换上月(演示)')">‹</button><b>2026 年 7 月</b><button class="btn small" onclick="wp_showToast('已切换下月(演示)')">›</button><span class="spacer"></span><button class="btn primary small" onclick="wp_openNewForSelected()">+ 填写今日工作</button></div>
<div class="calendar-grid">
<div class="calendar-week">一</div><div class="calendar-week">二</div><div class="calendar-week">三</div><div class="calendar-week">四</div><div class="calendar-week">五</div><div class="calendar-week">六</div><div class="calendar-week">日</div>
<div class="calendar-cell mute"><div class="calendar-day">29</div></div><div class="calendar-cell mute"><div class="calendar-day">30</div></div><div class="calendar-cell"><div class="calendar-day">1</div><button class="calendar-chip">电话沟通|华润置地有限公司</button></div><div class="calendar-cell"><div class="calendar-day">2</div><button class="calendar-chip visit">客户拜访|广州零亿信息科技有限公司</button></div><div class="calendar-cell"><div class="calendar-day">3</div><button class="calendar-chip solution">方案沟通|海康威视智慧园区项目</button></div><div class="calendar-cell"><div class="calendar-day">4</div></div><div class="calendar-cell"><div class="calendar-day">5</div></div>
<div class="calendar-cell"><div class="calendar-day">6</div><button class="calendar-chip visit">跑工地|菱湖院子二期项目</button></div><div class="calendar-cell"><div class="calendar-day">7</div><button class="calendar-chip">电话沟通|武汉卷烟厂改造项目</button></div><div class="calendar-cell"><div class="calendar-day">8</div></div><div class="calendar-cell"><div class="calendar-day">9</div><button class="calendar-chip solution" data-work-item="第二轮技术方案确认">方案沟通|光电集团安防改造项目</button><button class="calendar-chip" data-work-item="确认投标沟通时间">电话沟通|华润置地有限公司</button><button class="calendar-chip visit" data-work-item="二期项目需求回访">客户拜访|广州零亿信息科技有限公司</button><button class="calendar-chip fbb" data-work-item="补充方案资料">资料补充|望江党校智慧校园建设项目</button><button class="calendar-chip" data-work-item="确认沙龙参会名单">组会组局|广东省建筑业协会</button></div><div class="calendar-cell"><div class="calendar-day">10</div><button class="calendar-chip">组会组局|广东省建筑业协会</button></div><div class="calendar-cell"><div class="calendar-day">11</div></div><div class="calendar-cell"><div class="calendar-day">12</div></div>
<div class="calendar-cell"><div class="calendar-day">13</div><button class="calendar-chip travel">出差计划|武汉项目推进</button></div><div class="calendar-cell"><div class="calendar-day">14</div><button class="calendar-chip visit">客户拜访|菱湖院子二期项目</button></div><div class="calendar-cell"><div class="calendar-day">15</div><button class="calendar-chip fbb">资料补充|望江党校智慧校园建设项目</button></div><div class="calendar-cell"><div class="calendar-day">16</div></div><div class="calendar-cell"><div class="calendar-day">17</div><button class="calendar-chip">电话沟通|华润置地有限公司</button></div><div class="calendar-cell"><div class="calendar-day">18</div></div><div class="calendar-cell"><div class="calendar-day">19</div></div>
<div class="calendar-cell"><div class="calendar-day">20</div><button class="calendar-chip visit" data-work-item="二期推进沟通">客户拜访|广州零亿信息科技有限公司</button><button class="calendar-chip" data-work-item="确认项目负责人">电话沟通|武汉卷烟厂改造项目</button><button class="calendar-chip solution" data-work-item="汇报材料确认">方案沟通|海康威视智慧园区项目</button><button class="calendar-chip fbb" data-work-item="补充客户反馈">资料补充|望江党校智慧校园建设项目</button><button class="calendar-chip visit" data-work-item="现场沟通">客户拜访|华润置地有限公司</button><button class="calendar-chip" data-work-item="行业活动邀约">组会组局|广东省建筑业协会</button></div><div class="calendar-cell"><div class="calendar-day">21</div><button class="calendar-chip solution">方案沟通|海康威视智慧园区项目</button></div><div class="calendar-cell"><div class="calendar-day">22</div></div><div class="calendar-cell"><div class="calendar-day">23</div><button class="calendar-chip travel">出差计划|深圳重点客户拜访</button></div><div class="calendar-cell"><div class="calendar-day">24</div><button class="calendar-chip" data-work-item="行业沙龙筹备">组会组局|广东省建筑业协会</button><button class="calendar-chip" data-work-item="确认参会人员">电话沟通|华润置地有限公司</button><button class="calendar-chip solution" data-work-item="技术资料沟通">方案沟通|光电集团安防改造项目</button><button class="calendar-chip visit" data-work-item="客户关系维护">客户拜访|广州零亿信息科技有限公司</button><button class="calendar-chip fbb" data-work-item="补充现场资料">资料补充|望江党校智慧校园建设项目</button></div><div class="calendar-cell"><div class="calendar-day">25</div></div><div class="calendar-cell"><div class="calendar-day">26</div></div>
<div class="calendar-cell"><div class="calendar-day">27</div><button class="calendar-chip visit">客户拜访|光电集团安防改造项目</button></div><div class="calendar-cell"><div class="calendar-day">28</div><button class="calendar-chip">电话沟通|华润置地有限公司</button></div><div class="calendar-cell today"><div class="calendar-day"><b>29</b></div><button class="calendar-chip solution" data-work-item="智慧园区方案沟通">方案沟通|海康威视智慧园区项目</button><button class="calendar-chip" data-work-item="投标方案回访">电话沟通|华润置地有限公司</button><button class="calendar-chip fbb" data-work-item="项目资料补充">资料补充|望江党校智慧校园建设项目</button><button class="calendar-chip visit" data-work-item="投标方案回访">客户拜访|华润置地有限公司</button><button class="calendar-chip solution" data-work-item="技术交流">方案沟通|光电集团安防改造项目</button></div><div class="calendar-cell"><div class="calendar-day">30</div><button class="calendar-chip visit">客户拜访|广州零亿信息科技有限公司</button></div><div class="calendar-cell"><div class="calendar-day">31</div><button class="calendar-chip">组会组局|广东省建筑业协会</button></div><div class="calendar-cell mute"><div class="calendar-day">1</div></div><div class="calendar-cell mute"><div class="calendar-day">2</div></div>
</div>
</div>
<div class="day-side"><div class="card-head"><div class="card-title" id="day-summary-title">7 月 29 日 · 当日工作(3)</div></div><div class="daily-list" id="day-summary-list"></div><div class="daily-actions"><button class="btn preview-add" style="width:100%" onclick="wp_openNewForSelected()">+ 新增工作计划</button></div><div class="day-metrics"><div class="day-focus-head"><b>工作提醒</b></div><div class="metric-mini-list"><div><span>今日未完成</span><b>6</b></div><div class="warn"><span>已超时</span><b>2</b></div><div class="good"><span>本周已完成</span><b>14</b></div><div><span>重点对象覆盖</span><b>8 / 12</b></div><div><span>待补资料</span><b>3</b></div></div></div><div class="day-metrics"><div class="day-focus-head"><b>本周工作概览</b></div><div class="metric-list"><div class="metric-row"><div class="metric-left"><i class="dot"></i>客户/商机沟通</div><div class="metric-right">8 次</div></div><div class="metric-row"><div class="metric-left"><i class="dot green"></i>客户拜访 / 跑工地</div><div class="metric-right">4 次</div></div><div class="metric-row"><div class="metric-left"><i class="dot purple"></i>方案沟通 / 资料补充</div><div class="metric-right">5 项</div></div><div class="metric-row"><div class="metric-left"><i class="dot amber"></i>行业关系 / 组会组局</div><div class="metric-right">2 次</div></div></div></div></div>
</div>
</div>
</section>
<section class="view" id="view-actions">
<div class="workplan-list-titlebar"><span>计划管理</span></div>
<div class="card">
<div class="page-tools"><input class="search" id="action-search" placeholder="搜索工作计划或关联对象" onkeydown="if(event.key==='Enter') wp_filterActionTable()"><select class="filter" id="action-type-filter" onchange="wp_filterActionTable()"><option value="">全部工作类型</option><option>电话沟通</option><option>客户拜访</option><option>出差安排</option><option>跑工地</option><option>方案沟通</option><option>组会组局</option><option>资料补充</option></select><select class="filter" id="action-status-filter" onchange="wp_filterActionTable()"><option value="">全部完成情况</option><option>未完成</option><option>已完成</option><option>已超时</option></select><select class="filter" id="action-period-filter" onchange="wp_filterActionTable()"><option value="all">全部时间</option><option value="today">今日</option><option value="week">本周</option><option value="month">本月</option><option value="history">历史记录</option></select><button class="btn primary small" onclick="wp_filterActionTable()">查询</button><span class="spacer"></span><button class="btn small" onclick="wp_showToast('已导出当前筛选结果(演示)')">导出</button><button class="btn primary" onclick="wp_openActionModal()">+ 新增工作计划</button><div class="workplan-list-tools"><div style="position:relative"><button class="workplan-tool-icon" id="workplan-density-trigger" title="列表密度" onclick="wp_toggleWorkplanPopover('density')"><svg aria-hidden="true" fill="none" stroke="currentColor" stroke-width="4" viewBox="0 0 48 48"><path d="M4 8h14.5M33 8H18.5m0 0v34"/><path fill="currentColor" stroke="none" d="M39 9.5 37 13h4l-2-3.5ZM39 38.5 37 35h4l-2-3.5Z"/><path d="M39 13h2l-2-3.5-2 3.5h2Zm0 0v22m0 0h2l-2 3.5-2-3.5h2Z"/></svg></button><div class="workplan-popover workplan-density-pop" id="workplan-density-pop"><button data-density="compact" onclick="wp_setWorkplanDensity('compact')">紧凑</button><button data-density="default" class="active" onclick="wp_setWorkplanDensity('default')">默认</button><button data-density="relaxed" onclick="wp_setWorkplanDensity('relaxed')">宽松</button></div></div><button class="workplan-tool-icon" id="workplan-refresh" title="刷新数据" onclick="wp_refreshWorkplanList()"><svg aria-hidden="true" fill="none" stroke="currentColor" stroke-width="4" viewBox="0 0 48 48"><path d="M38.837 18C36.463 12.136 30.715 8 24 8 15.163 8 8 15.163 8 24s7.163 16 16 16c7.455 0 13.72-5.1 15.496-12M40 8v10H30"/></svg></button><div style="position:relative"><button class="workplan-tool-icon" id="workplan-fields-trigger" title="自定义显示字段" onclick="wp_toggleWorkplanPopover('fields')"><svg aria-hidden="true" fill="none" viewBox="0 0 48 48"><path d="M18.2838 43.1713C14.9327 42.1736 11.9498 40.3213 9.58787 37.867C10.469 36.8227 11 35.4734 11 34.0001C11 30.6864 8.31371 28.0001 5 28.0001C4.79955 28.0001 4.60139 28.01 4.40599 28.0292C4.13979 26.7277 4 25.3803 4 24.0001C4 21.9095 4.32077 19.8938 4.91579 17.9995C4.94381 17.9999 4.97188 18.0001 5 18.0001C8.31371 18.0001 11 15.3138 11 12.0001C11 11.0488 10.7786 10.1493 10.3846 9.35011C12.6975 7.1995 15.5205 5.59002 18.6521 4.72314C19.6444 6.66819 21.6667 8.00013 24 8.00013C26.3333 8.00013 28.3556 6.66819 29.3479 4.72314C32.4795 5.59002 35.3025 7.1995 37.6154 9.35011C37.2214 10.1493 37 11.0488 37 12.0001C37 15.3138 39.6863 18.0001 43 18.0001C43.0281 18.0001 43.0562 17.9999 43.0842 17.9995C43.6792 19.8938 44 21.9095 44 24.0001C44 25.3803 43.8602 26.7277 43.594 28.0292C43.3986 28.01 43.2005 28.0001 43 28.0001C39.6863 28.0001 37 30.6864 37 34.0001C37 35.4734 37.531 36.8227 38.4121 37.867C36.0502 40.3213 33.0673 42.1736 29.7162 43.1713C28.9428 40.752 26.676 39.0001 24 39.0001C21.324 39.0001 19.0572 40.752 18.2838 43.1713Z" fill="currentColor" stroke="currentColor" stroke-width="3" stroke-linejoin="round"/><path d="M24 31C27.866 31 31 27.866 31 24C31 20.134 27.866 17 24 17C20.134 17 17 20.134 17 24C17 27.866 20.134 31 24 31Z" fill="#fff" stroke="currentColor" stroke-width="3" stroke-linejoin="round"/></svg></button><div class="workplan-popover workplan-fields-pop" id="workplan-fields-pop"><h4>自定义显示字段</h4><div class="workplan-field-list" id="workplan-field-list"></div><div class="workplan-fields-foot"><button class="btn small" onclick="wp_resetWorkplanColumns()">恢复默认</button><button class="btn primary small" onclick="wp_closeWorkplanPopovers()">完成</button></div></div></div></div></div>
<div class="action-table-scroll" id="action-table-scroll"><table class="action-history-table"><colgroup><col style="width:220px"><col style="width:280px"><col style="width:105px"><col style="width:95px"><col style="width:200px"><col style="width:150px"><col style="width:100px"><col style="width:100px"><col style="width:250px"><col style="width:180px"></colgroup><thead><tr><th>工作事项</th><th>计划说明</th><th>工作类型</th><th>对象类型</th><th>关联对象</th><th>计划完成时间</th><th>负责人</th><th>完成情况</th><th>下一步</th><th>操作</th></tr></thead>
<tbody id="action-table">
<tr data-type="客户拜访" data-status="已超时" data-period="history"><td class="name-cell">光电集团第二次技术交流<span class="secondary">需补充客户反馈与竞品情况</span></td><td>客户拜访</td><td><span class="object-tag">商机</span> 光电集团安防改造</td><td>07-26 14:00</td><td>张明远</td><td><span class="state overdue">已超时</span></td><td>确认技术交流时间</td><td><button class="link" onclick="wp_showToast('已打开工作详情(演示)')">详情</button><button class="link" onclick="wp_openActionModal('客户拜访')">编辑</button><button class="link" onclick="wp_openCompleteModal('客户拜访')">登记完成</button></td></tr>
<tr data-type="方案沟通" data-status="未完成" data-period="today"><td class="name-cell">海康威视智慧园区方案沟通<span class="secondary">与甲方沟通方案重点及后续汇报安排</span></td><td>方案沟通</td><td><span class="object-tag">项目</span> 海康威视智慧园区</td><td>07-29 14:30</td><td>张明远</td><td><span class="state pending">未完成</span></td><td>形成方案反馈记录</td><td><button class="link" onclick="wp_showToast('已打开工作详情(演示)')">详情</button><button class="link" onclick="wp_openActionModal('方案沟通')">编辑</button><button class="link" onclick="wp_openCompleteModal('方案沟通')">登记完成</button></td></tr>
<tr data-type="电话沟通" data-status="未完成" data-period="today"><td class="name-cell">华润置地投标方案回访<span class="secondary">核对客户当前关注点与下次沟通人</span></td><td>电话沟通</td><td><span class="object-tag">客户</span> 华润置地</td><td>07-29 16:00</td><td>张明远</td><td><span class="state pending">未完成</span></td><td>确认是否安排现场沟通</td><td><button class="link" onclick="wp_showToast('已打开工作详情(演示)')">详情</button><button class="link" onclick="wp_openActionModal('电话沟通')">编辑</button><button class="link" onclick="wp_openCompleteModal('电话沟通')">登记完成</button></td></tr>
<tr data-type="组会组局" data-status="已超时" data-period="history"><td class="name-cell">建筑业协会关键人联络<span class="secondary">补齐秘书长及重点会员通讯录</span></td><td>组会组局</td><td><span class="object-tag">行业关系</span> 建筑业协会</td><td>07-27 10:00</td><td>张明远</td><td><span class="state overdue">已超时</span></td><td>确定下月行业会议对接</td><td><button class="link" onclick="wp_showToast('已打开工作详情(演示)')">详情</button><button class="link" onclick="wp_openActionModal('组会组局')">编辑</button><button class="link" onclick="wp_openCompleteModal('组会组局')">登记完成</button></td></tr>
<tr data-type="资料补充" data-status="未完成" data-period="today"><td class="name-cell">望江党校项目资料补充<span class="secondary">补齐本次沟通所需资料与说明</span></td><td>资料补充</td><td><span class="object-tag">项目</span> 望江党校</td><td>07-29 11:00</td><td>张明远</td><td><span class="state pending">未完成</span></td><td>补充资料并记录结果</td><td><button class="link" onclick="wp_showToast('已打开工作详情(演示)')">详情</button><button class="link" onclick="wp_openActionModal('资料补充')">编辑</button><button class="link" onclick="wp_openCompleteModal('资料补充')">登记完成</button></td></tr>
<tr data-type="跑工地" data-status="已完成" data-period="week"><td class="name-cell">菱湖院子现场踏勘<span class="secondary">已完成现场问题确认与照片留存</span></td><td>跑工地</td><td><span class="object-tag">项目</span> 菱湖院子二期</td><td>07-28 10:00</td><td>张明远</td><td><span class="state done">已完成</span></td><td>整理现场反馈并约方案沟通</td><td><button class="link" onclick="wp_showToast('已打开工作详情(演示)')">详情</button><button class="link" onclick="wp_openActionModal('跑工地')">编辑</button><button class="link" onclick="wp_showToast('已打开完成记录(演示)')">查看记录</button></td></tr>
<tr data-type="电话沟通" data-status="已完成" data-period="week"><td class="name-cell">零亿二期需求确认<span class="secondary">已确认下阶段沟通人及预计时间</span></td><td>电话沟通</td><td><span class="object-tag">客户</span> 零亿</td><td>07-28 15:30</td><td>张明远</td><td><span class="state done">已完成</span></td><td>安排项目现场拜访</td><td><button class="link" onclick="wp_showToast('已打开工作详情(演示)')">详情</button><button class="link" onclick="wp_openActionModal('电话沟通')">编辑</button><button class="link" onclick="wp_showToast('已打开完成记录(演示)')">查看记录</button></td></tr>
<tr data-type="客户拜访" data-status="已完成" data-period="history"><td class="name-cell">华润置地首次客户拜访<span class="secondary">已记录客户关注点与后续沟通方向</span></td><td>客户拜访</td><td><span class="object-tag">客户</span> 华润置地</td><td>07-18 14:00</td><td>张明远</td><td><span class="state done">已完成</span></td><td>准备投标方案回访</td><td><button class="link" onclick="wp_showToast('已打开工作详情(演示)')">详情</button><button class="link" onclick="wp_openActionModal('客户拜访')">编辑</button><button class="link" onclick="wp_showToast('已打开完成记录(演示)')">查看记录</button></td></tr>
<tr data-type="方案沟通" data-status="已完成" data-period="history"><td class="name-cell">光电集团方案重点沟通<span class="secondary">已完成需求确认与方案重点说明</span></td><td>方案沟通</td><td><span class="object-tag">商机</span> 光电集团安防改造</td><td>07-16 16:00</td><td>张明远</td><td><span class="state done">已完成</span></td><td>准备第二次技术交流</td><td><button class="link" onclick="wp_showToast('已打开工作详情(演示)')">详情</button><button class="link" onclick="wp_openActionModal('方案沟通')">编辑</button><button class="link" onclick="wp_showToast('已打开完成记录(演示)')">查看记录</button></td></tr>
<tr data-type="组会组局" data-status="已完成" data-period="history"><td class="name-cell">华南设计院技术沙龙沟通<span class="secondary">已完成参会人员与后续对接方向确认</span></td><td>组会组局</td><td><span class="object-tag">行业关系</span> 华南设计院</td><td>07-12 15:00</td><td>张明远</td><td><span class="state done">已完成</span></td><td>安排方案交流</td><td><button class="link" onclick="wp_showToast('已打开工作详情(演示)')">详情</button><button class="link" onclick="wp_openActionModal('组会组局')">编辑</button><button class="link" onclick="wp_showToast('已打开完成记录(演示)')">查看记录</button></td></tr>
<tr data-type="资料补充" data-status="已完成" data-period="history"><td class="name-cell">智慧园区资料整理<span class="secondary">已归集本次方案沟通所需资料</span></td><td>资料补充</td><td><span class="object-tag">项目</span> 海康威视智慧园区</td><td>07-08 11:00</td><td>张明远</td><td><span class="state done">已完成</span></td><td>提交客户确认</td><td><button class="link" onclick="wp_showToast('已打开工作详情(演示)')">详情</button><button class="link" onclick="wp_openActionModal('资料补充')">编辑</button><button class="link" onclick="wp_showToast('已打开完成记录(演示)')">查看记录</button></td></tr>
</tbody>
</table></div>
<div class="table-pagination"><span id="action-total">共 12 条</span><div class="pagination-actions"><select id="action-page-size" onchange="wp_showActionPage(1)"><option value="10">10条/页</option></select><button class="page-btn" onclick="wp_showActionPage(1)">‹</button><button class="page-btn active" data-page="1" onclick="wp_showActionPage(1)">1</button><button class="page-btn" data-page="2" onclick="wp_showActionPage(2)">2</button><button class="page-btn" onclick="wp_showActionPage(2)">›</button></div></div>
</div>
</section>
<section class="view" id="view-trips">
<div class="workplan-list-titlebar"><span>出差与拜访</span></div>
<div class="card">
<div class="page-tools"><input class="search" id="trip-search" placeholder="搜索出差主题或地区" onkeydown="if(event.key==='Enter') wp_filterTripTable()"><select class="filter" id="trip-schedule-filter" onchange="wp_filterTripTable()"><option value="">全部日程</option><option value="未开始">未开始</option><option value="进行中">进行中</option><option value="已结束">已结束</option></select><select class="filter" id="trip-period-filter" onchange="wp_filterTripTable()"><option value="all">全部时间</option><option value="month">本月</option><option value="future">后续安排</option></select><button class="btn primary small" onclick="wp_filterTripTable()">查询</button><span class="spacer"></span><button class="btn primary" onclick="wp_openTripModal()">+ 新建出差计划</button><div class="workplan-list-tools"><button class="workplan-tool-icon" title="刷新数据" onclick="wp_renderTripTable();wp_showToast('出差计划列表已刷新')"><svg aria-hidden="true" fill="none" stroke="currentColor" stroke-width="4" viewBox="0 0 48 48"><path d="M38.837 18C36.463 12.136 30.715 8 24 8 15.163 8 8 15.163 8 24s7.163 16 16 16c7.455 0 13.72-5.1 15.496-12M40 8v10H30"/></svg></button></div></div>
<div class="trip-table-wrap"><table class="trip-table"><colgroup><col style="width:235px"><col style="width:145px"><col style="width:145px"><col style="width:110px"><col style="width:90px"><col style="width:115px"><col style="width:90px"><col style="width:160px"><col style="width:120px"><col style="width:160px"><col style="width:190px"></colgroup><thead><tr><th>出差主题</th><th>出差日期</th><th>出差地区</th><th>负责人</th><th>拜访安排</th><th>已完成 / 总项</th><th>已超时</th><th>下一场拜访</th><th>出差日程</th><th>出差总结</th><th>操作</th></tr></thead><tbody id="trip-table-body"></tbody></table></div>
<div class="table-pagination"><span id="trip-total">共 4 条</span><div class="pagination-actions"><select><option>10条/页</option></select><button class="page-btn">‹</button><button class="page-btn active">1</button><button class="page-btn">›</button></div></div>
</div>
</section>
</main>
<div class="modal-mask" id="action-detail-modal" onclick="wp_maskClose(event,'action-detail-modal')"><div class="modal" onclick="event.stopPropagation()"><div class="modal-head"><h2 id="detail-title">工作计划详情</h2><button class="x" onclick="wp_closeModal('action-detail-modal')">×</button></div><div class="detail-grid"><div class="detail-row"><div class="detail-label">工作类型</div><div class="detail-value" id="detail-type"></div></div><div class="detail-row"><div class="detail-label">计划完成时间</div><div class="detail-value" id="detail-time"></div></div><div class="detail-row"><div class="detail-label">关联对象类型</div><div class="detail-value" id="detail-object-type"></div></div><div class="detail-row"><div class="detail-label">关联对象</div><div class="detail-value" id="detail-object"></div></div><div class="detail-row"><div class="detail-label">经营归属(双50)</div><div class="detail-value" id="detail-attribution"></div></div><div class="detail-row"><div class="detail-label">完成情况</div><div class="detail-value" id="detail-status"></div></div><div class="detail-row"><div class="detail-label">计划说明</div><div class="detail-value" id="detail-plan"></div></div><div class="detail-row"><div class="detail-label">完成结果 / 客户反馈</div><div class="detail-value" id="detail-result"></div></div></div><div class="modal-foot"><button class="btn" onclick="wp_closeModal('action-detail-modal')">关闭</button><button class="btn primary" onclick="wp_editSelectedAction()">编辑</button></div></div></div>
<div class="modal-mask" id="action-modal" onclick="wp_maskClose(event,'action-modal')"><div class="modal" onclick="event.stopPropagation()"><div class="modal-head"><h2 id="action-modal-title">填写工作计划</h2><button class="x" onclick="wp_closeModal('action-modal')">×</button></div><div class="modal-body"><div class="form-grid"><div class="field"><label>行动类型 <span class="required">*</span></label><select id="action-type" onchange="wp_changeActionHint()"><option>电话沟通</option><option>客户拜访</option><option>出差安排</option><option>跑工地</option><option>方案沟通</option><option>组会组局</option><option>FBB处理</option><option>其他</option></select></div><div class="field"><label>计划时间 <span class="required">*</span></label><input id="action-datetime" type="datetime-local" value="2026-07-29T14:00"></div><div class="field full"><label>关联对象 <span class="required">*</span></label><select id="action-object"><option>海康威视智慧园区(项目)</option><option>华润置地(潜在客户)</option><option>光电集团安防改造(商机)</option><option>望江党校(方案卡)</option><option>建筑业协会(行业关系)</option></select></div><div class="field"><label>行动事项 <span class="required">*</span></label><input id="action-name" placeholder="例如:确认方案沟通时间"></div><div class="field"><label>执行状态</label><select id="action-status"><option>待执行</option><option>执行中</option><option>已完成</option></select></div><div class="field full"><label>经营归属(双50) <span class="required">*</span></label><select id="action-attribution"><option value="">请选择</option><option value="stock">存量经营(老客户深耕 / 存量项目推进)</option><option value="growth">增量开发(新客户 / 新项目 / 行业关系)</option><option value="exclude">不纳入双50</option></select></div><div class="field full"><label>计划说明 / 本次目标</label><textarea id="action-plan-input" placeholder="本次希望确认什么、推进什么,或需要补齐什么信息?"></textarea></div></div><div class="form-tip" id="action-hint">“经营归属”由销售确认,不能仅根据电话、拜访等行动类型自动判断;仅归属为存量或增量的工作项进入双50过程统计。</div></div><div class="modal-foot"><button class="btn" onclick="wp_closeModal('action-modal')">取消</button><button class="btn primary" onclick="wp_saveAction()">保存工作计划</button></div></div></div>
<div class="modal-mask" id="completion-modal" onclick="wp_maskClose(event,'completion-modal')"><div class="modal" onclick="event.stopPropagation()"><div class="modal-head"><h2>登记完成</h2><button class="x" onclick="wp_closeModal('completion-modal')">×</button></div><div class="modal-body"><div class="form-grid"><div class="field full"><label>工作事项</label><input id="completion-name" readonly></div><div class="field full"><label>完成时间 <span class="required">*</span></label><input id="completion-time" type="datetime-local"></div><div class="field full"><label>完成结果 / 客户反馈 <span class="required">*</span></label><textarea id="completion-result" placeholder="填写完成结果、客户反馈或形成的结论"></textarea></div><div class="field full"><label>风险或协同事项</label><textarea id="completion-risk" placeholder="可选:需协同处理的风险或事项"></textarea></div></div></div><div class="modal-foot"><button class="btn" onclick="wp_closeModal('completion-modal')">取消</button><button class="btn primary" onclick="wp_saveQuickCompletion()">保存登记</button></div></div></div>
<div class="modal-mask" id="trip-modal" onclick="wp_maskClose(event,'trip-modal')"><div class="modal" onclick="event.stopPropagation()"><div class="modal-head"><h2>新建出差计划</h2><button class="x" onclick="wp_closeModal('trip-modal')">×</button></div><div class="modal-body"><div class="form-grid"><div class="field full"><label>出差主题 <span class="required">*</span></label><input placeholder="例如:深圳南山区重点客户拜访"></div><div class="field"><label>开始日期 <span class="required">*</span></label><input type="date"></div><div class="field"><label>结束日期 <span class="required">*</span></label><input type="date"></div><div class="field"><label>出差区域</label><input placeholder="例如:深圳南山区"></div><div class="field"><label>协同人</label><input placeholder="可选"></div><div class="field full"><label>出差目标</label><textarea placeholder="本次出差希望推进的客户、项目或行业关系"></textarea></div></div><div class="form-tip">保存后可在该出差计划下持续增加多个拜访安排;拜访完成后分别记录完成结果与风险事项。</div></div><div class="modal-foot"><button class="btn" onclick="wp_closeModal('trip-modal')">取消</button><button class="btn primary" onclick="wp_saveTrip()">保存并添加拜访安排</button></div></div></div>
<div class="modal-mask" id="focus-modal" onclick="wp_maskClose(event,'focus-modal')"><div class="modal" onclick="event.stopPropagation()"><div class="modal-head"><h2>加入重点经营清单</h2><button class="x" onclick="wp_closeModal('focus-modal')">×</button></div><div class="modal-body"><div class="form-grid"><div class="field"><label>经营类型 <span class="required">*</span></label><select><option>客户深耕</option><option>新客户开发</option><option>核心项目</option><option>行业开发</option></select></div><div class="field"><label>经营周期 <span class="required">*</span></label><select><option>2026年7月</option><option>2026年8月</option><option>2026年第三季度</option></select></div><div class="field full"><label>选择 CRM 对象 <span class="required">*</span></label><select><option>零亿(客户)</option><option>华润置地(潜在客户)</option><option>海康威视智慧园区(项目)</option><option>建筑业协会(行业关系)</option></select></div><div class="field full"><label>本期经营目标 <span class="required">*</span></label><textarea placeholder="例如:完成关键关系维护,确认二期工程的推进条件"></textarea></div><div class="field full"><label>初步行动节奏</label><input placeholder="例如:本周完成 2 次拜访 + 1 次方案沟通"></div></div><div class="form-tip">这里只维护本期目标和行动节奏;客户、商机、项目的基础信息仍在原模块维护。</div></div><div class="modal-foot"><button class="btn" onclick="wp_closeModal('focus-modal')">取消</button><button class="btn primary" onclick="wp_saveFocus()">加入清单</button></div></div></div>
<div class="toast" id="toast"></div></div></div></section>
<section class="page workplan-native-page" id="workplanList"><div id="workplanListRoot"></div></section>
<section class="page workplan-native-page" id="workplanTrips"><div id="workplanTripsRoot"></div></section>
<section class="page crm-host" id="lead"><div id="crmLeadRoot"></div></section>
<section class="page crm-host" id="leadFollow"><div id="crmLeadFollowRoot"></div></section>
<section class="page crm-host" id="leadClaim"><div id="crmLeadClaimRoot"></div></section>
<section class="page crm-host" id="leadManage"><div id="crmLeadManageRoot"></div></section>
<section class="page crm-host" id="leadRecent"><div id="crmLeadRecentRoot"></div></section>
<section class="page crm-host" id="leadImport"><div id="crmLeadImportRoot"></div></section>
<section class="page crm-host" id="leadSetting"><div id="crmLeadSettingRoot"></div></section>
<section class="page crm-host" id="leadCreate"><div id="crmLeadCreateRoot"></div></section>
<section class="page crm-host" id="leadSettingEdit"><div id="crmLeadSettingEditRoot"></div></section>
<section class="page crm-host" id="leadSettingImport"><div id="crmLeadSettingImportRoot"></div></section>
<section class="page crm-host" id="customerPool"><div id="crmCustomerOverviewRoot"></div></section>
<section class="page crm-host" id="myCustomer"><div id="crmMyCustomerRoot"></div></section>
<section class="page crm-host" id="customerSea"><div id="crmCustomerSeaRoot"></div></section>
<section class="page crm-host" id="contact"><div id="crmContactRoot"></div></section>
<section class="page crm-host" id="customerDedupe"><div id="crmCustomerDedupeRoot"></div></section>
<section class="page crm-host" id="customerHandover"><div id="crmCustomerHandoverRoot"></div></section>
<section class="page crm-host" id="customerSetting"><div id="crmCustomerSettingRoot"></div></section>
<section class="page" id="myOpportunity"><div aria-live="polite" id="oppModuleRoot"></div></section>
<section class="page" id="oppSetting"><div id="oppSettingRoot"></div></section>
<section class="page fp-host" id="project"><div id="fpProjectRoot"></div></section>
<section class="page fp-host" id="myProject"><div id="fpMyProjectRoot"></div></section>
<section class="page fp-host" id="projectSetting"><div id="fpProjectSettingRoot"></div></section>
<section class="page fp-host" id="projectStats"><div id="fpProjectStatsRoot"></div></section>
<section class="page" id="board"><div class="placeholder-page">数据看板页面后续按块迁移</div></section>
<section class="page" id="notice"><div id="noticePageRoot"></div></section>
<section class="page fp-host" id="adminUsers"><div id="fpAdminRoot"></div></section>
</main>
</div>
<div class="modal-mask" id="modal" onclick="closeModal(event)"><div class="modal" onclick="event.stopPropagation()"><h3 id="modalTitle">提示</h3><div class="m-body">S2 阶段保留入口,后续按模块迁移具体页面。</div><div class="m-foot"><button class="btn" onclick="closeModal()">取消</button> <button class="btn primary" onclick="closeModal()">确认</button></div></div></div>
<div class="notice-panel" id="noticePanel" onclick="closeNoticePanel(event)"><section class="notice-popover" onclick="event.stopPropagation()" aria-label="消息通知"><div class="notice-popover-head"><h3>消息通知 <small id="noticePopoverCount">3 条未读</small></h3><button class="notice-more-btn" onclick="openNoticePage()">更多 &gt;</button></div><div class="notice-popover-list" id="noticePopoverList"></div></section></div>
<div class="ai-panel-mask" id="aiPanel" onclick="closeAiPanel(event)">
<div class="ai-panel-drawer" onclick="event.stopPropagation()">
<div class="ai-panel-head">
<div class="ai-panel-head-title">
<span class="ui-icon" data-icon="sparkles" style="width:22px;height:22px;color:#1677ff"></span>
<h2>AI 智能助手</h2>
<span class="ai-panel-badge">Codex 驱动</span>
</div>
<button class="btn" onclick="closeAiPanel()">关闭</button>
</div>
<div class="ai-panel-shortcuts">
<button class="ai-shortcut-btn" onclick="sendAiShortcut('帮我分析一下今日需优先跟进的客户')">📋 今日跟进建议</button>
<button class="ai-shortcut-btn" onclick="sendAiShortcut('分析我目前的商机漏斗转化率,指出风险点')">📊 漏斗转化分析</button>
<button class="ai-shortcut-btn" onclick="sendAiShortcut('帮我起草一份针对教育行业客户的报价邮件模板')">✉️ 报价邮件</button>
<button class="ai-shortcut-btn" onclick="sendAiShortcut('招投标线索如何快速转化为正式商机?给我一个清晰的操作步骤')">🏆 招投标转化</button>
<button class="ai-shortcut-btn" onclick="sendAiShortcut('帮我生成一份本周工作汇报摘要,重点突出商机推进和客户维护')">📝 工作汇报</button>
<button class="ai-shortcut-btn" onclick="sendAiShortcut('针对7天未联系客户,给出3个破冰话术')">💬 客户话术</button>
</div>
<div class="ai-chat-body" id="aiChatBody">
<div class="ai-chat-msg assistant">
<div class="ai-chat-bubble">你好,Sam!我是你的 AI 销售助手。<br/><br/>我可以帮你分析客户跟进优先级、生成销售话术、起草报价方案、解读商机漏斗风险……<br/><br/>今天有什么需要我帮忙的?</div>
<div class="ai-chat-meta">AI助手 · 刚刚</div>
</div>
</div>
<div class="ai-panel-input-area">
<div class="ai-input-wrap">
<textarea id="aiChatInput" oninput="autoResizeAiInput(this)" onkeydown="handleAiInputKey(event)" placeholder="输入问题,按 Enter 发送,Shift+Enter 换行" rows="1"></textarea>
<button class="ai-send-btn" id="aiSendBtn" onclick="sendAiMessage()" title="发送"><svg viewbox="0 0 24 24"><path d="m22 2-7 20-4-9-9-4Z"></path><path d="M22 2 11 13"></path></svg></button>
</div>
</div>
</div>
</div>
<script>
function openHomeWorkplan(){
const modal=document.getElementById('action-modal');
if(!modal)return;
let host=document.getElementById('homeWorkplanModalHost');
if(!host){
host=document.createElement('div');
host.id='homeWorkplanModalHost';
host.className='workplan-native home-workplan-modal-host';
document.body.appendChild(host);
}
if(modal.parentElement!==host)host.appendChild(modal);
modal.dataset.homeWorkplanHosted='1';
wp_openNewForSelected();
}
function restoreHomeWorkplanModal(){
const modal=document.getElementById('action-modal');
if(!modal||modal.dataset.homeWorkplanHosted!=='1')return;
const mount=document.getElementById('workplanMount');
if(mount)mount.appendChild(modal);
delete modal.dataset.homeWorkplanHosted;
}
function homeShowNotice(message){
let toast=document.getElementById('home-notice');
if(!toast){toast=document.createElement('div');toast.id='home-notice';toast.className='home-notice';document.body.appendChild(toast)}
toast.textContent=message;
toast.classList.add('show');
clearTimeout(window.homeNoticeTimer);
window.homeNoticeTimer=setTimeout(()=>toast.classList.remove('show'),2200);
}
function setActivePage(page,label){
const text=String(label||'');
if(page==='opportunity'){page='myOpportunity';label='商机 / 商机管理'}
if(page==='oppPool'){page='myOpportunity';label='商机 / 商机公海'}
document.querySelectorAll('.page').forEach(p=>p.classList.remove('active'));
const target=document.getElementById(page)||document.getElementById('home');
target.classList.add('active');
document.querySelectorAll('.neo-tab').forEach(t=>t.classList.toggle('active',t.dataset.page===page||(page==='oppSetting'&&t.dataset.page==='myOpportunity')));
document.querySelectorAll('.side-item[data-page]').forEach(t=>t.classList.toggle('active',t.dataset.page===page));
document.querySelectorAll('.neo-menu-panel button[data-page]').forEach(t=>t.classList.toggle('active',t.dataset.page===page));
const locationNode=document.getElementById('locationCurrent');if(locationNode)locationNode.textContent=label||'首页';
document.querySelector('.neo-shell')?.classList.toggle('home-active',page==='home');
if(page==='notice'){renderNoticePage()}
else if(page==='myOpportunity'){syncOpportunitySideNav(label);if(label==='商机')oppModule.openDefault();else if(text.includes('商机管理'))oppModule.openManagement();else if(text.includes('商机公海'))oppModule.openPublicPool();else if(text.includes('销售机会'))oppModule.openSalesHome();else oppModule.onEnter()}
else if(page==='oppSetting'){oppSettingsV2.render();oppModule.renderSystemBreadcrumb(label)}
else if(typeof leadV2!=='undefined'&&leadV2.handles(page)){leadV2.enter(page,label);leadV2.renderBreadcrumb()}
else if(typeof customerV2!=='undefined'&&customerV2.handles(page)){customerV2.enter(page,label);customerV2.renderBreadcrumb()}
else if(typeof faithfulProject!=='undefined'&&faithfulProject.handles(page)){faithfulProject.enter(page,label)}
else if(typeof faithfulAdmin!=='undefined'&&faithfulAdmin.handles(page)){faithfulAdmin.enter(page,label)}
else if(typeof workplanModule!=='undefined'&&['workplan','workplanList','workplanTrips'].includes(page)){workplanModule.enter(page);oppModule.renderSystemBreadcrumb(label)}
else oppModule.renderSystemBreadcrumb(label);
}
document.getElementById('neoNav').addEventListener('click',function(e){
const btn=e.target.closest('[data-page]');
if(!btn)return;
setActivePage(btn.dataset.page,btn.dataset.label||btn.textContent.trim());
});
document.getElementById('sideNav').addEventListener('click',function(e){
const group=e.target.closest('.side-group');
if(group){
group.classList.toggle('collapsed');
let next=group.nextElementSibling;
while(next&&next.classList.contains('side-sub')){
next.classList.toggle('is-hidden');
next=next.nextElementSibling;
}
return;
}
const btn=e.target.closest('[data-page]');
if(!btn)return;
setActivePage(btn.dataset.page,btn.dataset.label||btn.textContent.trim());
});
function toggleNavMode(){
const shell=document.querySelector('.neo-shell');
const toSide=!shell.classList.contains('side-mode');
shell.classList.toggle('side-mode',toSide);
shell.classList.toggle('top-mode',!toSide);
if(!toSide) shell.classList.remove('side-nav-collapsed');
const btn=document.querySelector('[data-nav-toggle]');
if(btn){
const title=toSide?'切换至顶部导航模式':'切换至左侧导航模式';
btn.title=title;
btn.setAttribute('aria-label',title);
}
}
function toggleSidebarCollapse(){
const shell=document.querySelector('.neo-shell');
if(!shell.classList.contains('side-mode')) return;
shell.classList.toggle('side-nav-collapsed');
}
function openModal(title,options={}){const mask=document.getElementById('modal'),panel=mask?.querySelector('.modal');if(!mask||!panel)return;panel.classList.remove('opp-modal-wide','opp-modal-small','opp-modal-medium','crm-member-modal','crm-home-customer-dialog');(options.classes||[]).forEach(name=>panel.classList.add(name));document.getElementById('modalTitle').textContent=title||'提示';mask.querySelector('.m-body').innerHTML=options.body||'请填写相关信息。';mask.querySelector('.m-foot').innerHTML=options.footer||'<button class="btn" onclick="closeModal()">取消</button><button class="btn primary" onclick="closeModal()">确认</button>';mask.classList.add('show')}
function closeModal(e){if(!e||e.target.id==='modal'||e.target.tagName==='BUTTON'){const mask=document.getElementById('modal'),panel=mask?.querySelector('.modal');if(panel){panel.classList.remove('opp-modal-wide','opp-modal-small','opp-modal-medium','crm-member-modal','crm-home-customer-dialog');panel.style.width='';}if(mask){mask.classList.remove('show');mask.querySelector('.m-body').innerHTML='';mask.querySelector('.m-foot').innerHTML='';}}}
function openAiPanel(){document.getElementById('aiPanel').classList.add('show');setTimeout(()=>document.getElementById('aiChatInput')?.focus(),250)}
function closeAiPanel(e){if(!e||e.target.id==='aiPanel'||e.target.closest('.btn'))document.getElementById('aiPanel').classList.remove('show')}
const aiHistory=[];
function getTime(){return new Date().toLocaleTimeString('zh-CN',{hour:'2-digit',minute:'2-digit'})}
function appendAiMsg(role,text,loading){
const body=document.getElementById('aiChatBody');
if(!body)return null;
const div=document.createElement('div');
div.className='ai-chat-msg '+(role==='user'?'user':'assistant')+(loading?' loading':'');
div.innerHTML=loading
? '<div class="ai-chat-bubble"><div class="ai-typing-dot"></div><div class="ai-typing-dot"></div><div class="ai-typing-dot"></div></div>'
: '<div class="ai-chat-bubble">'+text.replace(/\n/g,'<br>')+'</div><div class="ai-chat-meta">'+(role==='user'?'你':'AI助手')+' · '+getTime()+'</div>';
body.appendChild(div);
body.scrollTop=body.scrollHeight;
return div;
}
async function sendAiMessage(){
const input=document.getElementById('aiChatInput');
const btn=document.getElementById('aiSendBtn');
if(!input)return;
const text=input.value.trim();
if(!text)return;
input.value='';input.style.height='38px';
appendAiMsg('user',text);
aiHistory.push({role:'user',content:text});
btn.disabled=true;
const loadingEl=appendAiMsg('assistant','',true);
try{
const resp=await fetch('https://api.anthropic.com/v1/messages',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({
model:'claude-sonnet-4-6',max_tokens:1000,
system:'你是一个专业的B2B销售CRM助手,帮助销售团队管理客户关系、跟进商机和制定销售策略。用户使用的CRM系统叫"itc 信息化平台",主要业务涵盖会议系统、广播系统、显示系统等AV集成行业。请用简洁、实用、专业的中文回答,适当使用结构化输出(如分点)。',
messages:aiHistory
})});
const data=await resp.json();
const reply=data.content?.map(c=>c.text||'').join('')||'抱歉,暂时无法获取回复,请稍后再试。';
aiHistory.push({role:'assistant',content:reply});
if(loadingEl){loadingEl.className='ai-chat-msg assistant';loadingEl.innerHTML='<div class="ai-chat-bubble">'+reply.replace(/\n/g,'<br>')+'</div><div class="ai-chat-meta">AI助手 · '+getTime()+'</div>';}
document.getElementById('aiChatBody').scrollTop=99999;
}catch(err){
if(loadingEl){loadingEl.className='ai-chat-msg assistant';loadingEl.innerHTML='<div class="ai-chat-bubble" style="color:#dc2626">连接失败,请检查网络后重试。</div>';}
}
btn.disabled=false;
input.focus();
}
function sendAiShortcut(text){
const input=document.getElementById('aiChatInput');
if(input){input.value=text;sendAiMessage();}
}
function handleAiInputKey(e){if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();sendAiMessage();}}
function autoResizeAiInput(el){el.style.height='38px';el.style.height=Math.min(el.scrollHeight,120)+'px'}
const noticeRecords=[
{id:'notice-lead',type:'线索',title:'您有 14 条商机线索待验证',time:'今天 10:30',unread:true,page:'lead',label:'线索 / 线索公海'},
{id:'notice-project',type:'项目',title:'XX学校项目,方案工程师已上传方案资料',time:'今天 09:45',unread:true,page:'project',label:'项目管理 / 项目管理'},
{id:'notice-customer',type:'客户',title:'您有 4 个客户超过 7 天未更新跟进记录',time:'今天 08:30',unread:true,page:'customerPool',label:'客户 / 客户总览'},
{id:'notice-payment',type:'回款',title:'华南智慧校园项目新增一笔回款待确认',time:'昨天 17:36',unread:false,page:'project',label:'项目管理 / 项目管理'},
{id:'notice-work',type:'工作计划',title:'明日 10:00 客户拜访计划即将开始',time:'昨天 16:10',unread:false,page:'workplan',label:'工作计划 / 工作日历'},
{id:'notice-opportunity',type:'商机',title:'XX地铁监控项目已进入商务阶段',time:'昨天 14:22',unread:false,page:'myOpportunity',label:'商机 / 商机管理'},
{id:'notice-project-overdue',type:'项目',title:'XX政府多功能厅项目存在逾期节点',time:'2026-08-01 18:20',unread:false,page:'project',label:'项目管理 / 项目管理'},
{id:'notice-customer-follow',type:'客户',title:'广州市XX职业技术学院新增一条跟进记录',time:'2026-08-01 15:08',unread:false,page:'customerPool',label:'客户 / 客户总览'}
];
let noticeFilter='all';
function noticeEsc(value){return String(value??'').replace(/[&<>"']/g,function(char){return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[char]})}
function unreadNoticeCount(){return noticeRecords.filter(function(item){return item.unread}).length}
function renderNoticeSummary(){
const unread=noticeRecords.filter(function(item){return item.unread});
const badge=document.getElementById('noticeUnreadBadge'),count=document.getElementById('noticePopoverCount'),list=document.getElementById('noticePopoverList');
if(badge){badge.textContent=unread.length;badge.hidden=!unread.length}
if(count)count.textContent=unread.length+' 条未读';
if(list)list.innerHTML=unread.length?unread.slice(0,3).map(function(item){return '<button class="notice-popover-item unread" type="button" onclick="openNoticeItem(\''+noticeEsc(item.id)+'\')"><b>【'+noticeEsc(item.type)+'】'+noticeEsc(item.title)+'</b><small>'+noticeEsc(item.time)+'</small></button>'}).join(''):'<div class="notice-history-empty">暂无未读消息</div>';
}
function openNoticePanel(){renderNoticeSummary();document.getElementById('noticePanel').classList.add('show');document.getElementById('noticeTrigger')?.classList.add('is-open');document.getElementById('noticeTrigger')?.setAttribute('aria-expanded','true')}
function closeNoticePanel(e){if(!e||e.target.id==='noticePanel'){document.getElementById('noticePanel').classList.remove('show');document.getElementById('noticeTrigger')?.classList.remove('is-open');document.getElementById('noticeTrigger')?.setAttribute('aria-expanded','false')}}
function renderNoticePage(){
const root=document.getElementById('noticePageRoot');if(!root)return;
const query=(root.querySelector('[data-notice-query]')?.value||'').trim().toLowerCase();
const rows=noticeRecords.filter(function(item){return (noticeFilter==='all'||(noticeFilter==='unread'&&item.unread))&&(!query||[item.type,item.title,item.time].join(' ').toLowerCase().includes(query))});
const list=rows.length?rows.map(function(item){return '<article class="notice-history-item '+(item.unread?'unread':'')+'"><span class="notice-history-type">'+noticeEsc(item.type)+'</span><div class="notice-history-copy"><b>'+noticeEsc(item.title)+'</b><small>'+(item.unread?'未读 · ':'已读 · ')+noticeEsc(item.time)+'</small></div><time class="notice-history-time">'+noticeEsc(item.time)+'</time><button type="button" onclick="openNoticeItem(\''+noticeEsc(item.id)+'\')">查看</button></article>'}).join(''):'<div class="notice-history-empty">暂无符合条件的消息</div>';
root.innerHTML='<div class="notice-page"><section class="notice-page-shell"><header class="notice-page-head"><div><h1>消息通知</h1><p>查看当前账号收到的全部业务提醒与系统通知。</p></div></header><div class="notice-page-tools"><div class="notice-filter-group"><button class="'+(noticeFilter==='all'?'active':'')+'" onclick="setNoticeFilter(\'all\')">全部 '+noticeRecords.length+'</button><button class="'+(noticeFilter==='unread'?'active':'')+'" onclick="setNoticeFilter(\'unread\')">未读 '+unreadNoticeCount()+'</button></div><input data-notice-query type="search" value="'+noticeEsc(query)+'" placeholder="搜索通知内容" oninput="renderNoticePage()"></div><div class="notice-history-list">'+list+'</div></section></div>';
}
function setNoticeFilter(filter){noticeFilter=filter;renderNoticePage()}
function openNoticePage(){closeNoticePanel();setActivePage('notice','消息通知')}
function openNoticeItem(id){const item=noticeRecords.find(function(record){return record.id===id});if(!item)return;item.unread=false;renderNoticeSummary();closeNoticePanel();if(document.getElementById('notice')?.classList.contains('active'))renderNoticePage();else setActivePage(item.page,item.label)}
// S2-gpt · 商机模块:冻结V8.2视觉结果,仅清理重复逻辑和全局污染。
// 商机、项目统一列表工具图标:Arco Design IconLineHeight / IconRefresh,IconPark SettingTwo(MIT)。
function sharedListToolIcon(type){
const icons={
density:'<svg class="shared-list-icon" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="4" viewBox="0 0 48 48"><path d="M4 8h14.5M33 8H18.5m0 0v34"/><path fill="currentColor" stroke="none" d="M39 9.5 37 13h4l-2-3.5ZM39 38.5 37 35h4l-2 3.5Z"/><path d="M39 13h2l-2-3.5-2 3.5h2Zm0 0v22m0 0h2l-2 3.5-2-3.5h2Z"/></svg>',
refresh:'<svg class="shared-list-icon" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="4" viewBox="0 0 48 48"><path d="M38.837 18C36.463 12.136 30.715 8 24 8 15.163 8 8 15.163 8 24s7.163 16 16 16c7.455 0 13.72-5.1 15.496-12M40 8v10H30"/></svg>',
fields:'<svg class="shared-list-icon shared-list-icon--settings" aria-hidden="true" fill="none" viewBox="0 0 48 48"><path d="M18.2838 43.1713C14.9327 42.1736 11.9498 40.3213 9.58787 37.867C10.469 36.8227 11 35.4734 11 34.0001C11 30.6864 8.31371 28.0001 5 28.0001C4.79955 28.0001 4.60139 28.01 4.40599 28.0292C4.13979 26.7277 4 25.3803 4 24.0001C4 21.9095 4.32077 19.8938 4.91579 17.9995C4.94381 17.9999 4.97188 18.0001 5 18.0001C8.31371 18.0001 11 15.3138 11 12.0001C11 11.0488 10.7786 10.1493 10.3846 9.35011C12.6975 7.1995 15.5205 5.59002 18.6521 4.72314C19.6444 6.66819 21.6667 8.00013 24 8.00013C26.3333 8.00013 28.3556 6.66819 29.3479 4.72314C32.4795 5.59002 35.3025 7.1995 37.6154 9.35011C37.2214 10.1493 37 11.0488 37 12.0001C37 15.3138 39.6863 18.0001 43 18.0001C43.0281 18.0001 43.0562 17.9999 43.0842 17.9995C43.6792 19.8938 44 21.9095 44 24.0001C44 25.3803 43.8602 26.7277 43.594 28.0292C43.3986 28.01 43.2005 28.0001 43 28.0001C39.6863 28.0001 37 30.6864 37 34.0001C37 35.4734 37.531 36.8227 38.4121 37.867C36.0502 40.3213 33.0673 42.1736 29.7162 43.1713C28.9428 40.752 26.676 39.0001 24 39.0001C21.324 39.0001 19.0572 40.752 18.2838 43.1713Z" fill="currentColor" stroke="currentColor" stroke-width="3" stroke-linejoin="round"/><path d="M24 31C27.866 31 31 27.866 31 24C31 20.134 27.866 17 24 17C20.134 17 17 20.134 17 24C17 27.866 20.134 31 24 31Z" fill="#fff" stroke="currentColor" stroke-width="3" stroke-linejoin="round"/></svg>'
};
return icons[type]||'';
}
function sharedViewModeIcon(mode){
if(mode==='split')return '<svg viewBox="0 0 24 24"><rect x="4" y="5" width="6" height="14" rx="1.5"/><rect x="14" y="5" width="6" height="14" rx="1.5"/></svg>';
if(mode==='board'||mode==='card')return '<svg viewBox="0 0 24 24"><rect x="4" y="5" width="4" height="14" rx="1"/><rect x="10" y="5" width="4" height="14" rx="1"/><rect x="16" y="5" width="4" height="14" rx="1"/></svg>';
return '<svg viewBox="0 0 24 24"><path d="M9 7h10M9 12h10M9 17h10"/><circle cx="5" cy="7" r="1.2"/><circle cx="5" cy="12" r="1.2"/><circle cx="5" cy="17" r="1.2"/></svg>';
}
const oppModule=(function(){
const stages=['客户圈定','关系摸排','资料采集','方案卡','已转项目'];
const rows=[
{id:1,name:'金山谷智慧园区项目',partyA:'招商局物业管理广州分公司',customer:'广州亚东智能科技有限公司',industry:'园区',region:'广州番禺',amount:'¥418.90万',amountNum:418.9,node:'方案卡',stay:'6天',recent:'今天 10:30',next:'已转项目',scheme:'当前方案卡',closeDate:'2026-07-30',owner:'张三',summary:'甲方、总包及主要意向客户已明确,方案卡资料正在完善。'},
{id:2,name:'地铁站点视频会议升级',partyA:'广州地铁集团',customer:'广东轨交通信工程有限公司',industry:'交通',region:'广州市',amount:'¥780万',amountNum:780,node:'关系摸排',stay:'12天',recent:'昨天 16:20',next:'资料采集',scheme:'未创建',closeDate:'2026-08-15',owner:'李四',summary:'正在摸排总包、设计院和集成商关系。'},
{id:3,name:'番禺文旅中心多功能厅',partyA:'番禺区文旅中心',customer:'广州番禺文旅工程有限公司',industry:'文旅',region:'广州番禺',amount:'¥236万',amountNum:236,node:'资料采集',stay:'4天',recent:'2天前',next:'方案卡',scheme:'填写中',closeDate:'2026-07-20',owner:'陈晨',summary:'现场资料待补,需确认预算和品牌口径。'},
{id:4,name:'天河区智慧教室改造',partyA:'天河区教育局',customer:'广东智慧教育科技有限公司',industry:'教育',region:'广州天河',amount:'¥365万',amountNum:365,node:'客户圈定',stay:'8天',recent:'3天前',next:'关系摸排',scheme:'未创建',closeDate:'2026-09-10',owner:'张三',summary:'客户需求初步明确,正在确认甲方和参与单位。'},
{id:5,name:'白云国际会议中心升级',partyA:'白云国际会议中心',customer:'广州白云会展工程有限公司',industry:'会展',region:'广州白云',amount:'¥960万',amountNum:960,node:'资料采集',stay:'3天',recent:'今天 09:15',next:'方案卡',scheme:'已完善',closeDate:'2026-07-18',owner:'吴迪',summary:'现场踏勘完成,待补充设计院及总包资料。'},
{id:6,name:'海珠区医院会议系统采购',partyA:'海珠区人民医院',customer:'广东康联医疗科技有限公司',industry:'医疗',region:'广州海珠',amount:'¥520万',amountNum:520,node:'资料采集',stay:'9天',recent:'5天前',next:'方案卡',scheme:'填写中',closeDate:'2026-08-30',owner:'张三',summary:'需求已形成初稿,需进一步确认预算和招标时间。'},
{id:7,name:'南沙港调度中心改造',partyA:'南沙港集团',customer:'广州南沙港信息工程有限公司',industry:'交通',region:'广州南沙',amount:'¥1150万',amountNum:1150,node:'关系摸排',stay:'11天',recent:'昨天 11:40',next:'资料采集',scheme:'未创建',closeDate:'2026-11-20',owner:'王强',summary:'正在确认设计院、总包与信息化部门的关系链。'},
{id:8,name:'花都文旅剧场扩声项目',partyA:'花都区文旅集团',customer:'广东花都演艺设备有限公司',industry:'文旅',region:'广州花都',amount:'¥288万',amountNum:288,node:'客户圈定',stay:'5天',recent:'4天前',next:'关系摸排',scheme:'未创建',closeDate:'2026-10-25',owner:'张三',summary:'客户正在内部评估,待补充场地和预算信息。'},
{id:9,name:'黄埔产业园多媒体升级',partyA:'黄埔产业园运营公司',customer:'广州黄埔智联科技有限公司',industry:'园区',region:'广州黄埔',amount:'¥430万',amountNum:430,node:'已转项目',stay:'7天',recent:'2天前',next:'已转项目',scheme:'已推送',closeDate:'2026-08-28',owner:'张三',summary:'方案卡已确认并完成项目转化。'},
{id:10,name:'增城区学校报告厅建设',partyA:'增城区教育局',customer:'广东增城教育装备有限公司',industry:'教育',region:'广州增城',amount:'¥680万',amountNum:680,node:'已转项目',stay:'2天',recent:'今天 14:05',next:'已转项目',scheme:'已完善',closeDate:'2026-08-20',owner:'何经理',summary:'客户、总包和项目名称明确,已完成立项报备。'}
];
const columns=[{key:'select',label:'',fixed:true},{key:'no',label:'序号',fixed:true},{key:'name',label:'商机名称',fixed:true},{key:'source',label:'当前业务场景'},{key:'lastVisited',label:'最近访问时间'},{key:'partyA',label:'甲方'},{key:'customer',label:'意向客户'},{key:'industry',label:'行业'},{key:'region',label:'地区'},{key:'amount',label:'预计金额'},{key:'node',label:'当前工作节点'},{key:'stay',label:'节点停留时间'},{key:'recent',label:'最近跟进'},{key:'next',label:'下一节点'},{key:'scheme',label:'方案卡状态'},{key:'closeDate',label:'计划成交时间'},{key:'actions',label:'操作',fixed:true}];
const poolRows=rows.map((r,i)=>({...r,id:101+i,isPool:true,canClaim:i%2===0,owner:'--',node:['方案卡','关系摸排','资料采集','客户圈定','方案卡','资料采集','关系摸排','客户圈定','已转项目','方案卡'][i],stay:['12天','20天','31天','9天','14天','28天','17天','11天','22天','35天'][i],summary:'公海无主商机,等待销售关注或领取。'}));
const projectRoles=['商机负责人','协同销售','方案工程师','现场工程师','商务专员','跟单报备专员'];
const teamMemberDirectory=[
{id:'u-zhangsan',name:'张三',dept:'粤晋战队-广东15部',job:'销售经理'},
{id:'u-panyu',name:'Panyu10109',dept:'产品中心',job:'产品经理'},
{id:'u-lisi',name:'李四',dept:'方案支持部',job:'方案工程师'},
{id:'u-wangwu',name:'王五',dept:'市场服务部',job:'现场工程师'},
{id:'u-zhaoliu',name:'赵六',dept:'商务部',job:'报价专员'},
{id:'u-chenqi',name:'陈七',dept:'跟单部',job:'报备专员'}
];
const teamMembers=[
{id:'u-zhangsan',role:'商机负责人',duty:'商机统筹与推进',joinedAt:'2026-07-01',isOwner:true,source:'负责人自动加入'},
{id:'u-panyu',role:'',duty:'创建人自动加入',joinedAt:'2026-07-01',isOwner:false,source:'创建人自动加入'},
{id:'u-lisi',role:'方案工程师',duty:'方案设计与方案卡维护',joinedAt:'2026-07-02',isOwner:false,source:'手工添加'},
{id:'u-wangwu',role:'现场工程师',duty:'现场勘察与资料补充',joinedAt:'2026-07-03',isOwner:false,source:'手工添加'}
];
/* 仅用于商机管理看板的“大数据量”交互演示,不影响列表、销售机会或公海数据。 */
const boardStageTotals=[12,18,24,16,9];
const boardManageRows=stages.flatMap((stage,stageIndex)=>{
const seedRows=rows.filter(r=>r.node===stage),total=boardStageTotals[stageIndex],result=[];
for(let i=0;i<total;i++){
const seed=seedRows[i%seedRows.length];
result.push(i<seedRows.length?seed:{...seed,id:500+stageIndex*100+i,node:stage,name:seed.name+'(管理范围商机 '+String(i+1).padStart(2,'0')+')',amountNum:Math.round((seed.amountNum+(i+1)*37.5)*10)/10,amount:'¥'+(Math.round((seed.amountNum+(i+1)*37.5)*10)/10).toFixed(1)+'万',stay:(i%27+2)+'天',recent:(i%5+1)+'天前',owner:['张三','李四','陈晨','吴迪','王强'][i%5]});
}
return result;
});
const participatedIds=new Set([2,3,5,7]);
const now=Date.now();
const customSearchStorageKey='crm-opp-custom-searches-v1.6';
const state={mode:'list',screen:'list',workspace:'sales',returnWorkspace:'sales',returnView:'mine',detailScene:'personal',query:'',industry:'全部行业',selectedId:1,detailTab:'overview',density:'default',visible:new Set(columns.map(c=>c.key)),menuOpen:false,menuTab:'common',defaultView:'mine',view:'mine',splitCollapsed:false,stageFilter:'全部',popover:null,logRange:'recent',planRange:'pending',schemeExpanded:new Set([1]),schemeFocusedCardId:1,schemeEditingCardId:0,schemeCardTemplates:{1:'scheme-company',2:'scheme-company'},selected:new Set(),followedOpps:new Set([1,3,10,105,106]),savedSearches:[],customDraft:null,customMenuId:null,editingSearchId:null,renamingSearchId:null,recentVisits:[{id:1,scene:'personal',at:now-3*60*1000},{id:105,scene:'publicPool',at:now-18*60*1000},{id:2,scene:'personal',at:now-42*60*1000},{id:10,scene:'manage',at:now-76*60*1000},{id:106,scene:'publicPool',at:now-130*60*1000},{id:3,scene:'personal',at:now-190*60*1000}],groupChat:null,pushScenario:'success',pushStep:'select',pushProgress:0,pushScheme:1,schemeTemplateId:'scheme-company',schemeSavedTemplateId:'scheme-company',schemeEditTemplateId:'scheme-company',pendingSchemeTemplateId:'scheme-company',boardPageSize:3,boardLoadedByStage:{}};
const schemeTemplates=[
{id:'scheme-company',name:'公司通用方案卡',version:'V1.1',scope:'所有部门通用',description:'公司标准统一方案卡模板,适用于全部有方案卡维护权限的用户。',recommended:true},
{id:'scheme-team',name:'桂冠天夏战队方案卡',version:'V2.0',scope:'满足模板适用权限的用户',description:'独立维护的完整方案卡模板,适用于满足当前模板权限条件的用户。',recommended:false}
];
let bypassSchemeTemplatePicker=false;
const viewTitles={recent:'最近访问',mine:'我负责的销售机会',participated:'我参与的销售机会',followed:'我关注的销售机会',custom:'自定义检索'};
const viewItems=['recent','mine','participated','followed'];
const tabNames={overview:'概览',customer:'客户信息',scheme:'方案卡',survey:'现场勘察',attachment:'相关附件',follow:'跟进记录',plan:'工作计划',team:'团队成员',log:'操作日志'};
let toastTimer;
function esc(v){return String(v??'').replace(/[&<>'"]/g,ch=>({'&':'&amp;','<':'&lt;','>':'&gt;',"'":'&#39;','"':'&quot;'}[ch]));}
function btn(text,type,extra=''){return '<button class="btn '+(type||'')+'" '+extra+'>'+text+'</button>'}
function modeIcon(mode){return sharedViewModeIcon(mode)}
function pinSvg(){return '<svg class="classic-pushpin" viewBox="0 0 24 24" aria-hidden="true"><g transform="rotate(42 12 12)"><path class="pin-body" d="M8 3h8l-1 7 3 3H6l3-3-1-7Z"/><path class="pin-needle" d="M12 13v8"/></g></svg>'}
function toolIcon(type){return sharedListToolIcon(type)}
function allRows(){return [...rows,...poolRows]}
function rowById(id){return [...allRows(),...boardManageRows].find(r=>r.id===id)}
function personalRows(){return rows.filter(r=>r.owner==='张三'||participatedIds.has(r.id))}
function savedSearch(v=state.view){return state.savedSearches.find(x=>x.id===v)}
function currentViewTitle(){return viewTitles[state.view]||savedSearch()?.name||'我负责的销售机会'}
function workspaceTitle(v=state.workspace){return v==='manage'?'商机管理':v==='publicPool'?'商机公海':'销售机会'}
function sceneForRow(r){if(r?.visitScene)return r.visitScene;if(r?.isPool)return 'publicPool';if(state.workspace==='manage')return 'manage';if(state.workspace==='publicPool')return 'publicPool';if(state.view==='followed'&&r?.owner!=='张三'&&!participatedIds.has(r?.id))return 'manage';return 'personal'}
function sourceLabel(r,scene=sceneForRow(r)){return scene==='publicPool'?'商机公海':scene==='manage'?'商机管理':'销售机会'}
function visitTimeLabel(at){return new Date(at).toLocaleString('zh-CN',{month:'2-digit',day:'2-digit',hour:'2-digit',minute:'2-digit',hour12:false}).replaceAll('/','-')}
function recentRows(){return state.recentVisits.slice().sort((a,b)=>b.at-a.at).map(v=>{const r=rowById(v.id);return r?{...r,visitScene:v.scene,lastVisited:v.at,lastVisitedLabel:visitTimeLabel(v.at),source:sourceLabel(r,v.scene)}:null}).filter(Boolean)}
function recordVisit(id,scene){state.recentVisits=state.recentVisits.filter(v=>v.id!==id);state.recentVisits.unshift({id,scene,at:Date.now()});state.recentVisits=state.recentVisits.slice(0,20)}
function selectableView(){return state.workspace==='manage'||state.workspace==='publicPool'}
function creatableView(){return state.workspace==='sales'}
function followedRows(){return allRows().filter(r=>state.followedOpps.has(r.id)).map(r=>{const scene=r.isPool?'publicPool':(r.owner==='张三'||participatedIds.has(r.id)?'personal':'manage');return {...r,visitScene:scene,source:sourceLabel(r,scene)}})}
function viewRows(){if(state.view==='recent')return recentRows();if(state.view==='mine')return rows.filter(r=>r.owner==='张三');if(state.view==='participated')return rows.filter(r=>participatedIds.has(r.id));if(state.view==='manage')return rows;if(state.view==='publicPool')return poolRows;if(state.view==='followed')return followedRows();return personalRows()}
function customValue(r,field){return field==='销售机会所有人'?r.owner:field==='客户名称'?r.customer:field==='计划成交时间'?r.closeDate:r.name}
function customMatches(r,rule){if(!rule||!rule.value)return true;const left=String(customValue(r,rule.field)||'').toLowerCase(),right=String(rule.value||'').toLowerCase();if(rule.operator==='不等于')return left!==right;if(rule.operator==='包含')return left.includes(right);if(rule.operator==='不包含')return !left.includes(right);if(rule.operator==='为空')return !left;if(rule.operator==='不为空')return !!left;return left===right}
function filteredRows(){const q=state.query.trim().toLowerCase(),rule=savedSearch()||((state.view==='custom')?state.customDraft:null),out=viewRows().filter(r=>(!q||[r.name,r.partyA,r.customer,r.industry,r.region,r.node,r.owner].some(v=>String(v).toLowerCase().includes(q)))&&(state.industry==='全部行业'||r.industry===state.industry)&&(state.stageFilter==='全部'||r.node===state.stageFilter)&&customMatches(r,rule)),dir=rule?.direction==='asc'?1:-1;if(rule?.sort==='预计成交金额')out.sort((a,b)=>(a.amountNum-b.amountNum)*dir);else if(rule?.sort==='计划成交时间')out.sort((a,b)=>String(a.closeDate).localeCompare(String(b.closeDate))*dir);return out;}
function boardFilteredRows(){const q=state.query.trim().toLowerCase();return boardManageRows.filter(r=>(!q||[r.name,r.partyA,r.customer,r.industry,r.region,r.node,r.owner].some(v=>String(v).toLowerCase().includes(q)))&&(state.industry==='全部行业'||r.industry===state.industry)&&(state.stageFilter==='全部'||r.node===state.stageFilter));}
function resetBoardLoad(){state.boardLoadedByStage={}}
function persistCustomSearches(){try{localStorage.setItem(customSearchStorageKey,JSON.stringify({savedSearches:state.savedSearches,defaultView:state.defaultView}))}catch(e){}}
function restoreCustomSearches(){try{const raw=localStorage.getItem(customSearchStorageKey);if(!raw)return;const data=JSON.parse(raw),seen=new Set();if(Array.isArray(data.savedSearches))state.savedSearches=data.savedSearches.filter(x=>x&&/^saved-[\w-]+$/.test(String(x.id||''))&&String(x.name||'').trim()&&!seen.has(x.id)&&seen.add(x.id)).map(x=>({id:String(x.id),name:String(x.name).trim(),field:String(x.field||'机会名称'),operator:String(x.operator||'等于'),value:String(x.value||''),sort:String(x.sort||'预计成交金额'),direction:x.direction==='asc'?'asc':'desc'}));if(viewItems.includes(data.defaultView)||state.savedSearches.some(x=>x.id===data.defaultView))state.defaultView=data.defaultView}catch(e){state.savedSearches=[];state.defaultView='mine'}}
function nextCustomSearchId(){return 'saved-'+Date.now().toString(36)+'-'+Math.random().toString(36).slice(2,7)}
function menuRow(key,label){return '<div class="opp-view-row"><button class="opp-view-link" onclick="oppModule.switchView(\''+key+'\')">'+esc(label)+'</button><button class="opp-pin '+(state.defaultView===key?'active':'')+'" title="设为默认视图" onclick="oppModule.pinView(event,\''+key+'\')">'+pinSvg()+'</button></div>'}
function customMenuRow(item){if(state.renamingSearchId===item.id)return '<div class="opp-view-row is-renaming"><input id="oppRenameInput-'+item.id+'" class="opp-rename-input" value="'+esc(item.name)+'" onclick="event.stopPropagation()" onkeydown="if(event.key===\'Enter\')oppModule.confirmRename(\''+item.id+'\');if(event.key===\'Escape\')oppModule.cancelRename()"><span class="opp-rename-actions"><button class="confirm" title="确认" onclick="oppModule.confirmRename(\''+item.id+'\')">√</button><button class="cancel" title="取消" onclick="oppModule.cancelRename()">×</button></span></div>';const open=state.customMenuId===item.id;return '<div class="opp-view-row is-custom"><button class="opp-view-link" onclick="oppModule.switchView(\''+item.id+'\')">'+esc(item.name)+'</button><button class="opp-more-action '+(open?'active':'')+'" title="管理自定义检索" onclick="oppModule.toggleCustomActions(event,\''+item.id+'\')">…</button><button class="opp-pin '+(state.defaultView===item.id?'active':'')+'" title="设为默认视图" onclick="oppModule.pinView(event,\''+item.id+'\')">'+pinSvg()+'</button>'+(open?'<div class="opp-saved-search-menu" onclick="event.stopPropagation()"><button onclick="oppModule.editSearch(event,\''+item.id+'\')">编辑检索</button><button onclick="oppModule.startRename(event,\''+item.id+'\')">重命名</button><button class="danger" onclick="oppModule.confirmDeleteSearch(event,\''+item.id+'\')">删除</button></div>':'')+'</div>'}
function optionList(values,current){return values.map(x=>'<option '+(x===current?'selected':'')+'>'+esc(x)+'</option>').join('')}
function readCustomForm(){return {name:(document.getElementById('oppCustomName')?.value||'').trim(),field:document.getElementById('oppCustomField')?.value||'机会名称',operator:document.getElementById('oppCustomOperator')?.value||'等于',value:(document.getElementById('oppCustomValue')?.value||'').trim(),sort:document.getElementById('oppCustomSort')?.value||'预计成交金额',direction:document.getElementById('oppCustomDirection')?.value||'desc'}}
function clearCustomError(){const error=document.getElementById('oppCustomError');if(error)error.textContent='';['oppCustomName','oppCustomValue'].forEach(id=>document.getElementById(id)?.classList?.remove('is-invalid'))}
function showCustomError(message,fieldId){clearCustomError();const error=document.getElementById('oppCustomError'),field=document.getElementById(fieldId);if(error)error.textContent=message;field?.classList?.add('is-invalid');field?.focus?.();toast(message)}
function validateCustomRule(rule,excludeId,action){if(!rule.name){showCustomError('请输入检索名称后再'+action,'oppCustomName');return false}if(state.savedSearches.some(x=>x.id!==excludeId&&x.name===rule.name)){showCustomError('检索名称已存在,请更换名称','oppCustomName');return false}if(!['为空','不为空'].includes(rule.operator)&&!rule.value){showCustomError('请输入检索条件值后再'+action,'oppCustomValue');return false}return true}
function activateSavedSearch(rule,message){state.workspace='sales';state.view=rule.id;state.editingSearchId=null;state.customMenuId=null;state.menuOpen=true;state.menuTab='common';state.screen='list';persistCustomSearches();render();toast(message)}
function saveCustomSearch(){const rule=readCustomForm();if(!validateCustomRule(rule,null,'保存'))return;rule.id=nextCustomSearchId();state.savedSearches.push(rule);activateSavedSearch(rule,'已保存到常用检索,可点击图钉设为默认')}
function saveEditedSearch(){const current=savedSearch(state.editingSearchId),rule=readCustomForm();if(!current)return;if(!validateCustomRule(rule,current.id,'保存'))return;Object.assign(current,rule,{id:current.id});activateSavedSearch(current,'检索修改已保存')}
function saveAsNewSearch(){const rule=readCustomForm();if(!validateCustomRule(rule,null,'另存'))return;rule.id=nextCustomSearchId();state.savedSearches.push(rule);activateSavedSearch(rule,'已另存为新的常用检索')}
function runCustomSearch(){const rule=readCustomForm();if(!['为空','不为空'].includes(rule.operator)&&!rule.value){showCustomError('请输入检索条件值后再检索','oppCustomValue');return}state.customDraft=rule;state.workspace='sales';state.view='custom';state.editingSearchId=null;state.menuOpen=false;state.screen='list';render();toast('已按自定义条件检索')}
function toggleCustomActions(e,id){e.stopPropagation();state.customMenuId=state.customMenuId===id?null:id;state.renamingSearchId=null;render()}
function editSearch(e,id){e?.stopPropagation?.();if(!savedSearch(id))return;state.editingSearchId=id;state.customMenuId=null;state.renamingSearchId=null;state.menuOpen=true;state.menuTab='search';render()}
function startRename(e,id){e?.stopPropagation?.();if(!savedSearch(id))return;state.renamingSearchId=id;state.customMenuId=null;render();setTimeout(()=>{const input=document.getElementById('oppRenameInput-'+id);input?.focus();input?.select()},0)}
function cancelRename(){state.renamingSearchId=null;render()}
function confirmRename(id){const item=savedSearch(id),input=document.getElementById('oppRenameInput-'+id),name=(input?.value||'').trim();if(!item)return;if(!name){toast('检索名称不能为空');input?.focus();return}if(state.savedSearches.some(x=>x.id!==id&&x.name===name)){toast('检索名称已存在,请更换名称');input?.focus();input?.select();return}item.name=name;state.renamingSearchId=null;persistCustomSearches();render();toast('检索已重命名')}
function confirmDeleteSearch(e,id){e?.stopPropagation?.();const item=savedSearch(id),mask=document.getElementById('modal'),panel=mask?.querySelector('.modal');if(!item||!mask||!panel)return;state.customMenuId=null;panel.classList.remove('opp-modal-wide','opp-modal-medium');panel.classList.add('opp-modal-small');document.getElementById('modalTitle').textContent='删除自定义检索';mask.querySelector('.m-body').innerHTML='<p style="margin:0;line-height:1.7">删除后无法恢复,是否确认删除自定义检索“<b>'+esc(item.name)+'</b>”?</p>';mask.querySelector('.m-foot').innerHTML='<button class="btn" onclick="closeModal()">取消</button><button class="btn primary" style="background:#dc2626;border-color:#dc2626" onclick="oppModule.deleteSearch(\''+item.id+'\')">确认删除</button>';mask.classList.add('show')}
function deleteSearch(id){const item=savedSearch(id);if(!item)return;state.savedSearches=state.savedSearches.filter(x=>x.id!==id);if(state.view===id){state.workspace='sales';state.view='mine';state.screen='list'}if(state.defaultView===id)state.defaultView='mine';if(state.editingSearchId===id)state.editingSearchId=null;state.customMenuId=null;state.renamingSearchId=null;state.menuOpen=true;state.menuTab='common';persistCustomSearches();closeModal();render();toast('自定义检索已删除')}
function renderCustomForm(){const editing=savedSearch(state.editingSearchId),rule=editing||{name:'',field:'机会名称',operator:'等于',value:'',sort:'预计成交金额',direction:'desc'},hint=editing?'正在编辑“'+esc(editing.name)+'”,保存修改将覆盖原检索':'保存后将加入常用检索,并可设为默认视图',actions=editing?'<button class="btn save" onclick="oppModule.saveAsNewSearch()">另存为新检索</button><button class="btn primary" onclick="oppModule.saveEditedSearch()">保存修改</button>':'<button class="btn save" onclick="oppModule.saveCustomSearch()">保存为常用检索</button><button class="btn primary" onclick="oppModule.runCustomSearch()">检索</button>';return '<div class="opp-search-panel"><span>检索名称</span><input id="oppCustomName" class="wide" value="'+esc(rule.name)+'" placeholder="请输入检索名称" oninput="oppModule.clearCustomError()"><span>检索条件</span><span class="opp-search-hint">'+hint+'</span><span></span><div class="opp-condition-row"><span>1.</span><select id="oppCustomField" onchange="oppModule.clearCustomError()">'+optionList(['机会名称','销售机会所有人','客户名称','计划成交时间'],rule.field)+'</select><select id="oppCustomOperator" onchange="oppModule.clearCustomError()">'+optionList(['等于','不等于','包含','不包含','为空','不为空'],rule.operator)+'</select><input id="oppCustomValue" value="'+esc(rule.value)+'" placeholder="请输入..." oninput="oppModule.clearCustomError()"><button class="remove" type="button" onclick="document.getElementById(\'oppCustomValue\').value=\'\';oppModule.clearCustomError()">−</button></div><span></span><div id="oppCustomError" class="opp-custom-error"></div><span></span><button class="opp-more-cond" type="button" onclick="oppModule.toast(\'Demo 本次支持保存一组检索条件\')">+ 添加更多条件</button><span></span><label class="opp-advanced"><input type="checkbox" onchange="if(this.checked){this.checked=false;oppModule.toast(\'Demo 本次暂不启用高级公式\')}">启用高级公式</label><span>排序条件</span><div class="opp-sort-row"><select id="oppCustomSort">'+optionList(['预计成交金额','计划成交时间'],rule.sort)+'</select><select id="oppCustomDirection"><option value="desc" '+(rule.direction==='desc'?'selected':'')+'>降序</option><option value="asc" '+(rule.direction==='asc'?'selected':'')+'>升序</option></select></div><div class="opp-search-actions">'+actions+'</div></div>'}
function renderMenu(){const common=viewItems.map(key=>menuRow(key,viewTitles[key])).join('')+state.savedSearches.map(customMenuRow).join('');return '<div class="opp-view-menu '+(state.menuOpen?'show ':'')+(state.menuTab==='search'?'is-search ':'')+(state.customMenuId?'custom-actions-open':'')+'" onclick="event.stopPropagation()"><div class="opp-view-tabs"><button class="'+(state.menuTab==='common'?'active':'')+'" onclick="oppModule.menuTab(\'common\')">常用检索</button><button class="'+(state.menuTab==='search'?'active':'')+'" onclick="oppModule.menuTab(\'search\')">自定义检索</button></div><div class="opp-view-panels"><div class="opp-view-panel '+(state.menuTab==='common'?'active':'')+'">'+common+'</div><div class="opp-view-panel '+(state.menuTab==='search'?'active':'')+'">'+renderCustomForm()+'</div></div></div>'}
function renderTop(){const title=state.workspace==='sales'?currentViewTitle():workspaceTitle();const titleNode=state.workspace==='sales'?'<div class="opp-view-title '+(state.menuOpen?'open':'')+'" onclick="oppModule.toggleMenu(event)">'+esc(title)+'<span class="chev">⌄</span>'+renderMenu()+'</div>':'<div class="opp-view-title">'+esc(title)+'</div>';return '<div class="opp-top"><div class="opp-top-left"><div class="opp-mode-group">'+['split','list','board'].map(m=>'<button class="opp-mode-btn '+(state.mode===m?'active':'')+'" title="'+({split:'分屏视图',list:'列表视图',board:'看板视图'}[m])+'" onclick="oppModule.setMode(\''+m+'\')">'+modeIcon(m)+'</button>').join('')+'</div>'+titleNode+'</div></div>'}
function statsRows(){const q=state.query.trim().toLowerCase(),rule=savedSearch()||((state.view==='custom')?state.customDraft:null);return viewRows().filter(r=>(!q||[r.name,r.partyA,r.customer,r.industry,r.region,r.node,r.owner].some(v=>String(v).toLowerCase().includes(q)))&&(state.industry==='全部行业'||r.industry===state.industry)&&customMatches(r,rule))}
function renderStats(){const data=statsRows(),items=[['商机总数',data.length],...stages.map(stage=>[stage,data.filter(r=>r.node===stage).length])];return '<div class="opp-stats" aria-label="'+esc(workspaceTitle())+'阶段统计">'+items.map(x=>'<div class="opp-stat-card"><span>'+esc(x[0])+'</span><b>'+x[1]+'</b></div>').join('')+'</div>'}
function renderDensityPopover(){return '<div class="opp-popover opp-density-pop '+(state.popover==='density'?'show':'')+'" onclick="event.stopPropagation()">'+[['compact','紧凑'],['default','默认'],['relaxed','宽松']].map(x=>'<button class="'+(state.density===x[0]?'active':'')+'" onclick="oppModule.setDensity(\''+x[0]+'\')">'+x[1]+'</button>').join('')+'</div>'}
function renderFieldsPopover(){return '<div class="opp-popover opp-fields-pop '+(state.popover==='fields'?'show':'')+'" onclick="event.stopPropagation()"><h4>自定义显示字段</h4><div class="opp-field-list">'+columns.map(c=>'<label><input type="checkbox" '+(state.visible.has(c.key)?'checked':'')+' '+(c.fixed?'disabled':'')+' onchange="oppModule.toggleField(\''+c.key+'\',this.checked)">'+c.label+'</label>').join('')+'</div><div class="opp-fields-foot"><button class="btn" onclick="oppModule.resetFields()">恢复默认</button><button class="btn primary" onclick="oppModule.closePopover()">完成</button></div></div>'}
function batchActions(){if(state.workspace==='manage')return [['分配','批量分配'],['催办','批量催办'],['关闭','批量关闭商机']];if(state.workspace==='publicPool')return [['关注','批量关注'],['领取','批量领取']];return []}
function renderWorkspaceActions(){if(creatableView())return '<button class="btn primary" onclick="oppModule.openModal(\'新建商机\')">+ 新增商机</button>';if(state.mode==='board')return '';return batchActions().map(x=>'<button class="btn" onclick="oppModule.bulkAction(\''+x[0]+'\')">'+x[1]+'</button>').join('')}
function renderToolbar(){return '<div class="opp-toolbar"><div class="opp-toolbar-left"><input id="oppQueryInput" class="opp-query-input" value="'+esc(state.query)+'" placeholder="搜索商机名称、客户、甲方"><select onchange="oppModule.setIndustry(this.value)"><option '+(state.industry==='全部行业'?'selected':'')+'>全部行业</option><option '+(state.industry==='教育'?'selected':'')+'>教育</option><option '+(state.industry==='交通'?'selected':'')+'>交通</option><option '+(state.industry==='文旅'?'selected':'')+'>文旅</option><option '+(state.industry==='园区'?'selected':'')+'>园区</option><option '+(state.industry==='医疗'?'selected':'')+'>医疗</option><option '+(state.industry==='会展'?'selected':'')+'>会展</option></select><button class="btn primary" onclick="oppModule.query()">查询</button><button class="btn" onclick="oppModule.resetQuery()">重置</button></div><div class="opp-toolbar-right">'+renderWorkspaceActions()+'<div style="position:relative"><button class="opp-tool-icon" title="列表密度" onclick="oppModule.togglePopover(event,\'density\')">'+toolIcon('density')+'</button>'+renderDensityPopover()+'</div><button id="oppRefreshBtn" class="opp-tool-icon" title="刷新数据" onclick="oppModule.refresh(event)">'+toolIcon('refresh')+'</button><div style="position:relative"><button class="opp-tool-icon" title="自定义显示字段" onclick="oppModule.togglePopover(event,\'fields\')">'+toolIcon('fields')+'</button>'+renderFieldsPopover()+'</div></div></div>'}
function followRowButton(r){const followed=state.followedOpps.has(r.id);return '<button class="opp-text-link" onclick="oppModule.toggleFollow('+r.id+')">'+(followed?'取消关注':'关注')+'</button>'}
function rowActions(r){const scene=sceneForRow(r),detail='<button class="opp-text-link" onclick="oppModule.openDetail('+r.id+')">详情</button>',follow=followRowButton(r);if(state.view==='followed')return detail+' '+follow;if(scene==='publicPool')return detail+' '+follow+(r.canClaim?' <button class="opp-text-link" onclick="oppModule.openModal(\'领取商机\')">领取</button>':'');if(scene==='manage')return detail+' <button class="opp-text-link" onclick="oppModule.openModal(\'分配商机\')">分配</button> <button class="opp-text-link" onclick="oppModule.openModal(\'督办\')">督办</button> '+follow+' <button class="opp-text-link" onclick="oppModule.openModal(\'关闭商机\')">关闭</button>';return detail+' <button class="opp-text-link" onclick="oppModule.openModal(\'写跟进\')">写跟进</button> '+follow}
function cell(c,r,i){if(c.key==='no')return i+1;if(c.key==='select')return '<input class="opp-row-check" type="checkbox" '+(state.selected.has(r.id)?'checked':'')+' onclick="event.stopPropagation()" onchange="oppModule.toggleSelect('+r.id+',this.checked)">';if(c.key==='name')return '<button class="opp-name-link" onclick="oppModule.openDetail('+r.id+')">'+esc(r.name)+'</button>';if(c.key==='source')return esc(r.source||sourceLabel(r));if(c.key==='lastVisited')return esc(r.lastVisitedLabel||'--');if(c.key==='industry')return '<span class="opp-industry">'+esc(r.industry)+'</span>';if(c.key==='node'||c.key==='next')return '<span class="opp-node-pill">'+esc(r[c.key])+'</span>';if(c.key==='actions')return rowActions(r);return esc(r[c.key])}
function renderList(data){const selectable=selectableView(),contextKeys=state.view==='recent'?new Set(['source','lastVisited']):state.view==='followed'?new Set(['source']):new Set(),cols=columns.filter(c=>state.visible.has(c.key)&&(c.key!=='select'||selectable)&&(!['source','lastVisited'].includes(c.key)||contextKeys.has(c.key))),wideActions=data.some(r=>sceneForRow(r)==='manage'),all=selectable&&data.length&&data.every(r=>state.selected.has(r.id)),partial=selectable&&!all&&data.some(r=>state.selected.has(r.id));const master='<input type="checkbox" '+(all?'checked':'')+' '+(partial?'data-partial="1"':'')+' onchange="oppModule.toggleAll(this.checked)">';return '<div class="opp-list-shell"><div class="opp-table-wrap"><table class="opp-table '+(selectable?'has-select':'no-select')+(wideActions?' has-wide-actions':'')+'"><thead><tr>'+cols.map(c=>'<th class="col-'+c.key+'">'+(c.key==='select'?master:c.label)+'</th>').join('')+'</tr></thead><tbody>'+data.map((r,i)=>'<tr class="'+(state.selected.has(r.id)?'selected':'')+'">'+cols.map(c=>'<td class="col-'+c.key+'">'+cell(c,r,i)+'</td>').join('')+'</tr>').join('')+'</tbody></table></div><div class="opp-pagination"><span>共 '+data.length+' 条</span><select><option>10条/页</option><option>20条/页</option><option>50条/页</option></select><div class="opp-page-buttons"><button disabled>‹</button><button class="active">1</button><button>2</button><button>3</button><button>›</button></div></div></div>'}
function renderSplit(data){
const selected=data.find(x=>x.id===state.selectedId)||data[0]||rows[0],selectable=selectableView(),all=selectable&&data.length&&data.every(r=>state.selected.has(r.id)),partial=selectable&&!all&&data.some(r=>state.selected.has(r.id));state.selectedId=selected.id;
const master=selectable?'<label class="opp-split-check" onclick="event.stopPropagation()"><input type="checkbox" '+(all?'checked':'')+' '+(partial?'data-partial="1"':'')+' onchange="oppModule.toggleAll(this.checked)"></label>':'';
return '<div class="opp-split-shell crm-unified-split '+(state.splitCollapsed?'collapsed':'')+'">'
+'<div class="opp-split-left crm-unified-split-left '+(selectable?'has-select':'no-select')+'"><div class="opp-split-list crm-unified-split-list">'
+'<div class="opp-split-table-head">'+master+'<span>商机名称</span><span>阶段</span></div>'
+data.map(r=>'<div class="opp-split-row '+(r.id===selected.id?'active ':'')+(state.selected.has(r.id)?'selected':'')+'" onclick="oppModule.selectSplit('+r.id+')">'
+(selectable?'<label class="opp-split-check" onclick="event.stopPropagation()"><input type="checkbox" '+(state.selected.has(r.id)?'checked':'')+' onchange="oppModule.toggleSelect('+r.id+',this.checked)"></label>':'')
+'<span class="opp-split-row-main"><b>'+esc(r.name)+'</b></span>'
+'<span class="opp-split-stage">'+esc(r.node)+'</span>'
+'</div>').join('')
+'</div></div>'
+'<div class="opp-split-divider crm-unified-split-divider"><button class="opp-split-toggle crm-unified-split-toggle" aria-label="'+(state.splitCollapsed?'展开左侧列表':'收起左侧列表')+'" onclick="oppModule.toggleSplit()">'+(state.splitCollapsed?'›':'‹')+'</button></div>'
+'<div class="opp-split-detail crm-unified-split-detail">'+renderDetail(selected,true,sceneForRow(selected))+'</div>'
+'</div>';
}
function renderBoard(){
const data=state.workspace==='manage'?boardFilteredRows():filteredRows(),groups=stages.map(s=>({stage:s,list:data.filter(r=>r.node===s)}));
const scope=state.workspace==='manage'?'商机管理 · 当前数据权限 · 当前筛选条件':'当前页面视图 · 当前数据权限 · 当前筛选条件';
return '<div class="opp-board-shell"><div class="opp-board-type"><select><option>默认业务类型</option><option>销售机会标准流程</option></select></div><div class="opp-board-scope"><span><b>阶段汇总范围:</b>'+scope+'</span><em>阶段头部按全量匹配商机计算;卡片明细按阶段加载</em></div><div class="opp-board-pipeline">'+groups.map(g=>'<div class="opp-pipe-stage"><span>'+esc(g.stage)+'(共 '+g.list.length+' 条)</span><small>预计金额 '+g.list.reduce((a,b)=>a+b.amountNum,0).toFixed(2)+' 万元</small></div>').join('')+'</div><div class="opp-board-lanes">'+groups.map(g=>{
const loaded=Math.min(state.boardLoadedByStage[g.stage]||state.boardPageSize,g.list.length),visible=g.list.slice(0,loaded),more=loaded<g.list.length;
return '<section class="opp-board-lane">'+(visible.length?visible.map(r=>'<article class="opp-board-card" onclick="oppModule.openBoardCard('+r.id+')"><b>'+esc(r.name)+'</b><span>'+esc(r.industry)+'</span><span class="client">'+esc(r.customer)+'</span><span>'+esc(r.amount)+' '+esc(r.owner)+' '+esc(r.stay)+'</span></article>').join(''):'<div class="opp-board-empty">当前筛选条件下暂无商机</div>')+(g.list.length?'<div class="opp-board-loaded">已展示 '+loaded+' / 共 '+g.list.length+' 条</div>':'')+(more?'<button class="opp-board-more" onclick="oppModule.loadMoreBoardStage(\''+g.stage+'\')">加载更多(剩余 '+(g.list.length-loaded)+' 条)</button>':'')+'</section>';
}).join('')+'</div></div>'
}
function detailFollowButton(r){const followed=state.followedOpps.has(r.id);return '<button class="btn" onclick="oppModule.toggleFollow('+r.id+')">'+(followed?'取消关注':'关注')+'</button>'}
function standardDetailActions(r,primaryAction='写跟进'){
return '<div class="opp-detail-actions"><button class="btn primary" onclick="oppModule.openModal(\''+primaryAction+'\')">'+primaryAction+'</button>'+detailFollowButton(r)+'<button class="btn" onclick="oppModule.openModal(\'编辑商机\')">编辑</button><button class="btn" onclick="oppModule.openModal(\'新增方案卡\')">新增方案卡</button><button class="btn" onclick="oppModule.openModal(\'添加关联客户\')">添加关联客户</button><button class="btn warn" onclick="oppModule.openModal(\'推送方案\')">推送方案</button><button class="btn" onclick="oppModule.openModal(\'暂缓商机\')">暂缓商机</button><button class="btn" onclick="oppModule.openModal(\'抛公海\')">抛公海</button><button class="btn" onclick="oppModule.openTeamMemberDialog(\'add\')">添加团队成员</button><button class="btn danger" onclick="oppModule.openModal(\'关闭商机\')">关闭商机</button></div>';
}
function poolDetailActions(r){
const claim=r.canClaim?'<button class="btn primary" onclick="oppModule.openModal(\'领取商机\')">领取</button>':'<button class="btn" disabled title="当前仅有查看和关注权限">无领取权限</button>';
return '<div class="opp-detail-actions">'+claim+detailFollowButton(r)+'<button class="btn" onclick="oppModule.openModal(\'分配商机\')">分配</button><button class="btn danger" onclick="oppModule.openModal(\'关闭商机\')">关闭商机</button></div>';
}
function detailActions(r,scene){
if(scene==='publicPool')return poolDetailActions(r);
if(scene==='manage')return standardDetailActions(r,'督办');
return standardDetailActions(r);
}
function renderStages(r){
const current=Math.max(0,stages.indexOf(r.node));
return '<div class="opp-stage-track">'+stages.map((s,i)=>'<button class="opp-stage-node '+(i<current?'done':i===current?'active':'')+'" onclick="oppModule.advanceStage(\''+s+'\')"><i>'+(i<=current?'√':i+1)+'</i>'+s+'</button>').join('')+'</div>'
+'<div class="opp-guide-copy"><div class="opp-guide-title">商机推进指引</div><div class="opp-guide-desc">按照公司标准打法,对商机定义当前进度及明确节点工作</div></div>';
}
function tabHeader(title,actions=''){return '<div class="a3-tab-header"><div class="a3-tab-header-title">'+title+'</div><div class="a3-tab-header-actions">'+(actions||'')+'</div></div>'}
function overview(r){return tabHeader('概览')+'<div class="a3-two-col"><div><section class="a3-section"><div class="a3-section-head">近期工作计划<button onclick="oppModule.switchTab(\'plan\')">查看更多 ></button></div><div class="a3-plan-list"><div class="a3-plan-item"><b>面访客户,最好能组上饭局。</b><span>2026-07-01 10:30</span><span class="danger">距离开始还有3天2小时7分</span><span>用户1</span><span class="a3-plan-actions"><button onclick="oppModule.toast(\'计划已进入编辑状态\')">编辑</button><button onclick="oppModule.toast(\'计划已删除\')">删除</button><button onclick="oppModule.toast(\'计划已标记完成\')">标记完成</button></span></div><div class="a3-plan-item"><b>至少电话沟通一次,以添加微信、约拜访时间为前期接洽目标。</b><span>2026-06-01 16:30</span><span class="danger">超时未完成14天13小时14分</span><span>用户1</span><span class="a3-plan-actions"><button onclick="oppModule.toast(\'计划已进入编辑状态\')">编辑</button><button onclick="oppModule.toast(\'计划已删除\')">删除</button><button onclick="oppModule.toast(\'计划已标记完成\')">标记完成</button></span></div></div></section><section class="a3-section"><div class="a3-section-head">基础信息</div><table class="a3-table"><tbody><tr><th>项目名称</th><td>'+esc(r.name)+'</td><th>项目类型</th><td>新建</td></tr><tr><th>项目行业</th><td>'+esc(r.industry)+'</td><th>项目阶段</th><td>标前设计</td></tr><tr><th>项目区域</th><td>广州市番禺区</td><th>招标形式</th><td>GC(工程)</td></tr><tr><th>当前负责人</th><td>'+esc(r.owner)+'</td><th>项目属地</th><td>本地项目</td></tr><tr><th>所属团队</th><td>特战团队</td><th>所属部门</th><td>粤晋战队-广东15部</td></tr><tr><th>项目金额</th><td colspan="3">648.00 万元</td></tr></tbody></table></section><section class="a3-section"><div class="a3-section-head">客户情况<button onclick="oppModule.switchTab(\'customer\')">查看更多 ></button></div><table class="a3-table"><tbody><tr><th>项目甲方</th><td>招商局物业管理广州分公司</td><th>甲方联系人</th><td>王主任 <span class="link">查看</span></td></tr><tr><th>联系人职务</th><td>招商主任</td><th>联系方式</th><td>136******46</td></tr><tr><th>主要意向客户</th><td>广州亚东智能科技有限公司</td><th>客户联系人</th><td>张经理 <span class="link">查看</span></td></tr><tr><th>联系人职务</th><td>客户经理</td><th>联系方式</th><td>138******12</td></tr><tr><th>客户B</th><td>广东轨交通信工程有限公司</td><th>客户联系人</th><td>小马 <span class="link">查看</span></td></tr><tr><th>联系人职务</th><td>业务代表</td><th>联系方式</th><td>138******12</td></tr></tbody></table></section><section class="a3-section"><div class="a3-section-head">方案卡<button onclick="oppModule.switchTab(\'scheme\')">查看更多 ></button></div>'+schemeDetailTable()+'</section></div><aside class="a3-side"><div class="a3-section-head">近期跟进<button onclick="oppModule.switchTab(\'follow\')">查看更多 ></button></div><div class="a3-timeline"><div class="a3-time-item"><b>【电话】电话沟通记录</b><p>跟进日期 2025-10-30</p><small>张三 2025-11-22 16:58:32</small></div><div class="a3-time-item"><b>【微信】微信沟通记录</b><p>跟进日期 2025-11-22</p><small>张三 2025-11-22 16:58:32</small></div><div class="a3-time-item"><b>【拜访】现场拜访沟通简单记录</b><p>跟进日期 2025-09-30</p><small>张三 2025-11-22 16:58:32</small></div></div></aside></div>'}
function schemeDetailTable(cardId=1){
const card=cardId===2
?{customer:'广东轨交通信工程有限公司',type:'工程商',contact:'李**',phone:'157******33',stage:'设计',tender:'BC(EPC总分包)',survey:'--',million:'待确认',budgetClear:'否',budget:'待确认',probability:'--',bidTime:'--',config:'--',brand:'--',replaceBrand:'待确认',bidNo:'--',bidder:'--',need:'--',business:'--'}
:{customer:'广州亚东智能科技有限公司',type:'总包',contact:'张**',phone:'136******46',stage:'标前改单',tender:'GC(工程)',survey:'金山谷现场勘察资料.rar',million:'是',budgetClear:'是',budget:'238.00 万元',probability:'60%',bidTime:'--',config:'按清单配置',brand:'itc,TS系列',replaceBrand:'否',bidNo:'--',bidder:'--',need:'根据现有清单匹配产品参数并提供调整建议。',business:'中配,主要从参数基础上控制配置成本,支持后续报价。'};
return '<table class="a3-table"><tbody><tr><th>客户名称</th><td>'+esc(card.customer)+'</td><th>客户类型</th><td>'+esc(card.type)+'</td></tr><tr><th>战略VIP客户等级</th><td>--</td><th>已成交VIP客户等级</th><td>--</td></tr><tr><th>客户联系人</th><td>'+esc(card.contact)+'</td><th>客户联系电话</th><td>'+esc(card.phone)+' <span class="link">查看</span></td></tr><tr><th>项目阶段</th><td>'+esc(card.stage)+'</td><th>招标形式</th><td>'+esc(card.tender)+'</td></tr><tr><th>是否有看现场</th><td>'+(cardId===2?'待确认':'是')+'</td><th>是否与客户电话沟通</th><td>'+(cardId===2?'待确认':'是')+'</td></tr><tr><th>现场勘察资料</th><td>'+esc(card.survey)+'</td><th>是否百万级项目</th><td>'+esc(card.million)+'</td></tr><tr><th>方案预算是否明确</th><td>'+esc(card.budgetClear)+'</td><th>方案预算金额</th><td>'+esc(card.budget)+'</td></tr><tr><th>预计成交概率</th><td>'+esc(card.probability)+'</td><th>预计投标时间</th><td>'+esc(card.bidTime)+'</td></tr><tr><th>设计配置</th><td>'+esc(card.config)+'</td><th>品牌要求</th><td>'+esc(card.brand)+'</td></tr><tr><th>是否可换品牌</th><td>'+esc(card.replaceBrand)+'</td><th>招标编号</th><td>'+esc(card.bidNo)+'</td></tr><tr><th>标底方</th><td>'+esc(card.bidder)+'</td><td colspan="2"></td></tr><tr><th>客户需求描述</th><td colspan="3">'+esc(card.need)+'</td></tr><tr><th>业务需求</th><td colspan="3">'+esc(card.business)+'</td></tr><tr><th>对方要求的先进性及前瞻性的要求</th><td colspan="3">--</td></tr><tr><th>清单参数(是否是我司产品参数、工程师需要确认,是否有品牌?)</th><td colspan="3">'+(cardId===2?'--':'是否是我司产品参数、工程师需要确认,是否有品牌。')+'</td></tr><tr><th>清单参数是否能改</th><td colspan="3">'+(cardId===2?'--':'可改,按深化方案处理。')+'</td></tr><tr><th>客户特殊要求(补充说明、备注说明)</th><td colspan="3">--</td></tr><tr><th>方案卡填写时间</th><td>--</td><th>期望方案设计完成时间</th><td>--</td></tr><tr><th>创建人</th><td>--</td><th>创建时间</th><td>--</td></tr></tbody></table>';
}
function customerTab(){const actions='<button class="btn primary" onclick="oppModule.openModal(\'添加关联客户\')">+ 添加关联客户</button>';return tabHeader('客户信息',actions)+'<table class="a3-table"><thead><tr><th>客户名称</th><th>客户类型</th><th>联系人 / 联系方式</th><th>关系标记</th><th>操作</th></tr></thead><tbody>'+[['广州亚东智能科技有限公司','总包','陈** / 138******12','主要意向客户'],['广东轨交通信工程有限公司','工程商','张** / 157******33','普通关联'],['广东智慧教育科技有限公司','集成商','王** / 136******46','普通关联'],['广东康联医疗科技有限公司','工程商','何** / 133******75','普通关联']].map((x,i)=>'<tr><td><span class="link">'+x[0]+'</span></td><td>'+x[1]+'</td><td>'+x[2]+'</td><td><span class="a3-status '+(i===0?'blue':'')+'">'+x[3]+'</span></td><td class="a3-actions"><button>查看客户详情</button>'+(i?'<button onclick="oppModule.toast(\'已设为主要意向客户\')">设为主要</button>':'')+'</td></tr>').join('')+'</tbody></table>'}
function schemeOverviewTable(cards){
const rows=cards.map(card=>'<tr class="'+(state.schemeFocusedCardId===card.id?'active':'')+'" onclick="oppModule.focusSchemeCard('+card.id+')" title="定位并展开方案卡 '+card.id+'"><td>'+card.id+'</td><td class="scheme-overview-name">'+esc(card.customer)+'</td><td>'+esc(card.type)+'</td><td>'+esc(card.stage)+'</td><td>'+esc(card.tender)+'</td><td>'+esc(card.budget)+'</td><td><span class="a3-status '+card.statusClass+'">'+esc(card.status)+'</span></td></tr>').join('');
return '<div class="scheme-overview"><table class="a3-table"><thead><tr><th>序号</th><th>客户名称</th><th>客户类型</th><th>项目阶段</th><th>招标形式</th><th>方案预算金额</th><th>状态</th></tr></thead><tbody>'+rows+'</tbody></table></div>';
}
function schemeCard(card){
const expanded=state.schemeExpanded.has(card.id);
const trailing=card.id===1?'<button onclick="oppModule.openModal(\'推送方案\')">推送</button>':'<button onclick="oppModule.toast(\'草稿方案卡删除需二次确认\')">删除</button>';
return '<article id="scheme-card-'+card.id+'" class="a3-card '+(state.schemeFocusedCardId===card.id?'scheme-card-focused':'')+'" data-scheme-card="'+card.id+'"><div class="a3-card-head"><span class="scheme-card-title"><b>方案卡 '+card.id+' · '+esc(card.customer)+'</b><span class="a3-status '+card.statusClass+'">'+esc(card.status)+'</span></span><div class="scheme-card-actions"><button onclick="oppModule.toggleScheme('+card.id+')">'+(expanded?'收起详情':'展开详情')+'</button><button onclick="oppModule.openSchemeCardEdit('+card.id+')">编辑</button>'+trailing+'</div></div>'+(expanded?'<div class="a3-card-body">'+schemeDetailTable(card.id)+'</div>':'')+'</article>';
}
function schemeTab(){
const actions='<button class="btn primary" onclick="oppModule.openModal(\'新增方案卡\')">+ 新增方案卡</button><button class="btn" onclick="oppModule.openModal(\'推送方案\')">推送方案</button>';
const cards=[
{id:1,customer:'广州亚东智能科技有限公司',type:'总包',stage:'标前改单',tender:'GC(工程)',budget:'238.00 万元',status:'当前方案卡',statusClass:'blue'},
{id:2,customer:'广东轨交通信工程有限公司',type:'工程商',stage:'设计',tender:'BC(EPC总分包)',budget:'待确认',status:'草稿',statusClass:'orange'}
];
return tabHeader('方案卡',actions)+schemeOverviewTable(cards)+'<div class="a3-card-grid">'+cards.map(schemeCard).join('')+'</div>';
}
function surveyTab(){
const actions='<select><option>全部状态</option><option>已完成</option><option>待补充</option></select><button class="btn primary" onclick="oppModule.openModal(\'新增现场勘察\')">+ 新增现场勘察</button>';
return tabHeader('现场勘察',actions)+'<div class="a3-table-wrap"><table class="a3-table"><colgroup><col style="width:12%"><col style="width:15%"><col style="width:14%"><col style="width:12%"><col style="width:25%"><col style="width:11%"><col style="width:7%"><col style="width:10%"></colgroup><thead><tr><th>勘察时间</th><th>勘察地点</th><th>来源单据</th><th>参与人员</th><th>结果说明</th><th>附件</th><th>状态</th><th>操作</th></tr></thead><tbody><tr><td>2026-07-02 14:30</td><td>金山谷智慧园区项目现场</td><td>OA-SURVEY-20260702-018</td><td>张三、李工</td><td>完成会议室、园区广播和中控机房勘察。</td><td><span class="link">现场勘察资料.rar</span></td><td><span class="a3-status green">已完成</span></td><td><div class="a3-row-actions"><button>查看</button><button>编辑</button></div></td></tr><tr><td>2026-06-25 10:00</td><td>园区物业办公室</td><td>飞书申请单 FS-20418</td><td>张三</td><td>确认甲方联系人与现场进场要求。</td><td>--</td><td><span class="a3-status orange">待补充</span></td><td><div class="a3-row-actions"><button>查看</button><button>补充</button></div></td></tr></tbody></table></div>';
}
function attachmentTab(){
const actions='<select><option>全部分类</option><option>现场勘察</option><option>客户资料</option><option>方案资料</option></select><button class="btn primary" onclick="oppModule.openModal(\'上传附件\')">+ 上传附件</button>';
const files=[
['RAR','金山谷现场勘察资料.rar','现场勘察','张三','2026-07-08 09:42',false],
['PDF','项目平面图.pdf','现场勘察','李工','2026-07-02 16:10',true],
['XLS','客户需求清单.xlsx','客户资料','张三','2026-07-01 11:20',true],
['DOC','招标需求初稿.docx','方案资料','王建','2026-06-28 15:35',true],
['ZIP','机房现场照片.zip','现场勘察','李工','2026-06-25 17:02',false]
];
const rows=files.map(x=>'<tr><td><div class="a3-file-cell"><div class="a3-file-icon">'+x[0]+'</div><b>'+x[1]+'</b></div></td><td>'+x[2]+'</td><td>'+x[3]+'</td><td>'+x[4]+'</td><td><div class="a3-row-actions">'+(x[5]?'<button onclick="oppModule.toast(\'正在预览 '+x[1]+'\')">预览</button>':'')+'<button onclick="oppModule.toast(\'开始下载 '+x[1]+'\')">下载</button><button class="danger" onclick="if(confirm(\'确认删除该附件吗?\'))oppModule.toast(\'附件已删除\')">删除</button></div></td></tr>').join('');
return tabHeader('相关附件',actions)+'<div class="a3-card"><div class="a3-card-head"><span>附件列表</span><span>共 5 个</span></div><div class="a3-table-wrap"><table class="a3-table a3-attachment-table"><colgroup><col class="file"><col class="category"><col class="uploader"><col class="time"><col class="action"></colgroup><thead><tr><th>文件名称</th><th>分类</th><th>上传人</th><th>上传时间</th><th>操作</th></tr></thead><tbody>'+rows+'</tbody></table></div></div>';
}
function followTab(){const actions='<select><option>全部跟进方式</option><option>电话</option><option>微信</option><option>拜访</option><option>会议</option></select><input placeholder="搜索跟进内容"><button class="btn primary" onclick="oppModule.openModal(\'写跟进\')">+ 写跟进</button>';return tabHeader('跟进记录',actions)+'<div class="a3-timeline"><div class="a3-time-item"><b>【电话】与张经理确认方案预算与品牌要求</b><p>客户确认当前预算约 238 万元,品牌要求以 itc、TS 系列为主,需补充清单参数比对。</p><small>张三 2026-07-08 10:32</small></div><div class="a3-time-item"><b>【拜访】现场沟通会议室和园区广播需求</b><p>完成会议室、园区广播、中控机房需求梳理,客户希望方案采用中配。</p><small>张三 2026-07-02 16:20</small></div><div class="a3-time-item"><b>【微信】约定下一次拜访时间</b><p>添加客户微信并约定下周到现场复核机房和布线条件。</p><small>张三 2026-06-28 09:15</small></div></div>'}
function planTab(){
const pending=state.planRange==='pending';
const actions='<button class="btn primary" onclick="oppModule.openModal(\'新建计划\')">+ 新建计划</button>';
const body=pending?'<tr><td>面访客户,最好能组上饭局。</td><td>用户1</td><td>2026-07-01 10:30</td><td class="danger">距离开始还有3天2小时7分</td><td><span class="a3-status blue">待开始</span></td><td><div class="a3-row-actions"><button>编辑</button><button>删除</button><button>标记完成</button></div></td></tr><tr><td>至少电话沟通一次,以添加微信、约拜访时间为目标。</td><td>用户1</td><td>2026-06-01 16:30</td><td class="danger">超时未完成14天13小时14分</td><td><span class="a3-status red">已超时</span></td><td><div class="a3-row-actions"><button>编辑</button><button>删除</button><button>标记完成</button></div></td></tr>':'<tr><td>确认甲方联系人与项目基本信息</td><td>张三</td><td>2026-06-25 10:00</td><td>已完成</td><td><span class="a3-status green">已完成</span></td><td><div class="a3-row-actions"><button>查看</button></div></td></tr>';
return tabHeader('工作计划',actions)+'<div class="a3-subtabs"><button class="'+(pending?'active':'')+'" onclick="oppModule.setPlanRange(\'pending\')">待完成</button><button class="'+(!pending?'active':'')+'" onclick="oppModule.setPlanRange(\'done\')">已完成</button></div><div class="a3-table-wrap"><table class="a3-table"><colgroup><col style="width:28%"><col style="width:11%"><col style="width:18%"><col style="width:22%"><col style="width:9%"><col style="width:18%"></colgroup><thead><tr><th>计划事项</th><th>负责人</th><th>计划时间</th><th>提醒时间</th><th>状态</th><th>操作</th></tr></thead><tbody>'+body+'</tbody></table></div>';
}
function memberInfo(id){return teamMemberDirectory.find(x=>x.id===id)}
function memberRoleOptions(value='',allowOwner=true){return '<option value="">请选择(非必填)</option>'+projectRoles.filter(x=>allowOwner||x!=='商机负责人').map(x=>'<option value="'+x+'" '+(value===x?'selected':'')+'>'+x+'</option>').join('')}
function teamTab(){
const actions='<button class="btn" onclick="oppModule.openGroupChatDialog()">一键拉群</button><button class="btn primary" onclick="oppModule.openTeamMemberDialog(\'add\')">+ 添加成员</button>';
const rows=teamMembers.map(m=>{const p=memberInfo(m.id)||{name:'--',dept:'--',job:'--'},ops=m.isOwner?'<button onclick="oppModule.openTeamMemberDialog(\'edit\',\''+m.id+'\')">编辑</button><button onclick="oppModule.openTeamMemberDialog(\'transfer\',\''+m.id+'\')">移交</button>':'<button onclick="oppModule.openTeamMemberDialog(\'edit\',\''+m.id+'\')">编辑</button><button class="danger" onclick="oppModule.removeTeamMember(\''+m.id+'\')">移除</button>';
return '<tr><td><b>'+esc(p.name)+'</b>'+(m.isOwner?'<span class="a3-status blue" style="margin-left:8px">负责人</span>':'')+'</td><td>'+esc(p.dept)+'</td><td>'+esc(p.job)+'</td><td>'+esc(m.role||'--')+'</td><td>'+esc(m.duty||'--')+'</td><td>'+esc(m.joinedAt)+'</td><td class="a3-actions">'+ops+'</td></tr>'}).join('');
const chat=state.groupChat?'<div class="opp-group-record"><div><b>协同群聊:'+esc(state.groupChat.name)+'</b><small>已回写 CRM · '+state.groupChat.count+' 名内部成员 · 创建人:张三 · '+state.groupChat.createdAt+'</small></div><button class="btn" onclick="oppModule.toast(\'Demo:将跳转至企业微信查看群聊\')">查看群聊</button></div>':'';
return tabHeader('团队成员',actions)+chat+'<div class="opp-business-note" style="margin:0 0 12px">团队成员用于记录协作分工。功能权限由系统角色控制;加入团队后,系统自动补充成员对当前商机的记录级数据访问资格。</div><table class="a3-table"><thead><tr><th>成员名称</th><th>所属部门</th><th>职位</th><th>项目角色</th><th>职责</th><th>加入时间</th><th>操作</th></tr></thead><tbody>'+rows+'</tbody></table>'
}
function teamMemberById(id){return teamMembers.find(x=>x.id===id)}
function teamModalShell(title){const mask=document.getElementById('modal'),panel=mask?.querySelector('.modal');if(!mask||!panel)return null;panel.classList.remove('opp-modal-wide','opp-modal-medium','opp-modal-small','opp-push-dialog');panel.classList.add('opp-modal-medium');document.getElementById('modalTitle').textContent=title;return {mask,panel}}
function openGroupChatDialog(){
const modal=teamModalShell('一键拉群');if(!modal)return;
const r=rowById(state.selectedId)||rows[0],members=teamMembers.map(m=>({member:m,person:memberInfo(m.id)})).filter(x=>x.person);
const defaultName='【商机】'+r.name;
const memberRows=members.map(x=>'<label class="opp-group-member"><input type="checkbox" name="oppGroupMember" value="'+x.member.id+'" checked onchange="oppModule.updateGroupChatCount()"><span class="opp-group-avatar">'+esc(x.person.name.slice(0,1))+'</span><span>'+esc(x.person.name)+'</span><span class="opp-group-role">'+esc(x.member.role||x.person.job||'团队成员')+'</span></label>').join('');
const body='<div class="opp-group-dialog"><div class="opp-business-note">默认带入当前商机团队。可取消成员或补充内部协同人;创建成功后,CRM 仅回写建群记录,群成员与群公告在企业微信侧继续管理。</div><div class="opp-group-layout"><section class="opp-team-section"><h4 class="opp-team-section-title">群成员 <span class="opp-team-required">*</span></h4><div class="opp-group-members">'+memberRows+'</div><button type="button" class="btn" style="margin-top:12px" onclick="oppModule.toast(\'Demo:可从组织通讯录补充内部协同人员\')">+ 添加内部成员</button><p class="opp-team-caption" id="oppGroupMemberCount">已选择 '+members.length+' 名内部成员</p></section><section class="opp-team-section"><h4 class="opp-team-section-title">建群设置</h4><div class="opp-team-fields"><div class="opp-team-field"><label><span class="opp-team-required">*</span>群名称</label><input id="oppGroupChatName" value="'+esc(defaultName)+'"></div><div class="opp-team-field"><label>群类型</label><select id="oppGroupChatType"><option>项目协同群</option><option>方案沟通群</option><option>报价协同群</option></select></div></div><label class="opp-team-check"><input id="oppGroupWriteback" type="checkbox" checked> 创建后在商机详情回写群聊记录</label></section></div></div>';
const foot='<button class="btn" onclick="closeModal()">取消</button><button class="btn primary" onclick="oppModule.saveGroupChatDialog()">创建群聊</button>';
modal.mask.querySelector('.m-body').innerHTML=body;modal.mask.querySelector('.m-foot').innerHTML=foot;modal.mask.classList.add('show');
}
function updateGroupChatCount(){const count=document.querySelectorAll('input[name="oppGroupMember"]:checked').length,tip=document.getElementById('oppGroupMemberCount');if(tip)tip.textContent='已选择 '+count+' 名内部成员'}
function saveGroupChatDialog(){const ids=[...document.querySelectorAll('input[name="oppGroupMember"]:checked')].map(x=>x.value),name=(document.getElementById('oppGroupChatName')?.value||'').trim(),writeback=document.getElementById('oppGroupWriteback')?.checked!==false;if(!ids.length){toast('请至少选择一位内部成员');return}if(!name){toast('请填写群名称');return}closeModal();if(writeback){state.groupChat={name,count:ids.length,createdAt:'2026-07-27 10:30'};render()}toast('已创建“'+name+'”,并回写当前商机的协同群聊记录')}
function openTeamMemberDialog(mode,id){
const modal=teamModalShell(mode==='add'?'添加团队成员':mode==='transfer'?'移交商机':'编辑团队成员');if(!modal)return;
const member=teamMemberById(id),person=member&&memberInfo(member.id);let body='',foot='';
if(mode==='add'){
body='<div class="opp-team-dialog"><div class="opp-business-note">加入团队后,系统自动补充其对当前商机的记录级数据访问资格;功能权限仍按该用户的系统角色生效。</div><div class="opp-team-search"><input placeholder="搜索姓名 / 部门 / 职位"><select><option>全部部门</option><option>销售中心</option><option>方案支持部</option><option>市场服务部</option><option>商务部</option></select><button class="btn primary">查询</button><button class="btn">重置</button></div><div class="opp-select-table-wrap"><table class="opp-select-table"><thead><tr><th>选择</th><th>姓名</th><th>所属部门</th><th>职位</th><th>当前参与商机数</th></tr></thead><tbody>'+teamMemberDirectory.filter(x=>x.id!=='u-zhangsan'&&x.id!=='u-panyu').map((p,i)=>'<tr><td><input type="checkbox" name="oppTeamAdd" value="'+p.id+'"></td><td>'+esc(p.name)+'</td><td>'+esc(p.dept)+'</td><td>'+esc(p.job)+'</td><td>'+[8,12,5,7][i]+'</td></tr>').join('')+'</tbody></table></div><section class="opp-team-section"><h4 class="opp-team-section-title">协作信息</h4><div class="opp-team-fields"><div class="opp-team-field"><label>项目角色</label><select id="oppTeamRole">'+memberRoleOptions('',false)+'</select></div><div class="opp-team-field"><label>职责</label><input id="oppTeamDuty" placeholder="例如:负责方案设计与资料维护(非必填)"></div></div></section><p class="opp-team-caption">批量添加时,所选成员将统一采用本次填写的项目角色和职责;已在团队中的成员不可重复加入。</p></div>';
foot='<button class="btn" onclick="closeModal()">取消</button><button class="btn primary" onclick="oppModule.saveTeamMemberDialog(\'add\')">确认添加</button>';
}else if(mode==='edit'&&member&&person){
const isOwner=member.isOwner;
body='<div class="opp-team-dialog"><section class="opp-team-section"><h4 class="opp-team-section-title">成员信息</h4><div class="opp-team-fields"><div class="opp-team-field"><label>成员</label><select id="oppTeamMember" '+(isOwner?'disabled':'')+'>'+teamMemberDirectory.map(p=>'<option value="'+p.id+'" '+(p.id===member.id?'selected':'')+'>'+esc(p.name)+'|'+esc(p.dept)+'|'+esc(p.job)+'</option>').join('')+'</select></div><div class="opp-team-field"><label>项目角色</label><select id="oppTeamRole" '+(isOwner?'disabled':'')+'>'+memberRoleOptions(member.role,isOwner)+'</select></div></div><div class="opp-team-field"><label>职责</label><input id="oppTeamDuty" value="'+esc(member.duty||'')+'" placeholder="例如:负责方案设计与资料维护(非必填)"></div></section><p class="opp-team-caption">'+(isOwner?'当前成员为商机负责人,成员变更请使用“移交”;此处仅允许调整职责。':'可直接替换为其他成员;若目标用户已在团队中,系统将阻止重复替换。')+'</p></div>';
foot='<button class="btn" onclick="closeModal()">取消</button><button class="btn primary" onclick="oppModule.saveTeamMemberDialog(\'edit\',\''+member.id+'\')">保存</button>';
}else if(mode==='transfer'&&member&&member.isOwner){
body='<div class="opp-team-dialog"><section class="opp-team-section"><h4 class="opp-team-section-title">负责人移交</h4><div class="opp-team-transfer-grid"><div class="opp-team-field"><label>当前负责人</label><input value="'+esc(person.name)+'" disabled></div><div class="opp-team-field"><label><span class="opp-team-required">*</span>新负责人</label><select id="oppTeamTransferTo"><option value="">请选择新负责人</option>'+teamMemberDirectory.filter(p=>p.id!==member.id).map(p=>'<option value="'+p.id+'">'+esc(p.name)+'|'+esc(p.dept)+'|'+esc(p.job)+'</option>').join('')+'</select></div><div class="opp-team-field"><label>移交说明</label><input id="oppTeamTransferNote" placeholder="填写移交原因或交接说明(非必填)"></div></div></section><label class="opp-team-check"><input id="oppTeamKeepFormer" type="checkbox" checked> 原负责人保留为团队成员,并调整为协同销售</label><p class="opp-team-caption">移交将同步更新商机负责人、团队成员关系及当前商机的记录级数据访问资格。</p></div>';
foot='<button class="btn" onclick="closeModal()">取消</button><button class="btn primary" onclick="oppModule.saveTeamMemberDialog(\'transfer\',\''+member.id+'\')">确认移交</button>';
}
modal.mask.querySelector('.m-body').innerHTML=body;modal.mask.querySelector('.m-foot').innerHTML=foot;modal.mask.classList.add('show');
}
function saveTeamMemberDialog(mode,id){
const today='2026-07-24';
if(mode==='add'){
const ids=[...document.querySelectorAll('input[name="oppTeamAdd"]:checked')].map(x=>x.value);if(!ids.length){toast('请至少选择一位成员');return}const duplicated=ids.map(x=>teamMemberById(x)).find(Boolean);if(duplicated){toast((memberInfo(duplicated.id)?.name||'该成员')+'已在团队中,不能重复加入');return}const role=document.getElementById('oppTeamRole')?.value||'',duty=document.getElementById('oppTeamDuty')?.value.trim()||'';ids.forEach(memberId=>teamMembers.push({id:memberId,role,duty,joinedAt:today,isOwner:false,source:'手工添加'}));closeModal();render();toast('已添加 '+ids.length+' 位团队成员');return;
}
const member=teamMemberById(id);if(!member)return;
if(mode==='edit'){
const nextId=member.isOwner?member.id:(document.getElementById('oppTeamMember')?.value||member.id),exists=teamMemberById(nextId);if(nextId!==member.id&&exists){toast((memberInfo(nextId)?.name||'该成员')+'已在团队中,不能重复替换');return}member.id=nextId;if(!member.isOwner)member.role=document.getElementById('oppTeamRole')?.value||'';member.duty=document.getElementById('oppTeamDuty')?.value.trim()||'';closeModal();render();toast('团队成员已更新');return;
}
if(mode==='transfer'){
const nextId=document.getElementById('oppTeamTransferTo')?.value||'',nextPerson=memberInfo(nextId);if(!nextPerson){toast('请选择新负责人');return}const keep=document.getElementById('oppTeamKeepFormer')?.checked!==false,existing=teamMemberById(nextId);member.isOwner=false;if(keep){member.role='协同销售';member.duty=member.duty||'原负责人协同跟进'}else{teamMembers.splice(teamMembers.indexOf(member),1)}if(existing){existing.isOwner=true;existing.role='商机负责人';existing.duty='商机统筹与推进'}else{teamMembers.push({id:nextId,role:'商机负责人',duty:'商机统筹与推进',joinedAt:today,isOwner:true,source:'负责人移交'})}const r=rowById(state.selectedId);if(r)r.owner=nextPerson.name;closeModal();render();toast('商机已移交给 '+nextPerson.name);return;
}
}
function removeTeamMember(id){const member=teamMemberById(id);if(!member||member.isOwner){toast('商机负责人不能通过移除成员操作删除');return}const name=memberInfo(id)?.name||'该成员';if(!confirm('确认移除团队成员“'+name+'”吗?'))return;teamMembers.splice(teamMembers.indexOf(member),1);render();toast('已移除 '+name+',其补充数据访问资格已撤销')}
function logTab(){const logs=[['2026-07-08 10:18:42','系统','商机','商机状态','推进中','已转项目','将「商机状态」从「推进中」变更为「已转项目」。'],['2026-07-08 10:18:30','系统','商机','当前工作节点','方案卡','已转项目','将「当前工作节点」从「方案卡」变更为「已转项目」。'],['2026-07-08 10:18:12','系统','推送方案','场景平台任务状态','未创建','已创建','场景平台方案任务创建成功。'],['2026-07-08 10:17:55','系统','推送方案','项目创建状态','未创建','已创建','已在项目管理模块创建项目。'],['2026-07-08 10:17:38','系统','推送方案','查重校验结果','待校验','通过','项目地址、项目区域、附件和查重规则校验通过。'],['2026-07-08 10:17:21','张三','推送方案','推送方案卡','--','广州亚东智能科技有限公司','选择方案卡进入推送方案流程。'],['2026-07-08 09:42:33','张三','相关附件','附件名称','--','金山谷现场勘察资料.rar','上传现场勘察资料,作为推送方案校验附件。'],['2026-07-08 09:37:04','张三','方案卡','方案预算','238.00 万元','418.90 万元','将「方案预算」从「238.00 万元」变更为「418.90 万元」。'],['2026-07-08 09:35:16','张三','商机','项目阶段','标前抢单','标前设计','将「项目阶段」从「标前抢单」变更为「标前设计」。'],['2026-07-08 09:31:05','张三','团队成员','方案工程师','--','李工','新增团队成员李工,角色为方案工程师。'],['2026-07-08 09:16:22','张三','客户关系','客户联系人','--','张经理','通过联系人搜索选择并关联客户联系人。'],['2026-07-08 09:15:10','张三','客户关系','主要意向客户','--','广州亚东智能科技有限公司','将主要意向客户设置为广州亚东智能科技有限公司。'],['2026-07-08 09:12:35','张三','商机','创建商机','--','金山谷智慧园区项目','创建了商机。']];const actions='<div class="a3-subtabs"><button class="'+(state.logRange==='recent'?'active':'')+'" onclick="oppModule.setLogRange(\'recent\')">最近6个月</button><button class="'+(state.logRange==='older'?'active':'')+'" onclick="oppModule.setLogRange(\'older\')">6个月之前</button></div><button class="btn" onclick="oppModule.toast(\'操作日志已导出\')">导出日志</button>';return tabHeader('操作日志',actions)+'<table class="a3-table"><thead><tr><th>操作时间</th><th>操作人</th><th>实体对象</th><th>字段名称</th><th>旧值</th><th>新值</th><th>操作说明</th></tr></thead><tbody>'+logs.map(x=>'<tr>'+x.map((v,i)=>'<td'+(i===6?' style="width:25%"':'')+'>'+v+'</td>').join('')+'</tr>').join('')+'</tbody></table><div class="a3-pagination"><span>共 33 条</span><select><option>10条/页</option><option>20条/页</option><option>50条/页</option></select><button class="active">1</button><button>2</button><button>3</button><button>›</button></div>'}
function tabPanel(k,r){return k==='overview'?overview(r):k==='customer'?customerTab():k==='scheme'?schemeTab():k==='survey'?surveyTab():k==='attachment'?attachmentTab():k==='follow'?followTab():k==='plan'?planTab():k==='team'?teamTab():logTab()}
function renderDetail(r,compact,scene=sceneForRow(r)){return '<article class="opp-detail"><div class="opp-detail-head"><div class="opp-detail-title"><div><h1>'+esc(r.name)+'</h1><div class="opp-detail-tags"><span class="opp-detail-tag">'+esc(r.industry)+'</span><span class="opp-detail-tag">'+esc(sourceLabel(r,scene))+'</span></div></div>'+(compact?'':'<button class="opp-detail-back" onclick="oppModule.returnList()">返回</button>')+'</div><div class="opp-detail-meta"><div>责任人: <span>'+esc(r.owner)+'</span></div><div>所属团队: <span>特战团队</span></div><div>所属部门: <span>粤晋战队-广东15部</span></div><div>商机状态: <span>推进中</span></div><div>项目阶段: <span>标前设计</span></div><div>招标形式: <span>ZC(政采标)</span></div><div>方案预算: <span>418.90 万元</span></div><div>主要意向客户: <span>'+esc(r.customer)+'</span></div></div>'+detailActions(r,scene)+renderStages(r)+'</div><div class="opp-detail-tabs">'+Object.keys(tabNames).map(k=>'<button class="'+(state.detailTab===k?'active':'')+'" onclick="oppModule.switchTab(\''+k+'\')">'+tabNames[k]+'</button>').join('')+'</div><div class="opp-detail-body"><div class="opp-tab-panel active">'+tabPanel(state.detailTab,r)+'</div></div></article>'}
function syncIndeterminate(root){root.querySelectorAll('input[data-partial="1"]').forEach(x=>x.indeterminate=true)}
function render(){const root=document.getElementById('oppModuleRoot');if(!root)return;if(state.screen==='detail'){const r=rowById(state.selectedId)||rows[0];root.innerHTML=renderDetail(r,false,state.detailScene)+'<div class="opp-toast" id="oppToast"></div>';if(document.getElementById('myOpportunity')?.classList.contains('active'))setBreadcrumb(true);return}const data=filteredRows();root.innerHTML='<div class="opp-app density-'+state.density+' '+(state.mode==='split'?'split-mode':'')+'">'+renderTop()+'<div class="opp-control-stack">'+renderStats()+renderToolbar()+'</div><main class="opp-main">'+(state.mode==='split'?renderSplit(data):state.mode==='board'?renderBoard():renderList(data))+'</main></div><div class="opp-toast" id="oppToast"></div>';syncIndeterminate(root);if(document.getElementById('myOpportunity')?.classList.contains('active'))setBreadcrumb(false)}
function setBreadcrumb(detail){const bar=document.querySelector('.location-bar');if(!bar)return;const r=rowById(state.selectedId)||rows[0],label=workspaceTitle(detail?state.returnWorkspace:state.workspace),listNode=detail?'<button class="crumb-btn" onclick="oppModule.returnList()">'+esc(label)+'</button>':'<span class="crumb-current">'+esc(label)+'</span>';bar.innerHTML='<span class="crumb-parent">商机</span><span class="crumb-sep">›</span>'+listNode+(detail?'<span class="crumb-sep">›</span><span class="crumb-current">'+esc(r.name)+'</span>':'')}
function renderSystemBreadcrumb(label){const bar=document.querySelector('.location-bar');if(!bar)return;const parts=String(label||'首页').split('/').map(x=>x.trim()).filter(Boolean);bar.innerHTML=parts.map((p,i)=>{const value=esc(p);if(i===parts.length-1)return '<span class="crumb-current">'+value+'</span>';if(i===0&&p==='商机')return '<button class="crumb-btn" onclick="setActivePage(\'myOpportunity\',\'商机\')">'+value+'</button><span class="crumb-sep">›</span>';return '<span class="crumb-parent">'+value+'</span><span class="crumb-sep">›</span>'}).join('')}
function pushScenarioLabel(v){return {missing:'资料不完整',duplicate:'查重冲突',success:'顺利推送'}[v]||'顺利推送'}
function pushSteps(states){const names=['地址/区域校验','附件校验','查重校验','创建项目','推送场景平台任务'];return '<div class="push-steps">'+names.map((n,i)=>{const st=states[i]||'wait',icon=st==='done'?'✓':(st==='fail'||st==='warn'?'!':(st==='loading'?'…':i+1));return '<div class="push-step '+st+'"><i>'+icon+'</i>'+n+'</div>'}).join('')+'</div>'}
function pushModalBody(){
if(state.pushStep==='select'){
return '<div class="bygpt-push"><div class="bygpt-push-intro">先选择要推送的客户方案卡,再逐步执行系统校验与推送。</div><div class="bygpt-push-tabs">'+[['missing','资料不完整'],['duplicate','查重冲突'],['success','顺利推送']].map(x=>'<button class="'+(state.pushScenario===x[0]?'active':'')+'" onclick="oppModule.setPushScenario(\''+x[0]+'\')">'+x[1]+'</button>').join('')+'</div><div class="push-scheme-list"><label class="push-scheme-option '+(state.pushScheme===1?'active':'')+'" onclick="oppModule.selectPushScheme(1)"><input type="radio" name="pushScheme" '+(state.pushScheme===1?'checked':'')+'><div><h4>广州亚东智能科技有限公司|方案卡 V2</h4><p>项目阶段:标前设计 招标形式:GC(工程) 预算金额:238.00 万元<br>资料状态:项目地址/区域已明确,相关附件已上传。</p></div><span class="push-scheme-status">可推送</span></label><label class="push-scheme-option '+(state.pushScheme===2?'active':'')+'" onclick="oppModule.selectPushScheme(2)"><input type="radio" name="pushScheme" '+(state.pushScheme===2?'checked':'')+'><div><h4>广东轨交通信工程有限公司|草稿方案卡</h4><p>项目阶段:BC设计 招标形式:BC(EPC总分包) 预算金额:待确认<br>资料状态:预算和需求未完整确认。</p></div><span class="push-scheme-status draft">草稿</span></label></div><p class="push-pick-tip">推送方案前必须先选择一张客户方案卡;后续校验、创建项目和场景平台任务都围绕该方案卡执行。</p></div>';
}
if(state.pushStep==='checking'){
const states=Array.from({length:5},(_,i)=>i<state.pushProgress?'done':i===state.pushProgress?'loading':'wait');
return pushSteps(states)+'<div class="push-result"><h4>正在执行:'+['地址/区域校验','附件校验','查重校验','创建项目','推送场景平台任务'][Math.min(state.pushProgress,4)]+'</h4><p>系统按步骤自动加载校验结果,当前方案卡:'+(state.pushScheme===1?'广州亚东智能科技有限公司|方案卡 V2':'广东轨交通信工程有限公司|草稿方案卡')+'。</p></div>';
}
if(state.pushStep==='forced')return pushSteps(['done','done','warn','done','done'])+'<div class="push-result"><h4>已强推项目,等待处理冲突</h4><p>本商机已创建项目并推送场景平台任务;新旧项目均进入【处理冲突】节点,需销售一键拉群线下协调。</p><div class="mini-actions"><button class="btn opp-green-btn" onclick="oppModule.openModal(\'一键拉群\')">一键拉群</button></div></div>';
if(state.pushScenario==='missing'||state.pushScheme===2)return pushSteps(['fail','','','',''])+'<div class="push-result"><h4>校验未通过,暂不能推送方案</h4><p>需明确项目地址、项目区域,并保证相关附件栏至少上传 1 个附件。</p><ul class="push-missing"><li>甲方单位</li><li>项目地址</li><li>方案需求说明</li><li>现场资料附件</li></ul></div>';
if(state.pushScenario==='duplicate')return pushSteps(['done','done','warn','',''])+'<div class="push-result"><h4>查重发现疑似冲突项目</h4><p>如销售强推,本商机创建的项目与被冲突旧项目都会进入【处理冲突】节点,需要通过【一键拉群】线下协商。</p><div class="push-dup-grid"><b>冲突项目</b><span>番禺区第二中学报告厅改造项目</span><b>首次推送</b><span>2026-06-24 14:50</span><b>跟进销售</b><span>王秀芬</span><b>所属部门</b><span>广东10部</span></div></div>';
return pushSteps(['done','done','done','done','done'])+'<div class="push-result"><h4>推送方案成功</h4><p>项目已创建,场景平台任务已创建,商机状态自动更新为【已转项目】。后续方案、报价、清单在项目管理模块完成。</p></div>';
}
function updatePushModal(){const mask=document.getElementById('modal');if(!mask||!mask.classList.contains('show'))return;mask.querySelector('.m-body').innerHTML=pushModalBody();let foot='<div class="opp-modal-foot">';if(state.pushStep==='select')foot+='<button class="btn" onclick="closeModal()">取消</button><button class="btn primary" onclick="oppModule.startPushValidation()">下一步:开始校验</button>';else if(state.pushStep==='checking')foot+='<button class="btn" onclick="oppModule.resetPushFlow()">返回选择方案卡</button><button class="btn primary" disabled>校验中...</button>';else if(state.pushStep==='forced')foot+='<button class="btn primary" onclick="closeModal()">完成</button>';else if(state.pushScenario==='duplicate')foot+='<button class="btn" onclick="closeModal()">取消推送</button><button class="btn primary" onclick="oppModule.forcePush()">强推项目</button>';else if(state.pushScenario==='missing'||state.pushScheme===2)foot+='<button class="btn" onclick="oppModule.resetPushFlow()">返回选择方案卡</button><button class="btn primary" onclick="closeModal();oppModule.toast(\'请先补充方案卡资料\')">返回补充</button>';else foot+='<button class="btn primary" onclick="closeModal()">完成</button>';foot+='</div>';mask.querySelector('.m-foot').innerHTML=foot}
function setPushScenario(v){state.pushScenario=v;state.pushStep='select';state.pushProgress=0;updatePushModal()}
function selectPushScheme(v){state.pushScheme=v;state.pushStep='select';state.pushProgress=0;updatePushModal()}
function startPushValidation(){state.pushStep='checking';state.pushProgress=0;updatePushModal();clearInterval(state.pushTimer);state.pushTimer=setInterval(()=>{if((state.pushScenario==='missing'||state.pushScheme===2)&&state.pushProgress===0){clearInterval(state.pushTimer);state.pushStep='result';updatePushModal();return}if(state.pushScenario==='duplicate'&&state.pushProgress===2){clearInterval(state.pushTimer);state.pushStep='result';updatePushModal();return}state.pushProgress+=1;if(state.pushProgress>=5){clearInterval(state.pushTimer);state.pushStep='result'}updatePushModal()},520)}
function resetPushFlow(){clearInterval(state.pushTimer);state.pushStep='select';state.pushProgress=0;updatePushModal()}
function forcePush(){clearInterval(state.pushTimer);state.pushStep='forced';updatePushModal()}
function clearCloseError(){const wrap=document.getElementById('oppCloseCheckWrap');if(wrap){wrap.classList.remove('invalid');const err=document.getElementById('oppCloseError');if(err)err.style.display='none'}}
function schemeTemplate(id){return schemeTemplates.find(x=>x.id===id)||schemeTemplates[0]}
function schemeTemplatePickerBody(mode){const selected=state.pendingSchemeTemplateId;return '<div class="opp-template-picker"><p class="opp-template-picker-intro">'+(mode==='switch'?'请选择要切换的方案卡模板。页面仅展示当前用户满足权限条件、且处于发布中的模板。':'请选择本次方案卡使用的填写模板。公司通用方案卡默认推荐,仍可切换为其他有权使用的模板。')+'</p><div class="opp-template-options">'+schemeTemplates.map(t=>'<label class="opp-template-option '+(selected===t.id?'active':'')+'" data-scheme-template-option="'+t.id+'"><input type="radio" name="schemeTemplate" value="'+t.id+'" '+(selected===t.id?'checked':'')+' onchange="oppModule.selectSchemeTemplate(\''+t.id+'\')"><span class="opp-template-main"><span class="opp-template-title"><b>'+esc(t.name)+'</b>'+(t.recommended?'<i class="opp-template-recommend">公司推荐</i>':'')+'</span><p>'+esc(t.description)+'</p><span class="opp-template-permission">适用范围:'+esc(t.scope)+'</span></span><span class="opp-template-version">'+esc(t.version)+' · 已发布</span></label>').join('')+'</div></div>'}
function selectSchemeTemplate(id){state.pendingSchemeTemplateId=id;document.querySelectorAll('[data-scheme-template-option]').forEach(x=>x.classList.toggle('active',x.dataset.schemeTemplateOption===id));document.querySelectorAll('input[name="schemeTemplate"]').forEach(x=>x.checked=x.value===id)}
function openSchemeTemplatePicker(mode='create'){
if(mode==='create')state.schemeEditingCardId=0;
state.pendingSchemeTemplateId=mode==='switch'?state.schemeEditTemplateId:'scheme-company';
const mask=document.getElementById('modal'),panel=mask.querySelector('.modal');
panel.classList.remove('opp-modal-wide','opp-modal-small','opp-push-dialog');panel.classList.add('opp-modal-medium');
document.getElementById('modalTitle').textContent=mode==='switch'?'切换方案卡模板':'选择方案卡模板';
mask.querySelector('.m-body').innerHTML=schemeTemplatePickerBody(mode);
mask.querySelector('.m-foot').innerHTML='<div class="opp-modal-foot"><button class="btn" onclick="'+(mode==='switch'?'oppModule.cancelSchemeTemplateSwitch()':'closeModal()')+'">取消</button><button class="btn primary" onclick="oppModule.confirmSchemeTemplateChoice(\''+mode+'\')">'+(mode==='switch'?'下一步':'下一步:填写方案卡')+'</button></div>';
mask.classList.add('show');
}
function confirmSchemeTemplateChoice(mode){
if(mode==='create'){state.schemeTemplateId=state.pendingSchemeTemplateId;openSchemeForm('新增方案卡');return}
if(state.pendingSchemeTemplateId===state.schemeEditTemplateId){openSchemeForm('编辑方案卡');return}
openSchemeSwitchWarning();
}
function openSchemeSwitchWarning(){
const from=schemeTemplate(state.schemeEditTemplateId),to=schemeTemplate(state.pendingSchemeTemplateId),mask=document.getElementById('modal'),panel=mask.querySelector('.modal');
panel.classList.remove('opp-modal-wide','opp-modal-medium','opp-push-dialog');panel.classList.add('opp-modal-small');
document.getElementById('modalTitle').textContent='确认切换模板';
mask.querySelector('.m-body').innerHTML='<div class="opp-template-warning"><p>即将从“<b>'+esc(from.name)+'</b>”切换为“<b>'+esc(to.name)+'</b>”。</p><div class="opp-business-note"><strong>请注意:</strong>切换模板后,系统将自动带入可匹配字段的数据;无法匹配的原字段将不再保留,新模板字段可能需要重新填写。切换结果仅在保存后生效,未保存退出不会影响原方案卡。</div></div>';
mask.querySelector('.m-foot').innerHTML='<div class="opp-modal-foot"><button class="btn" onclick="oppModule.openSchemeForm(\'编辑方案卡\')">取消</button><button class="btn primary" onclick="oppModule.applySchemeTemplateSwitch()">确认切换</button></div>';
}
function applySchemeTemplateSwitch(){state.schemeEditTemplateId=state.pendingSchemeTemplateId;openSchemeForm('编辑方案卡')}
function cancelSchemeTemplateSwitch(){openSchemeForm('编辑方案卡')}
function openSchemeForm(type){bypassSchemeTemplatePicker=true;openModal(type);bypassSchemeTemplatePicker=false}
function openSchemeCardEdit(cardId){
state.schemeEditingCardId=cardId;
state.schemeSavedTemplateId=state.schemeCardTemplates[cardId]||'scheme-company';
state.schemeEditTemplateId=state.schemeSavedTemplateId;
openModal('编辑方案卡');
}
function schemeCompanyForm(){return '<div class="opp-modal-form"><div class="opp-form-section">方案卡内容</div>'+
'<label>*客户名称</label><select><option '+(state.schemeEditingCardId===2?'':'selected')+'>广州亚东智能科技有限公司</option><option '+(state.schemeEditingCardId===2?'selected':'')+'>广东轨交通信工程有限公司</option></select><label>*客户类型</label><select><option '+(state.schemeEditingCardId===2?'selected':'')+'>工程商</option><option '+(state.schemeEditingCardId===2?'':'selected')+'>总包</option><option>投标公司</option><option>集成商</option><option>设计院</option></select>'+
'<label>战略VIP客户等级</label><select><option>--</option><option>战略A类</option><option>战略B类</option><option>战略C类</option></select><label>已成交VIP客户等级</label><select><option>--</option><option>VIP1</option><option>VIP2</option><option>VIP3</option></select>'+
'<label>客户联系人</label><div class="opp-inline-action"><select><option>张经理</option><option>李工</option></select><button class="btn">搜索</button></div><label>客户联系电话</label><input value="138******12" disabled>'+
'<label>*项目阶段</label><select><option>设计</option><option>改单</option><option>投标</option><option>抢标</option></select><label>*招标形式</label><select><option>GC(工程)</option><option>ZC(政采标)</option><option>BC(EPC总分包)</option><option>YB(邀标)</option></select>'+
'<label>是否有看现场</label><select><option>是</option><option>否</option><option>待确认</option></select><label>是否与客户电话沟通</label><select><option>是</option><option>否</option><option>待确认</option></select>'+
'<label>现场勘察资料</label><div class="opp-inline-action"><select><option>请选择</option><option>金山谷现场勘察资料.rar</option></select><button class="btn">添加</button></div><label>是否百万级项目</label><select><option>是</option><option>否</option></select>'+
'<label>方案预算是否明确</label><select><option>是</option><option>否</option><option>待确认</option></select><label>方案预算金额</label><input value="238.00 万元">' +
'<label>预计成交概率</label><input value="60%"><label>预计投标时间</label><input type="date">' +
'<label>设计配置</label><select><option>高配</option><option>标准配置</option><option>按清单配置</option><option>低配</option><option>定制</option></select><label>品牌要求</label><input value="ITC、TS系列">' +
'<label>是否可换品牌</label><select><option>否</option><option>是</option><option>待确认</option></select><label>招标编号</label><input placeholder="投标/抢标阶段填写">' +
'<label>标底方</label><input placeholder="投标/抢标阶段填写"><span></span>'+
'<label>*客户需求描述</label><textarea class="full opp-long-text">根据现有清单匹配产品参数并提供调整建议。</textarea><label>*业务需求</label><textarea class="full opp-long-text">中配,主要从参数基础上控制配置成本,支持后续报价。</textarea>'+
'<label>对方要求的先进性及前瞻性的要求</label><textarea class="full opp-long-text">--</textarea><label>清单参数(是否是我司产品参数、工程师需要确认,是否有品牌?)</label><textarea class="full opp-long-text">是否是我司产品参数、工程师需要确认,是否有品牌。</textarea>'+
'<label>清单参数是否能改</label><textarea class="full opp-long-text">可改,按深化方案处理。</textarea><label>客户特殊要求(补充说明、备注说明)</label><textarea class="full opp-long-text">--</textarea>'+
'<label>方案卡填写时间</label><input type="datetime-local"><label>期望方案设计完成时间</label><input type="datetime-local"></div>'}
function schemeTeamForm(){return '<div class="opp-modal-form"><div class="opp-form-section">方案卡内容</div><label>*客户名称</label><select><option>广州亚东智能科技有限公司</option><option>广东轨交通信工程有限公司</option></select><label>*项目阶段</label><select><option>设计</option><option>改单</option><option>投标</option><option>抢标</option></select><label>*招标形式</label><select><option>GC(工程)</option><option>ZC(政采标)</option><option>BC(EPC总分包)</option><option>YB(邀标)</option></select><label>设计配置</label><select><option>高配</option><option>标准配置</option><option>按清单配置</option><option>低配</option><option>定制</option></select><label>部门协同要求</label><textarea class="full opp-long-text" placeholder="请输入当前模板要求的协同信息"></textarea></div>'}
function schemeFormBody(type){const id=type==='编辑方案卡'?state.schemeEditTemplateId:state.schemeTemplateId,t=schemeTemplate(id);return '<div class="opp-scheme-form-shell"><div class="opp-scheme-template-head"><div><b>'+esc(t.name)+' <span class="opp-template-recommend" style="display:'+(t.recommended?'inline-flex':'none')+'">公司推荐</span></b><span>模板版本 '+esc(t.version)+' · '+esc(t.scope)+'</span></div>'+(type==='编辑方案卡'?'<button class="btn" type="button" onclick="oppModule.openSchemeTemplatePicker(\'switch\')">切换模板</button>':'')+'</div>'+(id==='scheme-team'?schemeTeamForm():schemeCompanyForm())+'</div>'}
function previewFieldControl(field){const name=field[0],type=field[1],required=field[3],label='<label>'+(required?'*':'')+esc(name)+'</label>';if(type==='多行文本')return label+'<textarea class="full opp-long-text" placeholder="请输入"></textarea>';if(type==='是/否')return label+'<select><option>请选择</option><option>是</option><option>否</option></select>';if(type.includes('下拉'))return label+'<select><option>请选择</option></select>';if(type==='附件上传')return label+'<div class="opp-inline-action"><input placeholder="请选择附件" disabled><button class="btn">添加</button></div>';return label+'<input placeholder="请输入" '+(type==='自动回填'?'disabled':'')+'>'}
function schemePreviewBody(name,version,fields){const t=schemeTemplates.find(x=>x.name===name)||{name,version:version||'--',scope:'按模板权限条件适用',recommended:name==='公司通用方案卡'};return '<div class="opp-scheme-form-shell opp-scheme-preview-shell"><div class="opp-scheme-template-head"><div><b>'+esc(t.name)+' '+(t.recommended?'<span class="opp-template-recommend">公司推荐</span>':'')+'</b><span>模板版本 '+esc(version||t.version||'--')+' · '+esc(t.scope||'按模板权限条件适用')+'</span></div></div><div class="opp-modal-form"><div class="opp-form-section">方案卡内容</div>'+fields.filter(x=>x[2]).map(previewFieldControl).join('')+'</div></div>'}
function modalBody(type){
if(type==='推进商机阶段'){const current=rowById(state.selectedId);return '<div class="opp-modal-form"><div class="opp-form-section">推进商机阶段</div><label>当前节点</label><input value="'+esc(current?.node||'--')+'" disabled><label>目标节点</label><input value="'+esc(state.pendingStage||'已转项目')+'" disabled><label>推进说明</label><textarea class="full" placeholder="填写本次推进依据、已完成事项及下一步计划"></textarea></div>'}
if(type==='新建商机'||type==='编辑商机')return '<div class="opp-modal-form"><div class="opp-form-section">项目基础信息</div><label>*商机名称</label><input value="'+(type==='编辑商机'?'金山谷智慧园区项目':'')+'"><label>*商机来源</label><select><option>线索转入</option><option>线下挖掘</option><option>客户转介绍</option></select><label>*商机行业</label><select><option>智慧园区</option><option>普教</option><option>高校(职校)</option><option>医院</option><option>文旅</option><option>轨道交通</option></select><label>*商机类型</label><select><option>新建</option><option>改造</option><option>升级</option></select><label>*项目阶段</label><select><option>标前设计</option><option>标前改单</option><option>投标阶段</option><option>抢标阶段</option><option>BC设计</option></select><label>*招标形式</label><select><option>ZC(政采)</option><option>GC(工程)</option><option>BC(EPC总分包)</option><option>YB(邀标)</option></select><label>*项目详细地址</label><input value="广州市番禺区"><label>*项目区域</label><input value="广东省 / 广州市 / 番禺区"><label>*项目属地</label><select><option>本地项目</option><option>异地项目</option></select><label>项目金额</label><input value="418.90 万元"><div class="opp-form-section">客户信息</div><label>*甲方是否明确</label><select><option>是</option><option>否</option></select><label>甲方名称</label><input value="招商局物业管理广州分公司"><label>*关联客户</label><input value="广州亚东智能科技有限公司"><label>*客户类型</label><select><option>总包</option><option>工程商</option><option>投资方</option><option>设计院</option><option>集成商</option><option>投标公司</option></select><label>客户联系人</label><input value="张经理"><label>*是否主要客户</label><select><option>是</option><option>否</option></select></div>';
if(type==='写跟进')return '<div class="opp-modal-form"><label>*跟进内容</label><textarea class="full opp-long-text" placeholder="请详细记录本次跟进的沟通内容、阶段判断、成果和下一步动作"></textarea><label>*跟进时间</label><input type="datetime-local"><label>*跟进方式</label><select><option>电话沟通</option><option>微信沟通</option><option>上门拜访</option><option>邮件沟通</option><option>会议沟通</option><option>其他</option></select><label>*客户名称</label><select><option>广州亚东智能科技有限公司</option><option>广东轨交通信工程有限公司</option></select><label>客户联系人</label><div class="opp-inline-action"><select><option>张经理</option><option>李工</option></select><button class="btn">搜索</button></div><label>客户联系电话</label><input value="138****6612" disabled><label>跟进结果标签</label><select><option>关系维护</option><option>需求确认</option><option>方案推进</option><option>资料采集</option><option>预算沟通</option><option>现场勘察</option></select><label>计划下次跟进时间</label><input type="datetime-local"><label>附件</label><div class="opp-upload-box full"><b>点击或拖拽文件至此处上传</b><span>支持常用文档、图片、音视频格式,最多上传10个附件</span></div></div>';
if(type==='督办')return '<div class="opp-compact-layout"><p>确认向商机负责人“张三”发送督办提醒吗?</p><p>确认后,系统将向该负责人发送督办提醒,并生成一条待办事项。</p><div class="opp-inline-field full"><label>备注(选填)</label><textarea maxlength="100" placeholder="请输入需要负责人重点关注的事项"></textarea></div><div class="opp-business-note">若未填写备注,系统将发送默认督办提醒文案。</div></div>';
if(type==='新增方案卡'||type==='编辑方案卡')return schemeFormBody(type);
if(type==='添加关联客户')return '<div class="opp-customer-picker"><div class="opp-search-bar"><input placeholder="客户名称 / 联系人 / 手机号"><select><option>全部客户类型</option><option>集成商</option><option>总包</option><option>设计院</option><option>工程商</option></select><button class="btn primary">查询</button><button class="btn">重置</button></div><div class="opp-picker-title"><b>客户搜索结果</b><button class="btn" onclick="oppModule.openModal(\'快速创建客户\')">+ 快速创建客户</button></div><div class="opp-select-table-wrap"><table class="opp-select-table"><thead><tr><th>选择</th><th>客户名称</th><th>客户类型</th><th>地区</th><th>联系人</th><th>联系电话</th><th>负责人</th></tr></thead><tbody><tr><td><input type="radio" name="customerPick" checked></td><td>广州新联智能工程有限公司</td><td>集成商</td><td>广东·广州</td><td>陈经理</td><td>138****6612</td><td>张三</td></tr><tr><td><input type="radio" name="customerPick"></td><td>深圳华创系统集成有限公司</td><td>总包</td><td>广东·深圳</td><td>李工</td><td>136****2208</td><td>李四</td></tr><tr><td><input type="radio" name="customerPick"></td><td>广州新联智能工程有限公司</td><td>集成商</td><td>广东·广州</td><td>陈经理</td><td>138****6612</td><td>张三</td></tr></tbody></table></div><div class="opp-picker-pagination">共 30 条 20条/页 ‹ <b>1</b> 2 3 4 5 ›</div><div class="opp-modal-form opp-modal-form--relation"><label>*客户类型</label><select><option>投标公司</option><option>工程商</option><option>总包</option><option>集成商</option><option>设计院</option></select><label>客户联系人</label><select><option>陈经理 138****6612</option><option>李工 136****2208</option></select><label>是否主要意向客户</label><select><option>是</option><option>否</option></select></div></div>';
if(type==='快速创建客户')return '<div class="opp-modal-form"><label>*客户名称</label><input><label>*客户类型</label><select><option>集成商</option><option>总包</option><option>设计院</option><option>工程商</option></select><label>*地区</label><div class="opp-inline-selects"><select><option>广东省</option></select><select><option>广州市</option></select><select><option>番禺区</option></select></div><label>联系人</label><input><label>联系电话</label><input><label>负责人</label><select><option>张三</option><option>李四</option></select></div>';
if(type==='推送方案')return pushModalBody();
if(type==='暂缓商机')return `<div class="opp-compact-layout"><div class="opp-compact-top"><div class="opp-inline-field"><label>*暂缓原因</label><select><option>预算冻结</option><option>客户暂缓</option><option>等待招标</option><option>项目延期</option><option>其他</option></select></div><div class="opp-inline-field"><label>*预计重启日期</label><input type="date"></div></div><div class="opp-inline-field full"><label>补充说明</label><textarea placeholder="请概要说明当前商机暂缓推进的客观情况"></textarea></div><div class="opp-business-note">暂缓商机用于保留销售对销售机会的归属保护,不进入公海,也不会被其他销售领取。仅个人任务暂缓用途。</div></div>`;
if(type==='抛公海')return '<div class="opp-compact-layout"><div class="opp-compact-top"><div class="opp-inline-field"><label>*进入公海原因</label><select><option>长期未有效跟进</option><option>客户暂无预算</option><option>项目暂停或延期</option><option>介入时机不成熟</option><option>其他</option></select></div><div class="opp-inline-field"><label>*进入公海范围</label><select><option>所属部门公海</option><option>公司公海</option></select></div></div><div class="opp-inline-field full"><label>补充说明</label><textarea placeholder="请输入抛入公海的补充说明"></textarea></div><div class="opp-business-note">抛入公海后,当前负责人将失去该商机的跟进权限;历史跟进记录、客户、方案卡等数据将完整保留。</div></div>';
if(type==='添加团队成员')return '<div class="opp-business-note">请通过团队成员页签或详情主操作区的“添加团队成员”入口维护成员、项目角色与职责。团队成员不在此处配置功能权限或数据权限。</div>';
if(type==='关闭商机')return `<div class="opp-close-layout"><div class="opp-business-note opp-business-note--danger">关闭后商机将停止推进,常规跟进、节点推进和方案推送等操作将被限制。历史数据不会删除。</div><div class="opp-close-fields"><div class="opp-inline-field"><label>*关闭原因</label><select><option>重复商机</option><option>项目取消</option><option>客户放弃</option><option>竞争失败</option><option>无效商机</option><option>其他</option></select></div><div class="opp-inline-field"><label>补充说明</label><textarea placeholder="请概要说明关闭商机的主要原因、客户反馈及后续处理办法"></textarea></div><div class="opp-confirm-check" id="oppCloseCheckWrap"><input type="checkbox" id="oppCloseConfirm" onchange="oppModule.clearCloseError()"><label for="oppCloseConfirm">我已确认关闭当前商机,并知晓关闭后需要管理权限才能重新开启。</label><div class="opp-confirm-error" id="oppCloseError">请先确认关闭商机</div></div></div></div>`;
if(type==='新增现场勘察')return `<div class="opp-modal-form"><div class="opp-form-section">申请信息</div><label>*申请方式</label><select><option>工程申请</option><option>线下申请</option><option>其他外部流程</option></select><label>*勘察次数</label><select><option>第一次</option><option>第二次</option><option>第三次</option><option>其他</option></select><label>*预计看场日期</label><input type="date"><label>申请单号</label><input placeholder="请输入(若有)"><div class="opp-form-section">工程师选择</div><div class="opp-search-bar full"><input placeholder="搜索姓名 / 部门 / 职位"><select><option>全部部门</option><option>销售中心</option><option>方案支持部</option><option>市场服务部</option><option>商务部</option></select><button class="btn primary">查询</button><button class="btn">重置</button></div><div class="opp-select-table-wrap full"><table class="opp-select-table"><thead><tr><th>选择</th><th>姓名</th><th>所属部门</th></tr></thead><tbody><tr><td><input type="radio" name="surveyEngineer" checked></td><td>李四</td><td>销售中心</td></tr><tr><td><input type="radio" name="surveyEngineer"></td><td>王五</td><td>方案支持部</td></tr><tr><td><input type="radio" name="surveyEngineer"></td><td>赵六</td><td>市场服务部</td></tr><tr><td><input type="radio" name="surveyEngineer"></td><td>陈七</td><td>商务部</td></tr></tbody></table></div><div class="opp-form-section">勘察结果</div><label>*勘察补充说明</label><textarea class="full opp-long-text" placeholder="请概要填写现场环境、客户需求、安装条件、设备数量、特殊要求或工程师建议等内容"></textarea><label>相关附件</label><div class="opp-upload-box full"><b>点击或拖拽文件至该处上传</b><span>支持文档、表格、图片、音视频及压缩文件,最多上传10个附件</span></div><div class="opp-business-note">本页面仅用于录入现场勘察完成后的结果数据,不承担申请、派单或审批流程。</div></div>`;
if(type==='上传附件')return '<div class="opp-modal-form"><label>*附件分类</label><select><option>现场勘察</option><option>客户资料</option><option>方案资料</option><option>招投标资料</option><option>其他</option></select><label>附件说明</label><input><label>附件</label><div class="opp-upload-box">点击或拖拽文件至此处上传</div></div>';
if(type==='新建计划')return '<div class="opp-modal-form"><label>*计划事项</label><input><label>*负责人</label><select><option>张三</option><option>李四</option></select><label>*计划时间</label><input type="datetime-local"><label>提醒时间</label><select><option>提前1天</option><option>提前2小时</option></select><label>计划说明</label><textarea class="full"></textarea></div>';
if(type==='领取商机')return '<div class="opp-modal-form"><label>领取后负责人</label><input value="Panyu10109" disabled><label>领取说明</label><textarea class="full" placeholder="可填写领取原因或后续跟进计划"></textarea><div class="opp-business-note">领取后该商机将进入“我负责的销售机会”,原有客户、跟进、方案卡及附件资料保持不变。</div></div>';
if(type==='关注商机')return '<div class="opp-modal-form"><label>关注范围</label><input value="当前商机" disabled><label>关注说明</label><textarea class="full" placeholder="可填写关注原因"></textarea></div>';
if(type==='取消关注')return '<div class="opp-compact-layout"><p>确认取消关注当前商机吗?</p><div class="opp-business-note">取消关注后,该商机将不再出现在“我的关注”中,商机原有数据不受影响。</div></div>';
if(type==='一键拉群')return '<div class="opp-business-note"><b>拉群用途</b><br>用于查重冲突、强推项目或关键节点协调。系统带出本商机团队成员、冲突项目负责人及相关管理角色。</div><div class="opp-check-grid"><label><input type="checkbox" checked> 张三|销售负责人</label><label><input type="checkbox" checked> 李工|方案工程师</label><label><input type="checkbox" checked> 王秀芬|冲突项目销售</label><label><input type="checkbox" checked> 李总|销售总监</label></div>';
if(type==='分配商机')return '<div class="opp-modal-form"><label>*所属部门</label><select><option>粤晋战队-广东15部</option><option>轨交战队-广东8部</option><option>文旅战队-广东12部</option></select><label>*负责人</label><select><option>张三</option><option>李四</option><option>陈晨</option><option>吴迪</option></select><label>分配说明</label><textarea class="full"></textarea></div>';
return '<div class="opp-modal-form"><label>操作说明</label><textarea class="full"></textarea></div>';
}
function decorateRequired(mask){
mask.querySelectorAll('.m-body label').forEach(label=>{
const node=[...label.childNodes].find(n=>n.nodeType===3&&n.nodeValue.trim().startsWith('*'));
if(!node)return;
node.nodeValue=node.nodeValue.replace(/^\s*\*/,'');
const star=document.createElement('span');star.className='opp-required-star';star.textContent='*';
label.insertBefore(star,label.firstChild);
});
}
function openModal(type){
if(type==='新增方案卡'&&!bypassSchemeTemplatePicker){openSchemeTemplatePicker('create');return}
if(type==='编辑方案卡'&&!bypassSchemeTemplatePicker)state.schemeEditTemplateId=state.schemeSavedTemplateId;
const mask=document.getElementById('modal'),panel=mask.querySelector('.modal');
const titleNode=document.getElementById('modalTitle');
titleNode.textContent=type==='添加关联客户'?'新增关联客户':type==='抛公海'?'抛入商机公海':type==='新增现场勘察'?'新增现场勘察记录':type==='督办'?'发起督办':type;
panel.classList.remove('opp-modal-wide','opp-modal-medium','opp-modal-small','opp-push-dialog');
if(type==='推送方案'){titleNode.innerHTML='推送方案 <span class="bygpt-push-badge">先选方案卡</span>';panel.classList.add('opp-push-dialog')}
const wide=['新建商机','编辑商机','新增方案卡','编辑方案卡','添加关联客户','推送方案','写跟进'];
const medium=['快速创建客户','新增现场勘察','上传附件','新建计划','添加团队成员'];
panel.classList.add(wide.includes(type)?'opp-modal-wide':medium.includes(type)?'opp-modal-medium':'opp-modal-small');
if(type==='推送方案'){state.pushStep='select';state.pushProgress=0;state.pushScheme=1;state.pushScenario='success';clearInterval(state.pushTimer)}
mask.querySelector('.m-body').innerHTML=modalBody(type);
let foot='<div class="opp-modal-foot"><button class="btn" onclick="closeModal()">取消</button>';
if(type==='写跟进')foot+='<button class="btn" onclick="oppModule.commit(\''+type+'\')">保存草稿</button><button class="btn primary" onclick="oppModule.commit(\''+type+'\')">提交</button>';
else if(type==='推送方案')foot+='<button class="btn primary" onclick="oppModule.startPushValidation()">下一步:开始校验</button>';
else foot+='<button class="btn primary" onclick="oppModule.commit(\''+type+'\')">确认</button>';
foot+='</div>';
mask.querySelector('.m-foot').innerHTML=foot;
decorateRequired(mask);
mask.classList.add('show');
if(type==='推送方案')updatePushModal();
}
function commit(type){
if(type==='关闭商机'){
const box=document.getElementById('oppCloseConfirm'),wrap=document.getElementById('oppCloseCheckWrap'),err=document.getElementById('oppCloseError');
if(!box||!box.checked){if(wrap){wrap.classList.remove('shake');void wrap.offsetWidth;wrap.classList.add('invalid','shake')}if(err)err.style.display='block';box?.focus();return}
}
if(type==='推送方案'){startPushValidation();return}
if(type==='编辑方案卡'){
state.schemeSavedTemplateId=state.schemeEditTemplateId;
if(state.schemeEditingCardId)state.schemeCardTemplates[state.schemeEditingCardId]=state.schemeEditTemplateId;
}
if(type==='推进商机阶段'){
const r=rowById(state.selectedId),target=state.pendingStage;
if(r&&target&&r.node!==target){r.node=target;r.next=target;resetBoardLoad();closeModal();render();toast('商机已推进至“'+target+'”,阶段汇总已刷新');return}
}
if(state.screen==='detail'&&(type==='关注商机'||type==='取消关注')){
type==='关注商机'?state.followedOpps.add(state.selectedId):state.followedOpps.delete(state.selectedId);
closeModal();render();toast(type==='关注商机'?'关注成功':'已取消关注');return;
}
closeModal();toast(type+'已提交')
}
function toast(t){let n=document.getElementById('oppToast');if(!n){n=document.createElement('div');n.id='oppToast';n.className='opp-toast';document.body.appendChild(n)}n.textContent=t;n.classList.add('show');clearTimeout(toastTimer);toastTimer=setTimeout(()=>n.classList.remove('show'),1700)}
function setMode(v){state.mode=v;state.screen='list';state.popover=null;state.selected.clear();render()}
function toggleMenu(e){e.stopPropagation();if(state.workspace!=='sales')return;state.menuOpen=!state.menuOpen;state.customMenuId=null;state.renamingSearchId=null;state.popover=null;render()}
function menuTab(v){if(v==='search'&&state.menuTab!=='search')state.editingSearchId=null;state.menuTab=v;state.customMenuId=null;state.renamingSearchId=null;render()}
function switchView(v){const valid=viewItems.includes(v)||v==='custom'||!!savedSearch(v);state.selected.clear();state.workspace='sales';state.view=valid?v:'mine';state.menuOpen=false;state.customMenuId=null;state.renamingSearchId=null;state.editingSearchId=null;state.screen='list';syncOpportunitySideNav('商机 / 销售机会');render()}
function pinView(e,v){e.stopPropagation();if(!viewItems.includes(v)&&!savedSearch(v))return;state.defaultView=v;state.customMenuId=null;persistCustomSearches();render();toast('已设为销售机会默认视图')}
function toggleSelect(id,on){on?state.selected.add(id):state.selected.delete(id);render()}
function toggleAll(on){filteredRows().forEach(r=>on?state.selected.add(r.id):state.selected.delete(r.id));render()}
function clearSelection(){state.selected.clear();render()}
function bulkAction(type){if(!state.selected.size){toast('请先选择商机');return}const count=state.selected.size;if(type==='关注')state.selected.forEach(id=>state.followedOpps.add(id));if(type==='取消关注')state.selected.forEach(id=>state.followedOpps.delete(id));state.selected.clear();render();toast('已完成批量'+type+'('+count+'条)')}
function query(){state.query=document.getElementById('oppQueryInput')?.value||'';resetBoardLoad();render()}
function resetQuery(){state.query='';state.industry='全部行业';state.stageFilter='全部';resetBoardLoad();render()}
function setIndustry(v){state.industry=v;resetBoardLoad();render()}
function refresh(e){e.stopPropagation();const b=document.getElementById('oppRefreshBtn');b?.classList.add('refreshing');setTimeout(()=>{b?.classList.remove('refreshing');toast('列表数据已刷新')},520)}
function togglePopover(e,v){e.stopPropagation();state.popover=state.popover===v?null:v;state.menuOpen=false;render()}
function closePopover(){state.popover=null;render()}
function setDensity(v){state.density=v;state.popover=null;render()}
function toggleField(k,on){on?state.visible.add(k):state.visible.delete(k);state.popover='fields';render()}
function resetFields(){state.visible=new Set(columns.map(c=>c.key));state.popover='fields';render()}
function setStageFilter(v){state.stageFilter=v;resetBoardLoad();render()}
function selectSplit(id){const r=viewRows().find(x=>x.id===id)||rowById(id);state.selectedId=id;state.detailScene=sceneForRow(r);recordVisit(id,state.detailScene);render()}
function toggleSplit(){state.splitCollapsed=!state.splitCollapsed;const shell=document.querySelector('.opp-split-shell');if(!shell){render();return}shell.classList.toggle('collapsed',state.splitCollapsed);const b=shell.querySelector('.opp-split-toggle');if(b){b.textContent=state.splitCollapsed?'›':'‹';b.setAttribute('aria-label',state.splitCollapsed?'展开左侧列表':'收起左侧列表')}}
function openDetail(id){const r=viewRows().find(x=>x.id===id)||rowById(id);if(!r)return;state.returnWorkspace=state.workspace;state.returnView=state.view;state.selectedId=id;state.detailScene=sceneForRow(r);recordVisit(id,state.detailScene);state.screen='detail';state.detailTab='overview';state.menuOpen=false;render()}
function openBoardCard(id){openDetail(id)}
function loadMoreBoardStage(stage){const current=state.boardLoadedByStage[stage]||state.boardPageSize;state.boardLoadedByStage[stage]=current+state.boardPageSize;render();toast('已加载“'+stage+'”下一批商机')}
function openDetailFrom(id,v){if(v==='manage')openManagement();else if(v==='publicPool')openPublicPool();openDetail(id)}
function returnList(){state.workspace=state.returnWorkspace;state.view=state.returnView;state.screen='list';state.menuOpen=false;syncOpportunitySideNav('商机 / '+workspaceTitle());render()}
function toggleFollow(id){const followed=state.followedOpps.has(id);followed?state.followedOpps.delete(id):state.followedOpps.add(id);render();toast(followed?'已取消关注':'关注成功')}
function switchTab(v){state.detailTab=v;render()}
function advanceStage(v){state.pendingStage=v;openModal('推进商机阶段')}
function setPlanRange(v){state.planRange=v;render()}
function setLogRange(v){state.logRange=v;render()}
function focusSchemeCard(v){
state.schemeFocusedCardId=v;
state.schemeExpanded.add(v);
render();
requestAnimationFrame(()=>document.getElementById('scheme-card-'+v)?.scrollIntoView({behavior:'smooth',block:'start'}));
}
function toggleScheme(v){state.schemeFocusedCardId=v;state.schemeExpanded.has(v)?state.schemeExpanded.delete(v):state.schemeExpanded.add(v);render()}
function openSalesHome(){state.workspace='sales';state.view=(viewItems.includes(state.defaultView)||savedSearch(state.defaultView))?state.defaultView:'mine';state.selected.clear();state.selectedId=rows[0].id;state.menuOpen=false;state.screen='list';render()}
function openManagement(){state.workspace='manage';state.view='manage';state.selected.clear();state.selectedId=rows[0].id;state.menuOpen=false;state.screen='list';resetBoardLoad();render()}
function openPublicPool(){state.workspace='publicPool';state.view='publicPool';state.selected.clear();state.selectedId=poolRows[0].id;state.menuOpen=false;state.screen='list';render()}
function openDefault(){openSalesHome()}
function onEnter(){state.mode='list';state.screen='list';render()}
function closeFloating(){if(!state.menuOpen&&!state.popover&&!state.customMenuId&&!state.renamingSearchId)return;state.menuOpen=false;state.customMenuId=null;state.renamingSearchId=null;state.popover=null;render()}
restoreCustomSearches();
return {render,onEnter,setMode,toggleMenu,menuTab,switchView,pinView,saveCustomSearch,saveEditedSearch,saveAsNewSearch,runCustomSearch,clearCustomError,toggleCustomActions,editSearch,startRename,cancelRename,confirmRename,confirmDeleteSearch,deleteSearch,toggleSelect,toggleAll,clearSelection,bulkAction,query,resetQuery,setIndustry,refresh,togglePopover,closePopover,setDensity,toggleField,resetFields,setStageFilter,selectSplit,toggleSplit,openDetail,openBoardCard,loadMoreBoardStage,openDetailFrom,returnList,toggleFollow,switchTab,advanceStage,openModal,openSchemeTemplatePicker,selectSchemeTemplate,confirmSchemeTemplateChoice,openSchemeForm,openSchemeCardEdit,applySchemeTemplateSwitch,cancelSchemeTemplateSwitch,schemePreviewBody,openTeamMemberDialog,saveTeamMemberDialog,removeTeamMember,openGroupChatDialog,updateGroupChatCount,saveGroupChatDialog,commit,toast,renderSystemBreadcrumb,closeFloating,setPlanRange,setLogRange,toggleScheme,focusSchemeCard,setPushScenario,selectPushScheme,startPushValidation,resetPushFlow,forcePush,clearCloseError,openDefault,openSalesHome,openManagement,openPublicPool};
})();
/* 0716 商机管理设置:以原型页面结构为主,V1.3 仅补齐详情、草稿与版本交互 */
const oppSettingsV2=(function(){
const copy=x=>JSON.parse(JSON.stringify(x));
const esc=x=>String(x??'').replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
const now='2026-07-17';
const tabs=[['stage','商机阶段设置'],['scheme','方案卡模板'],['push','方案推送规则'],['duplicate','项目查重规则'],['pool','公海与提醒'],['dict','商机相关字典']];
const data={
stage:[
{id:'stage-company',name:'公司通用售前指引',code:'OPP_GUIDE_01',scope:'全部商机',description:'覆盖商机从客户圈定到转项目的通用销售工作指引。',versions:[{v:'V1.2',status:'草稿',used:0,updated:'2026-07-14',note:'待发布版本'},{v:'V1.1',status:'发布中',used:326,updated:'2026-06-20',note:'公司通用规则'},{v:'V1.0',status:'已停用',used:100,updated:'2026-03-01',note:'历史版本'}],nodes:[['OPP_STAGE_01','客户圈定','确认甲方、主要意向客户及商机基本归属'],['OPP_STAGE_02','关系摸排','明确客户关系及商机决策链'],['OPP_STAGE_03','资料采集','补齐需求、预算、现场资料及项目材料'],['OPP_STAGE_04','方案卡','基于实际需求填写方案卡,形成项目基础档案'],['OPP_STAGE_FIXED','已转项目','固定节点:方案卡转项目或创建项目后触发',true]]},
{id:'stage-nonbid',name:'非招投标类销售指引',code:'OPP_GUIDE_02',scope:'招标形式=其他',description:'适用于非招投标类销售机会。',versions:[{v:'V2.0',status:'发布中',used:148,updated:'2026-07-01',note:'当前生效'}],nodes:[['OPP_STAGE_01','客户圈定','明确甲方与意向客户'],['OPP_STAGE_03','资料采集','补齐需求与预算'],['OPP_STAGE_04','方案卡','形成方案资料'],['OPP_STAGE_FIXED','已转项目','固定节点:创建或关联项目后触发',true]]},
{id:'stage-zc',name:'政采类商机销售指引',code:'OPP_GUIDE_03',scope:'招标形式=ZC',description:'适用于政采标类销售机会。',versions:[{v:'V1.0',status:'发布中',used:87,updated:'2026-05-18',note:'当前生效'}],nodes:[['OPP_STAGE_01','客户圈定','确认采购主体'],['OPP_STAGE_02','关系摸排','确认投标相关方'],['OPP_STAGE_03','资料采集','补齐招标基础资料'],['OPP_STAGE_04','方案卡','形成投标前方案资料'],['OPP_STAGE_FIXED','已转项目','固定节点:创建项目后触发',true]]}
],
scheme:[
{id:'scheme-company',name:'公司通用方案卡',code:'SCHEME_CARD_01',scope:'所有部门通用',description:'公司标准方案卡字段。',versions:[{v:'V1.2',status:'草稿',used:0,updated:'2026-07-14',note:'字段调整草稿'},{v:'V1.1',status:'发布中',used:326,updated:'2026-06-20',note:'当前生效'}],fields:[['客户名称','关联对象',true,true],['客户类型','单选下拉',true,true],['战略VIP客户等级','单选下拉',true,false],['已成交VIP客户等级','单选下拉',true,false],['客户联系人','关联对象',true,false],['客户联系电话','自动回填',true,false],['项目阶段','单选下拉',true,true],['招标形式','单选下拉',true,true],['是否有看现场','是/否',true,false],['是否与客户电话沟通','是/否',true,false],['现场勘察资料','附件上传',true,false],['是否百万级项目','是/否',true,false],['方案预算是否明确','是/否',true,false],['方案预算金额','金额',true,false],['预计成交概率','百分比',true,false],['预计投标时间','日期',true,false],['设计配置','单选下拉',true,false],['品牌要求','单行文本',true,false],['是否可换品牌','是/否',true,false],['招标编号','单行文本',true,false],['标底方','单行文本',true,false],['客户需求描述','多行文本',true,true],['业务需求','多行文本',true,true],['对方要求的先进性及前瞻性的要求','多行文本',true,false],['清单参数(是否是我司产品参数、工程师需要确认,是否有品牌?)','多行文本',true,false],['清单参数是否能改','多行文本',true,false],['客户特殊要求(补充说明、备注说明)','多行文本',true,false],['方案卡填写时间','日期时间',true,false],['期望方案设计完成时间','日期时间',true,false]]},
{id:'scheme-team',name:'桂冠天夏战队方案卡',code:'SCHEME_CARD_02',scope:'满足模板适用权限的用户',description:'独立维护的完整方案卡模板;用户满足模板权限条件时即可选择。',versions:[{v:'V2.0',status:'发布中',used:148,updated:'2026-07-01',note:'当前生效'}],fields:[['客户名称','关联对象',true,true],['项目阶段','单选下拉',true,true],['招标形式','单选下拉',true,true],['设计配置','单选下拉',true,false],['部门协同要求','多行文本',true,false]]}
],
push:[
{id:'push-company',name:'公司通用方案推送规则',code:'PUSH_RULE_01',scope:'全部商机',description:'用于控制方案卡转项目的校验、查重、创建与场景平台推送流程。',scheme:'公司通用方案卡 V1.1',versions:[{v:'V1.2',status:'草稿',used:0,updated:'2026-07-14',note:'待发布规则'},{v:'V1.1',status:'发布中',used:238,updated:'2026-07-08',note:'当前生效'},{v:'V1.0',status:'已停用',used:431,updated:'2025-11-27',note:'历史版本'}],config:{checks:['客户名称','客户类型','客户联系人','项目阶段','方案卡','现场勘察资料'],duplicate:'公司项目标准查重规则 V1.1',duplicateAction:'按查重规则分级处理',create:'生成项目并进入确认页面',mapping:[['项目名称','商机名称'],['客户','主要意向客户'],['项目负责人','商机负责人']],platform:true,platformName:'与 CRM 项目名称一致',note:'选填'}},
{id:'push-bid',name:'投标方案推送规则',code:'PUSH_RULE_04',scope:'项目阶段=投标阶段',description:'用于投标场景下的方案卡转项目。',scheme:'公司通用方案卡 V1.1',versions:[{v:'V1.0',status:'发布中',used:71,updated:'2026-06-30',note:'当前生效'}],config:{checks:['客户名称','项目阶段','招标形式','预计投标时间','品牌要求','方案卡'],duplicate:'公司项目标准查重规则 V1.1',duplicateAction:'按查重规则分级处理',create:'生成项目并进入确认页面',mapping:[['项目名称','商机名称'],['客户','主要意向客户'],['项目负责人','商机负责人']],platform:true,platformName:'与 CRM 项目名称一致',note:'必填'}}
],
duplicate:[
{id:'dup-company',name:'公司项目标准查重规则',code:'DUP_RULE_01',scope:'全公司',description:'同一套规则同时供“商机转项目”和“直接创建项目”调用;先查找可能指向同一机会源头的记录,再输出系统初判,异常项目由跟单专员人工裁决。',versions:[{v:'V1.1',status:'发布中',used:3,updated:'2026-06-20'}],range:'全部未删除项目',entryPoints:['商机转项目','直接创建项目'],sourceLinkCheck:true,fields:['项目名称','项目区域','主要意向客户','甲方'],logic:'分级组合判定',matchMode:'相似匹配',threshold:85,hit:'分级处理',actions:{block:'项目名称高度重复 + 项目区域相同 + 主要意向客户相同:强制拦截创建',conflict:'提示异常;操作人可取消或仍要创建,继续后项目进入【冲突处理】并通知跟单专员',normal:'未发现明显异常:按正常流程创建项目'}},
{id:'dup-design',name:'设计阶段项目查重规则',code:'DUP_RULE_02',scope:'项目阶段=设计阶段',description:'沿用公司标准字段,只调整项目名称相似度阈值。',versions:[{v:'V1.0',status:'发布中',used:1,updated:'2026-05-05'}],range:'全公司进行中项目',entryPoints:['商机转项目','直接创建项目'],sourceLinkCheck:true,fields:['项目名称','项目区域','主要意向客户','甲方'],logic:'分级组合判定',matchMode:'相似匹配',threshold:80,hit:'分级处理',actions:{block:'项目名称高度重复 + 项目区域相同 + 主要意向客户相同:强制拦截创建',conflict:'提示异常;操作人可取消或仍要创建,继续后项目进入【冲突处理】并通知跟单专员',normal:'未发现明显异常:按正常流程创建项目'}},
{id:'dup-team',name:'广东15部项目查重规则',code:'DUP_RULE_03',scope:'所属部门=粤晋战队-广东15部',description:'部门草稿;仍使用系统已有的项目名称、项目区域、主要意向客户和甲方。',versions:[{v:'V1.0',status:'草稿',used:0,updated:'2026-07-17'}],range:'全公司项目',entryPoints:['商机转项目','直接创建项目'],sourceLinkCheck:true,fields:['项目名称','项目区域','主要意向客户','甲方'],logic:'分级组合判定',matchMode:'相似匹配',threshold:85,hit:'分级处理',actions:{block:'项目名称高度重复 + 项目区域相同 + 主要意向客户相同:强制拦截创建',conflict:'提示异常;操作人可取消或仍要创建,继续后项目进入【冲突处理】并通知跟单专员',normal:'未发现明显异常:按正常流程创建项目'}}
],
pool:[
{id:'pool-company',name:'公司通用商机公海规则',code:'POOL_RULE_01',scope:'所有部门通用',description:'公司默认商机进入公海、自动回收、领取及提醒规则;无法匹配部门规则时自动适用。',versions:[{v:'V1.2',status:'草稿',used:0,updated:'2026-07-14',note:'待发布规则'},{v:'V1.1',status:'发布中',used:420,updated:'2026-07-08',note:'当前生效'},{v:'V1.0',status:'已停用',used:0,updated:'2025-11-27',note:'历史版本'}],config:{manual:true,auto:true,days:30,claim:true,remind:true,remindDays:7}},
{id:'pool-fire',name:'烈焰队公海规则',code:'POOL_RULE_02',scope:'所属部门=烈焰队',description:'烈焰队商机公海与提醒规则。',versions:[{v:'V2.0',status:'发布中',used:64,updated:'2026-03-01',note:'当前生效'}],config:{manual:true,auto:true,days:20,claim:true,remind:true,remindDays:5}}
],
dict:[
{id:'d-stage',name:'商机阶段',code:'opportunity_stage',count:8,sort:1,status:'启用',description:'供商机工作指引模板选择与编排',values:[['客户圈定','OPP_STAGE_01','确认甲方、主要意向客户及商机基本归属'],['关系摸排','OPP_STAGE_02','明确关键人、设计院、总包及客户关系'],['资料采集','OPP_STAGE_03','补齐需求、预算、时间、现场及项目材料'],['方案卡','OPP_STAGE_04','完成方案卡填写并组织方案协同']]},
{id:'d-status',name:'商机状态',code:'opportunity_status',count:4,sort:2,status:'启用',description:'商机当前业务状态',values:[['推进中','OPP_STATUS_01','商机正常推进'],['暂缓','OPP_STATUS_02','暂不推进'],['关闭','OPP_STATUS_03','商机已关闭'],['已转项目','OPP_STATUS_04','已创建或关联项目']]},
{id:'d-project-role',name:'项目角色',code:'opportunity_project_role',count:6,sort:3,status:'启用',description:'供商机团队成员维护项目协作分工;不用于配置功能权限或数据权限。',values:[['商机负责人','OPP_ROLE_OWNER','负责当前商机统筹、推进及负责人移交'],['协同销售','OPP_ROLE_CO_SALES','协同客户经营与销售推进'],['方案工程师','OPP_ROLE_SOLUTION','负责方案设计及方案资料协同'],['现场工程师','OPP_ROLE_FIELD','负责现场勘察及资料补充'],['商务专员','OPP_ROLE_BUSINESS','负责商务及报价协同'],['跟单报备专员','OPP_ROLE_FOLLOW','负责报备及跟单协同']]},
{id:'d-close',name:'关闭原因',code:'opportunity_close_reason',count:6,sort:4,status:'启用',description:'商机关闭时的可选原因',values:[['客户取消项目','OPP_CLOSE_01','关闭操作可选'],['项目终止','OPP_CLOSE_02','关闭操作可选'],['竞争失败','OPP_CLOSE_03','关闭操作可选'],['重复商机','OPP_CLOSE_05','关闭操作可选']]}
]
};
const state={tab:'stage',modes:{stage:'list',scheme:'list',push:'list',duplicate:'list',pool:'list'},active:{stage:'stage-company',scheme:'scheme-company',push:'push-company',duplicate:'dup-company',pool:'pool-company',dict:'d-stage'},detail:null,detailNode:0,detailTab:'content',edit:null,step:0,dupPreview:'convert',dupCase:'conflict',dupOutcome:null,dupOpenRuleId:null,selectedNode:0,dictView:'groups',filters:{stage:{q:'',scope:'',status:''},scheme:{q:'',scope:'',status:''},push:{q:'',scope:'',status:''},duplicate:{q:'',scope:'',status:''},pool:{q:'',scope:'',status:''}}};
function toast(message){let n=document.getElementById('osToast');if(!n){n=document.createElement('div');n.id='osToast';n.className='os-toast';document.body.appendChild(n)}n.textContent=message;n.classList.add('show');clearTimeout(n._timer);n._timer=setTimeout(()=>n.classList.remove('show'),1800)}
function current(type,id){return data[type].find(x=>x.id===(id||state.active[type]))||data[type][0]}
function version(item,v){return item.versions.find(x=>x.v===v)||item.versions[0]}
function badge(status){const c=status==='发布中'?'live':status==='草稿'?'draft':'history';return '<span class="os-badge '+c+'">'+esc(status)+'</span>'}
function head(title,desc,action){return '<div class="os-section-head"><div><h2>'+title+'</h2><p>'+desc+'</p></div>'+ (action||'')+'</div>'}
function nav(){return '<aside class="os-nav">'+tabs.map(x=>'<button data-os="tab" data-tab="'+x[0]+'" class="'+(state.tab===x[0]?'active':'')+'">'+x[1]+'</button>').join('')+'</aside>'}
function toolbar(type,label){const f=state.filters[type];return '<div class="os-toolbar"><div class="os-query"><input id="os-q-'+type+'" value="'+esc(f.q)+'" placeholder="'+label+'名称 / 编码 / 版本号"><select id="os-scope-'+type+'"><option value="">全部适用范围</option><option value="全部">全部商机</option><option value="所属部门">指定部门</option><option value="招标形式">指定业务类型</option></select><select id="os-status-'+type+'"><option value="">全部状态</option><option value="草稿">草稿</option><option value="发布中">发布中</option><option value="已停用">已停用</option></select><button class="os-btn primary" data-os="query" data-type="'+type+'">查询</button><button class="os-btn" data-os="reset" data-type="'+type+'">重置</button></div><button class="os-btn primary" data-os="new" data-type="'+type+'">+ 新增'+label+'</button></div>'}
function filtered(type){const f=state.filters[type];const rows=[];data[type].forEach(item=>item.versions.forEach(v=>rows.push({item,v})));return rows.filter(({item,v})=>{const s=[item.name,item.code,item.scope,v.v,v.status].join(' ').toLowerCase();return (!f.q||s.includes(f.q.toLowerCase()))&&(!f.scope||item.scope.includes(f.scope))&&(!f.status||v.status===f.status)})}
function pager(n){return '<div class="os-pager"><span>共 '+n+' 条</span><span><button class="active">1</button><button>2</button><button>›</button></span></div>'}
function listStage(){const rows=filtered('stage');return head('商机阶段设置','维护商机推进工作指引模板、节点编排及版本。','')+'<div class="os-card">'+toolbar('stage','模板')+'<div class="os-table-wrap"><table class="os-table"><thead><tr><th>序号</th><th>模板名称</th><th>编码(code)</th><th>版本号</th><th>状态</th><th>适用范围</th><th>最近更新</th><th>绑定商机</th><th>操作</th></tr></thead><tbody>'+rows.map(({item,v},i)=>'<tr><td>'+ (i+1)+'</td><td class="os-rule-name"><b>'+esc(item.name)+'</b><small>'+esc(item.description)+'</small></td><td><code>'+esc(item.code)+'</code></td><td><b>'+v.v+'</b></td><td>'+badge(v.status)+'</td><td>'+esc(item.scope)+'</td><td>'+v.updated+'</td><td>'+v.used+' 条</td><td class="os-actions"><button data-os="detail" data-type="stage" data-id="'+item.id+'" data-version="'+v.v+'">详情</button><button data-os="edit" data-type="stage" data-id="'+item.id+'" data-version="'+v.v+'">编辑</button><button data-os="copy" data-type="stage" data-id="'+item.id+'">复制</button>'+ (v.status==='草稿'&&v.used===0?'<button class="danger" data-os="remove" data-type="stage" data-id="'+item.id+'" data-version="'+v.v+'">删除</button>':'')+'</td></tr>').join('')+'</tbody></table></div>'+pager(rows.length)+'</div>'}
function listRule(type,title,extra){const rows=filtered(type);return head(title,'按版本维护规则;保存草稿或发布由系统自动记录版本状态。','')+'<div class="os-card">'+toolbar(type,'规则')+'<div class="os-table-wrap"><table class="os-table"><thead><tr><th>序号</th><th>规则名称</th><th>编码(code)</th><th>版本号</th><th>状态</th><th>适用范围</th>'+extra.head+'<th>最近更新</th><th>操作</th></tr></thead><tbody>'+rows.map(({item,v},i)=>'<tr><td>'+ (i+1)+'</td><td class="os-rule-name"><b>'+esc(item.name)+'</b><small>'+esc(item.description)+'</small></td><td><code>'+esc(item.code)+'</code></td><td><b>'+v.v+'</b></td><td>'+badge(v.status)+'</td><td>'+esc(item.scope)+'</td>'+extra.cell(item,v)+'<td>'+v.updated+'</td><td class="os-actions"><button data-os="detail" data-type="'+type+'" data-id="'+item.id+'" data-version="'+v.v+'">详情</button><button data-os="edit" data-type="'+type+'" data-id="'+item.id+'" data-version="'+v.v+'">编辑</button><button data-os="copy" data-type="'+type+'" data-id="'+item.id+'">复制</button>'+ (v.status==='草稿'&&v.used===0?'<button class="danger" data-os="remove" data-type="'+type+'" data-id="'+item.id+'" data-version="'+v.v+'">删除</button>':'')+'</td></tr>').join('')+'</tbody></table></div>'+pager(rows.length)+'</div>'}
function listTemplate(type,title,extra){const rows=filtered(type);return head(title,'按版本维护模板;保存草稿或发布由系统自动记录版本状态。','')+'<div class="os-card">'+toolbar(type,'模板')+'<div class="os-table-wrap"><table class="os-table"><thead><tr><th>序号</th><th>模板名称</th><th>编码(code)</th><th>版本号</th><th>状态</th><th>适用范围</th>'+extra.head+'<th>最近更新</th><th>操作</th></tr></thead><tbody>'+rows.map(({item,v},i)=>'<tr><td>'+ (i+1)+'</td><td class="os-rule-name"><b>'+esc(item.name)+'</b><small>'+esc(item.description)+'</small></td><td><code>'+esc(item.code)+'</code></td><td><b>'+v.v+'</b></td><td>'+badge(v.status)+'</td><td>'+esc(item.scope)+'</td>'+extra.cell(item,v)+'<td>'+v.updated+'</td><td class="os-actions"><button data-os="detail" data-type="'+type+'" data-id="'+item.id+'" data-version="'+v.v+'">详情</button><button data-os="edit" data-type="'+type+'" data-id="'+item.id+'" data-version="'+v.v+'">编辑</button><button data-os="copy" data-type="'+type+'" data-id="'+item.id+'">复制</button>'+ (v.status==='草稿'&&v.used===0?'<button class="danger" data-os="remove" data-type="'+type+'" data-id="'+item.id+'" data-version="'+v.v+'">删除</button>':'')+'</td></tr>').join('')+'</tbody></table></div>'+pager(rows.length)+'</div>'}
function listScheme(){return listTemplate('scheme','方案卡模板',{head:'<th>展示字段数</th><th>必填字段数</th>',cell:(i,v)=>'<td>'+i.fields.filter(x=>x[2]).length+'</td><td>'+i.fields.filter(x=>x[3]).length+'</td>'})}
function listPush(){return listRule('push','方案推送规则',{head:'<th>关联方案卡</th><th>执行次数</th>',cell:(i,v)=>'<td>'+esc(i.scheme)+'</td><td>'+v.used+' 次</td>'})}
function listPool(){return listRule('pool','公海与提醒规则',{head:'<th>自动回收</th><th>允许领取</th><th>提前提醒</th>',cell:(i)=>'<td>'+ (i.config.auto?i.config.days+' 天':'未启用')+'</td><td>'+ (i.config.claim?'允许':'不允许')+'</td><td>'+ (i.config.remind?i.config.remindDays+' 天':'不提醒')+'</td>'})}
function detailCommon(type,title,body){const item=current(type,state.detail.id),v=version(item,state.detail.version),isTemplate=type==='stage'||type==='scheme',usedLabel=type==='pool'?'绑定商机':type==='push'?'执行次数':'使用数据',usedValue=v.used+(type==='push'?' 次':' 条'),contentLabel=type==='stage'?'阶段':type==='scheme'?'模板字段':'规则配置',descriptionLabel=isTemplate?'模板说明':'规则说明',target=item.target||'--',isDefault=item.isDefault||(isTemplate||type==='pool'?'是':'--');const versionPanel='<section class="os-version"><div class="os-subtitle"><h3>版本记录</h3><span class="os-muted">系统根据“保存草稿 / 发布”自动记录版本状态</span></div><div class="os-table-wrap"><table class="os-table"><thead><tr><th>版本</th><th>状态</th><th>使用/执行</th><th>发布时间</th></tr></thead><tbody>'+item.versions.map(x=>'<tr><td><b>'+x.v+'</b></td><td>'+badge(x.status)+'</td><td>'+x.used+(type==='push'?' 次':' 条')+'</td><td>'+x.updated+'</td></tr>').join('')+'</tbody></table></div></section>';return '<div class="os-detail-shell"><section class="os-detail-sheet"><header class="os-detail-hero"><div class="os-detail-core"><div class="os-detail-name"><h2>'+esc(item.name)+'</h2></div><div class="os-detail-return"><button class="os-btn" data-os="back" data-type="'+type+'">返回列表</button></div></div><div class="os-detail-meta"><div><span>编码</span><b>'+esc(item.code)+'</b></div><div><span>适用范围</span><b>'+esc(item.scope)+'</b></div><div><span>适用对象</span><b>'+esc(target)+'</b></div><div><span>是否默认</span><b>'+esc(isDefault)+'</b></div><div><span>版本号</span><b>'+v.v+'</b></div><div><span>状态</span><b>'+badge(v.status)+'</b></div><div><span>'+usedLabel+'</span><b>'+usedValue+'</b></div></div><div class="os-detail-description"><span>'+descriptionLabel+'</span><p>'+esc(item.description)+'</p></div><div class="os-detail-actionbar"><button class="os-btn" data-os="copy" data-type="'+type+'" data-id="'+item.id+'">复制</button><button class="os-btn primary" data-os="edit" data-type="'+type+'" data-id="'+item.id+'" data-version="'+v.v+'">编辑</button></div></header><div class="os-detail-tabs"><button class="'+(state.detailTab==='content'?'active':'')+'" data-os="detail-tab" data-tab="content">'+contentLabel+'</button><button class="'+(state.detailTab==='versions'?'active':'')+'" data-os="detail-tab" data-tab="versions">版本记录</button></div><div class="os-detail-content">'+(state.detailTab==='versions'?versionPanel:body)+'</div></section></div>'}
function detailStage(){const item=current('stage',state.detail.id),selected=item.nodes[state.detailNode]||item.nodes[0];const body='<section class="os-detail-section os-stage-detail"><div class="os-subtitle"><h3>阶段节点配置</h3><span class="os-drag-tip">点击节点查看工作目标;编辑时支持拖拽排序</span></div><div class="os-flow">'+item.nodes.map((n,i)=>'<button class="os-flow-node os-detail-node '+(i===state.detailNode?'active ':'')+(n[3]?'fixed':'')+'" data-os="detail-stage-node" data-index="'+i+'"><b>'+(i+1)+'. '+esc(n[1])+(n[3]?' <span class="os-badge fixed">固定</span>':'')+'</b><small>'+esc(n[2])+'</small></button>'+(i<item.nodes.length-1?'<i class="os-flow-arrow">›</i>':'')).join('')+'</div><div class="os-current-stage"><h4>当前选择节点:'+esc(selected[1])+'</h4><div class="os-current-stage-grid"><label><span>阶段字典</span><select disabled><option>'+esc(selected[1])+'('+esc(selected[0])+')</option></select></label><label><span>自定义节点名称</span><input value="'+esc(selected[1])+'" disabled></label><label class="full"><span>阶段工作目标</span><textarea disabled>'+esc(selected[2])+'</textarea></label></div></div></section>';return detailCommon('stage','模板',body)}
function detailPush(){const item=current('push',state.detail.id),c=item.config;const steps=[['信息完整度查验',item.scheme+' · '+c.checks.length+' 项校验'],['项目查重',c.duplicate],['创建项目',c.create+' · '+c.mapping.length+' 项映射'],['推送场景平台',c.platform?'已启用':'不推送']];const body='<section class="os-card" style="margin-top:12px"><div class="os-subtitle"><h3>四步推送流程</h3></div><div class="os-stepper">'+steps.map((x,i)=>'<button class="os-step '+(state.step===i?'active':'')+'" data-os="push-step" data-step="'+i+'"><span class="n">'+(i+1)+'</span><b>'+x[0]+'</b><small>'+esc(x[1])+'</small></button>').join('')+'</div><div class="os-step-body">'+pushStepBody(c,false,item.scheme)+'</div></section>';return detailCommon('push','规则',body)}
function detailPool(){const item=current('pool',state.detail.id);const body='<section class="os-card" style="margin-top:12px"><div class="os-subtitle"><h3>规则配置</h3></div>'+poolCards(item.config,false)+'<div class="os-fixed-note">有效跟进:商机详情有任何形式的跟进记录更新、附件上传和字段编辑修订,均被认可为有效跟进。</div></section>';return detailCommon('pool','规则',body)}
function detailScheme(){const item=current('scheme',state.detail.id);const body='<section class="os-card" style="margin-top:12px"><div class="os-subtitle"><h3>模板字段</h3><button class="os-text-btn" data-os="preview">预览填写页</button></div>'+fieldTable(item.fields,false)+'</section>';return detailCommon('scheme','模板',body)}
function duplicateMatchText(item){return item.matchMode==='相似匹配'?'相似匹配 ≥ '+item.threshold+'%':'完全一致'}
function dedupeFieldCatalog(){
return [
{field:'来源商机关系',source:'系统关联关系',fixed:true},
{field:'项目名称',source:'项目名称'},
{field:'项目区域',source:'项目区域'},
{field:'主要意向客户',source:'主要意向客户'},
{field:'甲方',source:'甲方'}
]
}
function dedupeTone(name){
if(String(name).includes('明确重复'))return 'duplicate';
if(String(name).includes('疑似重复'))return 'high';
if(String(name).includes('同商机'))return 'conflict';
if(String(name).includes('跨商机'))return 'cross';
return 'safe'
}
function dedupeDefaults(item){
item.entryPoints=item.entryPoints||['商机转项目','直接创建项目'];
item.projectStatuses=item.projectStatuses||['进行中','已中止/暂停'];
if(!item.detectionConditions){
item.detectionConditions=[
{id:'dc-source',field:'来源商机关系',source:'系统关联关系',match:'完全一致',threshold:100,fixed:true},
{id:'dc-name',field:'项目名称',source:'项目名称',match:'名称相似',threshold:Number(item.threshold||85)},
{id:'dc-region',field:'项目区域',source:'项目区域',match:'完全一致',threshold:100},
{id:'dc-customer',field:'主要意向客户',source:'主要意向客户',match:'名称相似',threshold:70},
{id:'dc-party',field:'甲方',source:'甲方',match:'完全一致',threshold:100}
]
}
if(!item.relationRules){
item.relationRules=[
{id:'rr-block',name:'明确重复',expression:{type:'group',children:[{type:'condition',field:'项目名称',value:'命中'},{type:'condition',join:'and',field:'项目区域',value:'命中'},{type:'condition',join:'and',field:'主要意向客户',value:'命中'}]}},
{id:'rr-repeat',name:'疑似重复项目',expression:{type:'group',children:[{type:'condition',field:'来源商机关系',value:'相同'},{type:'condition',join:'and',field:'主要意向客户',value:'命中'}]}},
{id:'rr-same',name:'同商机多客户冲突',expression:{type:'group',children:[{type:'condition',field:'来源商机关系',value:'相同'},{type:'condition',join:'and',field:'主要意向客户',value:'未命中'}]}},
{id:'rr-cross',name:'跨商机同源冲突',expression:{type:'group',children:[{type:'condition',field:'来源商机关系',value:'不同'},{type:'condition',join:'and',field:'项目名称',value:'命中'},{type:'group',join:'and',children:[{type:'condition',field:'项目区域',value:'命中'},{type:'condition',join:'or',field:'甲方',value:'命中'}]}]}},
{id:'rr-tag',name:'一般相似 / 标签提示',expression:{type:'group',children:[{type:'condition',field:'项目名称',value:'命中'},{type:'condition',join:'or',field:'甲方',value:'命中'}]}}
]
}
item.relationRules.forEach(normalizeDedupeExpression);
item.treatmentMappings=item.treatmentMappings||{
block:['明确重复'],
confirm:['疑似重复项目','同商机多客户冲突','跨商机同源冲突'],
normal:['一般相似 / 标签提示','无明显冲突']
};
item.actions={
block:'阻止本次创建,不生成新项目。',
conflict:'提示异常;操作人确认继续后创建项目,并进入【冲突处理】,通知跟单专员。',
normal:'按正常流程创建项目;命中标签类结论时,同步添加相应标签。'
};
return item
}
function duplicateMatchText(item){
item=dedupeDefaults(item);
const row=item.detectionConditions.find(x=>x.field==='项目名称');
return row?.match==='名称相似'?'项目名称相似度 ≥ '+row.threshold+'%':'项目名称完全一致'
}
function dedupeConditionText(c){
return c.field+' '+(c.field==='来源商机关系'?'为':'')+'「'+c.value+'」'
}
function dedupeValueOptions(field){
return field==='来源商机关系'?['相同','不同']:['命中','未命中']
}
function normalizeDedupeExpression(rule){
if(!rule.expression){
const join=rule.mode==='any'?'or':'and';
rule.expression={type:'group',children:(rule.conditions||[]).map((c,i)=>({type:'condition',join:i?join:undefined,field:c.field,value:c.value}))}
}
function walk(node){
if(!node||node.type!=='group')return;
node.children=Array.isArray(node.children)?node.children:[];
node.children.forEach((child,i)=>{
child.join=i?(child.join==='or'?'or':'and'):undefined;
if(child.type==='group')walk(child);
else{
child.type='condition';
child.field=child.field||'来源商机关系';
const opts=dedupeValueOptions(child.field);
if(!opts.includes(child.value))child.value=opts[0]
}
})
}
walk(rule.expression);
return rule.expression
}
function dedupeExprText(node,isRoot){
if(!node)return '未配置条件';
if(node.type==='condition')return dedupeConditionText(node);
const parts=(node.children||[]).map((child,i)=>(i?(child.join==='or'?' 或 ':' 且 '):'')+dedupeExprText(child,false)).join('');
if(!parts)return '未配置条件';
return isRoot?parts:'('+parts+')'
}
function dedupeRuleText(rule){
return dedupeExprText(normalizeDedupeExpression(rule),true)
}
function dedupeExprParts(path){
if(!path||path==='root')return [];
return path.replace(/^root\.?/,'').split('.').filter(Boolean).map(Number)
}
function dedupeExprNode(root,path){
let node=root;
for(const i of dedupeExprParts(path))node=node?.children?.[i];
return node
}
function dedupeExprParent(root,path){
const parts=dedupeExprParts(path),index=parts.pop();
let parent=root;
for(const i of parts)parent=parent?.children?.[i];
return {parent,index}
}
function dedupeExprNormalizeGroup(group){
if(!group||group.type!=='group')return;
group.children=(group.children||[]).filter(Boolean);
group.children.forEach((child,i)=>{child.join=i?(child.join==='or'?'or':'and'):undefined;if(child.type==='group')dedupeExprNormalizeGroup(child)})
}
function dedupeExprConditionHtml(node,ruleIndex,path,fields){
const vals=dedupeValueOptions(node.field);
return '<div class="os-expr-condition"><select data-os-dup-expr-field="'+ruleIndex+'" data-path="'+path+'">'+fields.map(f=>'<option '+(node.field===f?'selected':'')+'>'+esc(f)+'</option>').join('')+'</select><select data-os-dup-expr-value="'+ruleIndex+'" data-path="'+path+'">'+vals.map(v=>'<option '+(node.value===v?'selected':'')+'>'+v+'</option>').join('')+'</select><div class="os-expr-node-actions"><button class="os-text-btn" data-os="dup-expr-up" data-rule-index="'+ruleIndex+'" data-path="'+path+'">上移</button><button class="os-text-btn" data-os="dup-expr-down" data-rule-index="'+ruleIndex+'" data-path="'+path+'">下移</button><button class="os-text-btn danger" data-os="dup-expr-del" data-rule-index="'+ruleIndex+'" data-path="'+path+'">删除</button></div></div>'
}
function dedupeExprGroupHtml(group,ruleIndex,path,fields,depth){
const isRoot=path==='root';
const children=(group.children||[]).map((child,i)=>{
const childPath=path+'.'+i;
const join=i?'<select class="os-expr-join" data-os-dup-expr-join="'+ruleIndex+'" data-path="'+childPath+'"><option value="and" '+(child.join!=='or'?'selected':'')+'>且</option><option value="or" '+(child.join==='or'?'selected':'')+'>或</option></select>':'<div class="os-expr-join-placeholder">首项</div>';
const body=child.type==='group'?'<div class="os-expr-group-body">'+dedupeExprGroupHtml(child,ruleIndex,childPath,fields,depth+1)+'</div>':dedupeExprConditionHtml(child,ruleIndex,childPath,fields);
return '<div class="os-expr-child">'+join+body+'</div>'
}).join('');
return '<section class="os-expr-group '+(isRoot?'':'nested')+'"><div class="os-expr-group-head"><div class="os-expr-group-title"><span class="paren">'+(isRoot?'整组':'( )')+'</span><span>'+(isRoot?'条件表达式':'括号条件组')+'</span>'+(depth?'<small class="os-expr-depth-note">第 '+depth+' 层</small>':'')+'</div><div class="os-expr-group-tools"><button class="os-text-btn" data-os="dup-expr-add-condition" data-rule-index="'+ruleIndex+'" data-path="'+path+'">+ 条件</button><button class="os-text-btn" data-os="dup-expr-add-group" data-rule-index="'+ruleIndex+'" data-path="'+path+'">+ 条件组</button>'+(isRoot?'':'<button class="os-text-btn" data-os="dup-expr-up" data-rule-index="'+ruleIndex+'" data-path="'+path+'">上移</button><button class="os-text-btn" data-os="dup-expr-down" data-rule-index="'+ruleIndex+'" data-path="'+path+'">下移</button><button class="os-text-btn danger" data-os="dup-expr-del" data-rule-index="'+ruleIndex+'" data-path="'+path+'">删除组</button>')+'</div></div><div class="os-expr-children">'+(children||'<div class="os-expr-empty">当前条件组为空,请添加条件或条件组。</div>')+'</div></section>'
}
function dedupeTreatmentRows(item){
item=dedupeDefaults(item);
return [
{key:'block',name:'强制拦截',tone:'duplicate',action:item.actions.block,relations:item.treatmentMappings.block||[]},
{key:'confirm',name:'异常确认',tone:'conflict',action:item.actions.conflict,relations:item.treatmentMappings.confirm||[]},
{key:'normal',name:'正常创建',tone:'safe',action:item.actions.normal,relations:item.treatmentMappings.normal||[]}
]
}
function dedupeCandidateCard(c){
return '<section class="os-candidate-card"><header><div><b>'+c.name+'</b><small>'+c.meta+'</small></div><span class="os-risk-tag '+c.tone+'">'+c.relation+'</span></header><table>'
+c.rows.map(r=>'<tr><th>'+r[0]+'</th><td>'+r[1]+'</td><td class="'+(r[2]?'hit':'miss')+'">'+(r[2]?'命中':'参考')+'</td></tr>').join('')
+'</table><footer><span>'+c.note+'</span><div><button class="os-btn" data-os="dup-view-existing">查看已有项目</button></div></footer></section>'
}
function dedupePreview(item){
dedupeDefaults(item);
const isConvert=state.dupPreview!=='create';
const isBlock=state.dupCase==='block';
const input=isConvert
?[['来源商机','广东省人民政府办公写字楼新址-无纸化会议系统'],['项目名称','广东省人民政府办公写字楼新址-无纸化会议系统'],['甲方','广东省人民政府办公厅'],['主要意向客户','客户B|广州智联工程有限公司'],['项目区域','广州市越秀区']]
:[['来源入口','直接创建项目'],['项目名称','广东省政府新办公楼无纸化会议系统'],['甲方','广东省人民政府办公厅'],['主要意向客户','客户B|广州智联工程有限公司'],['项目区域','广州市越秀区']];
let candidates=[];
if(isBlock){
candidates=[{name:'广东省政府办公新址无纸化会议系统|客户B项目',meta:'负责人:张三 状态:进行中',relation:'明确重复',tone:'duplicate',rows:[['来源商机',isConvert?'相同':'不同 / 待核实',isConvert],['项目名称','相似度 97%',true],['项目区域','广州市越秀区|相同',true],['主要意向客户','客户B|相同',true],['甲方','广东省人民政府办公厅|相同',true]],note:'项目名称、项目区域、主要意向客户三项核心依据同时命中,触发强制拦截。'}];
}else if(isConvert){
candidates=[
{name:'同一商机|客户A项目',meta:'客户A:广州亚东智能科技有限公司 方案:第一设计方案',relation:'同商机多客户冲突',tone:'conflict',rows:[['来源商机','相同',true],['项目名称','相似度 100%',true],['项目区域','广州市越秀区|相同',true],['主要意向客户','不同(客户A)',false],['已有方案','第一设计方案',true]],note:'不是重复项目,但客户B方案需与客户A的第一设计方案错开部分参数、品牌和设计表达。'},
{name:'广东省政府新办公楼会议系统建设|客户C项目',meta:'来源商机不同 负责人:李四 状态:进行中',relation:'跨商机同源冲突',tone:'cross',rows:[['来源商机','不同',false],['项目名称','相似度 91%',true],['项目区域','广州市越秀区|相同',true],['甲方','广东省人民政府办公厅|相同',true],['主要意向客户','不同(客户C)',false]],note:'虽然来自另一条商机,但高度疑似指向同一个办公楼销售机会源头。'}
];
}else{
candidates=[{name:'广东省人民政府办公写字楼新址-无纸化会议系统|客户A项目',meta:'来源商机:既有商机 负责人:张三 状态:进行中',relation:'跨商机同源冲突',tone:'cross',rows:[['来源商机','本次无来源商机 / 既有项目有来源商机',false],['项目名称','相似度 94%',true],['项目区域','广州市越秀区|相同',true],['甲方','广东省人民政府办公厅|相同',true],['主要意向客户','不同(客户A)',false]],note:'直接创建入口同样检索所选状态的全公司项目,不会因为没有来源商机而漏掉同一机会源头。'}];
}
const relationText=isBlock?'明确重复,触发强制拦截':candidates.map(c=>c.relation).join('、');
const decision=isBlock
?'<div class="os-decision-panel block"><div><h4>系统处理:强制拦截</h4><p>项目名称、项目区域、主要意向客户同时命中明确重复规则。当前不允许继续创建。</p></div><div><button class="os-btn" data-os="dup-view-existing">查看已有项目</button><button class="os-btn primary" disabled>不能继续创建</button></div></div>'
:state.dupOutcome==='created'
?'<div class="os-conflict-created"><div class="os-created-icon">✓</div><div><h4>项目已创建,当前停留在【冲突处理】节点</h4><p>项目编号:PRJ20260718-018 系统已通知跟单专员及相关部门。人工判定完成前,不进入后续正常节点。</p><div class="os-created-flow"><span>异常项目创建</span><i>→</i><b>冲突处理</b><i>→</i><span>跟单专员判定</span><i>→</i><span>解除 / 合并 / 差异化处理</span></div></div><button class="os-btn" data-os="dup-reset-outcome">重新演示</button></div>'
:'<div class="os-decision-panel warn"><div><h4>系统提示:本次创建存在异常</h4><p>操作人可以取消,也可以确认仍要创建;继续后,新项目进入【冲突处理】节点,由跟单专员介入判定。</p></div><div><button class="os-btn" data-os="dup-cancel-create">取消创建</button><button class="os-btn primary" data-os="dup-continue-create">仍要创建项目</button></div></div>';
return '<div class="os-preview-switch"><button class="'+(isConvert?'active':'')+'" data-os="dup-preview" data-mode="convert">商机转项目</button><button class="'+(!isConvert?'active':'')+'" data-os="dup-preview" data-mode="create">直接创建项目</button><span class="os-preview-label">检测情形</span><button class="'+(!isBlock?'active':'')+'" data-os="dup-case" data-case="conflict">异常但可确认继续</button><button class="'+(isBlock?'active':'')+'" data-os="dup-case" data-case="block">明确重复直接拦截</button></div>'
+'<div class="os-dedupe-demo-flow"><div class="os-dedupe-source-check"><div><span class="os-flow-index">1</span><b>关联项目检索</b><small>'+(isConvert?'检索当前商机及跨商机的可能关联项目,不因客户不同而排除。':'按配置条件检索所选状态的全公司项目。')+'</small></div><span class="os-result-engine">找到 '+candidates.length+' 条</span></div><div class="os-dedupe-source-check"><div><span class="os-flow-index">2</span><b>异常关系分析</b><small>逐条套用判定规则,识别冲突风险及研判结论。</small></div><span class="os-result-engine">'+relationText+'</span></div></div>'
+'<div class="os-dedupe-preview-grid"><section class="os-preview-input"><h4>'+(isConvert?'从商机带入的本次数据':'本次直接创建数据')+'</h4>'+input.map(x=>'<div><span>'+x[0]+'</span><b>'+x[1]+'</b></div>').join('')+'</section><div class="os-candidate-stack">'+candidates.map(dedupeCandidateCard).join('')+'</div></div>'
+decision
}
function dedupeReadOnlyBody(d){
dedupeDefaults(d);
if(state.step===0){
const statuses=['进行中','已中止/暂停','已结项','已关闭'];
return '<h4>第一步:设置触发场景与检测范围</h4><p class="os-muted" style="margin:0 0 12px">查看本规则在哪些业务操作中触发,以及哪些状态的已有项目参与检测。</p>'
+'<div class="os-config-stack"><section class="os-panel"><h4>触发场景</h4><p class="os-muted">已配置的业务触发场景。</p><div class="os-check-list horizontal">'
+['商机转项目','直接创建项目'].map(x=>'<label><input type="checkbox" '+(d.entryPoints.includes(x)?'checked':'')+' disabled> '+x+'</label>').join('')
+'</div></section><section class="os-panel"><h4>参与检测的项目状态</h4><p class="os-muted">仅所选状态的已有项目参与异常检测。</p><div class="os-check-list horizontal">'
+statuses.map(x=>'<label><input type="checkbox" '+(d.projectStatuses.includes(x)?'checked':'')+' disabled> '+x+'</label>').join('')
+'</div></section></div>'
}
if(state.step===1){
return '<h4>第二步:配置关联项目检索条件</h4><p class="os-muted" style="margin:0 0 12px">查看当前使用的检索字段、匹配方式及相似度阈值。</p>'
+'<table class="os-check-table os-editable-rule-table"><thead><tr><th>检索字段</th><th>数据来源</th><th>匹配方式</th><th>相似度阈值</th><th>操作</th></tr></thead><tbody>'
+d.detectionConditions.map(x=>'<tr><td><b>'+esc(x.field)+'</b>'+(x.fixed?' <span class="os-badge fixed">系统固定</span>':'')+'</td><td>'+esc(x.source)+'</td><td><select disabled><option>'+esc(x.match)+'</option></select></td><td>'+(x.match==='名称相似'?'<input class="os-inline-number" type="number" value="'+x.threshold+'" disabled> %':'<span class="os-muted">—</span>')+'</td><td class="os-actions"><button disabled>只读</button></td></tr>').join('')
+'</tbody></table><div class="os-fixed-note" style="margin-top:12px">来源商机关系由系统固定提供;其他字段的匹配方式与阈值按已发布版本展示。</div>'
}
if(state.step===2){
return '<h4>第三步:配置异常关系判定规则</h4><p class="os-muted" style="margin:0 0 12px">查看条件表达式、括号嵌套关系及对应的系统研判结论。</p>'
+'<table class="os-check-table os-rule-matrix"><thead><tr><th>排序</th><th>条件表达式</th><th>输出研判结论</th><th>操作</th></tr></thead><tbody>'
+d.relationRules.map((r,i)=>{const summary=dedupeRuleText(r);return '<tr><td><div class="os-rule-order"><b>'+(i+1)+'</b><i>/ '+d.relationRules.length+'</i></div></td><td><div class="os-expression-summary"><span class="os-expression-text '+(summary==='未配置条件'?'empty':'')+'">'+esc(summary)+'</span></div></td><td><input class="os-rule-output-input" value="'+esc(r.name)+'" disabled></td><td><div class="os-rule-row-actions"><button class="os-text-btn" disabled>只读</button></div></td></tr>'}).join('')
+'</tbody></table><div class="os-fixed-note" style="margin-top:12px">规则按列表顺序执行;系统按照括号层级优先计算,并对每条候选项目独立套用规则。</div>'
}
const relationNames=d.relationRules.map(x=>x.name).filter(Boolean);
const rows=dedupeTreatmentRows(d);
return '<h4>第四步:配置研判结果对应的处理方式</h4><p class="os-muted" style="margin:0 0 12px">查看各类研判结论对应的强制拦截、异常确认或正常创建方式。</p>'
+'<table class="os-check-table os-treatment-table"><thead><tr><th>处理情形</th><th>系统动作</th><th>适用研判结论</th></tr></thead><tbody>'
+rows.map(x=>'<tr><td><span class="os-risk-tag '+x.tone+'">'+x.name+'</span></td><td>'+esc(x.action)+'</td><td><div class="os-treatment-options">'+relationNames.map(n=>'<label><input type="checkbox" '+(x.relations.includes(n)?'checked':'')+' disabled> '+esc(n)+'</label>').join('')+(x.key==='normal'?'<label><input type="checkbox" '+(x.relations.includes('无明显冲突')?'checked':'')+' disabled> 无明显冲突</label>':'')+'</div></td></tr>').join('')
+'</tbody></table><div class="os-fixed-note" style="margin-top:12px">正常创建不等于没有异常。标签类研判结论可正常创建,并同步为方案或项目添加对应标签。</div>'
}
function detailDuplicate(){
const item=dedupeDefaults(current('duplicate',state.detail.id));
const v=version(item,state.detail.version);
const steps=[['触发场景','设置检测范围'],['关联项目检索','查找可能关联记录'],['异常关系分析','识别冲突风险及研判结论'],['处理策略','根据研判结果执行对应处置']];
const body='<section class="os-card os-dedupe-detail-card" style="margin-top:12px">'
+'<div class="os-subtitle"><h3>异常检测与查重规则详情</h3><span class="os-badge live">'+esc(v.v)+' 只读</span></div>'
+'<div class="os-stepper">'+steps.map((x,i)=>'<button class="os-step '+(state.step===i?'active':'')+'" data-os="dup-step" data-step="'+i+'"><span class="n">'+(i+1)+'</span><b>'+x[0]+'</b><small>'+x[1]+'</small></button>').join('')+'</div>'
+'<div class="os-step-body os-readonly-config">'+dedupeReadOnlyBody(item)+'</div>'
+'</section>';
return detailCommon('duplicate','规则',body)
}
function fieldTable(fields,editing){return '<div class="os-table-wrap"><table class="os-table"><thead><tr><th>序号</th><th>字段名称</th><th>字段类型</th><th>显示</th><th>必填</th>'+ (editing?'<th>操作</th>':'')+'</tr></thead><tbody>'+fields.map((x,i)=>'<tr><td>'+ (i+1)+'</td><td><b>'+esc(x[0])+'</b></td><td>'+esc(x[1])+'</td><td>'+ (x[2]?'是':'否')+'</td><td>'+ (x[3]?'是':'否')+'</td>'+ (editing?'<td class="os-actions"><button data-os="scheme-up" data-index="'+i+'">上移</button><button data-os="scheme-down" data-index="'+i+'">下移</button><button data-os="scheme-add-at" data-index="'+i+'">新增</button><button class="danger" data-os="scheme-del" data-index="'+i+'">删除</button></td>':'')+'</tr>').join('')+'</tbody></table></div>'}
function openSchemePreview(){const item=current('scheme',state.detail?.id),v=version(item,state.detail?.version);const mask=document.getElementById('modal'),panel=mask?.querySelector('.modal');if(!mask||!panel)return;panel.classList.remove('opp-modal-small','opp-modal-medium','opp-push-dialog');panel.classList.add('opp-modal-wide');document.getElementById('modalTitle').textContent='方案卡填写效果预览';mask.querySelector('.m-body').innerHTML=typeof oppModule!=='undefined'&&oppModule.schemePreviewBody?oppModule.schemePreviewBody(item.name,v?.v,item.fields):'<div class="os-preview"><h3>'+esc(item.name)+' · 填写页</h3></div>';mask.querySelector('.m-foot').innerHTML='<button class="btn primary" onclick="closeModal()">关闭预览</button>';mask.classList.add('show')}
function poolCards(c,editing){const d=editing?'':'disabled';return '<div class="os-pool-grid"><section class="os-pool-card"><h4>进入公海</h4><div class="body"><label class="os-line"><input type="checkbox" data-os-pool="manual" '+(c.manual?'checked':'')+' '+d+'>允许负责人手动转入公海</label><label class="os-line"><input type="checkbox" data-os-pool="auto" '+(c.auto?'checked':'')+' '+d+'>连续 <input type="number" data-os-pool="days" value="'+c.days+'" '+(!editing||!c.auto?'disabled':'')+'> 天未有效跟进自动回收</label></div></section><section class="os-pool-card"><h4>领取规则</h4><div class="body"><label class="os-line"><input type="checkbox" data-os-pool="claim" '+(c.claim?'checked':'')+' '+d+'>允许适用范围内的销售自由领取商机</label></div></section><section class="os-pool-card"><h4>提醒设置</h4><div class="body"><label class="os-line"><input type="checkbox" data-os-pool="remind" '+(c.remind?'checked':'')+' '+d+'>自动回收前 <input type="number" data-os-pool="remindDays" value="'+c.remindDays+'" '+(!editing||!c.remind?'disabled':'')+'> 天提醒商机负责人</label></div></section></div>'}
function nextVersion(item){const last=item.versions.map(x=>Number((x.v.match(/\d+(?:\.\d+)?/)||['0'])[0])).sort((a,b)=>b-a)[0]||0;return 'V'+(Math.round((last+.1)*10)/10).toFixed(1)}
function editStart(type,kind,id,ver){let item;if(kind==='new'){item=blank(type)}else{item=copy(current(type,id));if(kind==='edit'&&item.versions[0]?.status!=='草稿'){item.versions.unshift({v:nextVersion(item),status:'草稿',used:0,updated:'--'})}if(kind==='copy'){item.id=type+'-'+Date.now();item.name+=' 副本';item.code=item.code.replace(/\d+$/,m=>String(Number(m)+9).padStart(2,'0'));item.versions=[{v:'V1.0',status:'草稿',used:0,updated:'--'}]}}state.edit={type,kind,origin:id||null,draft:item,version:ver||item.versions[0].v};state.active[type]=item.id;state.modes[type]='edit';state.step=0;state.selectedNode=0;render()}
function blank(type){const id=type+'-'+Date.now();if(type==='stage')return {id,name:'新商机阶段模板',code:'OPP_GUIDE_05',scope:'全部商机',description:'',versions:[{v:'V1.0',status:'草稿',used:0,updated:'--'}],nodes:[['OPP_STAGE_01','客户圈定','请输入该阶段工作目标'],['OPP_STAGE_FIXED','已转项目','固定节点:创建或关联项目后触发',true]]};if(type==='push')return {id,name:'新方案推送规则',code:'PUSH_RULE_05',scope:'全部商机',description:'',scheme:'公司通用方案卡 V1.1',versions:[{v:'V1.0',status:'草稿',used:0,updated:'--'}],config:copy(data.push[0].config)};if(type==='pool')return {id,name:'新公海与提醒规则',code:'POOL_RULE_05',scope:'所有部门通用',description:'',versions:[{v:'V1.0',status:'草稿',used:0,updated:'--'}],config:{manual:true,auto:true,days:30,claim:true,remind:true,remindDays:7}};if(type==='scheme')return {id,name:'新方案卡模板',code:'SCHEME_CARD_06',scope:'所有部门通用',description:'',versions:[{v:'V1.0',status:'草稿',used:0,updated:'--'}],fields:copy(data.scheme[0].fields)};return {id,name:'新项目查重规则',code:'DUP_RULE_04',scope:'全公司',description:'',versions:[{v:'V1.0',status:'草稿',used:0,updated:'--'}],range:'全部未删除项目',fields:['客户','项目名称'],logic:'所有勾选字段均匹配',matchMode:'相似匹配',threshold:85,hit:'阻止创建'}}
function editHead(title,source){return '<div class="os-edit-head"><div><h2>'+title+'</h2></div><div class="os-edit-actions"><button class="os-btn" data-os="cancel-edit">取消</button><button class="os-btn" data-os="save-draft">暂存草稿</button><button class="os-btn primary" data-os="publish">保存并发布</button></div></div><div class="os-origin">'+source+'</div>'}
function baseForm(draft,type){const isTemplate=type==='stage'||type==='scheme',isPool=type==='pool';const nameLabel=isTemplate?'模板名称':isPool?'公海规则名称':'规则名称';const codeLabel=isTemplate?'模板编码':'规则编码';const descLabel=isTemplate?'模板说明':'规则说明';const scopeLabel='适用范围';const scopeDefault=type==='stage'?'所有商机':'所有部门通用';const scopeOptions=[draft.scope,scopeDefault,'对应部门专用','项目阶段=投标阶段'].filter((v,i,a)=>v&&a.indexOf(v)===i);const targetLabel=(type==='stage'?'适用对象':(type==='scheme'||type==='pool'?'适用对象(仅“对应部门专用”时选择)':'适用对象'));const defaultLabel=(type==='scheme'||type==='pool'?'是否设为默认(仅所有部门通用时可设为默认·唯一)':'是否设为默认');const defaultRequired=type==='stage';const required=label=>'<i class="os-required">*</i>'+label;return '<section class="os-card"><div class="os-subtitle"><h3>'+ (isTemplate?'模板':'规则')+'基本信息</h3></div><div class="os-form"><label><span>'+required(nameLabel)+'</span><input data-os-field="name" value="'+esc(draft.name)+'" placeholder="请输入"></label><label><span>'+codeLabel+'</span><input value="'+esc(draft.code)+'" disabled></label><label><span>'+required(scopeLabel)+'</span><select data-os-field="scope">'+scopeOptions.map(x=>'<option '+(draft.scope===x?'selected':'')+'>'+esc(x)+'</option>').join('')+'</select></label><label><span>'+targetLabel+'</span><input data-os-field="target" value="'+esc(draft.target||'')+'" placeholder="请输入"></label><label><span>'+(defaultRequired?required('是否设为默认'):defaultLabel)+'</span><select data-os-field="isDefault"><option value="是" '+(draft.isDefault!=='否'?'selected':'')+'>是</option><option value="否" '+(draft.isDefault==='否'?'selected':'')+'>否</option></select></label><label><span>版本号</span><input value="'+esc(draft.versions[0].v)+'" disabled></label><label class="full"><span>'+descLabel+'</span><textarea data-os-field="description" placeholder="请输入">'+esc(draft.description)+'</textarea></label></div></section>'}
function editStage(){const d=state.edit.draft,n=d.nodes[state.selectedNode]||d.nodes[0];return editHead(state.edit.kind==='new'?'新增商机阶段模板':state.edit.kind==='copy'?'复制商机阶段模板':'编辑商机阶段模板新版本','发布后只影响后续新建商机,历史商机仍保留原模板版本。')+baseForm(d,'stage')+'<section class="os-card os-stage-editor"><div class="os-subtitle"><h3>阶段编辑区</h3><span class="os-drag-tip">拖拽节点调整顺序;点击节点间+插入新节点</span></div><div class="os-stage-canvas"><div class="os-flow">'+d.nodes.map((x,i)=>'<div draggable="'+(!x[3])+'" class="os-flow-node os-stage-node '+(i===state.selectedNode?'active ':'')+(x[3]?'fixed':'')+'" data-os="stage-node" data-os-node="'+i+'"><b>'+(i+1)+'. '+esc(x[1])+(x[3]?' <span class="os-badge fixed">固定</span>':'')+'</b><small>'+esc(x[2])+'</small>'+(!x[3]?'<span class="os-node-tools"><button data-os="node-left" title="左移">←</button><button data-os="node-right" title="右移">→</button><button data-os="node-del" title="删除">×</button></span>':'')+'</div>'+(i<d.nodes.length-1?'<span class="os-stage-gap"><i class="os-flow-arrow">›</i><button data-os="node-insert" data-index="'+(i+1)+'" title="在此插入阶段节点">+</button></span>':'')).join('')+'</div></div><div class="os-node-editor"><div class="os-node-form"><label><span>当前选中节点</span><input value="'+esc(n[1])+'" disabled></label><label><span>阶段字典值</span><select data-os-node-field="key" '+(n[3]?'disabled':'')+'>'+stageDictionary.filter(x=>!x[0].includes('FIXED')).map(x=>'<option value="'+x[0]+'" '+(n[0]===x[0]?'selected':'')+'>'+esc(x[1])+'('+x[0]+')</option>').join('')+'</select></label><label><span>自定义节点名称</span><input data-os-node-field="name" value="'+esc(n[1])+'" '+(n[3]?'disabled':'')+'></label><label><span>阶段工作目标</span><textarea data-os-node-field="goal" '+(n[3]?'disabled':'')+'>'+esc(n[2])+'</textarea></label><div>'+(!n[3]?'<button class="os-btn primary" data-os="node-save">保存当前选中阶段</button>':'<span class="os-muted">固定节点不可编辑、删除或跨越排序。</span>')+'</div></div><aside class="os-node-dict"><h4>商机阶段字典</h4>'+stageDictionary.slice(4).map(x=>'<div class="os-dict-choice"><div><b>'+esc(x[1])+'('+x[0]+')</b>'+esc(x[2])+'</div></div>').join('')+'<button class="os-text-btn" data-os="dict-tab">编辑数据字典</button></aside></div></section>' }
function pushStepBody(c,editing,scheme){const disabled=editing?'':'disabled';if(state.step===0)return '<h4>第一步:信息完整度查验</h4><p class="os-muted" style="margin:0 0 12px">选择方案卡模板并配置校验字段;未通过时阻止推送,并逐项提示销售补充。</p><div class="os-push-check-head"><label>引用方案卡模板 <select data-os-push="scheme" '+disabled+'><option>'+esc(scheme||'公司通用方案卡 V1.1')+'</option><option>桂冠天夏战队方案卡 V2.0</option></select></label><span><b>未通过时</b> 阻止推送并提示补充</span></div><table class="os-check-table"><thead><tr><th>校验</th><th>字段名称</th><th>字段来源</th><th>校验要求</th></tr></thead><tbody>'+['客户名称','客户类型','客户联系人','项目阶段','招标形式','现场勘察资料','方案卡'].map(x=>'<tr><td><input type="checkbox" '+(c.checks.includes(x)?'checked':'')+' '+disabled+' data-os-check="'+x+'"></td><td><b>'+x+'</b></td><td>'+ (x==='方案卡'?'方案卡模板':'商机 / 客户')+'</td><td>'+ (x==='现场勘察资料'?'必须上传附件':'字段必填')+'</td></tr>').join('')+'</tbody></table>';
if(state.step===1)return '<h4>第二步:项目查重</h4><p class="os-muted" style="margin:0 0 12px">方案推送作为“商机转项目”入口调用独立查重规则;入口不再覆盖命中处理,避免出现两套口径。</p><div class="os-two-col"><div class="os-panel"><h4>引用规则</h4><label class="os-line">项目查重规则 <select data-os-push="duplicate" '+disabled+'><option>'+esc(c.duplicate)+'</option><option>设计阶段项目查重规则 V1.0</option></select></label><label class="os-line">当前调用入口 <input value="商机转项目" disabled></label><label class="os-line">结果处理 <input value="按查重规则执行异常检测与处理(不可在此覆盖)" disabled></label></div><div class="os-panel"><h4>当前规则摘要</h4><div class="os-mini-kv"><span>参与检测状态</span><span>进行中、已中止/暂停</span><span>检索字段</span><span>项目名称 + 项目区域 + 主要意向客户 + 甲方</span><span>名称匹配</span><span>阈值按字段独立配置</span><span>最终结果</span><span>强制拦截 / 异常确认 / 正常创建</span></div></div></div>';
if(state.step===2)return '<h4>第三步:创建项目</h4><p class="os-muted" style="margin:0 0 12px">校验通过后生成 CRM 项目;默认进入确认页,避免直接创建错误数据。</p><div class="os-two-col"><div class="os-panel"><h4>创建方式</h4><label class="os-line">项目创建方式 <select data-os-push="create" '+disabled+'><option>'+esc(c.create)+'</option><option>校验通过后自动创建</option></select></label><label class="os-line">项目名称来源 <select '+disabled+'><option>使用商机名称</option><option>使用自定义名称模板</option></select></label></div><div class="os-panel"><h4>字段映射</h4><table class="os-check-table"><thead><tr><th>项目字段</th><th>数据来源</th></tr></thead><tbody>'+c.mapping.map(x=>'<tr><td>'+x[0]+'</td><td>'+x[1]+'</td></tr>').join('')+'</tbody></table></div></div>';
return '<h4>第四步:推送场景平台</h4><p class="os-muted" style="margin:0 0 12px">CRM 项目创建成功后,可按规则继续推送场景平台;推送失败不回滚 CRM 项目,记录原因后可重新推送。</p><div class="os-two-col"><div class="os-panel"><h4>推送开关与名称</h4><label class="os-line"><input type="checkbox" '+(c.platform?'checked':'')+' '+disabled+' data-os-push-check="platform">创建后推送场景平台</label><label class="os-line">场景平台项目名称 <select data-os-push="platformName" '+disabled+'><option>'+esc(c.platformName)+'</option><option>使用名称模板</option></select></label></div><div class="os-panel"><h4>备注与失败处理</h4><label class="os-line">推送备注 <select data-os-push="note" '+disabled+'><option>'+esc(c.note)+'</option><option>必填</option><option>不要求</option></select></label><p class="os-muted">失败时保留 CRM 项目及失败原因,有权限用户可重新推送。</p></div></div>'}
function editPush(){const d=state.edit.draft;return editHead(state.edit.kind==='new'?'新增方案推送规则':state.edit.kind==='copy'?'复制方案推送规则':'编辑方案推送规则新版本','发布后只影响后续方案推送,历史执行记录继续保留原规则版本。')+baseForm(d,'push')+'<section class="os-card" style="margin-top:12px"><div class="os-subtitle"><h3>推送流程编辑</h3><span class="os-badge draft">'+d.versions[0].v+' 草稿</span></div><div class="os-stepper">'+[['信息完整度查验','校验字段与附件'],['项目查重','引用独立查重规则'],['创建项目','确认创建方式与映射'],['推送场景平台','名称、备注与失败处理']].map((x,i)=>'<button class="os-step '+(state.step===i?'active':'')+'" data-os="push-step" data-step="'+i+'"><span class="n">'+(i+1)+'</span><b>'+x[0]+'</b><small>'+x[1]+'</small></button>').join('')+'</div><div class="os-step-body">'+pushStepBody(d.config,true,d.scheme)+'</div></section>'}
function editPool(){const d=state.edit.draft;return editHead(state.edit.kind==='new'?'新增公海与提醒规则':state.edit.kind==='copy'?'复制公海与提醒规则':'编辑公海与提醒规则新版本','发布后只影响后续进入公海及回收判断,历史商机保留原规则口径。')+baseForm(d,'pool')+'<section class="os-card" style="margin-top:12px"><div class="os-subtitle"><h3>公海与提醒规则配置</h3></div>'+poolCards(d.config,true)+'<div class="os-fixed-note">有效跟进:商机详情有任何形式的跟进记录更新、附件上传和字段编辑修订,均被认可为有效跟进。</div></section>'}
function editScheme(){const d=state.edit.draft;return editHead(state.edit.kind==='new'?'新增方案卡模板':state.edit.kind==='copy'?'复制方案卡模板':'编辑方案卡模板新版本','已发布模板不可直接覆盖;发布新版本后仅影响后续新建或重新填写的方案卡。')+baseForm(d,'scheme')+'<section class="os-card" style="margin-top:12px"><div class="os-subtitle"><h3>模板编辑区</h3><button class="os-btn" data-os="scheme-add">+ 新增字段</button></div>'+fieldTable(d.fields,true)+'</section>'}
function dedupeEditBody(d){
dedupeDefaults(d);
if(state.step===0){
const statuses=['进行中','已中止/暂停','已结项','已关闭'];
return '<h4>第一步:设置触发场景与检测范围</h4><p class="os-muted" style="margin:0 0 12px">选择在哪些业务操作中调用本规则,以及哪些状态的已有项目参与检测。</p>'
+'<div class="os-config-stack"><section class="os-panel"><h4>触发场景</h4><p class="os-muted">选择在哪些业务操作中自动调用本规则。</p><div class="os-check-list horizontal">'
+['商机转项目','直接创建项目'].map(x=>'<label><input type="checkbox" data-os-dup-entry="'+x+'" '+(d.entryPoints.includes(x)?'checked':'')+'> '+x+'</label>').join('')
+'</div></section><section class="os-panel"><h4>参与检测的项目状态</h4><p class="os-muted">系统仅检索所选状态的已有项目;未选状态不参与本次异常检测。</p><div class="os-check-list horizontal">'
+statuses.map(x=>'<label><input type="checkbox" data-os-dup-status="'+x+'" '+(d.projectStatuses.includes(x)?'checked':'')+'> '+x+'</label>').join('')
+'</div></section></div>'
}
if(state.step===1){
const catalog=dedupeFieldCatalog(),used=new Set(d.detectionConditions.map(x=>x.field)),available=catalog.filter(x=>!x.fixed&&!used.has(x.field));
return '<h4>第二步:配置关联项目检索条件</h4><p class="os-muted" style="margin:0 0 12px">管理员可从系统支持的项目字段中添加条件,并分别设置完全一致或名称相似及其阈值。</p>'
+'<table class="os-check-table os-editable-rule-table"><thead><tr><th>检索字段</th><th>数据来源</th><th>匹配方式</th><th>相似度阈值</th><th>操作</th></tr></thead><tbody>'
+d.detectionConditions.map((x,i)=>'<tr><td><b>'+esc(x.field)+'</b>'+(x.fixed?' <span class="os-badge fixed">系统固定</span>':'')+'</td><td>'+esc(x.source)+'</td><td><select data-os-dup-cond-match="'+i+'" '+(x.fixed?'disabled':'')+'><option '+(x.match==='完全一致'?'selected':'')+'>完全一致</option><option '+(x.match==='名称相似'?'selected':'')+'>名称相似</option></select></td><td>'+(x.match==='名称相似'?'<input class="os-inline-number" type="number" min="1" max="100" data-os-dup-cond-threshold="'+i+'" value="'+x.threshold+'"> %':'<span class="os-muted">—</span>')+'</td><td class="os-actions">'+(x.fixed?'<span class="os-muted">不可删除</span>':'<button class="danger" data-os="dup-condition-del" data-index="'+i+'">删除</button>')+'</td></tr>').join('')
+'</tbody></table><div class="os-add-rule-row"><select id="osDupAddField" '+(available.length?'':'disabled')+'>'+(available.length?available.map(x=>'<option value="'+x.field+'">'+x.field+'</option>').join(''):'<option>暂无可添加字段</option>')+'</select><button class="os-btn" data-os="dup-condition-add" '+(available.length?'':'disabled')+'>+ 添加检索条件</button></div>'
+'<div class="os-fixed-note" style="margin-top:12px">来源商机关系由系统固定提供;其他字段可自行增删,并独立设置匹配方式和相似度阈值。</div>'
}
if(state.step===2){
const fields=d.detectionConditions.map(x=>x.field);
return '<h4>第三步:配置异常关系判定规则</h4><p class="os-muted" style="margin:0 0 12px">在同一张规则列表中维护条件表达式与研判结论;点击“编辑条件”时,仅展开当前规则。</p>'
+'<table class="os-check-table os-rule-matrix"><thead><tr><th>排序</th><th>条件表达式</th><th>输出研判结论</th><th>操作</th></tr></thead><tbody>'
+d.relationRules.map((r,i)=>{const expression=normalizeDedupeExpression(r),open=state.dupOpenRuleId===r.id,summary=dedupeExprText(expression,true);return '<tr><td><div class="os-rule-order"><b>'+(i+1)+'</b><i>/ '+d.relationRules.length+'</i></div></td><td><div class="os-expression-summary"><span class="os-expression-text '+(summary==='未配置条件'?'empty':'')+'">'+esc(summary)+'</span></div></td><td><input class="os-rule-output-input" data-os-dup-rule-name="'+i+'" value="'+esc(r.name)+'" aria-label="输出研判结论"></td><td><div class="os-rule-row-actions"><button class="os-text-btn" data-os="dup-rule-toggle" data-index="'+i+'">'+(open?'收起条件':'编辑条件')+'</button><button class="os-text-btn" data-os="dup-rule-up" data-index="'+i+'">上移</button><button class="os-text-btn" data-os="dup-rule-down" data-index="'+i+'">下移</button><button class="os-text-btn danger" data-os="dup-rule-del" data-index="'+i+'">删除</button></div></td></tr>'+(open?'<tr class="os-rule-editor-row"><td colspan="4"><div class="os-expression-editor"><div class="os-expression-editor-head"><div><b>规则 '+(i+1)+' 条件表达式</b><span>通过“且 / 或”连接条件;添加条件组即可形成括号,并支持继续嵌套。</span></div><button class="os-btn" data-os="dup-rule-toggle" data-index="'+i+'">完成并收起</button></div><div class="os-expression-editor-preview"><strong>系统解析:</strong>'+esc(summary)+'</div>'+dedupeExprGroupHtml(expression,i,'root',fields,0)+'<div class="os-expression-help"><b>说明:</b>系统按照括号层级优先计算;每条候选项目仍独立套用本规则,不跨项目拼接命中条件。</div></div></td></tr>':'')}).join('')
+'</tbody></table><button class="os-btn primary" style="margin-top:12px" data-os="dup-rule-add">+ 新增判定规则</button>'
+'<div class="os-fixed-note" style="margin-top:12px">规则按列表顺序执行;复杂组合可通过“添加条件组”形成括号嵌套。</div>'
}
const relationNames=d.relationRules.map(x=>x.name).filter(Boolean);
const rows=dedupeTreatmentRows(d);
return '<h4>第四步:配置研判结果对应的处理方式</h4><p class="os-muted" style="margin:0 0 12px">选择哪些研判结论进入强制拦截、异常确认或正常创建;系统动作由平台固定执行。</p>'
+'<table class="os-check-table os-treatment-table"><thead><tr><th>处理情形</th><th>系统动作</th><th>适用研判结论</th></tr></thead><tbody>'
+rows.map(x=>'<tr><td><span class="os-risk-tag '+x.tone+'">'+x.name+'</span></td><td>'+esc(x.action)+'</td><td><div class="os-treatment-options">'+relationNames.map(n=>'<label><input type="checkbox" data-os-dup-treatment="'+x.key+'" data-relation="'+esc(n)+'" '+(x.relations.includes(n)?'checked':'')+'> '+esc(n)+'</label>').join('')+(x.key==='normal'?'<label><input type="checkbox" data-os-dup-treatment="normal" data-relation="无明显冲突" '+(x.relations.includes('无明显冲突')?'checked':'')+'> 无明显冲突</label>':'')+'</div></td></tr>').join('')
+'</tbody></table><div class="os-fixed-note" style="margin-top:12px">正常创建不等于没有异常。异地项目、预算不明确、甲方不明确等标签类结论,可正常创建并同步打上对应标签。</div>'
}
function editDuplicate(){
const d=dedupeDefaults(state.edit.draft);
const steps=[['触发场景','设置检测范围'],['关联项目检索','查找可能关联记录'],['异常关系分析','识别冲突风险及研判结论'],['处理策略','根据研判结果执行对应处置']];
return editHead(state.edit.kind==='new'?'新增项目查重规则':state.edit.kind==='copy'?'复制项目查重规则':'编辑项目查重规则新版本','系统筛查并初判;明确重复直接拦截,其余异常由跟单专员介入裁决。')
+baseForm(d,'duplicate')
+'<section class="os-card" style="margin-top:12px"><div class="os-subtitle"><h3>异常检测与查重规则编辑</h3><span class="os-badge draft">'+d.versions[0].v+' 草稿</span></div><div class="os-stepper">'+steps.map((x,i)=>'<button class="os-step '+(state.step===i?'active':'')+'" data-os="dup-step" data-step="'+i+'"><span class="n">'+(i+1)+'</span><b>'+x[0]+'</b><small>'+x[1]+'</small></button>').join('')+'</div><div class="os-step-body">'+dedupeEditBody(d)+'</div></section>'
}
function dictPage(){const groups=data.dict,isGroups=state.dictView==='groups',values=groups.flatMap(g=>g.values.map((v,i)=>({id:g.id+'-'+v[1],name:v[0],code:v[1],group:g.name,groupCode:g.code,sort:i+1,status:'启用',description:v[2]})));const action=isGroups?'<button class="os-btn primary" data-os="dict-new">+ 新增分组</button>':'<button class="os-btn primary" data-os="dict-add-value">+ 新增数据字典</button>';const groupTable='<section class="os-card"><div class="os-toolbar"><div class="os-query"><input placeholder="分组名称 / 编码"><button class="os-btn primary" data-os="dict-query">查询</button><button class="os-btn" data-os="dict-reset">重置</button></div>'+action+'</div><div class="os-table-wrap"><table class="os-table"><thead><tr><th>序号</th><th>分组名称</th><th>分组编码</th><th>描述</th><th>排序</th><th>状态</th><th>操作</th></tr></thead><tbody>'+groups.map((g,i)=>'<tr><td>'+ (i+1)+'</td><td><b>'+esc(g.name)+'</b></td><td><code>'+esc(g.code)+'</code></td><td>'+esc(g.description)+'</td><td>'+g.sort+'</td><td>'+badge(g.status==='启用'?'发布中':'已停用')+'</td><td class="os-actions"><button data-os="dict-edit">编辑</button><button class="danger">停用</button></td></tr>').join('')+'</tbody></table></div>'+pager(groups.length)+'</section>';const valueTable='<section class="os-card"><div class="os-toolbar"><div class="os-query"><input placeholder="字典名称 / 键值"><select><option>全部字典分组</option>'+groups.map(g=>'<option>'+esc(g.name)+'</option>').join('')+'</select><button class="os-btn primary" data-os="dict-query">查询</button><button class="os-btn" data-os="dict-reset">重置</button></div>'+action+'</div><div class="os-table-wrap"><table class="os-table"><thead><tr><th>序号</th><th>字典名称</th><th>字典键值</th><th>所属分组</th><th>排序</th><th>状态</th><th>描述</th><th>操作</th></tr></thead><tbody>'+values.map((v,i)=>'<tr><td>'+ (i+1)+'</td><td><b>'+esc(v.name)+'</b></td><td><code>'+esc(v.code)+'</code></td><td>'+esc(v.group)+'</td><td>'+v.sort+'</td><td>'+badge(v.status==='启用'?'发布中':'已停用')+'</td><td>'+esc(v.description)+'</td><td class="os-actions"><button data-os="dict-edit">编辑</button><button class="danger">停用</button></td></tr>').join('')+'</tbody></table></div>'+pager(values.length)+'</section>';return head('商机相关字典','分别维护字典分组与数据字典;业务页面只引用启用的数据字典。','')+'<div class="os-dict-tabs"><button class="'+(isGroups?'active':'')+'" data-os="dict-view" data-view="groups">字典分组</button><button class="'+(!isGroups?'active':'')+'" data-os="dict-view" data-view="values">数据字典</button></div>'+(isGroups?groupTable:valueTable)}
function renderMain(){const type=state.tab;if(type==='dict')return dictPage();if(state.modes[type]==='detail'){return type==='stage'?detailStage():type==='push'?detailPush():type==='pool'?detailPool():type==='scheme'?detailScheme():detailDuplicate()}if(state.modes[type]==='edit'){return type==='stage'?editStage():type==='push'?editPush():type==='pool'?editPool():type==='scheme'?editScheme():editDuplicate()}return type==='stage'?listStage():type==='scheme'?listScheme():type==='push'?listPush():type==='pool'?listPool():listRule('duplicate','项目查重规则',{head:'<th>触发场景</th><th>判定方式</th><th>引用数量</th>',cell:(i,v)=>'<td>'+esc((i.entryPoints||['商机转项目','直接创建项目']).join('、'))+'</td><td>分级组合判定</td><td>'+v.used+' 套</td>'})}
function settingLabel(){return state.tab==='stage'?'商机阶段设置':state.tab==='scheme'?'方案卡模板':state.tab==='push'?'方案推送规则':state.tab==='pool'?'公海与提醒规则':state.tab==='duplicate'?'项目查重规则':'商机相关字典'}
function backToList(){if(state.tab!=='dict')state.modes[state.tab]='list';state.detail=null;state.edit=null;state.step=0;render()}
function backToSettingsHome(){state.tab='stage';state.modes.stage='list';state.detail=null;state.edit=null;state.step=0;state.dictView='groups';render()}
globalThis.oppSettingsBackToList=backToList;
globalThis.oppSettingsBackToSettingsHome=backToSettingsHome;
function crumb(){const bar=document.querySelector('.location-bar');if(!bar)return;const config=settingLabel(),inObject=state.tab!=='dict'&&(state.modes[state.tab]==='detail'||state.modes[state.tab]==='edit'),objectLabel=state.modes[state.tab]==='detail'?current(state.tab,state.detail.id).name:state.modes[state.tab]==='edit'?(state.edit.kind==='new'?'新增':state.edit.kind==='copy'?'复制':'编辑')+config:'';bar.innerHTML='<span class="crumb-parent">商机</span><span class="crumb-sep">›</span><button class="crumb-btn" onclick="oppSettingsBackToSettingsHome()">商机管理设置</button><span class="crumb-sep">›</span>'+(inObject?'<button class="crumb-btn" onclick="oppSettingsBackToList()">'+esc(config)+'</button><span class="crumb-sep">›</span><span class="crumb-current">'+esc(objectLabel)+'</span>':'<span class="crumb-current">'+esc(config)+'</span>')}
function render(){const root=document.getElementById('oppSettingRoot');if(!root)return;const detail=state.tab!=='dict'&&state.modes[state.tab]==='detail';root.innerHTML=detail?'<div class="os-page os-page-detail"><main class="os-main">'+renderMain()+'</main></div>':'<div class="os-page"><header class="os-head"><div><h1>商机管理设置</h1><p>围绕“市场线索 → 商机销售任务 → 正式立项”阶段,维护商机推进工作指引、方案卡模板、方案推送与查重规则及商机公海规则。</p></div></header><div class="os-layout">'+nav()+'<main class="os-main">'+renderMain()+'</main></div></div>';crumb();bindDrag(root)}
function publish(){const e=state.edit,d=e.draft;d.versions[0].status='发布中';d.versions[0].updated=now;d.versions.slice(1).forEach(x=>{if(x.status==='发布中')x.status='已停用'});const arr=data[e.type],i=arr.findIndex(x=>x.id===e.origin||x.id===d.id);if(i<0)arr.push(d);else arr[i]=d;state.active[e.type]=d.id;state.modes[e.type]='list';state.edit=null;toast('已发布 '+d.name+' '+d.versions[0].v);render()}
function save(){const e=state.edit,d=e.draft;d.versions[0].status='草稿';d.versions[0].updated=now;const arr=data[e.type],i=arr.findIndex(x=>x.id===e.origin||x.id===d.id);if(i<0)arr.push(d);else arr[i]=d;e.origin=d.id;e.kind='edit';state.active[e.type]=d.id;toast('草稿已保存,尚未影响已发布版本')}
function cancel(){state.modes[state.tab]='list';state.edit=null;render()}
function remove(type,id,vv){const item=current(type,id),v=version(item,vv);if(v.status!=='草稿'||v.used!==0){toast('仅未使用的草稿可删除');return}if(!confirm('确定删除“'+item.name+'”的 '+v.v+' 草稿吗?'))return;item.versions=item.versions.filter(x=>x.v!==v.v);if(!item.versions.length){const list=data[type],i=list.findIndex(x=>x.id===id);if(i>-1)list.splice(i,1)}state.active[type]=data[type][0]?.id;toast('草稿已删除');render()}
function query(type){const f=state.filters[type];f.q=document.getElementById('os-q-'+type)?.value.trim()||'';f.scope=document.getElementById('os-scope-'+type)?.value||'';f.status=document.getElementById('os-status-'+type)?.value||'';render()}
function reset(type){state.filters[type]={q:'',scope:'',status:''};render()}
function nodeMove(delta){const e=state.edit,a=e.draft.nodes,i=state.selectedNode,n=i+delta;if(!a[i]||!a[n]||a[i][3]||a[n][3])return;[a[i],a[n]]=[a[n],a[i]];state.selectedNode=n;render()}
const stageDictionary=[['OPP_STAGE_01','客户圈定','确认甲方、主要意向客户及商机基本归属'],['OPP_STAGE_02','关系摸排','明确客户关系及商机决策链'],['OPP_STAGE_03','资料采集','补齐需求、预算、现场资料及项目材料'],['OPP_STAGE_04','方案卡','基于实际需求填写方案卡,形成项目基础档案'],['OPP_STAGE_05','拟立项','已具备创建或关联正式项目的条件'],['OPP_STAGE_06','技术交流','需要独立记录技术交流过程的可选阶段'],['OPP_STAGE_07','竞品打压','从各可能场景影响客户对竞品的印象']];
function stageModal(){const mask=document.getElementById('modal'),panel=mask?.querySelector('.modal');if(!mask||!panel)return null;panel.classList.add('opp-modal-wide');return {mask,panel}}
function addNode(at){const modal=stageModal();if(!modal)return;const used=new Set(state.edit.draft.nodes.map(x=>x[0]));state.insertAt=Number.isInteger(at)?at:Math.max(0,state.edit.draft.nodes.length-1);const choices=stageDictionary.filter(x=>!used.has(x[0]));document.getElementById('modalTitle').textContent='在第 '+(state.insertAt+1)+' 个位置插入阶段节点';modal.mask.querySelector('.m-body').innerHTML='<div class="os-insert-picker"><p>从“商机阶段字典”选择节点;已在当前模板使用的阶段不可重复插入。</p>'+choices.map((x,i)=>'<label><input type="radio" name="os-stage-choice" value="'+x[0]+'" '+(i===0?'checked':'')+'><span><b>'+esc(x[1])+'</b><small>'+esc(x[2])+'</small></span></label>').join('')+(choices.length?'':'<div class="os-empty">暂无可插入的阶段节点</div>')+'</div>';modal.mask.querySelector('.m-foot').innerHTML='<button class="btn" onclick="closeModal()">取消</button><button class="btn primary" '+(choices.length?'onclick="oppSettingsChooseStage()"':'disabled')+'>下一步</button>';modal.mask.classList.add('show')}
function chooseStage(){const key=document.querySelector('input[name="os-stage-choice"]:checked')?.value,entry=stageDictionary.find(x=>x[0]===key),modal=stageModal();if(!entry||!modal)return;state.pendingStage=copy(entry);document.getElementById('modalTitle').textContent='编辑阶段节点';modal.mask.querySelector('.m-body').innerHTML='<div class="os-modal-form"><label>阶段字典</label><input value="'+esc(entry[1])+'('+entry[0]+')" disabled><label><i class="os-required">*</i> 自定义节点名称</label><input id="osPendingStageName" value="'+esc(entry[1])+'"><label><i class="os-required">*</i> 阶段工作目标</label><textarea id="osPendingStageGoal">'+esc(entry[2])+'</textarea></div>';modal.mask.querySelector('.m-foot').innerHTML='<button class="btn" onclick="oppSettingsAddStageBack()">上一步</button><button class="btn primary" onclick="oppSettingsConfirmStage()">确认插入</button>';modal.mask.classList.add('show')}
function confirmStage(){const p=state.pendingStage;if(!p)return;const name=document.getElementById('osPendingStageName')?.value.trim(),goal=document.getElementById('osPendingStageGoal')?.value.trim();if(!name||!goal){toast('请补充节点名称和阶段工作目标');return}const nodes=state.edit.draft.nodes;if(nodes.some(x=>x[0]===p[0])){toast('该阶段已在当前模板中使用');return}nodes.splice(state.insertAt,0,[p[0],name,goal]);state.selectedNode=state.insertAt;state.pendingStage=null;closeModal();render()}
globalThis.oppSettingsChooseStage=chooseStage;
globalThis.oppSettingsConfirmStage=confirmStage;
globalThis.oppSettingsAddStageBack=()=>addNode(state.insertAt);
function delNode(){const a=state.edit.draft.nodes,n=a[state.selectedNode];if(!n||n[3])return;a.splice(state.selectedNode,1);state.selectedNode=Math.max(0,state.selectedNode-1);render()}
function bindDrag(root){root.querySelectorAll('.os-stage-node[draggable="true"]').forEach(node=>{node.addEventListener('dragstart',e=>{state.dragIndex=Number(node.dataset.osNode);node.classList.add('dragging');e.dataTransfer.effectAllowed='move'});node.addEventListener('dragend',()=>root.querySelectorAll('.os-stage-node').forEach(x=>x.classList.remove('dragging','drop-before','drop-after')));node.addEventListener('dragover',e=>{e.preventDefault();const r=node.getBoundingClientRect();node.classList.toggle('drop-before',e.clientX<r.left+r.width/2);node.classList.toggle('drop-after',e.clientX>=r.left+r.width/2)});node.addEventListener('dragleave',()=>node.classList.remove('drop-before','drop-after'));node.addEventListener('drop',e=>{e.preventDefault();const from=state.dragIndex,to=Number(node.dataset.osNode),a=state.edit?.draft.nodes;if(!a||!Number.isInteger(from)||from===to||a[to]?.[3])return;const moved=a.splice(from,1)[0],r=node.getBoundingClientRect();let target=e.clientX<r.left+r.width/2?to:to+1;if(from<target)target--;a.splice(target,0,moved);state.selectedNode=target;render()})})}
function onClick(e){const root=e.target.closest('#oppSettingRoot');if(!root)return;const b=e.target.closest('[data-os]');if(!b)return;const a=b.dataset.os,t=b.dataset.type;if(a==='tab'){state.tab=b.dataset.tab;state.detail=null;state.edit=null;if(state.tab!=='dict')state.modes[state.tab]='list';render();return}if(a==='query')return query(t);if(a==='reset')return reset(t);if(a==='new')return editStart(t,'new');if(a==='detail'){state.active[t]=b.dataset.id;state.detail={id:b.dataset.id,version:b.dataset.version};state.detailNode=0;state.detailTab='content';state.modes[t]='detail';state.step=0;render();return}if(a==='edit')return editStart(t,'edit',b.dataset.id,b.dataset.version);if(a==='copy')return editStart(t,'copy',b.dataset.id);if(a==='back'){state.modes[t]='list';state.detail=null;render();return}if(a==='detail-tab'){state.detailTab=b.dataset.tab;render();return}if(a==='cancel-edit')return cancel();if(a==='save-draft')return save();if(a==='publish')return publish();if(a==='remove')return remove(t,b.dataset.id,b.dataset.version);if(a==='push-step'){state.step=Number(b.dataset.step);render();return}if(a==='dup-step'){state.step=Number(b.dataset.step);if(state.step!==2)state.dupOpenRuleId=null;render();return}if(a==='dup-condition-add'){const d=dedupeDefaults(state.edit.draft),field=document.getElementById('osDupAddField')?.value,meta=dedupeFieldCatalog().find(x=>x.field===field);if(!meta)return;d.detectionConditions.push({id:'dc-'+Date.now(),field:meta.field,source:meta.source,match:'完全一致',threshold:100});render();return}if(a==='dup-condition-del'){const d=dedupeDefaults(state.edit.draft);d.detectionConditions.splice(Number(b.dataset.index),1);render();return}if(a==='dup-rule-add'){const d=dedupeDefaults(state.edit.draft),field=d.detectionConditions[0]?.field||'来源商机关系',id='rr-'+Date.now();d.relationRules.push({id,name:'新研判结论',expression:{type:'group',children:[{type:'condition',field,value:dedupeValueOptions(field)[0]}]}});state.dupOpenRuleId=id;render();return}if(a==='dup-rule-toggle'){const d=dedupeDefaults(state.edit.draft),r=d.relationRules[Number(b.dataset.index)];if(!r)return;state.dupOpenRuleId=state.dupOpenRuleId===r.id?null:r.id;render();return}if(a==='dup-rule-del'){const d=dedupeDefaults(state.edit.draft),r=d.relationRules[Number(b.dataset.index)];if(r&&state.dupOpenRuleId===r.id)state.dupOpenRuleId=null;d.relationRules.splice(Number(b.dataset.index),1);render();return}if(a==='dup-rule-up'||a==='dup-rule-down'){const d=dedupeDefaults(state.edit.draft),i=Number(b.dataset.index),j=i+(a==='dup-rule-up'?-1:1);if(j<0||j>=d.relationRules.length)return;[d.relationRules[i],d.relationRules[j]]=[d.relationRules[j],d.relationRules[i]];render();return}if(a==='dup-expr-add-condition'||a==='dup-expr-add-group'){const d=dedupeDefaults(state.edit.draft),r=d.relationRules[Number(b.dataset.ruleIndex)],root=normalizeDedupeExpression(r),group=dedupeExprNode(root,b.dataset.path);if(!group||group.type!=='group')return;const field=d.detectionConditions[0]?.field||'来源商机关系',join=group.children.length?'and':undefined;group.children.push(a==='dup-expr-add-group'?{type:'group',join,children:[{type:'condition',field,value:dedupeValueOptions(field)[0]}]}:{type:'condition',join,field,value:dedupeValueOptions(field)[0]});dedupeExprNormalizeGroup(root);render();return}if(a==='dup-expr-del'){const d=dedupeDefaults(state.edit.draft),r=d.relationRules[Number(b.dataset.ruleIndex)],root=normalizeDedupeExpression(r),found=dedupeExprParent(root,b.dataset.path);if(!found.parent||found.index===undefined)return;found.parent.children.splice(found.index,1);dedupeExprNormalizeGroup(root);render();return}if(a==='dup-expr-up'||a==='dup-expr-down'){const d=dedupeDefaults(state.edit.draft),r=d.relationRules[Number(b.dataset.ruleIndex)],root=normalizeDedupeExpression(r),found=dedupeExprParent(root,b.dataset.path),i=found.index,j=i+(a==='dup-expr-up'?-1:1);if(!found.parent||j<0||j>=found.parent.children.length)return;[found.parent.children[i],found.parent.children[j]]=[found.parent.children[j],found.parent.children[i]];dedupeExprNormalizeGroup(root);render();return}if(a==='dup-preview'){state.dupPreview=b.dataset.mode||'convert';state.dupOutcome=null;render();return}if(a==='dup-case'){state.dupCase=b.dataset.case||'conflict';state.dupOutcome=null;render();return}if(a==='dup-continue-create'){state.dupOutcome='created';render();toast('项目已创建并进入【冲突处理】,已通知跟单专员');return}if(a==='dup-cancel-create'){toast('已取消本次项目创建');return}if(a==='dup-view-existing'){toast('已打开异常关联项目详情(Demo)');return}if(a==='dup-reset-outcome'){state.dupOutcome=null;render();return}if(a==='detail-stage-node'){state.detailNode=Number(b.dataset.index);render();return}if(a==='stage-node'){state.selectedNode=Number(b.dataset.index||b.dataset.osNode);render();return}if(a==='node-left')return nodeMove(-1);if(a==='node-right')return nodeMove(1);if(a==='node-add')return addNode();if(a==='node-insert')return addNode(Number(b.dataset.index));if(a==='node-del')return delNode();if(a==='node-save'){toast('当前节点配置已保存到草稿');return}if(a==='dict-tab'){state.tab='dict';state.dictView='groups';state.edit=null;state.detail=null;render();return}if(a==='dict-view'){state.dictView=b.dataset.view;render();return}if(a==='scheme-add'||a==='scheme-add-at'){const at=a==='scheme-add-at'?Number(b.dataset.index)+1:state.edit.draft.fields.length;state.edit.draft.fields.splice(at,0,['新增字段','单行文本',true,false]);render();return}if(a==='scheme-up'||a==='scheme-down'){const i=Number(b.dataset.index),j=i+(a==='scheme-up'?-1:1),f=state.edit.draft.fields;if(j<0||j>=f.length)return;[f[i],f[j]]=[f[j],f[i]];render();return}if(a==='scheme-del'){state.edit.draft.fields.splice(Number(b.dataset.index),1);render();return}if(a==='dict-select'){state.active.dict=b.dataset.id;render();return}if(a==='preview')return openSchemePreview();if(a==='crumb-root'){state.tab='stage';state.modes.stage='list';state.edit=null;state.detail=null;render();return}if(a==='dict-new'||a==='dict-add-value'){toast('已打开新增字典编辑入口(Demo)');return}if(a==='dict-edit'){toast('已打开字典编辑入口(Demo)');return}if(a==='dict-query'){toast('字典列表已按当前条件查询(Demo)');return}if(a==='dict-reset'){render()}}
function onInput(e){const root=e.target.closest('#oppSettingRoot');if(!root||!state.edit)return;const el=e.target;if(el.dataset.osField){state.edit.draft[el.dataset.osField]=el.value;return}if(el.dataset.osNodeField){const n=state.edit.draft.nodes[state.selectedNode];const map={key:0,name:1,goal:2};n[map[el.dataset.osNodeField]]=el.value;return}if(el.dataset.osPool){const c=state.edit.draft.config,k=el.dataset.osPool;c[k]=el.type==='checkbox'?el.checked:Number.isNaN(Number(el.value))?el.value:Number(el.value);if(el.type==='checkbox'&&(k==='auto'||k==='remind'))render();return}if(el.dataset.osPush){if(el.dataset.osPush==='scheme')state.edit.draft.scheme=el.value;else state.edit.draft.config[el.dataset.osPush]=el.value;return}if(el.dataset.osCheck){const c=state.edit.draft.config,x=el.dataset.osCheck;if(el.checked&&!c.checks.includes(x))c.checks.push(x);if(!el.checked)c.checks=c.checks.filter(v=>v!==x);return}if(el.dataset.osPushCheck){state.edit.draft.config[el.dataset.osPushCheck]=el.checked;return}if(el.dataset.osDupStatus){const d=dedupeDefaults(state.edit.draft),x=el.dataset.osDupStatus;if(el.checked&&!d.projectStatuses.includes(x))d.projectStatuses.push(x);if(!el.checked)d.projectStatuses=d.projectStatuses.filter(v=>v!==x);return}if(el.dataset.osDupCondMatch!==undefined){const d=dedupeDefaults(state.edit.draft),i=Number(el.dataset.osDupCondMatch);d.detectionConditions[i].match=el.value;if(el.value==='完全一致')d.detectionConditions[i].threshold=100;render();return}if(el.dataset.osDupCondThreshold!==undefined){const d=dedupeDefaults(state.edit.draft),i=Number(el.dataset.osDupCondThreshold);d.detectionConditions[i].threshold=Number(el.value)||0;return}if(el.dataset.osDupRuleName!==undefined){const d=dedupeDefaults(state.edit.draft),i=Number(el.dataset.osDupRuleName),old=d.relationRules[i].name,next=el.value;d.relationRules[i].name=next;Object.keys(d.treatmentMappings).forEach(k=>d.treatmentMappings[k]=d.treatmentMappings[k].map(x=>x===old?next:x));return}if(el.dataset.osDupExprJoin!==undefined){const d=dedupeDefaults(state.edit.draft),r=d.relationRules[Number(el.dataset.osDupExprJoin)],node=dedupeExprNode(normalizeDedupeExpression(r),el.dataset.path);if(!node)return;node.join=el.value==='or'?'or':'and';render();return}if(el.dataset.osDupExprField!==undefined){const d=dedupeDefaults(state.edit.draft),r=d.relationRules[Number(el.dataset.osDupExprField)],node=dedupeExprNode(normalizeDedupeExpression(r),el.dataset.path);if(!node||node.type!=='condition')return;node.field=el.value;node.value=dedupeValueOptions(el.value)[0];render();return}if(el.dataset.osDupExprValue!==undefined){const d=dedupeDefaults(state.edit.draft),r=d.relationRules[Number(el.dataset.osDupExprValue)],node=dedupeExprNode(normalizeDedupeExpression(r),el.dataset.path);if(!node||node.type!=='condition')return;node.value=el.value;render();return}if(el.dataset.osDupTreatment){const d=dedupeDefaults(state.edit.draft),k=el.dataset.osDupTreatment,x=el.dataset.relation,a=d.treatmentMappings[k]||[];if(el.checked&&!a.includes(x))a.push(x);if(!el.checked)d.treatmentMappings[k]=a.filter(v=>v!==x);return}if(el.dataset.osDup){const k=el.dataset.osDup;state.edit.draft[k]=k==='threshold'?Number(el.value):el.value;if(k==='matchMode')render();return}if(el.dataset.osDupField){const a=state.edit.draft.fields,x=el.dataset.osDupField;if(el.checked&&!a.includes(x))a.push(x);if(!el.checked)state.edit.draft.fields=a.filter(v=>v!==x)}}
document.addEventListener('click',onClick);document.addEventListener('input',onInput);document.addEventListener('change',onInput);
return {render,backToList};
})();
const oppSupplement=(function(){
let configTab='overview';
let ruleScenario='company';
const configTabs=[['overview','配置总览'],['track','工作节点设计'],['template','模板与字段'],['gate','流转与关口'],['pool','公海与提醒'],['state','状态与原因'],['release','规则发布与记录']];
function configNav(){return '<div class="opp-config-nav">'+configTabs.map(x=>'<button class="'+(configTab===x[0]?'active':'')+'" onclick="oppSupplement.switchConfigTab(\''+x[0]+'\')">'+x[1]+'</button>').join('')+'</div>'}
function overview(){return '<div class="opp-config-kpis"><div class="opp-config-kpi"><b>1</b><span>生效中的公司统一工作指引</span></div><div class="opp-config-kpi"><b>4</b><span>项目阶段模板</span></div><div class="opp-config-kpi"><b>6</b><span>已发布字段模板版本</span></div><div class="opp-config-kpi"><b>3</b><span>启用中的预警规则</span></div></div><div class="opp-config-note">商机管理设置只维护规则,不直接修改单条商机。项目阶段、工作节点、商机状态是三套不同口径:阶段反映业务成熟度,节点指导销售动作,状态控制是否继续推进。</div><div class="opp-config-grid"><section class="opp-config-card"><h3>当前生效规则 <button onclick="oppSupplement.switchConfigTab(\'track\')">查看工作指引</button></h3><div class="body"><div class="opp-config-list"><div><span>公司统一工作指引</span><small>客户圈定 → 关系摸排 → 资料采集 → 方案卡 → 已转项目</small></div><div><span>项目阶段</span><span class="tag">设计 / 改单 / 投标 / 抢标</span></div><div><span>招标形式</span><span class="tag">ZC / GC / BC / YB</span></div><div><span>适用范围</span><small>公司默认,战队可追加扩展工作指引</small></div></div></div></section><section class="opp-config-card"><h3>近期配置变更 <button onclick="oppSupplement.switchConfigTab(\'state\')">查看记录</button></h3><div class="body"><div class="opp-config-list"><div><span>方案卡推送校验 V1.2</span><small>昨日发布</small></div><div><span>广东15部扩展字段 V1.0</span><small>2026-07-08</small></div><div><span>投标阶段节点预警</span><small>2026-07-06</small></div><div><span>关闭原因字典</span><small>2026-07-03</small></div></div></div></section></div>'}
function track(){const nodes=[['客户圈定','确认甲方、意向客户及角色'],['关系摸排','明确关键人、设计院、总包关系'],['资料采集','补齐需求、预算、时间与附件'],['方案卡','形成方案资料并发起协同'],['已转项目','完成报备或项目关联']];return '<div class="opp-track-layout"><aside class="opp-track-list"><h3>工作指引模板</h3><button class="opp-track-template active">公司统一工作指引</button><button class="opp-track-template">设计阶段工作指引</button><button class="opp-track-template">投标阶段工作指引</button><button class="opp-track-template">广东15部扩展工作指引</button></aside><section class="opp-track-canvas"><div class="opp-track-canvas-head"><b>公司统一工作指引</b><span>适用:全部销售机会</span></div><div class="opp-track-flow">'+nodes.map((x,i)=>'<div class="opp-track-node '+(i===2?'active':'')+'"><b>'+(i+1)+'. '+x[0]+'</b><small>'+x[1]+'</small></div>'+(i<nodes.length-1?'<div class="opp-track-arrow">›</div>':'')).join('')+'</div><div class="opp-config-note" style="margin:18px 0 0">当前节点:资料采集。节点推进不是项目阶段切换;节点只定义销售当前工作及下一步,项目阶段仍按设计、改单、投标、抢标管理。</div></section><aside class="opp-track-detail"><h3>节点配置:资料采集</h3><div class="opp-field-row"><label>适用项目阶段</label><span>设计、改单</span></div><div class="opp-field-row"><label>预计停留 / 预警</label><span>7 天 / 第 5 天提醒负责人</span></div><div class="opp-field-row"><label>推进校验</label><span>需求描述、项目预算、主要意向客户</span></div><div class="opp-field-row"><label>下一节点</label><span>方案卡</span></div><div class="opp-field-row"><label>协同指引</label><span>可发起方案卡,通知方案支持人员</span></div></aside></div>'}
function template(){return '<table class="opp-template-table"><thead><tr><th>模板名称</th><th>构成</th><th>适用条件</th><th>当前版本</th><th>状态</th><th>操作</th></tr></thead><tbody><tr><td>公司通用字段</td><td>基础、客户、需求预算、商务时间</td><td>全部商机</td><td>V1.0</td><td><span class="status">已发布</span></td><td><button class="opp-text-link" onclick="oppModule.toast(\'查看公司通用字段\')">查看</button></td></tr><tr><td>设计阶段模板</td><td>设计需求、看现场、方案预算</td><td>项目阶段=设计</td><td>V1.2</td><td><span class="status">已发布</span></td><td><button class="opp-text-link" onclick="oppModule.toast(\'查看设计阶段字段\')">查看</button></td></tr><tr><td>招标形式条件字段</td><td>招标编号、标底方、投标资料</td><td>ZC / GC / BC / YB</td><td>V1.0</td><td><span class="status">已发布</span></td><td><button class="opp-text-link" onclick="oppModule.toast(\'查看招标形式条件字段\')">查看</button></td></tr><tr><td>战队扩展字段</td><td>部门备注、协同要求、特殊附件</td><td>广东15部</td><td>V1.0</td><td><span class="status">已发布</span></td><td><button class="opp-text-link" onclick="oppModule.toast(\'查看战队扩展字段\')">查看</button></td></tr></tbody></table><div class="opp-config-note" style="margin:14px 0 0">字段模板按“公司通用 + 项目阶段 + 招标形式条件 + 战队扩展”组合生效。已发布模板不可直接改写;规则变更应复制为新版本,商机创建时保存模板与组织快照。</div>'}
function gate(){const rows=[['创建商机','校验公司通用字段;匹配团队、战队、部门及模板版本','创建并保存模板快照'],['推进工作节点','校验当前节点的必填信息及前置节点','更新当前节点、下一节点、停留时间'],['推送方案卡','校验方案卡、关键附件及阶段条件','读取既有商机信息形成提交快照'],['创建/关联项目','校验项目阶段、客户关系及必要资料','创建或关联项目并写入操作日志'],['关闭/暂缓/抛公海','校验原因、说明及权限','限制后续动作并保留历史记录']];return '<div class="opp-gate-list">'+rows.map(x=>'<div class="opp-gate-row"><b>'+x[0]+'</b><p>'+x[1]+'<br><small>'+x[2]+'</small></p><button class="btn" onclick="oppModule.toast(\'查看'+x[0]+'规则\')">配置规则</button></div>').join('')+'</div>'}
function poolRule(){return '<div class="opp-rule-grid"><section class="opp-rule-card"><h3>入池规则</h3><label><input type="checkbox" checked> 超过 <input type="number" value="30"> 天未跟进自动入池</label><label><input type="checkbox" checked> 节点超期且无有效工作计划时预警</label><label><input type="checkbox"> 仅允许负责人主动释放</label><label><input type="checkbox" checked> 入池后保留客户、跟进、方案卡及附件</label></section><section class="opp-rule-card"><h3>领取与分配</h3><label><input type="checkbox" checked> 领取后自动成为主负责人</label><label><input type="checkbox" checked> 领取后保留原历史记录</label><label><input type="checkbox"> 领取需要直属负责人审批</label><label><input type="checkbox" checked> 管理员可按组织范围分配</label></section><section class="opp-rule-card"><h3>节点预警</h3><label><input type="checkbox" checked> 节点到期前提醒负责人</label><label><input type="checkbox" checked> 超期提醒负责人及直属管理者</label><label><input type="checkbox"> 自动创建催办工作计划</label></section><section class="opp-rule-card"><h3>提醒对象</h3><label><input type="checkbox" checked> 商机负责人</label><label><input type="checkbox" checked> 商机协同成员</label><label><input type="checkbox" checked> 所属部门管理者</label></section></div>'}
function stateReason(){return '<div class="opp-state-panel"><section class="opp-config-card"><h3>项目阶段 <button onclick="oppModule.toast(\'项目阶段由公司统一维护\')">维护</button></h3><div class="body"><p style="margin:0;color:#64748b;font-size:12px">反映商机业务成熟度,不由部门自行新增或改名。</p><div class="opp-state-line"><span class="tag">设计</span><i>›</i><span class="tag">改单</span><i>›</i><span class="tag">投标</span><i>›</i><span class="tag">抢标</span></div><div class="opp-policy-lock">公司统一口径。战队只能补充本阶段的工作指引或字段,不能改变项目阶段本身。</div></div></section><section class="opp-config-card"><h3>商机状态 <button onclick="oppModule.toast(\'商机状态由公司统一维护\')">维护</button></h3><div class="body"><p style="margin:0;color:#64748b;font-size:12px">控制商机是否继续推进,以及可见的业务动作。</p><div class="opp-state-line"><span class="tag">推进中</span><i>›</i><span class="tag">暂缓</span><i>›</i><span class="tag">关闭</span><i>›</i><span class="tag">已转项目</span></div><div class="opp-policy-lock">状态流转、暂缓/关闭原因由公司统一治理,不能因部门差异而各自维护。</div></div></section><section class="opp-config-card"><h3>暂缓与关闭原因 <button onclick="oppModule.toast(\'维护原因字典\')">维护原因</button></h3><div class="body"><div class="opp-state-group"><span>项目取消</span><span>客户放弃</span><span>竞争失败</span><span>重复商机</span><span>项目延期</span></div><p style="margin:14px 0 0;color:#64748b;font-size:12px;line-height:1.6">关闭、暂缓必须选择公司原因并补充说明,便于经营复盘和历史追溯。</p></div></section><section class="opp-config-card"><h3>团队可调整范围</h3><div class="body"><div class="opp-config-list"><div><span>可新增</span><small>战队扩展字段、附件要求、协同指引</small></div><div><span>可细调</span><small>预警提醒对象、建议停留时长</small></div><div><span>不可调整</span><small>状态、项目阶段、核心节点顺序、公司强校验</small></div></div></div></section></div>'}
function releaseHistory(){const scenarios={company:{name:'公司默认商机',range:'公司默认',layers:[['公司商机标准 V1.0','基础字段、核心工作节点、项目阶段、状态规则']],effective:[['公司标准','V1.0'],['项目阶段规则','未叠加'],['招标形式条件','未叠加'],['战队扩展','未叠加']]},design:{name:'设计阶段 + ZC',range:'设计阶段 / 政采标',layers:[['公司商机标准 V1.0','基础字段、核心工作节点、项目阶段、状态规则'],['设计阶段规则 V1.1','现场勘察、设计配置、方案预算'],['ZC 条件规则 V1.0','招标编号、标底方、投标资料']],effective:[['公司标准','V1.0'],['项目阶段规则','设计阶段 V1.1'],['招标形式条件','ZC V1.0'],['战队扩展','未叠加']]},team:{name:'设计 + ZC + 广东15部',range:'广东15部',layers:[['公司商机标准 V1.0','基础字段、核心工作节点、项目阶段、状态规则'],['设计阶段规则 V1.1','现场勘察、设计配置、方案预算'],['ZC 条件规则 V1.0','招标编号、标底方、投标资料'],['广东15部扩展 V1.0','部门备注、特殊附件、协同要求']],effective:[['公司标准','V1.0'],['项目阶段规则','设计阶段 V1.1'],['招标形式条件','ZC V1.0'],['战队扩展','广东15部 V1.0']]}};const current=scenarios[ruleScenario];return '<div class="opp-governance"><section class="opp-governance-stack">'+current.layers.map((x,i)=>'<div class="opp-governance-layer '+(i===0?'company':i===current.layers.length-1&&ruleScenario==='team'?'team':'stage')+'"><span class="scope">'+(i===0?'公司统一':i===current.layers.length-1&&ruleScenario==='team'?'战队扩展':'条件叠加')+'</span><h3>'+x[0]+'</h3><p>'+x[1]+'</p></div>'+(i<current.layers.length-1?'<div class="opp-governance-arrow">+</div>':'')).join('')+'</section><aside class="opp-scenario"><h3>一条商机的实际生效规则</h3><div class="opp-scenario-options"><button class="'+(ruleScenario==='company'?'active':'')+'" onclick="oppSupplement.setRuleScenario(\'company\')">公司默认</button><button class="'+(ruleScenario==='design'?'active':'')+'" onclick="oppSupplement.setRuleScenario(\'design\')">设计 + ZC</button><button class="'+(ruleScenario==='team'?'active':'')+'" onclick="oppSupplement.setRuleScenario(\'team\')">设计 + ZC + 广东15部</button></div><div class="opp-effective-rule"><div><span>当前示例</span><b>'+current.name+'</b></div><div><span>适用范围</span><b>'+current.range+'</b></div>'+current.effective.map(x=>'<div><span>'+x[0]+'</span><b>'+x[1]+'</b></div>').join('')+'</div><div class="opp-policy-lock">战队规则是追加,不是替代。它不能删除公司字段、改变状态口径或重排公司核心工作节点。</div></aside></div><div class="opp-config-grid" style="margin-top:14px"><section class="opp-config-card"><h3>已发布规则 <button onclick="oppModule.toast(\'新建规则草稿\')">新建草稿</button></h3><div class="body"><div class="opp-version-row"><span>公司商机标准 V1.0<br><small>适用:全公司 · 发布人:运营管理员</small></span><span class="status">已发布</span></div><div class="opp-version-row"><span>设计阶段规则 V1.1<br><small>适用:项目阶段=设计</small></span><span class="status">已发布</span></div><div class="opp-version-row"><span>广东15部扩展 V1.0<br><small>适用:广东15部 · 仅新增扩展要求</small></span><span class="status">已发布</span></div></div></section><section class="opp-config-card"><h3>发布控制</h3><div class="body"><div class="opp-config-list"><div><span>公司标准</span><small>由商机配置管理员发布</small></div><div><span>阶段/招标条件</span><small>由公司统一维护并发布</small></div><div><span>战队扩展</span><small>战队提交草稿,统一审核发布</small></div><div><span>历史商机</span><small>保留创建时规则快照,不被新版本覆盖</small></div></div></div></section></div>'}
function setting(){const content={overview:overview(),track:track(),template:template(),gate:gate(),pool:poolRule(),state:stateReason(),release:releaseHistory()}[configTab];const label=(configTabs.find(x=>x[0]===configTab)||configTabs[0])[1];return '<div class="opp-config-page"><div class="opp-config-head"><div><h1>商机管理设置</h1><p>维护工作节点、字段模板、关键关口、公海及规则发布范围。</p></div><div class="opp-config-head-actions"><button class="btn" onclick="oppSupplement.switchConfigTab(\'release\')">变更记录</button><button class="btn primary" onclick="oppModule.toast(\'商机管理设置草案已保存\')">保存草案</button></div></div><div class="opp-config-layout">'+configNav()+'<section class="opp-config-main"><div class="opp-config-section-head"><div><h2>'+label+'</h2><p>当前为配置草案展示;具体发布、权限与校验规则将在确认后落地。</p></div><div class="actions">'+(configTab==='track'?'<button class="btn" onclick="oppModule.toast(\'新增工作指引\')">+ 新增工作指引</button>':configTab==='template'?'<button class="btn" onclick="oppModule.toast(\'新建字段模板\')">+ 新建模板</button>':'')+'</div></div>'+content+'</section></div></div>'}
function switchConfigTab(tab){configTab=tab;render('oppSetting')}
function setRuleScenario(value){ruleScenario=value;render('oppSetting')}
function render(page){if(page!=='oppSetting')return;oppSettingsV2.render()}
return {render,switchConfigTab,setRuleScenario};
})();
const oppGuideEditor=(function(){
const guides=[
{id:'company',label:'公司统一工作指引',scope:'适用:全部销售机会',nodes:[['客户圈定','确认甲方、意向客户及角色','全部阶段','5 天 / 第 3 天提醒负责人','甲方、意向客户','关系摸排','明确项目相关方及客户类型'],['关系摸排','明确关键人、设计院、总包关系','全部阶段','7 天 / 第 5 天提醒负责人','关键人、客户关系','资料采集','补齐客户关系及协同分工'],['资料采集','补齐需求、预算、时间与附件','设计、改单','7 天 / 第 5 天提醒负责人','需求描述、项目预算、主要意向客户','方案卡','可发起方案卡,通知方案支持人员'],['方案卡','形成方案资料并发起协同','设计、改单、投标','10 天 / 第 7 天提醒负责人','方案卡、关键附件','已转项目','根据方案结果继续推进或关联项目'],['已转项目','完成报备或项目关联','全部阶段','--','项目/报备关联','--','完成商机与项目的关联留痕']]},
{id:'design',label:'设计阶段工作指引',scope:'适用:项目阶段=设计',nodes:[['资料采集','补充现场条件、需求和预算','设计','5 天 / 第 3 天提醒负责人','现场勘察、需求描述','方案卡','补齐设计输入资料'],['方案卡','补充设计配置、品牌要求和方案预算','设计','7 天 / 第 5 天提醒负责人','设计配置、品牌要求、方案预算','--','发起方案支持协同']]},
{id:'bid',label:'投标阶段工作指引',scope:'适用:项目阶段=投标',nodes:[['关系摸排','确认投标主体及关键协同方','投标','5 天 / 第 3 天提醒负责人','投标公司、关键联系人','资料采集','明确投标关系与分工'],['资料采集','补充招标编号、标底方和投标资料','投标','7 天 / 第 5 天提醒负责人','招标编号、标底方、投标资料','方案卡','完成投标资料准备']]},
{id:'team15',label:'广东15部扩展工作指引',scope:'适用:广东15部',nodes:[['资料采集','补充部门备注、特殊附件和协同要求','广东15部','5 天 / 第 3 天提醒负责人','部门备注、特殊附件','方案卡','补充本战队协同要求'],['方案卡','确认工程师出清单及报价体系','广东15部','7 天 / 第 5 天提醒负责人','工程师出清单要求、报价体系确认','--','通知方案支持人员']]}
];
let selectedGuide='company',selectedNode=2;
function findGuide(id){return guides.find(x=>x.id===id)||guides[0]}
function render(id,nodeIndex){selectedGuide=id||selectedGuide;selectedNode=Number.isInteger(nodeIndex)?nodeIndex:selectedNode;const guide=findGuide(selectedGuide);if(selectedNode>=guide.nodes.length)selectedNode=0;const root=document.querySelector('#oppSettingRoot');if(!root)return;const list=root.querySelector('.opp-track-list'),canvas=root.querySelector('.opp-track-canvas'),detail=root.querySelector('.opp-track-detail');if(!list||!canvas||!detail)return;list.innerHTML='<h3>工作指引模板</h3>'+guides.map(g=>'<button class="opp-track-template '+(g.id===guide.id?'active':'')+'" data-guide-id="'+g.id+'">'+g.label+(g.id.startsWith('draft-')?'<span class="opp-guide-draft">草稿</span>':'')+'</button>').join('');canvas.innerHTML='<div class="opp-track-canvas-head"><b>'+guide.label+'</b><span>'+guide.scope+'</span></div><div class="opp-track-flow">'+guide.nodes.map((n,i)=>'<button class="opp-track-node '+(i===selectedNode?'active':'')+'" data-guide-node="'+i+'"><b>'+(i+1)+'. '+n[0]+'</b><small>'+n[1]+'</small></button>'+(i<guide.nodes.length-1?'<div class="opp-track-arrow">›</div>':'')).join('')+'</div><div class="opp-config-note" style="margin:18px 0 0">已选节点:'+guide.nodes[selectedNode][0]+'。蓝色仅表示当前正在配置的节点,不表示某条商机的实际推进状态。</div>';const node=guide.nodes[selectedNode];detail.innerHTML='<h3>节点配置:'+node[0]+'</h3><div class="opp-field-row"><label>适用项目阶段</label><span>'+node[2]+'</span></div><div class="opp-field-row"><label>预计停留 / 预警</label><span>'+node[3]+'</span></div><div class="opp-field-row"><label>推进校验</label><span>'+node[4]+'</span></div><div class="opp-field-row"><label>下一节点</label><span>'+node[5]+'</span></div><div class="opp-field-row"><label>协同指引</label><span>'+node[6]+'</span></div>'}
function openCreate(){const mask=document.getElementById('modal'),panel=mask.querySelector('.modal');panel.classList.remove('opp-modal-wide','opp-modal-small');panel.classList.add('opp-modal-medium');document.getElementById('modalTitle').textContent='新增工作指引';mask.querySelector('.m-body').innerHTML='<div class="opp-guide-form"><label>*工作指引名称</label><input id="guideDraftName" value="新工作指引"><label>*类型</label><select id="guideDraftType"><option value="stage">项目阶段工作指引</option><option value="team">战队扩展工作指引</option><option value="company">公司统一工作指引</option></select><label>*适用范围</label><select id="guideDraftScope"><option>设计阶段</option><option>投标阶段</option><option>广东15部</option><option>公司默认</option></select><label>基于工作指引</label><select><option>公司统一工作指引</option></select><div class="opp-guide-form-note">阶段和战队工作指引是在公司统一工作指引上补充要求;不会删除公司核心节点,也不会改变项目阶段或商机状态。</div></div>';mask.querySelector('.m-foot').innerHTML='<button class="btn" onclick="closeModal()">取消</button><button class="btn primary" onclick="oppGuideEditor.createGuide()">保存为草稿</button>';mask.classList.add('show')}
function createGuide(){const name=(document.getElementById('guideDraftName')?.value||'新工作指引').trim();const scope=document.getElementById('guideDraftScope')?.value||'公司默认';const id='draft-'+Date.now();guides.push({id,label:name,scope:'适用:'+scope,nodes:[['资料采集','请继续配置此节点的工作要求',scope,'--','待配置','--','待配置']]});closeModal();oppSupplement.switchConfigTab('track');render(id,0);oppModule.toast('已创建工作指引草稿')}
document.addEventListener('click',function(e){const root=e.target.closest('#oppSettingRoot');if(!root)return;const template=e.target.closest('.opp-track-template');if(template){const id=template.dataset.guideId||({ '公司统一工作指引':'company','设计阶段工作指引':'design','投标阶段工作指引':'bid','广东15部扩展工作指引':'team15'}[template.textContent.trim()]);if(id)render(id,0);return}const node=e.target.closest('.opp-track-node');if(node){const index=Number(node.dataset.guideNode);if(Number.isInteger(index))render(selectedGuide,index);else{const nodes=[...root.querySelectorAll('.opp-track-node')];render(selectedGuide,nodes.indexOf(node))}return}const text=e.target.textContent.trim();if(text==='+ 新增工作指引'){openCreate()}else if(text==='保存草案'){e.target.textContent='草稿已保存';oppModule.toast('商机管理设置草案已保存')}});
return {render,openCreate,createGuide};
})();
const oppFieldTemplateDemo=(function(){
let mode='opp',stage='设计',tender='ZC',team='公司默认',source='common',selectedField='商机名称';
const sourceLabels={common:['公司通用字段','基础、客户、需求预算、商务时间'],stage:['项目阶段字段','随项目阶段追加'],tender:['招标形式条件字段','随招标形式追加'],team:['战队扩展字段','仅补充非核心要求']};
function fields(){const rows=[['商机名称','common','创建时必填'],['甲方','common','创建时必填'],['主要意向客户','common','推进节点必填'],['需求描述','common','资料采集必填'],['项目预算','common','资料采集必填']];if(stage==='设计')rows.push(['现场勘察','stage','方案卡前必填'],['设计配置','stage','方案卡前必填'],['品牌要求','stage','方案卡前必填'],['方案预算','stage','方案卡前必填']);if(stage==='投标')rows.push(['投标时间','stage','资料采集必填'],['投标策略','stage','方案卡前必填']);if(tender==='ZC')rows.push(['招标编号','tender','推送方案卡必填'],['标底方','tender','推送方案卡必填'],['投标资料','tender','推送方案卡必填']);if(team==='广东15部')rows.push(['部门备注','team','非必填补充'],['特殊附件','team','方案卡前补充'],['协同要求','team','非必填补充']);return rows}
function fieldDetail(){const row=fields().find(x=>x[0]===selectedField)||fields()[0];selectedField=row[0];const names={common:'公司通用字段',stage:'项目阶段字段',tender:'招标形式条件字段',team:'战队扩展字段'};return '<h3>字段设置:'+row[0]+'</h3><div class="row"><label>字段来源</label><b>'+names[row[1]]+'</b></div><div class="row"><label>当前校验</label><span>'+row[2]+'</span></div><div class="row"><div class="switch-line"><span>页面展示</span><i></i></div><div class="switch-line"><span>创建时必填</span><i></i></div><div class="switch-line"><span>推进节点时校验</span><i></i></div><div class="switch-line"><span>推送方案卡时校验</span><i></i></div></div><div class="row"><label>规则说明</label><span>公司强校验不可被战队扩展规则弱化;战队只能补充字段和要求。</span></div>'}
function render(){const main=document.querySelector('#oppSettingRoot .opp-config-main');if(!main)return;const fs=fields();const isScheme=mode==='scheme';main.innerHTML='<div class="opp-config-section-head"><div><h2>模板与字段</h2><p>通过阶段、招标形式和战队条件,查看一条商机实际生效的字段组合。</p></div><div class="actions"><button class="btn" data-field-action="new">+ 新建模板</button></div></div><div class="opp-template-mode"><button class="'+(!isScheme?'active':'')+'" data-field-mode="opp">商机字段模板</button><button class="'+(isScheme?'active':'')+'" data-field-mode="scheme">方案卡字段与推送规则</button></div><div class="opp-condition-bar"><div class="opp-condition-group"><label>项目阶段</label>'+['设计','改单','投标','抢标'].map(x=>'<button class="'+(stage===x?'active':'')+'" data-field-stage="'+x+'">'+x+'</button>').join('')+'</div><div class="opp-condition-group"><label>招标形式</label>'+['ZC','GC','BC','YB'].map(x=>'<button class="'+(tender===x?'active':'')+'" data-field-tender="'+x+'">'+x+'</button>').join('')+'</div><div class="opp-condition-group"><label>适用战队</label>'+['公司默认','广东15部'].map(x=>'<button class="'+(team===x?'active':'')+'" data-field-team="'+x+'">'+x+'</button>').join('')+'</div></div><div class="opp-field-workbench"><aside class="opp-field-library"><h3>字段模板</h3>'+Object.entries(sourceLabels).map(([key,x])=>'<button class="opp-field-source '+(source===key?'active':'')+'" data-field-source="'+key+'">'+x[0]+'<small>'+x[1]+'</small></button>').join('')+'</aside><section class="opp-field-preview"><h3>'+(isScheme?'方案卡字段映射预览':'当前有效表单预览')+'</h3>'+(isScheme?'<div class="opp-scheme-map">'+fs.map(x=>'<div class="opp-scheme-map-row"><span>'+x[0]+'</span><em>→</em><b>'+(x[1]==='common'?'方案卡快照字段':'方案卡补充/校验字段')+'</b></div>').join('')+'</div>':'<div class="opp-form-preview">'+fs.filter(x=>source==='all'||x[1]===source).map(x=>'<button class="opp-preview-field '+(selectedField===x[0]?'active':'')+'" data-preview-field="'+x[0]+'"><b>'+x[0]+'</b><small class="'+x[1]+'">'+({common:'公司通用',stage:'阶段字段',tender:'招标条件',team:'战队扩展'}[x[1]])+'</small></button>').join('')+'</div>')+'</section><aside class="opp-field-detail">'+fieldDetail()+'</aside></div><div class="opp-config-note" style="margin:14px 0 0">商机字段用于沉淀项目事实;推送方案卡时读取已有商机信息形成快照,方案支持部门只补充设计配置、清单、附件和方案版本结果。</div>'}
function openNew(){const mask=document.getElementById('modal'),panel=mask.querySelector('.modal');panel.classList.remove('opp-modal-wide','opp-modal-small');panel.classList.add('opp-modal-medium');document.getElementById('modalTitle').textContent='新建字段模板';mask.querySelector('.m-body').innerHTML='<div class="opp-guide-form"><label>*模板名称</label><input value="新字段模板"><label>*模板类型</label><select><option>项目阶段字段</option><option>招标形式条件字段</option><option>战队扩展字段</option></select><label>*适用条件</label><select><option>设计阶段</option><option>ZC 招标形式</option><option>广东15部</option></select><div class="opp-guide-form-note">公司通用字段、商机状态和公司强制校验不允许由战队模板覆盖;战队模板只能补充。</div></div>';mask.querySelector('.m-foot').innerHTML='<button class="btn" onclick="closeModal()">取消</button><button class="btn primary" onclick="closeModal();oppModule.toast(\'已创建字段模板草稿\')">保存为草稿</button>';mask.classList.add('show')}
document.addEventListener('click',function(e){const text=e.target.textContent.trim();if(text==='模板与字段'){setTimeout(render,0);return}const root=e.target.closest('#oppSettingRoot');if(!root)return;const modeBtn=e.target.closest('[data-field-mode]');if(modeBtn){mode=modeBtn.dataset.fieldMode;render();return}const sourceBtn=e.target.closest('[data-field-source]');if(sourceBtn){source=sourceBtn.dataset.fieldSource;render();return}const stageBtn=e.target.closest('[data-field-stage]');if(stageBtn){stage=stageBtn.dataset.fieldStage;render();return}const tenderBtn=e.target.closest('[data-field-tender]');if(tenderBtn){tender=tenderBtn.dataset.fieldTender;render();return}const teamBtn=e.target.closest('[data-field-team]');if(teamBtn){team=teamBtn.dataset.fieldTeam;render();return}const fieldBtn=e.target.closest('[data-preview-field]');if(fieldBtn){selectedField=fieldBtn.dataset.previewField;render();return}if(e.target.closest('[data-field-action="new"]'))openNew()});
return {render,openNew};
})();
const customerV2=(function(){
const roots={customerPool:'crmCustomerOverviewRoot',myCustomer:'crmMyCustomerRoot',customerSea:'crmCustomerSeaRoot',contact:'crmContactRoot',customerDedupe:'crmCustomerDedupeRoot',customerHandover:'crmCustomerHandoverRoot',customerSetting:'crmCustomerSettingRoot'};
const customerPages=new Set(['customerPool','myCustomer','customerSea','contact','customerDedupe','customerHandover','customerSetting']);
const pageLabels={customerPool:'客户总览',myCustomer:'我的客户',customerSea:'客户公海',contact:'联系人',customerDedupe:'客户查重',customerHandover:'客户交割',customerSetting:'客户管理设置'};
const state={page:'customerPool',query:'',density:'default',viewByPage:{customerPool:'list',myCustomer:'list',customerSea:'list',contact:'list',customerDedupe:'list',customerHandover:'list',customerSetting:'list'},selectedByPage:{customerPool:new Set(),myCustomer:new Set(),customerSea:new Set()},selectedCustomer:1,splitCollapsedByPage:{customerPool:false,myCustomer:false,customerSea:false},detail:null,tab:'客户信息',form:null,formId:null,formParent:null,searchOpen:false,searchTab:'常用检索',searchLabelByPage:{},cardGroupBy:{customerPool:'stage',myCustomer:'stage',customerSea:'stage'},folds:{},contactRows:[{name:'',job:'',phone:'',relation:'',inside:'否',gift:'',source:'销售拜访'}],detailContacts:null,selectedContactIds:new Set(),contactEditMode:false,contactDraft:{},contactOcrFilled:false,draftCustomer:{},ocrState:'upload',ocrScenario:'success',ocrContext:'customer',ocrReturn:'list',importMode:'仅新增数据',importDuplicateMode:'none',agreementFile:'',selectedAgreementId:1,resumeForm:false,memberDraft:new Set(),memberPermission:'读写'};
let ocrTaskToken=0;
let homeCustomerModal=false;
const customers=[
{id:1,name:'广州市XX职业技术学院',code:'KH20260624001',credit:'91440106123123587X',phone:'020-88886666',type:'甲方(中高职)',industry:'高校(职校)',stage:'潜在客户',stars:5,relation:5,relationWord:'合作',agreement:'三级',owner:'张三',dept:'广东1部',status:'有效',business:'2个商机、1个项目',opps:2,follow:'2026-06-15 17:31',created:'2026-05-08',area:'广东省 / 广州市 / 番禺区'},
{id:2,name:'广东XX智慧建筑工程有限公司',code:'KH20260624002',credit:'914401015A9M23821R',phone:'020-66881231',type:'甲方(企业用户)',industry:'集团大楼',stage:'重潜客户',stars:4,relation:4,relationWord:'重点',agreement:'一级',owner:'小李',dept:'广东1部',status:'有效',business:'1个商机',opps:1,follow:'2026-06-18 12:31',created:'2026-05-12',area:'广东省 / 广州市 / 天河区'},
{id:3,name:'佛山XX政务服务中心',code:'KH20260624003',credit:'12440600MB2D77318H',phone:'0757-82221234',type:'甲方(政府机关)',industry:'政府部门',stage:'已成交客户',stars:5,relation:4,relationWord:'战略',agreement:'二级',owner:'陈六',dept:'四川1部',status:'已合并',business:'2个项目',opps:0,follow:'2026-06-09 09:31',created:'2026-04-28',area:'广东省 / 佛山市 / 禅城区'},
{id:4,name:'东莞XX商业广场运营管理公司',code:'KH20260624004',credit:'91441900MA4W8D92XQ',phone:'0769-83335555',type:'甲方(企业用户)',industry:'商业中心',stage:'潜在客户',stars:1,relation:1,relationWord:'潜在',agreement:'--',owner:'吴八',dept:'重庆1部',status:'有效',business:'暂无',opps:0,follow:'2026-06-18 17:31',created:'2026-05-18',area:'广东省 / 东莞市 / 南城区'},
{id:5,name:'广东省XX监狱管理局',code:'KH20260624005',credit:'11440000712312345A',phone:'020-83331234',type:'甲方(政府机关)',industry:'监狱',stage:'潜在客户',stars:2,relation:2,relationWord:'高潜',agreement:'--',owner:'陈六',dept:'四川1部',status:'有效',business:'暂无',opps:0,follow:'2026-06-07 17:31',created:'2026-05-21',area:'广东省 / 广州市 / 越秀区'},
{id:6,name:'中山XX中学',code:'KH20260624006',credit:'12442000457213456F',phone:'0760-88221122',type:'甲方(普教)',industry:'高校(普教)',stage:'潜在客户',stars:1,relation:2,relationWord:'潜在',agreement:'--',owner:'小李',dept:'广东1部',status:'已废弃',business:'暂无',opps:0,follow:'2026-06-13 17:31',created:'2026-05-25',area:'广东省 / 中山市 / 东区'},
{id:7,name:'深圳市XX监狱管理局',code:'KH20260624007',credit:'11440300MB2C88772P',phone:'0755-88991234',type:'甲方(政府机关)',industry:'监狱',stage:'重潜客户',stars:3,relation:3,relationWord:'重点',agreement:'--',owner:'张三',dept:'广东1部',status:'有效',business:'1个商机',opps:1,follow:'2026-06-18 20:31',created:'2026-05-29',area:'广东省 / 深圳市 / 福田区'}
];
const contacts=[
{id:1,name:'陈大壮',company:'广州市XX职业技术学院',job:'采购负责人',department:'采购中心',phone:'13897283272',relation:'核心决策人',inside:'是',gift:'节日问候',source:'客户转介绍'},
{id:2,name:'李华',company:'广州市XX职业技术学院',job:'采购主管',department:'采购部',phone:'13259874315',relation:'项目采购接口',inside:'否',gift:'节日问候',source:'销售拜访'},
{id:3,name:'王立',company:'广州市XX职业技术学院',job:'信息科科长',department:'信息科',phone:'13542145754',relation:'技术评审人',inside:'是',gift:'生日问候',source:'展会'},
{id:4,name:'赵强',company:'广州市XX职业技术学院',job:'工程经理',department:'工程部',phone:'13987645464',relation:'现场对接人',inside:'否',gift:'节日问候',source:'线索转化'},
{id:5,name:'钱三',company:'广州市XX职业技术学院',job:'采购专员',department:'采购处',phone:'13697897844',relation:'采购经办',inside:'否',gift:'电话咨询',source:'电话咨询'}
];
const agreements=[
{id:1,name:'2026年度战略合作协议',code:'XY202601',level:'三级',signed:'2026-01-15',valid:'2026-01-15 至 2026-12-31',amount:'300万元',status:'生效中',attachment:'2026年度战略合作协议.pdf',remark:'年度战略合作协议,覆盖会议系统、录播系统及配套服务。'},
{id:2,name:'智慧校园联合推广协议',code:'XY202502',level:'二级',signed:'2025-06-18',valid:'2025-06-18 至 2027-06-17',amount:'180万元',status:'生效中',attachment:'智慧校园联合推广协议.pdf',remark:'围绕智慧校园项目开展联合推广与方案共建。'},
{id:3,name:'会议系统框架合作协议',code:'XY202403',level:'一级',signed:'2024-03-08',valid:'2024-03-08 至 2025-03-07',amount:'80万元',status:'已到期',attachment:'会议系统框架合作协议.pdf',remark:'历史会议系统框架协议。'}
];
function esc(v){return String(v??'').replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]))}
function root(page=state.page){return document.getElementById(roots[page])}
function handles(page){return !!roots[page]}
function view(){return state.viewByPage[state.page]||'list'}
function stars(n){return '<span class="crm-stars">'+('★'.repeat(n)+'☆'.repeat(5-n))+'</span>'}
function pill(v){let c='blue';if(/有效|成交|完成|成功|负责人|读写|全部查看/.test(v))c='green';if(/废弃|驳回|失败/.test(v))c='red';if(/待|重潜|脱敏/.test(v))c='orange';return '<span class="crm-pill '+c+'">'+esc(v)+'</span>'}
function notify(msg){let t=document.getElementById('crmToast');if(!t){t=document.createElement('div');t.id='crmToast';t.className='crm-toast';document.body.appendChild(t)}t.textContent=msg;t.classList.add('show');clearTimeout(t._timer);t._timer=setTimeout(()=>t.classList.remove('show'),1600)}
function icon(mode){return sharedViewModeIcon(mode)}
function filtered(rows,fields){const q=state.query.trim().toLowerCase();return rows.filter(r=>!q||fields.some(f=>String(r[f]||'').toLowerCase().includes(q)))}
function pagination(total=400){return '<div class="opp-pagination"><span>共 '+total+' 条</span><select><option>10条/页</option><option>20条/页</option><option>50条/页</option></select><div class="opp-page-buttons"><button disabled>‹</button><button class="active">1</button><button>2</button><button>3</button><button>›</button></div><div class="opp-jump"><span>跳至</span><input value="1"><span>页</span></div></div>'}
function viewSwitch(){return '<div class="crm-view-switch">'+[['split','分屏视图'],['list','列表视图'],['card','卡片视图']].map(m=>'<button class="'+(view()===m[0]?'active':'')+'" onclick="customerV2.setView(\''+m[0]+'\')" title="'+m[1]+'">'+icon(m[0])+'</button>').join('')+'</div>'}
function searchPin(){return '<svg class="classic-pushpin" viewBox="0 0 24 24" aria-hidden="true" style="width:20px;height:20px;overflow:visible;fill:none;stroke:currentColor;stroke-width:1.9;stroke-linecap:round;stroke-linejoin:round"><g transform="rotate(42 12 12)"><path class="pin-body" fill="#fff" d="M8 3h8l-1 7 3 3H6l3-3-1-7Z"/><path class="pin-needle" d="M12 13v8" fill="none" stroke-width="2"/></g></svg>'}
function searchMenu(){const common=[['customerPool','最近访问'],['myCustomer','我负责的客户'],['myCustomer','我协同的客户'],['customerSea','客户公海'],['myCustomer','我关注的客户']];const commonPanel='<div class="crm-search-panel '+(state.searchTab==='常用检索'?'active':'')+'"><div class="crm-search-list">'+common.map(x=>'<div class="crm-search-list-row"><button onclick="customerV2.navigateSearch(\''+x[0]+'\',\''+x[1]+'\')">'+x[1]+'</button><button class="crm-search-pin" title="固定到常用检索" onclick="event.stopPropagation();customerV2.quick(\'已固定:'+x[1]+'\')">'+searchPin()+'</button></div>').join('')+'</div></div>';const customPanel='<div class="crm-search-panel '+(state.searchTab==='自定义检索'?'active':'')+'"><div class="crm-search-editor"><label>检索名称</label><input value="自定义检索"><label>检索条件</label><span class="hint">检索条件,支持复合查询</span><span></span><div class="condition"><span>1.</span><select><option>客户名称</option><option>销售负责人</option><option>所属地区</option><option>创建时间</option></select><select><option>等于</option><option>不等于</option><option>包含</option><option>为空</option></select><input placeholder="请输入..."><button class="remove" onclick="customerV2.quick(\'已移除条件\')">−</button></div><span></span><button class="crm-link" style="text-align:left" onclick="customerV2.quick(\'已添加检索条件\')">+ 添加更多条件</button><span></span><label class="advanced"><input type="checkbox">启用高级公式</label><label>排序条件</label><div class="sort"><select><option>创建时间</option><option>最近跟进时间</option><option>客户星级</option></select><button>升序</button><button class="active">降序</button></div><footer><button class="btn" onclick="customerV2.quick(\'已保存为常用检索\')">保存为常用检索</button><button class="btn primary" onclick="customerV2.toggleSearchMenu()">搜索</button></footer></div></div>';return '<div class="crm-search-menu '+(state.searchOpen?'show ':'')+(state.searchTab==='自定义检索'?'is-custom':'')+'"><div class="crm-search-tabs"><button class="'+(state.searchTab==='常用检索'?'active':'')+'" onclick="customerV2.setSearchTab(\'常用检索\')">常用检索</button><button class="'+(state.searchTab==='自定义检索'?'active':'')+'" onclick="customerV2.setSearchTab(\'自定义检索\')">自定义检索</button></div>'+commonPanel+customPanel+'</div>'}
function titleBar(title,modes=true){const current=state.searchLabelByPage[state.page]||title;return '<div class="crm-head"><div class="crm-head-main">'+(modes?viewSwitch():'')+'<div class="crm-title-wrap"><button class="crm-title-trigger '+(state.searchOpen?'open':'')+'" onclick="customerV2.toggleSearchMenu()">'+esc(current)+'<span class="chev">⌄</span></button>'+searchMenu()+'</div></div></div>'}
function toolbar(actions=''){return '<div class="crm-customer-toolbar"><div class="crm-customer-query"><input id="crmQuery" value="'+esc(state.query)+'" placeholder="搜索客户名称 / 客户编号 / 姓名 / 电话 / 统一社会信用代码" onkeydown="if(event.key===\'Enter\')customerV2.query(this.value)"><select><option>全部客户类型</option></select><select><option>全部行业</option></select><select><option>全部客户档案状态</option></select><button class="btn primary" onclick="customerV2.query(document.getElementById(\'crmQuery\').value)">查询</button><button class="btn" onclick="customerV2.reset()">重置</button></div><div class="crm-customer-actions">'+actions+'</div></div>'}
function listActions(page){if(page==='customerSea')return '<button class="btn" onclick="customerV2.openForm(\'importCustomer\')">客户导入</button><button class="btn">客户导出</button><button class="btn primary" onclick="customerV2.openForm(\'newCustomer\')">+ 新增客户</button>';return '<button class="btn primary" onclick="customerV2.openForm(\'newCustomer\')">+ 新增客户</button><button class="btn" onclick="customerV2.openForm(\'ocr\',null,\'customer\')">OCR快捷新增</button><button class="btn" onclick="customerV2.openForm(\'importCustomer\')">客户导入</button><button class="btn">客户导出</button><button class="btn">工商信息更新</button>'}
function listTools(){if(view()!=='list')return '';return '<div class="core-list-tools"><button class="opp-tool-icon" title="列表密度" onclick="customerV2.listTool(\'density\',event)">'+sharedListToolIcon('density')+'</button><button id="crmCustomerRefreshBtn" class="opp-tool-icon" title="刷新数据" onclick="customerV2.listTool(\'refresh\',event)">'+sharedListToolIcon('refresh')+'</button><button class="opp-tool-icon" title="自定义显示字段" onclick="customerV2.listTool(\'fields\',event)">'+sharedListToolIcon('fields')+'</button></div>'}
function listTool(type,e){e?.stopPropagation();if(type==='density'){const order=['default','compact','relaxed'],label={default:'标准',compact:'紧凑',relaxed:'宽松'},i=(order.indexOf(state.density)+1)%order.length;state.density=order[i];renderCustomerList(state.page);notify('列表密度已切换为'+label[state.density]+'(Demo)');return}if(type==='refresh'){const b=document.getElementById('crmCustomerRefreshBtn');b?.classList.add('refreshing');setTimeout(()=>{b?.classList.remove('refreshing');notify('客户列表已刷新(Demo)')},520);return}openModal('自定义显示字段',{body:'<div class="fp-check-grid"><label><input type="checkbox" checked disabled> 客户名称</label><label><input type="checkbox" checked> 客户类型</label><label><input type="checkbox" checked> 行业</label><label><input type="checkbox" checked> 客户阶段</label><label><input type="checkbox" checked> 客户星级</label><label><input type="checkbox" checked> 销售负责人</label><label><input type="checkbox" checked> 客户档案状态</label><label><input type="checkbox" checked> 最近跟进时间</label></div>',footer:'<button class="btn" onclick="closeModal()">取消</button><button class="btn primary" onclick="closeModal();customerV2.notify(\'显示字段已保存(Demo)\')">保存</button>'})}
function rowActions(page,id){if(page==='customerPool')return '<button onclick="customerV2.openDetail(\'customer\','+id+')">详情</button><button>取消关注</button><button>分配</button><button>抛公海</button>';if(page==='myCustomer')return '<button onclick="customerV2.openDetail(\'customer\','+id+')">详情</button><button onclick="customerV2.openForm(\'editCustomer\','+id+')">编辑</button><button>跟进</button><button onclick="customerV2.openForm(\'addContact\','+id+')">添加联系人</button><button>关注</button>';return '<button onclick="customerV2.openDetail(\'customer\','+id+')">详情</button><button>领取</button><button>关注</button>'}
function customerRows(page){let rows=filtered(customers,['name','code','credit','phone','type','industry','owner']);if(page==='myCustomer')rows=rows.filter(x=>['小李','张三'].includes(x.owner));return rows}
function listTable(page){const rows=customerRows(page),selected=state.selectedByPage[page]||new Set(),all=rows.length&&rows.every(r=>selected.has(r.id)),partial=!all&&rows.some(r=>selected.has(r.id)),summary=selected.size?'<div class="crm-selection-summary">已选择 <b>'+selected.size+'</b> 个客户</div>':'';return summary+'<table class="crm-table crm-customer-table crm-density-'+state.density+'"><colgroup><col style="width:42px"><col style="width:50px"><col style="width:210px"><col style="width:140px"><col style="width:110px"><col style="width:110px"><col style="width:100px"><col style="width:100px"><col style="width:130px"><col style="width:110px"><col style="width:110px"><col style="width:120px"><col style="width:130px"><col style="width:150px"><col style="width:250px"></colgroup><thead><tr><th class="crm-check-col"><input type="checkbox" '+(all?'checked':'')+' '+(partial?'data-partial="1"':'')+' onclick="customerV2.toggleAllCustomers(this.checked)"></th><th class="crm-seq-col">序号</th><th class="crm-primary-name">客户名称</th><th>客户类型</th><th>行业</th><th>客户阶段</th><th>客户星级</th><th>关系星级</th><th>战略协议等级</th><th>销售负责人</th><th>销售部门</th><th>客户档案状态</th><th>关联业务</th><th>最近跟进时间</th><th class="crm-sticky-action">操作</th></tr></thead><tbody>'+rows.map((r,i)=>'<tr><td class="crm-check-col"><input type="checkbox" '+(selected.has(r.id)?'checked':'')+' onclick="customerV2.toggleCustomerSelection('+r.id+',this.checked)"></td><td class="crm-seq-col">'+(i+1)+'</td><td class="crm-primary-name"><button class="crm-link strong" onclick="customerV2.openDetail(\'customer\','+r.id+')">'+r.name+'</button></td><td>'+r.type+'</td><td>'+r.industry+'</td><td>'+pill(r.stage)+'</td><td>'+stars(r.stars)+'</td><td>'+stars(r.relation)+'</td><td>'+r.agreement+'</td><td>'+r.owner+'</td><td>'+r.dept+'</td><td>'+pill(r.status)+'</td><td>'+r.business+'</td><td>'+r.follow+'</td><td class="crm-sticky-action"><div class="crm-row-actions">'+rowActions(page,r.id)+'</div></td></tr>').join('')+'</tbody></table>'}
function summaryActions(r){return '<button class="btn primary" onclick="customerV2.summaryAction(\'跟进\','+r.id+')">跟进</button><button class="btn" onclick="customerV2.openForm(\'editCustomer\','+r.id+')">编辑</button><button class="btn">关注</button><button class="btn" onclick="customerV2.openForm(\'addContact\','+r.id+')">添加联系人</button><button class="btn">新增商机</button><button class="btn">新增项目</button><button class="btn" onclick="customerV2.openForm(\'agreement\','+r.id+')">新增战略协议</button><button class="btn">工商信息更新</button>'}
function customerSummary(r){return '<div class="crm-customer-summary"><div class="crm-summary-top"><h2>'+r.name+'</h2><div class="crm-summary-meta"><span class="crm-customer-level">客户星级 '+stars(r.stars)+' <b>'+esc(r.relationWord||'合作')+'</b></span><span>关系星级 '+stars(r.relation)+'</span><span>战略协议等级 '+r.agreement+'</span><span>档案状态 '+pill(r.status)+'</span><span>销售负责人 '+r.owner+' / '+r.dept+'</span></div><div class="crm-summary-actions">'+summaryActions(r)+'</div></div><div class="crm-customer-stage"><span class="active">潜在客户</span><span>重潜客户</span><span>已成交客户</span></div><div class="crm-customer-metrics"><div><b>238.00万元</b><span>已中标项目总额</span></div><div><b>486.00万元</b><span>跟进中方案预估总额</span></div><div><b>935.00万元</b><span>历史成交额</span></div><div><b>999.00万元</b><span>商机总额</span></div><div><b>'+r.business+'</b><span>关联业务</span></div></div><div class="crm-latest-follow"><div class="follow-head"><strong>最近跟进</strong><span>2026-06-28 09:30</span><em>电话</em><a>下次跟进:下周一 15:00 待跟进</a></div><div class="follow-body"><div class="follow-person">小李 广东1部</div>电话沟通年度合作续签事宜,客户表示内部审批流程进行中,预计7月初完成。</div></div></div>'}
function splitView(page){const rows=customerRows(page),selected=state.selectedByPage[page]||new Set(),r=customers.find(x=>x.id===state.selectedCustomer)||rows[0]||customers[0],all=rows.length&&rows.every(x=>selected.has(x.id)),partial=!all&&rows.some(x=>selected.has(x.id)),collapsed=!!state.splitCollapsedByPage[page];return '<div class="crm-customer-split crm-unified-split '+(collapsed?'collapsed':'')+'"><aside class="crm-unified-split-left"><div class="crm-unified-split-list"><div class="crm-split-head"><input type="checkbox" '+(all?'checked':'')+' '+(partial?'data-partial="1"':'')+' onclick="customerV2.toggleAllCustomers(this.checked)"><span>客户名称 已选中'+selected.size+'条</span><b></b></div>'+rows.map(x=>'<div class="crm-split-customer '+(x.id===r.id?'active':'')+'" onclick="customerV2.selectCustomer('+x.id+')"><input type="checkbox" '+(selected.has(x.id)?'checked':'')+' onclick="event.stopPropagation();customerV2.toggleCustomerSelection('+x.id+',this.checked)"><div><b>'+x.name+'</b><div class="rel-line">'+stars(x.relation)+' <span class="rel-word">'+x.relationWord+'</span></div></div><div class="crm-split-stage">'+pill(x.stage)+'</div></div>').join('')+'</div></aside><div class="crm-customer-split-divider crm-unified-split-divider"><button class="crm-customer-split-toggle crm-unified-split-toggle" onclick="customerV2.toggleCustomerSplit()" aria-label="'+(collapsed?'展开左侧列表':'收起左侧列表')+'">'+(collapsed?'›':'‹')+'</button></div><main class="crm-unified-split-detail">'+customerSummary(r)+detailTabs(r,true)+'</main></div>'}
function cardColumns(group){if(group==='relation')return {cols:['战略','重点','合作','高潜','潜在'],key:r=>r.relationWord};if(group==='stars')return {cols:['五星','四星','三星','二星','一星'],key:r=>['一星','二星','三星','四星','五星'][r.stars-1]};return {cols:['潜在客户','重潜客户','已成交客户'],key:r=>r.stage}}
function cardView(page){const rows=customerRows(page),group=state.cardGroupBy[page]||'stage',conf=cardColumns(group);return '<div class="crm-card-board"><div class="crm-card-board-toolbar"><select onchange="customerV2.setCardGroup(this.value)"><option value="stage" '+(group==='stage'?'selected':'')+'>按客户阶段</option><option value="relation" '+(group==='relation'?'selected':'')+'>按关系类型</option><option value="stars" '+(group==='stars'?'selected':'')+'>按客户星级</option></select></div><div class="crm-card-lanes cols-'+conf.cols.length+'">'+conf.cols.map(col=>{const items=rows.filter(r=>conf.key(r)===col);return '<section class="crm-card-lane"><header><b>'+((group==='relation'||group==='stars')?stars(Math.max(1,5-conf.cols.indexOf(col)))+' ':'')+col+'</b><span>('+items.length+')</span></header>'+items.map(r=>'<div class="crm-customer-card" onclick="customerV2.openDetail(\'customer\','+r.id+')"><button class="crm-link">'+r.name+'</button><a class="business-link">'+r.business+'</a><footer><span>'+r.owner+' / '+r.dept+'</span><span class="mini-stars">'+('★'.repeat(r.stars))+'</span></footer></div>').join('')+'</section>'}).join('')+'</div></div>'}
function renderCustomerList(page){const body=view()==='split'?splitView(page):view()==='card'?cardView(page):listTable(page);root(page).innerHTML='<div class="crm-page crm-customer-page">'+titleBar(pageLabels[page],true)+toolbar(listActions(page)+listTools())+'<div class="crm-main"><div class="crm-scroll">'+body+'</div>'+pagination()+'</div></div>';syncIndeterminate()}
function syncIndeterminate(){document.querySelectorAll('input[data-partial="1"]').forEach(x=>x.indeterminate=true)}
function fold(title,key,body){const hidden=state.folds[key];return '<section class="crm-fold-panel"><header><h3>'+title+'</h3><button onclick="customerV2.toggleFold(\''+key+'\')">'+(hidden?'展开':'折叠')+'</button></header><div class="crm-fold-body '+(hidden?'hidden':'')+'">'+body+'</div></section>'}
function infoGrid(items){return '<div class="crm-info-grid">'+items.map((x,i)=>'<label>'+x[0]+'</label><span class="'+(x[2]?'wide':'')+'">'+x[1]+'</span>').join('')+'</div>'}
function detailContactRows(){if(!state.detailContacts)state.detailContacts=contacts.map(c=>Object.assign({},c));return state.detailContacts}
function contactCell(c,key){if(!state.contactEditMode&&!c.isNew)return esc(c[key]||'--');const options=key==='inside'?'<select onchange="customerV2.updateDetailContact('+c.id+',\''+key+'\',this.value)"><option '+(c[key]==='是'?'selected':'')+'>是</option><option '+(c[key]!=='是'?'selected':'')+'>否</option></select>':'<input value="'+esc(c[key]||'')+'" placeholder="请输入" oninput="customerV2.updateDetailContact('+c.id+',\''+key+'\',this.value)">';return options}
function contactTab(){const rows=detailContactRows(),all=rows.length&&rows.every(c=>state.selectedContactIds.has(c.id)),partial=!all&&rows.some(c=>state.selectedContactIds.has(c.id));return '<div class="crm-contact-tab"><section class="crm-contact-list-panel"><header><h3>联系人列表(共'+rows.length+'位联系人)</h3></header><div style="overflow:auto"><table class="crm-table"><thead><tr><th><input class="row-check" type="checkbox" '+(all?'checked':'')+' '+(partial?'data-partial="1"':'')+' onclick="customerV2.toggleAllDetailContacts(this.checked)"></th><th>序号</th><th>联系人</th><th>职务</th><th>联系电话</th><th>人脉关系情况</th><th>是否内线</th><th>礼品备注</th><th>来源</th><th>操作</th></tr></thead><tbody>'+rows.map((c,i)=>'<tr class="'+(state.selectedContactIds.has(c.id)?'is-selected ':'')+(c.isNew?'is-new':'')+'"><td><input class="row-check" type="checkbox" '+(state.selectedContactIds.has(c.id)?'checked':'')+' onclick="customerV2.toggleDetailContact('+c.id+',this.checked)"></td><td>'+(i+1)+'</td><td>'+contactCell(c,'name')+'</td><td>'+contactCell(c,'job')+'</td><td>'+contactCell(c,'phone')+'</td><td>'+contactCell(c,'relation')+'</td><td>'+contactCell(c,'inside')+'</td><td>'+contactCell(c,'gift')+'</td><td>'+contactCell(c,'source')+'</td><td><button class="crm-link">详情</button> <button class="crm-link crm-danger" onclick="customerV2.removeDetailContact('+c.id+')">删除</button></td></tr>').join('')+'</tbody></table></div><div class="crm-contact-inline-actions"><div class="left"><button class="btn primary" onclick="customerV2.addDetailContactRow()">+ 新增一行</button><button class="btn" onclick="customerV2.openContactOcr()">OCR识别名片</button><button class="btn" onclick="customerV2.quick(\'批量导入联系人(Demo)\')">批量导入</button></div><div class="right"><button class="btn" onclick="customerV2.quick(\'联系人导出(Demo)\')">导出</button><button class="btn" onclick="customerV2.setContactEdit(true)">编辑</button><button class="btn" onclick="customerV2.deleteSelectedContacts()">删除</button><button class="btn primary" onclick="customerV2.saveDetailContacts()">保存</button></div></div></section><section class="crm-contact-network-panel"><header><h3>联系人图谱</h3><div class="crm-network-tools"><select><option>核心自定义关系</option></select><button class="btn">重置</button><button class="btn">保存</button></div></header><div class="crm-contact-network-canvas"><i class="crm-network-line" style="left:53%;top:124px;width:290px;transform:rotate(150deg)"></i><i class="crm-network-line" style="left:53%;top:124px;width:245px;transform:rotate(90deg)"></i><i class="crm-network-line" style="left:53%;top:124px;width:310px;transform:rotate(28deg)"></i><div class="crm-network-node crm-network-root"><strong>陈大壮</strong><small>采购负责人</small></div><div class="crm-network-node crm-network-a"><strong>李华</strong><small>采购主管</small></div><div class="crm-network-node crm-network-b"><strong>赵强</strong><small>工程经理</small></div><div class="crm-network-node crm-network-c"><strong>王立</strong><small>信息科科长</small></div></div></section></div>'}
function teamTab(){return '<div class="crm-tab-actions"><span>共3名成员</span><button class="btn primary" onclick="customerV2.openMemberDialog(\'add\')">+ 添加成员</button></div><table class="crm-table compact"><thead><tr><th>成员名称</th><th>所属部门</th><th>职务</th><th>角色</th><th>权限</th><th>加入时间</th><th>操作</th></tr></thead><tbody><tr><td>小李</td><td>广东1部</td><td>客户经理</td><td>'+pill('负责人')+'</td><td>'+pill('读写')+'</td><td>2026-05-08</td><td><button class="crm-link" onclick="customerV2.openMemberDialog(\'permission\')">调整权限</button> <button class="crm-link crm-danger" onclick="customerV2.openMemberDialog(\'delete\')">删除</button></td></tr><tr><td>王敏</td><td>广东1部</td><td>销售助理</td><td>'+pill('协同人')+'</td><td>'+pill('全部查看')+'</td><td>2026-05-12</td><td><button class="crm-link">调整权限</button></td></tr></tbody></table>'}
function agreementTab(r){const current=agreements.find(x=>x.id===state.selectedAgreementId)||agreements[0];return '<div class="crm-agreement-actions"><button class="btn primary" onclick="customerV2.openForm(\'agreement\','+r.id+')">+ 新增战略协议</button><button class="btn" '+(state.selectedAgreementId?'':'disabled')+' onclick="customerV2.openForm(\'editAgreement\','+state.selectedAgreementId+')">编辑</button></div><div class="crm-agreement-list"><table><thead><tr><th></th><th>协议名称</th><th>协议编号</th><th>战略协议等级</th><th>签订日期</th><th>有效期</th><th>签订金额</th><th>协议状态</th><th>附件</th></tr></thead><tbody>'+agreements.map(a=>'<tr class="'+(a.id===state.selectedAgreementId?'active':'')+'" onclick="customerV2.selectAgreement('+a.id+')"><td><input type="radio" name="agreementSelect" '+(a.id===state.selectedAgreementId?'checked':'')+'></td><td>'+a.name+'</td><td>'+a.code+'</td><td>'+a.level+'</td><td>'+a.signed+'</td><td>'+a.valid+'</td><td>'+a.amount+'</td><td>'+pill(a.status)+'</td><td>'+a.attachment+'</td></tr>').join('')+'</tbody></table></div>'+fold('系统信息','agreement-system',infoGrid([['协议名称',current.name],['协议编号',current.code],['战略协议等级',current.level],['签订金额',current.amount],['签订日期',current.signed],['有效期',current.valid],['协议状态',current.status],['附件数量','1']]))+fold('备注','agreement-note','<p style="padding:12px">'+current.remark+'</p>')+fold('附件','agreement-file','<p style="padding:12px"><a class="crm-link">'+current.attachment+'</a></p>')}
function customerInfo(r){const related=infoGrid([['公司决策人','陈主任'],['是否有商机勾兑','是'],['是否签订战略协议','是'],['总监近期拜访时间','2026-07-01'],['是否子客户','否'],['父客户关联','--'],['合作系统','会议系统'],['不合作原因','--'],['是否参加总裁班','是'],['是否参加产品经理班','否'],['总裁班参加人','陈主任'],['产品班参加人','--'],['总裁班参加人电话','13928762361'],['产品班参加人电话','--']])+'<div class="crm-info-note"><strong>备注</strong><div>重点合作客户,持续跟进年度战略协议续签与智慧校园项目。</div></div><div class="crm-info-attachment"><strong>附件</strong><div><a>《广州市XX职业技术学院客户资料》.doc</a></div></div>';const business=infoGrid([['统一社会信用代码',r.credit],['法定代表人','张三'],['成立时间','2018-03-15'],['注册资本','1000万元'],['经营范围','教育工程施工'],['年产值','--'],['人员规模','--'],['公司地址','广东省广州市番禺区某街1号']]);const system=infoGrid([['创建人','小李'],['创建时间','2026-06-05 17:47'],['最后修改人','小李'],['最后修改时间','2026-06-13 12:34']]);return fold('基本信息','basic',infoGrid([['客户名称',r.name],['客户编号',r.code],['客户类型',r.type],['所属地区',r.area],['行业',r.industry]]))+fold('跟进人员','follow-people',infoGrid([['销售负责人',r.owner],['销售部门',r.dept],['协同人','王敏、刘阳']]))+fold('关联信息','related',related)+fold('工商信息','business-info',business)+fold('系统信息','system-info',system)}
function detailTabs(r,embedded=false){const tabs=['客户信息','联系人','跟进记录','关联商机','关联项目','战略协议','团队成员','操作日志'];let body=state.tab==='客户信息'?customerInfo(r):state.tab==='联系人'?contactTab():state.tab==='战略协议'?agreementTab(r):state.tab==='团队成员'?teamTab():'<div class="crm-empty">'+state.tab+'演示内容</div>';return '<div class="crm-detail-tabs crm-customer-detail-tabs">'+tabs.map(t=>'<button class="'+(state.tab===t?'active':'')+'" onclick="customerV2.switchTab(\''+t+'\')">'+t+'</button>').join('')+'</div><div class="crm-customer-tab-body">'+body+'</div>'}
function renderCustomerDetail(id){const r=customers.find(x=>x.id===id)||customers[0];state.detail={type:'customer',id,from:state.detail?.from||state.page};root(state.page).innerHTML='<div class="crm-detail">'+customerSummary(r)+detailTabs(r)+'</div>';renderBreadcrumb();syncIndeterminate()}
function refreshCustomerDetailContext(){if(state.detail?.type==='customer'){renderCustomerDetail(state.detail.id);return}renderCustomer(state.page)}
function renderContacts(){const rows=filtered(contacts,['name','company','phone','job']);root('contact').innerHTML='<div class="crm-page crm-customer-page">'+titleBar('联系人',false)+toolbar('<button class="btn primary" onclick="customerV2.openForm(\'newContact\')">+ 新增联系人</button><button class="btn" onclick="customerV2.openForm(\'ocr\',null,\'contact\')">OCR识别名片</button>')+'<div class="crm-main"><div class="crm-scroll"><table class="crm-table"><thead><tr><th>序号</th><th>联系人</th><th>所属客户</th><th>职务</th><th>部门</th><th>联系电话</th><th>人脉关系</th><th>操作</th></tr></thead><tbody>'+rows.map((c,i)=>'<tr><td>'+(i+1)+'</td><td><button class="crm-link strong">'+c.name+'</button></td><td>'+c.company+'</td><td>'+c.job+'</td><td>'+c.department+'</td><td>'+c.phone+'</td><td>'+c.relation+'</td><td><button class="crm-link">详情</button></td></tr>').join('')+'</tbody></table></div>'+pagination(5)+'</div></div>'}
function simplePage(page,title,body){root(page).innerHTML='<div class="crm-page crm-customer-page no-toolbar">'+titleBar(title,false)+'<div class="crm-main" style="border-top:0"><div class="crm-scroll">'+body+'</div>'+pagination(20)+'</div></div>'}
function renderDedupe(){simplePage('customerDedupe','客户查重','<div class="crm-cards">'+customers.slice(0,4).map(r=>'<div class="crm-card"><h3>'+r.name+'</h3><div class="crm-card-grid"><span>相似度<br><b>92%</b></span><span>统一信用代码<br><b>'+r.credit+'</b></span></div></div>').join('')+'</div>')}
function renderHandover(){simplePage('customerHandover','客户交割','<table class="crm-table"><thead><tr><th>客户名称</th><th>原负责人</th><th>接收负责人</th><th>交割状态</th><th>操作</th></tr></thead><tbody>'+customers.slice(0,4).map(r=>'<tr><td>'+r.name+'</td><td>'+r.owner+'</td><td>待选择</td><td>'+pill('待交割')+'</td><td><button class="crm-link">查看</button></td></tr>').join('')+'</tbody></table>')}
function renderCustomerSetting(){simplePage('customerSetting','客户管理设置','<div class="crm-setting-grid"><section class="crm-setting-card"><h3>客户阶段</h3><p>配置潜在、重潜、已成交客户阶段。</p></section><section class="crm-setting-card"><h3>关系等级</h3><p>配置战略、重点、合作、高潜、潜在关系。</p></section></div>')}
function renderCustomer(page){if(['customerPool','myCustomer','customerSea'].includes(page))renderCustomerList(page);else if(page==='contact')renderContacts();else if(page==='customerDedupe')renderDedupe();else if(page==='customerHandover')renderHandover();else renderCustomerSetting()}
function field(label,key,type='input',required=false,value=''){const val=state.draftCustomer[key]??value;return '<label>'+(required?'<i style="color:#e11d48">*</i> ':'')+label+'</label>'+(type==='select'?'<select data-draft-key="'+key+'"><option>'+esc(val||'请选择')+'</option></select>':type==='textarea'?'<textarea data-draft-key="'+key+'">'+esc(val)+'</textarea>':'<input data-draft-key="'+key+'" value="'+esc(val)+'" placeholder="请输入'+label+'">')}
function customerForm(kind,id){const edit=kind==='editCustomer',r=customers.find(x=>x.id===id)||customers[0];if(!Object.keys(state.draftCustomer).length)state.draftCustomer=edit?{name:r.name,type:r.type,industry:r.industry,area:r.area,credit:r.credit,legal:'陈国强',registered:'5000万元',established:'2012-08-16',scope:'教育设备、信息系统集成与技术服务',scale:'500-999人',address:r.area,stage:r.stage,stars:'3星',relation:'3星',owner:'Panyu10109',dept:'广东1部',decision:'',deal:'否',agreement:'否',visit:'',sub:'否',parent:'',system:'',reason:'',president:'否',productManager:'否',presidentName:'',presidentPhone:'',productName:'',productPhone:'',remark:''}:{name:'',type:'请选择',industry:'请选择',area:'请选择',credit:'',legal:'',registered:'',established:'',scope:'',scale:'',address:'',stage:'潜在客户',stars:'3星',relation:'3星',owner:'Panyu10109',dept:'广东1部',decision:'',deal:'否',agreement:'否',visit:'',sub:'否',parent:'',system:'',reason:'',president:'否',productManager:'否',presidentName:'',presidentPhone:'',productName:'',productPhone:'',remark:''};return '<div class="crm-form-page"><div class="crm-form-page-head"><h1>'+(edit?'编辑客户信息':'新增客户信息')+'</h1><button class="btn" onclick="customerV2.back()">返回</button></div><section class="crm-form-section"><header><h3>基本信息</h3></header><div class="crm-form-grid">'+field('客户名称','name','input',true)+field('客户编号','code','input',false,edit?r.code:'系统自动生成')+field('客户类型','type','select',true)+field('所属地区','area','select',true)+field('行业','industry','select',true)+'</div><h3 style="padding:0 12px">联系人</h3><div class="crm-contact-editor">'+contactEditor()+'</div></section><section class="crm-form-section"><header><h3>工商信息</h3></header><div class="crm-form-grid">'+field('统一社会信用代码','credit','input',true)+field('法定代表人','legal')+field('成立时间','established')+field('注册资本','registered')+field('经营范围','scope','textarea')+field('年产值','annual','input',false,'2.6亿元')+field('人员规模','scale','select')+field('公司地址','address')+'</div></section><section class="crm-form-section"><header><h3>评级信息</h3></header><div class="crm-form-grid">'+field('客户阶段','stage','select',true)+field('客户星级','stars','select',true)+field('关系星级','relation','select',true)+'</div></section><section class="crm-form-section"><header><h3>跟进人员</h3></header><div class="crm-form-grid">'+field('销售负责人','owner','select',true)+field('销售部门','dept','select',true)+field('协同人','helper')+'</div></section><section class="crm-form-section"><header><h3>关联信息</h3></header><div class="crm-form-grid">'+field('公司决策人','decision')+field('是否商机勾兑','deal','select',true)+field('是否签订战略协议','agreement','select',true)+field('总监近期拜访时间','visit')+field('是否子客户','sub','select',true)+field('父客户关联','parent')+field('合作系统','system')+field('不合作原因','reason','textarea')+field('是否参加总裁班','president','select')+field('是否参加产品经理班','productManager','select')+field('总裁班参加人','presidentName')+field('总裁班参加人电话','presidentPhone')+field('产品班参加人','productName')+field('产品班参加人电话','productPhone')+field('备注','remark','textarea')+'</div></section><section class="crm-form-section"><header><h3>附件</h3></header><div class="crm-attachment-row"><button class="btn" onclick="customerV2.mockAttachment()">上传文件</button><span id="crmCustomerFile">'+(state.draftCustomer.file?'<span class="crm-file-chip">'+esc(state.draftCustomer.file)+' <button onclick="customerV2.removeAttachment()">删除</button></span>':'支持 .doc / .docx / .pdf / .xls / .xlsx')+'</span></div></section><div class="crm-form-foot"><button class="btn" onclick="customerV2.back()">取消</button><button class="btn primary" onclick="customerV2.submitForm(\''+(edit?'客户信息已保存':'客户已创建')+'\')">保存</button></div></div>'}
function homeCustomerModalHtml(){
const wrap=document.createElement('div');
wrap.innerHTML=customerForm('newCustomer');
const page=wrap.firstElementChild;
page?.querySelector('.crm-form-page-head')?.remove();
page?.querySelector('.crm-form-foot')?.remove();
return page?.outerHTML||'';
}
function renderHomeCustomerModal(){
const mask=document.getElementById('modal'),panel=mask?.querySelector('.modal');
if(!mask||!panel)return;
panel.classList.remove('opp-modal-wide','opp-modal-small','opp-modal-medium','crm-member-modal');
panel.classList.add('crm-home-customer-dialog');
document.getElementById('modalTitle').textContent='新增客户';
mask.querySelector('.m-body').innerHTML=homeCustomerModalHtml();
mask.querySelector('.m-foot').innerHTML='<button class="btn" onclick="customerV2.closeHomeCustomer()">取消</button><button class="btn primary" onclick="customerV2.submitHomeCustomer()">保存</button>';
mask.classList.add('show');
}
function openHomeCustomer(){
homeCustomerModal=true;
state.form='newCustomer';state.formId=null;state.formParent=null;state.resumeForm=false;
state.draftCustomer={};
state.contactRows=[{name:'',job:'',phone:'',relation:'',inside:'否',gift:'',source:'销售拜访'}];
renderHomeCustomerModal();
}
function closeHomeCustomer(){homeCustomerModal=false;state.form=null;closeModal()}
function submitHomeCustomer(){captureDraft();homeCustomerModal=false;state.form=null;notify('客户已创建');closeModal()}
function contactEditor(){return '<table class="crm-table"><thead><tr><th>序号</th><th>联系人*</th><th>职务*</th><th>联系电话*</th><th>人脉关系情况</th><th>是否内线</th><th>礼品备注</th><th>来源</th><th>操作</th></tr></thead><tbody>'+state.contactRows.map((r,i)=>'<tr><td>'+(i+1)+'</td><td><input value="'+esc(r.name)+'" oninput="customerV2.updateContactRow('+i+',\'name\',this.value)"></td><td><input value="'+esc(r.job)+'" oninput="customerV2.updateContactRow('+i+',\'job\',this.value)"></td><td><input value="'+esc(r.phone)+'" oninput="customerV2.updateContactRow('+i+',\'phone\',this.value)"></td><td><input value="'+esc(r.relation)+'" oninput="customerV2.updateContactRow('+i+',\'relation\',this.value)"></td><td><select onchange="customerV2.updateContactRow('+i+',\'inside\',this.value)"><option>'+r.inside+'</option><option>'+(r.inside==='是'?'否':'是')+'</option></select></td><td><input value="'+esc(r.gift)+'"></td><td><select><option>'+r.source+'</option></select></td><td><button class="crm-link crm-danger" onclick="customerV2.removeContactRow('+i+')">删除</button></td></tr>').join('')+'</tbody></table><div class="crm-contact-editor-footer"><button class="btn" onclick="customerV2.addContactRow()">新增一行</button><button class="btn" onclick="customerV2.captureAndOpenOcr()">OCR识别名片</button><button class="btn">批量导入</button></div>'}
function standaloneContactField(label,key,type='input',required=false){const value=state.contactDraft[key]||'',cls=state.contactOcrFilled&&value?'ocr-filled':'';let control=type==='textarea'?'<textarea class="'+cls+'" data-contact-key="'+key+'" oninput="customerV2.updateStandaloneContact(\''+key+'\',this.value)">'+esc(value)+'</textarea>':type==='select'?'<select class="'+cls+'" data-contact-key="'+key+'" onchange="customerV2.updateStandaloneContact(\''+key+'\',this.value)"><option>'+esc(value||'请选择')+'</option><option>是</option><option>否</option></select>':'<input class="'+cls+'" value="'+esc(value)+'" data-contact-key="'+key+'" oninput="customerV2.updateStandaloneContact(\''+key+'\',this.value)" placeholder="请输入">';return '<label>'+(required?'<i>*</i>':'')+label+'</label>'+control}
function newContactForm(){return '<div class="crm-form-page crm-contact-standalone"><div class="crm-form-page-head"><h1>新增联系人</h1><button class="btn" onclick="customerV2.back()">返回</button></div>'+(state.contactOcrFilled?'<div class="ocr-banner">✓ OCR已自动填充 5 项字段</div>':'')+'<section class="crm-form-section"><header><h3>基本信息</h3></header><div class="crm-form-grid">'+standaloneContactField('联系人姓名','name','input',true)+standaloneContactField('职务','job')+standaloneContactField('公司名称','company','input',true)+standaloneContactField('部门','department')+standaloneContactField('联系人角色','role')+standaloneContactField('联系人负责人','owner','input',true)+standaloneContactField('性别','gender')+standaloneContactField('出生日期','birthday')+'</div></section><section class="crm-form-section"><header><h3>联系信息</h3></header><div class="crm-form-grid">'+standaloneContactField('联系电话','phone')+standaloneContactField('微信','wechat')+standaloneContactField('电子邮箱','email')+standaloneContactField('地址','address')+'</div></section><section class="crm-form-section"><header><h3>关联信息</h3></header><div class="crm-form-grid">'+standaloneContactField('人脉关系情况','relation')+standaloneContactField('是否内线','inside','select')+standaloneContactField('礼品备注','gift')+standaloneContactField('来源','source','select')+standaloneContactField('备注','remark','textarea')+'</div></section><div class="crm-form-foot"><button class="btn" onclick="customerV2.back()">取消</button><button class="btn primary" onclick="customerV2.submitForm(\'联系人已保存\')">保存</button></div></div>'}
function addContactForm(id){const r=customers.find(x=>x.id===id)||customers[0];return '<div class="crm-form-page"><div class="crm-form-page-head"><div><h1>添加联系人</h1><p>客户:'+r.name+'</p></div><button class="btn" onclick="customerV2.back()">返回</button></div><section class="crm-form-section"><header><h3>联系人</h3></header><div class="crm-contact-editor">'+contactEditor()+'</div></section><div class="crm-form-foot"><button class="btn" onclick="customerV2.back()">取消</button><button class="btn primary" onclick="customerV2.submitForm(\'联系人已保存\')">保存</button></div></div>'}
function sharedImportPage(module){const isLead=module==='lead',title=isLead?'导入线索':'客户导入',back=isLead?'leadV2.back()':'customerV2.back()';return '<div class="crm-import-page"><div class="crm-import-head"><h1>'+title+'</h1><button class="btn" onclick="'+back+'">返回</button></div><div class="crm-import-body"><section><h3>导入方式</h3><div class="crm-import-modes">'+['仅新增数据','仅更新数据','新增或更新数据'].map(x=>'<button class="'+(state.importMode===x?'active':'')+'" onclick="customerV2.setImportMode(\''+x+'\',\''+module+'\')">'+x+'</button>').join('')+'</div><div class="crm-import-checks"><label><input type="checkbox"> 跳过校验规则、业务规则、集成自动化流、消息通知和第三方服务回调</label><label><input type="checkbox" '+(state.importDuplicateMode==='overwrite'?'checked':'')+' onchange="customerV2.setImportDuplicateMode(\'overwrite\',this.checked,\''+module+'\')"> 数据重复时覆盖导入</label><label><input type="checkbox" '+(state.importDuplicateMode==='skip'?'checked':'')+' onchange="customerV2.setImportDuplicateMode(\'skip\',this.checked,\''+module+'\')"> 数据重复时不导入</label></div></section><section><h3>上传 Excel 文件</h3><p>为了保证数据导入顺利,请先 <button class="crm-link" onclick="customerV2.quick(\'下载导入模板(Demo)\')">⇩ 下载导入模板</button>,并按照规范示例导入数据。</p><div class="crm-upload-zone" onclick="customerV2.quick(\'已选择:'+(isLead?'线索':'客户')+'导入模板.xlsx(Demo)\')"><b>⇧ 点击上传</b><span>可拖拽或粘贴到此处上传</span><small>支持 .xls、.xlsx 类型文件,大小上限20M</small></div></section><section class="crm-file-rules"><h3>文件规则</h3><ol><li>文件大小不超过 20M,且单个 Sheet 页数据量不超过 50000 行;当 Excel 内包含附件、图片、成员、部门、地址、富文本等复杂组件,或触发校验规则、业务规则、集成自动化流、消息通知和第三方服务回调时,单个 Sheet 页数据量不超过 5000 行(子表行数也纳入计算)。</li><li>仅支持 .xls 和 .xlsx 文件。</li><li>请确保需要导入的 Sheet 表头中不包含空的单元格,否则该 Sheet 页数据系统将不做导入。</li><li>批量导入的数据不支持以“内容为空”作为条件进行过滤。</li><li>导入文件不支持 Excel 公式计算,如 SUM、H2*J2 等。</li><li>目前导入图片/附件会被保存,占用一定附件容量。</li></ol></section><div class="crm-import-foot"><button class="btn" onclick="'+back+'">取消</button><button class="btn primary" onclick="customerV2.quick(\'进入下一步(Demo)\')">下一步</button></div></div></div>'}
function agreementForm(id,edit){const r=customers.find(x=>x.id===state.detail?.id)||customers.find(x=>x.id===id)||customers[0],a=edit?(agreements.find(x=>x.id===id)||agreements[0]):null,title=edit?'编辑战略协议':'新增战略协议';return '<div class="crm-form-page"><div class="crm-form-page-head"><h1>'+title+'</h1><button class="btn" onclick="customerV2.back()">返回</button></div><section class="crm-form-section"><header><h3>协议信息</h3></header><div class="crm-form-grid">'+field('关联客户','agCustomer','select',true,r.name)+field('协议名称','agName','input',true,a?a.name:'')+field('战略协议等级','agLevel','select',true,a?a.level:'三级')+field('签订金额','agAmount','input',true,a?a.amount:'300万元')+field('签订日期','agSigned','input',true,a?a.signed:'2026-07-01')+field('有效期','agValid','input',true,a?a.valid:'2026-07-01 至 2027-06-30')+field('备注','agRemark','textarea',false,a?a.remark:'年度战略合作协议')+'</div></section><section class="crm-form-section"><header><h3>附件</h3></header><div class="crm-agreement-upload"><button class="btn" onclick="customerV2.mockAgreementFile()">上传文件</button><span id="crmAgreementFile">'+(state.agreementFile?'<span class="crm-file-chip">'+state.agreementFile+' <button onclick="customerV2.removeAgreementFile()">删除</button></span>':a?'<span class="crm-file-chip">'+a.attachment+' <button onclick="customerV2.removeAgreementFile()">删除</button></span>':'支持 .rar、.zip、.doc、.docx、.pdf')+'</span></div></section><div class="crm-form-foot"><button class="btn" onclick="customerV2.back()">取消</button><button class="btn primary" onclick="customerV2.submitForm(\''+(edit?'战略协议已更新':'战略协议已保存')+'\')">保存</button></div></div>'}
function ocrPage(){const isContact=state.ocrContext==='contact',scenes=isContact?[['success','识别成功'],['fail','识别失败'],['existingContact','已有联系人']]:[['success','识别成功'],['fail','识别失败'],['existingCustomer','已有客户']];return '<div class="crm-ocr-page"><div class="crm-ocr-head"><h1>'+(isContact?'OCR识别联系人':'OCR快捷新增客户')+'</h1><button class="btn" onclick="customerV2.returnFromOcr()">返回</button></div><div class="crm-ocr-body"><div class="crm-ocr-scenarios"><span class="crm-ocr-context">演示场景:</span>'+scenes.map(x=>'<button class="btn '+(state.ocrScenario===x[0]?'active':'')+'" onclick="customerV2.setOcrScenario(\''+x[0]+'\')">'+x[1]+'</button>').join('')+'</div><div class="crm-ocr-stage">'+ocrStage()+'</div></div></div>'}
function ocrStage(){const isContact=state.ocrContext==='contact',subject=isContact?'联系人':'客户';if(state.ocrState==='loading')return '<div class="crm-ocr-loading"><div class="crm-ocr-spinner"></div><h3>正在识别'+subject+'名片信息...</h3><p>预计需要 2-5 秒</p></div>';if(state.ocrState==='fail')return '<div class="crm-ocr-result"><h3>OCR识别失败</h3><div class="crm-ocr-alert fail">图片模糊或信息不完整,请重新上传或手动新增'+subject+'。</div><footer><button class="btn" onclick="customerV2.resetOcr()">重新上传</button><button class="btn primary" onclick="customerV2.finishOcr(\'manual\')">手动新增'+subject+'</button></footer></div>';if(state.ocrState==='existingCustomer')return '<div class="crm-ocr-result"><h3>OCR-检测到已有该客户</h3><div class="crm-ocr-alert warn">检测到系统中已有同名客户,请选择关联已有客户,或继续创建新客户。</div><label class="crm-match-card active"><input type="radio" name="customerMatch" checked><span><b>广州星熠科技有限公司</b><small>名称完全匹配 甲方(企业用户) 负责人:小李</small></span></label><label class="crm-match-card"><input type="radio" name="customerMatch"><span><b>广州星熠科技发展有限公司</b><small>名称相似度85% 甲方(企业用户) 负责人:小王</small></span></label><div class="crm-ocr-choice"><input type="radio" name="customerMatch"><span><b>不关联,创建新客户</b></span></div><footer><button class="btn" onclick="customerV2.returnFromOcr()">取消</button><button class="btn primary" onclick="customerV2.finishOcr(\'existingCustomer\')">确认</button></footer></div>';if(state.ocrState==='existingContact')return '<div class="crm-ocr-result"><h3>OCR-检测到已有该联系人</h3><div class="crm-ocr-alert fail">检测到系统中已存在相同联系人,请确认后选择处理方式。</div><div class="crm-ocr-compare"><dl><dt>姓名</dt><dd><span style="color:#16a34a">陈明辉(一致)</span></dd><dt>所属客户</dt><dd><span style="color:#16a34a">广州星熠科技有限公司(一致)</span></dd><dt>联系电话</dt><dd><span style="color:#16a34a">13800138000(一致)</span></dd><dt>职务</dt><dd><span style="color:#ef4444">系统:采购部经理 OCR:采购经理</span></dd><dt>地址</dt><dd><span style="color:#ef4444">系统:-- OCR:天河XX路</span></dd><dt>负责人</dt><dd>小李</dd><dt>最近跟进</dt><dd>2026-06-05 09:12</dd></dl></div><div class="crm-ocr-choice-grid"><label class="crm-ocr-choice active"><input type="radio" name="contactChoice" checked><span><b>更新已有记录</b><small>用OCR信息更新已有字段</small></span></label><label class="crm-ocr-choice"><input type="radio" name="contactChoice"><span><b>仍创建新联系人</b><small>作为新的联系人保存</small></span></label></div><footer><button class="btn" onclick="customerV2.returnFromOcr()">取消</button><button class="btn primary" onclick="customerV2.finishOcr(\'existingContact\')">确认</button></footer></div>';return '<div class="crm-ocr-upload" onclick="customerV2.startOcr()"><div class="icon">⇧</div><b>拖拽图片到此处</b><span>或点击选择文件</span><small>支持 JPG / PNG 格式,文件大小不超过10MB</small><button class="btn">从本地选择</button></div>'}
function openForm(kind,id,context){const same=state.form===kind&&state.formId===(id??null);if(kind==='newCustomer'&&!same&&!state.resumeForm){state.draftCustomer={};state.contactRows=[{name:'',job:'',phone:'',relation:'',inside:'否',gift:'',source:'销售拜访'}]}if(kind==='newContact'&&!same&&!state.resumeForm){state.contactDraft={};state.contactOcrFilled=false}if(kind==='editCustomer'&&!same&&!state.resumeForm){state.draftCustomer={};state.contactRows=[{name:'陈大壮',job:'采购负责人',phone:'13897283272',relation:'核心决策人',inside:'是',gift:'节日问候',source:'销售拜访'}]}state.resumeForm=false;state.form=kind;state.formId=id??null;if(kind==='ocr'){state.ocrContext=context||state.ocrContext||'customer';if(!state.formParent)state.ocrReturn=state.ocrContext==='contact'&&state.detail?'contactTab':'list'}const r=root();if(kind==='newCustomer'||kind==='editCustomer')r.innerHTML=customerForm(kind,id);else if(kind==='newContact')r.innerHTML=newContactForm();else if(kind==='addContact')r.innerHTML=addContactForm(id);else if(kind==='importCustomer')r.innerHTML=sharedImportPage('customer');else if(kind==='ocr')r.innerHTML=ocrPage();else if(kind==='agreement')r.innerHTML=agreementForm(id,false);else if(kind==='editAgreement')r.innerHTML=agreementForm(id,true);else r.innerHTML='<div class="crm-form-page"><div class="crm-form-page-head"><h1>新增联系人</h1><button class="btn" onclick="customerV2.back()">返回</button></div></div>';renderBreadcrumb()}
function captureDraft(){document.querySelectorAll('[data-draft-key]').forEach(el=>state.draftCustomer[el.dataset.draftKey]=el.value)}
function captureAndOpenOcr(){if(homeCustomerModal){captureDraft();notify('首页新增客户弹窗暂不展开OCR子流程');return}captureDraft();state.formParent=state.form;state.ocrContext='contact';state.ocrReturn='form';state.ocrState='upload';state.ocrScenario='success';openForm('ocr',state.formId,'contact')}
function returnFromOcr(){const parent=state.formParent;if(parent){state.formParent=null;state.resumeForm=true;openForm(parent,state.formId);return}if(state.ocrContext==='contact'&&state.detail){state.form=null;state.tab='联系人';renderCustomerDetail(state.detail.id);return}back()}
function addContactRow(){captureDraft();state.contactRows.push({name:'',job:'',phone:'',relation:'',inside:'否',gift:'',source:'销售拜访'});if(homeCustomerModal){renderHomeCustomerModal();return}openForm(state.form,state.formId)}
function removeContactRow(i){if(state.contactRows.length===1){notify('至少保留一行联系人');return}captureDraft();state.contactRows.splice(i,1);if(homeCustomerModal){renderHomeCustomerModal();return}openForm(state.form,state.formId)}
function updateContactRow(i,key,val){if(state.contactRows[i])state.contactRows[i][key]=val}
function setOcrScenario(v){ocrTaskToken++;state.ocrScenario=v;state.ocrState='upload';openForm('ocr',state.formId,state.ocrContext)}
function startOcr(){const token=++ocrTaskToken;state.ocrState='loading';openForm('ocr',state.formId,state.ocrContext);setTimeout(()=>{if(token!==ocrTaskToken||state.form!=='ocr'||!document.querySelector('.page.active .crm-ocr-page'))return;if(state.ocrScenario==='success'){finishOcr('success');return}state.ocrState=state.ocrScenario;openForm('ocr',state.formId,state.ocrContext)},1050)}
function resetOcr(){ocrTaskToken++;state.ocrState='upload';openForm('ocr',state.formId)}
function finishOcr(kind){if(state.ocrContext==='customer'){if(kind==='existingCustomer'){notify('已关联已有客户(Demo)');back();return}state.draftCustomer={name:'广州星熠科技有限公司',type:'甲方(企业用户)',industry:'信息系统集成',area:'广东省 / 广州市 / 天河区',credit:'91440101MA5OCR2026',legal:'陈国强',registered:'5000万元',established:'2012-08-16',scope:'教育设备、信息系统集成与技术服务',scale:'500-999人',address:'广州市天河区XX路',stage:'潜在客户',stars:'3星',relation:'3星',owner:'Panyu10109',dept:'广东1部',decision:'',deal:'否',agreement:'否',visit:'',sub:'否',parent:'',system:'',reason:'',president:'否',productManager:'否',presidentName:'',presidentPhone:'',productName:'',productPhone:'',remark:''};state.contactRows=[{name:'陈明辉',job:'采购经理',phone:'13800138000',relation:'核心联系人',inside:'否',gift:'',source:'OCR识别'}];state.form=null;state.formParent=null;state.resumeForm=true;notify('OCR已自动填充客户字段');openForm('newCustomer',null);return}const recognized={id:Date.now(),name:'陈明辉',company:'广州星熠科技有限公司',job:'采购经理',department:'采购部',phone:'13800138000',relation:'核心联系人',inside:'否',gift:'',source:'OCR识别',isNew:true};if(state.formParent){state.contactRows[0]={name:recognized.name,job:recognized.job,phone:recognized.phone,relation:recognized.relation,inside:recognized.inside,gift:'',source:'OCR识别'};notify('OCR已回填联系人字段');returnFromOcr();return}if(state.detail){detailContactRows().push(recognized);state.contactEditMode=true;notify(kind==='existingContact'?'已有联系人处理完成(Demo)':'OCR已新增联系人行');returnFromOcr();return}state.contactDraft={name:recognized.name,job:recognized.job,company:recognized.company,department:recognized.department,phone:recognized.phone,address:'天河XX路',owner:'小李',relation:recognized.relation,inside:'否',source:'OCR识别'};state.contactOcrFilled=true;state.form=null;state.resumeForm=true;notify('OCR已自动填充联系人字段');openForm('newContact')}
function mockAttachment(){state.draftCustomer.file='客户资料附件.pdf';document.getElementById('crmCustomerFile').innerHTML='<span class="crm-file-chip">客户资料附件.pdf <button onclick="customerV2.removeAttachment()">删除</button></span>'}
function removeAttachment(){state.draftCustomer.file='';document.getElementById('crmCustomerFile').textContent='支持 .doc / .docx / .pdf / .xls / .xlsx'}
function mockAgreementFile(){state.agreementFile='2026年度战略合作协议.pdf';document.getElementById('crmAgreementFile').innerHTML='<span class="crm-file-chip">'+state.agreementFile+' <button onclick="customerV2.removeAgreementFile()">删除</button></span>'}
function removeAgreementFile(){state.agreementFile='';document.getElementById('crmAgreementFile').textContent='支持 .rar、.zip、.doc、.docx、.pdf'}
function submitForm(msg){notify(msg);setTimeout(()=>back(),250)}
function setImportMode(v,module){state.importMode=v;if(module==='lead')document.getElementById('crmLeadImportRoot').innerHTML=sharedImportPage('lead');else openForm('importCustomer')}
function renderBreadcrumb(){
const bar=document.querySelector('.location-bar');if(!bar)return;
const root='<span class="crumb-parent">客户</span>';
const currentPage=state.searchLabelByPage[state.page]||pageLabels[state.page];
if(state.form){
const current=state.form==='newCustomer'?'新增客户':state.form==='editCustomer'?'编辑客户信息':state.form==='importCustomer'?'客户导入':state.form==='ocr'?(state.ocrContext==='contact'?'OCR识别联系人':'OCR快捷新增客户'):state.form==='agreement'?'新增战略协议':state.form==='editAgreement'?'编辑战略协议':'新增联系人';
if(state.detail){const item=customers.find(x=>x.id===state.detail.id);bar.innerHTML=root+'<span class="crumb-sep">›</span><button class="crumb-btn" onclick="customerV2.backToList()">'+(pageLabels[state.detail.from]||currentPage)+'</button><span class="crumb-sep">›</span><button class="crumb-btn" onclick="customerV2.backToDetail()">'+esc(item?.name||'客户详情')+'</button><span class="crumb-sep">›</span><span class="crumb-current">'+current+'</span>'}
else bar.innerHTML=root+'<span class="crumb-sep">›</span><button class="crumb-btn" onclick="customerV2.back()">'+currentPage+'</button><span class="crumb-sep">›</span><span class="crumb-current">'+current+'</span>';
return;
}
if(state.detail){const item=customers.find(x=>x.id===state.detail.id),parent=pageLabels[state.detail.from]||currentPage;bar.innerHTML=root+'<span class="crumb-sep">›</span><button class="crumb-btn" onclick="customerV2.backToList()">'+parent+'</button><span class="crumb-sep">›</span><span class="crumb-current">'+esc(item?.name||'详情')+'</span>';return}
bar.innerHTML=root+'<span class="crumb-sep">›</span><span class="crumb-current">'+currentPage+'</span>';
}
function enter(page,label){state.page=page;if(['customerPool','myCustomer','customerSea'].includes(page))state.viewByPage[page]='list';state.detail=null;state.form=null;state.formId=null;state.formParent=null;state.query='';state.searchOpen=false;state.tab='客户信息';renderCustomer(page);renderBreadcrumb()}
function setView(v){state.viewByPage[state.page]=v;state.detail=null;renderCustomer(state.page)}
function setCardGroup(v){state.cardGroupBy[state.page]=v;renderCustomer(state.page)}
function query(v){state.query=v||'';renderCustomer(state.page)}
function reset(){state.query='';renderCustomer(state.page)}
function toggleCustomerSelection(id,checked){const set=state.selectedByPage[state.page]||new Set();checked?set.add(id):set.delete(id);state.selectedByPage[state.page]=set;renderCustomer(state.page)}
function toggleAllCustomers(checked){const set=state.selectedByPage[state.page]||new Set();customerRows(state.page).forEach(r=>checked?set.add(r.id):set.delete(r.id));state.selectedByPage[state.page]=set;renderCustomer(state.page)}
function selectCustomer(id){state.selectedCustomer=id;renderCustomer(state.page)}
function toggleCustomerSplit(){state.splitCollapsedByPage[state.page]=!state.splitCollapsedByPage[state.page];renderCustomer(state.page)}
function openDetail(type,id){state.detail={type:'customer',id,from:state.page};state.form=null;state.tab='客户信息';renderCustomerDetail(id)}
function back(){if(state.form){if(state.formParent){returnFromOcr();return}const detail=state.detail;state.form=null;state.formId=null;state.draftCustomer={};if(detail){renderCustomerDetail(detail.id);return}renderCustomer(state.page);renderBreadcrumb();return}if(state.detail){backToList();return}setActivePage(state.page,'客户 / '+pageLabels[state.page])}
function backToList(){const from=state.detail?.from||state.page;state.form=null;state.formId=null;state.detail=null;state.page=from;renderCustomer(from);renderBreadcrumb()}
function backToDetail(){if(!state.detail)return;state.form=null;state.formId=null;state.draftCustomer={};renderCustomerDetail(state.detail.id)}
function switchTab(t){state.tab=t;refreshCustomerDetailContext()}
function toggleFold(k){state.folds[k]=!state.folds[k];refreshCustomerDetailContext()}
function toggleSearchMenu(){state.searchOpen=!state.searchOpen;renderCustomer(state.page)}
function closeSearchMenu(){if(!state.searchOpen)return;state.searchOpen=false;document.querySelectorAll('.crm-search-menu').forEach(node=>node.classList.remove('show'));document.querySelectorAll('.crm-title-trigger').forEach(node=>node.classList.remove('open'))}
function setSearchTab(t){state.searchTab=t;renderCustomer(state.page)}
function navigateSearch(page,label){state.searchLabelByPage[page]=label;state.searchOpen=false;setActivePage(page,'客户 / '+label)}
function summaryAction(name,id){if(name==='跟进')quick(name);else quick(name)}
function quick(name){notify(name+'(Demo)')}
function openMemberDialog(kind){const mask=document.getElementById('modal'),box=mask?.querySelector('.modal'),title=document.getElementById('modalTitle'),body=mask?.querySelector('.m-body'),foot=mask?.querySelector('.m-foot');if(!mask||!box||!title||!body||!foot){quick('团队成员');return}box.classList.add('crm-member-modal');if(kind==='add'){title.textContent='添加成员';body.innerHTML='<div class="crm-member-search"><input placeholder="搜索姓名 / 部门 / 职务"></div><div class="crm-member-candidates"><label><input type="checkbox"><span class="crm-member-avatar">王</span><span><b>王敏</b><small>广东1部 · 销售助理</small></span></label><label><input type="checkbox"><span class="crm-member-avatar">刘</span><span><b>刘阳</b><small>方案中心 · 方案工程师</small></span></label><label><input type="checkbox"><span class="crm-member-avatar">陈</span><span><b>陈亚</b><small>广东10部 · 销售经理</small></span></label></div><div class="crm-member-selected">已选择 0 名成员</div><div class="crm-member-field"><label>权限</label><div class="crm-member-permissions"><button class="active">读写</button><button>全部查看</button><button>脱敏查看</button></div></div>';foot.innerHTML='<button class="btn" onclick="closeModal()">取消</button><button class="btn primary" onclick="closeModal();customerV2.quick(\'成员已添加\')">确认添加</button>'}else if(kind==='permission'){title.textContent='调整权限';body.innerHTML='<div class="crm-member-profile"><span>小</span><div><b>小李</b><small>广东1部 · 客户经理</small></div></div><div class="crm-member-field"><label>权限</label><div class="crm-member-permissions"><button class="active">读写</button><button>全部查看</button><button>脱敏查看</button></div></div>';foot.innerHTML='<button class="btn" onclick="closeModal()">取消</button><button class="btn primary" onclick="closeModal();customerV2.quick(\'权限已调整\')">保存</button>'}else{title.textContent='删除成员';body.innerHTML='<div class="crm-member-delete"><b>确认删除成员“小李”吗?</b><p>删除后该成员将无法继续查看或编辑此客户。</p></div>';foot.innerHTML='<button class="btn" onclick="closeModal()">取消</button><button class="btn crm-delete-confirm" onclick="closeModal();customerV2.quick(\'成员已删除\')">确认删除</button>'}mask.classList.add('show')}
function addDetailContactRow(){const rows=detailContactRows(),id=Math.max(0,...rows.map(x=>Number(x.id)||0))+1;rows.push({id,name:'',company:'',job:'',department:'',phone:'',relation:'',inside:'否',gift:'',source:'销售拜访',isNew:true});state.contactEditMode=true;state.selectedContactIds.add(id);refreshCustomerDetailContext()}
function removeDetailContact(id){const rows=detailContactRows();if(rows.length<=1){notify('至少保留一位联系人');return}state.detailContacts=rows.filter(x=>x.id!==id);state.selectedContactIds.delete(id);refreshCustomerDetailContext()}
function updateDetailContact(id,key,value){const row=detailContactRows().find(x=>x.id===id);if(row)row[key]=value}
function toggleDetailContact(id,on){on?state.selectedContactIds.add(id):state.selectedContactIds.delete(id);refreshCustomerDetailContext()}
function toggleAllDetailContacts(on){detailContactRows().forEach(x=>on?state.selectedContactIds.add(x.id):state.selectedContactIds.delete(x.id));refreshCustomerDetailContext()}
function setContactEdit(on){state.contactEditMode=on;refreshCustomerDetailContext()}
function deleteSelectedContacts(){if(!state.selectedContactIds.size){notify('请先选择联系人');return}if(detailContactRows().length-state.selectedContactIds.size<1){notify('至少保留一位联系人');return}state.detailContacts=detailContactRows().filter(x=>!state.selectedContactIds.has(x.id));state.selectedContactIds.clear();refreshCustomerDetailContext();notify('已删除选中联系人')}
function saveDetailContacts(){state.contactEditMode=false;state.selectedContactIds.clear();detailContactRows().forEach(x=>delete x.isNew);refreshCustomerDetailContext();notify('联系人信息已保存')}
function openContactOcr(){state.ocrContext='contact';state.ocrScenario='success';state.ocrState='upload';state.formParent=null;openForm('ocr',state.detail?.id,'contact')}
function selectAgreement(id){state.selectedAgreementId=id;refreshCustomerDetailContext()}
function setImportDuplicateMode(mode,checked,module){state.importDuplicateMode=checked?mode:'none';if(module==='lead')document.getElementById('crmLeadImportRoot').innerHTML=sharedImportPage('lead');else openForm('importCustomer')}
function updateStandaloneContact(key,value){state.contactDraft[key]=value}
function captureAndEdit(id){openForm('editCustomer',id)}
function getHomeContactRows(customerName){return contacts.filter(function(contact){return contact.company===customerName}).map(function(contact){return {name:contact.name,job:contact.job,department:contact.department,phone:contact.phone,relation:contact.relation}})}
return {handles,enter,renderBreadcrumb,setView,setCardGroup,query,reset,listTool,toggleCustomerSelection,toggleAllCustomers,selectCustomer,toggleCustomerSplit,openDetail,back,backToList,backToDetail,switchTab,toggleFold,toggleSearchMenu,closeSearchMenu,setSearchTab,navigateSearch,summaryAction,quick,openForm,openHomeCustomer,closeHomeCustomer,submitHomeCustomer,submitForm,addContactRow,removeContactRow,updateContactRow,captureAndOpenOcr,returnFromOcr,setOcrScenario,startOcr,resetOcr,finishOcr,mockAttachment,removeAttachment,mockAgreementFile,removeAgreementFile,setImportMode,setImportDuplicateMode,sharedImportPage,openMemberDialog,notify,addDetailContactRow,removeDetailContact,updateDetailContact,toggleDetailContact,toggleAllDetailContacts,setContactEdit,deleteSelectedContacts,saveDetailContacts,openContactOcr,selectAgreement,updateStandaloneContact,captureAndEdit,getHomeContactRows};
})();
const leadV2=(function(){
const pages={lead:'crmLeadRoot',leadClaim:'crmLeadClaimRoot',leadFollow:'crmLeadFollowRoot',leadManage:'crmLeadManageRoot',leadRecent:'crmLeadRecentRoot',leadImport:'crmLeadImportRoot',leadCreate:'crmLeadCreateRoot',leadSetting:'crmLeadSettingRoot',leadSettingEdit:'crmLeadSettingEditRoot',leadSettingImport:'crmLeadSettingImportRoot'};
const currentUser='Panyu10109';
const rows=[
{id:1,name:'网络奇迹有限公司',phone:'156****9615',urgent:'是',feedback:'有效',content:'有会议系统采购计划',lastFeedback:'2026-06-12 11:05',province:'广东省',city:'广州市',channel:'展会',brand:'itc',product:'LED显示屏',scene:'会议室',consult:'需要部署会议大屏系统,面积约150方',poolStatus:'待领取',mineStatus:'跟进中',followStatus:'待领取',manageStatus:'待领取',owner:'-',salesDept:'销售10部',salesTeam:'冠军队',created:'2026-05-20 14:30',attention:'8人关注',timeout:'10天',logs:[]},
{id:2,name:'网络奇迹有限公司',phone:'138****6150',urgent:'是',feedback:'无效',content:'空号',lastFeedback:'2026-06-11 10:25',province:'广东省',city:'广州市',channel:'知乎',brand:'itc',product:'会议系统',scene:'报告厅',consult:'-',poolStatus:'已领取',mineStatus:'已联系',followStatus:'已领取',manageStatus:'已领取',owner:'林枫',salesDept:'销售5部',salesTeam:'冠军队',created:'2026-05-11 14:30',attention:'2人关注',timeout:'10天',logs:[]},
{id:3,name:'全希科技有限公司',phone:'156****6169',urgent:'否',feedback:'未反馈',content:'不需要',lastFeedback:'2026-06-10 11:05',province:'湖南省',city:'长沙市',channel:'京东',brand:'世畅',product:'录播系统',scene:'普教',consult:'-',poolStatus:'待领取',mineStatus:'未处理',followStatus:'待领取',manageStatus:'未分发',owner:'-',salesDept:'销售5部',salesTeam:'冠军队',created:'2026-05-11 14:30',attention:'2人关注',timeout:'10天',logs:[]},
{id:4,name:'华南理工大学礼堂',phone:'186****1123',urgent:'否',feedback:'有效',content:'教学录播',lastFeedback:'2026-06-10 11:05',province:'广东省',city:'广州市',channel:'1688',brand:'北航星',product:'教学系统',scene:'高职教',consult:'-',poolStatus:'已领取',mineStatus:'已转商机',followStatus:'已领取',manageStatus:'已转商机',owner:currentUser,salesDept:'销售10部',salesTeam:'冠军队',created:'2026-05-11 14:30',attention:'0人关注',timeout:'20天',logs:[]},
{id:5,name:'星辰科技',phone:'158****9420',urgent:'是',feedback:'有效',content:'潜在客户',lastFeedback:'2026-06-10 11:05',province:'江西省',city:'南昌市',channel:'小红书',brand:'特空',product:'广播系统',scene:'金融',consult:'户外广播',poolStatus:'过期失效',mineStatus:'过期失效',followStatus:'过期失效',manageStatus:'过期失效',owner:currentUser,salesDept:'销售10部',salesTeam:'特战队',created:'2026-05-11 14:30',attention:'10人关注',timeout:'0天',logs:[]},
{id:6,name:'天河区法院',phone:'156****9816',urgent:'否',feedback:'有效',content:'长期跟进',lastFeedback:'2026-06-10 11:05',province:'广东省',city:'广州市',channel:'抖音号',brand:'CRX',product:'无纸化会议系统',scene:'政府单位',consult:'150平方米会议室',poolStatus:'待领取',mineStatus:'跟进中',followStatus:'待领取',manageStatus:'待领取',owner:'-',salesDept:'销售10部',salesTeam:'粤晋战队',created:'2026-05-11 14:30',attention:'8人关注',timeout:'10天',logs:[]},
{id:7,name:'广州市交通局',phone:'156****9603',urgent:'否',feedback:'有效',content:'有需要',lastFeedback:'2026-06-10 11:05',province:'广东省',city:'广州市',channel:'百度教育',brand:'丽声',product:'指挥中心',scene:'交通',consult:'大屏控制系统',poolStatus:'待领取',mineStatus:'已联系',followStatus:'待领取',manageStatus:'未分发',owner:'-',salesDept:'销售10部',salesTeam:'粤晋战队',created:'2026-05-11 14:30',attention:'8人关注',timeout:'10天',logs:[]},
{id:8,name:'舞台灯光采购线索',phone:'156****9818',urgent:'否',feedback:'无效',content:'空号',lastFeedback:'2026-06-10 11:05',province:'广东省',city:'广州市',channel:'凤凰网',brand:'3A',product:'舞台灯光',scene:'酒吧KTV',consult:'-',poolStatus:'已领取',mineStatus:'已转商机',followStatus:'已领取',manageStatus:'已转商机',owner:currentUser,salesDept:'销售10部',salesTeam:'粤晋战队',created:'2026-05-11 14:30',attention:'8人关注',timeout:'10天',logs:[]}
];
const fieldDefs=[
['no','序号'],['name','客户名称'],['phone','联系号码'],['urgent','加急'],['feedback','反馈情况'],['content','反馈内容'],['lastFeedback','最后反馈时间'],['province','省份'],['city','城市'],['channel','渠道'],['brand','品牌'],['product','需求产品'],['scene','需求场景'],['consult','咨询内容'],['status','线索状态'],['timeout','超时回收剩余有效期'],['owner','领取人'],['salesDept','销售部门'],['salesTeam','销售团队'],['created','新增时间'],['attention','关注 / 领取人数'],['action','操作']
];
const pageFields={
lead:['no','name','phone','urgent','feedback','content','lastFeedback','province','city','channel','brand','product','consult','status','owner','salesTeam','created','attention','action'],
leadClaim:['no','name','phone','urgent','feedback','content','lastFeedback','province','city','channel','brand','product','consult','status','timeout','owner','salesDept','salesTeam','action'],
leadFollow:['no','name','phone','urgent','feedback','content','lastFeedback','province','city','channel','brand','product','consult','status','owner','salesTeam','created','action'],
leadManage:['no','name','phone','urgent','province','city','channel','brand','product','scene','consult','status','salesTeam','owner','created','attention','action'],
leadRecent:['no','name','phone','urgent','feedback','lastFeedback','province','city','channel','brand','product','status','owner','created','attention','action']
};
const defaultVisible=fieldDefs.map(x=>x[0]);
const storedFields=(()=>{try{return JSON.parse(localStorage.getItem('crmLeadVisibleFields')||'null')}catch(e){return null}})();
const storedOrder=(()=>{try{return JSON.parse(localStorage.getItem('crmLeadFieldOrder')||'null')}catch(e){return null}})();
const state={page:'lead',sourcePage:'leadManage',view:'list',density:'default',detailId:null,tab:'detail',selectedId:1,selected:new Set(),query:'',channel:'全部渠道',brand:'全部品牌',product:'全部产品',urgent:'加急(全部)',status:'全部状态',splitCollapsed:false,historyEmpty:false,leadMenuOpen:false,leadMenuTab:'common',leadPinned:new Set(['mine','pool']),localFollowOn:new Set(),localFollowOff:new Set(),hiddenFollow:new Set(),visible:new Set(Array.isArray(storedFields)&&storedFields.length?storedFields:defaultVisible),order:Array.isArray(storedOrder)&&storedOrder.length?storedOrder:defaultVisible.slice(),fieldDraft:new Set(),fieldSearch:'',dragField:'',feedbackDrafts:{},importState:'initial',importMode:'仅新增数据',settingTab:'base',settingEditMode:'create',settingDirty:false,settingMarkets:['广州市','中山市','深圳市','东莞市','惠州市','珠海市','佛山市-禅城区'],settingMembers:['胡思轶','安心','毛毛冲','彩虹','王东旭'],pickerType:'',pickerDraft:new Set(),assignIds:[],assignSales:new Set()};
const poolRows=[
{name:'浙江区域公海池',team:'冠军队',dept:'浙江5部',province:'浙江省',markets:'杭州市、宁波市',leader:'李清',members:'胡思轶、安心、毛毛冲、彩虹、王东旭',rule:'成员可见可领取、管理员可分配',recycle:'7天',expire:'360天',limit:'200'},
{name:'山西通用公海池',team:'冠军队',dept:'山西1部',province:'山西省',markets:'太原市、晋中市、阳泉市',leader:'王明',members:'胡思轶、安心、毛毛冲、彩虹、王东旭',rule:'成员可见可领取、管理员可分配',recycle:'3天',expire:'180天',limit:'100'},
{name:'山西2部(山西企业常规)',team:'冠军队',dept:'山西2部',province:'山西省',markets:'太原市、忻州市、吕梁市',leader:'刘思思',members:'胡思轶、安心、毛毛冲、彩虹、王东旭',rule:'管理员可分配',recycle:'3天',expire:'180天',limit:'100'},
{name:'山西华北1号公海池',team:'冠军队',dept:'山西华北1部',province:'山西省',markets:'大同市、朔州市、晋城市',leader:'洪波',members:'胡思轶、安心、毛毛冲、彩虹、王东旭',rule:'成员可见可领取、管理员可分配',recycle:'3天',expire:'180天',limit:'100'},
{name:'山西华北12号公海池',team:'冠军队',dept:'山西华北2部',province:'山西省',markets:'长治市、长治',leader:'许静静',members:'胡思轶、安心、毛毛冲、彩虹、王东旭',rule:'成员可见可领取、管理员可分配',recycle:'3天',expire:'180天',limit:'100'},
{name:'江苏全域公海池1',team:'特战队',dept:'江苏队',province:'江苏省',markets:'盐城市',leader:'王安然',members:'胡思轶、安心、毛毛冲、彩虹、王东旭',rule:'成员可见可领取、管理员可分配',recycle:'7天',expire:'180天',limit:'100'},
{name:'江苏全域公海池2',team:'特战队',dept:'江苏队',province:'江苏省',markets:'淮安市、连云港',leader:'胡一帆',members:'胡思轶、安心、毛毛冲、彩虹、王东旭',rule:'成员可见可领取、管理员可分配',recycle:'15天',expire:'90天',limit:'200'},
{name:'江苏全域公海池3',team:'特战队',dept:'江苏队',province:'江苏省',markets:'泰州市、泰州',leader:'吴天',members:'胡思轶、安心、毛毛冲、彩虹、王东旭',rule:'成员可见可领取、管理员可分配',recycle:'15天',expire:'60天',limit:'200'}
];
function esc(v){return String(v??'').replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]))}
function root(page=state.page){return document.getElementById(pages[page])}
function handles(page){return !!pages[page]}
function title(page=state.page){return page==='leadRecent'?'最近访问':page==='leadClaim'?'我的线索':page==='leadFollow'?'我的关注':page==='leadManage'?'线索管理':page==='leadSetting'?'线索管理设置':'线索公海'}
function mode(page=state.page){return page==='leadRecent'?'recent':page==='leadClaim'?'mine':page==='leadFollow'?'follow':page==='leadManage'?'manage':'pool'}
function isFollowed(id,m=mode()){if(state.localFollowOff.has(id))return false;if(state.localFollowOn.has(id))return true;return m==='follow'||m==='mine'}
function leadSearchPin(){return '<svg class="classic-pushpin" viewBox="0 0 24 24" aria-hidden="true" style="width:20px;height:20px;overflow:visible;fill:none;stroke:currentColor;stroke-width:1.9;stroke-linecap:round;stroke-linejoin:round"><g transform="rotate(42 12 12)"><path class="pin-body" fill="#fff" d="M8 3h8l-1 7 3 3H6l3-3-1-7Z"/><path class="pin-needle" d="M12 13v8" fill="none" stroke-width="2"/></g></svg>'}
function leadTitleMenu(currentTitle){const common=[['recent','最近访问','leadRecent'],['mine','我的线索','leadClaim'],['follow','我的关注','leadFollow'],['manage','线索管理','leadManage'],['pool','线索公海','lead'],['setting','线索管理设置','leadSetting']],custom=[['high','高意向线索','leadManage'],['recent7','近7天新增线索','leadManage']],source=state.leadMenuTab==='common'?common:custom,items=source.map(x=>'<div class="crm-lead-view-row"><button class="crm-lead-view-link" onclick="leadV2.selectLeadPage(\''+x[2]+'\',\''+x[1]+'\')">'+x[1]+'</button><button class="crm-lead-pin '+(state.leadPinned.has(x[0])?'active':'')+'" onclick="leadV2.toggleLeadPin(\''+x[0]+'\',event)" title="'+(state.leadPinned.has(x[0])?'取消固定':'固定')+'">'+leadSearchPin()+'</button></div>').join('');return '<div class="crm-lead-title-wrap"><button class="crm-lead-title-btn '+(state.leadMenuOpen?'open':'')+'" onclick="leadV2.toggleLeadMenu(event)"><span>'+esc(currentTitle)+'</span><span class="chev">⌄</span></button><div class="crm-lead-view-menu '+(state.leadMenuOpen?'show':'')+'"><aside><button class="'+(state.leadMenuTab==='common'?'active':'')+'" onclick="leadV2.setLeadMenuTab(\'common\',event)">常用检索</button><button class="'+(state.leadMenuTab==='custom'?'active':'')+'" onclick="leadV2.setLeadMenuTab(\'custom\',event)">自定义检索</button></aside><section>'+items+'</section></div></div>'}
function toggleLeadMenu(e){e?.stopPropagation();state.leadMenuOpen=!state.leadMenuOpen;render()}
function setLeadMenuTab(v,e){e?.stopPropagation();state.leadMenuTab=v;render()}
function toggleLeadPin(k,e){e?.stopPropagation();state.leadPinned.has(k)?state.leadPinned.delete(k):state.leadPinned.add(k);render()}
function selectLeadPage(page,label){state.leadMenuOpen=false;setActivePage(page,'线索 / '+label)}
function icon(kind){return sharedViewModeIcon(kind)}
function statusFor(r,m=mode()){return m==='mine'?r.mineStatus:m==='follow'?r.followStatus:(m==='manage'||m==='recent')?r.manageStatus:r.poolStatus}
function ownerFor(r,m=mode()){if(m==='mine')return r.owner==='-'?currentUser:r.owner;return r.owner}
function statusHtml(v){let c='lead2-blue';if(v==='过期失效')c='lead2-danger';else if(v==='已转商机')c='lead2-blue';else if(v==='已领取'||v==='已联系')c='lead2-orange';else if(v==='跟进中'||v==='有效')c='lead2-green';return '<span class="lead2-status '+c+'">'+esc(v)+'</span>'}
function feedbackHtml(v){let c=v==='有效'?'lead2-green':v==='无效'?'lead2-orange':'';return '<span class="'+c+'">'+esc(v)+'</span>'}
function filtered(m=mode()){
let out=rows.filter(r=>{
const q=state.query.trim().toLowerCase();
return (!q||[r.name,r.phone,r.province,r.city,r.channel,r.brand,r.product].some(v=>String(v).toLowerCase().includes(q)))&&(state.channel==='全部渠道'||r.channel===state.channel)&&(state.brand==='全部品牌'||r.brand===state.brand)&&(state.product==='全部产品'||r.product===state.product)&&(state.urgent==='加急(全部)'||(state.urgent==='仅看加急'?r.urgent==='是':r.urgent!=='是'))&&(state.status==='全部状态'||statusFor(r,m)===state.status);
});
if(m==='follow')out=out.filter(r=>!state.hiddenFollow.has(r.id));
if(m==='recent')out=out.slice().sort((a,b)=>String(b.lastFeedback).localeCompare(String(a.lastFeedback))).slice(0,5);
return out;
}
function viewSwitch(){return '<div class="lead2-view-switch"><button class="'+(state.view==='split'?'active':'')+'" onclick="leadV2.setView(\'split\')" title="分屏视图">'+icon('split')+'</button><button class="'+(state.view==='list'?'active':'')+'" onclick="leadV2.setView(\'list\')" title="列表视图">'+icon('list')+'</button></div>'}
function stats(page){
if(page==='leadRecent')return [['最近访问','5'],['今日访问','2'],['本周访问','5']];
if(page==='leadManage')return [['线索总量','232'],['已领取','158'],['已转商机','88'],['今日新增','16'],['未分发','18']];
if(page==='leadClaim'||page==='leadFollow')return [['线索总量',page==='leadClaim'?'56':'56'],['有效',page==='leadClaim'?'40':'10'],['未反馈','10'],['无效','6'],['已转商机','6']];
return [['线索总量','232'],['待领取','158'],['今日新增','16']];
}
function statsHtml(page){return '<div class="lead2-stats">'+stats(page).map(x=>'<button><span>'+x[0]+'</span><b>'+x[1]+'</b></button>').join('')+'</div>'}
function filterHtml(){const options=(arr,val)=>arr.map(x=>'<option '+(x===val?'selected':'')+'>'+x+'</option>').join('');return '<select onchange="leadV2.setFilter(\'channel\',this.value)">'+options(['全部渠道','展会','知乎','京东','1688','小红书','抖音号','百度教育','凤凰网'],state.channel)+'</select><select onchange="leadV2.setFilter(\'brand\',this.value)">'+options(['全部品牌','itc','世畅','北航星','特空','CRX','丽声','3A'],state.brand)+'</select><select onchange="leadV2.setFilter(\'product\',this.value)">'+options(['全部产品','LED显示屏','会议系统','录播系统','教学系统','广播系统','无纸化会议系统','指挥中心','舞台灯光'],state.product)+'</select><select onchange="leadV2.setFilter(\'urgent\',this.value)">'+options(['加急(全部)','仅看加急','仅看非加急'],state.urgent)+'</select><select onchange="leadV2.setFilter(\'status\',this.value)">'+options(['全部状态','待领取','已领取','未分发','跟进中','已联系','未处理','已转商机','过期失效'],state.status)+'</select><input id="lead2Query" value="'+esc(state.query)+'" placeholder="线索名称 / 号码 / 地区" onkeydown="if(event.key===\'Enter\')leadV2.query(this.value)"><button class="btn primary" onclick="leadV2.query(document.getElementById(\'lead2Query\').value)">查询</button><button class="btn" onclick="leadV2.reset()">重置</button>'}
function actionsHtml(page){if(page==='leadRecent')return '<button class="btn" onclick="leadV2.batchFollow()">批量关注</button>';if(page==='leadClaim')return '<button class="btn primary" onclick="leadV2.openCreate()">+ 新增线索</button><button class="btn" onclick="leadV2.openImport()">导入线索</button><button class="btn" onclick="leadV2.exportLeads()">导出线索</button><button class="btn" onclick="leadV2.batchRelease()">批量释放</button>';if(page==='leadFollow')return '<button class="btn" onclick="leadV2.batchClaim()">批量领取</button><button class="btn" onclick="leadV2.batchUnfollow()">批量取关</button>';if(page==='leadManage')return '<button class="btn primary" onclick="leadV2.openCreate()">+ 新增线索</button><button class="btn" onclick="leadV2.openImport()">导入线索</button><button class="btn" onclick="leadV2.exportLeads()">导出线索</button><button class="btn" onclick="leadV2.openAssign()">批量分配</button><button class="btn" onclick="leadV2.mergeSelected()">线索合并</button><button class="btn" onclick="leadV2.deleteSelected()">批量删除</button>';return '<button class="btn" onclick="leadV2.batchClaim()">批量领取</button><button class="btn" onclick="leadV2.batchFollow()">批量关注</button>'}
function listTools(){if(state.view!=='list')return '';return '<div class="lead2-tools core-list-tools"><button class="opp-tool-icon" title="列表密度" onclick="leadV2.listTool(\'density\',event)">'+sharedListToolIcon('density')+'</button><button id="lead2RefreshBtn" class="opp-tool-icon" title="刷新数据" onclick="leadV2.listTool(\'refresh\',event)">'+sharedListToolIcon('refresh')+'</button><button class="opp-tool-icon" title="自定义显示字段" onclick="leadV2.listTool(\'fields\',event)">'+sharedListToolIcon('fields')+'</button></div>'}
function listTool(type,e){e?.stopPropagation();if(type==='density'){const order=['default','compact','relaxed'],label={default:'标准',compact:'紧凑',relaxed:'宽松'},i=(order.indexOf(state.density)+1)%order.length;state.density=order[i];render();notify('列表密度已切换为'+label[state.density]+'(Demo)');return}if(type==='refresh'){const b=document.getElementById('lead2RefreshBtn');b?.classList.add('refreshing');setTimeout(()=>{b?.classList.remove('refreshing');notify('线索列表已刷新(Demo)')},520);return}openFields()}
function tableFields(page){const allowed=pageFields[page]||pageFields.lead;return state.order.filter(k=>allowed.includes(k)&&state.visible.has(k));}
function cell(r,k,index,m){const s=statusFor(r,m);if(k==='no')return index+1;if(k==='name')return '<button class="lead2-link" onclick="leadV2.openDetail('+r.id+')"><b>'+esc(r.name)+'</b></button>';if(k==='urgent')return r.urgent==='是'?'<b class="lead2-danger">是</b>':'否';if(k==='feedback')return feedbackHtml(r.feedback);if(k==='status')return statusHtml(s);if(k==='owner')return esc(ownerFor(r,m)||'-');if(k==='action')return '<div class="lead2-row-actions">'+rowActions(r,m)+'</div>';return esc(r[k]??'-')}
function rowActions(r,m){const s=statusFor(r,m),followed=isFollowed(r.id,m),a=['<button class="lead2-link" onclick="leadV2.openDetail('+r.id+')">详情</button>'];if(m==='pool'){if(s==='待领取')a.push('<button class="lead2-link" onclick="leadV2.claim('+r.id+')">领取</button>');a.push('<button class="lead2-link" onclick="leadV2.'+(followed?'unfollow':'follow')+'('+r.id+')">'+(followed?'取消关注':'关注')+'</button>')}else if(m==='follow'){if(s==='待领取')a.push('<button class="lead2-link" onclick="leadV2.claim('+r.id+')">领取</button>');a.push('<button class="lead2-link" onclick="leadV2.'+(followed?'unfollow':'follow')+'('+r.id+')">'+(followed?'取消关注':'关注')+'</button>')}else if(m==='recent'){a.push('<button class="lead2-link" onclick="leadV2.'+(followed?'unfollow':'follow')+'('+r.id+')">'+(followed?'取消关注':'关注')+'</button>')}else if(m==='mine'){if(!['过期失效','已转商机'].includes(s))a.push('<button class="lead2-link" onclick="leadV2.openFeedback('+r.id+')">反馈</button>');if((r.feedback==='有效'||['跟进中','已联系'].includes(s))&&!['已转商机','过期失效'].includes(s))a.push('<button class="lead2-link" onclick="leadV2.openConvert('+r.id+')">转商机</button>');if(!['已转商机','过期失效'].includes(s))a.push('<button class="lead2-link" onclick="leadV2.release('+r.id+')">释放</button>');a.push('<button class="lead2-link" onclick="leadV2.'+(followed?'unfollow':'follow')+'('+r.id+')">'+(followed?'取消关注':'关注')+'</button>');if(s==='过期失效')a.push('<button class="lead2-link lead2-danger" onclick="leadV2.deleteMine('+r.id+')">删除</button>')}else{if(['待领取','未分发'].includes(s))a.push('<button class="lead2-link" onclick="leadV2.openAssign('+r.id+')">分配</button>');a.push('<button class="lead2-link lead2-danger" onclick="leadV2.deleteManage('+r.id+')">删除</button>')}return a.join('')}
function table(page){const m=mode(page),list=filtered(m),fields=tableFields(page),all=list.length&&list.every(r=>state.selected.has(r.id)),partial=!all&&list.some(r=>state.selected.has(r.id));return '<table class="lead2-table lead2-density-'+state.density+'"><thead><tr><th class="fixed-select"><input type="checkbox" '+(all?'checked':'')+' '+(partial?'data-partial="1"':'')+' onclick="leadV2.toggleAll(this.checked)"></th>'+fields.map(k=>{const d=fieldDefs.find(x=>x[0]===k);return '<th draggable="true" ondragstart="leadV2.dragStart(\''+k+'\')" ondragover="event.preventDefault()" ondrop="leadV2.dropField(\''+k+'\')" class="'+(k==='no'?'fixed-no':k==='name'?'fixed-name':k==='action'?'fixed-action':'')+'">'+d[1]+'</th>'}).join('')+'</tr></thead><tbody>'+list.map((r,i)=>'<tr><td class="fixed-select"><input type="checkbox" '+(state.selected.has(r.id)?'checked':'')+' onclick="event.stopPropagation();leadV2.toggleSelected('+r.id+',this.checked)"></td>'+fields.map(k=>'<td class="'+(k==='no'?'fixed-no':k==='name'?'fixed-name':k==='action'?'fixed-action':k==='consult'||k==='content'?'long':'')+'">'+cell(r,k,i,m)+'</td>').join('')+'</tr>').join('')+'</tbody></table>'}
function pagination(total){return '<div class="opp-pagination"><span>共 '+total+' 条</span><select><option>10条/页</option><option>20条/页</option><option>50条/页</option></select><div class="opp-page-buttons"><button disabled>‹</button><button class="active">1</button><button>2</button><button>3</button><button>›</button></div><div class="opp-jump"><span>跳至</span><input value="1" aria-label="跳转页码"><span>页</span></div></div>'}
function split(page){const m=mode(page),list=filtered(m);if(!list.length)return '<div class="crm-empty">暂无符合条件的线索</div>';if(!list.some(x=>x.id===state.selectedId))state.selectedId=list[0].id;const r=list.find(x=>x.id===state.selectedId)||list[0],all=list.length>0&&list.every(x=>state.selected.has(x.id)),partial=!all&&list.some(x=>state.selected.has(x.id));return '<div class="lead2-split crm-unified-split '+(state.splitCollapsed?'collapsed':'')+'"><div class="lead2-split-left crm-unified-split-left"><div class="crm-unified-split-list"><div class="lead2-split-head"><label class="lead2-check"><input type="checkbox" '+(all?'checked':'')+' '+(partial?'data-partial="1"':'')+' onclick="event.stopPropagation();leadV2.toggleAll(this.checked)"></label><span>线索列表</span></div>'+list.map(x=>'<div class="lead2-split-row '+(x.id===r.id?'active':'')+'" onclick="leadV2.selectSplit('+x.id+')"><label class="lead2-check" onclick="event.stopPropagation()"><input type="checkbox" '+(state.selected.has(x.id)?'checked':'')+' onchange="leadV2.toggleSelected('+x.id+',this.checked)"></label><b>'+esc(x.name)+'</b><span class="lead2-split-status">'+statusHtml(statusFor(x,m))+'</span><small>'+esc(x.province)+' / '+esc(x.city)+' / '+esc(x.product)+(x.owner&&x.owner!=='-'?' / '+esc(x.owner):'')+'</small></div>').join('')+'</div></div><div class="lead2-split-divider crm-unified-split-divider"><button class="lead2-split-toggle crm-unified-split-toggle" onclick="leadV2.toggleSplit()" aria-label="'+(state.splitCollapsed?'展开左侧列表':'收起左侧列表')+'">'+(state.splitCollapsed?'›':'‹')+'</button></div><div class="lead2-split-detail crm-unified-split-detail">'+detailMarkup(r,true)+'</div></div>'}
function selectionSummary(list){const count=list.filter(r=>state.selected.has(r.id)).length;return count?'<div class="lead2-selection-summary" style="height:40px;display:flex;align-items:center;padding:0 12px;border:1px solid #cfe0f5;border-bottom:0;background:#f2f8ff;color:#334155">已选择 <b style="color:#1677ff;margin:0 4px">'+count+'</b> 条线索</div>':''}
function renderList(page){const list=filtered(mode(page)),tools=listTools();const target=root(page);target.innerHTML='<div class="lead2-page"><div class="lead2-head"><div class="lead2-head-left">'+viewSwitch()+leadTitleMenu(title(page))+'</div></div><div class="lead2-controls">'+statsHtml(page)+'<div class="lead2-toolbar"><div class="lead2-filters">'+filterHtml()+'</div><div class="lead2-actions">'+actionsHtml(page)+'</div>'+tools+'</div></div>'+selectionSummary(list)+'<div class="lead2-main"><div class="lead2-scroll">'+(state.view==='split'?split(page):table(page))+'</div>'+pagination(list.length)+'</div></div>';target.querySelectorAll('input[data-partial="1"]').forEach(x=>x.indeterminate=true)}
function detailActions(r,m,embedded){const s=statusFor(r,m),followed=isFollowed(r.id,m),a=[];if(!embedded)a.push('<button class="btn" onclick="leadV2.back()">返回列表</button>');if(m==='pool'){if(s==='待领取')a.push('<button class="btn primary" onclick="leadV2.claim('+r.id+')">领取</button>');a.push('<button class="btn" onclick="leadV2.'+(followed?'unfollow':'follow')+'('+r.id+',true)">'+(followed?'取消关注':'关注')+'</button>')}else if(m==='recent'){a.push('<button class="btn" onclick="leadV2.'+(followed?'unfollow':'follow')+'('+r.id+',true)">'+(followed?'取消关注':'关注')+'</button>')}else if(m==='mine'){if(!['过期失效','已转商机'].includes(s))a.push('<button class="btn" onclick="leadV2.editLead('+r.id+')">编辑</button><button class="btn primary" onclick="leadV2.openFeedback('+r.id+')">反馈</button><button class="btn" onclick="leadV2.release('+r.id+')">释放</button>');a.push('<button class="btn" onclick="leadV2.'+(followed?'unfollow':'follow')+'('+r.id+',true)">'+(followed?'取消关注':'关注')+'</button>')}else if(m==='follow'){if(s==='待领取')a.push('<button class="btn primary" onclick="leadV2.claim('+r.id+')">领取</button>');a.push('<button class="btn" onclick="leadV2.'+(followed?'unfollow':'follow')+'('+r.id+',true)">'+(followed?'取消关注':'关注')+'</button>')}else{if(['待领取','未分发'].includes(s))a.push('<button class="btn primary" onclick="leadV2.openAssign('+r.id+')">分配</button>');a.push('<button class="btn lead2-danger" onclick="leadV2.deleteManage('+r.id+')">删除</button>')}return a.join('')}
function detailInfo(r,m){return '<h3 class="lead2-plain-title">线索详情</h3><dl class="lead2-info"><dt>客户名称</dt><dd>'+esc(r.name)+'</dd><dt>电话</dt><dd>'+esc(r.phone)+'</dd><dt>联系方式</dt><dd>微信号:XV123456</dd><dt>客户相关文件</dt><dd><button class="lead2-link" onclick="leadV2.notify(\'正在预览附件(Demo)\')">项目需求文件 ◉</button> <button class="lead2-link" onclick="leadV2.notify(\'附件已下载(Demo)\')">⇩</button></dd><dt>省份</dt><dd>'+esc(r.province)+'</dd><dt>城市</dt><dd>'+esc(r.city)+'</dd><dt>详细地址</dt><dd>'+esc(r.city)+'番禺区企业大道2街</dd><dt>渠道</dt><dd>'+esc(r.channel)+'</dd><dt>品牌</dt><dd>'+esc(r.brand)+'</dd><dt>需求产品</dt><dd>'+esc(r.product)+'</dd><dt>需求场景</dt><dd>'+esc(r.scene)+'</dd><dt>咨询内容</dt><dd>'+esc(r.consult)+'</dd><dt>线索状态</dt><dd>'+statusHtml(statusFor(r,m))+'</dd><dt>公海池</dt><dd>广东广州区域公海池</dd><dt>销售团队</dt><dd>'+esc(r.salesTeam)+'</dd><dt>销售部门</dt><dd>'+esc(r.salesDept)+'</dd><dt>销售人员</dt><dd>'+esc(ownerFor(r,m)||'-')+'</dd><dt>新增人员</dt><dd>吴忧</dd><dt>新增时间</dt><dd>'+esc(r.created)+'</dd><dt>是否加急</dt><dd class="'+(r.urgent==='是'?'lead2-danger':'')+'">'+esc(r.urgent)+'</dd><dt>是否领取</dt><dd>'+(['待领取','未分发'].includes(statusFor(r,m))?'否':'已领取')+'</dd><dt>关注</dt><dd>'+esc(r.attention)+'</dd><dt>反馈情况</dt><dd>'+feedbackHtml(r.feedback)+'</dd><dt>反馈内容</dt><dd>'+esc(r.content)+'</dd><dt>反馈人</dt><dd>吴忠</dd><dt>反馈时间</dt><dd>'+esc(r.lastFeedback)+'</dd></dl>'}
function historyHtml(r){
const toolbar='<div class="lead2-history-toolbar"><button class="btn" onclick="leadV2.toggleHistoryDemo()">'+(state.historyEmpty?'切换至有记录样式':'切换至暂无记录样式')+'</button></div>';
if(state.historyEmpty)return '<div class="lead2-history-stage">'+toolbar+'<div class="lead2-history-empty"><svg viewBox="0 0 120 92" aria-hidden="true"><rect x="20" y="14" width="80" height="62" rx="6"/><path d="M34 31h52M34 44h38M34 57h45"/><circle cx="91" cy="69" r="13"/><path d="m100 78 9 9"/></svg><strong>暂无记录</strong></div></div>';
const records=[
{time:'2026-06-28 09:30',user:'小李',dept:'销售一部',type:'释放',kind:'release',content:'将线索释放至线索公海。'},
{time:'2026-06-27 09:30',user:'小张',dept:'销售一部',type:'反馈',kind:'feedback',content:'填写了反馈内容:无效。原因可能为客户电话号码为空号、无法接通、重复线索等。'},
{time:'2026-06-27 09:30',user:'小张',dept:'销售一部',type:'领取',kind:'claim',content:'领取了该线索。'}
];
const items=records.map(x=>'<div class="lead2-history-item"><div class="lead2-history-meta"><span class="date">'+esc(x.time)+'</span><span class="person">'+esc(x.user)+'</span><span class="dept">'+esc(x.dept)+'</span><em class="lead2-history-tag '+esc(x.kind)+'">'+esc(x.type)+'</em></div><div class="lead2-history-content">'+esc(x.content)+'</div></div>').join('');
return '<div class="lead2-history-stage">'+toolbar+'<div class="lead2-history">'+items+'</div></div>'
}
function logHtml(r){const dynamic=(r.logs||[]).map(x=>'<tr><td>'+esc(x.time)+'</td><td>'+esc(x.user)+'</td><td>'+esc(x.type)+'</td><td>'+esc(x.content)+'</td><td>线索模块</td></tr>').join('');return '<table class="lead2-log"><thead><tr><th>操作时间</th><th>操作人</th><th>操作类型</th><th>操作内容</th><th>操作来源</th></tr></thead><tbody>'+dynamic+'<tr><td>2026-06-28 09:30</td><td>小李</td><td>关注</td><td>关注状态由未关注变更为已关注</td><td>线索详情</td></tr><tr><td>2026-06-27 09:30</td><td>小张</td><td>领取</td><td>线索状态由待领取变更为已领取</td><td>线索公海</td></tr></tbody></table>'}
function detailMarkup(r,embedded){const m=mode(),tabs=['detail','history','log'];let body=state.tab==='history'?historyHtml(r):state.tab==='log'?logHtml(r):detailInfo(r,m);return '<div class="lead2-detail"><div class="lead2-detail-top"><h1>'+esc(r.name)+'</h1>'+(!embedded?'<div>'+detailActions(r,m,false).split('</button>').shift()+'</button></div>':'')+'</div><div class="lead2-detail-actions">'+detailActions(r,m,true)+'</div><div class="lead2-detail-tabs">'+tabs.map(x=>'<button class="'+(state.tab===x?'active':'')+'" onclick="leadV2.switchDetailTab(\''+x+'\')">'+(x==='detail'?'线索详情':x==='history'?'历史记录':'操作日志')+'</button>').join('')+'</div><div class="lead2-detail-body">'+body+'</div></div>'}
function renderDetail(){const r=rows.find(x=>x.id===state.detailId)||rows[0];root().innerHTML=detailMarkup(r,false)}
function render(){if(['lead','leadClaim','leadFollow','leadManage','leadRecent'].includes(state.page)){state.detailId?renderDetail():renderList(state.page)}else if(state.page==='leadImport')renderImport();else if(state.page==='leadCreate')renderCreate();else if(state.page==='leadSetting')renderSetting();else if(state.page==='leadSettingEdit')renderSettingEdit();else if(state.page==='leadSettingImport')renderSettingImport()}
function enter(page,label){state.page=page;if(['lead','leadClaim','leadFollow','leadManage','leadRecent'].includes(page)){state.detailId=null;state.view='list'}render()}
function setView(v){state.view=v;state.leadMenuOpen=false;render()}
function setFilter(k,v){state[k]=v;render()}
function query(v){state.query=v||'';render()}
function reset(){state.query='';state.channel='全部渠道';state.brand='全部品牌';state.product='全部产品';state.urgent='加急(全部)';state.status='全部状态';state.selected.clear();render()}
function toggleSelected(id,on){on?state.selected.add(id):state.selected.delete(id);render()}
function toggleAll(on){filtered().forEach(r=>on?state.selected.add(r.id):state.selected.delete(r.id));render()}
function selectSplit(id){state.selectedId=id;render()}
function toggleSplit(){state.splitCollapsed=!state.splitCollapsed;render()}
function openDetail(id){state.detailId=id;state.tab='detail';render();renderBreadcrumb()}
function back(){state.detailId=null;render();renderBreadcrumb()}
function switchDetailTab(v){state.tab=v;render()}
function toggleHistoryDemo(){state.historyEmpty=!state.historyEmpty;render()}
function dragStart(k){state.dragField=k}
function dropField(k){if(!state.dragField||state.dragField===k)return;const a=state.order.indexOf(state.dragField),b=state.order.indexOf(k);if(a<0||b<0)return;state.order.splice(a,1);state.order.splice(b,0,state.dragField);localStorage.setItem('crmLeadFieldOrder',JSON.stringify(state.order));state.dragField='';render()}
function claim(id){openDemoConfirm('领取线索','确认执行领取操作?','领取操作已确认(Demo),未修改线索归属或状态')}
function follow(id){state.hiddenFollow.delete(id);state.localFollowOff.delete(id);state.localFollowOn.add(id);notify('关注成功');render()}
function unfollow(id,fromDetail){const mask=document.getElementById('modal'),panel=mask.querySelector('.modal');panel.classList.remove('opp-modal-wide');document.getElementById('modalTitle').textContent='取消关注';mask.querySelector('.m-body').innerHTML='<p>确定取消关注该线索吗?</p>';mask.querySelector('.m-foot').innerHTML='<button class="btn" onclick="closeModal()">取消</button> <button class="btn primary" onclick="leadV2.confirmUnfollow('+id+')">确认</button>';mask.classList.add('show')}
function release(id){openDemoConfirm('释放线索','确认执行释放操作?','释放操作已确认(Demo),未修改线索归属或状态')}
function confirmUnfollow(id){state.hiddenFollow.add(id);state.localFollowOn.delete(id);state.localFollowOff.add(id);closeModal();notify('已取消关注');render()}
function openDemoConfirm(titleText,message,successText){const mask=document.getElementById('modal'),panel=mask.querySelector('.modal');panel.classList.remove('opp-modal-wide');document.getElementById('modalTitle').textContent=titleText;mask.querySelector('.m-body').innerHTML='<p>'+message+'</p><div class="lead2-note">该操作仅用于 Demo 流程展示,不会修改真实数据。</div>';mask.querySelector('.m-foot').innerHTML='<button class="btn" onclick="closeModal()">取消</button> <button class="btn primary" onclick="leadV2.completeDemoAction(\''+successText.replace(/'/g,"&#39;")+'\')">确认</button>';mask.classList.add('show')}
function completeDemoAction(message){closeModal();notify(message)}
function deleteMine(id){openDemoConfirm('删除失效线索','确认执行删除操作?','删除操作已确认(Demo),线索未移除,也未回流公海池')}
function deleteManage(id){openDemoConfirm('删除线索','确认执行删除操作?','删除操作已确认(Demo),线索数据未发生变化')}
function batchClaim(){const list=filtered().filter(r=>state.selected.has(r.id));if(!list.length){notify('请先勾选线索');return}openDemoConfirm('批量领取','确认对选中的 '+list.length+' 条线索执行批量领取?','批量领取操作已确认(Demo),未修改线索归属或状态')}
function batchFollow(){const list=filtered().filter(r=>state.selected.has(r.id));if(!list.length){notify('请先勾选线索');return}list.forEach(r=>{state.hiddenFollow.delete(r.id);state.localFollowOff.delete(r.id);state.localFollowOn.add(r.id)});state.selected.clear();notify('已批量关注 '+list.length+' 条线索');render()}
function batchUnfollow(){const list=filtered().filter(r=>state.selected.has(r.id));if(!list.length){notify('请先勾选线索');return}list.forEach(r=>{state.hiddenFollow.add(r.id);state.localFollowOn.delete(r.id);state.localFollowOff.add(r.id)});state.selected.clear();notify('已批量取消关注 '+list.length+' 条线索');render()}
function batchRelease(){const list=filtered('mine').filter(r=>state.selected.has(r.id));if(!list.length){notify('请先勾选线索');return}openDemoConfirm('批量释放','确认对选中的 '+list.length+' 条线索执行批量释放?','批量释放操作已确认(Demo),未修改线索归属或状态')}
function mergeSelected(){const list=filtered('manage').filter(r=>state.selected.has(r.id));if(list.length<2){notify('请至少勾选两条重复线索');return}openDemoConfirm('线索合并','确认对选中的 '+list.length+' 条线索执行合并?','合并操作已确认(Demo),未归档或修改任何线索')}
function deleteSelected(){const list=filtered('manage').filter(r=>state.selected.has(r.id));if(!list.length){notify('请先勾选线索');return}openDemoConfirm('批量删除','确认删除选中的 '+list.length+' 条线索?','批量删除操作已确认(Demo),线索数据未发生变化')}
function openFields(){state.fieldDraft=new Set(state.visible);state.fieldSearch='';renderFieldModal()}
function renderFieldModal(){const mask=document.getElementById('modal'),panel=mask.querySelector('.modal');panel.classList.add('opp-modal-wide');document.getElementById('modalTitle').textContent='列表设置';const list=fieldDefs.filter(x=>x[1].includes(state.fieldSearch));mask.querySelector('.m-body').innerHTML='<div class="lead2-field-top"><input type="text" value="'+esc(state.fieldSearch)+'" placeholder="输入关键字搜索" oninput="leadV2.searchFields(this.value)"><label><input type="checkbox" '+(state.fieldDraft.size===fieldDefs.length?'checked':'')+' onchange="leadV2.toggleAllFields(this.checked)"> 全选('+state.fieldDraft.size+'/'+fieldDefs.length+')</label></div><div class="lead2-field-grid">'+list.map(x=>'<label><input type="checkbox" '+(state.fieldDraft.has(x[0])?'checked':'')+' onchange="leadV2.toggleField(\''+x[0]+'\',this.checked)"> '+x[1]+'</label>').join('')+'</div><div class="lead2-field-note">注:勾选的字段保存后在表单显示</div>';mask.querySelector('.m-foot').innerHTML='<button class="btn" onclick="closeModal()">取消</button> <button class="btn primary" onclick="leadV2.saveFields()">保存</button>';mask.classList.add('show')}
function searchFields(v){state.fieldSearch=v;renderFieldModal()}
function toggleField(k,on){on?state.fieldDraft.add(k):state.fieldDraft.delete(k);renderFieldModal()}
function toggleAllFields(on){state.fieldDraft=new Set(on?defaultVisible:[]);renderFieldModal()}
function saveFields(){if(!state.fieldDraft.size){notify('至少保留 1 个字段');return}state.visible=new Set(state.fieldDraft);localStorage.setItem('crmLeadVisibleFields',JSON.stringify([...state.visible]));closeModal();render();notify('列表字段配置已同步至线索公海、我的线索、我的关注和线索管理')}
function openFeedback(id){const r=rows.find(x=>x.id===id);if(!r||['过期失效','已转商机'].includes(r.mineStatus))return;const d=state.feedbackDrafts[id]||{content:'',status:'',product:r.product,files:[],dirty:false};state.feedbackDrafts[id]=d;const mask=document.getElementById('modal'),panel=mask.querySelector('.modal');panel.classList.add('opp-modal-wide');document.getElementById('modalTitle').textContent='反馈';mask.querySelector('.m-body').innerHTML='<div class="lead2-modal-grid"><label>客户名称</label><div class="lead2-readonly">'+esc(r.name)+'</div><label>联系号码</label><div class="lead2-readonly">'+esc(r.phone)+'</div><label>反馈人</label><div class="lead2-readonly">'+currentUser+'</div><label>反馈时间</label><div class="lead2-readonly">提交时自动生成</div><label>销售团队</label><div class="lead2-readonly">'+esc(r.salesTeam)+'</div><label>销售部门</label><div class="lead2-readonly">'+esc(r.salesDept)+'</div><label>领取销售人员</label><div class="lead2-readonly">'+esc(ownerFor(r,'mine'))+'</div><span></span><span></span><label class="lead2-required">反馈内容</label><textarea class="full" placeholder="输入内容" oninput="leadV2.updateFeedback('+id+',\'content\',this.value)">'+esc(d.content)+'</textarea><label class="lead2-required">反馈情况</label><select onchange="leadV2.updateFeedback('+id+',\'status\',this.value)"><option value="">请选择</option>'+['有效','无效','未反馈'].map(x=>'<option '+(d.status===x?'selected':'')+'>'+x+'</option>').join('')+'</select><label class="lead2-required">需求产品</label><select onchange="leadV2.updateFeedback('+id+',\'product\',this.value)">'+['LED显示屏','会议系统','录播系统','教学系统','广播系统','无纸化会议系统','指挥中心','舞台灯光'].map(x=>'<option '+(d.product===x?'selected':'')+'>'+x+'</option>').join('')+'</select><label>添加文件</label><div class="lead2-upload-small" onclick="leadV2.feedbackFile('+id+')">⇧ 添加文件(Demo,不触发本地文件选择)</div><span></span><div class="lead2-file-list">'+d.files.map((f,i)=>'<div class="lead2-file"><span>'+esc(f)+'</span><button class="lead2-link" onclick="leadV2.notify(\'在线预览:'+esc(f)+'\')">预览</button><button class="lead2-link" onclick="leadV2.notify(\'已下载:'+esc(f)+'\')">下载</button><button class="lead2-link lead2-danger" onclick="leadV2.removeFeedbackFile('+id+','+i+')">删除</button></div>').join('')+'</div></div>';mask.querySelector('.m-foot').innerHTML='<button class="btn primary" onclick="leadV2.saveFeedback('+id+')">保存</button> <button class="btn" onclick="leadV2.stashFeedback('+id+')">暂存</button> <button class="btn" onclick="leadV2.cancelFeedback('+id+')">取消</button>';mask.classList.add('show')}
function updateFeedback(id,k,v){const d=state.feedbackDrafts[id];if(d){d[k]=v;d.dirty=true}}
function feedbackFile(id,file){notify('Demo 环境不触发、不读取本地文件')}
function removeFeedbackFile(id,i){state.feedbackDrafts[id].files.splice(i,1);state.feedbackDrafts[id].dirty=true;openFeedback(id)}
function saveFeedback(id){const d=state.feedbackDrafts[id];if(!d.content.trim()||!d.status||!d.product){notify('请填写反馈内容、反馈情况和需求产品');return}delete state.feedbackDrafts[id];closeModal();notify('反馈保存操作已完成(Demo),未修改线索、历史记录或日志')}
function stashFeedback(id){delete state.feedbackDrafts[id];closeModal();notify('暂存操作已完成(Demo),未写入线索数据')}
function cancelFeedback(id){const d=state.feedbackDrafts[id];if(d?.dirty&&!confirm('是否放弃本次编辑内容?'))return;delete state.feedbackDrafts[id];closeModal()}
function openConvert(id){const r=rows.find(x=>x.id===id);if(!r)return;const mask=document.getElementById('modal'),panel=mask.querySelector('.modal');panel.classList.add('opp-modal-wide');document.getElementById('modalTitle').textContent='转商机';mask.querySelector('.m-body').innerHTML='<div class="lead2-modal-grid"><label class="lead2-required">项目名称</label><input id="lead2OppName" placeholder="输入项目名称"><label class="lead2-required">行业</label><select id="lead2OppIndustry"><option value="">选择行业</option><option>教育</option><option>政府</option><option>交通</option><option>企业</option><option>文旅</option></select><label>甲方</label><input id="lead2OppParty" placeholder="输入内容"><label class="lead2-required">意向客户</label><input id="lead2OppCustomer" value="'+esc(r.name)+'" placeholder="输入内容"><label class="lead2-required">地区</label><input id="lead2OppArea" value="'+esc(r.province+r.city)+'" placeholder="输入内容"><label>备注</label><textarea id="lead2OppNote" class="full" placeholder="输入内容"></textarea></div>';mask.querySelector('.m-foot').innerHTML='<button class="btn" onclick="closeModal()">取消</button> <button class="btn primary" onclick="leadV2.saveConvert('+id+')">保存</button>';mask.classList.add('show')}
function saveConvert(id){const name=document.getElementById('lead2OppName').value.trim(),industry=document.getElementById('lead2OppIndustry').value,customer=document.getElementById('lead2OppCustomer').value.trim(),area=document.getElementById('lead2OppArea').value.trim();if(!name||!industry||!customer||!area){notify('请完成所有必填项');return}closeModal();notify('转商机操作已完成(Demo),未生成商机或修改线索状态')}
function editLead(id){const r=rows.find(x=>x.id===id);const mask=document.getElementById('modal'),panel=mask.querySelector('.modal');panel.classList.add('opp-modal-wide');document.getElementById('modalTitle').textContent='编辑线索';mask.querySelector('.m-body').innerHTML='<div class="lead2-modal-grid"><label>客户名称</label><input id="lead2EditName" value="'+esc(r.name)+'"><label>联系号码</label><input value="'+esc(r.phone)+'"><label>咨询内容</label><textarea id="lead2EditConsult" class="full">'+esc(r.consult)+'</textarea></div>';mask.querySelector('.m-foot').innerHTML='<button class="btn" onclick="closeModal()">取消</button> <button class="btn primary" onclick="leadV2.saveEdit('+id+')">保存</button>';mask.classList.add('show')}
function saveEdit(id){closeModal();notify('编辑保存操作已完成(Demo),线索数据未发生变化')}
function openAssign(id){let ids=id?[id]:filtered('manage').filter(r=>state.selected.has(r.id)&&['待领取','未分发'].includes(r.manageStatus)).map(r=>r.id);if(!ids.length){notify('请选择待领取或未分发线索');return}state.assignIds=ids;state.assignSales=new Set();renderAssignModal(id?'分配':'批量分配')}
function renderAssignModal(titleText){const mask=document.getElementById('modal'),panel=mask.querySelector('.modal');panel.classList.add('opp-modal-wide');document.getElementById('modalTitle').textContent=titleText;const sales=['李清','吴辛柚','郑明云','区晓田','王东旭','王宇','张国梁','王超','周泽','郑晓龙','刘树生','曾丽丽','赵剑锋','董钰','王娟'];mask.querySelector('.m-body').innerHTML='<div class="lead2-modal-grid"><label>线索池</label><select id="lead2AssignPool" onchange="leadV2.assignCascade(1)"><option>请选择</option><option>广东广州区域公海池</option><option>浙江区域公海池</option><option>江苏全域公海池1</option></select><label>销售团队</label><select id="lead2AssignTeam" disabled onchange="leadV2.assignCascade(2)"><option>请选择</option><option>冠军队</option><option>特战队</option><option>粤晋战队</option></select><label>销售部门</label><select id="lead2AssignDept" disabled onchange="leadV2.assignCascade(3)"><option>请选择</option><option>销售10部</option><option>销售5部</option><option>广东1部</option></select><label>销售人员</label><div class="lead2-field-grid full">'+sales.map(x=>'<label><input type="checkbox" onchange="leadV2.toggleAssignSales(\''+x+'\',this.checked)"> '+x+'</label>').join('')+'</div><div class="lead2-note full">批量分配时,每条线索仍只保留一名领取人;系统按所选销售人员顺序循环分配。</div></div>';mask.querySelector('.m-foot').innerHTML='<button class="btn" onclick="closeModal()">取消</button> <button class="btn primary" onclick="leadV2.saveAssign()">保存</button>';mask.classList.add('show')}
function assignCascade(level){if(level===1){const v=document.getElementById('lead2AssignPool').value;document.getElementById('lead2AssignTeam').disabled=v==='请选择';document.getElementById('lead2AssignTeam').selectedIndex=0;document.getElementById('lead2AssignDept').disabled=true;document.getElementById('lead2AssignDept').selectedIndex=0}else if(level===2){const v=document.getElementById('lead2AssignTeam').value;document.getElementById('lead2AssignDept').disabled=v==='请选择';document.getElementById('lead2AssignDept').selectedIndex=0}}
function toggleAssignSales(name,on){on?state.assignSales.add(name):state.assignSales.delete(name)}
function saveAssign(){const sales=[...state.assignSales];if(!sales.length){notify('请至少勾选 1 名销售人员');return}state.assignIds=[];state.assignSales.clear();closeModal();notify('分配操作已完成(Demo),未修改领取人、归属或线索状态')}
function openCreate(){state.sourcePage=state.page;setActivePage('leadCreate','线索 / 线索管理 / 新增线索')}
function createFormData(){return '<div class="lead2-form-section"><h3>客户 & 产品信息</h3><div class="lead2-form-grid"><label>客户名称</label><input id="leadCreateName" placeholder="输入名称"><label>联系号码</label><input id="leadCreatePhone" placeholder="输入号码"><label class="lead2-required">省份</label><select id="leadCreateProvince" onchange="leadV2.createProvinceChanged()"><option value="">选择省份</option><option>广东省</option><option>浙江省</option><option>江苏省</option><option>山西省</option></select><label class="lead2-required">城市</label><select id="leadCreateCity" disabled><option value="">选择城市</option></select><label>详细地址</label><input id="leadCreateAddress" placeholder="输入地址"><span></span><span></span><label class="lead2-required">渠道</label><select id="leadCreateChannel"><option value="">请选择</option><option>展会</option><option>知乎</option><option>京东</option><option>1688</option></select><label class="lead2-required">品牌</label><select id="leadCreateBrand"><option value="">请选择</option><option>itc</option><option>世畅</option><option>北航星</option><option>特空</option></select><label class="lead2-required">需求产品</label><select id="leadCreateProduct"><option value="">请选择</option><option>LED显示屏</option><option>会议系统</option><option>录播系统</option><option>教学系统</option></select><label>需求场景</label><select id="leadCreateScene"><option value="">请选择</option><option>会议室</option><option>报告厅</option><option>普教</option><option>政府单位</option></select><label class="lead2-required">咨询内容</label><textarea id="leadCreateConsult" class="wide" placeholder="输入内容"></textarea></div></div><div class="lead2-form-section"><h3>线索归属分配信息</h3><div class="lead2-form-grid"><label>线索公海池</label><select id="leadCreatePool" onchange="leadV2.createCascade(1)"><option value="">请选择</option><option>广东广州区域公海池</option><option>浙江区域公海池</option><option>江苏全域公海池1</option></select><label>线索状态</label><select id="leadCreateStatus"><option value="">请选择</option><option>待领取</option><option>未分发</option></select><label>销售团队</label><select id="leadCreateTeam" disabled onchange="leadV2.createCascade(2)"><option value="">请选择</option><option>冠军队</option><option>特战队</option><option>粤晋战队</option></select><label>销售部门</label><select id="leadCreateDept" disabled onchange="leadV2.createCascade(3)"><option value="">请选择</option><option>销售10部</option><option>销售5部</option><option>广东1部</option></select><label>销售人员</label><select id="leadCreateOwner" disabled><option value="">输入或选择销售人员</option><option>'+currentUser+'</option><option>李清</option><option>林枫</option><option>王明</option></select><label>新增时间</label><input value="2026-07-11 10:30" disabled><label>是否加急</label><div><label><input type="radio" name="leadCreateUrgent" value="是"> 是</label> <label><input type="radio" name="leadCreateUrgent" value="否" checked> 否</label></div></div></div>'}
function renderCreate(){root().innerHTML='<div class="lead2-form-page"><h2>新增线索</h2>'+createFormData()+'<div class="lead2-form-foot"><button class="btn primary" onclick="leadV2.saveCreate()">新增线索</button><button class="btn" onclick="leadV2.cancelCreate()">关闭</button><label><input id="leadCreateContinue" type="checkbox"> 连续新增</label></div></div>'}
function createProvinceChanged(){const p=document.getElementById('leadCreateProvince').value,city=document.getElementById('leadCreateCity');const map={'广东省':['广州市','深圳市','佛山市'],'浙江省':['杭州市','宁波市'],'江苏省':['南京市','苏州市'],'山西省':['太原市','大同市']};city.disabled=!p;city.innerHTML='<option value="">选择城市</option>'+((map[p]||[]).map(x=>'<option>'+x+'</option>').join(''))}
function createCascade(level){if(level===1){const ok=!!document.getElementById('leadCreatePool').value;document.getElementById('leadCreateTeam').disabled=!ok;document.getElementById('leadCreateTeam').selectedIndex=0;document.getElementById('leadCreateDept').disabled=true;document.getElementById('leadCreateOwner').disabled=true}else if(level===2){const ok=!!document.getElementById('leadCreateTeam').value;document.getElementById('leadCreateDept').disabled=!ok;document.getElementById('leadCreateDept').selectedIndex=0;document.getElementById('leadCreateOwner').disabled=true}else{document.getElementById('leadCreateOwner').disabled=!document.getElementById('leadCreateDept').value}}
function saveCreate(){const get=id=>document.getElementById(id).value.trim(),province=get('leadCreateProvince'),city=get('leadCreateCity'),channel=get('leadCreateChannel'),brand=get('leadCreateBrand'),product=get('leadCreateProduct'),consult=get('leadCreateConsult');if(!province||!city||!channel||!brand||!product||!consult){notify('请完成所有带 * 的必填项');return}notify('新增保存操作已完成(Demo),未创建真实线索');if(!document.getElementById('leadCreateContinue').checked)setActivePage(state.sourcePage,'线索 / '+title(state.sourcePage))}
function cancelCreate(){if(confirm('是否放弃当前编辑内容?'))setActivePage(state.sourcePage,'线索 / '+title(state.sourcePage))}
function openImport(){state.sourcePage=state.page;state.importState='initial';setActivePage('leadImport','线索 / '+title(state.sourcePage)+' / 导入线索')}
function importPanel(kind){return '<div class="lead2-demo-upload" onclick="leadV2.chooseImportFile()"><div class="lead2-upload-icon">⇧</div><strong>文件上传区域(Demo)</strong><span>点击仅展示交互提示,不会打开本地文件选择器</span><small>不读取、不解析、不上传任何文件</small></div>'}
function renderImport(){root().innerHTML=customerV2.sharedImportPage('lead')}
function chooseImportFile(){notify('Demo 环境不触发本地文件上传')}
function dropImport(e){e?.preventDefault();notify('Demo 环境不接收拖拽文件')}
function pasteImport(){notify('Demo 环境不读取粘贴文件')}
function startImport(){notify('Demo 环境不执行真实导入')}
function retryImport(){state.importState='initial';render()}
function setImportMode(v){state.importMode=v;render()}
function downloadLeadTemplate(){notify('Demo 环境不生成或下载模板文件')}
function exportLeads(){notify('导出任务已创建(Demo),不会生成真实文件')}
function renderSetting(){root().innerHTML='<div class="lead2-page"><div class="lead2-head"><div class="lead2-head-left"><h1>线索管理设置</h1></div><div class="lead2-head-right"><span>当前权限:超级管理员</span></div></div><div class="lead2-controls"><div class="lead2-toolbar"><div class="lead2-filters"><select><option>全部团队</option><option>冠军队</option><option>特战队</option></select><select><option>全部部门</option><option>销售10部</option><option>销售5部</option></select><select><option>全部省份</option><option>广东省</option><option>浙江省</option></select><input placeholder="部门 / 负责人"><button class="btn primary">查询</button><button class="btn">重置</button></div><div class="lead2-actions"><button class="btn primary" onclick="leadV2.openSettingEdit(\'create\')">新增公海池</button><button class="btn" onclick="leadV2.openSettingImport()">导入公海池</button><button class="btn" onclick="leadV2.exportPools()">导出公海池</button><button class="btn" onclick="leadV2.notify(\'请勾选公海池后批量删除\')">批量删除</button></div></div></div><div class="lead2-main"><div class="lead2-scroll"><table class="lead2-table"><thead><tr><th class="fixed-no"><label class="lead2-check"><input type="checkbox"><span>序号</span></label></th><th>公海池名称</th><th>团队</th><th>部门</th><th>省份</th><th>负责市场(城市)</th><th>部门负责人</th><th>部门成员</th><th>领取规则</th><th>超时回收公海</th><th>线索失效时长</th><th>个人持有上限</th><th class="fixed-action">操作</th></tr></thead><tbody>'+poolRows.map((r,i)=>'<tr><td class="fixed-no"><label class="lead2-check"><input type="checkbox"><span>'+(i+1)+'</span></label></td><td>'+esc(r.name)+'</td><td>'+esc(r.team)+'</td><td>'+esc(r.dept)+'</td><td>'+esc(r.province)+'</td><td>'+esc(r.markets)+'</td><td>'+esc(r.leader)+'</td><td class="long" title="'+esc(r.members)+'">'+esc(r.members)+'</td><td>'+esc(r.rule)+'</td><td>'+esc(r.recycle)+'</td><td>'+esc(r.expire)+'</td><td>'+esc(r.limit)+'</td><td class="fixed-action"><div class="lead2-row-actions"><button class="lead2-link" onclick="leadV2.openSettingEdit(\'edit\','+i+')">编辑</button><button class="lead2-link lead2-danger" onclick="leadV2.deletePool('+i+')">删除</button></div></td></tr>').join('')+'</tbody></table></div>'+pagination(poolRows.length)+'</div></div>'}
function openSettingEdit(modeValue,index){state.settingEditMode=modeValue;state.settingIndex=Number.isInteger(index)?index:0;state.settingTab='base';state.settingDirty=false;setActivePage('leadSettingEdit','线索 / 线索管理设置 / '+(modeValue==='create'?'新增公海池':'编辑公海池'))}
function baseSettingForm(){const r=state.settingEditMode==='edit'?poolRows[state.settingIndex]:{name:'',team:'',dept:'',province:'',leader:'',rule:'成员可见可领取、管理员可分配',recycle:'7天',expire:'180天',limit:'200'};return '<div class="lead2-form-section"><div class="lead2-form-grid"><label class="lead2-required">公海池名称</label><input id="poolName" value="'+esc(r.name)+'" placeholder="输入名称" oninput="leadV2.markSettingDirty()"><label class="lead2-required">销售团队</label><select id="poolTeam" onchange="leadV2.markSettingDirty()"><option value="">请选择</option><option '+(r.team==='冠军队'?'selected':'')+'>冠军队</option><option '+(r.team==='特战队'?'selected':'')+'>特战队</option><option '+(r.team==='粤晋战队'?'selected':'')+'>粤晋战队</option></select><label class="lead2-required">销售部门</label><select id="poolDept" onchange="leadV2.markSettingDirty()"><option value="">请选择</option><option '+(r.dept==='销售10部'?'selected':'')+'>销售10部</option><option '+(r.dept==='销售5部'?'selected':'')+'>销售5部</option><option '+(r.dept==='江苏队'?'selected':'')+'>江苏队</option></select><label class="lead2-required">省份</label><select id="poolProvince" onchange="leadV2.markSettingDirty()"><option value="">选择省份</option><option '+(r.province==='广东省'?'selected':'')+'>广东省</option><option '+(r.province==='浙江省'?'selected':'')+'>浙江省</option><option '+(r.province==='江苏省'?'selected':'')+'>江苏省</option><option '+(r.province==='山西省'?'selected':'')+'>山西省</option></select><label>负责市场</label><div><button class="btn" onclick="leadV2.openPicker(\'market\')">添加</button><div class="lead2-tags" style="margin-top:8px">'+state.settingMarkets.map((x,i)=>'<span class="lead2-tag">'+esc(x)+' <button onclick="leadV2.removeSettingTag(\'market\','+i+')">×</button></span>').join('')+'</div></div><span></span><div class="lead2-note">市场范围支持按省份检索、多选城市,保存后用于公海线索归属与领取权限校验。</div><label>部门负责人</label><input id="poolLeader" value="'+esc(r.leader||'')+'" placeholder="请输入管理员姓名" oninput="leadV2.markSettingDirty()"><label>部门协作人</label><input placeholder="请输入姓名检索,多选"><label>部门成员</label><div><button class="btn" onclick="leadV2.openPicker(\'member\')">添加</button><div class="lead2-tags" style="margin-top:8px">'+state.settingMembers.map((x,i)=>'<span class="lead2-tag">'+esc(x)+' <button onclick="leadV2.removeSettingTag(\'member\','+i+')">×</button></span>').join('')+'</div></div><span></span><div class="lead2-note">部门成员决定哪些销售可查看和领取本公海池线索;负责人仅能选择本部门人员。</div></div></div>'}
function otherSettingForm(){return '<div class="lead2-rule-list"><div class="lead2-rule-item"><h4>领取规则</h4><label><input type="radio" name="poolRule" value="成员可见可领取、管理员可分配" checked> 成员可见 / 可领取、管理员可分配</label><br><label><input type="radio" name="poolRule" value="管理员可分配"> 管理员可分配</label></div><div class="lead2-rule-item"><h4>个人领取上限</h4><div class="lead2-inline-number"><span>个人每日领取上限</span><input id="poolDaily" type="number" min="0" value="10"><span>条</span></div><div class="lead2-inline-number" style="margin-top:9px"><span>个人持有上限</span><input id="poolLimit" type="number" min="0" value="200"><span>条</span></div></div><div class="lead2-rule-item"><h4>超时回收公海</h4><div class="lead2-inline-number"><span>超过</span><input id="poolRecycle" type="number" min="1" value="7"><span>天未跟进自动回收原公海池</span></div></div><div class="lead2-rule-item"><h4>线索失效时长</h4><div class="lead2-inline-number"><span>线索有效时长</span><input id="poolExpire" type="number" min="1" value="180"><span>天,超时自动标记过期失效</span></div></div><div class="lead2-note">规则修改后次日起作用于存量线索;当销售达到每日领取上限或个人持有上限时,公海领取按钮置灰并提示原因。</div></div>'}
function renderSettingEdit(){root().innerHTML='<div class="lead2-form-page"><h2>'+(state.settingEditMode==='create'?'新增公海池':'编辑公海池')+'</h2><div class="lead2-setting-tabs"><button class="'+(state.settingTab==='base'?'active':'')+'" onclick="leadV2.setSettingTab(\'base\')">公海池</button><button class="'+(state.settingTab==='other'?'active':'')+'" onclick="leadV2.setSettingTab(\'other\')">其他设置</button></div>'+(state.settingTab==='base'?baseSettingForm():otherSettingForm())+'<div class="lead2-form-foot"><button class="btn primary" onclick="leadV2.saveSetting()">保存</button><button class="btn" onclick="leadV2.cancelSetting()">取消</button></div></div>'}
function setSettingTab(v){state.settingTab=v;render()}
function markSettingDirty(){state.settingDirty=true}
function removeSettingTag(type,i){(type==='market'?state.settingMarkets:state.settingMembers).splice(i,1);state.settingDirty=true;render()}
function openPicker(type){state.pickerType=type;const source=type==='market'?['广州市','深圳市','中山市','东莞市','惠州市','珠海市','佛山市-禅城区','杭州市','宁波市','南京市','苏州市']:['胡思轶','安心','毛毛冲','彩虹','王东旭','李清','林枫','王明','吴天','许静静'];state.pickerDraft=new Set(type==='market'?state.settingMarkets:state.settingMembers);const mask=document.getElementById('modal'),panel=mask.querySelector('.modal');panel.classList.add('opp-modal-wide');document.getElementById('modalTitle').textContent=type==='market'?'添加负责市场':'添加部门成员';mask.querySelector('.m-body').innerHTML='<div class="lead2-picker"><div class="lead2-picker-list"><input style="width:100%;height:34px;margin-bottom:8px" placeholder="输入城市 / 人员 / 部门"><h4>'+(type==='market'?'省份 / 城市':'营销中心 > 团队 > 部门')+'</h4>'+source.map(x=>'<label><input type="checkbox" '+(state.pickerDraft.has(x)?'checked':'')+' onchange="leadV2.togglePicker(\''+x+'\',this.checked)"> '+x+'</label>').join('')+'</div><div class="lead2-picker-list"><h4>已选择('+state.pickerDraft.size+') <button class="lead2-link" onclick="leadV2.clearPicker()">清空</button></h4><div class="lead2-picker-selected">'+[...state.pickerDraft].map(x=>'<span class="lead2-tag">'+esc(x)+'</span>').join('')+'</div></div></div>';mask.querySelector('.m-foot').innerHTML='<button class="btn" onclick="closeModal()">取消</button> <button class="btn primary" onclick="leadV2.savePicker()">保存</button>';mask.classList.add('show')}
function togglePicker(x,on){on?state.pickerDraft.add(x):state.pickerDraft.delete(x);openPicker(state.pickerType)}
function clearPicker(){state.pickerDraft.clear();openPicker(state.pickerType)}
function savePicker(){if(state.pickerType==='market')state.settingMarkets=[...state.pickerDraft];else state.settingMembers=[...state.pickerDraft];state.settingDirty=true;closeModal();render()}
function saveSetting(){notify('配置保存成功(Demo),未写入真实数据');state.settingDirty=false;setActivePage('leadSetting','线索 / 线索管理设置')}
function cancelSetting(){if(!state.settingDirty||confirm('是否放弃当前所有编辑内容?'))setActivePage('leadSetting','线索 / 线索管理设置')}
function deletePool(i){openDemoConfirm('删除公海池','确定执行删除操作?','删除操作已确认(Demo),公海池数据未发生变化')}
function openSettingImport(){state.sourcePage='leadSetting';state.importState='initial';setActivePage('leadSettingImport','线索 / 线索管理设置 / 导入公海池')}
function renderSettingImport(){root().innerHTML='<div class="lead2-import"><div class="lead2-import-head"><h1>导入公海池</h1></div><div class="lead2-import-body"><section><h3>导入方式</h3><div class="lead2-import-modes">'+['仅新增数据','仅更新数据','新增或更新数据'].map(x=>'<button class="'+(state.importMode===x?'active':'')+'" onclick="leadV2.setImportMode(\''+x+'\')">'+x+'</button>').join('')+'</div></section><section><h3>上传公海池配置文件</h3><p><button class="lead2-link" onclick="leadV2.downloadPoolTemplate()">下载标准模板 ⇩</button></p>'+importPanel('pool')+'</section><section><h3>校验说明</h3><ol><li>规则说明仅用于原型评审。</li><li>Demo 环境不选择、不读取、不解析本地文件。</li><li>不会修改公海池、权限、领取规则或下拉数据源。</li></ol></section></div></div>'}
function downloadPoolTemplate(){notify('Demo 环境不生成或下载模板文件')}
function exportPools(){notify('导出任务已创建(Demo),不会生成真实文件')}
function renderBreadcrumb(){const bar=document.getElementById('locationCurrent')?.parentElement||document.querySelector('.location-bar');if(!bar)return;let html='<span class="crumb-parent">线索</span><span class="crumb-sep">›</span>';if(state.page==='leadCreate')html+='<button class="crumb-btn" onclick="setActivePage(\''+state.sourcePage+'\',\'线索 / '+title(state.sourcePage)+'\')">'+title(state.sourcePage)+'</button><span class="crumb-sep">›</span><span class="crumb-current">新增线索</span>';else if(state.page==='leadImport')html+='<button class="crumb-btn" onclick="setActivePage(\''+state.sourcePage+'\',\'线索 / '+title(state.sourcePage)+'\')">'+title(state.sourcePage)+'</button><span class="crumb-sep">›</span><span class="crumb-current">导入线索</span>';else if(state.page==='leadSettingEdit')html+='<button class="crumb-btn" onclick="setActivePage(\'leadSetting\',\'线索 / 线索管理设置\')">线索管理设置</button><span class="crumb-sep">›</span><span class="crumb-current">'+(state.settingEditMode==='create'?'新增公海池':'编辑公海池')+'</span>';else if(state.page==='leadSettingImport')html+='<button class="crumb-btn" onclick="setActivePage(\'leadSetting\',\'线索 / 线索管理设置\')">线索管理设置</button><span class="crumb-sep">›</span><span class="crumb-current">导入公海池</span>';else if(state.detailId){const r=rows.find(x=>x.id===state.detailId);html+='<button class="crumb-btn" onclick="leadV2.back()">'+title()+'</button><span class="crumb-sep">›</span><span class="crumb-current">'+esc(r?.name||'详情')+'</span>'}else html+='<span class="crumb-current">'+title()+'</span>';bar.innerHTML=html}
function closeFloating(){if(!state.leadMenuOpen)return;state.leadMenuOpen=false;const menu=root()?.querySelector('.crm-lead-view-menu'),btn=root()?.querySelector('.crm-lead-title-btn');menu?.classList.remove('show');btn?.classList.remove('open')}
function getCreatedOpportunities(){return []}
function notify(msg){let n=document.getElementById('lead2Toast');if(!n){n=document.createElement('div');n.id='lead2Toast';n.className='crm-toast';document.body.appendChild(n)}n.textContent=msg;n.classList.add('show');clearTimeout(n._t);n._t=setTimeout(()=>n.classList.remove('show'),1900)}
return {handles,enter,renderBreadcrumb,setView,setFilter,query,reset,listTool,toggleSelected,toggleAll,selectSplit,toggleSplit,openDetail,back,switchDetailTab,toggleHistoryDemo,dragStart,dropField,claim,follow,unfollow,confirmUnfollow,release,completeDemoAction,deleteMine,deleteManage,batchClaim,batchFollow,batchUnfollow,batchRelease,mergeSelected,deleteSelected,openFields,searchFields,toggleField,toggleAllFields,saveFields,openFeedback,updateFeedback,feedbackFile,removeFeedbackFile,saveFeedback,stashFeedback,cancelFeedback,openConvert,saveConvert,editLead,saveEdit,openAssign,assignCascade,toggleAssignSales,saveAssign,openCreate,createProvinceChanged,createCascade,saveCreate,cancelCreate,openImport,chooseImportFile,dropImport,pasteImport,startImport,retryImport,setImportMode,downloadLeadTemplate,exportLeads,openSettingEdit,setSettingTab,markSettingDirty,removeSettingTag,openPicker,togglePicker,clearPicker,savePicker,saveSetting,cancelSetting,deletePool,openSettingImport,downloadPoolTemplate,exportPools,getCreatedOpportunities,toggleLeadMenu,setLeadMenuTab,toggleLeadPin,selectLeadPage,closeFloating,notify};
})();
function syncOpportunitySideNav(label){const text=String(label||''),target=text.includes('商机管理')?'商机 / 商机管理':text.includes('商机公海')?'商机 / 商机公海':'商机 / 销售机会';document.querySelectorAll('.side-item[data-page="myOpportunity"],.neo-menu-panel button[data-page="myOpportunity"]').forEach(item=>item.classList.toggle('active',item.dataset.label===target))}
document.addEventListener('click',function(e){if(!e.target.closest('.opp-view-title')&&!e.target.closest('.opp-toolbar-right'))oppModule.closeFloating();if(!e.target.closest('.crm-lead-title-wrap'))leadV2.closeFloating();if(!e.target.closest('.crm-title-wrap'))customerV2.closeSearchMenu()});
document.querySelector('.neo-global-nav')?.addEventListener('pointerenter',function(){oppModule.closeFloating();leadV2.closeFloating();customerV2.closeSearchMenu()});
oppModule.render();document.querySelector('.neo-shell')?.classList.add('home-active');
(function renderIcons(){
const iconPaths={
'home':'<path d="M3 10.5 12 3l9 7.5"/><path d="M5 10v10h14V10"/><path d="M9 20v-6h6v6"/>',
'users':'<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>',
'folder-kanban':'<path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7l-2-2H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/><path d="M8 10v4M12 10v2M16 10v4"/>',
'bar-chart-3':'<path d="M3 3v18h18"/><path d="M7 16v-5M12 16V8M17 16v-3"/>',
'settings-2':'<path d="M20 7h-9"/><path d="M14 17H5"/><circle cx="17" cy="17" r="3"/><circle cx="7" cy="7" r="3"/>',
'settings':'<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.38a2 2 0 0 0-.73-2.73l-.15-.09a2 2 0 0 1-1-1.74v-.51a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2Z"/><circle cx="12" cy="12" r="3"/>',
'sparkles':'<path d="m12 3-1.9 5.8a2 2 0 0 1-1.3 1.3L3 12l5.8 1.9a2 2 0 0 1 1.3 1.3L12 21l1.9-5.8a2 2 0 0 1 1.3-1.3L21 12l-5.8-1.9a2 2 0 0 1-1.3-1.3Z"/><path d="M5 3v4M19 17v4M3 5h4M17 19h4"/>',
'circle-help':'<circle cx="12" cy="12" r="10"/><path d="M9.1 9a3 3 0 1 1 5.8 1c-.6 1.1-1.9 1.5-2.4 2.5"/><path d="M12 17h.01"/>',
'bell':'<path d="M18 8a6 6 0 0 0-12 0c0 7-3 7-3 9h18c0-2-3-2-3-9M10 21h4"/>',
'grid-2x2':'<rect width="7" height="7" x="3" y="3" rx="1"/><rect width="7" height="7" x="14" y="3" rx="1"/><rect width="7" height="7" x="3" y="14" rx="1"/><rect width="7" height="7" x="14" y="14" rx="1"/>',
'inbox':'<path d="M22 12h-6l-2 3h-4l-2-3H2"/><path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11Z"/>',
'calendar-check':'<path d="M8 2v4M16 2v4M3 10h18"/><rect x="3" y="4" width="18" height="18" rx="2"/><path d="m9 16 2 2 4-4"/>',
'briefcase-business':'<rect width="20" height="14" x="2" y="7" rx="2"/><path d="M16 7V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v2"/><path d="M2 12h20"/>',
'circle-dollar-sign':'<circle cx="12" cy="12" r="10"/><path d="M16 8h-6a2 2 0 1 0 0 4h4a2 2 0 1 1 0 4H8M12 18V6"/>',
'triangle-alert':'<path d="m21.73 18-8-14a2 2 0 0 0-3.46 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4M12 17h.01"/>',
'funnel':'<path d="M3 4h18l-7 8v6l-4 2v-8Z"/>',
'building-2':'<path d="M6 22V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v18Z"/><path d="M6 12H2v10h20V12h-4M10 6h4M10 10h4M10 14h4M10 18h4"/>',
'target':'<circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/>'
};
document.querySelectorAll('.ui-icon[data-icon]').forEach(node=>{
if(node.querySelector('svg'))return;
node.innerHTML='<svg viewBox="0 0 24 24" aria-hidden="true">'+(iconPaths[node.dataset.icon]||'')+'</svg>';
});
})();
</script>
<style id="opp-settings-v2-styles">
/* 商机管理设置优化版:遵循 0716 Axure 的列表、详情、草稿编辑与版本管理结构 */
#oppSetting{background:#f4f7fb;overflow:auto}.os-page{min-height:100%;padding:18px 20px 28px;color:#26364a}.os-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin-bottom:14px}.os-head h1{margin:0 0 6px;font-size:22px;color:#173b64}.os-head p{margin:0;color:#6b7b91;font-size:13px;line-height:1.7}.os-layout{display:grid;grid-template-columns:188px minmax(0,1fr);gap:14px;align-items:start}.os-nav{position:sticky;top:12px;border:1px solid #dce6f1;border-radius:9px;background:#fff;padding:7px}.os-nav button{display:block;width:100%;border:0;border-radius:6px;background:transparent;padding:10px 12px;text-align:left;color:#52637a;font-size:14px;cursor:pointer}.os-nav button:hover{background:#f1f6fd;color:#1677ff}.os-nav button.active{background:#eaf3ff;color:#1677ff;font-weight:700}.os-main{min-width:0}.os-section-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin:2px 0 10px}.os-section-head h2{margin:0 0 4px;font-size:18px;color:#173b64}.os-section-head p{margin:0;color:#7a899d;font-size:12px}.os-card{border:1px solid #dce6f1;border-radius:9px;background:#fff;overflow:hidden}.os-toolbar{display:flex;align-items:center;justify-content:space-between;gap:10px;min-height:58px;padding:10px 12px;border-bottom:1px solid #e7edf5;flex-wrap:wrap}.os-query{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.os-query input,.os-query select,.os-form input,.os-form select,.os-form textarea,.os-node-form input,.os-node-form select,.os-node-form textarea{height:32px;border:1px solid #cad7e5;border-radius:5px;padding:0 9px;color:#334155;background:#fff;font:inherit;font-size:13px}.os-query input{width:236px}.os-query select{min-width:120px}.os-form textarea,.os-node-form textarea{height:auto;min-height:70px;padding:8px;resize:vertical}.os-btn{height:32px;border:1px solid #cbd8e6;border-radius:5px;background:#fff;padding:0 12px;color:#40546b;cursor:pointer;font-size:13px}.os-btn:hover{border-color:#8dbcf2;color:#1677ff;background:#f6faff}.os-btn.primary{background:#1677ff;border-color:#1677ff;color:#fff}.os-btn.danger{border-color:#f1b4b4;color:#d93025}.os-btn:disabled{opacity:.52;cursor:not-allowed}.os-table-wrap{overflow:auto}.os-table{width:100%;min-width:1080px;border-collapse:collapse;font-size:13px}.os-table th{height:42px;background:#f7f9fc;border-bottom:1px solid #dfe8f2;color:#61728a;font-weight:600;text-align:left;padding:0 10px;white-space:nowrap}.os-table td{height:52px;border-bottom:1px solid #edf1f6;padding:7px 10px;color:#36475d;vertical-align:middle}.os-table tbody tr:hover{background:#f8fbff}.os-rule-name b{display:block;color:#1d3f66;margin-bottom:3px}.os-rule-name small,.os-muted{display:block;color:#8a99aa;font-size:12px;line-height:1.45}.os-actions{white-space:nowrap}.os-actions button,.os-text-btn{border:0;background:none;padding:2px 5px;color:#1677ff;cursor:pointer;font:inherit;font-size:13px}.os-actions button.danger{color:#d93025}.os-badge{display:inline-flex;align-items:center;min-height:21px;padding:0 7px;border-radius:11px;font-size:12px;white-space:nowrap}.os-badge.draft{background:#fff4dd;color:#bf7200}.os-badge.live{background:#e8f8ef;color:#16824a}.os-badge.history{background:#f0f3f6;color:#6d7b8d}.os-badge.fixed{background:#eef3f8;color:#52637a}.os-pager{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;color:#728197;font-size:12px}.os-pager button{margin-left:5px;border:1px solid #d7e1ec;background:#fff;border-radius:4px;width:27px;height:25px;color:#52637a}.os-pager button.active{border-color:#1677ff;background:#1677ff;color:#fff}.os-detail-head,.os-edit-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:12px}.os-detail-title{display:flex;align-items:center;gap:9px;min-width:0}.os-detail-title h2,.os-edit-head h2{margin:0;font-size:18px;color:#173b64}.os-back{border:0;background:transparent;color:#53708f;cursor:pointer;padding:4px 0;font-size:13px}.os-detail-actions,.os-edit-actions{display:flex;gap:8px;flex-wrap:wrap}.os-info-grid{display:grid;grid-template-columns:130px minmax(190px,1fr) 130px minmax(190px,1fr);gap:0;border-top:1px solid #e2eaf3}.os-info-grid dt,.os-info-grid dd{min-height:42px;margin:0;padding:11px;border-bottom:1px solid #e7edf4;font-size:13px;box-sizing:border-box}.os-info-grid dt{background:#f8fafc;color:#718197}.os-info-grid dd{color:#33465d}.os-info-grid .wide{grid-column:span 3}.os-subtitle{display:flex;align-items:center;justify-content:space-between;padding:12px 14px;border-bottom:1px solid #e7edf4}.os-subtitle h3{margin:0;color:#294763;font-size:15px}.os-flow{display:flex;align-items:stretch;gap:7px;padding:18px 14px;overflow-x:auto}.os-flow-node{position:relative;flex:0 0 176px;border:1px solid #d4e0ed;border-radius:7px;background:#fff;padding:11px 12px;text-align:left;color:#354b63;cursor:pointer}.os-flow-node b{display:block;margin-bottom:5px;color:#1d3f66;font-size:13px}.os-flow-node small{color:#718197;line-height:1.45}.os-flow-node.active{border-color:#1677ff;background:#f0f7ff;box-shadow:0 0 0 2px rgba(22,119,255,.1)}.os-flow-node.fixed{background:#f8fafc}.os-flow-arrow{flex:0 0 16px;align-self:center;color:#9ab0c7;font-size:23px}.os-version{margin-top:12px}.os-version .os-table{min-width:760px}.os-origin{margin-bottom:12px;border:1px solid #cfe2f8;border-radius:7px;background:#f4f9ff;padding:10px 12px;color:#4c6682;font-size:13px}.os-form{padding:14px;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px 16px}.os-form label,.os-node-form label{display:grid;gap:6px;color:#5f7186;font-size:13px}.os-form label.full{grid-column:1/-1}.os-required{color:#e34d42}.os-stage-editor{margin-top:12px}.os-drag-tip{color:#78889d;font-size:12px}.os-stage-canvas{border-top:1px solid #e7edf4;background:linear-gradient(180deg,#f9fbfe,#fff)}.os-stage-node{cursor:grab}.os-stage-node.dragging{opacity:.42}.os-stage-node.drop-before{box-shadow:-4px 0 0 #1677ff}.os-stage-node.drop-after{box-shadow:4px 0 0 #1677ff}.os-node-tools{display:flex;gap:4px;position:absolute;right:8px;top:7px}.os-node-tools button{width:22px;height:22px;border:1px solid #d7e2ed;background:#fff;border-radius:4px;color:#60768f;cursor:pointer}.os-node-editor{display:grid;grid-template-columns:1.18fr .82fr;gap:14px;padding:14px;border-top:1px solid #e7edf4}.os-node-form{display:grid;gap:11px}.os-node-dict{border:1px solid #dbe5ef;border-radius:7px;background:#f8fafc;padding:12px}.os-node-dict h4{margin:0 0 9px;color:#405871;font-size:13px}.os-dict-choice{display:flex;gap:7px;align-items:flex-start;padding:8px 0;border-bottom:1px solid #e6edf4;color:#52677d;font-size:12px}.os-dict-choice:last-child{border-bottom:0}.os-dict-choice b{display:block;color:#304a65;font-size:13px;margin-bottom:3px}.os-editor-foot{display:flex;justify-content:flex-end;gap:8px;padding:13px 14px;border-top:1px solid #e7edf4;background:#fbfcfe}.os-stepper{display:grid;grid-template-columns:repeat(4,minmax(140px,1fr));gap:8px;padding:13px;border-bottom:1px solid #e7edf4;background:#fbfcfe}.os-step{border:1px solid #d9e3ee;border-radius:7px;background:#fff;padding:10px;text-align:left;color:#566b82;cursor:pointer}.os-step b{display:block;font-size:13px;margin:4px 0}.os-step small{font-size:11px;color:#8190a2}.os-step .n{display:inline-grid;place-items:center;width:20px;height:20px;border-radius:50%;background:#edf2f7;color:#62758b;font-size:11px}.os-step.active{border-color:#1677ff;background:#eff7ff;color:#1677ff}.os-step.active .n{background:#1677ff;color:#fff}.os-step-body{padding:14px}.os-two-col{display:grid;grid-template-columns:1fr 1fr;gap:12px}.os-panel{border:1px solid #dce6f1;border-radius:7px;background:#fff;padding:12px}.os-panel h4{margin:0 0 10px;color:#294763;font-size:14px}.os-check-list{display:grid;gap:8px}.os-check-list label{display:flex;align-items:center;gap:7px;color:#53677d;font-size:13px}.os-check-table{width:100%;border-collapse:collapse;font-size:13px}.os-check-table th,.os-check-table td{border:1px solid #dfe8f1;padding:8px;text-align:left}.os-check-table th{background:#f7f9fc;color:#60738a;font-weight:600}.os-mini-kv{display:grid;grid-template-columns:100px 1fr;gap:0}.os-mini-kv span{padding:8px;border-bottom:1px solid #edf1f6;font-size:13px}.os-mini-kv span:nth-child(odd){color:#74849a;background:#f8fafc}.os-pool-grid{display:grid;grid-template-columns:repeat(3,minmax(180px,1fr));gap:12px;padding:14px}.os-pool-card{border:1px solid #dce6f1;border-radius:7px;background:#fff;min-height:155px}.os-pool-card h4{margin:0;padding:11px 12px;border-bottom:1px solid #e7edf4;color:#294763;font-size:14px}.os-pool-card .body{padding:12px;display:grid;gap:12px}.os-line{display:flex;align-items:center;gap:7px;color:#52677d;font-size:13px;line-height:1.45}.os-line input[type=number]{width:54px;height:28px;border:1px solid #cad7e5;border-radius:4px;text-align:center}.os-fixed-note{margin:0 14px 14px;border-left:3px solid #9dbfe7;background:#f5f9fd;padding:9px 11px;color:#5d7188;font-size:12px;line-height:1.6}.os-field-grid{display:grid;grid-template-columns:220px minmax(0,1fr) 300px;gap:12px}.os-side-list{border:1px solid #dce6f1;border-radius:8px;background:#fff;padding:8px}.os-side-list h3{margin:6px 7px 10px;color:#405871;font-size:14px}.os-side-list button{width:100%;border:0;border-radius:6px;background:transparent;padding:10px;text-align:left;color:#51677e;cursor:pointer}.os-side-list button.active{background:#eef6ff;color:#1677ff}.os-preview{border:1px solid #dce6f1;border-radius:8px;background:#fff;padding:14px}.os-preview h3{margin:0 0 12px;color:#294763;font-size:15px}.os-preview-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.os-preview-field{border:1px solid #e1e8f0;border-radius:6px;padding:9px}.os-preview-field b{display:block;font-size:13px;color:#40576f}.os-preview-field small{display:block;margin-top:5px;color:#8a98aa}.os-preview-field input{margin-top:7px;width:100%;height:29px;box-sizing:border-box;border:1px solid #d4deea;border-radius:4px}.os-dict-layout{display:grid;grid-template-columns:310px minmax(0,1fr);gap:12px}.os-empty{padding:35px;text-align:center;color:#8b99aa}.os-toast{position:fixed;z-index:9999;left:50%;bottom:42px;transform:translateX(-50%) translateY(12px);opacity:0;border-radius:6px;background:rgba(20,35,54,.9);color:#fff;padding:10px 16px;font-size:13px;transition:.2s;pointer-events:none}.os-toast.show{opacity:1;transform:translateX(-50%)}
.os-detail-topbar{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:12px}.os-page-detail{padding:16px 24px 30px}.os-detail-shell{width:100%;margin:0}.os-detail-sheet{background:#fff;border:1px solid #dce6f1;border-radius:8px;overflow:hidden}.os-detail-hero{margin:0;border:0;background:transparent;border-radius:0;overflow:visible}.os-detail-core{display:flex;align-items:flex-start;justify-content:space-between;gap:18px;padding:15px 14px 10px}.os-detail-name h2{display:flex;align-items:center;gap:8px;margin:0;color:#173b64;font-size:20px;line-height:1.35}.os-detail-version{font-weight:500;color:#496b8e}.os-detail-name p{margin:6px 0 0;color:#73849a;font-size:12px}.os-detail-return{flex:0 0 auto;padding-top:1px}.os-detail-meta{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px 20px;padding:8px 14px}.os-detail-meta div{display:flex;gap:6px;min-width:0;color:#718198;font-size:13px}.os-detail-meta span{white-space:nowrap}.os-detail-meta b{color:#354b63;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.os-detail-description{display:flex;gap:8px;padding:3px 14px 9px;color:#718198;font-size:13px}.os-detail-description span{white-space:nowrap}.os-detail-description p{margin:0;color:#354b63;line-height:1.5}.os-detail-actionbar{display:flex;align-items:center;gap:8px;padding:7px 14px 13px;border-bottom:0;margin:0}.os-detail-tabs{display:flex;gap:0;border-top:1px solid #e2eaf3;border-bottom:1px solid #dce6f1;padding:0 14px}.os-detail-tabs button{border:0;border-bottom:2px solid transparent;background:transparent;padding:11px 16px 9px;color:#667991;font-size:13px;cursor:pointer}.os-detail-tabs button.active{border-bottom-color:#1677ff;color:#1677ff;font-weight:700}.os-detail-content>.os-card,.os-detail-content>.os-detail-section{border:0;border-radius:0;background:transparent;margin:0;overflow:visible}.os-detail-content .os-subtitle{padding-left:14px;padding-right:14px}.os-detail-content .os-version{margin:0}.os-detail-content .os-table th:first-child,.os-detail-content .os-table td:first-child{padding-left:14px}.os-detail-section{display:block}.os-stage-detail .os-flow{padding-top:14px}.os-current-stage{padding:0 14px 16px}.os-current-stage h4{margin:0 0 10px;color:#294763;font-size:14px}.os-current-stage-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px 16px}.os-current-stage-grid label{display:grid;gap:6px;color:#5f7186;font-size:13px}.os-current-stage-grid label.full{grid-column:1/-1}.os-current-stage-grid input,.os-current-stage-grid select,.os-current-stage-grid textarea{height:32px;box-sizing:border-box;border:1px solid #cad7e5;border-radius:5px;padding:0 9px;color:#52677d;background:#f7f9fc;font:inherit;font-size:13px}.os-current-stage-grid textarea{height:76px;padding:8px;resize:none}.os-node-readonly{margin:0 14px 14px;border:1px solid #cfe0f0;border-radius:7px;background:#f8fbfe;padding:11px 12px}.os-node-readonly b{display:block;color:#294763;font-size:13px}.os-node-readonly p{margin:7px 0;color:#526a84;font-size:13px;line-height:1.55}.os-node-readonly span{color:#8190a2;font-size:12px}.os-version .os-table{min-width:640px}.os-dedupe-summary{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));border-bottom:1px solid #e7edf4;background:#fbfcfe}.os-dedupe-summary div{padding:13px 14px;border-right:1px solid #e7edf4}.os-dedupe-summary div:last-child{border-right:0}.os-dedupe-summary b{display:block;color:#1d4c7e;font-size:18px}.os-dedupe-summary span{display:block;margin-top:4px;color:#7b8a9d;font-size:12px}.os-dedupe-example{padding:0 14px 14px}.os-dedupe-example h4{margin:0;padding:12px 0 8px;color:#294763;font-size:14px}.os-dict-tabs{display:flex;gap:0;margin-bottom:12px;border-bottom:1px solid #dce6f1}.os-dict-tabs button{border:0;border-bottom:2px solid transparent;background:transparent;padding:10px 18px;color:#667991;font-size:14px;cursor:pointer}.os-dict-tabs button.active{border-bottom-color:#1677ff;color:#1677ff;font-weight:700}.os-stage-gap{display:flex;align-items:center;gap:2px}.os-stage-gap button{width:22px;height:22px;border:1px dashed #9cb9d6;border-radius:50%;background:#fff;color:#1677ff;cursor:pointer;font-size:16px;line-height:18px}.os-stage-gap button:hover{border-color:#1677ff;background:#eff7ff}.os-insert-picker{display:grid;gap:8px}.os-insert-picker p{margin:0 0 4px;color:#6c7d90;font-size:13px}.os-insert-picker label{display:grid;grid-template-columns:20px minmax(0,1fr);gap:8px;align-items:start;border:1px solid #dbe5ef;border-radius:6px;padding:10px;cursor:pointer}.os-insert-picker label:hover{border-color:#8dbcf2;background:#f7fbff}.os-insert-picker b,.os-insert-picker small{display:block}.os-insert-picker b{color:#294763;font-size:13px}.os-insert-picker small{margin-top:4px;color:#708197;font-size:12px}.os-modal-form{display:grid;grid-template-columns:120px minmax(0,1fr);gap:12px;align-items:start}.os-modal-form label{color:#61748a;font-size:13px;text-align:right;padding-top:8px}.os-modal-form input,.os-modal-form textarea{width:100%;box-sizing:border-box;border:1px solid #cad7e5;border-radius:5px;padding:8px;font:inherit;font-size:13px}.os-modal-form input{height:34px;padding:0 9px}.os-modal-form textarea{min-height:88px;resize:vertical}.os-push-check-head{display:grid;grid-template-columns:1fr 1fr;gap:12px;padding:11px 12px;margin-bottom:10px;border:1px solid #dce6f1;border-radius:7px;background:#fbfdff;color:#52677d;font-size:13px}.os-push-check-head label{display:flex;align-items:center;gap:8px}.os-push-check-head select{height:30px;border:1px solid #cad7e5;border-radius:4px;background:#fff;color:#334155}.os-push-check-head span{padding-top:5px}.os-preview-field textarea,.os-preview-field select{margin-top:7px;width:100%;box-sizing:border-box;border:1px solid #d4deea;border-radius:4px;padding:6px;font:inherit}.os-preview-field textarea{height:66px}
@media(max-width:1180px){.os-layout{grid-template-columns:1fr}.os-nav{position:static;display:flex;overflow:auto}.os-nav button{min-width:132px}.os-field-grid,.os-dict-layout,.os-node-editor,.os-current-stage-grid{grid-template-columns:1fr}.os-pool-grid{grid-template-columns:1fr}.os-info-grid{grid-template-columns:118px 1fr}.os-info-grid .wide,.os-current-stage-grid label.full{grid-column:auto}.os-stepper{grid-template-columns:1fr 1fr}.os-dedupe-summary{grid-template-columns:1fr 1fr}.os-detail-meta{grid-template-columns:1fr 1fr}}
</style>
<script id="a5-faithful-module">
const faithfulProject=(()=>{
const esc=v=>String(v??'').replace(/[&<>"']/g,m=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m]));
const state={page:'project',mode:'list',screen:'list',selected:1,tab:'概览',query:'',stage:'全部阶段',group:'all',treeQuery:'',density:'default',splitCollapsed:false,wizard:1,wizardConflict:true,viewStage:0,demoStages:{8:6},moreFollow:false,projectSchemeExpanded:true};
const stages=['方案设计','商务报价','招投标准备','商务跟进','赢单/输单','结项'];
const detailStages=['冲突处理','方案设计','商务报价','招投标准备','商务跟进','赢单/输单','结项'];
const rows=[
{id:1,region:'广州市-番禺区',name:'番禺区第二中学报告厅改造',dept:'广东10部',sales:'陈亚',customer:'广东亚东科技公司',contact:'张总',phone:'186****2286',source:'番禺二中需要报告厅翻新采购',node:'冲突待处理',brand:'itc',amount:'56.8万元',bid:'—',owner:'报备专员-林欣怡',created:'2026-06-24 16:20:33',track:0,conflict:'本地冲突'},
{id:2,region:'广州市-番禺区',name:'番禺八中体育馆升级改造',dept:'广东10部',sales:'陈亚',customer:'番禺区第二中学',contact:'李主任',phone:'156****2665',source:'番禺八中体育馆要升级改造',node:'冲突待处理',brand:'sl',amount:'78.5万元',bid:'—',owner:'报备专员-林欣怡',created:'2026-06-24 16:30:04',track:0,conflict:'异地冲突'},
{id:3,region:'广州市-天河区',name:'天河区第一小学智慧教室改造',dept:'广东5部',sales:'刘阳',customer:'广州文教科技发展有限公司',contact:'李总',phone:'139****6721',source:'天河一小需采购智能黑板及录播系统',node:'方案设计',brand:'3a',amount:'45.0万元',bid:'—',owner:'方案设计师-黄文杰',created:'2026-06-20 10:28:16',track:0},
{id:4,region:'深圳市-南山区',name:'南山外国语学校机房改造',dept:'广东7部',sales:'吴迪',customer:'南山外国语学校',contact:'陈主任',phone:'138****5526',source:'校园机房扩容与网络改造',node:'商务报价',brand:'itc',amount:'360.0万元',bid:'—',owner:'报价专员-顾昕',created:'2026-06-18 09:36:08',track:1},
{id:5,region:'佛山市-南海区',name:'佛山南海职业学校机房翻新',dept:'广东6部',sales:'吴迪',customer:'建明科技',contact:'陈总',phone:'137****4408',source:'新校区弱电集成及安防部署',node:'招投标准备',brand:'itc',amount:'360.0万元',bid:'2026-07-30',owner:'项目负责人-赵敏',created:'2026-06-12 17:20:51',track:2},
{id:6,region:'广州市-海珠区',name:'海珠区医院会议系统采购',dept:'广东12部',sales:'李清',customer:'海珠区人民医院',contact:'周主任',phone:'135****7786',source:'医院多功能会议系统采购',node:'商务跟进',brand:'CRX',amount:'520.0万元',bid:'2026-08-12',owner:'销售经理-李清',created:'2026-06-08 13:52:07',track:3},
{id:7,region:'广州市-黄埔区',name:'黄埔区产业园多媒体升级',dept:'广东2部',sales:'钟情',customer:'黄埔产业园运营中心',contact:'王经理',phone:'188****3168',source:'产业园会议系统升级',node:'赢单/输单',brand:'itc',amount:'280.0万元',bid:'2026-07-12',owner:'项目负责人-钟情',created:'2026-05-17 09:30:44',track:5,conflict:'本地冲突',outcome:'赢单',resultRegistered:true,performanceRecognized:true},
{id:8,region:'广州市-越秀区',name:'越秀区图书馆智能化设备采购',dept:'广东1部',sales:'孙亮',customer:'越秀区图书馆',contact:'赵馆长',phone:'136****8088',source:'图书馆智能化设备采购',node:'结项',brand:'3a',amount:'72.0万元',bid:'2026-06-20',owner:'销售经理-孙亮',created:'2026-04-11 13:52:07',track:6,conflict:'异地冲突',outcome:'输单',resultRegistered:true,demoFlow:true}
];
const handles=page=>['project','myProject','projectStats'].includes(page);
const root=()=>document.getElementById(state.page==='myProject'?'fpMyProjectRoot':state.page==='projectStats'?'fpProjectStatsRoot':'fpProjectRoot');
function breadcrumb(detail){const bar=document.querySelector('.location-bar');if(!bar)return;const current=state.page==='myProject'?'我的项目':state.page==='projectStats'?'项目统计':'项目管理';bar.innerHTML='<span class="crumb-parent">项目管理</span><span class="crumb-sep">›</span>'+(detail?'<button class="crumb-btn" onclick="faithfulProject.back()">'+current+'</button><span class="crumb-sep">›</span><span class="crumb-current">'+esc(rows.find(x=>x.id===state.selected)?.name||'项目详情')+'</span>':'<span class="crumb-current">'+current+'</span>')}
function enter(page){state.page=page;state.mode='list';state.screen='list';state.tab='概览';if(page==='projectStats')renderStats();else renderList();breadcrumb(false)}
function icon(mode){return sharedViewModeIcon(mode)}
function viewTop(title){return '<div class="fp-view-top"><div class="fp-view-left"><div class="fp-mode-group">'+[['split','分屏视图'],['list','列表视图'],['board','看板视图']].map(x=>'<button class="fp-mode '+(state.mode===x[0]?'active':'')+'" title="'+x[1]+'" onclick="faithfulProject.setMode(\''+x[0]+'\')">'+icon(x[0])+'</button>').join('')+'</div><div class="fp-view-title">'+title+'</div></div></div>'}
function statsStrip(){const items=[['全部项目','385929'],['冲突待处理','1662'],['设计中','41566'],['报价中','20655'],['跟进/赢单/输单','56291/29658/16229']];return '<div class="fp-stat-strip"><div class="fp-stat-cards">'+items.map(x=>'<div class="fp-stat-card"><b>'+x[1]+'</b><span>'+x[0]+'</span></div>').join('')+'</div></div>'}
function toolbar(){const stageOptions=['全部阶段',...stages.slice(0,5)];return '<div class="fp-toolbar"><div class="fp-query"><input id="fpQuery" value="'+esc(state.query)+'" placeholder="搜索项目名称/销售经理/客户名称"><select id="fpStage">'+stageOptions.map(x=>'<option '+(state.stage===x?'selected':'')+'>'+x+'</option>').join('')+'</select><select><option>全部品牌</option><option>itc</option><option>sl</option><option>3a</option><option>CRX</option></select><button class="btn primary" onclick="faithfulProject.query()">查询</button><button class="btn" onclick="faithfulProject.reset()">重置</button></div><div class="fp-actions"><button class="btn primary" onclick="faithfulProject.newProject()">+ 新增项目</button><button class="opp-tool-icon" title="列表密度" onclick="faithfulProject.listTool(\'density\',event)">'+sharedListToolIcon('density')+'</button><button id="fpRefreshBtn" class="opp-tool-icon" title="刷新数据" onclick="faithfulProject.listTool(\'refresh\',event)">'+sharedListToolIcon('refresh')+'</button><button class="opp-tool-icon" title="自定义显示字段" onclick="faithfulProject.listTool(\'fields\',event)">'+sharedListToolIcon('fields')+'</button></div></div>'}
function groupMatch(r){if(state.group==='all'||state.group==='guangdong')return true;if(state.group==='guangzhou')return r.region.includes('广州市');if(state.group==='panyu')return r.region.includes('番禺区');if(state.group==='panyu-related')return [1,2].includes(r.id);if(state.group==='tianhe')return r.region.includes('天河区');if(state.group==='haizhu')return r.region.includes('海珠区');if(state.group==='huangpu')return r.region.includes('黄埔区');if(state.group==='yuexiu')return r.region.includes('越秀区');if(state.group==='shenzhen')return r.region.includes('深圳市');if(state.group==='foshan')return r.region.includes('佛山市');return true}
function filtered(){return rows.filter(r=>(state.page!=='myProject'||['陈亚','刘阳','李清'].includes(r.sales))&&(!state.query||[r.name,r.sales,r.customer].join('|').includes(state.query))&&(state.stage==='全部阶段'||r.node===state.stage)&&groupMatch(r))}
function tag(node){const cls=node.includes('冲突')?'red':node==='方案设计'?'blue':node==='商务报价'?'orange':node==='商务跟进'?'green':node==='结项'?'gray':'purple';return '<span class="fp-tag '+cls+'">'+node+'</span>'}
function listTable(data){return '<div class="fp-main-card fp-project-table-card"><div class="fp-table-wrap"><table class="fp-table fp-project-table fp-density-'+state.density+'"><colgroup><col style="width:64px"><col style="width:120px"><col style="width:240px"><col style="width:150px"><col style="width:220px"><col style="width:240px"><col style="width:110px"><col style="width:72px"><col style="width:110px"><col style="width:120px"><col style="width:170px"><col style="width:170px"><col style="width:120px"></colgroup><thead><tr><th class="fp-sticky-no">序号</th><th class="fp-sticky-region">地区</th><th class="fp-sticky-project">项目名称</th><th>销售经理</th><th>客户信息</th><th>来源商机</th><th>当前阶段</th><th>品牌</th><th class="num">项目金额</th><th>中标时间</th><th>当前负责人</th><th>创建时间</th><th class="fp-sticky-action">操作</th></tr></thead><tbody>'+data.map((r,i)=>'<tr><td class="fp-sticky-no">'+(i+1)+'</td><td class="fp-sticky-region" title="'+esc(r.region)+'">'+r.region+'</td><td class="fp-sticky-project" title="'+esc(r.name)+'"><button class="fp-link strong" onclick="faithfulProject.openDetail('+r.id+')">'+r.name+'</button></td><td>'+r.dept+'-'+r.sales+'</td><td>'+r.customer+'-'+r.contact+'<br><span class="fp-muted">'+r.phone+'</span></td><td title="'+esc(r.source)+'">'+esc(r.source)+'</td><td>'+tag(r.node)+'</td><td>'+r.brand+'</td><td class="num">'+r.amount+'</td><td>'+r.bid+'</td><td>'+r.owner+'</td><td>'+r.created+'</td><td class="fp-sticky-action"><button class="fp-link" onclick="faithfulProject.openDetail('+r.id+')">查看</button></td></tr>').join('')+'</tbody></table></div>'+pager(data.length)+'</div>'}
function projectTree(){const nodes=[['all','全部',0],['guangdong','广东省',1],['guangzhou','广州市',2],['panyu','番禺区',3],['panyu-related','番禺第二中学 / 番禺八中',4],['tianhe','天河区',3],['haizhu','海珠区',3],['huangpu','黄埔区',3],['yuexiu','越秀区',3],['shenzhen','深圳市',2],['foshan','佛山市',2]],q=state.treeQuery.trim();const visible=q?nodes.filter(x=>x[1].includes(q)):nodes;return '<aside class="fp-project-tree"><div class="fp-tree-search"><input value="'+esc(state.treeQuery)+'" placeholder="请输入" onkeydown="if(event.key===\'Enter\')faithfulProject.searchTree(this.value)"><button title="搜索关联项目" onclick="faithfulProject.searchTree(this.previousElementSibling.value)">⌕</button></div><div class="fp-tree-caption">关联项目 / 甲方项目</div><div class="fp-tree-list">'+visible.map(x=>'<button class="level-'+x[2]+' '+(state.group===x[0]?'active':'')+'" onclick="faithfulProject.setGroup(\''+x[0]+'\')"><span>'+(x[2]<4?'›':'·')+'</span>'+x[1]+'</button>').join('')+'</div></aside>'}
function quickStages(){const items=[['全部','全部阶段'],['冲突待处理','冲突待处理'],['方案设计','方案设计'],['商务报价','商务报价'],['招投标准备','招投标准备'],['商务跟进','商务跟进'],['赢单/输单','赢单/输单'],['已结项','结项']];return '<div class="fp-quick-stages">'+items.map(x=>'<button class="'+(state.stage===x[1]?'active':'')+'" onclick="faithfulProject.setStage(\''+x[1]+'\')">'+x[0]+'</button>').join('')+'</div>'}
function listWorkspace(data){return '<div class="fp-project-workspace">'+projectTree()+'<main class="fp-project-list-main">'+quickStages()+listTable(data)+'</main></div>'}
function setGroup(v){state.group=v;renderList()} function searchTree(v){state.treeQuery=v||'';renderList()} function setStage(v){state.stage=v;renderList()}
function listTool(type,e){e?.stopPropagation();if(type==='density'){const order=['default','compact','relaxed'],label={default:'标准',compact:'紧凑',relaxed:'宽松'},i=(order.indexOf(state.density)+1)%order.length;state.density=order[i];renderList();notice('列表密度已切换为'+label[state.density]+'(Demo)');return}if(type==='refresh'){const b=document.getElementById('fpRefreshBtn');b?.classList.add('refreshing');setTimeout(()=>{b?.classList.remove('refreshing');notice('项目列表已刷新(Demo)')},520);return}openModal('自定义显示字段',{body:'<div class="fp-check-grid"><label><input type="checkbox" checked disabled> 项目名称</label><label><input type="checkbox" checked> 销售经理</label><label><input type="checkbox" checked> 客户信息</label><label><input type="checkbox" checked> 来源商机</label><label><input type="checkbox" checked> 当前阶段</label><label><input type="checkbox" checked> 品牌</label><label><input type="checkbox" checked> 项目金额</label><label><input type="checkbox" checked> 当前负责人</label></div>',footer:'<button class="btn" onclick="closeModal()">取消</button><button class="btn primary" onclick="closeModal();faithfulProject.notice(\'显示字段已保存(Demo)\')">保存</button>'})}
function board(data){const groups=['冲突待处理','方案设计','商务报价','招投标准备','商务跟进','赢单/输单'];return '<div class="fp-main-card fp-board-wrap"><div class="fp-board">'+groups.map(g=>{const list=data.filter(r=>r.node===g);return '<section class="fp-lane"><header><div><b>'+g+'</b><small>¥'+list.reduce((n,r)=>n+parseFloat(r.amount),0)+'万</small></div><span>'+list.length+'</span></header><div class="fp-lane-body">'+list.map(r=>'<article class="fp-board-card" onclick="faithfulProject.openDetail('+r.id+')"><b title="'+esc(r.name)+'">'+r.name+'</b><p title="'+esc(r.customer)+'">'+r.customer+'</p><div class="fp-board-card-foot"><div class="fp-board-owner"><span title="'+esc(r.sales)+'">负责人:'+r.sales+'</span><span title="'+esc(r.dept)+'">负责部门:'+r.dept+'</span></div><div class="fp-board-card-right">'+tag(r.node)+'<strong>¥'+r.amount.replace('万元','万')+'</strong></div></div></article>').join('')+(list.length?'':'<div class="fp-empty">暂无项目</div>')+'</div></section>'}).join('')+'</div></div>'}
function split(data){const r=data.find(x=>x.id===state.selected)||data[0];if(r)state.selected=r.id;return '<div class="fp-main-card fp-split crm-unified-split '+(state.splitCollapsed?'collapsed':'')+'"><aside class="fp-split-left crm-unified-split-left"><div class="fp-split-list crm-unified-split-list"><div class="fp-split-table-head"><span>项目名称</span><span>阶段</span></div>'+data.map(x=>'<button class="fp-split-row '+(r&&x.id===r.id?'active':'')+'" onclick="faithfulProject.select('+x.id+')"><span class="fp-split-row-main"><b>'+x.name+'</b><small>'+x.customer+'</small></span><span class="fp-split-stage">'+x.node+'</span></button>').join('')+'</div></aside><div class="fp-split-divider crm-unified-split-divider"><button class="fp-split-toggle crm-unified-split-toggle" onclick="faithfulProject.toggleSplit()" aria-label="'+(state.splitCollapsed?'展开左侧列表':'收起左侧列表')+'">'+(state.splitCollapsed?'›':'‹')+'</button></div><main class="fp-split-detail crm-unified-split-detail">'+(r?detailMarkup(r,true):'<div class="fp-empty">暂无项目</div>')+'</main></div>'}
function pager(n){return '<div class="fp-pager"><span>共 '+n+' 条 · 20条/页</span><div><button disabled>‹</button><button class="active">1</button><button>2</button><button>3</button><button>›</button></div></div>'}
function renderList(){const title=state.page==='myProject'?'我的项目':'项目管理',data=filtered();root().innerHTML='<div class="fp-app"><div class="fp-list-shell">'+viewTop(title)+statsStrip()+toolbar()+(state.mode==='list'?listWorkspace(data):state.mode==='split'?split(data):board(data))+'</div></div>';breadcrumb(false)}
function stageIndex(r){const map={'冲突待处理':0,'方案设计':1,'商务报价':2,'招投标准备':3,'商务跟进':4,'赢单/输单':5,'结项':6};return state.demoStages[r.id]??map[r.node]??0}
function setMode(v){state.mode=v;renderList()} function select(id){state.selected=id;state.tab='概览';const r=rows.find(x=>x.id===id);state.viewStage=r?stageIndex(r):0;renderList()} function toggleSplit(){state.splitCollapsed=!state.splitCollapsed;renderList()} function query(){state.query=document.getElementById('fpQuery')?.value.trim()||'';state.stage=document.getElementById('fpStage')?.value||'全部阶段';renderList()} function reset(){state.query='';state.stage='全部阶段';state.group='all';state.treeQuery='';renderList()}
function openDetail(id){state.selected=id;state.screen='detail';state.tab='概览';const r=rows.find(x=>x.id===id);state.viewStage=r?stageIndex(r):0;renderDetail();breadcrumb(true)} function back(){state.screen='list';renderList();breadcrumb(false)}
function actionButton(label,type=label,cls='btn',disabled=false){return '<button class="'+cls+'" '+(disabled?'disabled title="结项项目仅可查看,不可新增或编辑"':'onclick="faithfulProject.action(\''+type+'\')"')+'>'+label+'</button>'}
function detailActions(r){const current=stageIndex(r),buttons=[];if(current===0){buttons.push(actionButton('编辑','编辑项目','btn primary'),actionButton('写跟进'),actionButton('一键拉群'),actionButton('阶段回退'),actionButton('暂停项目'),actionButton('终止项目'),actionButton('移交负责人'))}else if(current===1){buttons.push(actionButton('编辑','编辑项目','btn primary'),actionButton('写跟进'),actionButton('一键拉群'),actionButton('阶段回退'),actionButton('暂停项目'),actionButton('终止项目'),actionButton('移交负责人'))}else if(current===2){buttons.push(actionButton('编辑','编辑项目','btn primary'),actionButton('写跟进'),actionButton('一键拉群'),actionButton('阶段回退'),actionButton('暂停项目'),actionButton('终止项目'),actionButton('移交负责人'))}else if(current===3){buttons.push(actionButton('编辑','编辑项目','btn primary'),actionButton('写跟进'),actionButton('一键拉群'),actionButton('阶段回退'),actionButton('暂停项目'),actionButton('终止项目'),actionButton('移交负责人'))}else if(current===4){buttons.push(actionButton('写跟进','写跟进','btn primary'),actionButton('一键拉群'),actionButton('阶段回退'),actionButton('暂停项目'),actionButton('终止项目'),actionButton('移交负责人'))}else if(current===5){const locked=!!r.resultRegistered;buttons.push(actionButton('编辑','编辑项目','btn primary',locked),actionButton('写跟进','写跟进','btn',locked),actionButton('一键拉群','一键拉群','btn',locked))}else{buttons.push(actionButton('编辑','编辑项目','btn primary',true),actionButton('写跟进','写跟进','btn',true),actionButton('一键拉群','一键拉群','btn',true))}if(r.demoFlow)buttons.push(current===2?actionButton('恢复结项演示','恢复结项演示','btn demo'):actionButton('演示回退至商务报价','演示回退至商务报价','btn demo'));return '<div class="opp-detail-actions fp-detail-actions">'+buttons.join('')+'</div>'}
function projectDisplayTitle(r){return /项目$/.test(r.name)?r.name:r.name+'项目'}
function projectCode(r){const date=String(r.created||'2026-06-24').slice(0,10).replaceAll('-','');return 'FBB-280w改单-'+projectDisplayTitle(r)+'-'+date}
function projectCore(r){const contract={1:'268W',2:'186W',3:'45W',4:'360W',5:'360W',6:'520W',7:'280W',8:'72W'}[r.id]||r.amount.replace('万元','W'),principal=r.id===1?'番禺区第二中学':r.customer;return '<div class="fp-project-core"><div class="fp-project-core-fields"><div><span>关联客户:</span><button class="fp-link">'+r.customer+'</button></div><div><span>客户联系人:</span><button class="fp-link">'+r.contact+' '+r.phone.replaceAll('*','')+'</button></div><div><span>合同金额:</span><b>'+contract+'</b></div><div><span>来源商机:</span><button class="fp-link">'+r.source+'</button></div><div><span>甲方单位:</span><button class="fp-link">'+principal+'</button></div><div><span>项目类型:</span><b>改造项目</b></div><div><span>回款进度:</span><b class="fp-project-progress">0%</b></div><div class="fp-eco-card"><span>生态关系:</span><div>'+[['投资方',0],['设计院',0],['总包方',0],['同行',1],['潜在集成商',0],['招标代理',0]].map(x=>'<label><span>'+x[0]+'</span><b>'+x[1]+'</b></label>').join('')+'</div></div></div></div>'}
function detailHead(r,embedded=false){return '<div class="opp-detail-head fp-project-head"><div class="opp-detail-title"><div class="fp-project-title-line"><h1>'+projectDisplayTitle(r)+'</h1><span class="fp-project-code">'+projectCode(r)+'</span><span class="fp-project-owner">'+r.dept+' '+r.sales+'</span></div>'+(!embedded?'<button class="opp-detail-back" onclick="faithfulProject.back()">返回项目列表</button>':'')+'</div>'+projectCore(r)+detailActions(r)+track(r)+'</div>'}
function track(r){const current=stageIndex(r),view=Math.min(state.viewStage,current);return '<div class="opp-stage-track fp-project-stage-track" role="list" aria-label="项目阶段">'+detailStages.map((s,i)=>{const locked=i>current,cls=i<current?'done':i===current?'active':'',status=i===current?'当前':i===view?'查看中':'';return '<button class="opp-stage-node fp-stage-step '+cls+(i===view?' viewing':'')+(locked?' locked':'')+'" '+(locked?'disabled title="项目尚未进入该阶段"':'onclick="faithfulProject.viewStage('+i+')"')+' aria-current="'+(i===current?'step':'false')+'" role="listitem"><i>'+(i<current?'✓':String(i+1).padStart(2,'0'))+'</i><span class="fp-stage-label"><b>'+s+'</b>'+(status?'<em>'+status+'</em>':'')+'</span></button>'}).join('')+'</div>'}
function stageContext(r){const current=stageIndex(r),view=Math.min(state.viewStage,current);if(view===current)return '<div class="fp-stage-context current"><span>当前阶段:<b>'+detailStages[current]+'</b></span></div>';return '<div class="fp-stage-context history"><span>正在查看:<b>'+detailStages[view]+'</b><em>历史数据只读</em></span><button onclick="faithfulProject.returnCurrent()">返回当前阶段:'+detailStages[current]+'</button></div>'}
function tabList(){return state.viewStage===4?['概览','方案卡','报价清单','招投标文件','关联客户','群聊详情','合同与订单','项目附件','跟进记录']:['概览','方案卡','项目文件','关联客户','群聊详情','合同与订单','团队成员','跟进记录']}
function tabs(r,compact=false){const list=tabList(r);return '<div class="opp-detail-tabs fp-project-tabs '+(compact?'compact':'')+'">'+list.map(x=>'<button class="'+(state.tab===x?'active':'')+'" onclick="faithfulProject.tab(\''+x+'\')">'+x+'</button>').join('')+'</div>'}
function detailMarkup(r,embedded=false){return '<article class="opp-detail fp-project-detail'+(embedded?' fp-project-split-detail':'')+'">'+detailHead(r,embedded)+stageContext(r)+tabs(r)+'<div class="opp-detail-body fp-detail-body"><div class="opp-tab-panel active">'+tabContent(r)+'</div></div></article>'}
function renderDetail(){const r=rows.find(x=>x.id===state.selected)||rows[0];root().innerHTML='<div class="fp-app fp-detail-page">'+detailMarkup(r,false)+'</div>'}
function tab(v){state.tab=v;if(state.screen==='detail')renderDetail();else renderList()}
function viewStage(i){const r=rows.find(x=>x.id===state.selected)||rows[0],current=stageIndex(r);if(i>current){notice('项目尚未进入该阶段');return}state.viewStage=i;state.tab='概览';if(state.screen==='detail')renderDetail();else renderList()}
function returnCurrent(){const r=rows.find(x=>x.id===state.selected)||rows[0];state.viewStage=stageIndex(r);state.tab='概览';if(state.screen==='detail')renderDetail();else renderList()}
function tabContent(r){if(state.tab==='概览')return overview(r);if(state.tab==='方案卡')return schemeTab();if(state.tab==='项目文件')return projectFilesTab();if(state.tab==='报价清单')return quotationTab();if(state.tab==='招投标文件')return tenderFilesTab();if(state.tab==='项目附件')return attachmentsTab();if(state.tab==='关联客户')return customerTab(r);if(state.tab==='群聊详情')return chatTab(r);if(state.tab==='合同与订单')return contractTab();if(state.tab==='团队成员')return memberTab();return followTab()}
function overview(r){return '<div class="opp-overview fp-project-overview"><main>'+stageOverview(r)+'</main>'+dynamics(r)+'</div>'}
function section(title,body,actions=''){return '<section class="fp-section"><header><h3>'+title+'</h3>'+actions+'</header>'+body+'</section>'}
function kv(items){return '<div class="fp-kv">'+items.map(x=>'<span>'+x[0]+'</span><b>'+x[1]+'</b>').join('')+'</div>'}
function stageOverview(r){return [conflictOverview,designOverview,quoteOverview,tenderOverview,businessOverview,resultOverview,closeOverview][Math.min(state.viewStage,6)](r)}
function isCurrentStage(r,index){return stageIndex(r)===index&&state.viewStage===index}
function stageButton(label,type,cls='btn',enabled=true){return '<button class="'+cls+'" '+(enabled?'onclick="faithfulProject.action(\''+type+'\')"':'disabled title="历史阶段数据只读"')+'>'+label+'</button>'}
function projectFile(r,version){return projectDisplayTitle(r)+version}
function fileLines(items){return '<div class="fp-file-lines">'+items.map(x=>'<div><span>'+x[0]+'</span><span><button class="fp-link" onclick="faithfulProject.previewFile(\''+esc(x[0])+'\')">预览</button><button class="fp-link" onclick="faithfulProject.downloadFile(\''+esc(x[0])+'\')">下载</button></span></div>').join('')+'</div>'}
function downloadLines(items){return '<div class="fp-file-lines">'+items.map(x=>'<div><span>'+x+'</span><button class="fp-link" onclick="faithfulProject.downloadFile(\''+esc(x)+'\')">下载</button></div>').join('')+'</div>'}
function conflictOverview(r){
const remote=(r.conflict||'本地冲突')==='异地冲突',editable=isCurrentStage(r,0)&&!r.conflictResolved,name=projectDisplayTitle(r);
const dims=[
['甲方项目名称','模糊匹配','3方均匹配「'+name+'」','⚠ 冲突'],
['项目地点','精确匹配','3方均为「'+r.region.replace('-', ' · ')+'」','⚠ 冲突'],
...(remote?[['异地销售判定','区域精确匹配','销售区域与项目所在地不一致','异地判定']]:[]),
['品牌/型号','文本相似度',r.brand+'·TP系列与TS系列已形成差异','✓ 通过'],
['关键联系人','手机号/姓名','各方联系人手机号不一致','✓ 通过'],
['报备时间戳','时间排序','张琪最早报备,为第一设计方','⚠ 冲突'],
['招标文件编号','精确匹配','各方均未填写','未检测']
];
const summary=remote?'7项维度中3项冲突 · 2项通过 · 1项未检测 · 1项异地判定':'6项维度中3项冲突 · 2项通过 · 1项未检测';
const participants=[['👑 张琪 · 广东10部','2026-06-23 10:20','标前设计','itc·TP系列','FBB-20260623-0029','不动','第一设计方案'],['王芳 · 广东12部','2026-06-24 09:00','标前设计','itc·TS系列','FBB-20260624-0018','错开','错开品牌及30%架构'],[r.sales+' · '+r.dept,'2026-07-03 14:14','标前设计',r.brand+'·TS系列','FBB-20260703-0037','承认标底','A+标 80:20']];
return (r.conflictResolved?'<div class="fp-resolved-banner">冲突已处理 · 处理结论已提交,项目已恢复正常推进。</div>':'')+
section('冲突检测详情','<div class="fp-conflict-summary"><div><span>检测时间</span><b>2026-07-03 14:14:05</b></div><div><span>检测结论</span><b class="'+(r.conflictResolved?'success':'danger')+'">'+(r.conflictResolved?'已解决':'明确冲突')+'</b></div><div><span>冲突类型</span><b>'+(remote?'异地冲突':'本地冲突')+'</b></div><div><span>冲突总览</span><b>'+summary+'</b></div></div><div class="fp-deadline">⏱ '+(r.conflictResolved?'处理完成时间:2026-07-03 16:42:18 · 操作人:报备专员林欣怡':remote?'剩余3小时38分钟(异地方4小时内回复,超时默认同意)':'剩余22小时38分钟(明确冲突24小时内)')+'</div><h4>维度匹配明细</h4><div class="fp-table-wrap"><table class="fp-table compact"><thead><tr><th>维度</th><th>匹配方式</th><th>详情</th><th>结论</th></tr></thead><tbody>'+dims.map(x=>'<tr><td>'+x[0]+'</td><td>'+x[1]+'</td><td>'+x[2]+'</td><td>'+x[3]+'</td></tr>').join('')+'</tbody></table></div>')+
section('冲突处理操作','<div class="fp-pre-action"><b>前置操作:拉群沟通</b>'+stageButton('+ 拉群沟通','冲突拉群','btn primary',editable)+'<span>拉群后与所有冲突方沟通了解情况</span></div><h4>冲突方处理明细</h4><div class="fp-table-wrap"><table class="fp-table compact fp-conflict-party-table"><thead><tr><th>参与方</th><th>提交方案卡时间</th><th>类型</th><th>品牌/型号</th><th>方案卡编号</th><th>处理方式</th><th>参数配置</th></tr></thead><tbody>'+participants.map((x,i)=>'<tr><td>'+x[0]+'</td><td>'+x[1]+'</td><td>'+x[2]+'</td><td>'+x[3]+'</td><td><button class="fp-link" onclick="faithfulProject.action(\'方案卡信息\')">'+x[4]+'</button></td><td><select '+(editable?'onchange="faithfulProject.conflictMethod(this)"':'disabled')+'><option '+(x[5]==='不动'?'selected':'')+'>不动</option><option '+(x[5]==='错开'?'selected':'')+'>错开</option><option '+(x[5]==='承认标底'?'selected':'')+'>承认标底</option><option '+(x[5]==='放弃'?'selected':'')+'>放弃</option></select></td><td><input '+(editable?'':'disabled')+' value="'+x[6]+'" data-parameter></td></tr>').join('')+'</tbody></table></div><div class="fp-ratio-tip">分配比例由发标后标底类型决定:A+ 80:20 / A- 60:40 / B+ 55:45 / B- 50:50 / C 30:70 / Y 50:50;H标、X标需提前申请。</div><div class="fp-form-actions"><button class="btn" '+(editable?'onclick="faithfulProject.saveConflictDraft()"':'disabled')+'>保存草稿</button><button class="btn primary" '+(editable?'onclick="faithfulProject.submitConflict()"':'disabled')+'>提交</button><button class="btn danger" '+(editable?'onclick="faithfulProject.action(\'撤销冲突\')"':'disabled')+'>撤销冲突</button></div>');
}
function stageTitle(step,name,tip=''){return '<div class="fp-stage-title"><b>阶段'+step+' · '+name+'</b>'+(tip?'<span>'+tip+'</span>':'')+'</div>'}
function designOverview(r){
const editable=isCurrentStage(r,1),currentFile=projectFile(r,'V4.0'),history=[projectFile(r,'V3.0'),projectFile(r,'V2.0'),projectFile(r,'V1.0')],materials=[projectDisplayTitle(r)+'点位图',projectDisplayTitle(r)+'建筑平面图',projectDisplayTitle(r)+'资质资料'];
return stageTitle(2,'方案设计')+
section('设计任务概况','<table class="fp-prototype-info"><tbody><tr><th>方案设计师</th><td>黄文杰</td><th>认领时间</th><td>2026-07-03 16:54:09</td></tr><tr><th>设计版本</th><td>V4.0(更新于07-08)</td><th>状态</th><td>待销售确认</td></tr><tr><th>备注</th><td colspan="3">冲突处理结果:错开品牌-'+r.brand+',错开系列型号TP,错开30%架构</td></tr></tbody></table>')+
section('确认方案清单','<div class="fp-confirm-entry">'+stageButton('确认方案清单','确认方案清单','btn primary',editable)+'<span>点击预览查看清单详情,确认通过后项目将推进至商务报价阶段。</span></div>')+
section('设计方案概况','<table class="fp-prototype-info fp-file-table"><tbody><tr><th>设备清单</th><td><button class="fp-link strong" onclick="faithfulProject.previewFile(\''+esc(currentFile)+'\')">'+currentFile+'</button></td><th>概算金额</th><td>380万</td></tr><tr><th>历史版本</th><td colspan="3">'+fileLines(history)+'</td></tr><tr><th>方案资料</th><td colspan="3">'+downloadLines(materials)+'</td></tr></tbody></table>')+
section('需求变更记录','<div class="fp-history"><p><time>2026-07-08 15:56:26</time>减少3套IP网络音箱</p><p><time>2026-07-05 13:22:34</time>取消一楼网络点位(缩减预算)</p><p><time>2026-07-04 11:04:34</time>广播系统从78广播改为88广播系列</p><p><time>2026-07-03 17:04:27</time>客户要求增加AI广播话筒</p></div>');
}
function quoteOverview(r){
const editable=isCurrentStage(r,2),currentFile=projectFile(r,'报价清单V4.0'),history=[projectFile(r,'报价清单V3.0'),projectFile(r,'报价清单V2.0'),projectFile(r,'报价清单V1.0')],materials=[projectDisplayTitle(r)+'点位图',projectDisplayTitle(r)+'建筑平面图',projectDisplayTitle(r)+'资质资料'];
return stageTitle(3,'商务报价')+
section('报价任务概况','<table class="fp-prototype-info"><tbody><tr><th>报价专员</th><td>陈燕</td><th>认领时间</th><td>2026-07-08 20:54:09</td></tr><tr><th>方案设计师</th><td>黄文杰</td><th>方案提交时间</th><td>2026-07-08 16:54:09</td></tr><tr><th>设计版本</th><td>V4.0(更新于07-08)</td><th>状态</th><td>报价中</td></tr></tbody></table>')+
section('确认报价清单','<div class="fp-confirm-entry">'+stageButton('确认报价清单','确认报价清单','btn primary',editable)+'<span>点击预览查看清单详情,确认通过后项目将推进至招投标准备阶段。</span></div>')+
section('设计方案概况','<table class="fp-prototype-info fp-file-table"><tbody><tr><th>报价清单</th><td><button class="fp-link strong" onclick="faithfulProject.previewFile(\''+esc(currentFile)+'\')">'+currentFile+'</button></td><th>报价总价</th><td>420万</td></tr><tr><th>历史版本</th><td colspan="3">'+fileLines(history)+'</td></tr><tr><th>方案资料</th><td colspan="3">'+downloadLines(materials)+'</td></tr></tbody></table>')+
section('需求变更记录','<div class="fp-history"><p><time>2026-07-08 15:56:26</time>减少3套IP网络音箱</p><p><time>2026-07-05 13:22:34</time>取消一楼网络点位(缩减预算)</p><p><time>2026-07-04 11:04:34</time>广播系统从78广播改为88广播系列</p><p><time>2026-07-03 17:04:27</time>客户要求增加AI广播话筒</p></div>');
}
function tenderOverview(r){
const editable=isCurrentStage(r,3),lock=editable?'':'disabled',block=(title,body)=>section(title,'<div class="fp-edit-grid fp-autosave-grid" onchange="faithfulProject.saveStageField(this)">'+body+'</div>');
return stageTitle(4,'招投标准备','投标前准备工作,完成后可推进至赢单/输单阶段。')+
'<div class="fp-stage-primary-actions">'+stageButton('📌 标记投标完成,推进至赢单/输单','标记投标完成','btn primary',editable)+stageButton('⏭ 无需招投标,直接推进','无需招投标','btn',editable)+'</div>'+
block('一、招标文件','<label>招标状态<select '+lock+'><option>进行中</option><option>未启动</option><option>已截标</option></select></label><label>预计发标<input '+lock+' type="date" value="2026-08-15"></label><label>投标截止<input '+lock+' type="date" value="2026-09-01"></label><label>招标文件<input '+lock+' type="file"><small class="fp-field-hint">未选择任何文件</small></label><label>采购方式<select '+lock+'><option>公开招标</option><option>邀请招标</option><option>竞争性谈判</option><option>单一来源</option></select></label><label>预算金额(万元)<input '+lock+' value="400.00"></label>')+
block('二、标底认定','<label>标底类型<select '+lock+' onchange="faithfulProject.bidBaseMode(this)"><option>发标后认定</option><option>发标前认定</option></select></label><label>认定时间<input class="fp-bid-dependent" disabled type="datetime-local"></label><label>分配比例<select class="fp-bid-dependent" disabled><option>50:50</option><option>60:40</option><option>80:20</option></select></label><label>认定人<input class="fp-bid-dependent" disabled value="系统自动记录"></label><div class="fp-wide fp-bid-help"><button type="button">标底类型说明</button><p>X标:全盘操控 H标:甲方关系 A+:80:20 A-:60:40 B+:55:45 B-:50:50 C:30:70 Y:50:50</p></div>')+
block('三、招标策略','<label>品牌控制<input value="'+r.brand+'品牌" readonly></label><label>参数控制<input '+lock+' placeholder="填写型号或技术要求"></label><label>商务条款<input '+lock+' placeholder="填写关键商务条款"></label><label>技术偏离<select '+lock+'><option>无偏离</option><option>正偏离</option><option>负偏离</option></select></label><label>资质要求<input '+lock+' placeholder="填写投标所需资质条件"></label><label>控标文件<input '+lock+' type="file"><small class="fp-field-hint">未选择任何文件</small></label>')+
block('四、投标公司','<label>投标主体<input value="'+r.customer+'" readonly></label><label>授权品牌<input value="'+r.brand+' | TP" readonly></label><label>投标保证金<input '+lock+' value="8万(已缴纳)"></label><label>投标文件<select '+lock+'><option>制作中</option><option>已完成</option><option>已提交</option></select></label><label>开标时间<input '+lock+' type="datetime-local" value="2026-09-10T09:30"></label><label>开标地点<input '+lock+' value="'+r.region.replace('-', '')+'政务中心"></label>');
}
function businessOverview(r){
const editable=isCurrentStage(r,4),lock=editable?'':'disabled',count=r.followAdded?16:15,latest=r.followAdded?'刚刚 电话沟通':'2026-07-06 线下拜访';
const extra=state.moreFollow?'<p><time>2026-07-04 11:04:34</time>现场演示TS系列会议系统,甲方认可效果。</p><p><time>2026-07-03 17:04:27</time>确认甲方预算调整,从250万增至268万。</p>':'';
return stageTitle(5,'商务跟进')+
section('跟进统计','<table class="fp-prototype-info"><tbody><tr><th>总跟进</th><td>'+count+'次</td><th>最近跟进</th><td>'+latest+'</td></tr><tr><th>跟进频率</th><td><input '+lock+' value="每周1次" onchange="faithfulProject.saveStageField(this)"></td><th>下次跟进</th><td><input '+lock+' type="date" value="2026-07-13" onchange="faithfulProject.saveStageField(this)"></td></tr></tbody></table>')+
section('跟进记录','<div class="fp-history">'+(r.followAdded?'<p><time>刚刚</time>新增跟进:已与'+r.contact+'确认下一步计划。</p>':'')+'<p><time>2026-07-06 10:30:02</time>与'+r.contact+'确认项目进度,设计院已出初稿,下周提交甲方审核。</p><p><time>2026-07-05 13:22:34</time>提交设计方案V1.2,甲方反馈需要增加图书馆灯光系统。</p>'+extra+'</div>','<button class="fp-link" onclick="faithfulProject.toggleMoreFollow()">'+(state.moreFollow?'收起':'查看更多')+'</button>')+
section('客情关系','<table class="fp-prototype-info"><tbody><tr><th>客户等级</th><td>重点客户</td><th>关系星级</th><td>★★★☆☆(3星)</td></tr><tr><th>关键人</th><td><input '+lock+' value="'+r.contact+'(决策人)" onchange="faithfulProject.saveStageField(this)"></td><th>关系状态</th><td>良好</td></tr></tbody></table>')+
section('资料收集','<div class="fp-material-grid">'+[['招标文件','待获取'],['设计图纸','已获取'],['资质文件','已备齐'],['投标授权','待申请']].map(x=>'<button '+lock+' data-name="'+x[0]+'" data-state="'+x[1]+'" onclick="faithfulProject.toggleMaterial(this)"><span>'+x[0]+'</span><b>'+x[1]+'</b></button>').join('')+'</div>')+
section('维护计划','<table class="fp-prototype-info"><tbody><tr><th>下次拜访</th><td><input '+lock+' type="date" value="2026-07-15" onchange="faithfulProject.saveStageField(this)"></td><th>拜访目标</th><td><input '+lock+' value="确认招标参数,争取品牌指定" onchange="faithfulProject.saveStageField(this)"></td></tr></tbody></table>');
}
function resultOverview(r){
const editable=isCurrentStage(r,5),registered=!!r.resultRegistered,result=registered?r.outcome:'待标记',openTime=r.openTime||'2026-09-10 09:30',openPlace=r.openPlace||r.region.replace('-', '')+'政务中心';
let body=stageTitle(6,'赢单/输单')+section('投标结果','<div class="fp-result-head">'+kv([['开标时间',openTime],['开标地点',openPlace],['中标方式',registered?(r.winMethod||'公开招标'):'待确认'],['中标单位',result==='赢单'?(r.winCompany||r.customer):result==='输单'?(r.competitorCompany||'竞品中标单位'):'待确认'],['中标金额',result==='赢单'?(r.winAmount||r.amount):'待确认']])+(registered?'<div class="fp-result-recorded">已标记'+result+'</div>':'<div class="fp-result-actions">'+stageButton('✔ 标记赢单','标记赢单','btn primary',editable)+stageButton('✖ 标记输单','标记输单','btn danger',editable)+'</div>')+'</div>');
if(result==='赢单'){
body+=section('合同与签约',kv([['合同金额',r.contractAmount||r.amount],['签约时间',r.signDate||'2026-09-20'],['签约状态',r.signStatus||'未签约'],['合同原件',r.contractFile||'已上传']]));
body+=section('业绩分配','<div class="fp-performance-summary">'+(r.performanceRecognized?'<table class="fp-prototype-info"><tbody><tr><th>张强</th><td>标底方 · '+r.dept+' · 60%</td></tr><tr><th>'+r.sales+'</th><td>参与方 · '+r.dept+' · 40%</td></tr><tr><th>认定状态</th><td>已锁定</td></tr></tbody></table>':'<div class="fp-callout">赢单信息已登记,待报备专员完成业绩认定。</div>'+stageButton('业绩认定','业绩认定','btn primary',editable))+'</div>');
}else if(result==='输单'){
body+=section('输单摘要',kv([['输单原因',r.lossReason||'竞品价格更低'],['竞品品牌',r.competitorBrand||'海康威视'],['竞品中标单位',r.competitorCompany||'深圳安科科技'],['我方报价',r.ourQuote||'380万'],['竞品报价',r.competitorQuote||'320万'],['失败分析',r.lossAnalysis||'竞品以低价策略切入,甲方对价格敏感度高,我方在品牌指定上未能形成有效壁垒。']]));
}else body+=section('标记说明','<div class="fp-callout">请选择“标记赢单”或“标记输单”,按原型填写对应结果信息。</div>');
return body;
}
function closeOverview(r){return '<div class="fp-readonly">只读归档 · 仅输单项目进入结项</div>'+stageTitle(7,'结项')+section('投标结果',kv([['开标时间',r.openTime||'2026-09-10 09:30'],['开标地点',r.openPlace||r.region.replace('-', '')+'政务中心'],['中标方式',r.winMethod||'公开招标'],['中标单位',r.competitorCompany||'竞品中标单位']]))+section('输单复盘',kv([['输单原因',r.lossReason||'竞品价格更低'],['竞品品牌',r.competitorBrand||'海康威视'],['竞品中标单位',r.competitorCompany||'深圳安科科技'],['我方报价',r.ourQuote||'380万'],['竞品报价',r.competitorQuote||'320万'],['失败分析',r.lossAnalysis||'竞品以低价策略切入,甲方对价格敏感度高,我方在品牌指定上未能形成有效壁垒。']]))}
function dynamics(r){const stage=Math.min(state.viewStage,6),sets=[
[[r.sales,'2026/07/03 14:14','提交方案卡后系统检测到冲突。','冲突检测'],['系统通知','2026/07/03 14:15','已通知冲突各方及报备专员处理。','系统通知'],['报备专员','2026/07/03 15:20','已发起冲突协调沟通。','拉群沟通']],
[[r.sales,'2026/06/25 15:00','现场调研完成,工程师已出概算方案。客户要求下周五提交完整方案书。','线下拜访'],['系统通知','2026/05/14 15:00','项目进入「方案设计」阶段,已通知方案主管分配工程师。','阶段变更'],[r.sales,'2026/05/12 10:30','拿到现场图纸,已提交报备。','电话拜访'],['系统通知','2026/05/10 09:18','方案设计任务已创建。','任务创建']],
[['黄文杰','2026/07/08 16:54','方案V4.0已提交,报价任务同步创建。','方案提交'],['陈燕','2026/07/08 20:54','已认领商务报价任务。','任务认领'],[r.sales,'2026/07/09 10:10','客户确认报价范围,等待最终清单。','电话拜访']],
[['系统通知','2026/07/10 09:00','项目进入「招投标准备」阶段。','阶段变更'],[r.sales,'2026/07/12 14:20','已获取招标计划,预计8月15日发标。','招标跟进'],['报备专员','2026/07/13 11:00','标底类型暂定发标后认定。','标底认定']],
[[r.sales,'2026/07/06 10:30','与'+r.contact+'确认项目进度,设计院已出初稿。','线下拜访'],[r.sales,'2026/07/05 13:22','提交设计方案V1.2,客户提出调整要求。','方案沟通'],['系统通知','2026/07/01 09:00','项目进入「商务跟进」阶段。','阶段变更']],
[['系统通知','2026/09/10 11:30','开标结果待项目负责人登记。','结果提醒'],[r.sales,'2026/09/10 09:30','已参加项目开标。','开标'],['报备专员','2026/09/09 18:00','投标资料复核完成。','资料复核']],
[['系统通知','2026/09/10 16:00','项目已标记输单并进入结项。','结项'],[r.sales,'2026/09/10 15:30','已完成输单原因及竞品信息登记。','输单复盘'],['系统通知','2026/09/10 15:31','项目全部资料已转为只读归档。','系统通知']]
],items=sets[stage];return '<aside class="fp-dynamics"><h3>项目动态</h3>'+items.map(x=>'<article><b>'+x[0]+'</b><time>'+x[1]+'</time><p>'+x[2]+'</p><span>'+x[3]+'</span></article>').join('')+'</aside>'}
function filePage(title,files,notes){return section(title,'<div class="fp-files">'+files.map((f,i)=>'<div><span>📄</span><b>'+f+'</b><small>'+notes[i]+'</small><button class="fp-link" onclick="faithfulProject.notice(\'已打开预览(Demo)\')">预览</button><button class="fp-link" onclick="faithfulProject.notice(\'已开始下载(Demo)\')">下载</button></div>').join('')+'</div>')}
function projectSchemeDetailTable(r){return '<table class="a3-table"><tbody><tr><th>客户名称</th><td>'+esc(r.customer)+'</td><th>客户类型</th><td>机电总包</td></tr><tr><th>战略VIP客户等级</th><td>--</td><th>已成交VIP客户等级</th><td>--</td></tr><tr><th>客户联系人</th><td>'+esc(r.contact)+'</td><th>客户联系电话</th><td>'+esc(r.phone)+' <span class="link">查看</span></td></tr><tr><th>项目阶段</th><td>标前改单</td><th>招标形式</th><td>GC(工程)</td></tr><tr><th>是否有看现场</th><td>是</td><th>是否与客户电话沟通</th><td>是</td></tr><tr><th>现场勘察资料</th><td>番禺二中现场勘察资料.rar</td><th>是否百万级项目</th><td>是</td></tr><tr><th>方案预算是否明确</th><td>是</td><th>方案预算金额</th><td>238.00 万元</td></tr><tr><th>预计成交概率</th><td>60%</td><th>预计投标时间</th><td>--</td></tr><tr><th>设计配置</th><td>按清单配置</td><th>品牌要求</th><td>'+esc(r.brand)+',TS系列</td></tr><tr><th>是否可换品牌</th><td>否</td><th>招标编号</th><td>--</td></tr><tr><th>标底方</th><td>--</td><td colspan="2"></td></tr><tr><th>客户需求描述</th><td colspan="3">根据现有清单匹配产品参数并提供调整建议。</td></tr><tr><th>业务需求</th><td colspan="3">中配,主要从参数基础上控制配置成本,支持后续报价。</td></tr><tr><th>对方要求的先进性及前瞻性的要求</th><td colspan="3">--</td></tr><tr><th>清单参数(是否是我司产品参数、工程师需要确认,是否有品牌?)</th><td colspan="3">是否是我司产品参数、工程师需要确认,是否有品牌。</td></tr><tr><th>清单参数是否能改</th><td colspan="3">可改,按深化方案处理。</td></tr><tr><th>客户特殊要求(补充说明、备注说明)</th><td colspan="3">--</td></tr><tr><th>方案卡填写时间</th><td>--</td><th>期望方案设计完成时间</th><td>--</td></tr><tr><th>创建人</th><td>--</td><th>创建时间</th><td>--</td></tr></tbody></table>'}
function schemeTab(){const r=currentRecord(),expanded=state.projectSchemeExpanded;return '<div class="a3-tab-header"><div class="a3-tab-header-title">方案卡</div><div class="a3-tab-header-actions"><button class="btn" onclick="faithfulProject.action(\'推送方案\')">推送方案</button></div></div><div class="a3-card-grid"><article class="a3-card"><div class="a3-card-head"><span>'+esc(r.customer)+'</span><span class="a3-status blue">当前方案卡</span></div><div class="a3-card-body"><div class="a3-kv"><label>项目阶段</label><span>标前设计</span><label>招标形式</label><span>GC(工程)</span><label>预算金额</label><span>238.00 万元</span><label>预计成交概率</label><span>60%</span><label>品牌要求</label><span>'+esc(r.brand)+',TS系列</span><label>设计配置</label><span>按清单配置</span></div><div style="margin-top:10px" class="a3-actions"><button onclick="faithfulProject.toggleProjectScheme()">'+(expanded?'收起详情':'展开详情')+'</button><button onclick="faithfulProject.action(\'编辑方案卡\')">编辑</button><button onclick="faithfulProject.action(\'推送方案\')">推送</button></div>'+(expanded?'<div style="margin-top:12px">'+projectSchemeDetailTable(r)+'</div>':'')+'</div></article></div>'}
function toggleProjectScheme(){state.projectSchemeExpanded=!state.projectSchemeExpanded;renderDetail()}
function fileRows(files){return '<div class="fp-files">'+files.map(x=>'<div><span>📄</span><b>'+x+'</b><small>项目文件</small><button class="fp-link" onclick="faithfulProject.notice(\'已打开预览(Demo)\')">预览</button><button class="fp-link" onclick="faithfulProject.notice(\'已开始下载(Demo)\')">下载</button></div>').join('')+'</div>'}
function projectFilesTab(){const r=currentRecord(),title=projectDisplayTitle(r),actions=name=>'<button class="fp-link" onclick="faithfulProject.previewFile(\''+esc(name)+'\')">预览</button><button class="fp-link" onclick="faithfulProject.downloadFile(\''+esc(name)+'\')">下载</button>',versions=['V3.0','V2.0','V1.0'];const versionLines=prefix=>versions.map(v=>{const name=title+prefix+v;return '<div><span>'+esc(name)+'</span><span>'+actions(name)+'</span></div>'}).join('');const materialLines=['点位图','建筑平面图','资质资料','勘查报告'].map(name=>'<div><span>'+esc(title+name)+'</span><span><button class="fp-link" onclick="faithfulProject.downloadFile(\''+esc(title+name)+'\')">下载</button></span></div>').join('');const single=(label,name)=>'<tr><th>'+label+'</th><td><div class="fp-file-lines"><div><span>'+esc(name)+'</span><span>'+actions(name)+'</span></div></div></td></tr>';return section('项目文件','<div class="fp-table-wrap"><table class="fp-prototype-info fp-file-table"><tbody><tr><th>报价清单</th><td><div class="fp-file-lines">'+versionLines('')+'</div></td></tr><tr><th>方案清单</th><td><div class="fp-file-lines">'+versionLines('')+'</div></td></tr><tr><th>方案资料</th><td><div class="fp-file-lines">'+materialLines+'</div></td></tr>'+single('招标文件',title+'V4.0')+single('投标文件',title+'V4.0')+single('控标文件',title+'V4.0')+'</tbody></table></div>')}
function quotationTab(){return section('报价清单',kv([['当前版本','报价清单V3.0'],['报价总价','420万'],['报价专员','陈燕'],['同步来源','场景平台']]))+filePage('历史报价版本',['番禺区第二中学报告厅报价清单V3.0','番禺区第二中学报告厅报价清单V2.0','番禺区第二中学报告厅报价清单V1.0'],['当前版本','历史版本','历史版本'])}
function tenderFilesTab(){return section('招投标文件',fileRows(['番禺区第二中学报告厅招标文件','番禺区第二中学报告厅控标文件','番禺区第二中学报告厅投标文件']))}
function attachmentsTab(){return section('项目附件',fileRows(['现场勘察资料.rar','客户资质资料.zip','会议纪要-20260706.docx','客户需求补充说明.pdf']))}
function customerTab(r){return section('关联客户','<div class="fp-table-wrap"><table class="fp-table compact"><thead><tr><th>客户名称</th><th>客户类型</th><th>客户联系人</th><th>联系人职务</th><th>联系方式</th><th>操作</th></tr></thead><tbody><tr><td>'+r.customer+'</td><td>机电总包</td><td>'+r.contact+'</td><td>招商主任</td><td>'+r.phone+'</td><td><button class="fp-link">查看</button></td></tr><tr><td>广东轨交通信工程有限公司</td><td>意向客户</td><td>小马</td><td>业务代表</td><td>138****1212</td><td><button class="fp-link">查看</button></td></tr></tbody></table></div>')+section('项目关联信息',kv([['战略VIP客户等级','—'],['已成交VIP客户等级','—'],['项目阶段','标前改单'],['招标形式','GC(工程)'],['是否看现场','是'],['是否电话沟通','是'],['项目预算','238.00万元'],['品牌要求','itc,TS系列']]))}
function chatTab(r){return section('群聊详情','<div class="fp-chat-list"><article><b>群聊1</b>'+kv([['群名称','项目冲突处理群·番禺二中'],['群号','20260624-003'],['群成员','张琪、王芳、陈亚、王秀芬、李国锋'],['创建时间','2026-06-24 14:52']])+'</article><article><b>群聊2</b>'+kv([['群名称','GC-设计-'+r.name+'-'+r.sales],['群号','DING-2026070832174'],['群成员','陈亚、王秀芬、李国锋、黄文杰、顾昕'],['创建时间','2026-07-08 10:30']])+'</article></div>')}
function contractTab(){return section('合同与订单','<div class="fp-table-wrap"><table class="fp-table compact"><thead><tr><th>合同名称</th><th>合同金额</th><th>签约方</th><th>签约时间</th><th>合同状态</th><th>合同原件</th><th>操作</th></tr></thead><tbody><tr><td>番禺二中报告厅改造项目合同</td><td>268.00万元</td><td>广东亚东科技公司</td><td>—</td><td>未签约</td><td>—</td><td><button class="fp-link">查看</button></td></tr></tbody></table></div>')}
function memberTab(){return section('团队成员','<div class="fp-table-wrap"><table class="fp-table compact"><thead><tr><th>序号</th><th>姓名</th><th>项目角色</th><th>所属部门</th><th>联系方式</th><th>加入时间</th><th>职责</th></tr></thead><tbody>'+[['1','张强','第一次设计方','广东10部','139****5521','2026-06-23','最早报备人、标底方'],['2','陈亚','参与方','广东10部','186****2268','2026-06-24','冲突方、销售跟进'],['3','黄文杰','方案设计师','方案部','—','2026-07-03','场景平台方案设计'],['4','张晓敏','商务专员','商务部','—','—','报价审核与审批'],['5','王秀芬','报备专员','风控部','—','2026-06-24','冲突处理、报备审核']].map(x=>'<tr>'+x.map(v=>'<td>'+v+'</td>').join('')+'</tr>').join('')+'</tbody></table></div>')}
function followTab(){return section('跟进记录','<div class="fp-follow-filter"><input placeholder="跟进人/内容"><select><option>全部跟进方式</option><option>电话拜访</option><option>线下拜访</option></select><button class="btn primary">搜索</button><button class="btn">重置</button></div><div class="fp-history"><p><time>2026-06-28 14:40:24</time>陈亚 · 广东10部 · 电话拜访<br>电话沟通年度合作续签事宜,客户表示内部审批流程进行中。</p><p><time>2026-06-26 09:30:17</time>陈亚 · 广东10部 · 线下拜访<br>参加客户供应商大会,了解下半年业务重点及战略方向。</p></div>','<button class="btn primary" onclick="faithfulProject.action(\'写跟进\')">写跟进</button>')}
function currentRecord(){return rows.find(x=>x.id===state.selected)||rows[0]}
function previewFile(name){openModal('文件预览',{classes:['opp-modal-wide','fp-prototype-modal'],body:'<div class="fp-file-preview"><h3>'+esc(name)+'</h3><p>文件来源:场景平台同步 · 当前为只读预览</p><div class="fp-file-preview-canvas">方案文件/清单内容预览区</div></div>',footer:'<button class="btn" onclick="closeModal()">关闭</button><button class="btn primary" onclick="faithfulProject.downloadFile(\''+esc(name)+'\')">下载</button>'})}
function downloadFile(name){notice(name+' 已加入下载任务(Demo)')}
function saveStageField(){notice('已自动保存(Demo)')}
function bidBaseMode(select){const wrap=select.closest('.fp-section'),enabled=select.value==='发标前认定';wrap?.querySelectorAll('.fp-bid-dependent').forEach(x=>{x.disabled=!enabled});notice(enabled?'已切换为发标前认定,可填写认定信息':'已切换为发标后认定,认定信息已锁定')}
function toggleMaterial(button){const pairs={待获取:'已获取',已获取:'待获取',待备齐:'已备齐',已备齐:'待备齐',待申请:'已申请',已申请:'待申请'},next=pairs[button.dataset.state]||'已获取';button.dataset.state=next;button.querySelector('b').textContent=next;notice(button.dataset.name+'已更新为'+next)}
function toggleMoreFollow(){state.moreFollow=!state.moreFollow;renderDetail()}
function conflictMethod(select){const input=select.closest('tr')?.querySelector('[data-parameter]'),config={不动:'第一设计方案',错开:'请填写架构调整说明',承认标底:'请填写分成比例(1~99)',放弃:'请填写放弃原因'};if(input){input.value='';input.placeholder=config[select.value]||''}}
function saveConflictDraft(){notice('冲突处理草稿已保存(Demo)')}
function moveToStage(index,message){const r=currentRecord();if(!r)return;r.node=detailStages[index];r.track=index;if(!r.demoFlow)delete state.demoStages[r.id];else state.demoStages[r.id]=index;state.viewStage=index;state.tab='概览';closeModal();renderDetail();notice(message||'项目已推进至'+detailStages[index])}
function submitConflict(){const r=currentRecord();r.conflictResolved=true;moveToStage(1,'冲突处理已提交,项目进入方案设计')}
function revokeConflict(){const r=currentRecord();r.conflictResolved=false;r.node='冲突待处理';r.track=0;delete state.demoStages[r.id];state.viewStage=0;closeModal();renderDetail();notice('冲突判定已撤销,项目恢复冲突待处理状态')}
function submitFollow(){const r=currentRecord();r.followAdded=true;closeModal();renderDetail();notice('跟进记录已新增,统计数据已更新')}
function submitWin(){const r=currentRecord(),value=id=>document.getElementById(id)?.value||'';r.outcome='赢单';r.resultRegistered=true;r.performanceRecognized=false;r.winAmount=(value('fpWinAmount')||r.amount.replace('万元',''))+'万';r.openTime=(value('fpWinTime')||'2026-09-10')+' 09:30';r.winCompany=value('fpWinCompany')||r.customer;r.winMethod=value('fpWinMethod')||'公开招标';r.contractAmount=r.amount;r.signStatus='未签约';r.contractFile='已上传';r.node='赢单/输单';r.track=5;delete state.demoStages[r.id];state.viewStage=5;closeModal();renderDetail();notice('赢单信息已登记,请继续完成业绩认定')}
function confirmPerformance(){const r=currentRecord();r.performanceRecognized=true;closeModal();renderDetail();notice('业绩认定已完成并锁定;赢单项目不进入结项')}
function submitLoss(){const r=currentRecord(),value=id=>document.getElementById(id)?.value||'';r.outcome='输单';r.resultRegistered=true;r.lossReason=value('fpLossReason')||value('fpLossOther')||'价格原因';r.competitorBrand=value('fpCompetitorBrand')||'海康威视';r.competitorCompany=value('fpCompetitorCompany')||'深圳安科科技';r.ourQuote=(value('fpOurQuote')||'380')+'万';r.competitorQuote=(value('fpCompetitorQuote')||'320')+'万';r.lossAnalysis=value('fpLossAnalysis')||'竞品以低价策略切入,甲方对价格敏感度高,我方在品牌指定上未能形成有效壁垒。';r.node='结项';r.track=6;if(r.demoFlow)state.demoStages[r.id]=6;else delete state.demoStages[r.id];state.viewStage=6;closeModal();renderDetail();notice('输单信息已登记,项目已直接结项')}
function demoStage(index){const r=rows.find(x=>x.id===state.selected);if(!r||!r.demoFlow)return;state.demoStages[r.id]=index;r.node=index===2?'商务报价':'结项';r.track=index;state.viewStage=index;state.tab='概览';closeModal();if(state.screen==='detail')renderDetail();else renderList();notice(index===2?'已回退至 Step03 商务报价;Step04—Step07 已锁定':'已恢复结项演示;全阶段均可点击查看')}
function action(type){const r=rows.find(x=>x.id===state.selected)||rows[0];let title=type,body='<div class="fp-modal-form"><label>操作类型</label><input value="'+type+'" disabled><label>*详细说明</label><textarea placeholder="请输入说明"></textarea></div>',footer='<button class="btn" onclick="closeModal()">取消</button><button class="btn primary" onclick="closeModal();faithfulProject.notice(\''+type+'已完成(Demo)\')">确定</button>';
if(type==='演示回退至商务报价'){title='演示状态切换';body='<div class="fp-note">此操作仅服务演示:项目当前为“结项”,确认后回退至 Step03 商务报价。Step01—Step03 可点击,Step04—Step07 锁定不可进入;历史数据不会删除。</div>';footer='<button class="btn" onclick="closeModal()">取消</button><button class="btn primary" onclick="faithfulProject.demoStage(2)">确认回退</button>'}
else if(type==='恢复结项演示'){title='恢复结项演示';body='<div class="fp-note">恢复后项目回到 Step07 结项,所有阶段节点均可点击回看。</div>';footer='<button class="btn" onclick="closeModal()">取消</button><button class="btn primary" onclick="faithfulProject.demoStage(6)">恢复结项</button>'}
else if(type==='编辑项目'){title='编辑项目信息';body='<div class="fp-modal-form fp-edit-project-modal"><label>项目名称</label><input value="'+projectDisplayTitle(r)+'"><label>项目编号</label><input value="'+projectCode(r)+'" disabled><label>项目负责人</label><div><input value="'+r.sales+'('+r.dept+')"><button class="fp-link">移交负责人</button></div><label>关联客户</label><input value="'+r.customer+'"><label>客户联系人</label><input value="'+r.contact+' '+r.phone+'"><label>甲方单位</label><input value="'+r.customer+'"><label>合同金额</label><input value="'+r.amount+'"><label>回款进度</label><input value="0%"><label>来源商机</label><input value="'+r.source+'"><label>关联方信息</label><div class="fp-eco-edit-grid">'+['投资方','设计院','总包方','同行(竞品)','潜在集成商','招标代理'].map(x=>'<label>'+x+'<input placeholder="请输入"></label>').join('')+'</div></div>'}
else if(type==='写跟进'){body='<div class="fp-follow-modal"><div class="fp-follow-main"><div class="fp-follow-content-head"><b>*跟进内容</b><span>关联项目-'+projectDisplayTitle(r)+'</span></div><textarea placeholder="请输入本次跟进内容,例如:沟通内容、产品、客户反馈、关键结论、总结思考、下一步计划等,尽量简洁。"></textarea><div class="fp-follow-fields"><label>跟进时间<input type="datetime-local" value="2026-07-22T10:30"></label><label>*跟进方式<select><option>电话沟通</option><option>线下拜访</option><option>线上沟通</option></select></label><label>*客户名称<select><option>'+r.customer+'</option></select></label><label>客户联系人<span class="fp-input-search"><input value="'+r.contact+'"><button type="button">搜索</button></span></label><label>客户联系电话<input value="'+r.phone+'" disabled></label><label>跟进结果标签<select><option>关系维护</option><option>需求确认</option><option>商务推进</option></select></label><label>计划下次跟进时间<input type="datetime-local"></label></div><div class="fp-upload"><b>附件</b><div>点击或拖拽文件至该处上传<small>支持 txt、doc、docx、jpg、png、jpeg、xls、xlsx、ppt、pptx、pdf、mp3、mp4、m4a、aac;最多10个附件。</small></div></div></div></div>';footer='<button class="btn" onclick="closeModal()">取消</button><button class="btn primary" onclick="faithfulProject.submitFollow()">确认</button>'}
else if(type==='冲突拉群'){title='拉群沟通';body='<div class="fp-group-modal"><p>系统将创建冲突协调群,并自动拉入本次冲突的全部参与方:</p><div class="fp-group-members"><label><input type="checkbox" checked> 张琪(广东10部 · 第一设计方)</label><label><input type="checkbox" checked> 王芳(广东12部 · 冲突方)</label><label><input type="checkbox" checked> '+r.sales+'('+r.dept+' · 当前项目)</label><label><input type="checkbox" checked> 林欣怡(报备专员)</label></div><p class="fp-note">拉群后与所有冲突方沟通处理方式:不动、错开、承认标底或放弃。</p></div>'}
else if(type==='一键拉群'||type==='拉群沟通'){title=type;body='<div class="fp-group-modal"><p>系统将自动创建项目沟通群,默认拉入以下项目相关人员:</p><div class="fp-group-members"><label><input type="checkbox" checked> '+r.sales+'(销售经理)</label><label><input type="checkbox" checked> 李峰(销售总监)</label><label><input type="checkbox" checked> 黄文杰(方案设计)</label><label><input type="checkbox" checked> 王娟(报备专员)</label><label><input type="checkbox" checked> 陈燕(商务专员)</label></div><h4>补充成员(可选)</h4><input class="fp-member-search" value="陆" placeholder="输入成员姓名搜索"><div class="fp-member-options"><label><input type="checkbox"> 陆惠娟 特战团队-魔都雷霆队-上海1部</label><label><input type="checkbox"> 陆楚 冠军团队-桂冠天夏战队-天津2部</label><label><input type="checkbox"> 陆行 冠军团队-桂冠天夏战队-天津1部</label><label><input type="checkbox"> 陆兴文 设计部-方案设计7组</label><label><input type="checkbox"> 陆海霞 商务部-商务一组</label></div></div>'}
else if(type==='方案卡信息'){body='<div class="fp-modal-form"><label>方案卡编号</label><input value="FBB-20260623-0029" disabled><label>参与方</label><input value="张琪 · 广东10部" disabled><label>类型</label><input value="标前设计" disabled><label>品牌/型号</label><input value="itc · TP系列" disabled><label>先进性及前瞻性要求</label><textarea disabled>根据甲方场景补充最新会议与广播系统方案。</textarea><label>清单参数</label><textarea disabled>工程师确认我司产品参数及品牌要求。</textarea><label>清单参数是否能改</label><input value="需冲突各方协商确认" disabled><label>客户特殊要求</label><textarea disabled>重点关注报告厅扩声、录播与AI广播联动。</textarea></div>'}
else if(type==='暂停项目'){body='<div class="fp-note">⚠ 暂停后,项目将冻结在当前阶段,所有待办任务暂停计时,可随时恢复。</div><div class="fp-modal-form"><label>*暂停原因</label><select><option>客户计划调整</option><option>预算暂缓</option><option>招标延期</option><option>其他</option></select><label>*详细说明</label><textarea></textarea><label>*预计恢复时间</label><input type="date"></div>'}
else if(type.startsWith('终止项目')){title='终止项目';body='<div class="fp-readonly">⚠ 警告:终止后项目将终止推进,相关操作与待办任务将自动关闭。此操作不可逆!</div><div class="fp-modal-form"><label>*暂停原因</label><select><option>客户取消项目</option><option>重复项目</option><option>无继续推进价值</option><option>其他</option></select><label>*详细说明</label><textarea></textarea></div><div class="fp-check-list"><label><input type="checkbox" checked> 通知所有项目成员</label><label><input type="checkbox" checked> 自动关闭关联待办</label><label><input type="checkbox" checked> 归档项目文件</label></div>'}
else if(type==='阶段回退'){body='<div class="fp-note">⚠ 回退后当前阶段的数据不会丢失,但需要重新推进才能回到此阶段。</div><div class="fp-modal-form"><label>*回退至阶段</label><select>'+detailStages.slice(0,Math.max(1,stageIndex(r))).map(x=>'<option>'+x+'</option>').join('')+'</select><label>*暂停原因</label><select><option>客户需求变更</option><option>方案需要重做</option><option>报价条件变化</option><option>数据填写错误</option><option>其他</option></select><label>*详细说明</label><textarea></textarea></div>'}
else if(type==='确认方案清单'){title='设备清单';body='<div class="fp-modal-source">数据来源:场景平台同步 · 只读</div><div class="fp-table-wrap"><table class="fp-table compact"><thead><tr><th>设备名称</th><th>品牌</th><th>型号</th><th>数量</th><th>单位</th><th>备注</th></tr></thead><tbody><tr><td>数字会议主机</td><td>'+r.brand+'</td><td>TS-0300M</td><td>2</td><td>台</td><td>报告厅主扩系统</td></tr><tr><td>专业功放</td><td>'+r.brand+'</td><td>TC-2500B</td><td>6</td><td>台</td><td>主扩及返听</td></tr><tr><td>网络音箱</td><td>'+r.brand+'</td><td>IP-6060</td><td>18</td><td>只</td><td>按V4.0方案配置</td></tr><tr><td>AI广播话筒</td><td>'+r.brand+'</td><td>TF-2026</td><td>3</td><td>套</td><td>客户新增需求</td></tr></tbody></table></div>';footer='<button class="btn" onclick="faithfulProject.action(\'反馈意见\')">反馈意见</button><button class="btn" onclick="closeModal()">取消</button><button class="btn primary" onclick="faithfulProject.action(\'确认方案通过\')">确认通过</button>'}
else if(type==='确认报价清单'){title='报价清单';body='<div class="fp-modal-source">数据来源:场景平台同步 · 只读</div><div class="fp-table-wrap"><table class="fp-table compact"><thead><tr><th>设备名称</th><th>品牌</th><th>型号</th><th>数量</th><th>单价</th><th>总价</th><th>折扣</th><th>最终报价</th></tr></thead><tbody><tr><td>数字会议主机</td><td>'+r.brand+'</td><td>TS-0300M</td><td>2</td><td>8.00万</td><td>16.00万</td><td>95%</td><td>15.20万</td></tr><tr><td>专业功放</td><td>'+r.brand+'</td><td>TC-2500B</td><td>6</td><td>4.20万</td><td>25.20万</td><td>92%</td><td>23.18万</td></tr><tr><td>网络音箱</td><td>'+r.brand+'</td><td>IP-6060</td><td>18</td><td>1.10万</td><td>19.80万</td><td>90%</td><td>17.82万</td></tr><tr><td colspan="7" style="text-align:right"><b>报价总价</b></td><td><b>420万</b></td></tr></tbody></table></div>';footer='<button class="btn" onclick="faithfulProject.action(\'反馈意见\')">反馈意见</button><button class="btn danger" onclick="faithfulProject.action(\'打回修改\')">打回修改</button><button class="btn" onclick="closeModal()">取消</button><button class="btn primary" onclick="faithfulProject.action(\'确认报价通过\')">确认通过</button>'}
else if(type==='反馈意见'){body='<div class="fp-modal-form"><label>*意见内容</label><textarea placeholder="请输入需要修改的内容"></textarea><label>附件</label><input type="file"><label>提交对象</label><input value="'+(state.viewStage===2?'陈燕(报价专员)':'黄文杰(方案设计师)')+'" disabled></div>'}
else if(type==='打回修改'){body='<div class="fp-note">确认后,清单将打回报价专员重新修改。</div><div class="fp-modal-form"><label>*打回原因</label><textarea placeholder="请输入打回原因"></textarea></div>'}
else if(type==='确认方案通过'){title='确认通过';body='<div class="fp-note">确认方案无误后,项目将推进至“商务报价”,并通知报价专员及相关项目成员。</div>';footer='<button class="btn" onclick="closeModal()">取消</button><button class="btn primary" onclick="faithfulProject.moveToStage(2,\'方案已确认,项目进入商务报价\')">确认通过</button>'}
else if(type==='确认报价通过'){title='确认通过';body='<div class="fp-note">确认报价无误后,项目将推进至“招投标准备”,并通知相关项目成员。</div>';footer='<button class="btn" onclick="closeModal()">取消</button><button class="btn primary" onclick="faithfulProject.moveToStage(3,\'报价已确认,项目进入招投标准备\')">确认通过</button>'}
else if(type==='标记投标完成'||type==='无需招投标'||type==='推进阶段'){title='阶段推进';body='<div class="fp-note">'+(type==='标记投标完成'?'确认投标工作已完成?推进后项目进入赢单/输单阶段,可标记中标结果。':type==='无需招投标'?'确认该项目无需招投标流程?推进后直接进入赢单/输单阶段,可填写签约信息。':'确认将项目推进至赢单/输单阶段?')+'</div>';footer='<button class="btn" onclick="closeModal()">取消</button><button class="btn primary" onclick="faithfulProject.moveToStage(5,\'项目已进入赢单/输单阶段\')">确定</button>'}
else if(type==='撤销冲突'||type==='撤回冲突'){body='<div class="fp-note">撤销冲突判定后,系统将解除冲突锁定状态,项目可正常推进。仅限报备专员操作。</div><div class="fp-modal-form"><label>*撤销原因</label><select><option>误判冲突</option><option>冲突方已撤回</option><option>项目事实发生变化</option></select><label>*详细说明</label><textarea></textarea></div>';footer='<button class="btn" onclick="closeModal()">取消</button><button class="btn primary" onclick="faithfulProject.revokeConflict()">确定</button>'}
else if(type==='标记赢单'){body='<div class="fp-win-note">🎉 恭喜!确认中标后,请填写以下信息:</div><div class="fp-modal-form"><label>*中标金额(万元)</label><input id="fpWinAmount" type="number" value="'+r.amount.replace('万元','')+'"><label>*中标时间</label><input id="fpWinTime" type="date" value="2026-09-10"><label>*中标单位</label><input id="fpWinCompany" value="'+r.customer+'"><label>*中标方式</label><select id="fpWinMethod"><option>公开招标</option><option>邀请招标</option><option>竞争性谈判</option><option>单一来源</option></select><label>*合同扫描件</label><input type="file" accept=".pdf,.png,.jpg,.jpeg"></div>';footer='<button class="btn" onclick="closeModal()">取消</button><button class="btn primary" onclick="faithfulProject.submitWin()">确定</button>'}
else if(type==='业绩认定'){title='业绩认定';body='<div class="fp-performance-modal"><p>报备专员根据招标文件内容认定标底类型,认定后分配比例自动确定。发标后不改变分配比例。</p><section><h4>标底类型速查</h4><table class="fp-table compact"><thead><tr><th>类型</th><th>判断标准</th><th>分配比例</th></tr></thead><tbody><tr><td>X标</td><td>全盘操控</td><td>打8折后分</td></tr><tr><td>H标</td><td>甲方关系≥2周</td><td>50:50</td></tr><tr><td>A+标</td><td>标底方主导</td><td>80:20</td></tr><tr><td>A-标</td><td>标底方主导</td><td>60:40</td></tr><tr><td>B+ / B-标</td><td>双方协同</td><td>55:45 / 50:50</td></tr><tr><td>C标</td><td>参与方主导</td><td>30:70</td></tr></tbody></table></section><label class="fp-performance-select">*认定标底类型<select><option>A-标(60:40)</option><option>A+标(80:20)</option><option>B+标(55:45)</option><option>B-标(50:50)</option><option>C标(30:70)</option></select></label><table class="fp-table compact"><thead><tr><th>人员</th><th>项目角色</th><th>所属部门</th><th>分配比例</th></tr></thead><tbody><tr><td>张强</td><td>标底方</td><td>'+r.dept+'</td><td>60%</td></tr><tr><td>'+r.sales+'</td><td>参与方</td><td>'+r.dept+'</td><td>40%</td></tr></tbody></table></div>';footer='<button class="btn" onclick="closeModal()">取消</button><button class="btn primary" onclick="faithfulProject.confirmPerformance()">确定</button>'}
else if(type==='标记输单'){body='<div class="fp-loss-note">很遗憾!请填写以下信息,以便复盘:</div><div class="fp-modal-form"><label>*输单原因</label><select id="fpLossReason"><option>价格原因</option><option>技术不满足</option><option>客户关系</option><option>竞品优势</option><option>预算取消</option><option>其他</option></select><label>补充原因</label><input id="fpLossOther" placeholder="可手动补充"><label>竞品品牌</label><input id="fpCompetitorBrand" value="海康威视"><label>竞品中标单位</label><input id="fpCompetitorCompany" value="深圳安科科技"><label>我方报价(万元)</label><input id="fpOurQuote" type="number" value="380"><label>竞品报价(万元)</label><input id="fpCompetitorQuote" type="number" value="320"><label>失败分析</label><textarea id="fpLossAnalysis">竞品以低价策略切入,甲方对价格敏感度高,我方在品牌指定上未能形成有效壁垒。</textarea></div>';footer='<button class="btn" onclick="closeModal()">取消</button><button class="btn primary" onclick="faithfulProject.submitLoss()">确定</button>'}
openModal(title,{classes:['opp-modal-wide','fp-prototype-modal'],body,footer})
}
function newProject(){state.wizard=1;wizard()} function wizard(){const step=state.wizard,head='<div class="fp-wizard-steps"><div class="'+(step===1?'active':'done')+'">1<span>填写项目基本信息<br>关联销售与客户</span></div><div class="'+(step===2?'active':step>2?'done':'')+'">2<span>以各种维度检测<br>是否有项目冲突</span></div><div class="'+(step===3?'active':'')+'">3<span>根据检测结果<br>新建项目节点</span></div></div>';let body=step===1?'<div class="fp-modal-form"><label>*所属地区</label><input value="浙江省 / 杭州市 / 西湖区"><label>*项目名称</label><input value="番禺二中报告厅改造项目"><label>*销售经理</label><input value="陈亚"><label>*关联客户</label><input value="番禺区第二中学"><label>*品牌</label><input value="itc / TP"><label>*项目金额(万元)</label><input value="268.00"></div>':step===2?'<div class="fp-conflict-result"><h3>查重发现疑似冲突项目</h3><p>冲突项目:番禺二中报告厅升级项目 创建时间:2026-06-17 10:37:41</p><p>第一设计人:张琪 所属部门:粤晋战队-广东14部</p><label><input type="radio" name="fpConflict" checked onchange="faithfulProject.wizardConflict(true)"> 有冲突情况</label> <label><input type="radio" name="fpConflict" onchange="faithfulProject.wizardConflict(false)"> 无冲突情况</label></div>':state.wizardConflict?'<div class="fp-success"><h3>项目创建成功!(触发冲突协调流程)</h3><p>✓ 项目已创建。</p><p>✓ 冲突协调群已创建。</p><p>✓ 冲突处理协助任务已创建。</p><p>🔔 项目节点当前为:冲突待处理。方案设计任务暂时挂起。</p></div>':'<div class="fp-success"><h3>项目创建成功!</h3><p>✓ 项目已创建。</p><p>✓ 项目群聊已创建。</p><p>✓ 场景平台方案设计任务已创建。</p><p>✓ 项目节点更新为:方案设计。</p></div>';openModal('新增项目',{classes:['opp-modal-wide'],body:head+body,footer:(step>1?'<button class="btn" onclick="faithfulProject.wizardBack()">上一步</button>':'')+'<button class="btn" onclick="closeModal()">取消</button><button class="btn primary" onclick="faithfulProject.wizardNext()">'+(step===3?'进入项目':'下一步')+'</button>'})}
function wizardNext(){if(state.wizard<3){state.wizard++;wizard()}else{closeModal();openDetail(state.wizardConflict?1:3)}} function wizardBack(){if(state.wizard>1){state.wizard--;wizard()}} function wizardConflict(v){state.wizardConflict=v}
function renderStats(){root().innerHTML='<div class="fp-app fp-stats-page"><div class="fp-page-head"><div><h1>项目统计</h1><p>公司全部项目的核心指标、阶段分布、转化漏斗和部门统计。</p></div></div><div class="fp-metric-grid">'+[['项目总数','385288','本月新增 +1265'],['赢单率','68.3%','同比 +2.1%'],['方案设计数','1662','环比 +8.6%'],['商务报价总额','3.85亿元','本月新增 +1265'],['待确认报价','288','实时统计'],['跟进中项目','380085','实时统计']].map(x=>'<div><span>'+x[0]+'</span><b>'+x[1]+'</b><small>'+x[2]+'</small></div>').join('')+'</div><div class="fp-stats-grid"><section class="fp-section"><header><h3>📋 项目阶段分布</h3><div><input type="date" value="2026-07-01"> ~ <input type="date" value="2026-07-31"></div></header><div class="fp-bars">'+[['冲突处理',266,18],['方案设计',1626,72],['商务报价',2063,92],['招投标准备',956,55],['赢单',956,55],['输单',203,14],['结项',222,16]].map(x=>'<div><span>'+x[0]+'</span><i><em style="width:'+x[2]+'%"></em></i><b>'+x[1]+'</b></div>').join('')+'</div></section><section class="fp-section"><header><h3>📋 方案转化统计</h3></header><div class="fp-table-wrap"><table class="fp-table compact"><thead><tr><th>转化环节</th><th>进入数</th><th>通过数</th><th>转化率</th></tr></thead><tbody>'+[['方案设计 → 商务报价','1,626','1,412','86.8%'],['商务报价 → 招投标准备','1,412','956','67.7%'],['招投标准备 → 投标','956','892','93.3%'],['投标 → 赢单','892','609','68.3%'],['投标 → 输单','892','203','22.8%']].map(x=>'<tr>'+x.map(v=>'<td>'+v+'</td>').join('')+'</tr>').join('')+'</tbody></table></div><div class="fp-insight">💡 转化洞察:方案设计到商务报价转化率最高;投标到赢单为68.3%,输单22.8%,剩余8.9%为流标/废标。</div></section><section class="fp-section fp-wide"><header><h3>📋 各部门项目统计</h3></header><div class="fp-table-wrap"><table class="fp-table compact"><thead><tr><th>部门</th><th>项目总数</th><th>进行中</th><th>赢单</th><th>输单</th><th>赢单率</th><th>合同金额</th></tr></thead><tbody>'+[['广东10部','1,286','760','438','88','83.3%','2.86亿'],['广东8部','1,024','612','356','72','83.2%','2.14亿'],['广东12部','876','524','298','65','82.1%','1.68亿'],['四川1部','654','398','218','52','80.7%','1.24亿'],['广东5部','512','306','168','38','81.6%','0.98亿'],['合计','7,528','4,492','2,576','604','81.0%','14.62亿']].map(x=>'<tr>'+x.map(v=>'<td>'+v+'</td>').join('')+'</tr>').join('')+'</tbody></table></div>'+pager(18)+'</section></div></div>';breadcrumb(false)}
function notice(msg){const old=document.getElementById('fpToast');if(old)old.remove();const n=document.createElement('div');n.id='fpToast';n.className='fp-toast';n.textContent=msg;document.body.appendChild(n);setTimeout(()=>n.classList.add('show'),10);setTimeout(()=>n.remove(),1800)}
return {handles,enter,setMode,select,toggleSplit,query,reset,setGroup,searchTree,setStage,listTool,openDetail,back,tab,viewStage,returnCurrent,demoStage,action,newProject,wizardNext,wizardBack,wizardConflict,notice,previewFile,downloadFile,saveStageField,bidBaseMode,toggleMaterial,toggleMoreFollow,toggleProjectScheme,conflictMethod,saveConflictDraft,submitConflict,revokeConflict,submitFollow,moveToStage,submitWin,submitLoss,confirmPerformance};
})();
</script>
<style id="a5-faithful-styles">
.fp-host{background:#f3f6fa;overflow:auto}.fp-app{min-height:100%;padding:18px 26px 26px;box-sizing:border-box;color:#172033;font-size:13px}.fp-list-shell{min-width:0}.fp-view-top{min-height:50px;display:flex;align-items:center;padding:8px 12px;box-sizing:border-box;background:#fff;border:1px solid #d6e0ec;border-radius:8px 8px 0 0}.fp-view-left,.fp-mode-group,.fp-actions,.fp-query,.fp-form-actions{display:flex;align-items:center;gap:8px}.fp-mode-group{gap:6px}.fp-mode{width:30px;height:30px;padding:0;border:1px solid #d7e2ee;border-radius:6px;background:#fff;color:#64748b;display:grid;place-items:center;cursor:pointer}.fp-mode svg{width:17px;height:17px;fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}.fp-mode:hover,.fp-mode.active{border-color:#bfdbfe;background:#eaf2ff;color:#1677ff}.fp-view-title{margin-left:2px;font-size:17px;font-weight:800}.fp-view-title span{font-size:11px;color:#64748b}.fp-stat-strip{display:flex;align-items:stretch;gap:10px;padding:10px 12px;background:#fff;border:1px solid #d6e0ec;border-top:0}.fp-stat-cards{display:grid;grid-template-columns:repeat(4,minmax(150px,1fr));gap:8px;flex:1}.fp-stat-card{border:1px solid #dce5ef;border-radius:5px;background:#fff;padding:9px 12px}.fp-stat-card b{display:block;font-size:20px;color:#173b64;line-height:1.1}.fp-stat-card span{display:block;margin-top:5px;color:#64748b;font-size:12px}.fp-conflict-entry{min-width:124px;border:1px solid #f0b6b2;border-radius:5px;background:#fff7f7;color:#d9363e;font-weight:700;cursor:pointer}.fp-conflict-entry em{display:inline-grid;place-items:center;min-width:20px;height:20px;margin-left:5px;border-radius:10px;background:#d9363e;color:#fff;font-style:normal;font-size:11px}.fp-toolbar{min-height:48px;display:flex;align-items:center;justify-content:space-between;gap:10px;padding:7px 12px;box-sizing:border-box;background:#f8fbff;border:1px solid #d6e0ec;border-top:0}.fp-query{min-width:0;flex:1}.fp-query input,.fp-query select,.fp-edit-grid input,.fp-edit-grid select,.fp-modal-form input,.fp-modal-form select,.fp-modal-form textarea,.fp-table input,.fp-table select{height:32px;box-sizing:border-box;border:1px solid #cfd9e5;border-radius:4px;background:#fff;padding:0 9px;font:inherit;color:#334155}.fp-query input{width:270px}.fp-query select{min-width:110px}.fp-main-card{background:#fff;border:1px solid #d6e0ec;border-top:0;border-radius:0 0 8px 8px;overflow:hidden}.fp-table-wrap{overflow:auto}.fp-table{width:100%;min-width:1380px;border-collapse:collapse;font-size:13px}.fp-table.compact{min-width:760px}.fp-table th{height:40px;background:#f7f9fc;border-bottom:1px solid #dce5ef;padding:0 10px;text-align:left;color:#5f7289;white-space:nowrap}.fp-table td{height:52px;border-bottom:1px solid #edf1f5;padding:7px 10px;color:#33475e;vertical-align:middle}.fp-table td:nth-child(6){max-width:170px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.fp-table .num{text-align:right;white-space:nowrap}.fp-link{border:0;background:transparent;color:#006fe8;padding:0 5px;cursor:pointer;font:inherit;white-space:nowrap}.fp-link.strong{font-weight:700}.fp-link.danger{color:#d9363e}.fp-muted{color:#94a3b8;font-size:12px}.fp-tag{display:inline-block;padding:3px 8px;border-radius:11px;white-space:nowrap;font-size:12px}.fp-tag.red{background:#fff0f0;color:#d9363e}.fp-tag.blue{background:#eaf3ff;color:#1677ff}.fp-tag.orange{background:#fff4e5;color:#b86d06}.fp-tag.green{background:#eaf8f0;color:#16824a}.fp-tag.purple{background:#f4efff;color:#7652b5}.fp-tag.gray{background:#eef1f5;color:#64748b}.fp-pager{height:44px;display:flex;align-items:center;justify-content:space-between;padding:0 12px;color:#64748b}.fp-pager button{min-width:28px;height:27px;margin-left:4px;border:1px solid #d5dfeb;background:#fff;border-radius:4px}.fp-pager button.active{background:#1677ff;border-color:#1677ff;color:#fff}.fp-board-wrap{overflow:auto}.fp-board{display:grid;grid-template-columns:repeat(5,minmax(245px,1fr));gap:10px;min-width:1300px;padding:12px;background:#f7f9fc}.fp-lane{border:1px solid #dce5ef;border-radius:7px;background:#f8fafc;overflow:hidden}.fp-lane>header{display:grid;grid-template-columns:1fr auto;gap:3px;padding:11px;background:#fff;border-bottom:1px solid #e5ebf2}.fp-lane>header small{grid-column:1/3;color:#64748b}.fp-lane article{margin:9px;padding:11px;border:1px solid #dbe5ef;border-radius:6px;background:#fff;cursor:pointer}.fp-lane article>b{display:block;color:#1f4f7c;margin-bottom:7px}.fp-lane article p{min-height:32px;margin:0 0 8px;color:#64748b}.fp-lane article div{display:flex;align-items:center;justify-content:space-between;gap:6px}.fp-lane article strong{display:block;margin-top:9px;color:#173b64}.fp-empty{padding:32px;text-align:center;color:#94a3b8}.fp-split{display:grid;grid-template-columns:330px minmax(720px,1fr);min-height:680px}.fp-split>aside{border-right:1px solid #dce5ef;overflow:auto}.fp-split>main{min-width:0;padding:14px;overflow:auto}.fp-split-title{height:40px;display:flex;align-items:center;padding:0 12px;background:#f8fafc;border-bottom:1px solid #e5ebf2;font-weight:700}.fp-split-item{padding:12px;border-bottom:1px solid #edf1f5;cursor:pointer}.fp-split-item.active{background:#eef6ff;box-shadow:inset 3px 0 #1677ff}.fp-split-item>b{display:block;margin-bottom:7px;color:#1f4f7c}.fp-split-item>div{display:flex;align-items:center;gap:8px;color:#64748b}.fp-split-item>div strong{margin-left:auto}.fp-split-item footer{display:flex;gap:9px;margin-top:9px}.fp-split-item footer button{border:0;background:transparent;color:#1677ff;padding:0;cursor:pointer}.fp-split-head h2{margin:0 0 8px;color:#173b64}.fp-split-head p{color:#64748b}.fp-split-overview{display:grid;grid-template-columns:minmax(0,1fr) 320px;gap:14px;padding-top:12px}.fp-detail-page{padding-top:14px}.fp-detail-head,.fp-track-shell,.fp-tabs,.fp-detail-body{max-width:1460px;margin:0 auto;background:#fff;border-left:1px solid #d6e0ec;border-right:1px solid #d6e0ec}.fp-detail-head{border-top:1px solid #d6e0ec;border-radius:8px 8px 0 0;padding:14px 16px 0}.fp-detail-title{display:flex;align-items:flex-start;gap:12px}.fp-detail-title h1{margin:0 0 6px;font-size:21px;color:#173b64}.fp-detail-title p{margin:0;color:#64748b}.fp-back{border:0;background:transparent;color:#1677ff;padding:4px 0;cursor:pointer}.fp-detail-actions{display:flex;justify-content:flex-end;gap:7px;margin-top:-36px;min-height:38px}.fp-facts{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));margin-top:12px;border-top:1px solid #e5ebf2}.fp-facts>div{min-width:0;padding:10px 12px;border-right:1px solid #edf1f5;border-bottom:1px solid #edf1f5}.fp-facts span{display:block;color:#94a3b8;font-size:11px}.fp-facts b{display:block;margin-top:5px;color:#33475e;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.fp-facts i{display:inline-block;margin-right:4px;padding:2px 6px;border-radius:9px;background:#eef5ff;color:#1677ff;font-style:normal;font-size:11px}.fp-track-shell{display:grid;grid-template-columns:88px minmax(0,1fr);align-items:center;padding:12px 16px;box-sizing:border-box}.fp-track-label{font-weight:700;color:#33475e}.fp-track{display:flex;align-items:flex-start;min-width:760px;overflow:auto}.fp-track-step{position:relative;display:flex;flex-direction:column;align-items:center;flex:1;min-width:125px;color:#94a3b8}.fp-track-step:before{content:'';position:absolute;left:-50%;right:50%;top:14px;height:3px;background:#dbe4ef}.fp-track-step:first-child:before{display:none}.fp-track-step span{position:relative;z-index:1;display:grid;place-items:center;width:28px;height:28px;border:2px solid #cbd5e1;border-radius:50%;background:#fff;font-weight:700}.fp-track-step b{margin-top:7px;font-size:12px;white-space:nowrap}.fp-track-step.done:before,.fp-track-step.done span{background:#5ea4ee;border-color:#5ea4ee;color:#fff}.fp-track-step.current:before{background:#5ea4ee}.fp-track-step.current span{border-color:#1677ff;background:#1677ff;color:#fff;box-shadow:0 0 0 4px #dcebff}.fp-track-step.current b{color:#1677ff}.fp-tabs{display:flex;overflow:auto;border-top:1px solid #e5ebf2;border-bottom:1px solid #d6e0ec;padding:0 12px}.fp-tabs button{height:42px;min-width:84px;border:0;border-bottom:2px solid transparent;background:transparent;color:#64748b;cursor:pointer}.fp-tabs button.active{border-bottom-color:#1677ff;color:#1677ff;font-weight:700}.fp-tabs.compact{margin:12px 0 0;border:1px solid #dce5ef}.fp-detail-body{min-height:520px;border-bottom:1px solid #d6e0ec;border-radius:0 0 8px 8px;padding:14px;box-sizing:border-box;background:#f8fafc}.fp-overview-grid{display:grid;grid-template-columns:minmax(0,1fr) 330px;gap:14px}.fp-overview-grid>main{min-width:0}.fp-section{margin-bottom:12px;border:1px solid #dce5ef;border-radius:7px;background:#fff;overflow:hidden}.fp-section>header{min-height:42px;display:flex;align-items:center;justify-content:space-between;gap:10px;padding:0 13px;background:#f8fafc;border-bottom:1px solid #e5ebf2}.fp-section h3{margin:0;font-size:14px;color:#33475e}.fp-section h4{margin:14px 13px 8px;color:#33475e}.fp-kv{display:grid;grid-template-columns:150px minmax(0,1fr) 150px minmax(0,1fr)}.fp-kv span,.fp-kv b{min-height:40px;display:flex;align-items:center;padding:7px 11px;box-sizing:border-box;border-bottom:1px solid #edf1f5}.fp-kv span{background:#fbfcfe;color:#64748b}.fp-kv b{font-weight:500;color:#33475e}.fp-callout,.fp-note,.fp-insight{margin:12px;padding:10px 12px;border-left:3px solid #1677ff;background:#f3f8ff;color:#52677d;line-height:1.6}.fp-form-actions{justify-content:flex-end;padding:0 12px 12px}.fp-history{padding:6px 12px}.fp-history p{margin:0;padding:10px 0;border-bottom:1px solid #edf1f5;color:#475569}.fp-history time{display:inline-block;min-width:150px;color:#94a3b8}.fp-dynamics{border:1px solid #dce5ef;border-radius:7px;background:#fff;padding:12px}.fp-dynamics h3{margin:0 0 10px}.fp-dynamics article{position:relative;margin-left:8px;padding:0 0 16px 16px;border-left:2px solid #bfdbfe}.fp-dynamics article:last-child{padding-bottom:0}.fp-dynamics article:before{content:'';position:absolute;left:-5px;top:2px;width:8px;height:8px;border-radius:50%;background:#1677ff}.fp-dynamics time{float:right;color:#94a3b8;font-size:11px}.fp-dynamics p{color:#64748b;line-height:1.55}.fp-dynamics span{font-size:11px;color:#1677ff}.fp-conflict-summary{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));padding:12px}.fp-conflict-summary div{padding:10px;border-right:1px solid #edf1f5}.fp-conflict-summary span{display:block;color:#94a3b8;font-size:11px}.fp-conflict-summary b{display:block;margin-top:5px}.fp-conflict-summary .danger{color:#d9363e}.fp-deadline{margin:0 12px 12px;padding:9px 11px;border:1px solid #f7c7c4;background:#fff7f7;color:#c2413d;border-radius:5px}.fp-section>.fp-table-wrap{margin:0 12px 12px;border:1px solid #e5ebf2}.fp-edit-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:11px;padding:12px}.fp-edit-grid label{display:grid;gap:5px;color:#64748b}.fp-edit-grid .fp-wide{grid-column:1/-1}.fp-check-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:9px;padding:12px}.fp-check-grid button{height:38px;border:1px solid #cfe0f5;border-radius:5px;background:#f7fbff;color:#356181}.fp-result-head{position:relative}.fp-result-actions{display:flex;gap:8px;position:absolute;right:12px;top:12px}.fp-readonly{margin-bottom:10px;padding:9px;border:1px solid #dbe4ef;background:#eef2f6;color:#64748b;text-align:center;border-radius:5px}.fp-files{display:grid;gap:0}.fp-files>div{display:grid;grid-template-columns:30px minmax(0,1fr) 220px 50px 50px;align-items:center;gap:8px;padding:11px 13px;border-bottom:1px solid #edf1f5}.fp-files small{color:#94a3b8}.fp-chat-head{display:flex;align-items:center;justify-content:space-between;padding:14px}.fp-chat-head p{color:#64748b}.fp-modal-form{display:grid;grid-template-columns:120px minmax(0,1fr);gap:11px;align-items:center}.fp-modal-form label{text-align:right;color:#64748b}.fp-modal-form textarea{height:76px;padding:8px}.fp-wizard-steps{display:flex;align-items:flex-start;margin-bottom:18px}.fp-wizard-steps>div{position:relative;flex:1;text-align:center;color:#94a3b8;font-weight:700}.fp-wizard-steps>div:before{content:attr(data-step);display:grid;place-items:center;width:28px;height:28px;margin:0 auto 7px;border:2px solid #cbd5e1;border-radius:50%;background:#fff}.fp-wizard-steps>div:after{content:'';position:absolute;left:50%;right:-50%;top:14px;height:2px;background:#dbe4ef;z-index:-1}.fp-wizard-steps>div:last-child:after{display:none}.fp-wizard-steps>div.active{color:#1677ff}.fp-wizard-steps>div.active:before,.fp-wizard-steps>div.done:before{border-color:#1677ff;background:#1677ff;color:#fff}.fp-wizard-steps span{display:block;font-weight:500;line-height:1.5}.fp-conflict-result,.fp-success{padding:14px;border:1px solid #dce5ef;border-radius:6px;background:#fbfdff}.fp-page-head{display:flex;align-items:flex-start;justify-content:space-between;margin-bottom:12px}.fp-page-head h1{margin:0;color:#173b64}.fp-page-head p{color:#64748b}.fp-metric-grid{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:10px;margin-bottom:12px}.fp-metric-grid>div{border:1px solid #dce5ef;border-radius:7px;background:#fff;padding:13px}.fp-metric-grid span,.fp-metric-grid small{display:block;color:#64748b}.fp-metric-grid b{display:block;margin:7px 0;font-size:22px;color:#173b64}.fp-stats-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}.fp-stats-grid .fp-section{margin:0}.fp-stats-grid .fp-wide{grid-column:1/-1}.fp-bars{padding:12px}.fp-bars>div{display:grid;grid-template-columns:100px minmax(0,1fr) 55px;align-items:center;gap:10px;margin:12px 0}.fp-bars i{height:12px;border-radius:6px;background:#edf2f7;overflow:hidden}.fp-bars em{display:block;height:100%;border-radius:6px;background:#5b9de2}.fp-toast{position:fixed;z-index:10000;left:50%;bottom:40px;transform:translate(-50%,12px);opacity:0;padding:10px 16px;border-radius:6px;background:rgba(15,23,42,.92);color:#fff;transition:.18s}.fp-toast.show{opacity:1;transform:translate(-50%,0)}
@media(max-width:1200px){.fp-stat-cards{grid-template-columns:repeat(2,minmax(150px,1fr))}.fp-toolbar{align-items:flex-start;flex-direction:column}.fp-split{grid-template-columns:300px minmax(680px,1fr)}.fp-overview-grid,.fp-split-overview{grid-template-columns:1fr}.fp-facts{grid-template-columns:repeat(2,minmax(0,1fr))}.fp-metric-grid{grid-template-columns:repeat(3,minmax(0,1fr))}.fp-stats-grid{grid-template-columns:1fr}.fp-stats-grid .fp-wide{grid-column:auto}}
@media(max-width:760px){.fp-app{padding:12px}.fp-stat-strip{align-items:stretch;flex-direction:column}.fp-stat-cards{grid-template-columns:1fr 1fr}.fp-query{align-items:stretch;flex-direction:column;width:100%}.fp-query input,.fp-query select{width:100%}.fp-detail-actions{margin:12px 0 0;justify-content:flex-start;flex-wrap:wrap}.fp-facts{grid-template-columns:1fr}.fp-track-shell{grid-template-columns:1fr;gap:8px;overflow:auto}.fp-kv{grid-template-columns:110px minmax(0,1fr)}.fp-edit-grid,.fp-check-grid{grid-template-columns:1fr}.fp-modal-form{grid-template-columns:1fr}.fp-modal-form label{text-align:left}.fp-metric-grid{grid-template-columns:1fr 1fr}}
.fp-app{padding:0}.fp-detail-page{padding-top:0}.fp-project-detail .fp-detail-actions{justify-content:flex-start;margin-top:10px;min-height:0}.fp-project-detail .fp-project-stage-track{grid-template-columns:repeat(6,minmax(0,1fr));min-width:840px;overflow:auto}.fp-project-detail .fp-detail-body{max-width:none;margin:0;border:0;border-radius:0;padding:12px 16px 20px;background:#fff}.fp-project-detail .fp-overview-grid{grid-template-columns:minmax(0,1.8fr) minmax(300px,.8fr);gap:18px}.fp-project-detail .fp-dynamics{border:0;border-left:1px solid #e5ebf2;border-radius:0;padding:0 0 0 18px}.fp-project-tabs.compact{margin:12px 0 0;border:1px solid #dce5ef}
@media(max-width:1180px){.fp-project-detail .fp-overview-grid{grid-template-columns:1fr}.fp-project-detail .fp-dynamics{border-left:0;border-top:1px solid #e5ebf2;padding:16px 0 0}}
@media(max-width:760px){.fp-app{padding:0}.fp-project-detail .fp-project-stage-track{min-width:760px}}
.fp-stat-cards{grid-template-columns:repeat(5,minmax(150px,1fr))}.fp-stat-card{min-width:0}.fp-stat-card b{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fp-project-workspace{display:grid;grid-template-columns:240px minmax(0,1fr);min-height:600px;background:#fff;border:1px solid #d6e0ec;border-top:0;border-radius:0 0 8px 8px;overflow:hidden}.fp-project-tree{min-width:0;border-right:1px solid #dce5ef;background:#f8fafc}.fp-tree-search{display:flex;align-items:center;gap:5px;padding:9px 8px 7px}.fp-tree-search input{width:100%;min-width:0;height:30px;box-sizing:border-box;border:1px solid #cfd9e5;border-radius:4px;background:#fff;padding:0 8px;font:inherit;color:#334155}.fp-tree-search button{flex:0 0 30px;width:30px;height:30px;border:1px solid #cfd9e5;border-radius:4px;background:#fff;color:#52677d;cursor:pointer}.fp-tree-caption{padding:5px 10px 8px;color:#7a899d;font-size:12px}.fp-tree-list{padding:0 5px 10px}.fp-tree-list button{display:flex;align-items:center;gap:4px;width:100%;min-height:34px;border:0;border-radius:4px;background:transparent;padding:5px 8px;text-align:left;color:#52677d;font:inherit;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.fp-tree-list button:hover{background:#f0f6ff;color:#1677ff}.fp-tree-list button.active{background:#e8f3ff;color:#1677ff;font-weight:700}.fp-tree-list button.level-1{padding-left:16px}.fp-tree-list button.level-2{padding-left:28px}.fp-tree-list button.level-3{padding-left:40px}.fp-tree-list button.level-4{padding-left:44px;font-size:12px}.fp-tree-list button span{flex:0 0 10px;color:#91a2b5}.fp-project-list-main{min-width:0;background:#fff}.fp-quick-stages{display:flex;align-items:center;gap:2px;height:42px;padding:0 8px;border-bottom:1px solid #dce5ef;overflow-x:auto;overflow-y:hidden}.fp-quick-stages button{flex:0 0 auto;height:30px;border:0;border-radius:4px;background:transparent;padding:0 13px;color:#52677d;font:inherit;cursor:pointer}.fp-quick-stages button:hover{background:#f0f6ff;color:#1677ff}.fp-quick-stages button.active{background:#1677ff;color:#fff}.fp-project-list-main .fp-project-table-card{border:0;border-radius:0}.fp-project-list-main .fp-project-table-card .fp-table-wrap{margin:0;border:0}.fp-project-table{width:1906px;min-width:1906px;table-layout:fixed}.fp-project-table th,.fp-project-table td{box-sizing:border-box;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fp-project-table .fp-link.strong{display:block;max-width:100%;overflow:hidden;text-overflow:ellipsis}.fp-project-table .fp-sticky-no,.fp-project-table .fp-sticky-region,.fp-project-table .fp-sticky-project,.fp-project-table .fp-sticky-action{position:sticky;background:#fff}.fp-project-table .fp-sticky-no{left:0;z-index:4}.fp-project-table .fp-sticky-region{left:64px;z-index:4}.fp-project-table .fp-sticky-project{left:184px;z-index:4;box-shadow:7px 0 10px -10px rgba(15,23,42,.72)}.fp-project-table .fp-sticky-action{right:0;z-index:4;width:120px;min-width:120px;max-width:120px;text-align:center;border-left:1px solid #dce5ef;box-shadow:-11px 0 16px -11px rgba(15,23,42,.62)}.fp-project-table .fp-sticky-action:before{content:"";position:absolute;left:-8px;top:0;bottom:0;width:8px;background:linear-gradient(90deg,transparent,rgba(15,23,42,.07));pointer-events:none}.fp-project-table thead .fp-sticky-no,.fp-project-table thead .fp-sticky-region,.fp-project-table thead .fp-sticky-project,.fp-project-table thead .fp-sticky-action{z-index:6;background:#f7f9fc}.fp-project-table tbody tr:hover td{background:#f8fbff}.fp-project-table tbody .fp-sticky-action:hover{background:#eef6ff!important}.fp-density-compact td{height:42px;padding-top:4px;padding-bottom:4px}.fp-density-relaxed td{height:62px;padding-top:10px;padding-bottom:10px}.opp-tool-icon.refreshing .shared-list-icon{animation:fp-list-refresh .52s linear}@keyframes fp-list-refresh{to{transform:rotate(360deg)}}
@media(max-width:1200px){.fp-stat-cards{grid-template-columns:repeat(3,minmax(150px,1fr))}.fp-project-workspace{grid-template-columns:220px minmax(0,1fr)}}
@media(max-width:760px){.fp-stat-cards{grid-template-columns:1fr 1fr}.fp-project-workspace{grid-template-columns:1fr}.fp-project-tree{max-height:260px;border-right:0;border-bottom:1px solid #dce5ef;overflow:auto}.fp-quick-stages{height:44px}}
/* 四大业务模块分屏外框统一:以商机分屏的 330px 左栏、9px 分隔轨和折叠方式为基准。 */
.crm-unified-split{height:100%;min-height:680px;display:grid!important;grid-template-columns:330px 9px minmax(0,1fr)!important;gap:0!important;padding:0!important;border:0!important;border-radius:0!important;background:#fff!important;overflow:hidden!important;transition:grid-template-columns .24s ease!important}.crm-unified-split.collapsed{grid-template-columns:0 18px minmax(0,1fr)!important}.crm-unified-split-left{min-width:0!important;min-height:0!important;border:0!important;background:#fff!important;overflow:hidden!important;opacity:1;transform:translateX(0);transition:opacity .18s ease,transform .24s ease!important}.crm-unified-split.collapsed .crm-unified-split-left{opacity:0!important;transform:translateX(-18px)!important;visibility:hidden!important;pointer-events:none!important}.crm-unified-split-list{height:100%;min-height:0;overflow:auto!important;background:#fff}.crm-unified-split-divider{position:relative!important;width:auto!important;min-width:0!important;border-left:1px solid #d8e1eb!important;border-right:1px solid #d8e1eb!important;border-top:0!important;border-bottom:0!important;background:#eef2f7!important;overflow:visible!important}.crm-unified-split-toggle{position:absolute!important;left:50%!important;top:50%!important;transform:translate(-50%,-50%)!important;width:18px!important;height:46px!important;padding:0!important;border:1px solid #cbd5e1!important;border-radius:0 6px 6px 0!important;background:#fff!important;color:#1677ff!important;cursor:pointer!important;z-index:5!important}.crm-unified-split-toggle:hover{border-color:#93c5fd!important;background:#eff6ff!important}.crm-unified-split-detail{min-width:0!important;min-height:0!important;border:0!important;border-radius:0!important;background:#fff!important;overflow:auto!important}.crm-unified-split.collapsed .crm-unified-split-divider{display:block!important}.crm-unified-split.collapsed .crm-unified-split-detail{grid-column:auto!important}.crm-customer-split.crm-unified-split>aside,.crm-customer-split.crm-unified-split>main,.lead2-split.crm-unified-split .lead2-split-left,.lead2-split.crm-unified-split .lead2-split-detail{border:0!important}.crm-customer-split.crm-unified-split>main{border-left:0!important}.lead2-split.crm-unified-split .lead2-split-detail{padding:0!important}
/* 项目分屏左栏忠实采用“项目名称 + 客户 / 阶段”结构,不放置列表内编辑类操作。 */
.fp-split-table-head{height:38px;position:sticky;top:0;z-index:3;display:grid;grid-template-columns:minmax(0,1fr) 86px;align-items:center;padding:0 12px;background:#f6f9fd;border-bottom:1px solid #dfe7f1;color:#475569;font-size:13px;font-weight:800}.fp-split-table-head span:last-child{text-align:right}.fp-split-row{width:100%;min-height:68px;display:grid;grid-template-columns:minmax(0,1fr) 86px;align-items:center;gap:8px;padding:9px 10px 9px 12px;border:0;border-bottom:1px solid #edf2f7;background:#fff;text-align:left;cursor:pointer}.fp-split-row:hover{background:#f8fbff}.fp-split-row.active{background:#eaf2ff;box-shadow:inset 3px 0 #1677ff}.fp-split-row-main{min-width:0}.fp-split-row b,.fp-split-row small{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fp-split-row b{margin-bottom:4px;color:#0563e8;font-size:13px;line-height:1.45}.fp-split-row small{color:#667085;font-size:12px}.fp-split-stage{justify-self:end;max-width:86px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;border-radius:12px;background:#eef2f7;padding:4px 8px;color:#667085;font-size:12px}.fp-split-detail{padding:0!important}.fp-project-split-detail{min-height:100%;border:0!important;border-radius:0!important}.fp-project-split-detail .opp-detail-back{display:none!important}.fp-project-split-detail .fp-detail-body{min-height:520px}
/* 项目看板固定列宽并横向滚动;信息位置遵循原型,右侧阶段/金额,左下负责人/部门。 */
.fp-board-wrap{overflow-x:scroll!important;overflow-y:hidden!important;scrollbar-gutter:stable;padding-bottom:2px;background:#f7f9fc}.fp-board{display:flex!important;align-items:stretch;gap:10px;width:max-content!important;min-width:max-content!important;padding:12px;background:#f7f9fc}.fp-lane{flex:0 0 270px;width:270px;min-width:270px;min-height:560px}.fp-lane>header{min-height:48px;display:grid;grid-template-columns:minmax(0,1fr) 28px;align-items:start;gap:6px;padding:10px 11px}.fp-lane>header>div{min-width:0}.fp-lane>header b,.fp-lane>header small{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fp-lane>header small{margin-top:4px;color:#64748b}.fp-lane>header>span{justify-self:end;display:grid;place-items:center;min-width:22px;height:22px;border-radius:11px;background:#eef3f8;color:#52677d;font-size:12px}.fp-lane-body{min-height:500px;padding:0 8px 10px}.fp-board-card{margin:9px 0 0!important;padding:11px!important}.fp-board-card>b,.fp-board-card>p{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fp-board-card>b{margin-bottom:5px!important}.fp-board-card>p{min-height:0!important;margin:0 0 12px!important;color:#64748b}.fp-board-card-foot{display:grid!important;grid-template-columns:minmax(0,1fr) auto;align-items:end!important;gap:8px!important}.fp-board-owner{min-width:0;display:grid!important;gap:4px!important}.fp-board-owner span{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#64748b;font-size:11px}.fp-board-card-right{display:grid!important;justify-items:end!important;gap:7px!important}.fp-board-card-right .fp-tag{max-width:88px;overflow:hidden;text-overflow:ellipsis}.fp-board-card-right strong{margin:0!important;color:#173b64;white-space:nowrap}
/* 四大业务模块列表页:统一视图按钮、工具栏、基础字号、行高、间距和关键固定列。 */
.opp-top,.crm-head,.lead2-head,.fp-view-top{height:50px!important;min-height:50px!important;box-sizing:border-box!important;padding:8px 12px!important}.opp-mode-group,.crm-view-switch,.lead2-view-switch,.fp-mode-group{display:flex!important;align-items:center!important;gap:6px!important}.opp-mode-btn,.crm-view-switch button,.lead2-view-switch button,.fp-mode{width:30px!important;height:30px!important;padding:0!important;border:1px solid #d7e2ee!important;border-radius:6px!important;background:#fff!important;color:#64748b!important;display:grid!important;place-items:center!important;cursor:pointer!important;transform:none!important}.opp-mode-btn svg,.crm-view-switch svg,.lead2-view-switch svg,.fp-mode svg{width:17px!important;height:17px!important;fill:none!important;stroke:currentColor!important;stroke-width:1.7!important;stroke-linecap:round!important;stroke-linejoin:round!important}.opp-mode-btn:hover,.crm-view-switch button:hover,.lead2-view-switch button:hover,.fp-mode:hover{border-color:#93c5fd!important;background:#eff6ff!important;color:#1677ff!important}.opp-mode-btn.active,.crm-view-switch button.active,.lead2-view-switch button.active,.fp-mode.active{border-color:#bfdbfe!important;background:#eaf2ff!important;color:#1677ff!important}.opp-view-title,.crm-title-trigger,.lead2-head h1,.crm-lead-title-btn,.fp-view-title{font-size:17px!important;line-height:32px!important;font-weight:800!important}.opp-toolbar,.crm-customer-toolbar,.lead2-toolbar,.fp-toolbar{min-height:48px!important;height:48px!important;box-sizing:border-box!important;padding:7px 12px!important;align-items:center!important;font-size:13px!important}.core-list-tools{display:flex;align-items:center;gap:4px;padding-left:7px;border-left:1px solid #dde5ee;white-space:nowrap}.core-list-tools .opp-tool-icon{flex:0 0 32px}.opp-table,.crm-table,.lead2-table,.fp-project-table{font-size:13px!important}.opp-table th,.crm-table th,.lead2-table th,.fp-project-table th{height:40px!important;box-sizing:border-box!important;padding:0 10px!important;font-size:13px!important;font-weight:700!important;line-height:1.45!important;color:#475569!important}.opp-table td,.crm-table td,.lead2-table td,.fp-project-table td{height:52px!important;box-sizing:border-box!important;padding:7px 10px!important;font-size:13px!important;line-height:1.45!important}.opp-table .col-select{left:0!important;width:42px!important;min-width:42px!important;max-width:42px!important}.opp-table .col-no{left:42px!important;width:50px!important;min-width:50px!important;max-width:50px!important}.opp-table.no-select .col-no{left:0!important}.opp-table .col-name{left:92px!important;width:210px!important;min-width:210px!important;max-width:210px!important}.opp-table.no-select .col-name{left:50px!important}.crm-customer-table{width:1862px!important;min-width:1862px!important;max-width:1862px!important;table-layout:fixed!important}.crm-customer-table .crm-check-col,.crm-customer-table .crm-seq-col,.crm-customer-table .crm-primary-name{position:sticky!important;background:#fff!important}.crm-customer-table .crm-check-col{left:0!important;width:42px!important;min-width:42px!important;max-width:42px!important;z-index:5!important}.crm-customer-table .crm-seq-col{left:42px!important;width:50px!important;min-width:50px!important;max-width:50px!important;z-index:5!important}.crm-customer-table .crm-primary-name{left:92px!important;width:210px!important;min-width:210px!important;max-width:210px!important;z-index:5!important;box-shadow:7px 0 10px -10px rgba(15,23,42,.65)}.crm-customer-table thead .crm-check-col,.crm-customer-table thead .crm-seq-col,.crm-customer-table thead .crm-primary-name{z-index:8!important;background:#f6f9fd!important}.crm-customer-table tbody tr:hover .crm-check-col,.crm-customer-table tbody tr:hover .crm-seq-col,.crm-customer-table tbody tr:hover .crm-primary-name{background:#f8fbff!important}.crm-customer-table .crm-sticky-action{width:250px!important;min-width:250px!important;max-width:250px!important}.lead2-table .fixed-name{width:210px!important;min-width:210px!important;max-width:210px!important}.crm-density-compact td,.lead2-density-compact td{height:44px!important;padding-top:4px!important;padding-bottom:4px!important}.crm-density-relaxed td,.lead2-density-relaxed td{height:60px!important;padding-top:10px!important;padding-bottom:10px!important}
.opp-control-stack .opp-toolbar{height:42px!important;min-height:42px!important;padding:3px 12px 7px!important}
/* 四大业务模块列表二次收敛:统一真实字体继承、搜索控件,以及选择/序号/主名称三列结构。 */
.opp-app,.crm-page,.lead2-page,.fp-app{font-family:"Microsoft YaHei","PingFang SC",Arial,sans-serif!important;font-size:13px!important}.opp-app button,.opp-app input,.opp-app select,.crm-page button,.crm-page input,.crm-page select,.lead2-page button,.lead2-page input,.lead2-page select,.fp-app button,.fp-app input,.fp-app select{font-family:inherit!important;font-size:13px!important}.opp-toolbar .opp-query-input,.crm-customer-query>#crmQuery,.lead2-filters>#lead2Query,.fp-query>#fpQuery{width:270px!important;height:32px!important;box-sizing:border-box!important;border:1px solid #cfd9e5!important;border-radius:4px!important;background:#fff!important;padding:0 10px!important;color:#334155!important;font-size:13px!important;line-height:30px!important;outline:none!important}.opp-toolbar .opp-query-input:focus,.crm-customer-query>#crmQuery:focus,.lead2-filters>#lead2Query:focus,.fp-query>#fpQuery:focus{border-color:#7cb5f5!important;box-shadow:0 0 0 2px rgba(22,119,255,.08)!important}.opp-toolbar .opp-query-input::placeholder,.crm-customer-query>#crmQuery::placeholder,.lead2-filters>#lead2Query::placeholder,.fp-query>#fpQuery::placeholder{color:#8a99aa!important;font-size:13px!important}.opp-table,.crm-customer-table,.lead2-table,.fp-project-table{font-family:inherit!important;font-size:13px!important;line-height:1.45!important}.opp-table th,.crm-customer-table th,.lead2-table th,.fp-project-table th{height:40px!important;padding:0 10px!important;border-bottom:1px solid #dfe7f1!important;background:#f6f9fd!important;color:#475569!important;font-size:13px!important;font-weight:600!important;line-height:1.45!important;white-space:nowrap!important}.opp-table td,.crm-customer-table td,.lead2-table td,.fp-project-table td{height:52px!important;padding:7px 10px!important;border-bottom:1px solid #e8eef5!important;color:#334155!important;font-size:13px!important;font-weight:400!important;line-height:1.45!important;white-space:nowrap!important;vertical-align:middle!important}.opp-table button,.crm-customer-table button,.lead2-table button,.fp-project-table button{font-family:inherit!important;font-size:13px!important;line-height:1.45!important}.opp-name-link,.crm-customer-table .crm-link.strong,.lead2-table .fixed-name .lead2-link b,.fp-project-table .fp-link.strong{font-size:13px!important;font-weight:700!important;line-height:1.45!important}.fp-project-table .fp-link.strong{padding:0!important}.opp-table input[type="checkbox"],.crm-customer-table input[type="checkbox"],.lead2-table input[type="checkbox"]{width:14px!important;height:14px!important;margin:0!important;padding:0!important;accent-color:#1677ff;vertical-align:middle}.opp-table .col-select,.crm-customer-table .crm-check-col,.lead2-table .fixed-select{text-align:center!important}.lead2-table .fixed-select{position:sticky!important;left:0!important;z-index:5!important;width:42px!important;min-width:42px!important;max-width:42px!important;background:#fff!important}.lead2-table .fixed-no{left:42px!important;width:50px!important;min-width:50px!important;max-width:50px!important}.lead2-table .fixed-name{left:92px!important;width:210px!important;min-width:210px!important;max-width:210px!important}.lead2-table thead .fixed-select{z-index:8!important;background:#f6f9fd!important}.lead2-table tbody tr:hover .fixed-select{background:#f8fbff!important}.lead2-table .lead2-check{gap:0!important}.opp-row-actions,.crm-row-actions,.lead2-row-actions{gap:9px!important}
/* 标题条的可见起点、基线和箭头统一,避免模块切换时标题横向跳动。 */
.opp-top-left,.crm-head-main,.lead2-head-left,.fp-view-left{display:flex!important;align-items:center!important;gap:8px!important;min-width:0!important}.opp-view-title,.crm-title-trigger,.crm-lead-title-btn,.fp-view-title{height:32px!important;min-height:32px!important;box-sizing:border-box!important;display:flex!important;align-items:center!important;gap:5px!important;margin:0!important;padding:0!important;border:0!important;background:transparent!important;color:#172033!important;font-family:inherit!important;font-size:17px!important;font-weight:800!important;line-height:32px!important;white-space:nowrap!important}.fp-view-title{margin-left:0!important}.opp-view-title .chev,.crm-title-trigger .chev,.crm-lead-title-btn .chev{display:inline-flex!important;align-items:center!important;justify-content:center!important;width:10px!important;height:16px!important;color:#64748b!important;font-size:11px!important;font-weight:400!important;line-height:1!important;transform:none!important}.opp-view-title.open .chev,.crm-title-trigger.open .chev,.crm-lead-title-btn.open .chev{transform:rotate(180deg)!important}
.crm-page .crm-title-trigger,.lead2-page .crm-lead-title-btn{font-size:17px!important;font-weight:800!important;line-height:32px!important}
/* 项目详情:按 0722 阶段页、页签页和超画布批注重做,轨道点击只查阅,不直接流转。 */
.fp-project-title-line{min-width:0;display:flex;align-items:center;gap:14px;flex-wrap:wrap}.fp-project-title-line h1{margin:0!important;color:#172033!important;font-size:18px!important;line-height:30px!important;white-space:nowrap}.fp-project-code{max-width:620px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#334155;font-size:13px}.fp-project-owner{display:inline-flex;align-items:center;justify-content:center;min-width:112px;height:28px;padding:0 16px;border-radius:14px;background:#58e5e5;color:#155e75;font-size:13px;white-space:nowrap}.fp-project-core{margin:11px 0 0;padding:12px 14px;border:1px solid #e1e8f0;border-radius:7px;background:#fff}.fp-project-core-fields{min-width:0;display:grid;grid-template-columns:repeat(4,minmax(170px,1fr));gap:15px 20px;align-items:center}.fp-project-core-fields>div{min-width:0;display:flex;align-items:center;gap:6px;white-space:nowrap}.fp-project-core-fields span,.fp-eco-card>span{color:#475569}.fp-project-core-fields b{font-weight:500;color:#172033}.fp-project-core-fields .fp-link{min-width:0;overflow:hidden;text-overflow:ellipsis;text-align:left}.fp-project-progress{color:#1677ff!important}.fp-project-core-fields>.fp-eco-card{grid-column:4;display:flex;align-items:flex-start;gap:8px;padding:7px 8px;border:1px solid #dce5ef;border-radius:5px;background:#fbfdff;white-space:nowrap}.fp-eco-card>span{padding-top:2px}.fp-eco-card>div{display:grid;grid-template-columns:repeat(6,minmax(38px,1fr));gap:4px}.fp-eco-card label{display:grid;justify-items:center;gap:4px;color:#475569;font-size:12px}.fp-eco-card label>b{display:grid;place-items:center;width:22px;height:20px;background:#ffe2b4;color:#8a5200;font-size:12px;font-weight:600}.fp-project-head .fp-detail-actions{margin:10px 0 0!important;flex-wrap:wrap}.fp-detail-actions .btn.demo{border-color:#8b5cf6;background:#f5f3ff;color:#6d28d9}.fp-project-detail .fp-project-stage-track{display:grid!important;grid-template-columns:repeat(7,minmax(124px,1fr))!important;gap:0!important;min-width:868px!important;margin:10px 0 0!important;padding:10px 18px 8px!important;overflow-x:auto;overflow-y:hidden;border-top:1px solid #edf1f5;background:#fff}.fp-project-stage-track .opp-stage-node{position:relative;min-width:124px!important;height:58px!important;display:grid!important;grid-template-rows:28px 24px!important;align-items:start!important;justify-items:center!important;gap:3px!important;padding:0 6px!important;border:0!important;border-radius:0!important;background:transparent!important;color:#64748b!important;clip-path:none!important;box-shadow:none!important;transform:none!important;filter:none!important;cursor:pointer}.fp-project-stage-track .opp-stage-node:before{content:"";position:absolute;z-index:0;top:13px;right:50%;width:100%;height:2px;background:#d9e2ec}.fp-project-stage-track .opp-stage-node:first-child:before{display:none}.fp-project-stage-track .opp-stage-node.done:before,.fp-project-stage-track .opp-stage-node.active:before{background:#79b7ff}.fp-project-stage-track .opp-stage-node i{position:relative;z-index:1;flex:0 0 auto;width:26px!important;height:26px!important;box-sizing:border-box;border:1px solid #cbd5e1;border-radius:50%;background:#fff!important;color:#64748b!important;display:grid;place-items:center;font-size:10px!important;font-weight:700}.fp-project-stage-track .opp-stage-node.done i{border-color:#1677ff;background:#1677ff!important;color:#fff!important;font-size:13px!important}.fp-project-stage-track .opp-stage-node.active i{border:2px solid #1677ff;background:#fff!important;color:#1677ff!important;box-shadow:0 0 0 4px rgba(22,119,255,.10)}.fp-project-stage-track .opp-stage-node.viewing:not(.active) i{border:2px solid #f59e0b;background:#fffaf0!important;color:#b45309!important;box-shadow:0 0 0 4px rgba(245,158,11,.12)}.fp-project-stage-track .opp-stage-node .fp-stage-label{position:relative;z-index:1;max-width:100%;display:flex;align-items:center;justify-content:center;gap:5px;overflow:visible!important;color:inherit;white-space:nowrap}.fp-project-stage-track .opp-stage-node .fp-stage-label b{max-width:100%;overflow:hidden;text-overflow:ellipsis;font-weight:500}.fp-project-stage-track .opp-stage-node.done .fp-stage-label b,.fp-project-stage-track .opp-stage-node.active .fp-stage-label b{color:#174a7c}.fp-project-stage-track .opp-stage-node.viewing:not(.active) .fp-stage-label b{color:#9a5700}.fp-project-stage-track .opp-stage-node .fp-stage-label em{flex:0 0 auto;padding:1px 5px;border-radius:8px;background:#eaf3ff;color:#1677ff;font-size:10px;font-style:normal;font-weight:600}.fp-project-stage-track .opp-stage-node.viewing:not(.active) .fp-stage-label em{background:#fff1d6;color:#b45309}.fp-project-stage-track .opp-stage-node:hover:not(.locked) .fp-stage-label b{color:#1677ff}.fp-project-stage-track .opp-stage-node.locked{cursor:not-allowed;opacity:.5}.fp-project-stage-track .opp-stage-node.locked:hover{filter:none!important;transform:none!important;box-shadow:none!important}.fp-stage-context{width:max-content;max-width:calc(100% - 36px);min-height:30px;display:flex;align-items:center;justify-content:flex-start;gap:16px;margin:2px 18px 8px;padding:5px 10px;box-sizing:border-box;border:1px solid #dbe7f3;border-radius:5px;background:#f8fbff;color:#52677d;font-size:12px}.fp-stage-context.current b{color:#1677ff}.fp-stage-context.history{border-color:#f4d58a;background:#fffaf0;color:#8a5b00}.fp-stage-context.history span{display:flex;align-items:center;gap:5px}.fp-stage-context.history em{padding:1px 6px;border-radius:8px;background:#ffedc2;color:#9a5700;font-style:normal}.fp-stage-context button{height:22px;padding:0 8px;border:0;border-left:1px solid #f0cf7b;background:transparent;color:#1677ff;font:inherit;cursor:pointer}.fp-stage-context button:hover{text-decoration:underline}.fp-stage-title{display:flex;align-items:center;justify-content:space-between;gap:16px;margin-bottom:12px;padding:10px 13px;border-left:4px solid #1677ff;background:#f4f8fd;color:#173b64}.fp-stage-title span{color:#64748b;font-size:12px}.fp-stage-primary-actions{display:flex;gap:12px;margin:0 0 12px;padding:12px;border:1px solid #dce5ef;border-radius:7px;background:#fff}.fp-result-recorded{position:absolute;right:12px;top:12px;padding:5px 10px;border-radius:12px;background:#eef6ff;color:#1677ff;font-weight:700}.fp-file-catalog{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;padding:12px}.fp-file-catalog article{display:grid;grid-template-columns:minmax(0,1fr) auto auto;align-items:center;gap:12px;padding:14px;border:1px solid #e1e8f0;border-radius:6px}.fp-file-catalog span{color:#64748b}.fp-chat-list{display:grid;grid-template-columns:1fr 1fr;gap:12px;padding:12px}.fp-chat-list>article{border:1px solid #dce5ef;border-radius:6px;overflow:hidden}.fp-chat-list>article>b{display:block;padding:10px 12px;background:#f7f9fc}.fp-follow-filter{display:flex;gap:8px;padding:12px;border-bottom:1px solid #e5ebf2}.fp-follow-filter input,.fp-follow-filter select{height:32px;border:1px solid #cfd9e5;border-radius:4px;padding:0 9px}.fp-check-list{display:grid;gap:9px;margin:14px 0 0 120px}.fp-prototype-modal .fp-modal-form>div{display:flex;align-items:center;gap:7px}.fp-edit-project-modal .fp-eco-edit-grid{display:grid!important;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}.fp-edit-project-modal .fp-eco-edit-grid label{display:grid;gap:4px;text-align:left}.fp-follow-content-head{display:flex;justify-content:space-between;gap:16px;margin-bottom:8px}.fp-follow-content-head span{color:#d9363e}.fp-follow-main>textarea{width:100%;height:118px;box-sizing:border-box;border:1px solid #cfd9e5;border-radius:4px;padding:9px;resize:vertical}.fp-follow-fields{display:grid;grid-template-columns:1fr 1fr;gap:10px 18px;margin-top:12px}.fp-follow-fields>label{display:grid;grid-template-columns:120px minmax(0,1fr);align-items:center;gap:8px;color:#475569}.fp-follow-fields input,.fp-follow-fields select,.fp-member-search{height:32px;box-sizing:border-box;border:1px solid #cfd9e5;border-radius:4px;padding:0 9px}.fp-input-search{display:grid;grid-template-columns:minmax(0,1fr) 54px;gap:6px}.fp-input-search button{border:1px solid #cfd9e5;border-radius:4px;background:#fff}.fp-upload{display:grid;grid-template-columns:120px minmax(0,1fr);gap:8px;margin-top:12px}.fp-upload>div{display:grid;place-items:center;min-height:90px;border:1px dashed #60a5fa;background:#fbfdff;color:#64748b}.fp-upload small{display:block;margin-top:8px}.fp-group-members{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;margin:14px 0}.fp-group-modal h4{margin:16px 0 8px}.fp-member-search{width:100%}.fp-member-options{display:grid;gap:0;margin-top:6px;border:1px solid #dce5ef}.fp-member-options label{padding:9px 10px;border-bottom:1px solid #edf1f5}.fp-member-options label:last-child{border-bottom:0}
.fp-prototype-info{width:100%;border-collapse:collapse;table-layout:fixed;color:#334155}.fp-prototype-info th,.fp-prototype-info td{height:42px;box-sizing:border-box;border:1px solid #d9e2ec;padding:8px 11px;text-align:left;vertical-align:middle}.fp-prototype-info th{width:17%;background:#f7f9fc;color:#64748b;font-weight:500}.fp-prototype-info td{background:#fff}.fp-prototype-info input,.fp-prototype-info select{width:100%;height:30px;box-sizing:border-box;border:1px solid #cfd9e5;border-radius:4px;padding:0 8px;color:#334155;font:inherit}.fp-file-table th{width:126px}.fp-file-lines{display:grid;gap:5px}.fp-file-lines>div{display:flex;align-items:center;justify-content:space-between;gap:14px;min-width:0}.fp-file-lines>div>span:first-child{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fp-file-lines>div>span:last-child{display:flex;gap:10px}.fp-confirm-entry,.fp-pre-action{display:flex;align-items:center;gap:14px;min-height:38px}.fp-confirm-entry>span,.fp-pre-action>span{color:#64748b}.fp-resolved-banner{margin-bottom:10px;padding:9px 12px;border:1px solid #9fd8b5;border-radius:5px;background:#f0fff5;color:#16794a}.fp-conflict-summary .success{color:#16834f}.fp-conflict-party-table select,.fp-conflict-party-table input{width:100%;min-width:108px;height:30px;box-sizing:border-box;border:1px solid #cfd9e5;border-radius:4px;padding:0 7px;font:inherit}.fp-ratio-tip{margin-top:8px;padding:8px 10px;border:1px solid #f3d18b;border-radius:4px;background:#fffaf0;color:#8a5b00;font-size:12px}.fp-autosave-grid label{position:relative}.fp-field-hint{color:#94a3b8;font-size:11px}.fp-bid-help{position:relative;display:inline-flex;align-items:center}.fp-bid-help>button{height:28px;border:1px solid #cfd9e5;border-radius:4px;background:#fff;color:#52677d;cursor:help}.fp-bid-help>p{display:none;position:absolute;z-index:10;left:0;top:28px;max-width:760px;margin:0;padding:9px 11px;border:1px solid #d9e2ec;border-radius:5px;background:#fff;box-shadow:0 8px 20px rgba(15,23,42,.16);white-space:nowrap}.fp-bid-help:hover>p,.fp-bid-help:focus-within>p{display:block}.fp-material-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px}.fp-material-grid button{display:flex;align-items:center;justify-content:space-between;gap:8px;min-height:44px;border:1px solid #dce5ef;border-radius:5px;background:#fff;padding:8px 10px;color:#334155;cursor:pointer}.fp-material-grid button>b{padding:2px 7px;border-radius:10px;background:#eef6ff;color:#1677ff;font-size:12px}.fp-material-grid button:disabled{cursor:default;opacity:.72}.fp-modal-source{margin-bottom:10px;color:#64748b;font-size:12px}.fp-file-preview{display:grid;gap:10px}.fp-file-preview h3,.fp-file-preview p{margin:0}.fp-file-preview-canvas{min-height:300px;display:grid;place-items:center;border:1px solid #dce5ef;background:#f7f9fc;color:#94a3b8}.fp-win-note,.fp-loss-note{margin-bottom:12px;padding:10px 12px;border-radius:5px}.fp-win-note{background:#f0fff5;color:#16794a}.fp-loss-note{background:#fff4f2;color:#b42318}.fp-performance-modal{display:grid;gap:14px}.fp-performance-modal>p{margin:0;padding:10px 12px;background:#f7f9fc;color:#52677d}.fp-performance-modal h4{margin:0 0 8px}.fp-performance-select{display:grid;grid-template-columns:140px minmax(0,1fr);align-items:center;gap:10px}.fp-performance-select select{height:32px;border:1px solid #cfd9e5;border-radius:4px;padding:0 8px}.fp-performance-summary .btn{margin-top:10px}.fp-result-actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:12px}
@media(max-width:1380px){.fp-project-core-fields{grid-template-columns:repeat(2,minmax(180px,1fr))}.fp-project-core-fields>.fp-eco-card{grid-column:2;width:max-content;max-width:100%}}
@media(max-width:1180px){.crm-unified-split{grid-template-columns:300px 9px minmax(0,1fr)!important}.crm-unified-split.collapsed{grid-template-columns:0 18px minmax(0,1fr)!important}}
</style>
<script id="a7-faithful-module">
const faithfulAdmin=(()=>{
const esc=v=>String(v??'').replace(/[&<>"']/g,m=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m]));
const state={page:'adminUsers',section:'users',roleTab:'list',customerTab:'stage',projectTab:'alias',logTab:'action',dictMode:'list',dictIndex:0,poolTab:'base'};
const users=[['张强','zhangqiang','广东10部','138****6913','销售经理','正常','2026-07-22 09:15'],['陈亚','chenya','广东10部','155****8962','部门管理员','正常','2026-07-22 08:42'],['王秀芬','wangxiufen','营销管理部','192****5656','报备专员','正常','2026-07-21 18:26'],['黄文杰','huangwenjie','技术中心','188****6523','方案设计师','正常','2026-07-21 16:10'],['顾昕','guxin','商务中心','132****2668','商务专员','停用','2026-07-18 11:35'],['林欣怡','linxinyi','营销管理部','189****6621','系统管理员','正常','2026-07-22 10:03']];
const roles=[['系统管理员','系统级','系统最高权限,负责账号、架构与基础数据维护','6','启用'],['团队管理员','团队级','维护本销售团队用户与业务数据','12','启用'],['部门管理员','部门级','维护本部门人员与业务规则','28','启用'],['销售经理','业务级','负责客户、商机及项目推进','326','启用'],['报备专员','业务级','处理项目报备、防撞单及标底认定','8','启用'],['方案设计师','技术支持','承接方案设计任务与清单回传','46','启用'],['商务专员','技术支持','承接报价及商务协同任务','31','启用']];
const dictionaries=[['行业','sys_industry','行业分类与客户行业字段','启用','2026-07-20'],['渠道','sys_channel','线索与客户来源渠道','启用','2026-07-19'],['品牌','sys_brand','方案、项目与报价品牌','启用','2026-07-18'],['客户类型','crm_customer_type','客户主体类型','禁用','2026-07-15']];
const handles=page=>['adminUsers','projectSetting'].includes(page);
const root=()=>document.getElementById(state.page==='projectSetting'?'fpProjectSettingRoot':'fpAdminRoot');
function enter(page){state.page=page;if(page==='projectSetting')state.section='projectRules';render();breadcrumb()}
function breadcrumb(){const bar=document.querySelector('.location-bar');if(!bar)return;const names={users:'用户列表',roles:'角色权限',leadRules:'线索规则',opportunityRules:'商机规则',customerRules:'客户规则',projectRules:'项目规则',logs:'日志管理',dictionary:'数据字典',masking:'脱敏设置'};bar.innerHTML='<span class="crumb-parent">管理后台</span><span class="crumb-sep">›</span><span class="crumb-current">'+names[state.section]+'</span>'}
function nav(){const groups=[['用户管理',[['users','用户列表'],['roles','角色权限']]],['业务规则',[['leadRules','线索规则'],['opportunityRules','商机规则'],['customerRules','客户规则'],['projectRules','项目规则']]],['日志管理',[['logs','操作与登录日志']]],['系统设置',[['dictionary','数据字典'],['masking','脱敏设置']]]];return '<aside class="opp-config-nav a7-config-nav">'+groups.map(g=>'<h4>'+g[0]+'</h4>'+g[1].map(x=>'<button class="'+(state.section===x[0]?'active':'')+'" onclick="faithfulAdmin.section(\''+x[0]+'\')">'+x[1]+'</button>').join('')).join('')+'</aside>'}
function title(name,desc,actions=''){return '<div class="fa-title"><div><h2>'+name+'</h2><p>'+desc+'</p></div><div class="fp-actions">'+actions+'</div></div>'}
function render(){const content=contentFor();root().innerHTML='<div class="opp-config-page a7-config-page"><header class="opp-config-head a7-config-head"><div><h1>管理后台</h1><p>用户、权限、业务规则、日志与系统基础数据配置。</p></div><div class="fp-actions"><span class="fp-tag green">当前权限:系统管理员</span></div></header><div class="opp-config-layout a7-config-layout">'+nav()+'<main class="opp-config-main a7-config-main">'+content+'</main></div></div>'}
function section(v){state.section=v;state.dictMode='list';render();breadcrumb()}
function contentFor(){if(state.section==='users')return usersPage();if(state.section==='roles')return rolesPage();if(state.section==='leadRules')return leadRules();if(state.section==='opportunityRules')return opportunityRules();if(state.section==='customerRules')return customerRules();if(state.section==='projectRules')return projectRules();if(state.section==='logs')return logsPage();if(state.section==='dictionary')return dictionaryPage();return maskingPage()}
function card(body,cls=''){return '<div class="fa-card '+cls+'">'+body+'</div>'}
function adminToolbar(placeholder,extra=''){return '<div class="fa-toolbar"><div class="fp-query"><input placeholder="'+placeholder+'"><select><option>全部组织</option><option>广东10部</option><option>营销管理部</option></select><button class="btn primary">查询</button><button class="btn">重置</button></div>'+extra+'</div>'}
function usersPage(){return title('用户列表','维护系统用户、组织、角色与账号启停状态。','<button class="btn primary" onclick="faithfulAdmin.userEdit(\'新增用户\')">+ 新增用户</button><button class="btn">导入</button><button class="btn" onclick="faithfulAdmin.notice(\'用户数据已导出(Demo)\')">导出</button>')+card(adminToolbar('姓名 / 账号 / 手机号')+'<div class="fp-table-wrap"><table class="fp-table compact fa-table"><thead><tr><th>姓名</th><th>账号</th><th>所属组织</th><th>手机号</th><th>角色</th><th>状态</th><th>最近登录</th><th>操作</th></tr></thead><tbody>'+users.map((u,i)=>'<tr><td><b>'+u[0]+'</b></td><td>'+u[1]+'</td><td>'+u[2]+'</td><td>'+u[3]+'</td><td>'+u[4]+'</td><td><span class="fa-switch '+(u[5]==='正常'?'on':'')+'" onclick="faithfulAdmin.notice(\'账号状态已保存(Demo)\')"></span></td><td>'+u[6]+'</td><td><button class="fp-link" onclick="faithfulAdmin.userEdit(\'编辑用户\','+i+')">编辑</button><button class="fp-link" onclick="faithfulAdmin.notice(\''+(u[5]==='正常'?'用户已禁用':'用户已启用')+'(Demo)\')">'+(u[5]==='正常'?'禁用':'启用')+'</button></td></tr>').join('')+'</tbody></table></div>'+adminPager(users.length))}
function tabs(list,current,fn){return '<div class="fa-tabs">'+list.map(x=>'<button class="'+(current===x[0]?'active':'')+'" onclick="'+fn+'(\''+x[0]+'\')">'+x[1]+'</button>').join('')+'</div>'}
function rolesPage(){const body=state.roleTab==='list'?roleList():state.roleTab==='members'?roleMembers():state.roleTab==='function'?functionPermission():dataPermission();return title('角色权限','角色成员、功能权限和数据范围共同决定系统访问。','<button class="btn primary" onclick="faithfulAdmin.roleAction(\'新增角色\')">+ 新增角色</button>')+card(tabs([['list','角色列表'],['members','角色成员'],['function','功能权限'],['data','数据权限']],state.roleTab,'faithfulAdmin.roleTab')+body)}
function roleTab(v){state.roleTab=v;render()}
function roleList(){return '<div class="fp-table-wrap"><table class="fp-table compact fa-table"><thead><tr><th>角色名称</th><th>角色层级</th><th>角色说明</th><th>成员数</th><th>状态</th><th>操作</th></tr></thead><tbody>'+roles.map((r,i)=>'<tr><td><b>'+r[0]+'</b></td><td>'+r[1]+'</td><td>'+r[2]+'</td><td>'+r[3]+'</td><td><span class="fa-switch on"></span></td><td><button class="fp-link" onclick="faithfulAdmin.roleTab(\'members\')">成员管理</button><button class="fp-link" onclick="faithfulAdmin.roleTab(\'function\')">权限配置</button><button class="fp-link">复制</button>'+(i?'<button class="fp-link danger">删除</button>':'')+'</td></tr>').join('')+'</tbody></table></div>'+adminPager(roles.length)}
function roleMembers(){return '<div class="fa-subtitle"><h3>角色成员</h3><button class="btn danger" onclick="faithfulAdmin.notice(\'已批量移除选中成员(Demo)\')">批量移除</button></div>'+adminToolbar('组织 / 账号 / 姓名','<button class="btn primary" onclick="faithfulAdmin.addMembers()">+ 添加成员</button>')+'<div class="fp-table-wrap"><table class="fp-table compact fa-table"><thead><tr><th><input type="checkbox"></th><th>姓名</th><th>账号</th><th>组织</th><th>当前角色</th><th>加入时间</th><th>操作</th></tr></thead><tbody>'+users.slice(0,4).map(u=>'<tr><td><input type="checkbox"></td><td>'+u[0]+'</td><td>'+u[1]+'</td><td>'+u[2]+'</td><td>销售经理</td><td>2026-06-12</td><td><button class="fp-link danger" onclick="faithfulAdmin.notice(\'成员已移除(Demo)\')">移除</button></td></tr>').join('')+'</tbody></table></div>'+adminPager(4)}
function functionPermission(){const modules=[['线索','线索公海'],['线索','线索管理'],['客户','客户管理'],['商机','商机管理'],['项目','项目管理'],['后台','日志管理'],['后台','数据字典']];return '<div class="fa-permission-tree"><div class="fa-tree-row head"><b>模块 / 菜单</b><b>查看</b><b>管理</b></div>'+modules.map(x=>'<div class="fa-tree-row"><span>'+x[0]+' › '+x[1]+'</span><label><input type="checkbox" checked> 查看</label><label><input type="checkbox" '+(x[0]==='后台'?'':'checked')+'> 管理</label></div>').join('')+'<div class="fp-form-actions"><button class="btn">取消</button><button class="btn primary" onclick="faithfulAdmin.notice(\'功能权限已保存(Demo)\')">保存权限</button></div></div>'}
function dataPermission(){const scopes=[['全部数据','系统管理员、公司级管理者'],['本团队及下级','营销副总裁、战队负责人'],['本部门及下级','部门管理员、销售总监'],['仅本人负责/参与','销售及专业协作角色'],['自定义组织范围','按组织树勾选部门'],['按业务关系','负责人、协作人、团队成员可见']];return '<div class="fa-permission-body"><div class="fa-subtitle"><h3>数据权限</h3><label>配置角色 <select><option>请选择</option><option>销售经理</option><option>部门管理员</option></select></label></div><h3>默认数据范围</h3><p class="fp-muted">功能权限决定能做什么,数据权限决定能看哪些记录,最终权限取两者交集。</p><div class="fa-scope-grid">'+scopes.map((x,i)=>'<label class="'+(i===2?'active':'')+'"><input type="radio" name="scope" '+(i===2?'checked':'')+'><b>'+x[0]+'</b><span>'+x[1]+'</span></label>').join('')+'</div><h3>业务对象例外</h3>'+[['客户','本部门及下级 + 我参与交割的客户'],['商机','本部门及下级 + 我参与团队的商机'],['项目','负责人、销售、专业协作成员及报备专员可见']].map(x=>'<div class="fa-config-row"><b>'+x[0]+'</b><span>'+x[1]+'</span><button class="btn">配置</button></div>').join('')+'<div class="fp-form-actions"><button class="btn">取消</button><button class="btn primary" onclick="faithfulAdmin.notice(\'数据权限已保存(Demo)\')">保存数据权限</button></div></div>'}
function leadRules(){return title('线索规则','配置线索公海池的归属范围、成员、领取和回收规则。','<button class="btn primary" onclick="faithfulAdmin.poolEdit()">+ 新增公海池</button>')+card(adminToolbar('公海池名称 / 负责人')+'<div class="fp-table-wrap"><table class="fp-table compact fa-table"><thead><tr><th>公海池名称</th><th>负责战队</th><th>负责部门</th><th>市场范围</th><th>负责人</th><th>成员权限</th><th>超期回收</th><th>失效时长</th><th>操作</th></tr></thead><tbody>'+[['广东广州区域公海池','冠军队','销售10部','广东省 / 广州、佛山','陈亚','成员可见/领取','7天','180天'],['浙江区域公海池','特战队','浙江销售部','浙江省 / 杭州、宁波','李清','管理员分配','5天','180天'],['江苏全域公海池','粤晋战队','江苏销售部','江苏省 / 南京、苏州','周泽','成员可见/领取','7天','120天']].map((x,i)=>'<tr>'+x.map(v=>'<td>'+v+'</td>').join('')+'<td><button class="fp-link" onclick="faithfulAdmin.poolEdit('+i+')">编辑</button></td></tr>').join('')+'</tbody></table></div>')}
function poolEdit(){const base=state.poolTab==='base';openModal('编辑公海池',{classes:['opp-modal-wide'],body:tabs([['base','公海池设置'],['other','其他设置']],state.poolTab,'faithfulAdmin.poolTab')+(base?'<div class="fp-modal-form"><label>*公海池名称</label><input value="广东广州区域公海池"><label>*负责战队</label><input value="冠军队"><label>*负责部门</label><input value="销售10部"><label>*负责市场</label><input value="广东省"><label>*负责城市</label><input value="广州市、佛山市"><label>*负责人</label><input value="陈亚"><label>协作人</label><input value="王秀芬"><label>成员权限</label><select><option>成员可见/可领取,管理员可分配</option></select></div>':'<div class="fp-modal-form"><label>每日领取上限</label><input value="20"><label>个人持有上限</label><input value="200"><label>超期未跟进回收</label><input value="7天"><label>线索失效时长</label><input value="180天"></div>'),footer:'<button class="btn" onclick="closeModal()">取消</button><button class="btn primary" onclick="closeModal();faithfulAdmin.notice(\'公海池已保存(Demo)\')">保存</button>'})}
function poolTab(v){state.poolTab=v;poolEdit()}
function opportunityRules(){return title('商机规则','0722 原型为未完成页;本页直接承接 S1 已完成的商机管理设置。','<button class="btn primary" onclick="setActivePage(\'oppSetting\',\'商机 / 商机管理设置\')">进入商机管理设置</button>')+card('<div class="fa-rule-grid"><div><h3>工作指引</h3><p>维护商机阶段、推进节点、停留时长和协同指引。</p></div><div><h3>模板与字段</h3><p>维护商机字段模板、方案卡字段及条件生效规则。</p></div><div><h3>方案推送</h3><p>维护方案推送对象、触发条件与生效范围。</p></div><div><h3>查重与公海</h3><p>维护商机查重规则、重复处置及商机公海策略。</p></div></div>')}
function customerRules(){const body=state.customerTab==='stage'?customerStages():state.customerTab==='stars'?customerStars():state.customerTab==='dedupe'?customerDedupe():customerReminder();return title('客户规则','维护客户阶段、星级、查重和超期未跟进提醒。','<button class="btn primary">保存设置</button>')+card(tabs([['stage','客户阶段设置'],['stars','星级设置'],['dedupe','客户查重设置'],['reminder','超期未跟进提醒']],state.customerTab,'faithfulAdmin.customerTab')+body)}
function customerTab(v){state.customerTab=v;render()}
function customerStages(){return '<div class="fa-subtitle"><h3>客户阶段</h3><button class="btn">+ 新增阶段</button></div><div class="fa-config-list">'+[['潜在客户','尚未建立稳定联系'],['重点跟进','已有明确需求或合作机会'],['成交客户','已产生合同或订单'],['沉睡客户','长期无有效跟进']].map((x,i)=>'<div class="fa-config-row"><b>☷ 排序 '+x[0]+'</b><span>'+x[1]+'</span><div><button class="fp-link">编辑</button><button class="fp-link danger">删除</button><span class="fa-switch on" title="启用 / 停用"></span><small>启用</small></div></div>').join('')+'</div><div class="fp-form-actions"><button class="btn">取消</button><button class="btn primary" onclick="faithfulAdmin.notice(\'客户阶段已保存(Demo)\')">保存</button></div>'}
function customerStars(){return '<div class="fa-subtitle"><h3>星级设置</h3><span class="fp-muted">固定 1星—5星,可编辑名称与描述</span></div><div class="fa-config-list">'+[['1星','低优先级','低优先级或待清洗'],['2星','培育客户','关系较弱,需持续培育'],['3星','普通客户','正常维护,存在潜在线索'],['4星','重点客户','近期机会明确'],['5星','战略客户','战略重点,决策关系稳固']].map(x=>'<div class="fa-config-row"><b class="fa-stars">'+x[0]+'</b><label>名称 <input value="'+x[1]+'"></label><label>描述 <input value="'+x[2]+'"></label></div>').join('')+'</div><div class="fp-form-actions"><button class="btn">取消</button><button class="btn primary" onclick="faithfulAdmin.notice(\'星级设置已保存(Demo)\')">保存</button></div>'}
function customerDedupe(){return '<div class="fa-subtitle"><h3>客户查重</h3><span class="fa-switch on"></span></div><div class="fa-form"><h3>规则组合逻辑</h3><label><input type="radio" name="faDedupe" checked> 任意命中</label> <label><input type="radio" name="faDedupe"> 全部命中</label></div><div class="fa-config-list">'+[['客户名称','模糊匹配'],['统一社会信用代码','精确匹配'],['联系电话','精确匹配']].map((x,i)=>'<div class="fa-config-row"><b>'+x[0]+'</b><span>'+x[1]+(i===0?' <select><option>70%</option><option selected>80%</option><option>90%</option></select>':'')+'</span><span class="fa-switch on"></span></div>').join('')+'</div><div class="fp-form-actions"><button class="btn">取消</button><button class="btn primary" onclick="faithfulAdmin.notice(\'客户查重规则已保存(Demo)\')">保存</button></div>'}
function customerReminder(){return '<div class="fa-subtitle"><h3>超期未跟进提醒</h3><span class="fa-switch on"></span></div><div class="fa-config-list"><div class="fa-config-row"><b>首次提醒</b><span><input type="number" value="30"> 天未跟进</span><span class="fa-switch on"></span></div><div class="fa-config-row"><b>二次提醒</b><span><input type="number" value="7"> 天后再次提醒</span><span class="fa-switch on"></span></div></div><div class="fp-form-actions"><button class="btn">取消</button><button class="btn primary" onclick="faithfulAdmin.notice(\'超期提醒规则已保存(Demo)\')">保存</button></div>'}
function projectRules(){const body=state.projectTab==='alias'?projectAliases():state.projectTab==='members'?projectMembers():state.projectTab==='conflict'?projectConflict():projectReminder();return title('项目规则','维护项目阶段别名、归档人员、冲突检测及阶段提醒。','<button class="btn primary">保存设置</button>')+card(tabs([['alias','阶段别名'],['members','归档人员'],['conflict','冲突规则'],['reminder','阶段提醒']],state.projectTab,'faithfulAdmin.projectTab')+body)}
function projectTab(v){state.projectTab=v;render()}
function projectAliases(){return '<div class="fa-config-list">'+[['方案设计','设计中'],['商务报价','报价中'],['招投标准备','招投标'],['商务跟进','商务跟进'],['赢单/输单','赢输单'],['结项','已结项']].map((x,i)=>'<div class="fa-config-row"><b>'+x[0]+'</b><span><input value="'+x[1]+'"></span><div><input type="number" value="'+((i+1)*10)+'"></div></div>').join('')+'</div>'}
function projectMembers(){return '<div class="fa-rule-grid"><div><h3>项目归档可见人员</h3><p>项目负责人、销售经理、销售总监、报备专员、管理员。</p><button class="btn">选择人员/角色</button></div><div><h3>输单归档</h3><p>输单项目进入结项后,全字段只读,管理员可查看完整历史。</p></div></div>'}
function projectConflict(){const dims=[['甲方项目名称','模糊匹配 ≥ 80%'],['项目地点','省/市/区精确匹配'],['品牌/型号','品牌层级树 + 文本相似度'],['关键联系人','手机号精确 + 姓名模糊'],['报备时间戳','时间排序,标记第一设计方'],['招标文件编号','至少两方精确匹配'],['异地销售判定','销售区域与项目所在地比对']];return '<div class="fa-config-list">'+dims.map((x,i)=>'<div class="fa-config-row"><b>'+x[0]+'</b><span>'+x[1]+'</span><span class="fa-switch '+(i<6?'on':'')+'"></span></div>').join('')+'</div><div class="fp-note">本地明确冲突须在24小时内协调;异地方须在4小时内回复,超时默认同意。</div>'}
function projectReminder(){return '<div class="fa-config-list">'+[['方案设计','到期前1天提醒负责人,超期升级主管'],['商务报价','待销售确认超过2天提醒'],['招投标准备','投标截止前3天提醒'],['商务跟进','30天无跟进标红提醒'],['赢单/输单','开标后2天未标记结果提醒']].map(x=>'<div class="fa-config-row"><b>'+x[0]+'</b><span>'+x[1]+'</span><span class="fa-switch on"></span></div>').join('')+'</div>'}
function logsPage(){const batch=state.logTab==='batch',rows=state.logTab==='login'?[['陈亚','登录系统','192.168.10.35','成功','2026-07-22 08:42'],['顾昕','登录系统','192.168.10.77','失败','2026-07-22 08:30']]:batch?[['王秀芬','批量导入线索','成功 186 / 失败 4','已完成','2026-07-21 16:35'],['林欣怡','批量导出客户','文件90天后过期','已完成','2026-07-21 10:18']]:[['陈亚','编辑项目','番禺区第二中学报告厅改造','成功','2026-07-22 10:18'],['王秀芬','提交冲突处理','FBB-20260703-0037','成功','2026-07-22 09:36']];return title('日志管理','日志只读,用于问题追踪与安全审计。','<button class="btn" onclick="faithfulAdmin.notice(\'日志已导出(Demo)\')">导出日志</button>')+card(tabs([['action','用户操作日志'],['login','登录日志'],['batch','批量操作日志']],state.logTab,'faithfulAdmin.logTab')+'<div class="fa-toolbar"><div class="fp-query"><input placeholder="组织"><input placeholder="人员"><label>日期 <input type="date" value="2026-07-01"></label><span>至</span><input type="date" value="2026-07-22"><button class="btn primary">查询</button><button class="btn">重置</button></div>'+(batch?'<button class="btn" onclick="faithfulAdmin.notice(\'选中日志结果文件已下载(Demo)\')">批量下载</button>':'')+'</div><div class="fp-table-wrap"><table class="fp-table compact fa-table"><thead><tr><th>'+(batch?'<input type="checkbox"> ':'')+'操作人</th><th>操作类型</th><th>操作对象 / IP</th><th>结果</th><th>操作时间</th>'+(batch?'<th>操作</th>':'')+'</tr></thead><tbody>'+rows.map(x=>'<tr>'+x.map((v,i)=>'<td>'+(batch&&i===0?'<input type="checkbox"> ':'')+v+'</td>').join('')+(batch?'<td><button class="fp-link" onclick="faithfulAdmin.notice(\'结果文件已下载(Demo)\')">下载</button></td>':'')+'</tr>').join('')+'</tbody></table></div>'+adminPager(rows.length))}
function logTab(v){state.logTab=v;render()}
function dictionaryPage(){return state.dictMode==='list'?dictionaryList():dictionaryEditor()}
function dictionaryList(){return title('数据字典','维护业务下拉选项和基础枚举;已被业务引用的字典不可删除。','<button class="btn primary" onclick="faithfulAdmin.dictEdit(\'new\')">+ 新增数据字典</button>')+card(adminToolbar('字典编码 / 字典名称')+'<div class="fp-table-wrap"><table class="fp-table compact fa-table"><thead><tr><th>字典名称</th><th>字典编码</th><th>字典描述</th><th>状态</th><th>更新时间</th><th>操作</th></tr></thead><tbody>'+dictionaries.map((d,i)=>'<tr><td><b>'+d[0]+'</b></td><td><code>'+d[1]+'</code></td><td>'+d[2]+'</td><td>'+d[3]+'</td><td>'+d[4]+'</td><td><button class="fp-link" onclick="faithfulAdmin.dictEdit(\'detail\','+i+')">详情</button><button class="fp-link" onclick="faithfulAdmin.dictEdit(\'edit\','+i+')">编辑</button><button class="fp-link danger">删除</button></td></tr>').join('')+'</tbody></table></div>')}
function dictEdit(mode,index=0){state.dictMode=mode;state.dictIndex=index;render()} function dictBack(){state.dictMode='list';render()}
function dictionaryEditor(){const d=dictionaries[state.dictIndex]||['','','','启用',''],detail=state.dictMode==='detail',fresh=state.dictMode==='new';return title((fresh?'新增':detail?'数据字典详情':'编辑')+'数据字典','字典编码创建后不可修改;排序数值越小越靠前。','<button class="btn" onclick="faithfulAdmin.dictBack()">← 返回列表</button>')+card('<div class="fp-modal-form fa-form"><label>*字典编码</label><input value="'+(fresh?'':d[1])+'" '+(!fresh?'disabled':'')+'><label>*字典名称</label><input value="'+(fresh?'':d[0])+'" '+(detail?'disabled':'')+'><label>状态</label><select '+(detail?'disabled':'')+'><option>'+d[3]+'</option></select><label>排序</label><input value="10" '+(detail?'disabled':'')+'><label>字典描述</label><textarea '+(detail?'disabled':'')+'>'+d[2]+'</textarea></div><div class="fa-subtitle"><h3>字典值明细</h3>'+(detail?'':'<button class="btn">+ 新增行</button>')+'</div><div class="fa-dict-values">'+[['教育','教育行业客户','启用','10'],['政府','政府与事业单位','启用','20'],['企业','企业客户','启用','30']].map(x=>'<div>'+x.map(v=>'<input value="'+v+'" '+(detail?'disabled':'')+'>').join('')+'</div>').join('')+'</div>'+(detail?'':'<div class="fp-form-actions"><button class="btn">取消</button><button class="btn primary">保存</button></div>'))}
function maskingPage(){return title('脱敏设置','按字段类型和业务页面配置脱敏;查看原文必须校验权限并记录审计日志。')+card('<div class="fa-mask"><div><b>联系人电话</b><small>138****1234</small></div><div><span class="fa-switch on" onclick="faithfulAdmin.notice(\'脱敏开关已即时保存(Demo)\')"></span><p><label><input type="checkbox" checked onchange="faithfulAdmin.notice(\'页面范围已即时保存(Demo)\')"> 线索详情</label> <label><input type="checkbox" checked onchange="faithfulAdmin.notice(\'页面范围已即时保存(Demo)\')"> 客户详情</label> <label><input type="checkbox" checked onchange="faithfulAdmin.notice(\'页面范围已即时保存(Demo)\')"> 商机详情</label> <label><input type="checkbox" checked onchange="faithfulAdmin.notice(\'页面范围已即时保存(Demo)\')"> 项目详情</label></p></div><div><b>金额</b><small>¥****</small></div><div><span class="fa-switch on" onclick="faithfulAdmin.notice(\'脱敏开关已即时保存(Demo)\')"></span><p><label><input type="checkbox" checked> 商机(方案金额)</label> <label><input type="checkbox" checked> 方案卡(报价金额)</label> <label><input type="checkbox" checked> 项目(合同金额)</label></p></div></div><div class="fp-note">所有开关与勾选即时保存。查看原文审计日志记录查看人、时间、IP、业务对象和字段。</div>')}
function userEdit(title){openModal(title,{body:'<div class="fp-modal-form"><label>*姓名</label><input value="陈亚"><label>*账号</label><input value="chenya"><label>*所属组织</label><select><option>广东10部</option></select><label>*手机号</label><input value="155****8962"><label>*角色</label><select><option>部门管理员</option><option>销售经理</option></select></div>',footer:'<button class="btn" onclick="closeModal()">取消</button><button class="btn primary" onclick="closeModal();faithfulAdmin.notice(\'用户已保存(Demo)\')">保存</button>'})}
function roleAction(title){openModal(title,{body:'<div class="fp-modal-form"><label>*角色名称</label><input><label>*角色层级</label><select><option>业务级</option><option>部门级</option><option>团队级</option></select><label>角色说明</label><textarea></textarea></div>',footer:'<button class="btn" onclick="closeModal()">取消</button><button class="btn primary" onclick="closeModal()">保存</button>'})}
function addMembers(){openModal('添加角色成员',{classes:['opp-modal-wide'],body:'<div class="fa-member-picker"><div><h3>组织与人员</h3><input placeholder="搜索人员 / 部门">'+users.slice(0,4).map(u=>'<label><input type="checkbox"> '+u[0]+' · '+u[2]+'</label>').join('')+'</div><div><h3>已选择</h3><span class="fp-tag blue">张强 ×</span><p>选择人员后在此同步显示。</p></div></div>',footer:'<button class="btn" onclick="closeModal()">取消</button><button class="btn primary" onclick="closeModal()">提交</button>'})}
function adminPager(n){return '<div class="fp-pager"><span>共 '+n+' 条</span><div><button class="active">1</button><button>2</button><button>›</button></div></div>'}
function notice(msg){faithfulProject.notice(msg)}
return {handles,enter,section,roleTab,customerTab,projectTab,logTab,poolEdit,poolTab,dictEdit,dictBack,userEdit,roleAction,addMembers,notice};
})();
</script>
<style id="a7-faithful-styles">
.fa-layout{display:grid;grid-template-columns:200px minmax(0,1fr);gap:14px}.fa-layout>main{min-width:0}.fa-side{align-self:start;position:sticky;top:10px;padding:8px;border:1px solid #dce5ef;border-radius:8px;background:#fff}.fa-side h4{margin:10px;color:#94a3b8;font-size:11px}.fa-side button{display:block;width:100%;height:36px;border:0;border-radius:5px;background:transparent;padding:0 10px;text-align:left;color:#52677d;cursor:pointer}.fa-side button:hover,.fa-side button.active{background:#eaf3ff;color:#1677ff}.fa-side button.active{font-weight:700}.fa-title{min-height:58px;display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:10px}.fa-title h2{margin:0;color:#173b64}.fa-title p{margin:6px 0 0;color:#64748b}.fa-card{border:1px solid #dce5ef;border-radius:8px;background:#fff;overflow:hidden}.fa-toolbar{min-height:50px;display:flex;align-items:center;justify-content:space-between;gap:10px;padding:8px 12px;background:#f8fafc;border-bottom:1px solid #e5ebf2}.fa-table{min-width:920px}.fa-tabs{display:flex;gap:3px;padding:9px 12px;border-bottom:1px solid #e5ebf2;overflow:auto}.fa-tabs button{height:32px;border:0;border-radius:5px;background:transparent;padding:0 12px;color:#64748b;white-space:nowrap;cursor:pointer}.fa-tabs button.active{background:#eaf3ff;color:#1677ff;font-weight:700}.fa-switch{position:relative;display:inline-block;width:38px;height:21px;border-radius:11px;background:#bdc7d2;cursor:pointer;vertical-align:middle}.fa-switch.on{background:#27a66b}.fa-switch:after{content:'';position:absolute;left:3px;top:3px;width:15px;height:15px;border-radius:50%;background:#fff}.fa-switch.on:after{left:20px}.fa-permission-tree,.fa-permission-body{padding:12px}.fa-tree-row{display:grid;grid-template-columns:minmax(250px,1fr) 100px 100px;align-items:center;min-height:42px;border-bottom:1px solid #edf1f5}.fa-tree-row.head{background:#f8fafc;padding:0 10px}.fa-scope-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;margin:12px 0}.fa-scope-grid label{display:grid;grid-template-columns:20px 1fr;padding:12px;border:1px solid #dce5ef;border-radius:6px}.fa-scope-grid label.active{border-color:#1677ff;background:#f0f7ff}.fa-scope-grid span{grid-column:2;color:#64748b;margin-top:5px}.fa-config-row{display:grid;grid-template-columns:minmax(150px,220px) minmax(0,1fr) 160px;align-items:center;gap:12px;min-height:52px;padding:7px 12px;border-bottom:1px solid #edf1f5}.fa-config-row input{height:32px;box-sizing:border-box;border:1px solid #cfd9e5;border-radius:4px;padding:0 8px}.fa-config-list{padding:4px 0}.fa-rule-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;padding:12px}.fa-rule-grid>div{border:1px solid #dce5ef;border-radius:7px;padding:13px}.fa-rule-grid h3{margin:0 0 9px}.fa-rule-grid p,.fa-rule-grid label{display:block;color:#64748b;line-height:1.6}.fa-stars{color:#f5a623;letter-spacing:2px}.fa-standalone{padding-top:18px}.fa-form{padding:16px}.fa-subtitle{display:flex;align-items:center;justify-content:space-between;padding:10px 14px;border-top:1px solid #e5ebf2;border-bottom:1px solid #e5ebf2;background:#f8fafc}.fa-subtitle h3{margin:0}.fa-dict-values{display:grid;gap:8px;padding:12px}.fa-dict-values>div{display:grid;grid-template-columns:1fr 1.4fr 100px 80px;gap:8px}.fa-dict-values input{height:32px;border:1px solid #cfd9e5;border-radius:4px;padding:0 8px}.fa-mask{display:grid;grid-template-columns:180px minmax(0,1fr)}.fa-mask>div{min-width:0;padding:13px;border-bottom:1px solid #edf1f5}.fa-mask>div:nth-child(odd){background:#f8fafc}.fa-mask small{display:block;margin-top:5px;color:#64748b}.fa-member-picker{display:grid;grid-template-columns:1fr 1fr;gap:12px}.fa-member-picker>div{border:1px solid #dce5ef;border-radius:6px;padding:12px}.fa-member-picker label{display:block;padding:8px 0}
@media(max-width:1100px){.fa-layout{grid-template-columns:1fr}.fa-side{position:static;display:flex;overflow:auto}.fa-side h4{display:none}.fa-side button{min-width:110px}.fa-scope-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media(max-width:760px){.fa-rule-grid,.fa-scope-grid,.fa-member-picker{grid-template-columns:1fr}.fa-config-row{grid-template-columns:1fr}.fa-dict-values>div{grid-template-columns:1fr}.fa-mask{grid-template-columns:1fr}.fa-title{flex-direction:column}}
.a7-config-nav h4{margin:7px 10px 4px;padding-top:8px;border-top:1px solid #edf2f7;color:#94a3b8;font-size:11px}.a7-config-nav h4:first-child{padding-top:0;border-top:0}.a7-config-head .fp-actions{display:flex;align-items:center;gap:8px}.a7-config-main>.fa-title{min-height:46px}
@media(max-width:1180px){.a7-config-nav h4{display:none}.a7-config-nav button{min-width:110px}}
</style>
<style id="faithful-corrections">
.fp-wizard-steps>div{font-size:0}.fp-wizard-steps>div:nth-child(1):before{content:'1';font-size:13px}.fp-wizard-steps>div:nth-child(2):before{content:'2';font-size:13px}.fp-wizard-steps>div:nth-child(3):before{content:'3';font-size:13px}.fp-wizard-steps>div span{font-size:13px}
</style>
<style id="workplan-static-migration-styles">
.workplan-nav-icon{display:inline-flex;width:18px;height:18px;align-items:center;justify-content:center}.workplan-nav-icon svg{width:17px;height:17px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}.workplan-native-page{padding:0;background:#f5f7fa;min-height:0}.workplan-native{min-height:0;padding:0}
.workplan-native{
--blue: #2563eb;
--blue-weak: #eff6ff;
--navy: #102a52;
--text: #172033;
--muted: #6b778c;
--line: #e6eaf0;
--bg: #f5f7fb;
--card: #fff;
--green: #0f9f6e;
--green-weak: #ecfdf5;
--amber: #d97706;
--amber-weak: #fffbeb;
--red: #dc3c3c;
--red-weak: #fff1f2;
--purple: #7557d8;
--radius: 10px;
--shadow: 0 4px 18px rgba(25, 45, 78, .06);
}
.workplan-native *{ box-sizing: border-box; }
.workplan-native{ margin: -6px 0 0; min-width: 1180px; color: var(--text); background: var(--bg); font: 14px/1.5 "Microsoft YaHei", "PingFang SC", Arial, sans-serif; }
.workplan-native button, .workplan-native input, .workplan-native select, .workplan-native textarea{ font: inherit; }
.workplan-native button{ cursor: pointer; }
.workplan-native .main{ width: calc(100% - 218px); padding: 21px 26px 36px; }
.workplan-native .page-head{ display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 18px; }
.workplan-native h1{ margin: 0; color: #172b4d; font-size: 22px; line-height: 1.3; }
.workplan-native .sub{ margin-top: 5px; color: var(--muted); }
.workplan-native .head-actions{ display: flex; gap: 8px; }
.workplan-native .btn{ height: 34px; padding: 0 13px; border: 1px solid var(--line); border-radius: 6px; color: #4a5870; background: #fff; }
.workplan-native .btn:hover{ border-color: #a9c6ff; color: var(--blue); }
.workplan-native .btn.primary{ border-color: var(--blue); color: #fff; background: var(--blue); box-shadow: 0 2px 5px rgba(37, 99, 235, .2); }
.workplan-native .btn.primary:hover{ background: #1d57d0; }
.workplan-native .btn.small{ height: 29px; padding: 0 9px; font-size: 12px; }
.workplan-native .view{ display: none; }
.workplan-native .view.active{ display: block; }
.workplan-native .kpi-grid{ display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 12px; margin-bottom: 14px; }
.workplan-native .kpi{ min-height: 91px; padding: 15px 16px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--card); box-shadow: var(--shadow); }
.workplan-native .kpi-label{ color: #748097; font-size: 12px; }
.workplan-native .kpi-number{ margin-top: 4px; color: #1c2f50; font-size: 25px; font-weight: 700; line-height: 1.1; }
.workplan-native .kpi-number.red{ color: var(--red); }
.workplan-native .kpi-number.green{ color: var(--green); }
.workplan-native .kpi-desc{ margin-top: 7px; color: #98a2b3; font-size: 11px; }
.workplan-native .grid-2-1{ display: grid; grid-template-columns: minmax(620px, 2fr) minmax(280px, 1fr); gap: 14px; margin-bottom: 14px; }
.workplan-native .card{ border: 1px solid var(--line); border-radius: var(--radius); background: var(--card); box-shadow: var(--shadow); }
.workplan-native .card-head{ min-height: 50px; display: flex; align-items: center; justify-content: space-between; padding: 0 16px; border-bottom: 1px solid #edf0f5; }
.workplan-native .card-title{ color: #263b5c; font-size: 15px; font-weight: 700; }
.workplan-native .card-title small{ margin-left: 7px; color: #9aa5b5; font-size: 12px; font-weight: 400; }
.workplan-native .link{ border: 0; padding: 0; color: var(--blue); background: none; font-size: 12px; }
.workplan-native .task-list{ padding: 0 16px; }
.workplan-native .task-row{ display: grid; grid-template-columns: 28px minmax(190px, 1.35fr) 105px 86px 74px 82px; align-items: center; min-height: 59px; border-bottom: 1px solid #edf0f5; gap: 8px; }
.workplan-native .task-row:last-child{ border-bottom: 0; }
.workplan-native .check{ width: 18px; height: 18px; border: 1.5px solid #b5c0d1; border-radius: 50%; background: #fff; }
.workplan-native .check.done{ border-color: var(--green); background: var(--green); color: #fff; font-size: 12px; }
.workplan-native .task-name{ color: #253650; font-weight: 600; }
.workplan-native .task-meta{ margin-top: 2px; color: #8e99aa; font-size: 12px; }
.workplan-native .object-tag, .workplan-native .state, .workplan-native .pill{ display: inline-flex; align-items: center; justify-content: center; min-height: 22px; padding: 0 7px; border-radius: 4px; font-size: 11px; white-space: nowrap; }
.workplan-native .object-tag{ color: #5c6c84; background: #f2f5f8; }
.workplan-native .state.pending{ color: #a05b00; background: #fff5e4; }
.workplan-native .state.progress{ color: #2767be; background: #eaf3ff; }
.workplan-native .state.overdue{ color: #c32e36; background: #fff0f1; }
.workplan-native .state.done{ color: #07835b; background: #eafbf3; }
.workplan-native .due{ color: #708099; font-size: 12px; }
.workplan-native .due.overdue{ color: var(--red); font-weight: 700; }
.workplan-native .metric-list{ padding: 5px 16px 10px; }
.workplan-native .metric-row{ display: flex; align-items: center; justify-content: space-between; padding: 10px 0; border-bottom: 1px solid #edf0f5; }
.workplan-native .metric-row:last-child{ border-bottom: 0; }
.workplan-native .metric-left{ display: flex; align-items: center; gap: 9px; color: #40516b; }
.workplan-native .dot{ width: 8px; height: 8px; border-radius: 50%; background: var(--blue); }
.workplan-native .dot.green{ background: var(--green); }
.workplan-native .dot.amber{ background: #f3a522; }
.workplan-native .dot.purple{ background: var(--purple); }
.workplan-native .metric-right{ color: #273c5b; font-weight: 700; }
.workplan-native .note-box{ margin: 13px 16px 16px; padding: 10px 12px; border-left: 3px solid #72a2f7; border-radius: 4px; color: #6f7e96; background: #f7faff; font-size: 12px; line-height: 1.7; }
.workplan-native .focus-card{ padding: 15px; }
.workplan-native .focus-top{ display: flex; justify-content: space-between; align-items: flex-start; }
.workplan-native .focus-type{ display: inline-flex; gap: 5px; align-items: center; color: #4d6090; font-size: 12px; }
.workplan-native .focus-name{ margin-top: 8px; color: #233857; font-size: 16px; font-weight: 700; }
.workplan-native .focus-desc{ height: 34px; margin-top: 4px; overflow: hidden; color: #77849a; font-size: 12px; }
.workplan-native .focus-bottom{ display: flex; justify-content: space-between; align-items: center; margin-top: 10px; padding-top: 10px; border-top: 1px solid #edf0f5; color: #7c899e; font-size: 12px; }
.workplan-native .focus-grid{ display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; }
.workplan-native .mini-progress{ width: 84px; height: 5px; overflow: hidden; border-radius: 4px; background: #ecf0f4; }
.workplan-native .mini-progress i{ display: block; height: 100%; border-radius: inherit; background: var(--blue); }
.workplan-native .page-tools{ display: flex; align-items: center; gap: 8px; padding: 13px 15px; margin-bottom: 13px; border: 1px solid var(--line); border-radius: var(--radius); background: #fff; }
.workplan-native .table-pagination{ display:flex; align-items:center; justify-content:space-between; min-height:48px; padding:0 15px; border-top:1px solid #edf0f5; color:#738198; font-size:12px; }
.workplan-native .pagination-actions{ display:flex; align-items:center; gap:7px; }
.workplan-native .pagination-actions select, .workplan-native .page-btn{ height:28px; min-width:28px; padding:0 8px; border:1px solid #d9e1ec; border-radius:4px; color:#4b5f7d; background:#fff; font:inherit; cursor:pointer; }
.workplan-native .page-btn.active{ border-color:var(--blue); color:#fff; background:var(--blue); }
.workplan-native .action-table-scroll{ overflow:auto; max-width:100%; scrollbar-color:#8b8b8b #e8ebef; scrollbar-width:auto; cursor:default; }
.workplan-native .action-table-scroll.is-dragging{ cursor:grabbing; user-select:none; }
.workplan-native #view-actions .action-history-table{ width:1880px; min-width:1880px; table-layout:fixed; border-collapse:separate; border-spacing:0; }
.workplan-native #view-actions .action-history-table th, .workplan-native #view-actions .action-history-table td{ white-space:normal; }
.workplan-native #view-actions .action-history-table th:first-child, .workplan-native #view-actions .action-history-table td:first-child{ position:sticky; left:0; z-index:2; background:#fff; box-shadow:none; }
.workplan-native #view-actions .action-history-table th:first-child{ z-index:4; background:#f7f9fc; }
.workplan-native #view-actions .action-history-table th:last-child, .workplan-native #view-actions .action-history-table td:last-child{ position:sticky; right:0; z-index:2; background:#fff; box-shadow:-10px 0 14px -15px rgba(26,49,84,.38); white-space:nowrap; }
.workplan-native #view-actions .action-history-table th:last-child{ z-index:4; background:#f7f9fc; }
.workplan-native #view-actions .action-history-table tbody tr:hover td:last-child{ background:#f8fbff; }
.workplan-native #action-table td:last-child .link{ display:inline-block; padding:4px 0; line-height:20px; }
.workplan-native #action-table td:last-child .link + .link{ margin-left:12px; }
.workplan-native #action-table .filter-empty td{ padding:38px 12px; color:#8b98aa; text-align:center; }
.workplan-native .workplan-list-tools{ display:flex; align-items:center; gap:5px; margin-left:3px; padding-left:4px; border-left:1px solid #e0e7ef; }
.workplan-native .workplan-tool-icon{ position:relative; width:32px; height:32px; padding:0; border:1px solid transparent; border-radius:5px; background:transparent; color:#475569; display:grid; place-items:center; cursor:pointer; transition:all .15s; }
.workplan-native .workplan-tool-icon svg{ width:17px; height:17px; fill:none; stroke:currentColor; stroke-width:1.8; stroke-linecap:round; stroke-linejoin:round; }
.workplan-native .workplan-tool-icon:hover, .workplan-native .workplan-tool-icon.active{ background:#eaf2ff; border-color:#bfdbfe; color:#1677ff; transform:translateY(-1px); }
.workplan-native .workplan-tool-icon.refreshing svg{ animation:workplanSpin .55s linear; } @keyframes workplanSpin { to { transform:rotate(360deg); } }
.workplan-native .workplan-popover{ position:absolute; z-index:40; top:38px; right:0; display:none; background:#fff; border:1px solid #d5dfeb; border-radius:7px; box-shadow:0 12px 28px rgba(15,23,42,.15); padding:10px; }
.workplan-native .workplan-popover.show{ display:block; }
.workplan-native .workplan-density-pop{ width:154px; }
.workplan-native .workplan-density-pop button{ width:100%; height:34px; border:0; background:#fff; border-radius:5px; text-align:left; padding:0 10px; color:#334155; cursor:pointer; }
.workplan-native .workplan-density-pop button:hover, .workplan-native .workplan-density-pop button.active{ background:#eaf2ff; color:#1677ff; }
.workplan-native .workplan-fields-pop{ width:285px; }
.workplan-native .workplan-fields-pop h4{ margin:0 0 8px; font-size:14px; }
.workplan-native .workplan-field-list{ max-height:280px; overflow:auto; display:grid; grid-template-columns:1fr 1fr; gap:2px; }
.workplan-native .workplan-field-list label{ height:30px; display:flex; align-items:center; gap:7px; padding:0 5px; border-radius:4px; color:#334155; }
.workplan-native .workplan-field-list label:hover{ background:#f4f8ff; }
.workplan-native .workplan-fields-foot{ display:flex; justify-content:flex-end; gap:7px; padding-top:9px; border-top:1px solid #edf2f7; margin-top:8px; }
.workplan-native #view-actions .action-history-table.compact th{ height:36px; }
.workplan-native #view-actions .action-history-table.compact td{ padding-top:7px; padding-bottom:7px; }
.workplan-native #view-actions .action-history-table.relaxed td{ padding-top:16px; padding-bottom:16px; }
.workplan-native .search{ width: 230px; height: 32px; padding: 0 10px; border: 1px solid #d9e0ea; border-radius: 5px; outline: none; }
.workplan-native .search:focus{ border-color: #8fb5ff; box-shadow: 0 0 0 3px rgba(70, 135, 245, .09); }
.workplan-native select.filter{ height: 32px; padding: 0 26px 0 9px; border: 1px solid #d9e0ea; border-radius: 5px; color: #596981; background: #fff; }
.workplan-native .spacer{ flex: 1; }
.workplan-native .tabs{ display: flex; gap: 20px; padding: 0 16px; border-bottom: 1px solid var(--line); background: #fff; border-radius: var(--radius) var(--radius) 0 0; }
.workplan-native .tab{ position: relative; padding: 13px 1px 11px; border: 0; color: #738097; background: transparent; }
.workplan-native .tab.active{ color: var(--blue); font-weight: 700; }
.workplan-native .tab.active::after{ content: ""; position: absolute; right: 0; bottom: -1px; left: 0; height: 2px; background: var(--blue); }
.workplan-native table{ width: 100%; border-spacing: 0; border-collapse: collapse; }
.workplan-native th{ height: 40px; padding: 0 14px; color: #66758d; background: #f8fafc; text-align: left; font-size: 12px; font-weight: 600; white-space: nowrap; }
.workplan-native td{ min-height: 52px; padding: 12px 14px; border-top: 1px solid #edf0f5; color: #526176; vertical-align: middle; }
.workplan-native tr:hover td{ background: #fbfdff; }
.workplan-native .name-cell{ color: #243a5a; font-weight: 700; }
.workplan-native .secondary{ display: block; margin-top: 2px; color: #929daf; font-size: 12px; font-weight: 400; }
.workplan-native .text-blue{ color: var(--blue); }
.workplan-native .text-muted{ color: #8c98a9; }
.workplan-native .priority{ display: inline-flex; align-items: center; gap: 4px; font-size: 12px; }
.workplan-native .priority::before{ content: ""; width: 6px; height: 6px; border-radius: 50%; background: #8ba0bb; }
.workplan-native .priority.high::before{ background: var(--red); }
.workplan-native .priority.mid::before{ background: #f2a52a; }
.workplan-native .priority.low::before{ background: #5aa98a; }
.workplan-native .empty-note{ padding: 13px 16px; color: #7d8b9e; border-top: 1px solid #edf0f5; background: #fbfcfe; font-size: 12px; }
.workplan-native .trip-layout{ display: grid; grid-template-columns: 330px 1fr; min-height: 520px; }
.workplan-native .trip-list{ border-right: 1px solid var(--line); background: #fff; }
.workplan-native .trip-item{ width: 100%; padding: 15px 16px; border: 0; border-bottom: 1px solid #edf0f5; background: #fff; text-align: left; }
.workplan-native .trip-item:hover, .workplan-native .trip-item.active{ background: #f6f9ff; }
.workplan-native .trip-item.active{ border-left: 3px solid var(--blue); padding-left: 13px; }
.workplan-native .trip-date{ color: #8a96a8; font-size: 12px; }
.workplan-native .trip-name{ margin: 4px 0; color: #2c405f; font-weight: 700; }
.workplan-native .trip-sub{ color: #77869b; font-size: 12px; }
.workplan-native .trip-detail{ padding: 20px 22px; background: #fff; }
.workplan-native .trip-title{ display: flex; justify-content: space-between; align-items: flex-start; }
.workplan-native .trip-title h2{ margin: 0; color: #1f3454; font-size: 18px; }
.workplan-native .detail-meta{ display: flex; gap: 18px; margin: 14px 0 18px; color: #748299; font-size: 12px; }
.workplan-native .itinerary{ margin-top: 12px; }
.workplan-native .itinerary-row{ display: grid; grid-template-columns: 70px 1fr 110px 105px 82px; align-items: center; min-height: 66px; border-bottom: 1px solid #edf0f5; gap: 8px; }
.workplan-native .time{ color: #5575a3; font-weight: 700; }
.workplan-native .result-box{ margin-top: 20px; padding: 14px; border: 1px solid #dce8fb; border-radius: 8px; background: #f8fbff; }
.workplan-native .result-box h3{ margin: 0 0 7px; color: #42699d; font-size: 13px; }
.workplan-native .result-box p{ margin: 0; color: #637996; font-size: 12px; }
.workplan-native #view-trips .page-head{ margin-bottom:10px; }
.workplan-native .trip-table-wrap{ overflow:auto; cursor:default; }
.workplan-native /* 宽屏时表格与分页共用容器右边界;不足 1560px 才横向滚动。 */
.trip-table{ width:100%; min-width:1560px; table-layout:fixed; }
.workplan-native .trip-table th, .workplan-native .trip-table td{ vertical-align:middle; }
.workplan-native .trip-table th:first-child, .workplan-native .trip-table td:first-child{ position:sticky; left:0; z-index:2; background:#fff; }
.workplan-native .trip-table th:first-child{ z-index:4; background:#f7f9fc; }
.workplan-native .trip-table th:last-child, .workplan-native .trip-table td:last-child{ position:sticky; right:0; z-index:2; background:#fff; box-shadow:-10px 0 14px -15px rgba(26,49,84,.38); white-space:nowrap; }
.workplan-native .trip-table th:last-child{ z-index:4; background:#f7f9fc; }
.workplan-native .trip-table tbody tr:hover td:first-child, .workplan-native .trip-table tbody tr:hover td:last-child{ background:#f8fbff; }
.workplan-native #trip-table-body td:last-child .link{ display:inline-block; padding:4px 0; line-height:20px; }
.workplan-native #trip-table-body td:last-child .link + .link{ margin-left:12px; }
.workplan-native #view-trips .trip-table.compact th{ height:36px; }
.workplan-native #view-trips .trip-table.compact td{ padding-top:7px; padding-bottom:7px; }
.workplan-native #view-trips .trip-table.relaxed th{ height:50px; }
.workplan-native #view-trips .trip-table.relaxed td{ padding-top:17px; padding-bottom:17px; }
.workplan-native .trip-count{ color:#294e82; font-weight:700; }
.workplan-native .trip-summary-status{ color:#8b98aa; font-size:12px; }
.workplan-native /* 出差主表单本身承载“出差计划 + 多条拜访明细”,使用宽版弹窗,避免把明细表压成卡片式录入。 */
.trip-form-modal{ width:min(1480px,calc(100vw - 48px)); max-height:calc(100vh - 32px); }
.workplan-native .trip-form-section{ margin:0 20px 16px; border:1px solid #dce4ee; border-radius:7px; overflow:hidden; }
.workplan-native .trip-form-section-head{ display:flex; align-items:center; justify-content:space-between; min-height:44px; padding:0 14px; border-bottom:1px solid #e4ebf3; color:#203b61; font-weight:700; }
.workplan-native .trip-form-section .form-grid{ padding:16px 14px; }
.workplan-native .trip-visit-table-wrap{ overflow:auto; }
.workplan-native .trip-visit-form-table{ width:1420px; min-width:1420px; table-layout:fixed; border-collapse:collapse; }
.workplan-native .trip-visit-form-table th, .workplan-native .trip-visit-form-table td{ padding:8px; border-bottom:1px solid #e8edf3; text-align:left; vertical-align:middle; }
.workplan-native .trip-visit-form-table th{ height:38px; background:#f7f9fc; color:#526985; font-size:12px; font-weight:700; }
.workplan-native .trip-visit-form-table tr:last-child td{ border-bottom:0; }
.workplan-native .trip-visit-form-table input, .workplan-native .trip-visit-form-table select{ width:100%; height:32px; padding:0 8px; border:1px solid #d5dfeb; border-radius:4px; color:#334a68; background:#fff; font:inherit; }
.workplan-native .trip-visit-add{ display:flex; justify-content:center; padding:10px; border-top:1px solid #e4ebf3; }
.workplan-native /* 详情与编辑采用同一宽度等级,拜访安排以完整明细表呈现。 */
.trip-detail-modal{ width:min(1480px,calc(100vw - 48px)); max-height:calc(100vh - 32px); }
.workplan-native .trip-detail-meta{ display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:0; padding:0 20px; border-bottom:1px solid #e7edf4; }
.workplan-native .trip-detail-meta div{ padding:13px 12px; border-right:1px solid #edf1f6; }
.workplan-native .trip-detail-meta div:first-child{ padding-left:0; }
.workplan-native .trip-detail-meta div:last-child{ border-right:0; }
.workplan-native .trip-detail-meta small{ display:block; margin-bottom:4px; color:#8a97a8; font-size:12px; }
.workplan-native .trip-detail-meta b{ color:#29435f; font-size:13px; font-weight:600; }
.workplan-native .trip-detail-section{ padding:18px 20px 0; }
.workplan-native .trip-detail-section-head{ display:flex; align-items:center; justify-content:space-between; margin-bottom:10px; color:#203b61; font-weight:700; }
.workplan-native .trip-detail-visit-table{ width:100%; min-width:1360px; table-layout:fixed; }
.workplan-native .trip-detail-visit-table th, .workplan-native .trip-detail-visit-table td{ padding:10px 9px; }
.workplan-native .trip-detail-visit-table td:last-child .link{ display:inline-block; padding:4px 0; line-height:20px; }
.workplan-native .trip-detail-visit-table td:last-child .link + .link{ margin-left:12px; }
.workplan-native .trip-summary-grid{ display:grid; grid-template-columns:1.25fr 1fr 1fr; gap:12px; padding-bottom:20px; }
.workplan-native .trip-summary-grid .detail-row{ padding:0; border:0; }
.workplan-native .review-grid{ display: grid; grid-template-columns: 1.25fr .75fr; gap: 14px; }
.workplan-native .review-list{ padding: 0 16px; }
.workplan-native .review-item{ padding: 15px 0; border-bottom: 1px solid #edf0f5; }
.workplan-native .review-item:last-child{ border-bottom: 0; }
.workplan-native .review-head{ display: flex; justify-content: space-between; gap: 14px; }
.workplan-native .review-name{ color: #293e5d; font-weight: 700; }
.workplan-native .review-body{ margin-top: 5px; color: #6c7b92; font-size: 12px; }
.workplan-native .review-next{ margin-top: 7px; color: #315e9e; font-size: 12px; }
.workplan-native .banner{ display: flex; align-items: center; justify-content: space-between; padding: 17px 18px; margin-bottom: 14px; border: 1px solid #d5e6ff; border-radius: var(--radius); background: linear-gradient(100deg, #f1f7ff, #fbfdff); }
.workplan-native .banner h2{ margin: 0; color: #24538f; font-size: 16px; }
.workplan-native .banner p{ margin: 4px 0 0; color: #657993; font-size: 12px; }
.workplan-native .team-grid{ display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-bottom: 14px; }
.workplan-native .team-card{ padding: 15px; border: 1px solid var(--line); border-radius: var(--radius); background: #fff; }
.workplan-native .team-card b{ display: block; color: #243c60; font-size: 18px; }
.workplan-native .team-card span{ color: #7d8a9d; font-size: 12px; }
.workplan-native .team-card .team-note{ margin-top: 8px; color: #315e9e; font-size: 12px; }
.workplan-native .double50{ display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 14px; }
.workplan-native .double50-item{ position: relative; overflow: hidden; padding: 16px 18px; border: 1px solid #dce8fb; border-radius: var(--radius); background: #fff; box-shadow: var(--shadow); }
.workplan-native .double50-item.increment{ border-color: #e9defe; }
.workplan-native .double50-item::after{ content: "50"; position: absolute; right: 18px; top: 3px; color: #eaf2ff; font-size: 74px; font-weight: 800; line-height: 1; }
.workplan-native .double50-item.increment::after{ color: #f2ecff; }
.workplan-native .double50-title{ position: relative; color: #34547f; font-weight: 700; }
.workplan-native .double50-title span{ margin-left: 7px; color: #8593a7; font-size: 12px; font-weight: 400; }
.workplan-native .double50-num{ position: relative; margin: 6px 0 4px; color: #173d70; font-size: 24px; font-weight: 700; }
.workplan-native .double50-item.increment .double50-num{ color: #6548ad; }
.workplan-native .double50-desc{ position: relative; color: #718097; font-size: 12px; }
.workplan-native .double50-bar{ position: relative; height: 6px; overflow: hidden; margin-top: 11px; border-radius: 6px; background: #e9eff8; }
.workplan-native .double50-bar i{ display: block; height: 100%; border-radius: inherit; background: #3975d6; }
.workplan-native .double50-item.increment .double50-bar{ background: #eee9f8; }
.workplan-native .double50-item.increment .double50-bar i{ background: #8865d9; }
.workplan-native .calendar-layout{ display: grid; grid-template-columns: minmax(720px, 1fr) 300px; gap: 14px; }
.workplan-native .calendar-tools{ display:flex; align-items:center; gap:9px; height:45px; padding:7px 15px; border-bottom:1px solid #edf0f5; }
.workplan-native .calendar-tools b{ color: #263d60; font-size: 16px; }
.workplan-native .calendar-add-plan{ margin-left: 3px; }
.workplan-native .calendar-tools .spacer{ flex: 1; }
.workplan-native .calendar-grid{ display: grid; grid-template-columns: repeat(7, 1fr); }
.workplan-native .calendar-week{ padding:6px 10px; color:#7b889a; background:#f8fafc; text-align:center; font-size:12px; font-weight:700; }
.workplan-native .calendar-cell{ position: relative; min-height: 114px; padding: 8px; border-top: 1px solid #edf0f5; border-right: 1px solid #edf0f5; background: #fff; cursor: default; }
.workplan-native .calendar-cell:nth-child(7n){ border-right: 0; }
.workplan-native .calendar-cell.mute{ background: #fbfcfe; }
.workplan-native .calendar-cell.today{ background: #f5f9ff; box-shadow: inset 0 0 0 1px #8bb5ff; }
.workplan-native .calendar-cell.selected{ background: #f2f7ff; box-shadow: inset 0 0 0 2px var(--blue); }
.workplan-native .calendar-day{ display: flex; justify-content: space-between; margin-bottom: 6px; color: #68778f; font-size: 12px; cursor:pointer; }
.workplan-native .calendar-cell.today .calendar-day b{ display: grid; place-items: center; width: 21px; height: 21px; margin-top: -3px; border-radius: 50%; color: #fff; background: var(--blue); }
.workplan-native .calendar-chip{ display:flex; align-items:center; width:100%; min-width:0; overflow:hidden; margin:3px 0; padding:3px 5px; border:0; border-radius:4px; color:#3d587e; background:#edf4ff; text-align:left; white-space:nowrap; font-size:11px; cursor:pointer; }
.workplan-native .calendar-chip-type{ flex:0 0 auto; font-weight:700; }
.workplan-native .calendar-chip-divider{ flex:0 0 auto; margin:0 3px; opacity:.55; }
.workplan-native .calendar-chip-object{ min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.workplan-native .calendar-chip.visit{ color: #087e5d; background: #eafbf4; }
.workplan-native .calendar-chip.travel{ color: #9a5a00; background: #fff5e3; }
.workplan-native .calendar-chip.solution{ color: #6550a7; background: #f1edff; }
.workplan-native .calendar-chip.fbb{ color: #9c3e63; background: #fff0f5; }
.workplan-native .calendar-chip:hover{ filter: brightness(.97); }
.workplan-native .calendar-add{ position: absolute; top: 6px; right: 6px; display: none; place-items: center; width: 20px; height: 20px; padding: 0; border: 0; border-radius: 4px; color: #2563eb; background: #e9f1ff; font-weight: 700; font-size: 15px; line-height: 1; cursor:pointer; }
.workplan-native .calendar-cell:hover .calendar-add, .workplan-native .calendar-cell.selected .calendar-add{ display: grid; }
.workplan-native .calendar-add:hover{ color: #fff; background: var(--blue); }
.workplan-native .calendar-chip.calendar-chip-overflowed{display:none!important}
.workplan-native .calendar-more{display:block;width:100%;margin:4px 0 0;padding:1px 5px;border:0;background:transparent;color:#5a82bd;text-align:left;font-size:11px;cursor:pointer}
.workplan-native .calendar-more:hover{color:var(--blue);text-decoration:underline}
.workplan-native .daily-list{ padding: 4px 15px 12px; }
.workplan-native .daily-item{ width: 100%; display:block; padding:12px 0; border:0; border-bottom:1px solid #edf0f5; color:inherit; background:transparent; text-align:left; }
.workplan-native .daily-primary{ display:grid; grid-template-columns:max-content minmax(0,1fr) max-content; align-items:center; gap:7px; }
.workplan-native .daily-subject{ overflow:hidden; color:#304764; text-overflow:ellipsis; white-space:nowrap; font-size:13px; font-weight:700; }
.workplan-native .daily-action{ min-width:0; overflow:hidden; color:#304764; text-overflow:ellipsis; white-space:nowrap; font-size:13px; font-weight:700; }
.workplan-native .daily-subject::after{ content:'|'; margin-left:7px; color:#a5b0bf; font-weight:400; }
.workplan-native .daily-item:hover .daily-subject, .workplan-native .daily-item:hover .daily-action{ color:var(--blue); }
.workplan-native .daily-time{ color:#7c8ca4; font-size:11px; white-space:nowrap; }
.workplan-native .daily-meta{ margin-top:4px; color:#8593a7; font-size:11px; }
.workplan-native .daily-empty{ padding: 26px 10px 18px; color: #8a97a9; text-align: center; font-size: 12px; }
.workplan-native .daily-actions{ padding: 0 15px 15px; }
.workplan-native .detail-grid{ padding: 2px 20px 8px; }
.workplan-native .detail-row{ padding: 12px 0; border-bottom: 1px solid #edf0f5; }
.workplan-native .detail-row:last-child{ border-bottom: 0; }
.workplan-native .detail-label{ color: #8895a8; font-size: 12px; }
.workplan-native .detail-value{ margin-top: 4px; color: #304764; line-height: 1.7; }
.workplan-native .team-calendar{ overflow: hidden; }
.workplan-native .team-calendar-head, .workplan-native .team-calendar-row{ display: grid; grid-template-columns: 130px repeat(5, 1fr); }
.workplan-native .team-calendar-head > div{ padding: 9px 10px; color: #718097; background: #f8fafc; font-size: 12px; font-weight: 700; }
.workplan-native .team-calendar-row > div{ min-height: 64px; padding: 9px 10px; border-top: 1px solid #edf0f5; border-right: 1px solid #edf0f5; }
.workplan-native .team-calendar-row > div:last-child{ border-right: 0; }
.workplan-native .member-name{ color: #2e4667; font-weight: 700; }
.workplan-native .member-name small{ display: block; margin-top: 2px; color: #8a98aa; font-weight: 400; }
.workplan-native .team-plan{ display: block; margin: 2px 0; overflow: hidden; padding: 2px 4px; border-radius: 3px; color: #4d6990; background: #edf4ff; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; }
.workplan-native .team-plan.warn{ color: #a76000; background: #fff4df; }
.workplan-native .modal-mask{ position: fixed; inset: 0; z-index: 300; display: none; align-items: center; justify-content: center; background: rgba(18, 31, 51, .38); }
.workplan-native .modal-mask.show{ display: flex; }
.workplan-native .modal{ width: 690px; max-height: calc(100vh - 80px); overflow: auto; border-radius: 11px; background: #fff; box-shadow: 0 18px 50px rgba(10, 31, 60, .22); }
.workplan-native /* 必须写在通用 .modal 之后,避免通用宽度把出差主表/详情重新覆盖成窄弹窗。 */
.modal.trip-form-modal, .workplan-native .modal.trip-detail-modal{ width:min(1480px,calc(100vw - 48px)); max-height:calc(100vh - 32px); }
.workplan-native .modal-head{ display: flex; align-items: center; justify-content: space-between; padding: 17px 20px; border-bottom: 1px solid var(--line); }
.workplan-native .modal-head h2{ margin: 0; color: #203656; font-size: 17px; }
.workplan-native .x{ width: 28px; height: 28px; border: 0; border-radius: 5px; color: #6f7d91; background: transparent; font-size: 21px; line-height: 20px; }
.workplan-native .x:hover{ background: #f2f5f8; }
.workplan-native .modal-body{ padding: 19px 20px 10px; }
.workplan-native .form-grid{ display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.workplan-native .field.full{ grid-column: 1 / -1; }
.workplan-native .completion-only{ display:none; }
.workplan-native .completion-only.show{ display:block; }
.workplan-native label{ display: block; margin-bottom: 6px; color: #53627a; font-size: 12px; }
.workplan-native label .required{ color: var(--red); }
.workplan-native .field input, .workplan-native .field select, .workplan-native .field textarea{ width: 100%; border: 1px solid #d8e0eb; border-radius: 5px; padding: 8px 9px; outline: 0; color: #34445e; background: #fff; }
.workplan-native .field textarea{ min-height: 68px; resize: vertical; }
.workplan-native .field input:focus, .workplan-native .field select:focus, .workplan-native .field textarea:focus{ border-color: #76a8ff; box-shadow: 0 0 0 3px rgba(52, 115, 240, .1); }
.workplan-native .form-tip{ padding: 9px 11px; margin: 16px 0 2px; border-radius: 5px; color: #627795; background: #f5f9ff; font-size: 12px; }
.workplan-native .modal-foot{ display: flex; justify-content: flex-end; gap: 9px; padding: 14px 20px 18px; }
.workplan-native .toast{ position: fixed; z-index: 330; top: 74px; left: 50%; display: none; transform: translateX(-50%); padding: 9px 14px; border-radius: 6px; color: #fff; background: #243c60; box-shadow: 0 5px 18px rgba(20, 40, 70, .2); }
.workplan-native .toast.show{ display: block; }
.workplan-native .toast.success{ background: #07835b; }
.workplan-native .boundary{ margin-top: 18px; color: #9aa6b6; font-size: 12px; text-align: center; }
.workplan-native /* S1 试嵌:严格沿用三层系统框架的高度与内容区节奏 */
body{ min-width:1180px; background:#f3f6fb; color:#0f172a; }
.workplan-native .main{ width:100%; padding:0; }
.workplan-native .page-head{ margin-bottom:7px; }
.workplan-native h1{ color:#0f2f58; font-size:20px; }
.workplan-native .sub{ color:#64748b; font-size:13px; }
.workplan-native .btn{ height:32px; border-radius:6px; }
.workplan-native .card{ border-color:#dbe4ef; border-radius:8px; box-shadow:none; }
.workplan-native .double50{ display:block; margin:0 0 12px; }
.workplan-native .double50-master{ overflow:hidden; border:1px solid #dbe4ef; border-radius:8px; background:#fff; }
.workplan-native .double50-master-head{ display:flex; align-items:center; justify-content:space-between; min-height:34px; padding:0 14px; border-bottom:1px solid #edf1f6; color:#1c3f70; font-size:13px; font-weight:700; }
.workplan-native .double50-master-head small{ color:#7b8ba2; font-size:11px; font-weight:400; }
.workplan-native .double50-master-body{ display:grid; grid-template-columns:1fr 1fr; }
.workplan-native .double50-segment{ display:grid; grid-template-columns:1fr auto; gap:4px 14px; min-height:62px; padding:10px 14px; }
.workplan-native .double50-segment + .double50-segment{ border-left:1px solid #edf1f6; }
.workplan-native .double50-segment b{ color:#274a7e; font-size:14px; }
.workplan-native .double50-segment span{ color:#7c8ca3; font-size:12px; }
.workplan-native .double50-segment .d50-num{ color:#173e72; font-size:20px; font-weight:700; line-height:1; }
.workplan-native .double50-segment .d50-finish{ color:#0b9d58; font-size:12px; text-align:right; }
.workplan-native .double50-segment.increment b, .workplan-native .double50-segment.increment .d50-num{ color:#6949aa; }
.workplan-native .double50-segment.increment .d50-finish{ color:#8156c7; }
.workplan-native .double50-note{ grid-column:1 / -1; color:#8b98aa; font-size:11px; }
.workplan-native .d50-progress{ grid-column:1 / -1; display:block; height:5px; overflow:hidden; border-radius:5px; background:#e9eff8; }
.workplan-native .d50-progress i{ display:block; height:100%; border-radius:inherit; background:#2f6fda; }
.workplan-native .double50-segment.increment .d50-progress{ background:#eee9f8; }
.workplan-native .double50-segment.increment .d50-progress i{ background:#8662d4; }
.workplan-native .calendar-layout{ align-items:stretch; }
.workplan-native .day-side{ height:638px; min-height:0; overflow-y:auto; }
.workplan-native .day-focus{ margin-top:auto; padding:11px 14px 8px; border-top:1px solid #edf0f5; }
.workplan-native .day-focus-head{ display:flex; align-items:center; justify-content:space-between; margin-bottom:5px; color:#314a70; font-size:12px; }
.workplan-native .focus-compact{ display:grid; grid-template-columns:1fr auto; align-items:end; gap:2px 8px; padding:7px 0; border-bottom:1px solid #edf0f5; }
.workplan-native .focus-compact:last-child{ border-bottom:0; }
.workplan-native .focus-compact span{ display:block; color:#8a97aa; font-size:10px; }
.workplan-native .focus-compact b{ color:#314a70; font-size:12px; }
.workplan-native .focus-compact em{ color:#6b7c94; font-size:11px; font-style:normal; }
.workplan-native .focus-compact i{ grid-column:1 / -1; display:block; height:3px; overflow:hidden; border-radius:3px; background:#edf0f5; }
.workplan-native .focus-compact u{ display:block; height:100%; border-radius:inherit; background:#2f6fda; text-decoration:none; }
.workplan-native .day-metrics{ padding:10px 14px 12px; border-top:1px solid #edf0f5; }
.workplan-native .metric-mini-list > div{ min-height:30px; display:flex; align-items:center; justify-content:space-between; border-bottom:1px solid #edf1f5; }
.workplan-native .metric-mini-list > div:last-child{ border-bottom:0; }
.workplan-native .metric-mini-list span{ color:#8794a7; font-size:11px; }
.workplan-native .metric-mini-list b{ color:#214a80; font-size:15px; line-height:1; }
.workplan-native .metric-mini-list .warn b{ color:#dc3c3c; }
.workplan-native .metric-mini-list .good b{ color:#0f9f6e; }
.workplan-native .double50-legacy{ display:grid; grid-template-columns:1fr 1fr; gap:12px; margin-bottom:12px; }
.workplan-native .d50-legacy-card{ position:relative; height:94px; overflow:hidden; padding:12px 18px 10px; border:1px solid #d7e4ff; border-radius:8px; background:#fff; }
.workplan-native .d50-legacy-card.growth{ border-color:#e5d9ff; }
.workplan-native .d50-watermark{ position:absolute; top:1px; right:17px; color:#edf4ff; font-size:56px; font-weight:800; line-height:1; }
.workplan-native .growth .d50-watermark{ color:#f3edff; }
.workplan-native .d50-legacy-title{ position:relative; color:#214a80; font-size:13px; font-weight:700; }
.workplan-native .d50-legacy-title small{ margin-left:7px; color:#8391a7; font-size:11px; font-weight:400; }
.workplan-native .growth .d50-legacy-title, .workplan-native .growth .d50-legacy-number{ color:#6b49aa; }
.workplan-native .d50-legacy-number{ position:relative; margin-top:6px; color:#153f73; font-size:21px; font-weight:700; line-height:1; }
.workplan-native .d50-legacy-desc{ position:relative; margin-top:5px; color:#7e8da3; font-size:11px; }
.workplan-native .d50-legacy-bar{ position:absolute; right:18px; bottom:11px; left:18px; height:5px; overflow:hidden; border-radius:5px; background:#e7eef9; }
.workplan-native .d50-legacy-bar i{ display:block; height:100%; border-radius:inherit; background:#2f6fda; }
.workplan-native .growth .d50-legacy-bar{ background:#eee8fa; }
.workplan-native .growth .d50-legacy-bar i{ background:#8861d7; }
.workplan-native .workbench-surface{ overflow:hidden; background:#fff; }
.workplan-native .workbench-surface .double50-legacy{ gap:0; margin:0; border-bottom:1px solid #edf1f6; }
.workplan-native .workbench-surface .d50-legacy-card{ height:100px; border:0; border-radius:0; background:transparent; }
.workplan-native .workbench-surface .d50-legacy-card + .d50-legacy-card{ border-left:1px solid #edf1f6; }
.workplan-native .workbench-surface .d50-legacy-desc{ margin-top:7px; }
.workplan-native .workbench-surface .d50-legacy-bar{ bottom:9px; }
.workplan-native .workbench-surface .calendar-layout{ gap:0; }
.workplan-native .workbench-surface .day-side{ border:0; border-left:1px solid #edf1f6; border-radius:0; box-shadow:none; }
.workplan-native .preview-add{ border-color:#e0e6ee; color:#526174; background:#f4f6f8; box-shadow:none; }
.workplan-native .preview-add:hover{ border-color:#cdd8e5; color:#315e9e; background:#eef2f6; }
/* 计划管理、出差与拜访采用 S1 列表页统一壳:标题栏、查询栏、表格连续衔接。 */
.workplan-native #view-actions .workplan-list-titlebar,
.workplan-native #view-trips .workplan-list-titlebar{height:50px;display:flex;align-items:center;padding:8px 12px;box-sizing:border-box;border:1px solid #d6e0ec;border-bottom:0;border-radius:8px 8px 0 0;background:#fff;color:#172033;font-size:17px;font-weight:800;line-height:32px}
.workplan-native #view-actions .workplan-list-titlebar + .card,
.workplan-native #view-trips .workplan-list-titlebar + .card{overflow:hidden;border-top:0;border-radius:0 0 8px 8px}
.workplan-native #view-actions .page-tools,
.workplan-native #view-trips .page-tools{height:48px;min-height:48px;box-sizing:border-box;margin:0;padding:7px 12px;gap:8px;border:0;border-bottom:1px solid #d6e0ec;border-radius:0;background:#f8fbff;font-size:13px}
.workplan-native #view-actions .page-tools .search,
.workplan-native #view-trips .page-tools .search{width:270px;height:32px;box-sizing:border-box;border-color:#cfd9e5;border-radius:4px;font-size:13px}
.workplan-native #view-actions .page-tools select.filter,
.workplan-native #view-trips .page-tools select.filter{height:32px;box-sizing:border-box;border-color:#cfd9e5;border-radius:4px;font-size:13px}
.workplan-native #view-actions .page-tools .btn,
.workplan-native #view-trips .page-tools .btn{height:32px;padding:0 13px;font-size:13px}
.workplan-native #view-actions .page-tools .workplan-list-tools,
.workplan-native #view-trips .page-tools .workplan-list-tools{gap:4px;margin-left:0;padding-left:7px;border-left:1px solid #dde5ee}
.workplan-native #view-actions .action-history-table,
.workplan-native #view-trips .trip-table{font-size:13px}
.workplan-native #view-actions .action-history-table th,
.workplan-native #view-trips .trip-table th{height:40px;box-sizing:border-box;padding:0 10px;background:#f6f9fd;color:#475569;font-size:13px;font-weight:600}
.workplan-native #view-actions .action-history-table td,
.workplan-native #view-trips .trip-table td{height:52px;box-sizing:border-box;padding:7px 10px;color:#334155;font-size:13px;line-height:1.45}
.workplan-native #view-actions .name-cell{color:#1677ff;cursor:pointer}
.workplan-native #view-actions .name-cell:hover{text-decoration:underline}
.workplan-native #view-actions .link,
.workplan-native #view-trips .link{color:#1677ff;font-size:13px;cursor:pointer}
</style>
<script id="workplan-static-migration-script">
const workplanModule=(()=>{
const viewTitles = { dashboard: '工作日历', actions: '计划管理', trips: '出差与拜访' };
function wp_switchView(view) {
if (!viewTitles[view]) return;
document.querySelectorAll('.view').forEach(v => v.classList.remove('active'));
document.getElementById('view-' + view).classList.add('active');
document.querySelectorAll('.s1-submenu-item[data-view]').forEach(b => b.classList.toggle('active', b.dataset.view === view));
window.scrollTo({ top: 0, behavior: 'smooth' });
}
function wp_openModal(id) {
const target = document.getElementById(id);
if (!target) return;
const currentTop = [...document.querySelectorAll('.workplan-native .modal-mask.show')]
.filter(mask => mask !== target)
.reduce((top, mask) => Math.max(top, Number(getComputedStyle(mask).zIndex) || 300), 300);
target.style.zIndex = String(currentTop + 10);
target.classList.add('show');
}
function wp_closeModal(id) {
const target = document.getElementById(id);
if (!target) return;
target.classList.remove('show');
target.style.removeProperty('z-index');
if (id === 'action-modal' && typeof restoreHomeWorkplanModal === 'function') restoreHomeWorkplanModal();
}
function wp_maskClose(e, id) { if (e.target.id === id) wp_closeModal(id); }
let selectedDate = '2026-07-29';
let selectedAction = null;
let editingAction = null;
const dailyPlans = {
'2026-07-29': [
{ time: '14:30', name: '海康威视|智慧园区方案沟通', type: '方案沟通', attribution: 'stock', object: '项目 · 存量经营', relatedObject: '海康威视智慧园区(项目)', status: '未完成', plan: '与甲方沟通方案重点及后续汇报安排。', result: '未完成时待补充客户反馈、风险。', next: '完成后填写下次动作与时间。' },
{ time: '16:00', name: '华润置地|投标方案回访', type: '电话沟通', attribution: 'growth', object: '潜在客户 · 增量开发', relatedObject: '华润置地(潜在客户)', status: '未完成', plan: '核对客户当前关注点与下次沟通人。', result: '未完成时待补充本次回访结果。', next: '确认是否安排现场沟通。' },
{ time: '全天', name: '望江党校|项目资料补充', type: '资料补充', attribution: 'stock', object: '项目 · 存量经营', relatedObject: '望江党校(项目)', status: '未完成', plan: '补齐本次沟通所需资料与说明。', result: '未完成。', next: '补充资料并记录结果。' }
]
};
let editingTripId = null;
let tripModalVisits = [];
const tripRecords = [
{ id: 1, title: '南山区客户拜访', start: '2026-07-29', end: '2026-07-30', region: '深圳南山区', owner: '张明远', purpose: '推进重点客户与项目沟通。', summary: '', visits: [
{ time: '2026-07-29T10:00', objectType: 'customer', object: '华润置地', type: '客户拜访', item: '投标方案回访', plan: '确认客户关注点与后续沟通人。', status: '未完成' },
{ time: '2026-07-29T15:00', objectType: 'opportunity', object: '光电集团安防改造', type: '方案沟通', item: '技术交流', plan: '确认技术方案与对接人。', status: '未完成' },
{ time: '2026-07-30T10:00', objectType: 'opportunity', object: '光电集团安防改造', type: '客户拜访', item: '安防方案沟通', plan: '补充客户方案反馈。', status: '未完成' }
]},
{ id: 2, title: '广州项目现场走访', start: '2026-07-22', end: '2026-07-23', region: '广州番禺', owner: '张明远', purpose: '现场核实项目推进条件。', summary: '已完成现场踏勘与客户沟通。', visits: [
{ time: '2026-07-22T10:00', objectType: 'project', object: '菱湖院子二期', type: '跑工地', item: '现场踏勘', plan: '核实现场条件与改造范围。', status: '已完成' },
{ time: '2026-07-23T14:00', objectType: 'customer', object: '零亿', type: '客户拜访', item: '二期推进沟通', plan: '确认项目推进节奏。', status: '已完成' }
]},
{ id: 3, title: '武汉项目推进', start: '2026-08-01', end: '2026-08-01', region: '武汉东西湖区', owner: '张明远', purpose: '确认项目推进与后续拜访安排。', summary: '', visits: [
{ time: '2026-08-01T09:00', objectType: 'opportunity', object: '武汉卷烟厂改造', type: '客户拜访', item: '项目推进沟通', plan: '确认出差现场沟通安排。', status: '未完成' },
{ time: '2026-08-01T15:00', objectType: 'project', object: '望江党校', type: '方案沟通', item: '资料补充沟通', plan: '补充方案资料与说明。', status: '未完成' }
]},
{ id: 4, title: '湖南行业关系走访', start: '2026-08-05', end: '2026-08-06', region: '长沙 / 湘潭', owner: '张明远', purpose: '维护行业关系并安排重点客户沟通。', summary: '', visits: [
{ time: '2026-08-05T10:00', objectType: 'industry', object: '建筑业协会', type: '组会组局', item: '协会关键人联络', plan: '确认行业活动与关键人安排。', status: '未完成' },
{ time: '2026-08-05T15:00', objectType: 'customer', object: '零亿', type: '客户拜访', item: '客户关系维护', plan: '沟通二期工程推进条件。', status: '未完成' },
{ time: '2026-08-06T10:00', objectType: 'industry', object: '华南设计院', type: '方案沟通', item: '技术交流', plan: '沟通后续方案交流安排。', status: '未完成' },
{ time: '2026-08-06T15:00', objectType: 'customer', object: '华润置地', type: '电话沟通', item: '后续回访', plan: '确认投标方案回访时间。', status: '未完成' }
]}
];
const teamPlanFixtures = [
{ owner:'李思琪', attribution:'stock', status:'已完成' }, { owner:'李思琪', attribution:'growth', status:'未完成' },
{ owner:'赵雪梅', attribution:'stock', status:'已完成' }, { owner:'赵雪梅', attribution:'growth', status:'已完成' },
{ owner:'赵雪梅', attribution:'growth', status:'未完成' }, { owner:'王强', attribution:'stock', status:'未完成' }
];
// 演示页仅展示少量日历明细;下列为本月已沉淀历史工作项的统计基数。
const dual50MonthlyBase = { stock: { done: 34, total: 49 }, growth: { done: 29, total: 48 } };
function wp_dateLabel(dateKey) { const [, month, day] = dateKey.split('-'); return Number(month) + ' 月 ' + Number(day) + ' 日'; }
function wp_completionStatus(plan, dateKey = selectedDate) {
if (plan.status === '已完成') return '已完成';
if (plan.status === '已超时') return '已超时';
if (!/^\d{2}:\d{2}$/.test(plan.time || '')) return '未完成';
const plannedAt = new Date(dateKey + 'T' + plan.time + ':00');
return plannedAt < new Date() ? '已超时' : '未完成';
}
function wp_formatCalendarChip(chip) {
if (!chip || chip.dataset.formatted === '1') return;
const fullText = chip.textContent.trim();
const parts = fullText.split('|');
const type = parts.shift() || '工作计划';
const objectName = parts.join('|') || '未关联对象';
chip.dataset.formatted = '1';
chip.dataset.workType = type;
chip.dataset.objectName = objectName;
chip.title = fullText;
chip.textContent = '';
const typeNode = document.createElement('span');
typeNode.className = 'calendar-chip-type';
typeNode.textContent = type;
const dividerNode = document.createElement('span');
dividerNode.className = 'calendar-chip-divider';
dividerNode.textContent = '|';
const objectNode = document.createElement('span');
objectNode.className = 'calendar-chip-object';
objectNode.textContent = objectName;
chip.append(typeNode, dividerNode, objectNode);
}
function wp_renderCalendarPreview(cell) {
const chips = Array.from(cell.querySelectorAll('.calendar-chip'));
cell.querySelector('.calendar-more')?.remove();
chips.forEach(chip => chip.classList.remove('calendar-chip-overflowed'));
if (chips.length <= 3) return;
chips.slice(2).forEach(chip => chip.classList.add('calendar-chip-overflowed'));
const more = document.createElement('button');
more.className = 'calendar-more';
more.type = 'button';
more.textContent = '还有' + (chips.length - 2) + '项';
more.title = '查看当日全部' + chips.length + '项工作';
more.addEventListener('click', event => { event.stopPropagation(); wp_selectCalendarDate(cell.dataset.date); });
cell.appendChild(more);
}
function wp_getCellPlans(dateKey) {
if (dailyPlans[dateKey]) return dailyPlans[dateKey];
const cell = document.querySelector('.calendar-cell[data-date="' + dateKey + '"]');
if (!cell) return [];
return Array.from(cell.querySelectorAll('.calendar-chip')).map((chip, index) => {
const type = chip.dataset.workType || '工作计划';
const objectName = chip.dataset.objectName || '未关联对象';
const workItem = chip.dataset.workItem || type;
return { time: index ? '全天' : '待定', name: objectName + '|' + workItem, workItem, type, relatedObject: objectName, attribution: 'exclude', object: '待确认 CRM 对象 · ' + objectName, status: '未完成', plan: '待补充本次计划说明。', result: '未完成时待补充客户反馈、风险。', next: '完成后填写下次动作与时间。' };
});
}
function wp_renderDaySummary() {
const plans = wp_getCellPlans(selectedDate);
document.getElementById('day-summary-title').textContent = wp_dateLabel(selectedDate) + ' · 当日工作(' + plans.length + ')';
const list = document.getElementById('day-summary-list');
const calendarChips = Array.from(document.querySelectorAll('.calendar-cell[data-date="' + selectedDate + '"] .calendar-chip'));
list.innerHTML = plans.length ? plans.map((plan, index) => {
const nameParts = plan.name.split('|');
const chip = calendarChips[index];
const type = chip?.dataset.workType || plan.type || '工作计划';
const objectName = chip?.dataset.objectName || plan.relatedObject || nameParts[0] || '未关联对象';
const workItem = chip?.dataset.workItem || plan.workItem || (nameParts.length > 1 ? nameParts.slice(1).join('|') : plan.name);
const attribution = ({ stock:'存量经营', growth:'增量开发', exclude:'不纳入双50' }[plan.attribution] || '待确认');
return '<button class="daily-item" onclick="wp_openPlanFromSummary(' + index + ')"><span class="daily-primary"><span class="daily-subject">' + type + '</span><span class="daily-action">' + objectName + '</span><span class="daily-time">' + plan.time + '</span></span><span class="daily-meta">' + workItem + ' · ' + attribution + ' · ' + wp_completionStatus(plan) + '</span></button>';
}).join('') : '<div class="daily-empty">当天暂无工作计划<br>可点击下方按钮新增一条计划。</div>';
}
function wp_renderDual50() {
const plans = Object.entries(dailyPlans).filter(([date]) => date.startsWith('2026-07-')).flatMap(([, items]) => items);
const count = attribution => plans.filter(plan => plan.attribution === attribution);
const stock = count('stock');
const growth = count('growth');
const stockDone = dual50MonthlyBase.stock.done + stock.filter(plan => plan.status === '已完成').length;
const growthDone = dual50MonthlyBase.growth.done + growth.filter(plan => plan.status === '已完成').length;
const stockTotal = dual50MonthlyBase.stock.total + stock.length;
const growthTotal = dual50MonthlyBase.growth.total + growth.length;
document.getElementById('stock-summary').textContent = stockDone + ' / ' + stockTotal + ' 项';
document.getElementById('growth-summary').textContent = growthDone + ' / ' + growthTotal + ' 项';
const width = (done, total) => total ? Math.round(done / total * 100) : 0;
document.getElementById('stock-progress').style.width = width(stockDone, stockTotal) + '%';
document.getElementById('growth-progress').style.width = width(growthDone, growthTotal) + '%';
const teamPlans = plans.concat(teamPlanFixtures);
const teamStock = teamPlans.filter(plan => plan.attribution === 'stock');
const teamGrowth = teamPlans.filter(plan => plan.attribution === 'growth');
const set = (id, text) => { const el = document.getElementById(id); if (el) el.textContent = text; };
set('team-stock-planned', teamStock.length + ' 项计划');
set('team-stock-done', teamStock.filter(plan => plan.status === '已完成').length + ' 项完成');
set('team-growth-planned', teamGrowth.length + ' 项计划');
set('team-growth-done', teamGrowth.filter(plan => plan.status === '已完成').length + ' 项完成');
}
function wp_selectCalendarDate(dateKey) {
selectedDate = dateKey;
document.querySelectorAll('.calendar-cell').forEach(cell => cell.classList.toggle('selected', cell.dataset.date === dateKey));
wp_renderDaySummary();
}
function wp_openNewForSelected() { wp_openActionModal(); }
function wp_openPlanFromSummary(index) { wp_openActionDetail(wp_getCellPlans(selectedDate)[index]); }
function wp_openActionDetail(plan) {
selectedAction = plan;
document.getElementById('detail-title').textContent = plan.name;
const relation = plan.object || '—';
const relationParts = relation.split(' · ');
const attributionMap = { stock:'存量经营', growth:'增量开发', exclude:'不纳入双50' };
document.getElementById('detail-type').textContent = plan.type || '—';
document.getElementById('detail-time').textContent = plan.plannedText || (wp_dateLabel(selectedDate) + ' · ' + plan.time);
document.getElementById('detail-object-type').textContent = plan.objectType || relationParts[0] || '—';
document.getElementById('detail-object').textContent = plan.relatedObject || relationParts[1] || relation;
document.getElementById('detail-attribution').textContent = attributionMap[plan.attribution] || plan.attributionLabel || '不纳入双50';
document.getElementById('detail-status').textContent = wp_completionStatus(plan);
document.getElementById('detail-plan').textContent = plan.plan || '—';
document.getElementById('detail-result').textContent = plan.result || '尚未登记完成结果。';
wp_openModal('action-detail-modal');
}
function wp_editSelectedAction() {
if (!selectedAction) return;
wp_closeModal('action-detail-modal');
wp_openActionModal(selectedAction.type, selectedAction);
}
function wp_initCalendar() {
const cells = Array.from(document.querySelectorAll('.calendar-cell'));
cells.forEach((cell, index) => {
let dateKey;
if (index < 2) dateKey = '2026-06-' + (29 + index);
else if (index < 33) dateKey = '2026-07-' + String(index - 1).padStart(2, '0');
else dateKey = '2026-08-' + String(index - 32).padStart(2, '0');
cell.dataset.date = dateKey;
cell.querySelectorAll('.calendar-chip').forEach(wp_formatCalendarChip);
wp_renderCalendarPreview(cell);
const add = document.createElement('button');
add.className = 'calendar-add';
add.type = 'button';
add.textContent = '+';
add.title = '新增工作计划';
add.addEventListener('click', event => { event.stopPropagation(); wp_selectCalendarDate(dateKey); wp_openNewForSelected(); });
cell.appendChild(add);
const dayTrigger = cell.querySelector('.calendar-day');
if (dayTrigger) dayTrigger.addEventListener('click', event => { event.stopPropagation(); wp_selectCalendarDate(dateKey); });
});
const grid = document.querySelector('.calendar-grid');
grid.addEventListener('click', event => {
const chip = event.target.closest('.calendar-chip');
if (!chip) return;
event.preventDefault();
event.stopImmediatePropagation();
const cell = chip.closest('.calendar-cell');
wp_selectCalendarDate(cell.dataset.date);
if (chip.classList.contains('travel')) {
wp_switchView('trips');
wp_renderTripTable();
return;
}
const plans = wp_getCellPlans(cell.dataset.date);
const chipIndex = Array.from(cell.querySelectorAll('.calendar-chip')).indexOf(chip);
wp_openActionDetail(plans[chipIndex] || plans[0]);
}, true);
wp_selectCalendarDate(selectedDate);
wp_renderDual50();
}
function wp_toggleCompletionFields() {
const status = document.getElementById('action-status');
const group = document.getElementById('completion-fields');
if (!status || !group) return;
const completed = status.value === '已完成';
group.classList.toggle('show', completed);
const completedAt = document.getElementById('action-completed-at');
if (completed && completedAt && !completedAt.value) completedAt.value = document.getElementById('action-datetime').value;
}
function wp_openActionModal(type, plan) {
editingAction = plan || null;
document.getElementById('action-modal-title').textContent = plan ? '编辑工作计划' : '新增工作计划';
document.getElementById('action-type').value = type || '电话沟通';
document.getElementById('action-datetime').value = selectedDate + 'T14:00';
document.getElementById('action-object-type').value = '';
wp_loadObjectQueue();
document.getElementById('action-name').value = '';
document.getElementById('action-status').value = '未完成';
document.getElementById('action-attribution').value = '';
document.getElementById('action-plan-input').value = '';
document.getElementById('action-completed-at').value = '';
document.getElementById('action-result-input').value = '';
document.getElementById('action-risk-input').value = '';
if (plan) {
const relationType = plan.object?.includes('客户') ? 'customer' : plan.object?.includes('商机') ? 'opportunity' : plan.object?.includes('行业') ? 'industry' : 'project';
document.getElementById('action-object-type').value = relationType;
wp_loadObjectQueue();
document.getElementById('action-name').value = plan.name;
document.getElementById('action-status').value = wp_completionStatus(plan) === '已超时' ? '未完成' : wp_completionStatus(plan);
document.getElementById('action-attribution').value = plan.attribution || 'exclude';
document.getElementById('action-plan-input').value = plan.plan || '';
document.getElementById('action-completed-at').value = plan.completedAt || '';
document.getElementById('action-result-input').value = plan.completionResult || '';
document.getElementById('action-risk-input').value = plan.risk || '';
}
wp_changeActionHint();
wp_toggleCompletionFields();
document.getElementById('action-hint').textContent += ';“已超时”由系统根据计划完成时间自动识别。';
wp_openModal('action-modal');
}
function wp_tripSchedule(trip) {
const today = '2026-07-29';
return trip.end < today ? '已结束' : trip.start > today ? '未开始' : '进行中';
}
function wp_tripObjectLabel(type) { return ({ project:'项目', customer:'客户', opportunity:'商机', industry:'行业关系' }[type] || '—'); }
function wp_tripVisitCounts(trip) {
const done = trip.visits.filter(item => item.status === '已完成').length;
const overdue = trip.visits.filter(item => item.status === '已超时').length;
return { total: trip.visits.length, done, overdue };
}
function wp_nextTripVisit(trip) {
return trip.visits.find(item => item.status !== '已完成') || trip.visits[0];
}
const tripColumns = [['title','出差主题',true],['date','出差日期'],['region','出差地区'],['owner','负责人'],['visits','拜访安排'],['done','已完成 / 总项'],['overdue','已超时'],['next','下一场拜访'],['schedule','出差日程'],['actions','操作',true]];
let visibleTripColumns = new Set(tripColumns.map(item => item[0]));
function wp_closeTripTools() {
document.querySelectorAll('#view-trips .workplan-popover').forEach(pop => pop.classList.remove('show'));
document.querySelectorAll('#view-trips .workplan-tool-icon').forEach(button => button.classList.remove('active'));
}
function wp_toggleTripTool(name) {
const pop = document.getElementById('trip-' + name + '-pop');
const trigger = document.getElementById('trip-' + name + '-trigger');
const opening = !pop.classList.contains('show'); wp_closeTripTools();
if (opening) { pop.classList.add('show'); trigger.classList.add('active'); }
}
function wp_setTripDensity(density) {
const table = document.querySelector('.trip-table');
table.classList.remove('compact','relaxed'); if (density !== 'default') table.classList.add(density);
document.querySelectorAll('#trip-density-pop button').forEach(button => button.classList.toggle('active', button.dataset.density === density));
}
function wp_renderTripFieldList() {
const holder = document.getElementById('trip-field-list'); if (!holder) return;
holder.innerHTML = tripColumns.map(([key,label,fixed]) => '<label><input type="checkbox" ' + (visibleTripColumns.has(key) ? 'checked ' : '') + (fixed ? 'disabled ' : '') + 'onchange="wp_toggleTripColumn(\'' + key + '\',this.checked)">' + label + '</label>').join('');
}
function wp_applyTripColumns() {
const table = document.querySelector('.trip-table'); if (!table) return;
tripColumns.forEach(([key], index) => table.querySelectorAll('tr').forEach(row => { const cell = row.children[index]; if (cell) cell.style.display = visibleTripColumns.has(key) ? '' : 'none'; }));
}
function wp_toggleTripColumn(key, visible) { if (visible) visibleTripColumns.add(key); else visibleTripColumns.delete(key); wp_applyTripColumns(); }
function wp_resetTripColumns() { visibleTripColumns = new Set(tripColumns.map(item => item[0])); wp_renderTripFieldList(); wp_applyTripColumns(); }
function wp_initTripListTools() {
const holder = document.querySelector('#view-trips .workplan-list-tools'); if (!holder) return;
const tripTable = document.querySelector('.trip-table');
tripTable?.querySelector('thead th:nth-child(10)')?.remove();
tripTable?.querySelector('colgroup col:nth-child(10)')?.remove();
holder.innerHTML = '<div style="position:relative"><button class="workplan-tool-icon" id="trip-density-trigger" title="列表密度" onclick="wp_toggleTripTool(\'density\')"><svg aria-hidden="true" fill="none" stroke="currentColor" stroke-width="4" viewBox="0 0 48 48"><path d="M4 8h14.5M33 8H18.5m0 0v34"/><path fill="currentColor" stroke="none" d="M39 9.5 37 13h4l-2-3.5ZM39 38.5 37 35h4l-2-3.5Z"/><path d="M39 13h2l-2-3.5-2 3.5h2Zm0 0v22m0 0h2l-2 3.5-2-3.5h2Z"/></svg></button><div class="workplan-popover workplan-density-pop" id="trip-density-pop"><button data-density="compact" onclick="wp_setTripDensity(\'compact\')">紧凑</button><button data-density="default" class="active" onclick="wp_setTripDensity(\'default\')">默认</button><button data-density="relaxed" onclick="wp_setTripDensity(\'relaxed\')">宽松</button></div></div><button class="workplan-tool-icon" title="刷新数据" onclick="wp_renderTripTable();wp_applyTripColumns();wp_showToast(\'出差计划列表已刷新\')"><svg aria-hidden="true" fill="none" stroke="currentColor" stroke-width="4" viewBox="0 0 48 48"><path d="M38.837 18C36.463 12.136 30.715 8 24 8 15.163 8 8 15.163 8 24s7.163 16 16 16c7.455 0 13.72-5.1 15.496-12M40 8v10H30"/></svg></button><div style="position:relative"><button class="workplan-tool-icon" id="trip-fields-trigger" title="自定义显示字段" onclick="wp_toggleTripTool(\'fields\')"><svg aria-hidden="true" fill="none" viewBox="0 0 48 48"><path d="M18.2838 43.1713C14.9327 42.1736 11.9498 40.3213 9.58787 37.867C10.469 36.8227 11 35.4734 11 34.0001C11 30.6864 8.31371 28.0001 5 28.0001C4.79955 28.0001 4.60139 28.01 4.40599 28.0292C4.13979 26.7277 4 25.3803 4 24.0001C4 21.9095 4.32077 19.8938 4.91579 17.9995C4.94381 17.9999 4.97188 18.0001 5 18.0001C8.31371 18.0001 11 15.3138 11 12.0001C11 11.0488 10.7786 10.1493 10.3846 9.35011C12.6975 7.1995 15.5205 5.59002 18.6521 4.72314C19.6444 6.66819 21.6667 8.00013 24 8.00013C26.3333 8.00013 28.3556 6.66819 29.3479 4.72314C32.4795 5.59002 35.3025 7.1995 37.6154 9.35011C37.2214 10.1493 37 11.0488 37 12.0001C37 15.3138 39.6863 18.0001 43 18.0001C43.0281 18.0001 43.0562 17.9999 43.0842 17.9995C43.6792 19.8938 44 21.9095 44 24.0001C44 25.3803 43.8602 26.7277 43.594 28.0292C43.3986 28.01 43.2005 28.0001 43 28.0001C39.6863 28.0001 37 30.6864 37 34.0001C37 35.4734 37.531 36.8227 38.4121 37.867C36.0502 40.3213 33.0673 42.1736 29.7162 43.1713C28.9428 40.752 26.676 39.0001 24 39.0001C21.324 39.0001 19.0572 40.752 18.2838 43.1713Z" fill="currentColor" stroke="currentColor" stroke-width="3" stroke-linejoin="round"/><path d="M24 31C27.866 31 31 27.866 31 24C31 20.134 27.866 17 24 17C20.134 17 17 20.134 17 24C17 27.866 20.134 31 24 31Z" fill="#fff" stroke="currentColor" stroke-width="3" stroke-linejoin="round"/></svg></button><div class="workplan-popover workplan-fields-pop" id="trip-fields-pop"><h4>自定义显示字段</h4><div class="workplan-field-list" id="trip-field-list"></div><div class="workplan-fields-foot"><button class="btn small" onclick="wp_resetTripColumns()">恢复默认</button><button class="btn primary small" onclick="wp_closeTripTools()">完成</button></div></div></div>';
wp_renderTripFieldList();
}
function wp_filterTripTable() { wp_renderTripTable(); }
function wp_tripVisitOptions(type, selected) {
const values = objectQueues[type] || [];
return '<option value="">请选择</option>' + values.map(value => '<option ' + (value === selected ? 'selected' : '') + '>' + value + '</option>').join('');
}
function wp_changeTripVisitType(index, type) { tripModalVisits[index].objectType = type; tripModalVisits[index].object = ''; wp_renderTripVisitRows(); }
function wp_removeTripVisitRow(index) { tripModalVisits.splice(index, 1); wp_renderTripVisitRows(); }
function wp_ensureTripDetailModal() {
if (document.getElementById('trip-detail-modal')) return;
document.getElementById('workplanMount').insertAdjacentHTML('beforeend', '<div class="modal-mask" id="trip-detail-modal" onclick="wp_maskClose(event,\'trip-detail-modal\')"></div>');
}
/* 出差计划只承载主表信息;拜访完成统一归入工作计划“登记完成”,不再维护平行的出差总结。 */
let tripCompletionContext = null;
function wp_ensureTripWorkPlanShape() {
tripRecords.forEach(trip => trip.visits.forEach((visit, index) => {
visit.id ||= trip.id + '-' + index;
visit.attribution ||= (visit.objectType === 'customer' ? 'growth' : 'stock');
visit.plan ||= '';
visit.result ||= '';
visit.risk ||= '';
visit.next ||= '';
}));
}
function wp_tripAttributionLabel(value) { return ({ stock:'存量经营', growth:'增量开发', exclude:'不纳入双50' }[value] || '—'); }
function wp_syncTripWorkPlans() {
wp_ensureTripWorkPlanShape();
Object.values(dailyPlans).forEach(items => { for (let i = items.length - 1; i >= 0; i--) if (items[i].tripWorkPlan) items.splice(i, 1); });
tripRecords.forEach(trip => trip.visits.forEach(visit => {
if (visit.sourceActionId) return;
const date = visit.time.slice(0, 10); (dailyPlans[date] ||= []).push({ tripWorkPlan:true, tripId:trip.id, visitId:visit.id, time:visit.time.slice(11,16), name:visit.object + '|' + visit.item, type:visit.type, attribution:visit.attribution, object:wp_tripObjectLabel(visit.objectType) + ' · ' + visit.object, objectType:wp_tripObjectLabel(visit.objectType), relatedObject:visit.object, status:visit.status, plan:visit.plan, result:visit.result, risk:visit.risk, next:visit.next, plannedText:visit.time.slice(5,16).replace('T',' ') });
}));
wp_renderDaySummary(); wp_renderDual50();
}
function wp_syncTripActionList() {
const body = document.getElementById('action-table'); if (!body) return;
body.querySelectorAll('tr[data-trip-work-plan]').forEach(row => row.remove());
tripRecords.forEach(trip => trip.visits.forEach(visit => {
if (visit.sourceActionId) return;
const statusClass = visit.status === '已完成' ? 'done' : visit.status === '已超时' ? 'overdue' : 'pending';
body.insertAdjacentHTML('beforeend', '<tr data-trip-work-plan="' + visit.id + '" data-type="' + visit.type + '" data-status="' + visit.status + '" data-period="week"><td class="name-cell">' + visit.item + '</td><td>' + (visit.plan || '—') + '</td><td>' + visit.type + '</td><td><span class="object-tag">' + wp_tripObjectLabel(visit.objectType) + '</span></td><td>' + visit.object + '</td><td>' + visit.time.slice(5,16).replace('T',' ') + '</td><td>' + trip.owner + '</td><td><span class="state ' + statusClass + '">' + visit.status + '</span></td><td>' + (visit.next || '—') + '</td><td><button class="link" onclick="wp_openTripVisitDetail(' + trip.id + ',\'' + visit.id + '\')">详情</button><button class="link" onclick="wp_openTripModal(' + trip.id + ')">编辑</button>' + (visit.status === '已完成' ? '' : '<button class="link" onclick="wp_openTripVisitCompletion(' + trip.id + ',\'' + visit.id + '\')">登记完成</button>') + '</td></tr>');
}));
wp_filterActionTable();
}
function wp_getTripVisit(tripId, visitId) { const trip = tripRecords.find(item => item.id === tripId); return { trip, visit: trip?.visits.find(item => item.id === visitId) }; }
function wp_openTripVisitEdit(tripId, visitId) {
const { trip, visit } = wp_getTripVisit(tripId, visitId); if (!trip || !visit) return;
const plan = {
name:visit.item,
type:visit.type,
objectType:wp_tripObjectLabel(visit.objectType),
objectTypeCode:visit.objectType,
object:wp_tripObjectLabel(visit.objectType) + ' · ' + visit.object,
relatedObject:visit.object,
attribution:visit.attribution,
status:visit.status,
plan:visit.plan,
completedAt:visit.completedAt || '',
completionResult:visit.result || '',
result:visit.result || '',
risk:visit.risk || '',
plannedText:visit.time.slice(5,16).replace('T',' '),
plannedDateTime:visit.time,
owner:trip.owner,
tripId:trip.id,
visitId:visit.id,
tripWorkPlan:true
};
wp_openActionModal(plan.type, plan);
}
function wp_openTripVisitCompletion(tripId, visitId) {
const { visit } = wp_getTripVisit(tripId, visitId); if (!visit) return;
tripCompletionContext = { tripId, visitId };
document.getElementById('completion-name').value = visit.item;
document.getElementById('completion-time').value = visit.completedAt || visit.time;
document.getElementById('completion-result').value = visit.result || '';
document.getElementById('completion-risk').value = visit.risk || '';
wp_openModal('completion-modal');
}
function wp_renderTripVisitRows() {
const body = document.getElementById('trip-visit-form-body'); if (!body) return;
body.innerHTML = tripModalVisits.map((visit, index) => '<tr><td><input type="datetime-local" value="' + (visit.time || '') + '" onchange="tripModalVisits[' + index + '].time=this.value"></td><td><select onchange="wp_changeTripVisitType(' + index + ',this.value)"><option value="customer" ' + (visit.objectType === 'customer' ? 'selected' : '') + '>客户</option><option value="opportunity" ' + (visit.objectType === 'opportunity' ? 'selected' : '') + '>商机</option><option value="project" ' + (visit.objectType === 'project' ? 'selected' : '') + '>项目</option><option value="industry" ' + (visit.objectType === 'industry' ? 'selected' : '') + '>行业关系</option></select></td><td><select onchange="tripModalVisits[' + index + '].object=this.value">' + wp_tripVisitOptions(visit.objectType, visit.object) + '</select></td><td><select onchange="tripModalVisits[' + index + '].type=this.value">' + ['客户拜访','电话沟通','方案沟通','跑工地','组会组局','资料补充'].map(type => '<option ' + (visit.type === type ? 'selected' : '') + '>' + type + '</option>').join('') + '</select></td><td><input value="' + (visit.item || '') + '" placeholder="填写工作事项" onchange="tripModalVisits[' + index + '].item=this.value"></td><td><select onchange="tripModalVisits[' + index + '].attribution=this.value"><option value="stock" ' + (visit.attribution === 'stock' ? 'selected' : '') + '>存量经营</option><option value="growth" ' + (visit.attribution === 'growth' ? 'selected' : '') + '>增量开发</option><option value="exclude" ' + (visit.attribution === 'exclude' ? 'selected' : '') + '>不纳入双50</option></select></td><td><input value="' + (visit.plan || '') + '" placeholder="填写计划说明" onchange="tripModalVisits[' + index + '].plan=this.value"></td><td><button class="link" onclick="wp_removeTripVisitRow(' + index + ')">删除</button></td></tr>').join('');
}
function wp_addTripVisitRow() { tripModalVisits.push({ id:'new-' + Date.now(), time:'', objectType:'customer', object:'', type:'客户拜访', item:'', attribution:'stock', plan:'', status:'未完成' }); wp_renderTripVisitRows(); }
/* 出差拜访分组仅作查询与批量维护容器;日期范围由明细任务自动汇总。 */
function wp_tripRange(trip) {
const dates = trip.visits.map(item => item.time.slice(0,10)).filter(Boolean).sort();
return { start:dates[0] || '', end:dates[dates.length - 1] || '' };
}
let completionRow = null;
function wp_openCompleteModal(type) {
completionRow = null;
document.getElementById('completion-name').value = type + '工作计划';
document.getElementById('completion-time').value = selectedDate + 'T14:00';
document.getElementById('completion-result').value = '';
document.getElementById('completion-risk').value = '';
wp_openModal('completion-modal');
}
function wp_openQuickCompletion(row) {
completionRow = row;
document.getElementById('completion-name').value = row.cells[0].childNodes[0].textContent.trim();
document.getElementById('completion-time').value = '2026-07-29T14:00';
document.getElementById('completion-result').value = '';
document.getElementById('completion-risk').value = '';
wp_openModal('completion-modal');
}
function wp_saveQuickCompletion() {
const result = document.getElementById('completion-result').value.trim();
if (!result) { wp_showToast('请填写完成结果 / 客户反馈'); return; }
if (tripCompletionContext) {
const { trip, visit } = wp_getTripVisit(tripCompletionContext.tripId, tripCompletionContext.visitId);
if (visit) {
visit.status = '已完成';
visit.completedAt = document.getElementById('completion-time').value;
visit.result = result;
visit.risk = document.getElementById('completion-risk').value.trim();
wp_syncTripLinkActionFromVisit(visit);
wp_syncTripWorkPlans(); wp_renderTripTable(); wp_syncTripActionList();
}
tripCompletionContext = null;
wp_closeModal('completion-modal'); wp_showToast('完成登记已保存', 'success'); return;
}
if (completionRow) {
completionRow.dataset.status = '已完成';
const state = completionRow.cells[7].querySelector('.state');
state.className = 'state done'; state.textContent = '已完成';
}
wp_closeModal('completion-modal');
wp_filterActionTable();
wp_showToast('完成登记已保存', 'success');
}
function wp_openFocusModal() { wp_openModal('focus-modal'); }
function wp_changeActionHint() { const type = document.getElementById('action-type').value; const hints = { '电话沟通':'记录沟通对象、客户反馈与风险;电话数量本身不作为业绩。', '客户拜访':'拜访完成后补充完成结果与客户反馈;如来自出差,将同步沉淀到出差完成记录。', '出差安排':'出差计划可包含多个拜访安排,建议先建立出差主计划后再维护明细。', '跑工地':'建议关联项目并记录现场进展、关键问题与需要协同的事项。', '方案沟通':'建议关联商机或项目,并记录沟通结论。', '组会组局':'记录参与单位、关键人及形成的机会。', '资料补充':'记录需要补充的资料、完成结果及后续沟通安排。', '其他':'请关联对应客户、商机、项目或行业关系,并记录可追溯的工作结果。' }; document.getElementById('action-hint').textContent = hints[type]; }
function wp_completeTask(btn) { const row = btn.closest('.task-row'); const name = row.dataset.task; wp_showToast('请通过“记录”或“更新”补充「' + name + '」的完成结果后,再标记为已完成'); }
function wp_changeTab(btn, cls) { document.querySelectorAll('#focus-tabs .tab').forEach(t => t.classList.remove('active')); btn.classList.add('active'); document.querySelectorAll('#focus-table tr').forEach(row => row.style.display = (cls === 'all-focus' || row.classList.contains(cls)) ? '' : 'none'); }
function wp_selectTrip(btn, name) { document.querySelectorAll('.trip-item').forEach(i => i.classList.remove('active')); btn.classList.add('active'); document.getElementById('trip-detail-title').textContent = name; wp_showToast('已切换出差计划:' + name); }
function wp_filterActionTable() {
const keyword = document.getElementById('action-search').value.trim().toLowerCase();
const type = document.getElementById('action-type-filter').value;
const status = document.getElementById('action-status-filter').value;
const period = document.getElementById('action-period-filter').value;
const tableBody = document.getElementById('action-table');
tableBody.querySelector('.filter-empty')?.remove();
const rows = [...tableBody.querySelectorAll('tr')];
rows.forEach(row => {
const textMatch = !keyword || row.textContent.toLowerCase().includes(keyword);
const typeMatch = !type || row.dataset.type === type;
const statusMatch = !status || row.dataset.status === status;
const dateText = row.cells[5].textContent.trim().match(/(\d{2})-(\d{2})/) || [];
const date = dateText.length ? '2026-' + dateText[1] + '-' + dateText[2] : '';
const periodMatch = period === 'all' || (period === 'today' && date === '2026-07-29') || (period === 'week' && date >= '2026-07-27' && date <= '2026-08-02') || (period === 'month' && date >= '2026-07-01' && date <= '2026-07-31') || (period === 'history' && date < '2026-07-27');
row.dataset.filterMatch = textMatch && typeMatch && statusMatch && periodMatch ? 'true' : 'false';
});
if (!rows.some(row => row.dataset.filterMatch === 'true')) tableBody.insertAdjacentHTML('beforeend', '<tr class="filter-empty"><td colspan="10">暂无符合条件的工作计划</td></tr>');
wp_showActionPage(1);
}
function wp_showActionPage(page) {
const rows = [...document.querySelectorAll('#action-table tr:not(.filter-empty)')];
const matched = rows.filter(row => row.dataset.filterMatch !== 'false');
const size = Number(document.getElementById('action-page-size').value) || 10;
const totalPages = Math.max(1, Math.ceil(matched.length / size));
const current = Math.min(Math.max(1, page), totalPages);
rows.forEach(row => row.style.display = 'none');
matched.slice((current - 1) * size, current * size).forEach(row => row.style.display = '');
document.getElementById('action-total').textContent = '共 ' + matched.length + ' 条';
document.querySelectorAll('.table-pagination [data-page]').forEach(button => {
const target = Number(button.dataset.page);
button.classList.toggle('active', target === current);
button.style.display = target <= totalPages ? '' : 'none';
});
}
function wp_showToast(message, kind) { const toast = document.getElementById('toast'); toast.textContent = message; toast.className = 'toast show' + (kind ? ' ' + kind : ''); clearTimeout(window.toastTimer); window.toastTimer = setTimeout(() => toast.className = 'toast', 2600); }
function wp_saveAction() {
const attribution = document.getElementById('action-attribution').value;
const name = document.getElementById('action-name').value.trim();
const objectType = document.getElementById('action-object-type').value;
const objectField = document.getElementById('action-object');
if (!name) { wp_showToast('请填写工作事项'); return; }
if (!objectType || !objectField.value) { wp_showToast('请先选择关联对象类型和关联对象'); return; }
if (!attribution) { wp_showToast('请确认该工作项是否纳入双50及其经营归属'); return; }
const completed = document.getElementById('action-status').value === '已完成';
const completionResult = document.getElementById('action-result-input').value.trim();
if (completed && !completionResult) { wp_showToast('请填写完成结果 / 客户反馈'); return; }
const record = editingAction || {};
record.time = document.getElementById('action-datetime').value.slice(11) || '待定';
record.name = name;
record.type = document.getElementById('action-type').value;
record.attribution = attribution;
record.status = document.getElementById('action-status').value;
record.object = document.getElementById('action-object-type').selectedOptions[0].text + ' · ' + objectField.selectedOptions[0].text + ' · ' + ({ stock:'存量经营', growth:'增量开发', exclude:'不纳入双50' }[attribution]);
record.relatedObject = objectField.selectedOptions[0].text;
record.plan = document.getElementById('action-plan-input').value || '待补充本次计划说明。';
record.completedAt = completed ? (document.getElementById('action-completed-at').value || document.getElementById('action-datetime').value) : '';
record.completionResult = completed ? completionResult : '';
record.risk = completed ? document.getElementById('action-risk-input').value.trim() : '';
record.result = completed ? completionResult + (record.risk ? ';风险/协同:' + record.risk : '') : '未完成时待补充客户反馈、风险。';
if (!editingAction) {
dailyPlans[selectedDate] = dailyPlans[selectedDate] || [];
dailyPlans[selectedDate].push(record);
}
wp_closeModal('action-modal');
wp_renderDaySummary();
wp_renderDual50();
wp_showToast('工作计划已保存,双50过程统计已按经营归属更新', 'success');
}
function wp_saveFocus() { wp_closeModal('focus-modal'); wp_showToast('已加入重点经营清单;基础档案未被重复创建', 'success'); }
const objectQueues = {
project: ['海康威视智慧园区', '望江党校', '菱湖院子二期', '武汉卷烟厂改造'],
customer: ['华润置地', '零亿'],
opportunity: ['光电集团安防改造', '武汉卷烟厂改造'],
industry: ['建筑业协会', '华南设计院']
};
function wp_loadObjectQueue() {
const type = document.getElementById('action-object-type').value;
const field = document.getElementById('action-object');
const options = objectQueues[type] || [];
field.disabled = !type;
field.innerHTML = type ? '<option value="">请选择</option>' + options.map(name => '<option value="' + name + '">' + name + '</option>').join('') : '<option value="">请先选择关联对象类型</option>';
}
const workplanColumns = [
['work', '工作事项', true], ['plan', '计划说明'], ['type', '工作类型'], ['objectType', '对象类型'], ['object', '关联对象'],
['planned', '计划完成时间'], ['owner', '负责人'], ['status', '完成情况'], ['next', '下一步'], ['actions', '操作', true]
];
let visibleWorkplanColumns = new Set(workplanColumns.map(item => item[0]).filter(key => key !== 'next'));
function wp_closeWorkplanPopovers() {
document.querySelectorAll('.workplan-popover').forEach(pop => pop.classList.remove('show'));
document.querySelectorAll('.workplan-tool-icon').forEach(button => button.classList.remove('active'));
}
function wp_toggleWorkplanPopover(name) {
const pop = document.getElementById('workplan-' + name + '-pop');
const trigger = document.getElementById('workplan-' + name + '-trigger');
const opening = !pop.classList.contains('show');
wp_closeWorkplanPopovers();
if (opening) { pop.classList.add('show'); trigger?.classList.add('active'); }
}
function wp_renderWorkplanFieldList() {
const container = document.getElementById('workplan-field-list');
if (!container) return;
container.innerHTML = workplanColumns.filter(([key]) => key !== 'next').map(([key, label, fixed]) => '<label><input type="checkbox" ' + (visibleWorkplanColumns.has(key) ? 'checked ' : '') + (fixed ? 'disabled ' : '') + 'onchange="wp_toggleWorkplanColumn(\'' + key + '\',this.checked)">' + label + '</label>').join('');
}
function wp_toggleWorkplanColumn(key, checked) {
if (checked) visibleWorkplanColumns.add(key); else visibleWorkplanColumns.delete(key);
wp_applyWorkplanColumns();
}
function wp_applyWorkplanColumns() {
const table = document.querySelector('.action-history-table');
if (!table) return;
const cols = table.querySelectorAll('colgroup col');
table.querySelectorAll('tr').forEach(row => [...row.cells].forEach((cell, index) => cell.style.display = visibleWorkplanColumns.has(workplanColumns[index][0]) ? '' : 'none'));
cols.forEach((col, index) => col.style.display = visibleWorkplanColumns.has(workplanColumns[index][0]) ? '' : 'none');
wp_renderWorkplanFieldList();
}
function wp_resetWorkplanColumns() { visibleWorkplanColumns = new Set(workplanColumns.map(item => item[0]).filter(key => key !== 'next')); wp_applyWorkplanColumns(); }
function wp_setWorkplanDensity(density) {
const table = document.querySelector('.action-history-table');
if (!table) return;
table.classList.remove('compact', 'relaxed');
if (density !== 'default') table.classList.add(density);
document.querySelectorAll('#workplan-density-pop [data-density]').forEach(button => button.classList.toggle('active', button.dataset.density === density));
}
function wp_refreshWorkplanList() {
const button = document.getElementById('workplan-refresh');
button.classList.add('refreshing');
document.getElementById('action-search').value = '';
document.getElementById('action-type-filter').value = '';
document.getElementById('action-status-filter').value = '';
document.getElementById('action-period-filter').value = 'all';
wp_filterActionTable();
setTimeout(() => button.classList.remove('refreshing'), 600);
wp_showToast('工作计划列表已刷新');
}
function wp_initActionTableDrag() {
const scroller = document.getElementById('action-table-scroll');
if (!scroller) return;
let startX = 0, startLeft = 0, dragging = false;
scroller.addEventListener('pointerdown', event => {
if (event.target.closest('button,a,input,select,textarea,label,.name-cell')) return;
dragging = true; startX = event.clientX; startLeft = scroller.scrollLeft;
scroller.classList.add('is-dragging'); scroller.setPointerCapture?.(event.pointerId);
});
scroller.addEventListener('pointermove', event => { if (dragging) scroller.scrollLeft = startLeft - (event.clientX - startX); });
['pointerup', 'pointercancel', 'pointerleave'].forEach(type => scroller.addEventListener(type, () => { dragging = false; scroller.classList.remove('is-dragging'); }));
}
function wp_initActionNameLinks() {
const body = document.getElementById('action-table');
if (!body || body.dataset.nameLinkBound) return;
body.dataset.nameLinkBound = 'true';
body.addEventListener('click', event => {
const cell = event.target.closest('td.name-cell');
if (!cell || !body.contains(cell)) return;
const row = cell.closest('tr');
if (!row) return;
const actionId = row.dataset.userWorkPlan;
if (actionId) wp_tripLinkOpenActionDetail(actionId);
else wp_openStaticActionDetail(row);
});
}
function wp_normalizeCompletionVocabulary() {
document.querySelector('#view-actions button[onclick*="已导出当前筛选结果"]')?.remove();
const completionField = document.getElementById('action-status');
const completionLabel = completionField.closest('.field').querySelector('label');
const workTypeField = document.getElementById('action-type');
[...workTypeField.options].forEach(option => { if (option.textContent === 'FBB处理') option.remove(); });
if (![...workTypeField.options].some(option => option.textContent === '资料补充')) workTypeField.insertAdjacentHTML('beforeend', '<option>资料补充</option>');
const objectField = document.getElementById('action-object');
[...objectField.options].forEach(option => { if (option.textContent.includes('方案卡')) option.remove(); });
const objectWrapper = objectField.closest('.field');
objectWrapper.classList.remove('full');
if (!document.getElementById('action-object-type')) objectWrapper.insertAdjacentHTML('beforebegin', '<div class="field"><label>关联对象类型 <span class="required">*</span></label><select id="action-object-type" onchange="wp_loadObjectQueue()"><option value="">请选择</option><option value="project">项目</option><option value="customer">客户</option><option value="opportunity">商机</option><option value="industry">行业关系</option></select></div>');
objectField.closest('.field').querySelector('label').childNodes[0].nodeValue = '关联对象';
wp_loadObjectQueue();
document.getElementById('action-type').closest('.field').querySelector('label').childNodes[0].nodeValue = '工作类型';
document.getElementById('action-datetime').closest('.field').querySelector('label').childNodes[0].nodeValue = '计划完成时间';
document.getElementById('action-name').closest('.field').querySelector('label').childNodes[0].nodeValue = '工作事项';
completionLabel.childNodes[0].nodeValue = '完成情况';
completionField.innerHTML = '<option>未完成</option><option>已完成</option>';
document.getElementById('action-hint').textContent = document.getElementById('action-hint').textContent.replaceAll('行动类型', '工作类型');
const focusFields = document.querySelectorAll('#focus-modal .field');
const paceLabel = [...focusFields].find(node => node.querySelector('label')?.textContent.includes('行动节奏'))?.querySelector('label');
if (paceLabel) paceLabel.childNodes[0].nodeValue = '初步工作节奏';
const focusTip = document.querySelector('#focus-modal .form-tip');
if (focusTip) focusTip.textContent = focusTip.textContent.replaceAll('行动节奏', '工作节奏');
const formGrid = completionField.closest('.form-grid');
if (!document.getElementById('completion-fields')) {
formGrid.insertAdjacentHTML('beforeend', '<div class="field full completion-only" id="completion-fields"><div class="form-grid"><div class="field"><label>完成时间 <span class="required">*</span></label><input id="action-completed-at" type="datetime-local"></div><div class="field"><label>风险或协同事项</label><input id="action-risk-input" placeholder="可选:需协同处理的风险或事项"></div><div class="field full"><label>完成结果 / 客户反馈 <span class="required">*</span></label><textarea id="action-result-input" placeholder="填写本次工作完成结果、客户反馈或形成的结论"></textarea></div></div></div>');
}
completionField.addEventListener('change', wp_toggleCompletionFields);
wp_toggleCompletionFields();
document.querySelectorAll('.state').forEach(node => {
const mapping = { '待执行':'未完成', '执行中':'未完成', '待处理':'未完成', '已安排':'未完成', '已超期':'已超时', '1项待执行':'1项未完成' };
const next = mapping[node.textContent.trim()];
if (!next) return;
node.textContent = next;
node.className = 'state ' + (next.includes('超时') ? 'overdue' : next.includes('完成') && !next.includes('未') ? 'done' : 'pending');
});
document.querySelectorAll('.trip-sub').forEach(node => node.textContent = node.textContent.replace('待执行', '未完成'));
document.querySelectorAll('.team-card').forEach(node => {
node.innerHTML = node.innerHTML.replace('待处理 / 已超期行动', '未完成 / 已超时行动').replace('FBB 待处理事项', 'FBB 未完成事项');
});
document.querySelectorAll('.team-plan').forEach(node => node.textContent = node.textContent.replace('超期', '已超时'));
const actionFilters = document.querySelectorAll('#view-actions .page-tools select');
if (actionFilters[1]) actionFilters[1].innerHTML = '<option value="">全部完成情况</option><option>未完成</option><option>已完成</option><option>已超时</option>';
const statusHead = document.querySelector('#view-actions thead th:nth-child(8)');
if (statusHead) statusHead.textContent = '完成情况';
document.querySelectorAll('#action-table tr').forEach(row => {
if (!row.dataset.planSplit) {
const explanation = row.cells[0].querySelector('.secondary')?.textContent.trim() || '—';
row.cells[0].querySelector('.secondary')?.remove();
const planCell = document.createElement('td');
planCell.textContent = explanation;
row.insertBefore(planCell, row.cells[1]);
row.dataset.planSplit = 'true';
}
if (!row.dataset.objectSplit) {
const objectCell = row.cells[3];
const objectType = objectCell.querySelector('.object-tag')?.textContent.trim() || '—';
objectCell.querySelector('.object-tag')?.remove();
const typeCell = document.createElement('td');
typeCell.innerHTML = '<span class="object-tag">' + objectType + '</span>';
objectCell.textContent = objectCell.textContent.trim();
row.insertBefore(typeCell, objectCell);
row.dataset.objectSplit = 'true';
}
const buttons = row.querySelectorAll('td:last-child .link');
if (buttons[0]) { buttons[0].textContent = '详情'; buttons[0].onclick = () => wp_openStaticActionDetail(row); }
if (buttons[1]) { buttons[1].textContent = '编辑'; buttons[1].onclick = () => wp_openStaticActionEdit(row); }
if (buttons[2]) { buttons[2].textContent = '登记完成'; buttons[2].onclick = () => wp_openQuickCompletion(row); }
});
wp_renderWorkplanFieldList();
wp_applyWorkplanColumns();
wp_showActionPage(1);
}
/* 回退出差拜访分组重构:恢复为出差计划主表 + 标准工作计划明细。 */
function wp_tripDateLabel(trip) { return trip.start === trip.end ? trip.start.slice(5) + ' · 当日往返' : trip.start.slice(5) + ' 至 ' + trip.end.slice(5); }
function wp_renderTripTable() {
const body = document.getElementById('trip-table-body'); if (!body) return;
const keyword = document.getElementById('trip-search')?.value.trim().toLowerCase() || '';
const schedule = document.getElementById('trip-schedule-filter')?.value || '';
const period = document.getElementById('trip-period-filter')?.value || 'all';
const rows = tripRecords.filter(trip => (!keyword || (trip.title + trip.region).toLowerCase().includes(keyword)) && (!schedule || wp_tripSchedule(trip) === schedule) && (period === 'all' || (period === 'month' && trip.start.startsWith('2026-07')) || (period === 'future' && trip.start >= '2026-08-01')));
body.innerHTML = rows.length ? rows.map(trip => { const counts = wp_tripVisitCounts(trip), next = wp_nextTripVisit(trip), state = wp_tripSchedule(trip); return '<tr><td class="name-cell">' + trip.title + '</td><td>' + wp_tripDateLabel(trip) + '</td><td>' + trip.region + '</td><td>' + trip.owner + '</td><td class="trip-count">' + counts.total + ' 项</td><td class="trip-count">' + counts.done + ' / ' + counts.total + '</td><td>' + (counts.overdue ? '<span class="state overdue">' + counts.overdue + ' 项</span>' : '—') + '</td><td>' + (next ? next.time.slice(5,16).replace('T',' ') + ' · ' + next.object : '—') + '</td><td><span class="state ' + (state === '已结束' ? 'done' : state === '进行中' ? 'progress' : 'pending') + '">' + state + '</span></td><td><button class="link" onclick="wp_openTripDetail(' + trip.id + ')">详情</button><button class="link" onclick="wp_openTripModal(' + trip.id + ')">编辑</button></td></tr>'; }).join('') : '<tr><td colspan="10" class="filter-empty">暂无符合条件的出差计划</td></tr>';
document.getElementById('trip-total').textContent = '共 ' + rows.length + ' 条'; wp_applyTripColumns();
}
function wp_openTripModal(id) {
editingTripId = id || null; const record = id ? tripRecords.find(item => item.id === id) : null;
tripModalVisits = record ? record.visits.map(item => ({ ...item })) : [{ id:'new-1', time:'', objectType:'customer', object:'', type:'客户拜访', item:'', attribution:'stock', plan:'', status:'未完成' }];
const modal = document.getElementById('trip-modal');
modal.innerHTML = '<div class="modal trip-form-modal" onclick="event.stopPropagation()"><div class="modal-head"><h2>' + (record ? '编辑出差计划' : '新建出差计划') + '</h2><button class="x" onclick="wp_closeModal(\'trip-modal\')">×</button></div><div class="modal-body" style="padding:18px 0 4px"><div class="trip-form-section"><div class="trip-form-section-head">出差基本信息</div><div class="form-grid"><div class="field full"><label>出差主题 <span class="required">*</span></label><input id="trip-title-input" value="' + (record?.title || '') + '"></div><div class="field"><label>开始日期 <span class="required">*</span></label><input id="trip-start-input" type="date" value="' + (record?.start || '2026-07-29') + '"></div><div class="field"><label>结束日期 <span class="required">*</span></label><input id="trip-end-input" type="date" value="' + (record?.end || '2026-07-29') + '"></div><div class="field"><label>出差地区 <span class="required">*</span></label><input id="trip-region-input" value="' + (record?.region || '') + '"></div><div class="field"><label>负责人</label><select id="trip-owner-input"><option>张明远</option></select></div><div class="field full"><label>出差目标</label><textarea id="trip-purpose-input">' + (record?.purpose || '') + '</textarea></div></div></div><div class="trip-form-section"><div class="trip-form-section-head"><span>拜访安排</span></div><div class="trip-visit-table-wrap"><table class="trip-visit-form-table"><thead><tr><th>计划完成时间</th><th>对象类型</th><th>关联对象</th><th>工作类型</th><th>工作事项</th><th>经营归属(双50)</th><th>计划说明</th><th>操作</th></tr></thead><tbody id="trip-visit-form-body"></tbody></table></div><div class="trip-visit-add"><button class="btn small" onclick="wp_addTripVisitRow()">+ 新增一行拜访安排</button></div></div></div><div class="modal-foot"><button class="btn" onclick="wp_closeModal(\'trip-modal\')">取消</button><button class="btn primary" onclick="wp_saveTrip()">保存出差计划</button></div></div>';
wp_renderTripVisitRows(); wp_openModal('trip-modal');
}
function wp_saveTrip() {
const title = document.getElementById('trip-title-input').value.trim(), start = document.getElementById('trip-start-input').value, end = document.getElementById('trip-end-input').value, region = document.getElementById('trip-region-input').value.trim();
if (!title || !start || !end || !region) { wp_showToast('请填写出差主题、日期和地区'); return; }
if (end < start) { wp_showToast('结束日期不能早于开始日期'); return; }
if (tripModalVisits.some(item => !item.time || !item.object || !item.item || !item.attribution)) { wp_showToast('请补齐每条拜访安排的时间、关联对象、工作事项和经营归属'); return; }
const record = { id:editingTripId || Date.now(), title, start, end, region, owner:document.getElementById('trip-owner-input').value, purpose:document.getElementById('trip-purpose-input').value.trim(), visits:tripModalVisits.map((item,index) => ({ ...item, id:item.id || (editingTripId || 'new') + '-' + index, status:item.status || '未完成' })) };
if (editingTripId) Object.assign(tripRecords.find(item => item.id === editingTripId), record); else tripRecords.unshift(record);
wp_closeModal('trip-modal'); wp_syncTripWorkPlans(); wp_renderTripTable(); wp_syncTripActionList(); wp_showToast('出差计划已保存', 'success');
}
function wp_openTripDetail(id) {
const trip = tripRecords.find(item => item.id === id); if (!trip) return; wp_ensureTripWorkPlanShape(); wp_ensureTripDetailModal();
const counts = wp_tripVisitCounts(trip), modal = document.getElementById('trip-detail-modal');
modal.innerHTML = '<div class="modal trip-detail-modal" onclick="event.stopPropagation()"><div class="modal-head"><h2>' + trip.title + '</h2><button class="x" onclick="wp_closeModal(\'trip-detail-modal\')">×</button></div><div class="trip-detail-meta"><div><small>出差日期</small><b>' + wp_tripDateLabel(trip) + '</b></div><div><small>出差地区</small><b>' + trip.region + '</b></div><div><small>负责人</small><b>' + trip.owner + '</b></div><div><small>出差日程</small><b>' + wp_tripSchedule(trip) + '</b></div></div><div class="trip-detail-section"><div class="detail-row"><div class="detail-label">出差目标</div><div class="detail-value">' + (trip.purpose || '—') + '</div></div></div><div class="trip-detail-section"><div class="trip-detail-section-head"><span>拜访安排(' + counts.done + ' / ' + counts.total + ' 已完成)</span></div><div class="trip-visit-table-wrap"><table class="trip-detail-visit-table"><thead><tr><th>计划完成时间</th><th>对象类型</th><th>关联对象</th><th>工作事项</th><th>工作类型</th><th>经营归属(双50)</th><th>计划说明</th><th>完成情况</th><th>操作</th></tr></thead><tbody>' + trip.visits.map(visit => '<tr><td>' + visit.time.slice(5,16).replace('T',' ') + '</td><td>' + wp_tripObjectLabel(visit.objectType) + '</td><td>' + visit.object + '</td><td>' + visit.item + '</td><td>' + visit.type + '</td><td>' + wp_tripAttributionLabel(visit.attribution) + '</td><td>' + (visit.plan || '—') + '</td><td><span class="state ' + (visit.status === '已完成' ? 'done' : visit.status === '已超时' ? 'overdue' : 'pending') + '">' + visit.status + '</span></td><td><button class="link" onclick="wp_openTripVisitDetail(' + trip.id + ',\'' + visit.id + '\')">详情</button><button class="link" onclick="wp_closeModal(\'trip-detail-modal\');wp_openTripModal(' + trip.id + ')">编辑</button>' + (visit.status === '已完成' ? '' : '<button class="link" onclick="wp_openTripVisitCompletion(' + trip.id + ',\'' + visit.id + '\')">登记完成</button>') + '</td></tr>').join('') + '</tbody></table></div></div><div class="modal-foot"><button class="btn" onclick="wp_closeModal(\'trip-detail-modal\')">关闭</button><button class="btn primary" onclick="wp_closeModal(\'trip-detail-modal\');wp_openTripModal(' + trip.id + ')">编辑出差计划</button></div></div>';
modal.querySelectorAll('.trip-detail-visit-table tbody tr').forEach((row, index) => {
const editButton = row.querySelectorAll('button.link')[1];
const visit = trip.visits[index];
if (editButton && visit) editButton.onclick = () => wp_openTripVisitEdit(trip.id, visit.id);
});
wp_openModal('trip-detail-modal');
}
/* 工作计划与出差计划双向关联:出差为聚合容器,工作计划保存可选的单一出差关联。 */
let tripLinkActionSequence = 105;
let tripLinkCompletionRecord = null;
const tripLinkStaticRecords = new WeakMap();
function wp_tripLinkCode(id) { return 'T' + String(id).padStart(3, '0'); }
function wp_tripLinkLabel(trip) { return wp_tripLinkCode(trip.id) + '|' + trip.title + '(' + wp_tripDateLabel(trip) + ')'; }
function wp_tripLinkEscape(value) { return String(value ?? '').replace(/[&<>"']/g, char => ({ '&':'&amp;', '<':'&lt;', '>':'&gt;', '"':'&quot;', "'":'&#39;' }[char])); }
function wp_tripLinkObjectTypeCode(label) {
return label?.includes('客户') ? 'customer' : label?.includes('商机') ? 'opportunity' : label?.includes('行业') ? 'industry' : 'project';
}
function wp_tripLinkDateTime(plan) {
if (plan?.plannedDateTime) return plan.plannedDateTime;
if (plan?.tripWorkPlan) {
const linked = wp_getTripVisit(plan.tripId, plan.visitId).visit;
if (linked?.time) return linked.time;
}
return /^\d{2}:\d{2}$/.test(plan?.time || '') ? selectedDate + 'T' + plan.time : selectedDate + 'T14:00';
}
function wp_initTripAssociationUI() {
const workType = document.getElementById('action-type');
[...workType.options].forEach(option => { if (option.textContent === '出差安排') option.remove(); });
const workTypeFilter = document.getElementById('action-type-filter');
[...workTypeFilter.options].forEach(option => { if (option.textContent === '出差安排') option.remove(); });
document.querySelectorAll('#action-table tr[data-type="出差安排"]').forEach(row => row.remove());
if (!document.getElementById('action-trip-id')) {
document.getElementById('action-attribution').closest('.field').insertAdjacentHTML('beforebegin', '<div class="field full" id="action-trip-field"><label>关联出差计划</label><select id="action-trip-id"><option value="">不关联出差计划</option></select></div>');
}
if (!document.getElementById('detail-trip')) {
document.getElementById('detail-attribution').closest('.detail-row').insertAdjacentHTML('beforebegin', '<div class="detail-row"><div class="detail-label">关联出差计划</div><div class="detail-value" id="detail-trip">未关联</div></div>');
}
wp_refreshActionTripOptions();
}
function wp_refreshActionTripOptions(selectedId) {
const select = document.getElementById('action-trip-id'); if (!select) return;
select.innerHTML = '<option value="">不关联出差计划</option>' + tripRecords.map(trip => '<option value="' + trip.id + '">' + wp_tripLinkEscape(wp_tripLinkLabel(trip)) + '</option>').join('');
select.value = selectedId ? String(selectedId) : '';
}
function wp_renderActionTripDetail(plan) {
const holder = document.getElementById('detail-trip'); if (!holder) return;
const trip = tripRecords.find(item => String(item.id) === String(plan?.tripId || ''));
holder.innerHTML = trip ? '<button class="link" onclick="wp_closeModal(\'action-detail-modal\');wp_openTripDetail(' + trip.id + ')">' + wp_tripLinkEscape(wp_tripLinkLabel(trip)) + '</button>' : '未关联';
}
const tripLinkBaseOpenActionModal = wp_openActionModal;
wp_openActionModal = function(type, plan) {
tripLinkBaseOpenActionModal(type, plan);
const dateTime = wp_tripLinkDateTime(plan);
document.getElementById('action-datetime').value = dateTime;
if (plan?.objectTypeCode || plan?.objectType) {
const typeCode = plan.objectTypeCode || wp_tripLinkObjectTypeCode(plan.objectType);
document.getElementById('action-object-type').value = typeCode;
wp_loadObjectQueue();
if (plan.relatedObject && [...document.getElementById('action-object').options].some(option => option.value === plan.relatedObject)) document.getElementById('action-object').value = plan.relatedObject;
}
wp_refreshActionTripOptions(plan?.tripId);
document.getElementById('action-trip-id').disabled = Boolean(plan?.tripWorkPlan);
};
const tripLinkBaseOpenActionDetail = wp_openActionDetail;
wp_openActionDetail = function(plan) {
tripLinkBaseOpenActionDetail(plan);
wp_renderActionTripDetail(plan);
};
function wp_openTripVisitDetail(tripId, visitId) {
const { trip, visit } = wp_getTripVisit(tripId, visitId); if (!visit) return;
selectedAction = { name:visit.item, type:visit.type, objectType:wp_tripObjectLabel(visit.objectType), objectTypeCode:visit.objectType, relatedObject:visit.object, attribution:visit.attribution, status:visit.status, plan:visit.plan, result:visit.result || '尚未登记完成结果。', next:visit.next || '—', plannedText:visit.time.slice(5,16).replace('T',' '), plannedDateTime:visit.time, tripId:trip.id, visitId:visit.id, tripWorkPlan:true };
wp_openActionDetail(selectedAction);
const editButton = document.querySelector('#action-detail-modal .modal-foot .primary');
if (editButton) editButton.onclick = () => { wp_closeModal('action-detail-modal'); wp_openTripModal(trip.id); };
}
function wp_tripLinkPlanFromStaticRow(row) {
if (tripLinkStaticRecords.has(row)) return tripLinkStaticRecords.get(row);
const cells = row.cells;
const plannedText = cells[5].textContent.trim();
const matched = plannedText.match(/(\d{2})-(\d{2})\s+(\d{2}:\d{2})/);
const objectType = cells[3].textContent.trim();
const record = {
actionId:'WS' + (++tripLinkActionSequence), name:cells[0].textContent.trim(), plan:cells[1].textContent.trim(), type:cells[2].textContent.trim(),
objectType, objectTypeCode:wp_tripLinkObjectTypeCode(objectType), relatedObject:cells[4].textContent.trim(), object:objectType + ' · ' + cells[4].textContent.trim(),
plannedDateTime:matched ? '2026-' + matched[1] + '-' + matched[2] + 'T' + matched[3] : selectedDate + 'T14:00',
time:matched ? matched[3] : '14:00', attribution:'exclude', status:cells[7].textContent.trim(), next:cells[8].textContent.trim(), staticRow:row
};
tripLinkStaticRecords.set(row, record);
return record;
}
function wp_openStaticActionDetail(row) { wp_openActionDetail(wp_tripLinkPlanFromStaticRow(row)); }
function wp_openStaticActionEdit(row) { const plan = wp_tripLinkPlanFromStaticRow(row); wp_openActionModal(plan.type, plan); }
function wp_tripLinkValidateSelection(tripId, plannedDateTime) {
if (!tripId) return true;
const trip = tripRecords.find(item => String(item.id) === String(tripId));
if (!trip) { wp_showToast('关联的出差计划不存在'); return false; }
const plannedDate = plannedDateTime.slice(0, 10);
if (plannedDate < trip.start || plannedDate > trip.end) {
wp_showToast('工作计划时间不在该出差计划的日期范围内');
return false;
}
return true;
}
function wp_syncTripLinkVisitFromAction(record, tripId, plannedDateTime, objectTypeCode, objectName) {
tripRecords.forEach(trip => { trip.visits = trip.visits.filter(visit => visit.sourceAction !== record); });
record.tripId = tripId ? Number(tripId) : null;
record.plannedDateTime = plannedDateTime;
record.objectTypeCode = objectTypeCode;
if (!tripId) { record.tripVisitId = null; return; }
const trip = tripRecords.find(item => String(item.id) === String(tripId));
const visitId = record.tripVisitId || 'linked-' + record.actionId;
record.tripVisitId = visitId;
trip.visits.push({
id:visitId, sourceActionId:record.actionId, sourceAction:record, time:plannedDateTime, objectType:objectTypeCode, object:objectName,
type:record.type, item:record.name, attribution:record.attribution, plan:record.plan, status:record.status, completedAt:record.completedAt,
result:record.result, risk:record.risk, next:record.next
});
}
function wp_syncTripLinkActionFromVisit(visit) {
const record = visit?.sourceAction; if (!record) return;
record.status = visit.status;
record.completedAt = visit.completedAt;
record.result = visit.result;
record.completionResult = visit.result;
record.risk = visit.risk;
record.next = visit.next;
wp_syncStandaloneActionList();
}
function wp_updateTripDerivedVisit(record, plannedDateTime, objectTypeCode, objectName) {
if (!record?.tripWorkPlan) return;
const visit = wp_getTripVisit(record.tripId, record.visitId).visit; if (!visit) return;
visit.time = plannedDateTime; visit.objectType = objectTypeCode; visit.object = objectName; visit.type = record.type; visit.item = record.name;
visit.attribution = record.attribution; visit.plan = record.plan; visit.status = record.status; visit.result = record.result; visit.risk = record.risk; visit.next = record.next;
}
function wp_updateStaticActionRow(record) {
const row = record.staticRow; if (!row) return;
row.dataset.type = record.type; row.dataset.status = record.status;
row.cells[0].textContent = record.name; row.cells[1].textContent = record.plan || '—'; row.cells[2].textContent = record.type;
row.cells[3].innerHTML = '<span class="object-tag">' + wp_tripLinkEscape(record.objectType || '—') + '</span>'; row.cells[4].textContent = record.relatedObject || '—';
row.cells[5].textContent = record.plannedDateTime.slice(5,16).replace('T',' '); row.cells[7].innerHTML = '<span class="state ' + (record.status === '已完成' ? 'done' : 'pending') + '">' + record.status + '</span>'; row.cells[8].textContent = record.next || '—';
}
const tripLinkBaseSaveAction = wp_saveAction;
wp_saveAction = function() {
const tripId = document.getElementById('action-trip-id').value;
const plannedDateTime = document.getElementById('action-datetime').value;
if (!wp_tripLinkValidateSelection(tripId, plannedDateTime)) return;
const wasEditing = editingAction;
const beforeCount = (dailyPlans[selectedDate] || []).length;
const objectTypeCode = document.getElementById('action-object-type').value;
const objectName = document.getElementById('action-object').selectedOptions[0]?.textContent || '';
tripLinkBaseSaveAction();
if (document.getElementById('action-modal').classList.contains('show')) return;
const record = wasEditing || (dailyPlans[selectedDate] || [])[beforeCount];
if (!record) return;
record.actionId ||= 'W' + (++tripLinkActionSequence);
record.objectType = document.getElementById('action-object-type').selectedOptions[0]?.textContent || '—';
record.objectTypeCode = objectTypeCode;
record.plannedDateTime = plannedDateTime;
record.owner ||= '张明远';
if (!wasEditing) record.userCreated = true;
if (record.userCreated) {
Object.values(dailyPlans).forEach(items => { const index = items.indexOf(record); if (index >= 0) items.splice(index, 1); });
(dailyPlans[plannedDateTime.slice(0,10)] ||= []).push(record);
}
if (record.tripWorkPlan) wp_updateTripDerivedVisit(record, plannedDateTime, objectTypeCode, objectName);
else wp_syncTripLinkVisitFromAction(record, tripId, plannedDateTime, objectTypeCode, objectName);
wp_updateStaticActionRow(record);
wp_syncTripWorkPlans(); wp_renderTripTable(); wp_syncTripActionList(); wp_syncStandaloneActionList();
if (record.tripWorkPlan && document.getElementById('trip-detail-modal')?.classList.contains('show')) wp_openTripDetail(record.tripId);
};
function wp_tripLinkFindAction(actionId) {
return Object.values(dailyPlans).flat().find(plan => plan.actionId === actionId);
}
function wp_tripLinkOpenActionDetail(actionId) { const plan = wp_tripLinkFindAction(actionId); if (plan) wp_openActionDetail(plan); }
function wp_tripLinkOpenActionEdit(actionId) { const plan = wp_tripLinkFindAction(actionId); if (plan) wp_openActionModal(plan.type, plan); }
function wp_tripLinkOpenCompletion(actionId) {
const plan = wp_tripLinkFindAction(actionId); if (!plan) return;
tripLinkCompletionRecord = plan;
document.getElementById('completion-name').value = plan.name;
document.getElementById('completion-time').value = plan.completedAt || plan.plannedDateTime;
document.getElementById('completion-result').value = '';
document.getElementById('completion-risk').value = '';
wp_openModal('completion-modal');
}
function wp_syncStandaloneActionList() {
const body = document.getElementById('action-table'); if (!body) return;
body.querySelectorAll('tr[data-user-work-plan]').forEach(row => row.remove());
Object.values(dailyPlans).flat().filter(plan => plan.userCreated && !plan.tripWorkPlan).forEach(plan => {
const statusClass = plan.status === '已完成' ? 'done' : plan.status === '已超时' ? 'overdue' : 'pending';
const period = plan.plannedDateTime?.slice(0,7) === '2026-07' ? 'month' : 'history';
body.insertAdjacentHTML('beforeend', '<tr data-user-work-plan="' + plan.actionId + '" data-type="' + wp_tripLinkEscape(plan.type) + '" data-status="' + wp_tripLinkEscape(plan.status) + '" data-period="' + period + '"><td class="name-cell">' + wp_tripLinkEscape(plan.name) + '</td><td>' + wp_tripLinkEscape(plan.plan || '—') + '</td><td>' + wp_tripLinkEscape(plan.type) + '</td><td><span class="object-tag">' + wp_tripLinkEscape(plan.objectType || '—') + '</span></td><td>' + wp_tripLinkEscape(plan.relatedObject || '—') + '</td><td>' + wp_tripLinkEscape((plan.plannedDateTime || '').slice(5,16).replace('T',' ')) + '</td><td>' + wp_tripLinkEscape(plan.owner || '张明远') + '</td><td><span class="state ' + statusClass + '">' + wp_tripLinkEscape(plan.status) + '</span></td><td>' + wp_tripLinkEscape(plan.next || '—') + '</td><td><button class="link" onclick="wp_tripLinkOpenActionDetail(\'' + plan.actionId + '\')">详情</button><button class="link" onclick="wp_tripLinkOpenActionEdit(\'' + plan.actionId + '\')">编辑</button>' + (plan.status === '已完成' ? '' : '<button class="link" onclick="wp_tripLinkOpenCompletion(\'' + plan.actionId + '\')">登记完成</button>') + '</td></tr>');
});
wp_applyWorkplanColumns();
wp_filterActionTable();
}
const tripLinkBaseSaveQuickCompletion = wp_saveQuickCompletion;
wp_saveQuickCompletion = function() {
if (!tripLinkCompletionRecord) { tripLinkBaseSaveQuickCompletion(); return; }
const result = document.getElementById('completion-result').value.trim();
if (!result) { wp_showToast('请填写完成结果 / 客户反馈'); return; }
const record = tripLinkCompletionRecord;
record.status = '已完成'; record.completedAt = document.getElementById('completion-time').value; record.completionResult = result; record.result = result;
record.risk = document.getElementById('completion-risk').value.trim();
wp_syncTripLinkVisitFromAction(record, record.tripId, record.plannedDateTime, record.objectTypeCode, record.relatedObject);
tripLinkCompletionRecord = null; wp_closeModal('completion-modal');
wp_renderDaySummary(); wp_renderDual50(); wp_renderTripTable(); wp_syncStandaloneActionList(); wp_showToast('完成登记已保存', 'success');
};
window.wp_openTripVisitEdit=wp_openTripVisitEdit;
window.wp_normalizeCompletionVocabulary=wp_normalizeCompletionVocabulary;window.wp_syncTripLinkVisitFromAction=wp_syncTripLinkVisitFromAction;window.wp_syncTripLinkActionFromVisit=wp_syncTripLinkActionFromVisit;window.wp_tripLinkPlanFromStaticRow=wp_tripLinkPlanFromStaticRow;window.wp_tripLinkValidateSelection=wp_tripLinkValidateSelection;window.wp_refreshActionTripOptions=wp_refreshActionTripOptions;window.wp_tripLinkOpenActionDetail=wp_tripLinkOpenActionDetail;window.wp_syncStandaloneActionList=wp_syncStandaloneActionList;window.wp_ensureTripWorkPlanShape=wp_ensureTripWorkPlanShape;window.wp_openTripVisitCompletion=wp_openTripVisitCompletion;window.wp_renderWorkplanFieldList=wp_renderWorkplanFieldList;window.wp_openStaticActionDetail=wp_openStaticActionDetail;window.wp_toggleCompletionFields=wp_toggleCompletionFields;window.wp_tripLinkObjectTypeCode=wp_tripLinkObjectTypeCode;window.wp_renderActionTripDetail=wp_renderActionTripDetail;window.wp_updateTripDerivedVisit=wp_updateTripDerivedVisit;window.wp_tripLinkOpenActionEdit=wp_tripLinkOpenActionEdit;window.wp_tripLinkOpenCompletion=wp_tripLinkOpenCompletion;window.wp_ensureTripDetailModal=wp_ensureTripDetailModal;window.wp_closeWorkplanPopovers=wp_closeWorkplanPopovers;window.wp_toggleWorkplanPopover=wp_toggleWorkplanPopover;window.wp_initTripAssociationUI=wp_initTripAssociationUI;window.wp_updateStaticActionRow=wp_updateStaticActionRow;window.wp_openStaticActionEdit=wp_openStaticActionEdit;window.wp_tripAttributionLabel=wp_tripAttributionLabel;window.wp_toggleWorkplanColumn=wp_toggleWorkplanColumn;window.wp_applyWorkplanColumns=wp_applyWorkplanColumns;window.wp_resetWorkplanColumns=wp_resetWorkplanColumns;window.wp_openPlanFromSummary=wp_openPlanFromSummary;window.wp_renderTripFieldList=wp_renderTripFieldList;window.wp_renderTripVisitRows=wp_renderTripVisitRows;window.wp_changeTripVisitType=wp_changeTripVisitType;window.wp_openTripVisitDetail=wp_openTripVisitDetail;window.wp_openQuickCompletion=wp_openQuickCompletion;window.wp_saveQuickCompletion=wp_saveQuickCompletion;window.wp_refreshWorkplanList=wp_refreshWorkplanList;window.wp_initActionTableDrag=wp_initActionTableDrag;window.wp_selectCalendarDate=wp_selectCalendarDate;window.wp_openNewForSelected=wp_openNewForSelected;window.wp_editSelectedAction=wp_editSelectedAction;window.wp_removeTripVisitRow=wp_removeTripVisitRow;window.wp_syncTripActionList=wp_syncTripActionList;window.wp_setWorkplanDensity=wp_setWorkplanDensity;window.wp_tripLinkFindAction=wp_tripLinkFindAction;window.wp_initTripListTools=wp_initTripListTools;window.wp_syncTripWorkPlans=wp_syncTripWorkPlans;window.wp_openCompleteModal=wp_openCompleteModal;window.wp_filterActionTable=wp_filterActionTable;window.wp_completionStatus=wp_completionStatus;window.wp_renderDaySummary=wp_renderDaySummary;window.wp_openActionDetail=wp_openActionDetail;window.wp_applyTripColumns=wp_applyTripColumns;window.wp_toggleTripColumn=wp_toggleTripColumn;window.wp_resetTripColumns=wp_resetTripColumns;window.wp_tripVisitOptions=wp_tripVisitOptions;window.wp_changeActionHint=wp_changeActionHint;window.wp_tripLinkDateTime=wp_tripLinkDateTime;window.wp_openActionModal=wp_openActionModal;window.wp_tripObjectLabel=wp_tripObjectLabel;window.wp_tripVisitCounts=wp_tripVisitCounts;window.wp_renderTripTable=wp_renderTripTable;window.wp_filterTripTable=wp_filterTripTable;window.wp_addTripVisitRow=wp_addTripVisitRow;window.wp_loadObjectQueue=wp_loadObjectQueue;window.wp_closeTripTools=wp_closeTripTools;window.wp_toggleTripTool=wp_toggleTripTool;window.wp_setTripDensity=wp_setTripDensity;window.wp_openTripDetail=wp_openTripDetail;window.wp_openFocusModal=wp_openFocusModal;window.wp_showActionPage=wp_showActionPage;window.wp_tripLinkEscape=wp_tripLinkEscape;window.wp_tripDateLabel=wp_tripDateLabel;window.wp_nextTripVisit=wp_nextTripVisit;window.wp_openTripModal=wp_openTripModal;window.wp_tripLinkLabel=wp_tripLinkLabel;window.wp_getCellPlans=wp_getCellPlans;window.wp_renderDual50=wp_renderDual50;window.wp_initCalendar=wp_initCalendar;window.wp_tripSchedule=wp_tripSchedule;window.wp_getTripVisit=wp_getTripVisit;window.wp_completeTask=wp_completeTask;window.wp_tripLinkCode=wp_tripLinkCode;window.wp_switchView=wp_switchView;window.wp_closeModal=wp_closeModal;window.wp_selectTrip=wp_selectTrip;window.wp_saveAction=wp_saveAction;window.wp_openModal=wp_openModal;window.wp_maskClose=wp_maskClose;window.wp_dateLabel=wp_dateLabel;window.wp_tripRange=wp_tripRange;window.wp_changeTab=wp_changeTab;window.wp_showToast=wp_showToast;window.wp_saveFocus=wp_saveFocus;window.wp_saveTrip=wp_saveTrip;
const mount=document.getElementById('workplanMount');
const roots={workplan:'workplanRoot',workplanList:'workplanListRoot',workplanTrips:'workplanTripsRoot'};
const views={workplan:'dashboard',workplanList:'actions',workplanTrips:'trips'};
let initialized=false;
function init(){
if(initialized)return;
initialized=true;
[
wp_normalizeCompletionVocabulary,
wp_initTripAssociationUI,
wp_initActionTableDrag,
wp_initActionNameLinks,
wp_initCalendar,
wp_initTripListTools,
wp_syncTripWorkPlans,
wp_renderTripTable,
wp_syncTripActionList,
wp_syncStandaloneActionList,
wp_applyTripColumns
].forEach(fn=>{
try{fn()}
catch(error){console.error('[workplan:init]',fn.name,error)}
});
}
function enter(page){
init();
const root=document.getElementById(roots[page]);
if(!root||!mount)return;
root.appendChild(mount);
wp_switchView(views[page]);
window.scrollTo({top:0,behavior:'auto'});
}
init();
return{enter};
})();</script>
<script id="home-today-work-controller">
(function(){
'use strict';
const workDate='2026-07-29';
let homeCompletionPlan=null;
const baseSaveCompletion=window.wp_saveQuickCompletion;
const baseSaveAction=window.wp_saveAction;
const baseCloseModal=window.wp_closeModal;
const baseMaskClose=window.wp_maskClose;
function hostWorkplanModal(id){
const modal=document.getElementById(id);
if(!modal)return;
let host=document.getElementById('homeWorkplanModalHost');
if(!host){
host=document.createElement('div');
host.id='homeWorkplanModalHost';
host.className='workplan-native home-workplan-modal-host';
document.body.appendChild(host);
}
if(modal.parentElement!==host)host.appendChild(modal);
modal.dataset.homeWorkplanHosted='1';
}
function restoreWorkplanModal(id){
const modal=document.getElementById(id);
if(!modal || modal.dataset.homeWorkplanHosted!=='1')return;
const mount=document.getElementById('workplanMount');
if(mount)mount.appendChild(modal);
delete modal.dataset.homeWorkplanHosted;
}
function escapeHtml(value){
return String(value == null ? '' : value).replace(/[&<>"']/g,function(char){
return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[char];
});
}
function plans(){
return (window.wp_getCellPlans ? window.wp_getCellPlans(workDate) : []).filter(function(plan){
return plan.time!=='全天';
}).slice().sort(function(a,b){
const timeA=a.time==='全天'?'23:59':a.time;
const timeB=b.time==='全天'?'23:59':b.time;
return timeA.localeCompare(timeB);
}).slice(0,5);
}
function relatedObject(plan){
const relation=plan.relatedObject || (plan.object || '').split(' · ')[1] || '未关联对象';
return relation.replace(/((?:项目|客户|潜在客户|商机|行业关系|方案卡))$/,'');
}
function workItem(plan){
if(plan.workItem)return plan.workItem;
if(plan.item)return plan.item;
const parts=String(plan.name || '').split('|');
return parts.length>1 ? parts.slice(1).join('|') : (plan.name || '未填写工作事项');
}
function status(plan){
return window.wp_completionStatus ? window.wp_completionStatus(plan) : (plan.status || '未完成');
}
function actionButton(index,action,label){
return '<button type="button" class="home-row-action home-work-action" onclick="homeOpenTodayWork(event,'+index+',\''+action+'\')">'+label+'</button>';
}
function homeRenderTodayWork(){
const list=document.getElementById('homeTodayWorkList');
if(!list)return;
list.innerHTML=plans().map(function(plan,index){
const currentStatus=status(plan);
const statusClass=currentStatus==='已完成'?' done':currentStatus==='已超时'?' overdue':'';
const completionLabel=currentStatus==='已完成'?'更新登记':'登记完成';
const primary=escapeHtml(plan.type || '工作计划')+'<span>|</span>'+escapeHtml(relatedObject(plan));
return '<div class="today-work-item" data-home-work-index="'+index+'"><time>'+escapeHtml(plan.time || '待定')+'</time><div class="home-work-copy"><b title="'+escapeHtml((plan.type || '工作计划')+'|'+relatedObject(plan))+'">'+primary+'</b><small title="'+escapeHtml(workItem(plan))+'">'+escapeHtml(workItem(plan))+'</small></div><div class="home-work-side">'+actionButton(index,'detail','详情')+actionButton(index,'edit','编辑')+actionButton(index,'complete',completionLabel)+'<span class="today-work-status'+statusClass+'">'+escapeHtml(currentStatus)+'</span></div></div>';
}).join('');
}
function findPlan(index){ return plans()[Number(index)] || null; }
function objectTypeCode(plan){
if(plan.objectTypeCode)return plan.objectTypeCode;
const source=(plan.objectType || '')+' '+(plan.object || '')+' '+(plan.relatedObject || '');
if(source.includes('客户'))return 'customer';
if(source.includes('商机'))return 'opportunity';
if(source.includes('行业'))return 'industry';
return 'project';
}
function openEdit(plan){
window.wp_selectCalendarDate(workDate);
hostWorkplanModal('action-modal');
window.wp_openActionModal(plan.type,plan);
const dateTime=document.getElementById('action-datetime');
const typeField=document.getElementById('action-object-type');
const objectField=document.getElementById('action-object');
const nameField=document.getElementById('action-name');
if(dateTime)dateTime.value=plan.plannedDateTime || (workDate+'T'+(plan.time==='全天'?'09:00':plan.time));
if(typeField){typeField.value=objectTypeCode(plan);window.wp_loadObjectQueue()}
if(objectField)objectField.value=relatedObject(plan);
if(nameField)nameField.value=workItem(plan);
}
function openCompletion(plan){
homeCompletionPlan=plan;
window.wp_selectCalendarDate(workDate);
hostWorkplanModal('completion-modal');
if(plan.actionId && window.wp_tripLinkOpenCompletion){
window.wp_tripLinkOpenCompletion(plan.actionId);
}else{
document.getElementById('completion-name').value=workItem(plan);
document.getElementById('completion-time').value=plan.completedAt || (workDate+'T'+(plan.time==='全天'?'09:00':plan.time));
document.getElementById('completion-result').value=plan.completionResult || (status(plan)==='已完成' ? plan.result || '' : '');
document.getElementById('completion-risk').value=plan.risk || '';
window.wp_openModal('completion-modal');
}
document.getElementById('completion-result').value=plan.completionResult || (status(plan)==='已完成' ? plan.result || '' : '');
document.getElementById('completion-risk').value=plan.risk || '';
}
function homeOpenTodayWork(event,index,action){
event.preventDefault();
event.stopPropagation();
const plan=findPlan(index);
if(!plan)return;
if(action==='detail'){
window.wp_selectCalendarDate(workDate);
hostWorkplanModal('action-detail-modal');
window.wp_openActionDetail(plan);
}else if(action==='edit'){
openEdit(plan);
}else if(action==='complete'){
openCompletion(plan);
}
}
window.wp_saveQuickCompletion=function(){
if(!homeCompletionPlan){baseSaveCompletion();homeRenderTodayWork();return}
if(homeCompletionPlan.actionId){
baseSaveCompletion();
}else{
const result=document.getElementById('completion-result').value.trim();
if(!result){window.wp_showToast('请填写完成结果 / 客户反馈');return}
homeCompletionPlan.status='已完成';
homeCompletionPlan.completedAt=document.getElementById('completion-time').value;
homeCompletionPlan.completionResult=result;
homeCompletionPlan.risk=document.getElementById('completion-risk').value.trim();
homeCompletionPlan.result=result+(homeCompletionPlan.risk?';风险/协同:'+homeCompletionPlan.risk:'');
baseCloseModal('completion-modal');
window.wp_renderDaySummary();
window.wp_renderDual50();
window.wp_showToast('完成登记已保存','success');
}
restoreWorkplanModal('completion-modal');
homeCompletionPlan=null;
homeRenderTodayWork();
};
window.wp_saveAction=function(){
baseSaveAction();
homeRenderTodayWork();
};
window.wp_closeModal=function(id){
if(id==='completion-modal')homeCompletionPlan=null;
baseCloseModal(id);
restoreWorkplanModal(id);
};
window.wp_maskClose=function(event,id){
baseMaskClose(event,id);
if(event.target && event.target.id===id){
if(id==='completion-modal')homeCompletionPlan=null;
restoreWorkplanModal(id);
}
};
window.homeOpenTodayWork=homeOpenTodayWork;
window.homeRenderTodayWork=homeRenderTodayWork;
homeRenderTodayWork();
})();
</script>
<style id="home-incremental-overrides">
#modal .modal.crm-home-customer-dialog{width:min(1120px,calc(100vw - 64px))!important;max-width:none!important;height:min(86vh,820px)!important;max-height:calc(100vh - 48px)!important;display:grid!important;grid-template-rows:auto minmax(0,1fr) auto!important}
#modal .modal.crm-home-customer-dialog .m-body{min-height:0!important;overflow:auto!important;padding:14px 16px!important;background:#f5f7fa!important}
#modal .modal.crm-home-customer-dialog .m-foot{display:flex!important;justify-content:flex-end!important;gap:8px!important;background:#fff!important}
#modal .modal.crm-home-customer-dialog .crm-form-page{min-height:0!important;background:transparent!important}
#modal .modal.crm-home-customer-dialog .crm-form-section{margin-bottom:12px!important;background:#fff!important}
#modal .modal.crm-home-customer-dialog .crm-form-grid{grid-template-columns:120px minmax(180px,1fr) 120px minmax(180px,1fr)!important}
#modal .modal.crm-home-customer-dialog .crm-contact-editor{overflow:auto!important}
#modal .modal.crm-home-customer-dialog .crm-contact-editor .crm-table{min-width:1060px!important}
.home-workplan-modal-host{position:fixed!important;inset:0!important;z-index:299!important;min-width:0!important;min-height:0!important;padding:0!important;margin:0!important;background:transparent!important;pointer-events:none!important}
.home-workplan-modal-host .modal-mask{pointer-events:auto!important}
@media(max-width:900px){#modal .modal.crm-home-customer-dialog{width:calc(100vw - 24px)!important;height:calc(100vh - 24px)!important}#modal .modal.crm-home-customer-dialog .crm-form-grid{grid-template-columns:110px minmax(0,1fr)!important}}
</style>
<style id="home-dashboard-enhancements">
#home .home-hero-side{display:flex;min-width:440px;align-items:center;justify-content:flex-end}
#home .home-data-bar{order:1!important;min-height:28px;display:flex;align-items:center;justify-content:flex-end;margin:0 0 6px;padding:0 2px}
#home .hero{order:2!important}
#home .kpis{order:3!important}
#home .home-data-context{display:flex;align-items:center;justify-content:flex-end;gap:8px;color:#64748b;font-size:11px;line-height:1}
#home .home-data-context-label{white-space:nowrap}
#home .home-scope-picker{position:relative}
#home .home-scope-trigger{width:190px;height:28px;display:flex;align-items:center;justify-content:space-between;gap:8px;border:1px solid #cfdced;border-radius:5px;background:#fff;padding:0 9px;color:#263b59;font:inherit;font-weight:600;cursor:pointer}
#home .home-scope-trigger:hover,#home .home-scope-trigger[aria-expanded="true"]{border-color:#7fb2f7;box-shadow:0 0 0 2px rgba(22,119,255,.07)}
#home .home-scope-trigger>span:first-child{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
#home .home-scope-chevron{color:#7b8da6;font-size:14px}
#home .home-scope-popover{display:none;position:absolute;right:0;top:34px;z-index:80;width:310px;padding:10px;border:1px solid #d6e1ee;border-radius:7px;background:#fff;box-shadow:0 14px 34px rgba(15,23,42,.16)}
#home .home-scope-popover.show{display:block}
#home .home-scope-search{width:100%;height:32px;box-sizing:border-box;border:1px solid #cfd9e5;border-radius:5px;padding:0 9px;color:#334155;font:inherit;outline:none}
#home .home-scope-search:focus{border-color:#7cb5f5;box-shadow:0 0 0 2px rgba(22,119,255,.08)}
#home .home-scope-options{max-height:246px;margin-top:8px;overflow:auto}
#home .home-scope-group{padding:4px 0 6px}
#home .home-scope-group+.home-scope-group{border-top:1px solid #edf2f7}
#home .home-scope-group-title{padding:5px 8px;color:#8a99ab;font-size:11px;font-weight:700}
#home .home-scope-group button{width:100%;min-height:38px;display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:8px;border:0;border-radius:5px;background:#fff;padding:5px 8px;color:#24364f;text-align:left;cursor:pointer}
#home .home-scope-group button:hover{background:#f3f8ff;color:#1677ff}
#home .home-scope-group button.active{background:#eaf3ff;color:#1677ff;font-weight:700}
#home .home-scope-group button small{color:#8a99ab;font-size:10.5px;font-weight:400}
#home .home-data-updated{display:inline-flex;align-items:center;gap:4px;white-space:nowrap}
#home .home-data-updated .home-data-clock{width:13px;height:13px;flex:0 0 auto;color:#8091a7}
#home .home-data-updated b{color:#52677d;font-weight:600}
#home .hero-actions{justify-content:flex-end}
#home .home-card-head-actions{display:flex;align-items:center;gap:10px}
#home .home-card-head-actions small{color:#8a99ab;font-size:10.5px;font-weight:400}
#home button.more{height:auto;border:0;background:transparent;padding:0;color:#334155;font:inherit;cursor:pointer}
#home button.more:hover{color:#1677ff}
#home [data-home-card="double50"]{display:grid!important;grid-template-rows:40px minmax(0,1fr)!important}
#home [data-home-card="double50"]>.card-body{min-height:0!important;display:grid!important;place-items:center!important;overflow:hidden!important}
#home [data-home-card="double50"] .planning-placeholder{width:100%;height:auto!important;box-sizing:border-box;padding:0 18px!important;transform:translateY(-3px)}
#home .today-work-list{height:100%;min-height:0;display:grid;grid-template-rows:repeat(4,minmax(0,1fr));align-content:stretch}
#home .today-work-item{grid-template-columns:42px minmax(0,1fr) auto!important;align-items:center!important;gap:8px!important;min-height:0!important;height:auto;padding:4px 0!important}
#home .home-work-copy{min-width:0;display:grid;gap:2px;align-content:center}
#home .home-work-copy b,#home .home-work-copy small{display:block;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
#home .home-work-copy b{color:#173b64;font-size:12.5px;line-height:1.2}
#home .home-work-copy b span{color:#94a3b8;font-weight:400}
#home .home-work-copy small{color:#74849a;font-size:10.5px;line-height:1.15}
#home .home-work-side{display:flex;align-items:center;justify-content:flex-end;gap:4px;white-space:nowrap}
#home .today-work-status{padding:1px 5px;font-size:10px}
#home .today-work-status.done{background:#ecfdf3;color:#15803d}
#home .home-row-action{height:24px!important;border:0!important;border-radius:4px!important;background:transparent!important;padding:0 5px!important;color:#1769d2!important;font:400 11px/24px "Microsoft YaHei","PingFang SC",Arial,sans-serif!important;letter-spacing:0!important;cursor:pointer;white-space:nowrap}
#home .home-row-action:hover{background:#edf5ff!important}
#home .funnel-step{grid-template-columns:62px minmax(0,1fr) 30px!important}
#home .funnel-step>span:first-child{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#475569}
#home .funnel-bar-fill{min-width:2px;transition:width .24s ease}
#home .funnel-step-num{color:#24364f}
#home .home-customer-list{height:100%;min-height:0;display:grid;grid-template-rows:repeat(3,minmax(0,1fr));align-content:stretch}
#home .home-customer-row{min-width:0;display:grid;grid-template-columns:25px minmax(0,1fr) auto auto;align-items:center;gap:8px;padding:6px 0;border-bottom:1px solid #edf2f7}
#home .home-customer-row:last-child{border-bottom:0}
#home .home-customer-row>.ui-icon{width:18px;height:18px;color:#5685c5}
#home .home-customer-main{min-width:0}
#home .home-customer-name{display:block;min-width:0}
#home .home-customer-name b{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#173b64;font-size:12.5px}
#home .home-customer-main small{display:block;margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#74849a;font-size:10.5px}
#home .home-customer-actions{grid-column:3;display:flex;align-items:center;justify-content:flex-end;gap:4px;min-width:0}
#home .home-customer-level{grid-column:4;justify-self:end;flex:0 0 auto;padding:1px 6px;border-radius:9px;background:#eef5ff;color:#3975c4;font-size:10px;font-style:normal;white-space:nowrap}
#home .home-project-table-wrap{height:100%;overflow:hidden}
#home .home-project-table{width:100%;table-layout:fixed}
#home .home-project-table th:nth-child(1){width:28%}#home .home-project-table th:nth-child(2){width:18%}#home .home-project-table th:nth-child(3){width:14%}#home .home-project-table th:nth-child(4){width:22%}#home .home-project-table th:nth-child(5){width:18%}
#home .home-project-table td{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
#home .home-project-table .project-progress{min-width:0}
#home .home-project-table .project-progress>span{min-width:34px}
#home .risk-list li{display:block!important;min-width:0}
#home .home-reminder-link{width:100%;display:flex;align-items:center;gap:8px;border:0;background:transparent;padding:0;color:#334155;font:inherit;text-align:left;cursor:pointer}
#home .home-reminder-link:hover{color:#1677ff}
#home .home-reminder-link>span:last-child{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
#modal .modal.home-dashboard-modal{width:min(680px,calc(100vw - 40px));max-width:none}
#modal .modal.home-dashboard-modal.home-contact-dialog{width:min(760px,calc(100vw - 40px))}
#modal .modal.home-dashboard-modal .m-body{max-height:62vh;overflow:auto;padding:16px 18px}
.home-contact-list{display:grid;gap:10px}.home-contact-toolbar{display:flex;align-items:center;justify-content:space-between;gap:12px;color:#64748b;font-size:12px}.home-contact-toolbar b{color:#24364f}.home-contact-toolbar input{width:220px;height:32px;box-sizing:border-box;border:1px solid #cfd9e5;border-radius:5px;padding:0 9px;color:#334155;font:inherit}.home-contact-table-wrap{max-height:min(44vh,330px);overflow:auto;border:1px solid #dce6f1;border-radius:6px}.home-contact-table{width:100%;border-collapse:collapse;table-layout:fixed;font-size:12px}.home-contact-table th,.home-contact-table td{padding:9px 10px;border-bottom:1px solid #edf2f7;text-align:left;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.home-contact-table th{position:sticky;top:0;background:#f7f9fc;color:#64748b;font-weight:600}.home-contact-table tr:last-child td{border-bottom:0}.home-contact-table th:nth-child(1){width:17%}.home-contact-table th:nth-child(2){width:16%}.home-contact-table th:nth-child(3){width:16%}.home-contact-table th:nth-child(4){width:18%}.home-contact-table th:nth-child(5){width:23%}.home-contact-table th:nth-child(6){width:10%}.home-contact-copy{border:0;background:transparent;padding:0;color:#1769d2;font:inherit;cursor:pointer}.home-contact-empty{padding:24px 12px;text-align:center;color:#94a3b8}
.home-follow-form{display:grid;grid-template-columns:100px minmax(0,1fr);gap:12px;align-items:center}.home-follow-form label{color:#64748b}.home-follow-form input,.home-follow-form select,.home-follow-form textarea{width:100%;box-sizing:border-box;border:1px solid #cfd9e5;border-radius:5px;padding:0 9px;color:#334155;font:inherit}.home-follow-form input,.home-follow-form select{height:34px}.home-follow-form textarea{height:104px;padding:9px;resize:vertical}.home-follow-form .full-label{align-self:start;padding-top:8px}
.home-record-list{display:grid;gap:0}.home-record-item{padding:12px 2px;border-bottom:1px solid #edf2f7}.home-record-item:last-child{border-bottom:0}.home-record-item b{display:block;margin-bottom:5px;color:#24364f}.home-record-item p{margin:0 0 5px;color:#475569;line-height:1.55}.home-record-item small{color:#8a99ab}
@media(min-width:1280px) and (min-height:760px){
.neo-shell.home-active .content{overflow:hidden!important}
#home.active,#home .workbench,#home .main-col{height:100%!important;min-height:0!important}
#home .main-col{display:grid!important;grid-template-rows:28px 66px 86px minmax(140px,1fr) minmax(140px,1fr) 128px!important;gap:4px!important}
#home .hero,#home .home-data-bar,#home .kpis,#home .layout-grid,#home .management-grid,#home .priority-grid{margin:0!important;min-height:0!important}
#home .hero{height:66px!important;padding:8px 18px!important}
#home .hero h1{font-size:22px!important;margin:0 0 3px!important}
#home .hero p{font-size:12px!important}
#home .home-data-bar{height:28px!important}
#home .kpis{height:86px!important}
#home .kpi{height:86px!important;min-height:0!important;padding:8px 14px!important}
#home .kpi .num{font-size:24px!important;margin-top:3px!important}
#home .today-work-item{min-height:0!important}
#home .home-customer-row{padding:6px 0!important}
#home .layout-grid,#home .management-grid,#home .priority-grid{height:100%!important}
#home .layout-grid>.card,#home .management-grid>.card,#home .priority-grid>.card{height:100%!important;min-height:0!important}
}
@media(max-width:1360px){#home .home-hero-side{min-width:400px}#home .home-scope-trigger{width:175px}}
@media(max-width:900px){#home .hero{align-items:stretch!important;flex-direction:column!important}#home .home-hero-side{min-width:0;align-items:stretch}#home .home-data-bar{justify-content:flex-start;height:auto;min-height:28px}#home .home-data-context{justify-content:flex-start;flex-wrap:wrap}#home .hero-actions{justify-content:flex-start}#home .home-scope-popover{left:0;right:auto}.home-follow-form{grid-template-columns:1fr}.home-follow-form .full-label{padding-top:0}.home-contact-toolbar{align-items:stretch;flex-direction:column}.home-contact-toolbar input{width:100%}}
</style>
<style id="home-empty-state-style">
/* 首页空值演示:不改写原始数据,只切换当前首页的视觉状态。 */
#home .home-empty-toggle{position:fixed;right:20px;bottom:16px;z-index:60;height:27px;padding:0 9px;border:1px solid #d8e2ef;border-radius:5px;background:rgba(255,255,255,.92);color:#7c8ba0;font-size:11px;box-shadow:0 2px 8px rgba(15,23,42,.05);cursor:pointer;opacity:.42;backdrop-filter:blur(4px);transition:opacity .16s ease,color .16s ease,border-color .16s ease,box-shadow .16s ease}
#home .home-empty-toggle:hover,#home .home-empty-toggle:focus-visible{opacity:1;color:#1677ff;border-color:#a9caff;box-shadow:0 4px 14px rgba(22,119,255,.12);outline:none}
#home .home-empty-toggle.is-active{opacity:.78;color:#1677ff;border-color:#c4d9fa;background:#f8fbff}
#home .home-empty-panel{display:none;height:100%;min-height:0;box-sizing:border-box;align-items:center;justify-content:center;flex-direction:column;text-align:center;padding:4px 14px;color:#72839a}
#home.home-empty-mode [data-home-card].home-empty-host>.card-body>:not(.home-empty-panel){display:none!important}
#home.home-empty-mode .home-empty-panel{display:flex!important}
#home .home-empty-illustration{display:block;width:108px;height:80px;flex:0 0 auto;margin:0 auto 3px;filter:drop-shadow(0 5px 8px rgba(65,112,183,.05))}
#home .home-empty-illustration.is-compact{width:88px;height:64px;margin:0 12px 0 0}
#home .home-empty-illustration.is-small{width:84px;height:60px;margin:0 auto}
#home .home-empty-title{margin:0 0 3px;color:#24364f;font-size:14px;font-weight:700;line-height:1.35}
#home .home-empty-subtitle{max-width:310px;margin:0;color:#71839c;font-size:11.5px;line-height:1.5}
#home .home-empty-action{height:28px;margin-top:9px;padding:0 13px;border:1px solid #a8c9ff;border-radius:5px;background:#f8fbff;color:#1769d2;font-size:11px;cursor:pointer;transition:.16s ease}
#home .home-empty-action:hover{border-color:#72a9ff;background:#eef6ff}
#home .home-empty-chips{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;width:min(100%,410px);margin-top:11px}
#home .home-empty-chip{height:28px;border:1px solid #e6edf6;border-radius:6px;background:#f7f9fc;color:#9aa8b9;font-size:11px}
#home .home-empty-panel.is-inline{flex-direction:row;justify-content:center;text-align:left;padding:4px 24px}
#home .home-empty-panel.is-inline .home-empty-copy{min-width:230px}
#home.home-empty-mode .kpi-trend-bar i{width:0!important}
#home.home-empty-mode .kpi:hover{transform:none;box-shadow:0 1px 2px rgba(15,23,42,.025)}
#home.home-empty-mode .layout-grid>.card{height:222px!important}
#home.home-empty-mode .management-grid>.card{height:142px!important}
#home.home-empty-mode .priority-grid>.card{min-height:108px!important}
#home.home-empty-mode .layout-grid,#home.home-empty-mode .management-grid{align-items:stretch!important}
#home.home-empty-mode [data-home-card="double50"]{display:none!important}
#home.home-empty-mode .layout-grid .card-body,#home.home-empty-mode .management-grid .card-body,#home.home-empty-mode .priority-grid .card-body{display:block!important;height:100%;padding:6px 10px!important;box-sizing:border-box}
#home.home-empty-mode [data-home-card="reminder"] .card-body{padding:2px 10px!important}
@media(max-width:1100px){#home .home-empty-toggle{right:12px;bottom:12px}#home .home-empty-panel.is-inline .home-empty-copy{min-width:0}}
/* 2026-08-05 restored from 商机查重规则深化版-v1.4 */
.os-process-flow{display:grid;grid-template-columns:minmax(150px,.65fr) 24px minmax(210px,1fr) 24px minmax(210px,1fr) 24px minmax(240px,1.15fr);gap:8px;align-items:stretch}.os-process-flow>i{display:grid;place-items:center;color:#8ba2ba;font-style:normal;font-size:19px}.os-process-card{border:1px solid #dce6f1;border-radius:7px;background:#fff;padding:12px}.os-process-card.find{border-color:#9dc8f7;background:#f1f7ff}.os-process-card.analyse{border-color:#c7b7ef;background:#f8f5ff}.os-process-card.result{border-color:#edc98e;background:#fff9ef}.os-process-card span,.os-process-card b,.os-process-card small{display:block}.os-process-card span{color:#718197;font-size:12px}.os-process-card b{margin-top:6px;color:#294763;font-size:13px;line-height:1.55}.os-process-card small{margin-top:5px;color:#7b8a9d;font-size:11px;line-height:1.45}.os-level-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.os-level-card.safe{border-top:3px solid #31a36b}.os-level-card.safe>span{background:#eaf8f0;color:#187c46}.os-preview-switch{align-items:center;flex-wrap:wrap}.os-preview-label{margin-left:8px;color:#7b8a9d;font-size:12px}.os-candidate-stack{display:grid;gap:10px;min-width:0}.os-candidate-card td.miss{width:54px;color:#8190a2;font-weight:700}.os-risk-tag.conflict{background:#fff4df;color:#b56b00}.os-risk-tag.cross{background:#f1edff;color:#6846b7}.os-risk-tag.safe{background:#eaf8f0;color:#187c46}.os-decision-panel{display:flex;align-items:center;justify-content:space-between;gap:18px;margin-top:12px;border:1px solid #dce6f1;border-radius:8px;padding:13px 14px}.os-decision-panel.warn{border-color:#f1c878;background:#fffaf0}.os-decision-panel.block{border-color:#edabab;background:#fff5f5}.os-decision-panel h4{margin:0 0 5px;color:#294763}.os-decision-panel p{margin:0;color:#65768b;font-size:12px;line-height:1.6}.os-decision-panel>div:last-child{display:flex;gap:8px;flex:0 0 auto}.os-conflict-created{display:grid;grid-template-columns:40px minmax(0,1fr) auto;gap:12px;align-items:start;margin-top:12px;border:1px solid #94d4ae;border-radius:8px;background:#f2fbf6;padding:14px}.os-created-icon{width:34px;height:34px;border-radius:50%;display:grid;place-items:center;background:#22a264;color:#fff;font-size:19px;font-weight:800}.os-conflict-created h4{margin:0 0 5px;color:#1d6041}.os-conflict-created p{margin:0;color:#567262;font-size:12px;line-height:1.6}.os-created-flow{display:flex;align-items:center;gap:7px;flex-wrap:wrap;margin-top:10px}.os-created-flow span,.os-created-flow b{border:1px solid #b9ddc8;border-radius:12px;background:#fff;padding:3px 8px;color:#35634a;font-size:11px}.os-created-flow i{color:#7e9c8b;font-style:normal}.os-human-flow{display:grid;grid-template-columns:1fr 24px 1fr 24px 1fr 24px 1fr;gap:8px;align-items:center;margin-top:14px}.os-human-flow>div{border:1px solid #dce6f1;border-radius:7px;background:#fbfdff;padding:11px}.os-human-flow b,.os-human-flow span{display:block}.os-human-flow b{color:#294763;font-size:13px}.os-human-flow span{margin-top:5px;color:#718197;font-size:12px;line-height:1.45}.os-human-flow>i{text-align:center;color:#8ba2ba;font-style:normal}.os-dedupe-preview-grid{grid-template-columns:minmax(290px,.65fr) minmax(520px,1.35fr)}
@media(max-width:1180px){.os-process-flow,.os-human-flow{grid-template-columns:1fr}.os-process-flow>i,.os-human-flow>i{transform:rotate(90deg)}.os-level-grid,.os-dedupe-preview-grid{grid-template-columns:1fr}.os-decision-panel{align-items:flex-start;flex-direction:column}.os-conflict-created{grid-template-columns:40px 1fr}.os-conflict-created>.os-btn{grid-column:2}}
.os-config-stack{display:grid;gap:12px}.os-check-list.horizontal{display:flex;align-items:center;gap:22px;flex-wrap:wrap}.os-check-list.horizontal label{min-width:auto}.os-editable-rule-table select,.os-treatment-table select{height:30px;border:1px solid #cad7e5;border-radius:4px;background:#fff;color:#40546b;padding:0 8px}.os-add-rule-row{display:flex;align-items:center;gap:8px;margin-top:12px}.os-add-rule-row select{min-width:220px;height:32px;border:1px solid #cad7e5;border-radius:5px;background:#fff;padding:0 8px}.os-relation-rule-list{display:grid;gap:12px;margin-bottom:12px}.os-relation-rule{border:1px solid #dce6f1;border-radius:8px;background:#fbfdff;padding:12px}.os-relation-rule>header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding-bottom:10px;border-bottom:1px solid #e7edf4}.os-relation-rule>header>div:first-child{display:flex;align-items:center;gap:10px;min-width:0}.os-relation-rule>header span{color:#7b8a9d;font-size:12px;white-space:nowrap}.os-relation-rule>header input{width:260px;height:32px;border:1px solid #cad7e5;border-radius:5px;padding:0 9px;font-weight:700;color:#294763;background:#fff}.os-relation-rule>header>div:last-child{display:flex;gap:8px}.os-text-btn.danger{color:#d44a4a}.os-rule-mode{display:flex;align-items:center;gap:8px;margin:11px 0;color:#52677d;font-size:13px}.os-rule-mode select,.os-rule-condition-list select{height:31px;border:1px solid #cad7e5;border-radius:5px;background:#fff;color:#40546b;padding:0 8px}.os-rule-condition-list{display:grid;gap:8px;margin-bottom:10px}.os-rule-condition-list>div{display:grid;grid-template-columns:minmax(180px,.8fr) minmax(140px,.45fr) auto;gap:8px;align-items:center}.os-treatment-options{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.os-treatment-options label{display:flex;align-items:center;gap:5px;white-space:nowrap;color:#52677d;font-size:12px}.os-detail-config-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}.os-detail-config-grid>div{border:1px solid #dce6f1;border-radius:7px;background:#fbfdff;padding:12px}.os-detail-config-grid>div>span{display:block;margin-bottom:9px;color:#718197;font-size:12px}.os-chip-row{display:flex;align-items:center;gap:7px;flex-wrap:wrap}.os-chip-row b{display:inline-flex;align-items:center;min-height:24px;border-radius:12px;background:#edf5ff;color:#1764b2;padding:0 9px;font-size:12px;font-weight:600}.os-chip-row.compact b{background:#f2f5f8;color:#506579}.os-rule-summary-list{display:grid;gap:9px}.os-rule-summary-list article{border:1px solid #dce6f1;border-radius:7px;background:#fbfdff;padding:11px 12px}.os-rule-summary-list header{display:flex;align-items:center;gap:10px}.os-rule-summary-list header>span{color:#7b8a9d;font-size:12px}.os-rule-summary-list p{margin:8px 0 0;color:#52677d;font-size:12px;line-height:1.6}.os-rule-summary-list strong{color:#294763}.os-treatment-table td:nth-child(2){min-width:360px;line-height:1.55}.os-dedupe-detail-card>.os-subtitle{align-items:flex-start}.os-dedupe-detail-card>.os-subtitle .os-muted{max-width:620px;text-align:right;line-height:1.5}.os-readonly-config input:disabled,.os-readonly-config select:disabled,.os-readonly-config textarea:disabled{background:#f5f7fa!important;color:#66788d!important;border-color:#d8e1eb!important;opacity:1!important;cursor:not-allowed}.os-readonly-config input[type=checkbox]:disabled{opacity:1;accent-color:#1677ff;cursor:not-allowed}.os-readonly-config button:disabled{color:#95a4b4!important;cursor:not-allowed!important;opacity:.72}.os-readonly-config .os-panel{background:#fbfcfe}
.os-rule-matrix{table-layout:fixed}.os-rule-matrix th:nth-child(1){width:72px}.os-rule-matrix th:nth-child(3){width:210px}.os-rule-matrix th:nth-child(4){width:250px}.os-rule-matrix td{vertical-align:middle}.os-rule-order{display:flex;align-items:center;gap:7px;color:#60758c;font-weight:700}.os-rule-order i{font-style:normal;color:#9aabba;font-weight:500}.os-rule-output-input{width:100%;height:32px;box-sizing:border-box;border:1px solid #cad7e5;border-radius:5px;padding:0 9px;color:#294763;font-weight:700;background:#fff}.os-expression-summary{display:flex;align-items:center;gap:8px;min-width:0}.os-expression-text{display:block;min-width:0;color:#40566e;font-size:12px;line-height:1.6;word-break:break-word}.os-expression-text.empty{color:#a07023}.os-rule-row-actions{display:flex;align-items:center;gap:9px;flex-wrap:wrap}.os-rule-editor-row>td{padding:0!important;background:#f7fbff}.os-expression-editor{padding:13px 14px;border-top:1px solid #dfe9f4}.os-expression-editor-head{display:flex;align-items:flex-start;justify-content:space-between;gap:14px;margin-bottom:10px}.os-expression-editor-head b{display:block;color:#294763;font-size:13px}.os-expression-editor-head span{display:block;margin-top:4px;color:#73859a;font-size:12px;line-height:1.5}.os-expression-editor-preview{margin-bottom:10px;border:1px solid #cfe0f2;border-radius:6px;background:#fff;padding:9px 11px;color:#40566e;font-size:12px;line-height:1.6}.os-expression-editor-preview strong{color:#1764b2}.os-expr-group{border:1px solid #dce6f1;border-radius:7px;background:#fff;padding:10px}.os-expr-group.nested{margin-left:18px;border-left:3px solid #8ebcf0;background:#fbfdff}.os-expr-group-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:8px}.os-expr-group-title{display:flex;align-items:center;gap:7px;color:#52677d;font-size:12px;font-weight:700}.os-expr-group-title .paren{display:inline-grid;place-items:center;min-width:24px;height:22px;border-radius:11px;background:#edf5ff;color:#1764b2;font-weight:800}.os-expr-group-tools{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.os-expr-children{display:grid;gap:7px}.os-expr-child{display:grid;grid-template-columns:54px minmax(0,1fr);gap:7px;align-items:start}.os-expr-child:first-child{grid-template-columns:54px minmax(0,1fr)}.os-expr-join{height:31px;border:1px solid #cad7e5;border-radius:5px;background:#fff;color:#1764b2;font-weight:700;padding:0 7px}.os-expr-join-placeholder{height:31px;display:grid;place-items:center;color:#9aa9b8;font-size:12px}.os-expr-condition{display:grid;grid-template-columns:minmax(180px,.75fr) minmax(140px,.45fr) auto;gap:8px;align-items:center}.os-expr-condition select{height:31px;border:1px solid #cad7e5;border-radius:5px;background:#fff;color:#40546b;padding:0 8px}.os-expr-node-actions{display:flex;align-items:center;gap:7px;white-space:nowrap}.os-expr-empty{border:1px dashed #bfd0e2;border-radius:6px;padding:12px;text-align:center;color:#7f91a5;font-size:12px;background:#fbfdff}.os-expr-group-body{min-width:0}.os-expr-group-body>.os-expr-group{margin:0}.os-expr-depth-note{color:#8291a4;font-size:11px}.os-expression-help{margin-top:10px;color:#718197;font-size:12px;line-height:1.55}.os-expression-help b{color:#294763}
.os-dedupe-architecture,.os-dedupe-section{padding:14px;border-bottom:1px solid #e7edf4}.os-dedupe-architecture h4,.os-dedupe-section h4{margin:0 0 10px;color:#294763;font-size:14px}.os-entry-flow{display:grid;grid-template-columns:minmax(180px,.75fr) 26px minmax(220px,1fr) 26px minmax(210px,.9fr);gap:8px;align-items:center;margin-top:9px}.os-entry-flow>i{text-align:center;color:#8ba2ba;font-style:normal;font-size:19px}.os-entry-card,.os-engine-card{min-height:68px;border:1px solid #dce6f1;border-radius:7px;background:#fff;padding:11px 12px}.os-engine-card{border-color:#9dc8f7;background:#f1f7ff}.os-entry-card.result{border-color:#b8d8c5;background:#f4fbf6}.os-entry-card b,.os-engine-card b{display:block;color:#294763;font-size:13px}.os-entry-card span,.os-engine-card span{display:block;margin-top:5px;color:#718197;font-size:12px;line-height:1.45}.os-level-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}.os-level-card{border:1px solid #dce6f1;border-radius:7px;padding:12px;background:#fff;min-height:142px}.os-level-card>span{display:inline-flex;border-radius:11px;padding:2px 8px;font-size:12px;font-weight:700}.os-level-card>b{display:block;margin:10px 0 7px;color:#334b63;font-size:13px;line-height:1.55}.os-level-card p{margin:0;color:#6d7f94;font-size:12px;line-height:1.55}.os-level-card.danger{border-top:3px solid #e35d5d}.os-level-card.danger>span{background:#fff0f0;color:#c63d3d}.os-level-card.warn{border-top:3px solid #e8a23a}.os-level-card.warn>span{background:#fff7e8;color:#b56b00}.os-level-card.info{border-top:3px solid #4f91db}.os-level-card.info>span{background:#edf6ff;color:#1764b2}.os-section-inline{display:flex;align-items:center;justify-content:space-between;gap:12px}.os-section-inline span{color:#8291a4;font-size:12px}.os-preview-switch{display:flex;gap:8px;margin-bottom:10px}.os-preview-switch button{height:30px;border:1px solid #cad7e5;border-radius:5px;background:#fff;padding:0 12px;color:#52677d;cursor:pointer}.os-preview-switch button.active{border-color:#1677ff;background:#eff7ff;color:#1677ff;font-weight:700}.os-dedupe-demo-flow{display:grid;gap:8px;margin-bottom:10px}.os-dedupe-source-check{display:flex;align-items:center;justify-content:space-between;gap:12px;border:1px solid #dce6f1;border-radius:7px;background:#fbfdff;padding:10px 12px}.os-dedupe-source-check.muted{background:#f8fafc}.os-dedupe-source-check>div{display:grid;grid-template-columns:24px minmax(0,1fr);gap:2px 8px;align-items:center}.os-flow-index{grid-row:1/3;display:grid;place-items:center;width:22px;height:22px;border-radius:50%;background:#1677ff;color:#fff;font-size:11px}.os-dedupe-source-check b{color:#334b63;font-size:13px}.os-dedupe-source-check small{color:#718197;font-size:12px}.os-result-pass,.os-result-skip,.os-result-engine{white-space:nowrap;border-radius:12px;padding:3px 9px;font-size:12px;font-weight:700}.os-result-pass{background:#e9f8ef;color:#187c46}.os-result-skip{background:#eef1f5;color:#65758a}.os-result-engine{background:#edf5ff;color:#1764b2}.os-dedupe-preview-grid{display:grid;grid-template-columns:minmax(260px,.72fr) minmax(480px,1.28fr);gap:10px}.os-preview-input,.os-candidate-card{border:1px solid #dce6f1;border-radius:7px;background:#fff;overflow:hidden}.os-preview-input h4{margin:0;padding:11px 12px;border-bottom:1px solid #e7edf4;background:#f8fafc}.os-preview-input>div{display:grid;grid-template-columns:84px 1fr;gap:8px;padding:9px 12px;border-bottom:1px solid #edf1f6;font-size:12px}.os-preview-input>div:last-child{border-bottom:0}.os-preview-input span{color:#7b8a9d}.os-preview-input b{color:#354b63}.os-candidate-card header{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;padding:11px 12px;border-bottom:1px solid #e7edf4}.os-candidate-card header b{display:block;color:#1d3f66}.os-candidate-card header small{display:block;margin-top:4px;color:#7b8a9d}.os-candidate-card table{width:100%;border-collapse:collapse;font-size:12px}.os-candidate-card th,.os-candidate-card td{padding:8px 10px;border-bottom:1px solid #edf1f6;text-align:left}.os-candidate-card th{width:92px;background:#fafbfd;color:#718197;font-weight:500}.os-candidate-card td.hit{width:54px;color:#c66a00;font-weight:700}.os-candidate-card footer{padding:10px 12px;background:#fbfcfe}.os-candidate-card footer>span{display:block;margin-bottom:9px;color:#6d7f94;font-size:12px;line-height:1.5}.os-candidate-card footer>div{display:flex;gap:7px;flex-wrap:wrap}.os-risk-tag{display:inline-flex;align-items:center;min-height:22px;border-radius:11px;padding:0 8px;font-size:12px;font-weight:700;white-space:nowrap}.os-risk-tag.duplicate{background:#fff0f0;color:#c63d3d}.os-risk-tag.high{background:#fff4df;color:#b56b00}.os-risk-tag.similar{background:#edf6ff;color:#1764b2}.os-check-table td.strong{font-weight:700;color:#294763}.os-inline-number{width:58px;height:28px;border:1px solid #cad7e5;border-radius:4px;padding:0 6px}.os-check-table select{max-width:420px;height:30px;border:1px solid #cad7e5;border-radius:4px;background:#fff;color:#40546b}
@media(max-width:1180px){.os-level-grid,.os-entry-flow,.os-dedupe-preview-grid{grid-template-columns:1fr}.os-entry-flow>i{transform:rotate(90deg)}}
</style>
<script id="home-dashboard-controller">
(function(){
'use strict';
const profiles={
mine:{
label:'我的数据',updated:'2026-08-02 10:30',
kpis:{lead:{num:8,note:'今日新增 2 条 · 待领取 6 条'},opportunity:{num:12,note:'预计金额 <b>2,680 万元</b>'},project:{num:6,note:'逾期预警 <span class="kpi-delta down">1 个</span>'},customer:{num:58,note:'本月新增 <span class="kpi-delta up">+4</span>'}},
funnel:[['线索',8],['商机',12],['项目·方案',3],['项目·报价',2],['项目·商务',1]],
reminders:[
{kind:'high',count:2,text:'个客户超过7天未跟进',page:'customerPool',label:'客户 / 客户总览'},
{kind:'mid',count:1,text:'个商机连续5天未推进',page:'myOpportunity',label:'商机 / 商机管理'},
{kind:'mid',count:1,text:'个项目存在逾期节点',page:'project',label:'项目管理 / 项目管理'},
{kind:'low',count:1,text:'个商机待补充方案卡',page:'myOpportunity',label:'商机 / 商机管理'}
],
customers:[
{id:'mine-1',crmId:1,name:'广州市XX职业技术学院',level:'5星客户',owner:'张三',follow:'7天未跟进',contact:'陈大壮',phone:'13897283272',wechat:'微信同手机号'},
{id:'mine-2',crmId:2,name:'广东XX智慧建筑工程有限公司',level:'4星客户',owner:'小李',follow:'报价后3天',contact:'胡晓',phone:'13922186621',wechat:'huxiao_crm'},
{id:'mine-3',crmId:7,name:'深圳市XX监狱管理局',level:'3星客户',owner:'张三',follow:'定期维护客情',contact:'林锐',phone:'13688990126',wechat:'linrui_sz'}
],
projects:[
{name:'XX学校广播项目',stage:'方案阶段',owner:'张三',progress:30,next:'报价阶段'},
{name:'XX地铁监控项目',stage:'商务阶段',owner:'张三',progress:60,next:'订单阶段'},
{name:'XX政府多功能厅',stage:'招投标阶段',owner:'小李',progress:45,next:'商务阶段'}
]
},
south:{
label:'华南战队',updated:'2026-08-02 10:30',
kpis:{lead:{num:86,note:'今日新增 17 条 · 待领取 69 条'},opportunity:{num:44,note:'预计金额 <b>26,800 万元</b>'},project:{num:19,note:'逾期预警 <span class="kpi-delta down">5 个</span>'},customer:{num:1260,note:'本月新增 <span class="kpi-delta up">+38</span>'}},
funnel:[['线索',86],['商机',44],['项目·方案',8],['项目·报价',6],['项目·商务',5]],
reminders:[
{kind:'high',count:18,text:'个客户超过7天未跟进',page:'customerPool',label:'客户 / 客户总览'},
{kind:'high',count:9,text:'个商机连续5天未推进',page:'myOpportunity',label:'商机 / 商机管理'},
{kind:'mid',count:5,text:'个项目存在逾期节点',page:'project',label:'项目管理 / 项目管理'},
{kind:'low',count:7,text:'个商机待补充方案卡',page:'myOpportunity',label:'商机 / 商机管理'}
],
customers:[
{id:'south-1',crmId:1,name:'广州市XX职业技术学院',level:'5星客户',owner:'广州一部 · 张三',follow:'7天未跟进',contact:'陈大壮',phone:'13897283272',wechat:'微信同手机号'},
{id:'south-2',crmId:7,name:'深圳市XX监狱管理局',level:'5星客户',owner:'深圳一部 · 林锐',follow:'2天前已拜访',contact:'周科长',phone:'13510886632',wechat:'zhouke_sz'},
{id:'south-3',crmId:2,name:'广东XX智慧建筑工程有限公司',level:'4星客户',owner:'广州一部 · 小李',follow:'报价后3天',contact:'胡晓',phone:'13922186621',wechat:'huxiao_crm'}
],
projects:[
{name:'华南智慧校园项目',stage:'方案阶段',owner:'张三',progress:35,next:'报价阶段'},
{name:'深圳轨交改造项目',stage:'商务阶段',owner:'林锐',progress:68,next:'订单阶段'},
{name:'珠海会展中心项目',stage:'招投标阶段',owner:'陈亚',progress:48,next:'商务阶段'},
{name:'佛山政务大厅项目',stage:'报价阶段',owner:'小李',progress:24,next:'招投标阶段'}
]
},
gz1:{
label:'华南战队 / 广州一部',updated:'2026-08-02 10:30',
kpis:{lead:{num:32,note:'今日新增 6 条 · 待领取 26 条'},opportunity:{num:18,note:'预计金额 <b>10,000 万元</b>'},project:{num:8,note:'逾期预警 <span class="kpi-delta down">2 个</span>'},customer:{num:286,note:'本月新增 <span class="kpi-delta up">+12</span>'}},
funnel:[['线索',32],['商机',18],['项目·方案',4],['项目·报价',2],['项目·商务',2]],
reminders:[
{kind:'high',count:7,text:'个客户超过7天未跟进',page:'customerPool',label:'客户 / 客户总览'},
{kind:'mid',count:4,text:'个商机连续5天未推进',page:'myOpportunity',label:'商机 / 商机管理'},
{kind:'mid',count:2,text:'个项目存在逾期节点',page:'project',label:'项目管理 / 项目管理'},
{kind:'low',count:3,text:'个商机待补充方案卡',page:'myOpportunity',label:'商机 / 商机管理'}
],
customers:[
{id:'gz1-1',crmId:1,name:'广州市XX职业技术学院',level:'5星客户',owner:'张三',follow:'7天未跟进',contact:'陈大壮',phone:'13897283272',wechat:'微信同手机号'},
{id:'gz1-2',crmId:2,name:'广东XX智慧建筑工程有限公司',level:'4星客户',owner:'小李',follow:'报价后3天',contact:'胡晓',phone:'13922186621',wechat:'huxiao_crm'},
{id:'gz1-3',crmId:5,name:'广东省XX监狱管理局',level:'4星客户',owner:'陈六',follow:'昨日电话沟通',contact:'林锐',phone:'13688990126',wechat:'linrui_gz'}
],
projects:[
{name:'XX学校广播项目',stage:'方案阶段',owner:'张三',progress:30,next:'报价阶段'},
{name:'XX地铁监控项目',stage:'商务阶段',owner:'张三',progress:60,next:'订单阶段'},
{name:'XX政府多功能厅',stage:'招投标阶段',owner:'小李',progress:45,next:'商务阶段'},
{name:'XXXX文旅项目',stage:'报价阶段',owner:'陈六',progress:20,next:'招投标阶段'}
]
},
sz1:{
label:'华南战队 / 深圳一部',updated:'2026-08-02 10:30',
kpis:{lead:{num:21,note:'今日新增 4 条 · 待领取 17 条'},opportunity:{num:11,note:'预计金额 <b>6,480 万元</b>'},project:{num:6,note:'逾期预警 <span class="kpi-delta down">1 个</span>'},customer:{num:198,note:'本月新增 <span class="kpi-delta up">+8</span>'}},
funnel:[['线索',21],['商机',11],['项目·方案',3],['项目·报价',2],['项目·商务',1]],
reminders:[
{kind:'high',count:4,text:'个客户超过7天未跟进',page:'customerPool',label:'客户 / 客户总览'},
{kind:'mid',count:2,text:'个商机连续5天未推进',page:'myOpportunity',label:'商机 / 商机管理'},
{kind:'mid',count:1,text:'个项目存在逾期节点',page:'project',label:'项目管理 / 项目管理'},
{kind:'low',count:2,text:'个商机待补充方案卡',page:'myOpportunity',label:'商机 / 商机管理'}
],
customers:[
{id:'sz1-1',crmId:7,name:'深圳市XX监狱管理局',level:'5星客户',owner:'林锐',follow:'2天前已拜访',contact:'周科长',phone:'13510886632',wechat:'zhouke_sz'},
{id:'sz1-2',crmId:2,name:'深圳XX智慧园区有限公司',level:'4星客户',owner:'周敏',follow:'方案确认中',contact:'许工',phone:'13823550118',wechat:'xugong_sz'},
{id:'sz1-3',crmId:4,name:'南山区XX商业运营公司',level:'4星客户',owner:'林锐',follow:'5天未联系',contact:'曾总',phone:'13602661290',wechat:'zengnan_shan'}
],
projects:[
{name:'深圳轨交改造项目',stage:'商务阶段',owner:'林锐',progress:68,next:'订单阶段'},
{name:'南山智慧园区项目',stage:'方案阶段',owner:'周敏',progress:38,next:'报价阶段'},
{name:'龙岗文体中心项目',stage:'招投标阶段',owner:'林锐',progress:52,next:'商务阶段'}
]
}
};
const followLogs={};
let scopeKey='gz1';
function esc(value){return String(value??'').replace(/[&<>"']/g,function(char){return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[char]})}
function readScope(){try{return localStorage.getItem('crm.home.scope')||'gz1'}catch(error){return 'gz1'}}
function writeScope(value){try{localStorage.setItem('crm.home.scope',value)}catch(error){}}
function currentProfile(){return profiles[scopeKey]||profiles.gz1}
function getCustomer(id){return currentProfile().customers.find(function(item){return item.id===id})}
function renderKpis(profile){
document.querySelectorAll('#home [data-home-kpi]').forEach(function(card){
const data=profile.kpis[card.dataset.homeKpi];
if(!data)return;
const num=card.querySelector('.num');
const note=card.querySelector('small');
if(num)num.innerHTML=esc(data.num)+(card.dataset.homeKpi==='lead'?'<span class="metric-unit">条</span>':'');
if(note)note.innerHTML=data.note;
});
}
function renderReminders(profile){
const list=document.getElementById('homeReminderList');
if(!list)return;
list.innerHTML=profile.reminders.map(function(item){
return '<li><button class="home-reminder-link" type="button" data-home-action="scope-jump" data-page="'+esc(item.page)+'" data-label="'+esc(item.label)+'"><span class="risk-dot '+esc(item.kind)+'"></span><span><b class="risk-count">'+esc(item.count)+'</b> '+esc(item.text)+'</span></button></li>';
}).join('');
}
function renderFunnel(profile){
const list=document.getElementById('homeFunnelList');
const total=profile.funnel.reduce(function(sum,item){return sum+item[1]},0);
const totalNode=document.getElementById('homeFunnelTotal');
if(totalNode)totalNode.textContent='未完结 '+total+' 项';
if(!list)return;
list.innerHTML=profile.funnel.map(function(item){
const width=total?Math.max(2,Math.round(item[1]/total*100)):0;
const ratio=total?Math.round(item[1]/total*100):0;
return '<div class="funnel-step" title="'+esc(item[0])+':'+esc(item[1])+' 项,占当前范围未完结业务 '+ratio+'%"><span>'+esc(item[0])+'</span><div class="funnel-bar-wrap"><div class="funnel-bar-fill" style="width:'+width+'%"></div></div><span class="funnel-step-num">'+esc(item[1])+'</span></div>';
}).join('');
}
function buildingIcon(){return '<span class="ui-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M6 22V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v18Z"/><path d="M6 12H2v10h20V12h-4M10 6h4M10 10h4M10 14h4M10 18h4"/></svg></span>'}
function renderCustomers(profile){
const list=document.getElementById('homeCustomerList');
if(!list)return;
list.innerHTML=profile.customers.map(function(item){
return '<div class="home-customer-row">'+buildingIcon()+'<div class="home-customer-main"><div class="home-customer-name"><b>'+esc(item.name)+'</b></div><small>负责人:'+esc(item.owner)+' · 联系人:'+esc(item.contact)+'</small></div><div class="home-customer-actions"><button class="home-row-action" type="button" data-home-action="contact" data-customer-id="'+esc(item.id)+'">联系</button><button class="home-row-action" type="button" data-home-action="follow" data-customer-id="'+esc(item.id)+'">写跟进</button><button class="home-row-action" type="button" data-home-action="records" data-customer-id="'+esc(item.id)+'">查看记录</button></div><em class="home-customer-level">'+esc(item.level)+'</em></div>';
}).join('');
}
function renderProjects(profile){
const rows=document.getElementById('homeProjectRows');
if(!rows)return;
rows.innerHTML=profile.projects.map(function(item){
return '<tr><td title="'+esc(item.name)+'">'+esc(item.name)+'</td><td><span class="tag">'+esc(item.stage)+'</span></td><td>'+esc(item.owner)+'</td><td><div class="project-progress"><span><i style="width:'+esc(item.progress)+'%"></i></span><b>'+esc(item.progress)+'%</b></div></td><td>'+esc(item.next)+'</td></tr>';
}).join('');
}
function renderScope(profile){
const label=document.getElementById('homeScopeLabel');
const updated=document.getElementById('homeDataUpdated');
if(label)label.textContent=profile.label;
if(updated)updated.textContent=profile.updated;
document.querySelectorAll('#homeScopeOptions [data-home-scope]').forEach(function(button){button.classList.toggle('active',button.dataset.homeScope===scopeKey)});
}
function renderHomeDashboard(options){
const home=document.getElementById('home');
if(!home)return;
const profile=currentProfile();
const wasEmpty=home.classList.contains('home-empty-mode');
const heroText=home.querySelector('.hero p');
if(heroText)heroText.innerHTML='今天是 2026年6月10日 星期三。重点关注今日工作、项目推进和客户经营。';
renderScope(profile);
renderKpis(profile);
renderReminders(profile);
renderFunnel(profile);
renderCustomers(profile);
renderProjects(profile);
if(wasEmpty&&typeof window.toggleHomeEmptyState==='function')window.toggleHomeEmptyState(true,{silent:true});
if(!options?.silent&&typeof window.homeShowNotice==='function')window.homeShowNotice('经营数据范围已切换为:'+profile.label);
}
function setScope(key,announce){
if(!profiles[key])return;
scopeKey=key;
writeScope(key);
closeScopePopover();
renderHomeDashboard({silent:announce===false});
}
function closeScopePopover(){
const trigger=document.getElementById('homeScopeTrigger');
const popover=document.getElementById('homeScopePopover');
trigger?.setAttribute('aria-expanded','false');
popover?.classList.remove('show');
}
function filterScopeOptions(query){
const normalized=String(query||'').trim().toLowerCase();
document.querySelectorAll('#homeScopeOptions .home-scope-group').forEach(function(group){
const groupMatch=group.dataset.homeScopeGroup.toLowerCase().includes(normalized);
let visible=0;
group.querySelectorAll('button').forEach(function(button){
const match=!normalized||groupMatch||button.textContent.toLowerCase().includes(normalized);
button.hidden=!match;
if(match)visible+=1;
});
group.hidden=visible===0;
});
}
function bindScopePicker(){
const picker=document.getElementById('homeScopePicker');
const trigger=document.getElementById('homeScopeTrigger');
const popover=document.getElementById('homeScopePopover');
const search=document.getElementById('homeScopeSearch');
const options=document.getElementById('homeScopeOptions');
if(!picker||!trigger||!popover||!search||!options)return;
trigger.addEventListener('click',function(event){event.stopPropagation();const show=!popover.classList.contains('show');popover.classList.toggle('show',show);trigger.setAttribute('aria-expanded',String(show));if(show){search.value='';filterScopeOptions('');setTimeout(function(){search.focus()},0)}});
popover.addEventListener('click',function(event){event.stopPropagation()});
search.addEventListener('input',function(){filterScopeOptions(search.value)});
options.addEventListener('click',function(event){const button=event.target.closest('[data-home-scope]');if(button)setScope(button.dataset.homeScope,true)});
document.addEventListener('click',closeScopePopover);
document.addEventListener('keydown',function(event){if(event.key==='Escape')closeScopePopover()});
}
function openDashboardModal(title,body,footer,extraClass){
const mask=document.getElementById('modal');
const panel=mask?.querySelector('.modal');
if(!mask||!panel)return;
panel.classList.remove('opp-modal-wide','opp-modal-small','opp-modal-medium','crm-member-modal','crm-home-customer-dialog','home-contact-dialog');
panel.classList.add('home-dashboard-modal');
if(extraClass)panel.classList.add(extraClass);
document.getElementById('modalTitle').textContent=title;
mask.querySelector('.m-body').innerHTML=body;
mask.querySelector('.m-foot').innerHTML=footer||'<button class="btn" onclick="homeCloseDashboardModal()">关闭</button>';
mask.classList.add('show');
}
function closeDashboardModal(){
document.querySelector('#modal .modal')?.classList.remove('home-dashboard-modal','home-contact-dialog');
closeModal();
}
function getContactRows(customer){
const rows=typeof customerV2?.getHomeContactRows==='function'?customerV2.getHomeContactRows(customer.name):[];
return rows.length?rows:[{name:customer.contact,job:'--',department:'--',phone:customer.phone,relation:'--'}];
}
function openContact(customer){
const rows=getContactRows(customer);
const tableRows=rows.map(function(contact){const search=[contact.name,contact.job,contact.department,contact.phone,contact.relation].join(' ').toLowerCase();return '<tr data-home-contact-row data-home-contact-search="'+esc(search)+'"><td>'+esc(contact.name||'--')+'</td><td>'+esc(contact.job||'--')+'</td><td>'+esc(contact.department||'--')+'</td><td>'+esc(contact.phone||'--')+'</td><td>'+esc(contact.relation||'--')+'</td><td><button class="home-contact-copy" type="button" data-home-copy="'+esc(contact.phone||'')+'">复制号码</button></td></tr>'}).join('');
const body='<div class="home-contact-list"><div class="home-contact-toolbar"><span><b>联系人列表</b> 共 '+rows.length+' 位联系人</span><input type="search" data-home-contact-filter placeholder="搜索姓名 / 部门 / 手机号"></div><div class="home-contact-table-wrap"><table class="home-contact-table"><thead><tr><th>联系人</th><th>职务</th><th>部门</th><th>联系电话</th><th>关系说明</th><th>操作</th></tr></thead><tbody>'+tableRows+'</tbody></table><div class="home-contact-empty" hidden>未找到匹配的联系人</div></div></div>';
openDashboardModal(customer.name+' · 联系人',body,undefined,'home-contact-dialog');
}
function openFollow(customer){
const body='<div class="home-follow-form"><label>关联客户</label><input value="'+esc(customer.name)+'" readonly><label>联系人</label><input value="'+esc(customer.contact)+'" readonly><label>跟进方式</label><select id="homeFollowType"><option>电话沟通</option><option>微信沟通</option><option>客户拜访</option><option>会议沟通</option></select><label>跟进时间</label><input id="homeFollowTime" type="datetime-local" value="2026-08-02T10:30"><label class="full-label">跟进内容</label><textarea id="homeFollowContent" placeholder="填写沟通内容、客户反馈、关键结论和下一步计划"></textarea></div>';
const footer='<button class="btn" onclick="homeCloseDashboardModal()">取消</button><button class="btn primary" onclick="homeSaveCustomerFollow(\''+esc(customer.id)+'\')">保存跟进</button>';
openDashboardModal('写跟进 · '+customer.name,body,footer);
}
function saveFollow(customerId){
const customer=getCustomer(customerId);
const content=document.getElementById('homeFollowContent')?.value.trim();
if(!customer||!content){homeShowNotice('请填写跟进内容');return}
const type=document.getElementById('homeFollowType')?.value||'电话沟通';
const time=(document.getElementById('homeFollowTime')?.value||'2026-08-02T10:30').replace('T',' ');
followLogs[customerId]=followLogs[customerId]||[];
followLogs[customerId].unshift({type:type,time:time,content:content});
customer.follow='刚刚跟进';
closeDashboardModal();
renderCustomers(currentProfile());
homeShowNotice('跟进记录已保存');
}
function viewRecords(customer){
const saved=followLogs[customer.id]||[];
const records=saved.concat([
{type:'电话沟通',time:'2026-07-29 16:00',content:'确认客户近期关注点与下一次沟通安排。'},
{type:'客户拜访',time:'2026-07-18 14:00',content:'完成需求梳理,客户反馈需补充项目方案与预算说明。'}
]);
const body='<div class="home-record-list">'+records.map(function(item){return '<div class="home-record-item"><b>【'+esc(item.type)+'】'+esc(customer.contact)+'</b><p>'+esc(item.content)+'</p><small>'+esc(item.time)+' · '+esc(customer.owner)+'</small></div>'}).join('')+'</div>';
openDashboardModal(customer.name+' · 跟进记录',body);
}
function arrangeWork(customer){
openHomeWorkplan();
setTimeout(function(){
const type=document.getElementById('action-object-type');
const object=document.getElementById('action-object');
if(!type||!object)return;
type.value='customer';
wp_loadObjectQueue();
let option=Array.from(object.options).find(function(item){return item.value===customer.name});
if(!option){option=new Option(customer.name,customer.name);object.add(option)}
object.value=customer.name;
const name=document.getElementById('action-name');
const plan=document.getElementById('action-plan-input');
const attribution=document.getElementById('action-attribution');
if(name)name.value='跟进'+customer.name;
if(plan)plan.value='联系'+customer.contact+',确认近期业务进展、客户反馈及下一步安排。';
if(attribution)attribution.value='stock';
},0);
}
function showFunnelDetails(){
const profile=currentProfile();
const total=profile.funnel.reduce(function(sum,item){return sum+item[1]},0);
const rows=profile.funnel.map(function(item){const ratio=total?Math.round(item[1]/total*100):0;return '<tr><td>'+esc(item[0])+'</td><td>'+esc(item[1])+' 项</td><td>'+ratio+'%</td></tr>'}).join('');
const body='<p style="margin:0 0 12px;color:#64748b;line-height:1.65">当前范围内未完结业务合计 <b style="color:#24364f">'+total+' 项</b>;各条长度按“阶段数量 ÷ 未完结业务总量”计算。</p><table class="table"><thead><tr><th>业务阶段</th><th>数量</th><th>占未完结总量</th></tr></thead><tbody>'+rows+'</tbody></table>';
openDashboardModal(profile.label+' · 成交漏斗明细',body);
}
function openScopedPage(page,label){
try{sessionStorage.setItem('crm.home.scope.context',JSON.stringify({key:scopeKey,label:currentProfile().label,updated:currentProfile().updated}))}catch(error){}
setActivePage(page,label);
}
function bindHomeActions(){
const home=document.getElementById('home');
const modal=document.getElementById('modal');
if(!home)return;
home.addEventListener('click',function(event){
const action=event.target.closest('[data-home-action]');
if(!action)return;
event.stopPropagation();
const type=action.dataset.homeAction;
if(type==='scope-jump'){openScopedPage(action.dataset.page,action.dataset.label);return}
if(type==='funnel-details'){showFunnelDetails();return}
const customer=getCustomer(action.dataset.customerId);
if(!customer)return;
if(type==='contact')openContact(customer);
else if(type==='follow')openFollow(customer);
else if(type==='records')viewRecords(customer);
});
modal?.addEventListener('input',function(event){
const input=event.target.closest('[data-home-contact-filter]');
if(!input)return;
const keyword=input.value.trim().toLowerCase();
const rows=[...modal.querySelectorAll('[data-home-contact-row]')];
let matched=0;
rows.forEach(function(row){const show=!keyword||(row.dataset.homeContactSearch||'').includes(keyword);row.hidden=!show;if(show)matched+=1});
const empty=modal.querySelector('.home-contact-empty');
if(empty)empty.hidden=matched>0;
});
modal?.addEventListener('click',function(event){
const copy=event.target.closest('[data-home-copy]');
if(copy){const value=copy.dataset.homeCopy;try{navigator.clipboard?.writeText(value)}catch(error){}homeShowNotice('联系电话已复制:'+value)}
if(event.target===modal)setTimeout(function(){document.querySelector('#modal .modal')?.classList.remove('home-dashboard-modal')},0);
});
}
function init(){
scopeKey=profiles[readScope()]?readScope():'gz1';
bindScopePicker();
bindHomeActions();
renderHomeDashboard({silent:true});
}
window.homeOpenScopedPage=openScopedPage;
window.homeCloseDashboardModal=closeDashboardModal;
window.homeSaveCustomerFollow=saveFollow;
window.renderHomeDashboard=renderHomeDashboard;
window.homeGetCurrentProfile=currentProfile;
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',init);
else init();
})();
</script>
<script id="home-empty-state-script">
(function(){
'use strict';
const art={
work:'<ellipse cx="66" cy="86" rx="43" ry="5" fill="#F1F6FD"/><rect x="39" y="23" width="48" height="53" rx="7" fill="#F9FBFF" stroke="#C9D9F1" stroke-width="2"/><rect x="53" y="15" width="20" height="12" rx="4" fill="#DCE9FA" stroke="#BFD2EC" stroke-width="2"/><path d="M49 39h27M49 48h21M49 57h13" stroke="#ABC2E2" stroke-width="3" stroke-linecap="round"/><circle cx="83" cy="63" r="12" fill="#F4F8FE" stroke="#AFC8E9" stroke-width="2"/><path d="m92 72 8 8" stroke="#7EA6D9" stroke-width="3" stroke-linecap="round"/><rect x="101" y="28" width="24" height="17" rx="6" fill="#F8FBFF" stroke="#D6E4F6"/><circle cx="108" cy="36.5" r="1.3" fill="#B7CAE4"/><circle cx="113" cy="36.5" r="1.3" fill="#B7CAE4"/><circle cx="118" cy="36.5" r="1.3" fill="#B7CAE4"/><path d="M23 39v7M19.5 42.5h7" stroke="#D2E3F8" stroke-width="2" stroke-linecap="round"/>',
funnel:'<ellipse cx="66" cy="86" rx="43" ry="5" fill="#F1F6FD"/><path d="M32 27c0-5 68-5 68 0 0 5-9 11-25 19v21l-19 8V46C40 38 32 32 32 27Z" fill="#DCE9FA" stroke="#B9CEE9" stroke-width="2"/><ellipse cx="66" cy="27" rx="34" ry="8" fill="#F9FBFF" stroke="#B9CEE9" stroke-width="2"/><path d="M41 28c8 4 42 4 50 0" stroke="#D2E0F2" stroke-width="2"/><rect x="102" y="32" width="24" height="17" rx="6" fill="#F8FBFF" stroke="#D4E2F4"/><circle cx="109" cy="40.5" r="1.3" fill="#B8CAE3"/><circle cx="114" cy="40.5" r="1.3" fill="#B8CAE3"/><circle cx="119" cy="40.5" r="1.3" fill="#B8CAE3"/><path d="M22 48v7M18.5 51.5h7" stroke="#D2E3F8" stroke-width="2" stroke-linecap="round"/>',
ai:'<ellipse cx="66" cy="86" rx="43" ry="5" fill="#F1F6FD"/><path d="M66 17v8" stroke="#AFC5E4" stroke-width="2" stroke-linecap="round"/><circle cx="66" cy="14" r="3" fill="#D9E7F8"/><rect x="38" y="28" width="56" height="42" rx="18" fill="#F7FAFF" stroke="#BFD4EE" stroke-width="2"/><rect x="47" y="38" width="38" height="22" rx="11" fill="#3F7BDA"/><circle cx="58" cy="49" r="2.5" fill="#fff"/><circle cx="74" cy="49" r="2.5" fill="#fff"/><path d="M60 55c4 2 8 2 12 0" stroke="#fff" stroke-width="2" stroke-linecap="round"/><path d="M38 44h-6M100 44h-6M51 71v5M81 71v5" stroke="#BFD4EE" stroke-width="3" stroke-linecap="round"/><rect x="99" y="22" width="26" height="18" rx="6" fill="#F8FBFF" stroke="#D4E2F4"/><circle cx="107" cy="31" r="1.3" fill="#B8CAE3"/><circle cx="112" cy="31" r="1.3" fill="#B8CAE3"/><circle cx="117" cy="31" r="1.3" fill="#B8CAE3"/><path d="M23 30v7M19.5 33.5h7" stroke="#A9CAFA" stroke-width="2" stroke-linecap="round"/>',
customer:'<ellipse cx="66" cy="87" rx="44" ry="5" fill="#F1F6FD"/><path d="M34 76V40l19-10v46M53 76V24l26-10v62M79 76V35l20 9v32" fill="#E1ECFA" stroke="#BDD0E9" stroke-width="2"/><path d="M42 47h4M42 56h4M42 65h4M61 31h5M61 41h5M61 51h5M61 61h5M87 48h4M87 57h4M87 66h4" stroke="#AFC4DF" stroke-width="2.5" stroke-linecap="round"/><rect x="100" y="25" width="25" height="18" rx="6" fill="#F8FBFF" stroke="#D4E2F4"/><circle cx="108" cy="34" r="1.3" fill="#B8CAE3"/><circle cx="113" cy="34" r="1.3" fill="#B8CAE3"/><circle cx="118" cy="34" r="1.3" fill="#B8CAE3"/>',
project:'<ellipse cx="66" cy="87" rx="44" ry="5" fill="#F1F6FD"/><path d="M40 18h39l14 14v44H40V18Z" fill="#F9FBFF" stroke="#BCD0EA" stroke-width="2"/><path d="M79 18v15h14" fill="#E1ECFA" stroke="#BCD0EA" stroke-width="2"/><path d="M50 37h21M50 46h16" stroke="#B4C8E2" stroke-width="2.5" stroke-linecap="round"/><circle cx="67" cy="61" r="12" fill="#E1ECFA" stroke="#AFC7E6" stroke-width="2"/><path d="M67 49v12h12" stroke="#5D8FD6" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/><rect x="100" y="35" width="25" height="18" rx="6" fill="#F8FBFF" stroke="#D4E2F4"/><circle cx="108" cy="44" r="1.3" fill="#B8CAE3"/><circle cx="113" cy="44" r="1.3" fill="#B8CAE3"/><circle cx="118" cy="44" r="1.3" fill="#B8CAE3"/>',
reminder:'<ellipse cx="66" cy="87" rx="42" ry="5" fill="#F1F6FD"/><path d="M48 64h36c-5-5-7-13-7-22 0-8-5-14-11-14s-11 6-11 14c0 9-2 17-7 22Z" fill="#E2EDFA" stroke="#B9CEE9" stroke-width="2"/><path d="M61 68c1 5 9 5 10 0" stroke="#AFC5E1" stroke-width="2.5" stroke-linecap="round"/><circle cx="66" cy="23" r="4" fill="#D4E4F7"/><circle cx="82" cy="64" r="10" fill="#F4F8FE" stroke="#AFC7E6" stroke-width="2"/><path d="m77 64 4 4 7-8" stroke="#4E86D6" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>'
};
function illustration(type,size){
return '<svg class="home-empty-illustration'+(size?' is-'+size:'')+'" viewBox="0 0 132 96" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">'+(art[type]||art.work)+'</svg>';
}
function panelMarkup(config){
const copy='<div class="home-empty-copy"><div class="home-empty-title">'+config.title+'</div><p class="home-empty-subtitle">'+config.subtitle+'</p>'+(config.extra||'')+'</div>';
const size=config.inline?'compact':config.small?'small':'';
return '<div class="home-empty-panel'+(config.inline?' is-inline':'')+'">'+illustration(config.type,size)+copy+'</div>';
}
function initHomeEmptyState(){
const home=document.getElementById('home');
if(!home||home.dataset.emptyStateReady==='1')return;
home.dataset.emptyStateReady='1';
const toggle=document.createElement('button');
toggle.type='button';
toggle.className='home-empty-toggle';
toggle.textContent='切换空值状态';
toggle.title='仅切换首页空值演示,不会覆盖原有演示数据';
toggle.setAttribute('aria-pressed','false');
home.appendChild(toggle);
const panels={
work:{type:'work',title:'暂无今日工作',subtitle:'当前没有待办或计划的今日工作',extra:'<button class="home-empty-action" type="button" onclick="openHomeWorkplan()">+ 新建工作计划</button>'},
funnel:{type:'funnel',title:'暂无业务推进数据',subtitle:'当前数据范围暂无未完结业务'},
ai:{type:'ai',title:'暂无重点提醒',subtitle:'AI 正在学习中,后续将为你提供更精准的建议',extra:'<div class="home-empty-chips"><button class="home-empty-chip" disabled>跟进建议</button><button class="home-empty-chip" disabled>客户洞察</button><button class="home-empty-chip" disabled>商机分析</button></div>'},
customer:{type:'customer',title:'暂无重点客户',subtitle:'当前数据范围暂无重点客户',small:true},
project:{type:'project',title:'暂无项目进度',subtitle:'当前数据范围暂无进行中项目',small:true},
reminder:{type:'reminder',title:'暂无需要提醒的事项',subtitle:'当前数据范围暂无经营提醒',inline:true}
};
Object.entries(panels).forEach(function(entry){
const card=home.querySelector('[data-home-card="'+entry[0]+'"]');
const body=card?.querySelector('.card-body');
if(!card||!body)return;
card.classList.add('home-empty-host');
body.insertAdjacentHTML('beforeend',panelMarkup(entry[1]));
});
const heroText=home.querySelector('.hero p');
const emptyKpis={
lead:{num:'0<span class="metric-unit">条</span>',small:'今日新增&nbsp;&nbsp;<b>0 条</b>'},
opportunity:{num:'0',small:'预计金额&nbsp;&nbsp;<b>0 万元</b>'},
project:{num:'0',small:'逾期预警&nbsp;&nbsp;<span class="kpi-delta neutral">0 个</span>'},
customer:{num:'0',small:'本月新增&nbsp;&nbsp;<span class="kpi-delta neutral">+0</span>'}
};
let empty=false;
function setEmptyState(on,options){
empty=Boolean(on);
home.classList.toggle('home-empty-mode',empty);
toggle.classList.toggle('is-active',empty);
toggle.textContent=empty?'恢复演示数据':'切换空值状态';
toggle.setAttribute('aria-pressed',String(empty));
if(empty){
if(heroText)heroText.innerHTML='今天是 2026年6月10日 星期三。当前数据范围暂时没有新的客户经营动态。';
home.querySelectorAll('[data-home-kpi]').forEach(function(kpi){
const emptyValue=emptyKpis[kpi.dataset.homeKpi];
if(!emptyValue)return;
const num=kpi.querySelector('.num'),small=kpi.querySelector('small'),bar=kpi.querySelector('.kpi-trend-bar i');
if(num)num.innerHTML=emptyValue.num;
if(small)small.innerHTML=emptyValue.small;
if(bar)bar.style.width='0%';
});
const funnelTotal=home.querySelector('#homeFunnelTotal');
if(funnelTotal)funnelTotal.textContent='未完结 0 项';
}else if(typeof window.renderHomeDashboard==='function'){
window.renderHomeDashboard({silent:true});
}
if(!options?.silent&&typeof window.homeShowNotice==='function')window.homeShowNotice(empty?'已切换至首页空值状态':'已恢复首页演示数据');
}
toggle.addEventListener('click',function(){setEmptyState(!empty)});
window.toggleHomeEmptyState=setEmptyState;
try{if(new URLSearchParams(location.search).get('emptyState')==='1')setEmptyState(true,{silent:true})}catch(error){}
}
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',initHomeEmptyState);
else initHomeEmptyState();
})();
</script>
</body>
</html>