SOURCE

console 命令行工具 X clear

                    
>
console
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>门店薪酬与利润核算系统 · 完整版</title>
<style>
  *{box-sizing:border-box;}
  body{font-family:"Microsoft YaHei",sans-serif;margin:0;padding:10px;background:#f0f2f5;color:#333;font-size:13px;}
  .wrap{max-width:1800px;margin:auto;background:#fff;padding:14px;border-radius:10px;box-shadow:0 2px 12px rgba(0,0,0,0.08);}
  h1{font-size:19px;text-align:center;background:linear-gradient(135deg,#1e3a5f,#0d47a1);color:#fff;padding:14px;margin:-14px -14px 16px -14px;border-radius:10px 10px 0 0;letter-spacing:1px;}
  h2{font-size:15px;margin:22px 0 10px 0;padding-left:10px;border-left:5px solid #ff9800;color:#1e3a5f;}
  h3{font-size:14px;color:#1e3a5f;margin:14px 0 8px;}
  .scroll{overflow-x:auto;width:100%;}
  .hero{background:linear-gradient(135deg,#1e3a5f 0%,#1565c0 100%);border-radius:12px;padding:20px;color:#fff;margin-bottom:18px;box-shadow:0 6px 18px rgba(0,0,0,0.18);}
  .hero-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px;margin-bottom:14px;}
  .hero-item{text-align:center;padding:10px;background:rgba(255,255,255,0.08);border-radius:8px;}
  .hero-item .label{font-size:12px;opacity:0.85;margin-bottom:4px;}
  .hero-item .value{font-size:24px;font-weight:bold;color:#ffeb3b;text-shadow:1px 1px 3px rgba(0,0,0,0.3);}
  .hero-item .sub{font-size:11px;opacity:0.8;margin-top:3px;}
  .hero-tier{background:rgba(255,235,59,0.15);border:2px dashed #ffeb3b;border-radius:8px;padding:10px;text-align:center;font-size:13px;}
  .hero-tier b{color:#ffeb3b;font-size:16px;}
  table{width:100%;border-collapse:collapse;font-size:12px;background:#fff;}
  th,td{border:1px solid #d6dde5;padding:6px 4px;text-align:center;vertical-align:middle;}
  th{background:#e8eef5;color:#1e3a5f;font-weight:bold;white-space:nowrap;}
  tbody tr:nth-child(even){background:#f8fafc;}
  tbody tr:hover{background:#fff8e1;}
  input[type=number],input[type=text]{width:100%;padding:5px 3px;border:1px solid #90caf9;background:#e3f2fd;border-radius:4px;font-size:12px;text-align:center;color:#0d47a1;font-weight:bold;}
  input:focus{outline:none;border-color:#1e3a5f;background:#fff;box-shadow:0 0 5px rgba(30,58,95,0.2);}
  input:disabled{background:#f0f0f0!important;color:#b0b0b0!important;border-color:#ddd!important;cursor:not-allowed;}
  select{padding:5px 3px;border:1px solid #90caf9;background:#e3f2fd;border-radius:4px;font-size:12px;color:#0d47a1;font-weight:bold;text-align:center;}
  .hl-yellow{background:#fff9c4!important;color:#bf360c!important;font-weight:bold;}
  .hl-green{background:#e8f5e9!important;color:#2e7d32!important;font-weight:bold;}
  .hl-blue{background:#e3f2fd!important;color:#1565c0!important;font-weight:bold;}
  .hl-orange{background:#ffe0b2!important;color:#e65100!important;font-weight:bold;}
  .hl-red{background:#ffebee!important;color:#c62828!important;font-weight:bold;}
  .param-card{background:#f8fafc;border:1px solid #cfd8dc;border-radius:8px;padding:12px;margin-bottom:12px;}
  .param-card table td{border:none;padding:5px 4px;text-align:left;}
  .param-card input{width:110px;}
  .param-card .p-label{font-size:12px;color:#455a64;}
  .toolbar{display:flex;flex-wrap:wrap;gap:10px;align-items:center;background:#fff3e0;border:2px solid #ff9800;border-radius:8px;padding:10px 12px;margin-bottom:12px;font-size:13px;}
  .toolbar .item{display:flex;align-items:center;gap:6px;color:#bf360c;font-weight:bold;}
  .toolbar .item input{width:120px;background:#fff;}
  .btn{display:block;width:100%;padding:12px;background:#ff9800;color:#fff;text-align:center;font-size:15px;font-weight:bold;border:none;border-radius:8px;margin-bottom:14px;cursor:pointer;box-shadow:0 3px 8px rgba(0,0,0,0.15);}
  .btn:hover{background:#fb8c00;}
  .btn-sm{display:inline-block;padding:6px 12px;background:#ff9800;color:#fff;border:none;border-radius:5px;font-size:12px;font-weight:bold;cursor:pointer;margin-right:4px;}
  .btn-sm:hover{background:#fb8c00;}
  .btn-green{background:#43a047;} .btn-green:hover{background:#2e7d32;}
  .btn-red{background:#e53935;} .btn-red:hover{background:#c62828;}
  .btn-blue{background:#1e88e5;} .btn-blue:hover{background:#1565c0;}
  .tip{background:#e3f2fd;border-left:4px solid #2196f3;padding:10px 14px;border-radius:4px;font-size:12px;line-height:1.7;color:#1565c0;margin-bottom:12px;}
  .tip b{color:#0d47a1;}
  .ok-box{background:#e8f5e9;border:2px solid #43a047;border-radius:6px;padding:8px 12px;font-size:12px;color:#2e7d32;font-weight:bold;margin-bottom:10px;}
  .err-box{background:#ffebee;border:2px solid #e53935;border-radius:6px;padding:8px 12px;font-size:12px;color:#c62828;font-weight:bold;margin-bottom:10px;}
  .fin-table td:first-child{text-align:left;padding-left:16px;}
  .fin-table .amt{text-align:right;font-weight:bold;color:#c62828;font-size:14px;}
  .fin-table .total td{background:#e0f2f1;font-size:16px;font-weight:bold;color:#004d40;}
  .fin-table .sub td{background:#fafafa;padding-left:36px;color:#555;font-weight:normal;}
  .tier-row-active{background:#c8e6c9!important;box-shadow:inset 0 0 0 2px #2e7d32;}
  .tier-row-active td{font-weight:bold;}
  .speech{background:#fff8e1;border:2px solid #ffb300;border-radius:10px;padding:16px;margin-top:12px;font-size:13px;line-height:2;}
  .speech-quote{background:#fff;border-left:5px solid #ff9800;padding:12px 16px;margin:8px 0;color:#1e3a5f;font-size:14px;line-height:2;border-radius:4px;}
  .data-table{min-width:2600px;}
  .data-table th{font-size:11px;padding:5px 3px;}
  .data-table td{font-size:11px;}
  .data-table input{font-size:11px;padding:4px 2px;}
  .ratio-bar{display:flex;justify-content:space-between;align-items:center;background:#fff3e0;border:2px solid #ff9800;border-radius:6px;padding:8px 12px;margin-top:8px;font-size:12px;font-weight:bold;}
  .ratio-bar .lbl{color:#bf360c;}
  .ratio-bar .val{font-size:14px;}
  .rank-1{background:#ffd700!important;color:#b71c1c!important;font-weight:bold;}
  .rank-2{background:#e0e0e0!important;color:#333!important;font-weight:bold;}
  .rank-3{background:#ffb74d!important;color:#4e342e!important;font-weight:bold;}
  .rule-table td{text-align:left;font-size:12px;line-height:1.6;}
  .rule-table td:first-child{text-align:center;font-weight:bold;background:#f8fafc;}
  .calc-detail{font-size:10px;color:#666;font-weight:normal;display:block;margin-top:2px;}
  .derive-box{background:#f8fafc;border:1px solid #cfd8dc;border-radius:8px;padding:14px;line-height:2.2;font-size:13px;}
  .derive-box .row{padding:2px 0;}
  .derive-box .plus{color:#2e7d32;font-weight:bold;}
  .derive-box .minus{color:#c62828;font-weight:bold;}
  .derive-box .sep{border-top:2px dashed #999;margin:8px 0;padding-top:8px;}
  .derive-box .result{background:#fff3e0;padding:10px;border-radius:6px;border:2px solid #ff9800;margin-top:8px;}
  @media(max-width:768px){
    .hero-item .value{font-size:18px;}
    h1{font-size:15px;}
  }
</style>
</head>
<body>
<div class="wrap">
  <button class="btn" onclick="downloadPage()">⬇️ 保存为本地文件</button>
  <h1>🏆 门店薪酬与利润核算系统 · 完整版</h1>

  <div class="hero">
    <div class="hero-grid">
      <div class="hero-item"><div class="label">老板最终到手纯利</div><div class="value" id="hero_profit">¥0</div><div class="sub" id="hero_margin">利润率 0%</div></div>
      <div class="hero-item"><div class="label">当月核销总业绩</div><div class="value" id="hero_revenue">¥0</div><div class="sub">充卡不计营收</div></div>
      <div class="hero-item"><div class="label">全店总提成率</div><div class="value" id="hero_comm_rate" style="color:#ffb74d;">0%</div><div class="sub" id="hero_comm_amt">总提成 ¥0</div></div>
      <div class="hero-item"><div class="label">员工实发工资合计</div><div class="value" id="hero_salary" style="color:#81c784;">¥0</div><div class="sub" id="hero_avg">人均 ¥0</div></div>
    </div>
    <div class="hero-tier" id="hero_tier">当前档位:<b>未达任何档位</b> | 提点加成:<b>+0%</b></div>
  </div>

  <h2>一、门店基础参数</h2>
  <div class="param-card">
    <table>
      <tr>
        <td class="p-label">当月总业绩(不含充卡)</td><td><input type="number" id="p_revenue" value="80000" oninput="calc()"></td>
        <td class="p-label">变动成本率</td><td><input type="number" id="p_var_rate" value="15" oninput="calc()"> %</td>
        <td class="p-label">房租</td><td><input type="number" id="p_rent" value="6000" oninput="calc()"></td>
      </tr>
      <tr>
        <td class="p-label">其他固定成本</td><td><input type="number" id="p_other" value="0" oninput="calc()"></td>
        <td class="p-label">每人固定补贴</td><td><input type="number" id="p_subsidy" value="500" oninput="calc()"></td>
        <td class="p-label">利润分红提取比例</td><td><input type="number" id="p_pool_rate" value="30" oninput="calc()"> %</td>
      </tr>
      <tr>
        <td class="p-label">发型组分红池占比</td><td><input type="number" id="p_hair_share" value="60" oninput="calc()"> %</td>
        <td class="p-label">洗护组分红池占比</td><td><input type="number" id="p_wash_share" value="40" oninput="calc()"> %</td>
        <td class="p-label">指定客目标占比</td><td><input type="number" id="p_zd_target" value="30" oninput="calc()"> %</td>
      </tr>
    </table>
  </div>

  <h2>二、项目单价、提点与项目占比</h2>
  <div class="tip">
    <b>项目占比的作用:</b>① 一键均分业绩时按占比分配到每位员工 ② 校验面板按"总业绩 × 占比"核算门店该项目营收。<br>
    <b>💡 双人合作项目说明:</b>如洗剪吹,客人消费 50 元,发型师和洗护师都各记 50 元业绩,各自按自己提点算提成,所以两边加总 > 门店营收是正常的。
  </div>
  <div class="scroll">
    <table>
      <thead><tr><th>项目</th><th>单价</th><th>项目占比</th><th>发型师提点</th><th>洗护师提点</th><th>助理提点</th></tr></thead>
      <tbody>
        <tr><td>洗剪吹</td><td><input type="number" id="pr_sc" value="50" oninput="calc()"></td><td class="hl-yellow"><input type="number" id="r_sc" value="40" oninput="calc()"> %</td><td><input type="number" id="h_sc" value="35" oninput="calc()">%</td><td><input type="number" id="w_sc" value="25" oninput="calc()">%</td><td><input type="number" id="a_sc" value="0" oninput="calc()">%</td></tr>
        <tr><td>洗吹</td><td><input type="number" id="pr_cw" value="30" oninput="calc()"></td><td class="hl-yellow"><input type="number" id="r_cw" value="10" oninput="calc()"> %</td><td><input type="number" id="h_cw" value="33" oninput="calc()">%</td><td><input type="number" id="w_cw" value="25" oninput="calc()">%</td><td><input type="number" id="a_cw" value="0" oninput="calc()">%</td></tr>
        <tr><td>吹风洗头疗</td><td><input type="number" id="pr_cf" value="40" oninput="calc()"></td><td class="hl-yellow"><input type="number" id="r_cf" value="5" oninput="calc()"> %</td><td><input type="number" id="h_cf" value="15" oninput="calc()">%</td><td><input type="number" id="w_cf" value="20" oninput="calc()">%</td><td><input type="number" id="a_cf" value="0" oninput="calc()">%</td></tr>
        <tr><td>头疗加剪发</td><td><input type="number" id="pr_tljf" value="150" oninput="calc()"></td><td class="hl-yellow"><input type="number" id="r_tljf" value="5" oninput="calc()"> %</td><td><input type="number" id="h_tljf" value="15" oninput="calc()">%</td><td><input type="number" id="w_tljf" value="30" oninput="calc()">%</td><td><input type="number" id="a_tljf" value="0" oninput="calc()">%</td></tr>
        <tr><td>烫染</td><td><input type="number" id="pr_tr" value="200" oninput="calc()"></td><td class="hl-yellow"><input type="number" id="r_tr" value="30" oninput="calc()"> %</td><td><input type="number" id="h_tr" value="30" oninput="calc()">%</td><td><input type="number" id="w_tr" value="0" oninput="calc()">%</td><td><input type="number" id="a_tr" value="18" oninput="calc()">%</td></tr>
        <tr><td>单人头疗</td><td><input type="number" id="pr_tl" value="100" oninput="calc()"></td><td class="hl-yellow"><input type="number" id="r_tl" value="10" oninput="calc()"> %</td><td><input type="number" id="h_tl" value="0" oninput="calc()">%</td><td><input type="number" id="w_tl" value="30" oninput="calc()">%</td><td><input type="number" id="a_tl" value="0" oninput="calc()">%</td></tr>
        <tr><td>造型单人</td><td><input type="number" id="pr_zx" value="80" oninput="calc()"></td><td class="hl-yellow"><input type="number" id="r_zx" value="0" oninput="calc()"> %</td><td><input type="number" id="h_zx" value="25" oninput="calc()">%</td><td><input type="number" id="w_zx" value="0" oninput="calc()">%</td><td><input type="number" id="a_zx" value="10" oninput="calc()">%</td></tr>
        <tr><td>外卖单人</td><td><input type="number" id="pr_wm" value="50" oninput="calc()"></td><td class="hl-yellow"><input type="number" id="r_wm" value="0" oninput="calc()"> %</td><td><input type="number" id="h_wm" value="10" oninput="calc()">%</td><td><input type="number" id="w_wm" value="10" oninput="calc()">%</td><td><input type="number" id="a_wm" value="5" oninput="calc()">%</td></tr>
      </tbody>
    </table>
  </div>
  <div class="ratio-bar"><span class="lbl">📊 项目占比合计:</span><span class="val" id="ratio_sum">-</span></div>

  <h2>三、充卡与指定客参数</h2>
  <div class="scroll">
    <table>
      <thead><tr><th>参数</th><th>填写值</th><th>含义</th><th>实时举例</th></tr></thead>
      <tbody>
        <tr><td class="hl-orange">充卡业绩占比</td><td><input type="number" id="r_card" value="15" oninput="calc()"> %</td><td style="text-align:left;font-size:11px;">全店充卡业绩 = 总业绩 × 占比</td><td id="ex_card_ratio" style="text-align:left;font-size:11px;color:#0d47a1;">-</td></tr>
        <tr><td class="hl-orange">指定客业绩占比</td><td><input type="number" id="r_zd" value="20" oninput="calc()"> %</td><td style="text-align:left;font-size:11px;">全店指定客业绩 = 总业绩 × 占比</td><td id="ex_zd_ratio" style="text-align:left;font-size:11px;color:#0d47a1;">-</td></tr>
        <tr><td class="hl-orange">充卡折算比例</td><td><input type="number" id="p_card_perf_rate" value="100" oninput="calc()"> %</td><td style="text-align:left;font-size:11px;">100% = 充多少算多少;80% = 打8折算</td><td id="ex_card_perf" style="text-align:left;font-size:11px;color:#0d47a1;">-</td></tr>
        <tr><td class="hl-orange">充卡提点</td><td><input type="number" id="p_card_comm" value="5" oninput="calc()"> %</td><td style="text-align:left;font-size:11px;">5% = 卖 1000 元卡拿 50 元</td><td id="ex_card_comm" style="text-align:left;font-size:11px;color:#0d47a1;">-</td></tr>
        <tr><td class="hl-orange">指定客提点</td><td><input type="number" id="p_zd_comm" value="3" oninput="calc()"> %</td><td style="text-align:left;font-size:11px;">3% = 做 1000 元拿 30 元</td><td id="ex_zd_comm" style="text-align:left;font-size:11px;color:#0d47a1;">-</td></tr>
      </tbody>
    </table>
  </div>

  <h2>四、业绩阶梯提点</h2>
  <div class="toolbar">
    <div class="item">🎯 本月实际完成业绩 <input type="number" id="actual_revenue" value="80000" oninput="calc()"> 元</div>
    <div class="item" style="background:#e8f5e9;color:#2e7d32;padding:6px 12px;border-radius:5px;border:2px solid #43a047;" id="tier_match">当前匹配:未达档位</div>
    <button class="btn-sm btn-green" onclick="addTier()">+ 增加档位</button>
  </div>
  <div class="scroll">
    <table>
      <thead><tr><th>档位名称</th><th>业绩门槛</th><th>提点加成</th><th>效果预览</th><th>操作</th></tr></thead>
      <tbody id="tier_body"></tbody>
    </table>
  </div>

  <h2>五、员工明细</h2>
  <div class="toolbar" style="background:#e8f5e9;border-color:#43a047;">
    <div class="item" style="color:#1b5e20;">
      👥 人数:发型师 <input type="number" id="n_hair" value="3" min="0" style="width:55px;" onchange="syncHeadcount()"> 人
      洗护师 <input type="number" id="n_wash" value="3" min="0" style="width:55px;" onchange="syncHeadcount()"> 人
      助理 <input type="number" id="n_assist" value="1" min="0" style="width:55px;" onchange="syncHeadcount()"> 人
    </div>
    <button class="btn-sm btn-green" onclick="addStaff('hair')">+ 发型师</button>
    <button class="btn-sm btn-green" onclick="addStaff('wash')">+ 洗护师</button>
    <button class="btn-sm btn-green" onclick="addStaff('assist')">+ 助理</button>
    <button class="btn-sm btn-blue" onclick="autoAllocate()">🎲 一键均分业绩</button>
  </div>
  <div class="scroll">
    <table class="data-table">
      <thead>
        <tr>
          <th rowspan="2">姓名</th><th rowspan="2">岗位</th>
          <th colspan="2">洗剪吹</th><th colspan="2">洗吹</th><th colspan="2">吹风洗</th>
          <th colspan="2">头疗剪发</th><th colspan="2">烫染</th><th colspan="2">头疗</th>
          <th colspan="2">造型</th><th colspan="2">外卖</th>
          <th rowspan="2">计件<br>基数</th><th rowspan="2">项目<br>提成</th>
          <th rowspan="2">充卡<br>现金</th><th rowspan="2">充卡<br>提成</th>
          <th rowspan="2">指定客<br>业绩</th><th rowspan="2">指定客<br>提成</th>
          <th rowspan="2">总提成</th>
          <th rowspan="2">散客<br>现金</th>
          <th colspan="5">考核分</th>
          <th rowspan="2">总分</th><th rowspan="2">排名</th><th rowspan="2">分红工资</th><th rowspan="2">实发工资</th>
          <th rowspan="2">操作</th>
        </tr>
        <tr>
          <th>业绩</th><th>提成</th><th>业绩</th><th>提成</th><th>业绩</th><th>提成</th>
          <th>业绩</th><th>提成</th><th>业绩</th><th>提成</th><th>业绩</th><th>提成</th>
          <th>业绩</th><th>提成</th><th>业绩</th><th>提成</th>
          <th>劳动<br>(30)</th><th>充卡<br>(20)</th><th>指定<br>(20)</th><th>口碑<br>(10)</th><th>管理<br>(20)</th>
        </tr>
      </thead>
      <tbody id="staff_body"></tbody>
    </table>
  </div>

  <!-- ★★★ 五-B:分红考核机制详解(新增) ★★★ -->
  <h2>五-B、分红考核机制详解(员工一看就懂)</h2>

  <div class="tip" style="font-size:13px;">
    <b>一句话总结:</b>门店利润扣完成本后,按比例提取"分红池",再按每位员工的"考核分占组内总分的比例"分给每个人。<br>
    <b style="color:#bf360c;">分数越高、排名越前 → 分红越多。排名只是给人看的,真正分钱是按"分数占比"算的。</b>
  </div>

  <div style="background:#e8f5e9;border:2px solid #43a047;border-radius:8px;padding:10px 14px;margin-bottom:12px;">
    <label style="font-size:13px;font-weight:bold;color:#1b5e20;cursor:pointer;">
      <input type="checkbox" id="auto_score_mode" onchange="toggleAutoMode()" style="width:auto;display:inline-block;margin-right:6px;transform:scale(1.2);">
      ⚡ 启用自动算分(劳动/充卡/指定分由系统按规则自动算,只留口碑/管理分手动填)
    </label>
    <div style="font-size:12px;color:#555;margin-top:6px;">
      关闭时,5 项分全部手动填(原逻辑)。开启后,员工表里劳动分/充卡分/指定分的输入框会变灰自动算,你只需填口碑分和管理分。
    </div>
  </div>

  <h3>📊 第一步:分红池是怎么算出来的(按当前真实数字)</h3>
  <div class="derive-box" id="derive_pool"></div>

  <h3>📋 第二步:考核分 5 项规则(怎么算的、拿满分的条件)</h3>
  <table class="rule-table">
    <thead>
      <tr><th width="12%">项目</th><th width="8%">满分</th><th width="40%">计算规则</th><th width="40%">举例说明(数字演示)</th></tr>
    </thead>
    <tbody>
      <tr>
        <td>1. 劳动业绩分</td>
        <td>30 分</td>
        <td>组内第一名拿满 30 分,其余:<br><b>(个人业绩 ÷ 组内第一名业绩) × 30</b></td>
        <td>发型组 A 做 5万(第一名),B 做 2.5万,C 做 1万:<br>A = (5万÷5万)×30 = <b>30 分</b><br>B = (2.5万÷5万)×30 = <b>15 分</b><br>C = (1万÷5万)×30 = <b>6 分</b></td>
      </tr>
      <tr>
        <td>2. 充卡业绩分</td>
        <td>20 分</td>
        <td>组内第一名拿满 20 分,其余:<br><b>(个人充卡 ÷ 组内第一名充卡) × 20</b></td>
        <td>A 充 1万(第一名),B 充 5千,C 充 0:<br>A = (1万÷1万)×20 = <b>20 分</b><br>B = (5千÷1万)×20 = <b>10 分</b><br>C = <b>0 分</b></td>
      </tr>
      <tr>
        <td>3. 指定客占比分</td>
        <td>20 分</td>
        <td>达标拿满分 20,未达按比例:<br><b>(实际占比 ÷ 目标占比) × 20</b><br>实际占比 = 个人指定客业绩 ÷ 个人项目业绩</td>
        <td>目标占比 30%:<br>A 实际 30% → 达标 = <b>20 分</b><br>B 实际 15% → (15%÷30%)×20 = <b>10 分</b><br>C 实际 5% → (5%÷30%)×20 = <b>3.33 分</b></td>
      </tr>
      <tr>
        <td>4. 线上口碑分</td>
        <td>10 分</td>
        <td>底分 5 分,好评 +0.5/个,差评 -5/个。老板手动填。</td>
        <td>A 有 4 个好评:5 + 4×0.5 = <b>7 分</b><br>B 有 1 个差评:5 - 5 = <b>0 分</b></td>
      </tr>
      <tr>
        <td>5. 管理综合分</td>
        <td>20 分</td>
        <td>出勤、卫生、团队配合等,老板直接打分。</td>
        <td>老板给 A 打 18 分,给 B 打 20 分(全勤)</td>
      </tr>
    </tbody>
  </table>

  <h3>🧮 第三步:实时算分演示(按当前员工真实数据)</h3>
  <div class="tip">
    💡 下面按规则自动算出每位员工的 5 项分和总分。<b>如果启用了自动算分,员工表里也是这个结果;如果没启用,下面是按规则算出的参考值,员工表里以你手动填的为准。</b>
  </div>
  <div class="scroll">
    <table id="score_demo_table">
      <thead>
        <tr>
          <th>组别</th><th>姓名</th>
          <th>个人业绩<br>(计件基数)</th><th>劳动分<br>(满分30)</th>
          <th>个人充卡</th><th>充卡分<br>(满分20)</th>
          <th>指定客业绩</th><th>指定客占比</th><th>指定分<br>(满分20)</th>
          <th>口碑分<br>(满分10)</th><th>管理分<br>(满分20)</th>
          <th>总分</th>
        </tr>
      </thead>
      <tbody id="score_demo_body"></tbody>
    </table>
  </div>

  <h3>🏆 第四步:排名与分红(发型组、洗护组分别排名)</h3>
  <div class="scroll">
    <table id="rank_demo_table">
      <thead>
        <tr>
          <th>组别</th><th>排名</th><th>姓名</th>
          <th>总分</th><th>组内总分占比</th>
          <th>组分红池</th><th>个人分红金额</th>
        </tr>
      </thead>
      <tbody id="rank_demo_body"></tbody>
    </table>
  </div>

  <h3>💡 第五步:第一名是怎么来的(举例详细说明)</h3>
  <div id="first_place_explain" style="background:#fff8e1;border:2px solid #ffb300;border-radius:8px;padding:14px;line-height:2;font-size:13px;"></div>

  <div class="tip" style="margin-top:12px;">
    <b>📌 排名规则(123 名怎么分):</b><br>
    • 按总分从高到低排,第 1 名、第 2 名、第 3 名...<br>
    • <b>同分并列:</b>如果两人都是 85 分,两人都是第 1 名,下一名是第 3 名(如 85、85、70 → 第1、第1、第3)。<br>
    • <b>排名只是给人看的激励,真正分钱是按"分数占比"算的。</b>即使排第 3 名,只要分数占比高,分红照样多。<br>
    • <b>分红公式:</b>个人分红 = 组分红池 × (个人总分 ÷ 组内总分合计)。<br>
    • <b>助理已纳入洗护组:</b>助理和洗护师一起排名、一起分洗护池。
  </div>

  <!-- 原有模块继续 -->
  <h2>六、门店财务剥洋葱核算</h2>
  <table class="fin-table">
    <thead><tr><th width="45%">项目</th><th width="20%">金额 / 比例</th><th width="35%">说明</th></tr></thead>
    <tbody>
      <tr><td><b>1. 门店营业收入</b></td><td class="amt" id="f_revenue">0</td><td>门店划卡(充卡不算营收)</td></tr>
      <tr><td>减去:变动成本</td><td class="amt" id="f_var">0</td><td>营收 × 变动成本率</td></tr>
      <tr><td>减去:固定成本</td><td class="amt" id="f_fix">0</td><td>房租 + 其他固定</td></tr>
      <tr><td><b>减去:全店员工总提成</b></td><td class="amt" id="f_total_comm" style="color:#bf360c;">0</td><td>项目提成 + 充卡提成 + 指定客提成</td></tr>
      <tr class="sub"><td>其中:项目提成</td><td class="amt" id="f_item_comm">0</td><td>项目业绩 × 提点(含阶梯加成)</td></tr>
      <tr class="sub"><td>其中:充卡提成</td><td class="amt" id="f_card_comm">0</td><td>充卡现金 × 折算 × 提点</td></tr>
      <tr class="sub"><td>其中:指定客提成</td><td class="amt" id="f_zd_comm">0</td><td>指定客业绩 × 提点</td></tr>
      <tr><td>减去:员工固定补贴</td><td class="amt" id="f_subsidy">0</td><td>人数 × 每人补贴</td></tr>
      <tr style="background:#fff3e0;"><td><b>2. 门店基础纯利(分红前)</b></td><td class="amt" id="f_base_profit" style="color:#e65100;">0</td><td>营收 - 变动 - 固定 - 总提成 - 补贴</td></tr>
      <tr><td>减去:实发分红</td><td class="amt" id="f_pool">0</td><td>基础纯利 × 分红比例</td></tr>
      <tr class="total"><td><b>3. 老板最终到手纯利</b></td><td class="amt" id="f_final_profit">0</td><td>基础纯利 - 实发分红</td></tr>
      <tr style="background:#e3f2fd;"><td><b>核心指标</b></td><td></td><td></td></tr>
      <tr><td>门店利润率</td><td class="amt" id="f_margin" style="color:#2e7d32;">0%</td><td>老板纯利 ÷ 总业绩</td></tr>
      <tr><td><b>全店总提成率</b></td><td class="amt" id="f_comm_rate" style="color:#c62828;">0%</td><td>总提成 ÷ 总业绩</td></tr>
      <tr class="sub"><td>其中:项目提成率</td><td class="amt" id="f_item_rate">0%</td><td>项目提成 ÷ 总业绩</td></tr>
      <tr class="sub"><td>其中:充卡提成率</td><td class="amt" id="f_card_rate">0%</td><td>充卡提成 ÷ 总业绩</td></tr>
      <tr class="sub"><td>其中:指定客提成率</td><td class="amt" id="f_zd_rate">0%</td><td>指定客提成 ÷ 总业绩</td></tr>
      <tr><td>全店新充现金总额</td><td class="amt" id="f_card_total" style="color:#1565c0;">0</td><td>所有员工充卡合计</td></tr>
    </tbody>
  </table>

  <h2>七、员工工资对账</h2>
  <div id="reconcile_box"></div>

  <h2>八、个人实际提成点测算</h2>
  <div class="toolbar" style="background:#f3e5f5;border-color:#8e24aa;">
    <div class="item" style="color:#4a148c;">
      测算门店业绩 <input type="number" id="sim_revenue" value="30000" oninput="renderPersonal()" style="width:120px;"> 元
      | 分红池比例 <input type="number" id="sim_pool_rate" value="80" oninput="renderPersonal()" style="width:70px;"> %
      | <label style="font-size:12px;"><input type="checkbox" id="sim_include_assist" checked onchange="renderPersonal()" style="width:auto;display:inline-block;margin-right:4px;"> 助理纳入洗护组分红</label>
    </div>
  </div>
  <div class="scroll">
    <table>
      <thead>
        <tr><th>姓名</th><th>岗位</th><th>计件基数</th><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="personal_body"></tbody>
    </table>
  </div>
  <div id="personal_summary"></div>

  <h2>九、提成比例健康区间 + 一键缩放</h2>
  <div class="toolbar" style="background:#e8f5e9;border-color:#43a047;">
    <div class="item" style="color:#1b5e20;">
      目标利润率 <input type="number" id="p_target_margin" value="20" oninput="calc()" style="width:60px;"> %
      | 警戒利润率 <input type="number" id="p_warn_margin" value="10" oninput="calc()" style="width:60px;"> %
      | 最低竞争力提成率 <input type="number" id="p_min_comm_rate" value="20" oninput="calc()" style="width:60px;"> %
    </div>
  </div>
  <div class="scroll">
    <table>
      <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="advice_body"></tbody>
    </table>
  </div>
  <div class="toolbar" style="background:#fff3e0;border-color:#ff9800;margin-top:12px;">
    <div class="item" style="color:#bf360c;">
      目标总提成率 <input type="number" id="scale_target" placeholder="例如 32.5" style="width:100px;"> %
      <button class="btn-sm btn-green" onclick="fillRecommend()">📋 填入当前推荐值</button>
      <button class="btn-sm btn-red" onclick="scaleCommissions()">⚖️ 一键缩放项目提成</button>
    </div>
  </div>

  <h2>十、面对应聘者直接照着说(自动生成)</h2>
  <div id="speech_box" class="speech"></div>

  <h2>十一、数据一致性校验</h2>
  <div id="validate_box"></div>

</div>

<script id="tiers-data" type="application/json">[{"name":"第1档","threshold":0,"bonus":0},{"name":"第2档","threshold":30000,"bonus":1},{"name":"第3档","threshold":50000,"bonus":2},{"name":"第4档","threshold":80000,"bonus":3},{"name":"第5档","threshold":120000,"bonus":5}]</script>

<script>
window.onerror = function(msg, src, line){ console.error('错误:' + msg + ' 行 ' + line); return false; };
function v(id){ var el = document.getElementById(id); if(!el) return 0; var n = parseFloat(el.value); return isNaN(n) ? 0 : n; }
function fmt(n){ var num = Number(n); if(isNaN(num)) return '0.00'; return num.toLocaleString('zh-CN',{minimumFractionDigits:2,maximumFractionDigits:2}); }
function set(id, txt){ var el = document.getElementById(id); if(el) el.innerText = txt; }

var ITEMS = ['sc','cw','cf','tljf','tr','tl','zx','wm'];
var ROLE_ALLOW = {hair:['sc','cw','cf','tljf','tr','zx','wm'],wash:['sc','cw','cf','tljf','tl','wm'],assist:['tr','zx','wm']};
var ROLE_NAME = {hair:'发型师',wash:'洗护师',assist:'助理'};

var TIERS = [];
try { var td = document.getElementById('tiers-data'); if(td && td.textContent.trim()) TIERS = JSON.parse(td.textContent); } catch(e){}
if(!TIERS || TIERS.length === 0) TIERS = [
  {name:'第1档',threshold:0,bonus:0},{name:'第2档',threshold:30000,bonus:1},
  {name:'第3档',threshold:50000,bonus:2},{name:'第4档',threshold:80000,bonus:3},
  {name:'第5档',threshold:120000,bonus:5}
];
function getBonusAt(r){ for(var i=TIERS.length-1;i>=0;i--) if(r>=TIERS[i].threshold) return TIERS[i].bonus; return 0; }
function getTierAt(r){ for(var i=TIERS.length-1;i>=0;i--) if(r>=TIERS[i].threshold) return TIERS[i]; return null; }

function renderTiers(){
  var body = document.getElementById('tier_body'); if(!body) return;
  var match = getTierAt(v('actual_revenue'));
  var matchIdx = match ? TIERS.indexOf(match) : -1;
  var hSc = v('h_sc');
  var html = '';
  for(var i=0;i<TIERS.length;i++){
    var t = TIERS[i];
    var cls = (i === matchIdx) ? 'tier-row-active' : '';
    html += '<tr class="'+cls+'">' +
      '<td><input type="text" value="'+t.name+'" onchange="updateTier('+i+',\'name\',this.value)" style="width:80px;"></td>' +
      '<td><input type="number" value="'+t.threshold+'" onchange="updateTier('+i+',\'threshold\',this.value)"> 元起</td>' +
      '<td><input type="number" value="'+t.bonus+'" onchange="updateTier('+i+',\'bonus\',this.value)"> %</td>' +
      '<td style="font-size:11px;color:#555;">洗剪吹提点 '+hSc+'% → '+(hSc+t.bonus)+'%</td>' +
      '<td><button class="btn-sm btn-red" onclick="removeTier('+i+')">删</button></td></tr>';
  }
  body.innerHTML = html;
  var mBox = document.getElementById('tier_match');
  if(mBox) mBox.innerHTML = match ? ('✅ 当前匹配:<b>'+match.name+'</b> | 加成 <b>+'+match.bonus+'%</b>') : '⚠️ 未达任何档位';
}
function updateTier(idx,key,val){
  if(key === 'threshold' || key === 'bonus') TIERS[idx][key] = parseFloat(val) || 0;
  else TIERS[idx][key] = val;
  calc();
}
function addTier(){ var last = TIERS[TIERS.length-1]; TIERS.push({name:'第'+(TIERS.length+1)+'档', threshold:last?last.threshold+30000:0, bonus:0}); calc(); }
function removeTier(idx){ if(TIERS.length <= 1){ alert('至少保留一个档位'); return; } if(!confirm('确定删除?')) return; TIERS.splice(idx,1); calc(); }

function createRow(role, name){
  var tr = document.createElement('tr');
  var roleName = ROLE_NAME[role] || '发型师';
  var cells = '';
  for(var i=0;i<ITEMS.length;i++){
    var k = ITEMS[i];
    cells += '<td><input type="number" class="v_'+k+'" value="0" oninput="calc()"></td>' +
             '<td class="c_'+k+'" style="background:#fff5f5;color:#c62828;font-weight:bold;">0.00</td>';
  }
  tr.innerHTML =
    '<td><input type="text" class="v_name" value="'+(name||roleName)+'" oninput="calc()"></td>' +
    '<td><select class="v_role" onchange="onRoleChange(this)">' +
      '<option value="hair"'+(role==='hair'?' selected':'')+'>发型师</option>' +
      '<option value="wash"'+(role==='wash'?' selected':'')+'>洗护师</option>' +
      '<option value="assist"'+(role==='assist'?' selected':'')+'>助理</option></select></td>' +
    cells +
    '<td class="out_base" style="background:#fff3e0;font-weight:bold;">0.00</td>' +
    '<td class="out_item_comm" style="background:#ffe0b2;font-weight:bold;color:#bf360c;">0.00</td>' +
    '<td><input type="number" class="v_card" value="0" oninput="calc()"></td>' +
    '<td class="out_card_comm" style="background:#e8f5e9;color:#2e7d32;font-weight:bold;">0.00</td>' +
    '<td><input type="number" class="v_zd_perf" value="0" oninput="calc()"></td>' +
    '<td class="out_zd_comm" style="background:#e8f5e9;color:#2e7d32;font-weight:bold;">0.00</td>' +
    '<td class="out_total_comm" style="background:#ffe0b2;color:#bf360c;font-weight:bold;">0.00</td>' +
    '<td><input type="number" class="v_cash" value="0" oninput="calc()"></td>' +
    '<td><input type="number" class="v_s_labor" value="0" oninput="calc()"></td>' +
    '<td><input type="number" class="v_s_card" value="0" oninput="calc()"></td>' +
    '<td><input type="number" class="v_s_zd" value="0" oninput="calc()"></td>' +
    '<td><input type="number" class="v_s_rep" value="0" oninput="calc()"></td>' +
    '<td><input type="number" class="v_s_manage" value="0" oninput="calc()"></td>' +
    '<td class="out_score" style="background:#e0f7fa;color:#006064;font-weight:bold;">0</td>' +
    '<td class="out_rank" style="font-weight:bold;">-</td>' +
    '<td class="out_bonus" style="background:#e8f5e9;color:#2e7d32;font-weight:bold;">0.00</td>' +
    '<td class="out_salary" style="background:#1e3a5f;color:#fff;font-weight:bold;">0.00</td>' +
    '<td><button class="btn-sm btn-red" onclick="removeStaff(this)">删</button></td>';
  return tr;
}
function onRoleChange(sel){
  var tr = sel.parentNode.parentNode;
  var role = sel.value;
  var allow = ROLE_ALLOW[role] || [];
  for(var i=0;i<ITEMS.length;i++){
    var k = ITEMS[i];
    var inp = tr.querySelector('.v_'+k);
    if(!inp) continue;
    var ok = allow.indexOf(k) !== -1;
    inp.disabled = !ok;
    if(!ok) inp.value = 0;
    var c = tr.querySelector('.c_'+k);
    if(c) c.style.opacity = ok ? 1 : 0.3;
  }
  calc();
}
function addStaff(role){ var body = document.getElementById('staff_body'); if(!body) return; body.appendChild(createRow(role)); calc(); }
function removeStaff(btn){
  var tr = btn.parentNode.parentNode; var body = tr.parentNode;
  if(body.children.length <= 1){ alert('至少保留一位员工'); return; }
  if(!confirm('确定删除?')) return;
  body.removeChild(tr); calc();
}
function syncHeadcount(){
  var body = document.getElementById('staff_body'); if(!body) return;
  var target = {hair:Math.max(0,parseInt(document.getElementById('n_hair').value)||0),wash:Math.max(0,parseInt(document.getElementById('n_wash').value)||0),assist:Math.max(0,parseInt(document.getElementById('n_assist').value)||0)};
  var current = {hair:0,wash:0,assist:0};
  for(var i=0;i<body.children.length;i++){ var r = body.children[i].querySelector('.v_role').value; if(current[r] !== undefined) current[r]++; }
  ['hair','wash','assist'].forEach(function(role){ while(current[role] < target[role]){ body.appendChild(createRow(role)); current[role]++; } });
  ['hair','wash','assist'].forEach(function(role){ while(current[role] > target[role]){ var all = body.children, last = null; for(var j=all.length-1;j>=0;j--){ if(all[j].querySelector('.v_role').value === role){ last = all[j]; break; } } if(!last) break; body.removeChild(last); current[role]--; } });
  if(body.children.length === 0) body.appendChild(createRow('hair'));
  calc();
}

function autoAllocate(){
  var revenue = v('p_revenue');
  if(revenue <= 0){ alert('请先填写门店总业绩'); return; }
  var body = document.getElementById('staff_body'); if(!body) return;
  var hair=[],wash=[],assist=[];
  for(var i=0;i<body.children.length;i++){
    var r = body.children[i].querySelector('.v_role').value;
    if(r==='hair') hair.push(body.children[i]);
    else if(r==='wash') wash.push(body.children[i]);
    else if(r==='assist') assist.push(body.children[i]);
  }
  var ratios = {};
  ITEMS.forEach(function(k){ ratios[k] = v('r_'+k)/100; });
  var nH = Math.max(hair.length,1), nW = Math.max(wash.length,1), nA = Math.max(assist.length,1);
  var nAll = Math.max(body.children.length,1);
  function alloc(rows, count, keys){
    rows.forEach(function(tr){
      keys.forEach(function(k){
        var inp = tr.querySelector('.v_'+k);
        if(inp) inp.value = (revenue * ratios[k] / count).toFixed(2);
      });
    });
  }
  alloc(hair, nH, ['sc','cw','cf','tljf','tr','zx','wm']);
  alloc(wash, nW, ['sc','cw','cf','tljf','tl','wm']);
  alloc(assist, nA, ['tr','zx','wm']);
  var cardPerfRate = v('p_card_perf_rate')/100;
  var cardPerfTotal = revenue * v('r_card')/100;
  var cardCashTotal = cardPerfRate > 0 ? cardPerfTotal / cardPerfRate : 0;
  var zdTotal = revenue * v('r_zd')/100;
  for(var i=0;i<body.children.length;i++){
    var cardInp = body.children[i].querySelector('.v_card');
    if(cardInp) cardInp.value = (cardCashTotal / nAll).toFixed(2);
    var zdInp = body.children[i].querySelector('.v_zd_perf');
    if(zdInp) zdInp.value = (zdTotal / nAll).toFixed(2);
  }
  calc();
}

// ============ 自动算分 ============
function getStaffStat(tr){
  var base = 0;
  for(var i=0;i<ITEMS.length;i++){
    var inp = tr.querySelector('.v_'+ITEMS[i]);
    if(inp && !inp.disabled) base += parseFloat(inp.value) || 0;
  }
  var cardInp = tr.querySelector('.v_card');
  var card = cardInp ? parseFloat(cardInp.value) || 0 : 0;
  var zdInp = tr.querySelector('.v_zd_perf');
  var zd = zdInp ? parseFloat(zdInp.value) || 0 : 0;
  return {base:base, card:card, zd:zd};
}

function calcAutoScores(){
  var body = document.getElementById('staff_body'); if(!body) return;
  var hairRows = [], washRows = [];
  for(var i=0;i<body.children.length;i++){
    var tr = body.children[i];
    var role = tr.querySelector('.v_role').value;
    if(role === 'hair') hairRows.push(tr);
    else if(role === 'wash' || role === 'assist') washRows.push(tr);
  }
  var targetRate = v('p_zd_target') / 100;
  function processGroup(rows){
    var maxRev = 0, maxCard = 0;
    rows.forEach(function(tr){ var s = getStaffStat(tr); if(s.base > maxRev) maxRev = s.base; if(s.card > maxCard) maxCard = s.card; });
    rows.forEach(function(tr){
      var s = getStaffStat(tr);
      var labor = maxRev > 0 ? (s.base / maxRev) * 30 : 0;
      var cardScore = maxCard > 0 ? (s.card / maxCard) * 20 : 0;
      var zdRate = s.base > 0 ? s.zd / s.base : 0;
      var zdScore;
      if(targetRate <= 0 || zdRate >= targetRate) zdScore = targetRate <= 0 ? 20 : 20;
      else zdScore = (zdRate / targetRate) * 20;
      var lInp = tr.querySelector('.v_s_labor'); if(lInp) lInp.value = labor.toFixed(1);
      var cInp = tr.querySelector('.v_s_card'); if(cInp) cInp.value = cardScore.toFixed(1);
      var zInp = tr.querySelector('.v_s_zd'); if(zInp) zInp.value = zdScore.toFixed(1);
    });
  }
  processGroup(hairRows);
  processGroup(washRows);
}

function toggleAutoMode(){
  var isAuto = document.getElementById('auto_score_mode').checked;
  var body = document.getElementById('staff_body'); if(!body) return;
  for(var i=0;i<body.children.length;i++){
    var tr = body.children[i];
    ['labor','card','zd'].forEach(function(k){
      var inp = tr.querySelector('.v_s_' + k);
      if(inp) inp.disabled = isAuto;
    });
  }
  calc();
}

// ============ 核心计算 ============
function calc(){
  // 自动算分(如果开启)
  var autoMode = document.getElementById('auto_score_mode');
  if(autoMode && autoMode.checked) calcAutoScores();

  var revenue = v('p_revenue');
  var varRate = v('p_var_rate')/100;
  var rent = v('p_rent'), other = v('p_other'), subsidy = v('p_subsidy');
  var poolRate = v('p_pool_rate')/100;
  var hairShare = v('p_hair_share')/100;
  var washShare = v('p_wash_share')/100;
  var cardPerfRate = v('p_card_perf_rate')/100;
  var cardCommRate = v('p_card_comm')/100;
  var zdCommRate = v('p_zd_comm')/100;

  var ratioSum = 0;
  ITEMS.forEach(function(k){ ratioSum += v('r_'+k); });
  var rSumEl = document.getElementById('ratio_sum');
  if(rSumEl){
    var ok = Math.abs(ratioSum - 100) < 0.01;
    rSumEl.innerHTML = '<b style="color:'+(ok?'#2e7d32':'#c62828')+'">'+ratioSum.toFixed(1)+'%</b>' + (ok?' ✓':' ⚠️ 应等于100%');
  }

  var bonusPct = getBonusAt(revenue);
  var H={},W={},A={};
  for(var i=0;i<ITEMS.length;i++){
    var k = ITEMS[i];
    var h = v('h_'+k), w = v('w_'+k), a = v('a_'+k);
    H[k] = h > 0 ? (h + bonusPct)/100 : 0;
    W[k] = w > 0 ? (w + bonusPct)/100 : 0;
    A[k] = a > 0 ? (a + bonusPct)/100 : 0;
  }

  var body = document.getElementById('staff_body'); if(!body) return;
  var rows = Array.prototype.slice.call(body.children);
  var storeItemComm=0, storeCardComm=0, storeZdComm=0, storeCardCash=0, storeCashCash=0;
  var totalSubsidy=0;
  var hairScore=0, washScore=0;
  var cnt={hair:0,wash:0,assist:0};
  var staffData = [];

  rows.forEach(function(tr){
    var role = tr.querySelector('.v_role').value;
    if(cnt[role] !== undefined) cnt[role]++;
    var rateMap = role === 'hair' ? H : role === 'wash' ? W : A;
    var itemComm=0, base=0;
    for(var kk=0;kk<ITEMS.length;kk++){
      var key = ITEMS[kk];
      var inp = tr.querySelector('.v_'+key);
      var val = inp ? (parseFloat(inp.value)||0) : 0;
      var comm = val * (rateMap[key] || 0);
      var cell = tr.querySelector('.c_'+key);
      if(cell) cell.innerText = comm.toFixed(2);
      itemComm += comm;
      base += val;
    }
    var cardEl = tr.querySelector('.v_card');
    var cardCash = cardEl ? (parseFloat(cardEl.value)||0) : 0;
    var cardComm = cardCash * cardPerfRate * cardCommRate;
    var zdEl = tr.querySelector('.v_zd_perf');
    var zdPerf = zdEl ? (parseFloat(zdEl.value)||0) : 0;
    var zdComm = zdPerf * zdCommRate;
    var cashEl = tr.querySelector('.v_cash');
    var cashCash = cashEl ? (parseFloat(cashEl.value)||0) : 0;
    var totalComm = itemComm + cardComm + zdComm;

    var score=0;
    ['labor','card','zd','rep','manage'].forEach(function(sk){
      var s = tr.querySelector('.v_s_'+sk);
      if(s) score += parseFloat(s.value)||0;
    });

    tr.querySelector('.out_base').innerText = base.toFixed(2);
    tr.querySelector('.out_item_comm').innerText = itemComm.toFixed(2);
    tr.querySelector('.out_card_comm').innerText = cardComm.toFixed(2);
    tr.querySelector('.out_zd_comm').innerText = zdComm.toFixed(2);
    tr.querySelector('.out_total_comm').innerText = totalComm.toFixed(2);
    tr.querySelector('.out_score').innerText = score.toFixed(1);

    storeItemComm += itemComm;
    storeCardComm += cardComm;
    storeZdComm += zdComm;
    storeCardCash += cardCash;
    storeCashCash += cashCash;
    totalSubsidy += subsidy;
    if(role==='hair') hairScore += score;
    else if(role==='wash' || role==='assist') washScore += score;

    var name = tr.querySelector('.v_name').value;
    staffData.push({tr:tr, role:role, name:name, base:base, itemComm:itemComm, cardComm:cardComm, zdComm:zdComm, totalComm:totalComm, score:score, cardCash:cardCash, zdPerf:zdPerf,
      laborScore:parseFloat((tr.querySelector('.v_s_labor')||{}).value)||0,
      cardScore:parseFloat((tr.querySelector('.v_s_card')||{}).value)||0,
      zdScore:parseFloat((tr.querySelector('.v_s_zd')||{}).value)||0,
      repScore:parseFloat((tr.querySelector('.v_s_rep')||{}).value)||0,
      manageScore:parseFloat((tr.querySelector('.v_s_manage')||{}).value)||0
    });
  });

  var storeTotalComm = storeItemComm + storeCardComm + storeZdComm;
  var varCost = revenue * varRate;
  var fixCost = rent + other;
  var baseProfit = revenue - varCost - fixCost - storeTotalComm - totalSubsidy;
  var pool = baseProfit > 0 ? baseProfit * poolRate : 0;
  var hairPool = pool * hairShare;
  var washPool = pool * washShare;
  var finalProfit = baseProfit - pool;

  // 排名(分组,标准竞赛排名:同分并列)
  function getRankMap(rows, groupScoreFn){
    // 返回 {tr: {rank, totalScoreInGroup}}
    var arr = rows.map(function(tr){ return {tr:tr, score: groupScoreFn(tr)}; });
    var result = {};
    arr.forEach(function(item){
      var rank = 1;
      arr.forEach(function(other){ if(other.score > item.score + 1e-9) rank++; });
      result[item.tr] = {rank: rank, score: item.score};
    });
    return result;
  }
  var hairRows = staffData.filter(function(s){ return s.role === 'hair'; }).map(function(s){ return s.tr; });
  var washRows = staffData.filter(function(s){ return s.role === 'wash' || s.role === 'assist'; }).map(function(s){ return s.tr; });
  var hairRankMap = getRankMap(hairRows, function(tr){ return parseFloat(tr.querySelector('.out_score').innerText) || 0; });
  var washRankMap = getRankMap(washRows, function(tr){ return parseFloat(tr.querySelector('.out_score').innerText) || 0; });

  // 更新排名显示
  staffData.forEach(function(sd){
    var rankEl = sd.tr.querySelector('.out_rank');
    var rankInfo = sd.role === 'hair' ? hairRankMap[sd.tr] : washRankMap[sd.tr];
    if(rankEl && rankInfo){
      rankEl.innerText = '第' + rankInfo.rank + '名';
      rankEl.classList.remove('rank-1','rank-2','rank-3');
      if(rankInfo.rank <= 3) rankEl.classList.add('rank-' + rankInfo.rank);
    }
  });

  // 分红下发
  var totalSalary = 0;
  staffData.forEach(function(sd){
    var ratio=0, bonus=0;
    if(sd.role === 'hair' && hairScore > 0){ ratio = sd.score / hairScore; bonus = hairPool * ratio; }
    else if((sd.role === 'wash' || sd.role === 'assist') && washScore > 0){ ratio = sd.score / washScore; bonus = washPool * ratio; }
    var salary = sd.totalComm + subsidy + bonus;
    sd.bonus = bonus;
    sd.salary = salary;
    sd.ratio = ratio;
    sd.tr.querySelector('.out_bonus').innerText = bonus.toFixed(2);
    sd.tr.querySelector('.out_salary').innerText = salary.toFixed(2);
    totalSalary += salary;
  });

  var margin = revenue > 0 ? finalProfit/revenue*100 : 0;
  var commRate = revenue > 0 ? storeTotalComm/revenue*100 : 0;
  var avg = staffData.length > 0 ? totalSalary/staffData.length : 0;

  set('hero_profit', '¥' + fmt(finalProfit));
  set('hero_margin', '利润率 ' + margin.toFixed(1) + '%');
  set('hero_revenue', '¥' + fmt(revenue));
  set('hero_comm_rate', commRate.toFixed(1) + '%');
  set('hero_comm_amt', '总提成 ¥' + fmt(storeTotalComm));
  set('hero_salary', '¥' + fmt(totalSalary));
  set('hero_avg', '人均 ¥' + fmt(avg));
  var tierNow = getTierAt(v('actual_revenue'));
  var heroT = document.getElementById('hero_tier');
  if(heroT) heroT.innerHTML = tierNow ? ('当前档位:<b>'+tierNow.name+'</b> | 提点加成:<b>+'+tierNow.bonus+'%</b>') : '当前档位:<b>未达任何档位</b> | 提点加成:<b>+0%</b>';

  set('f_revenue', '¥' + fmt(revenue));
  set('f_var', '¥' + fmt(varCost));
  set('f_fix', '¥' + fmt(fixCost));
  set('f_total_comm', '¥' + fmt(storeTotalComm));
  set('f_item_comm', '¥' + fmt(storeItemComm));
  set('f_card_comm', '¥' + fmt(storeCardComm));
  set('f_zd_comm', '¥' + fmt(storeZdComm));
  set('f_subsidy', '¥' + fmt(totalSubsidy));
  set('f_base_profit', '¥' + fmt(baseProfit));
  set('f_pool', '¥' + fmt(pool));
  set('f_final_profit', '¥' + fmt(finalProfit));
  set('f_margin', margin.toFixed(2) + '%');
  set('f_comm_rate', commRate.toFixed(2) + '%');
  set('f_item_rate', (revenue>0?storeItemComm/revenue*100:0).toFixed(2) + '%');
  set('f_card_rate', (revenue>0?storeCardComm/revenue*100:0).toFixed(2) + '%');
  set('f_zd_rate', (revenue>0?storeZdComm/revenue*100:0).toFixed(2) + '%');
  set('f_card_total', '¥' + fmt(storeCardCash));

  // 举例
  var demoCard = 10000;
  var cardPerf = demoCard * cardPerfRate;
  set('ex_card_perf', '卖' + fmt(demoCard) + '元卡 → ' + fmt(demoCard) + '×' + (cardPerfRate*100).toFixed(0) + '% = ' + fmt(cardPerf) + ' 元');
  set('ex_card_comm', fmt(cardPerf) + '×' + (cardCommRate*100).toFixed(0) + '% = ' + fmt(cardPerf*cardCommRate) + ' 元充卡提成');
  set('ex_zd_comm', '指定客业绩10000元×' + (zdCommRate*100).toFixed(0) + '% = ' + fmt(10000*zdCommRate) + ' 元提成');
  set('ex_card_ratio', '总业绩 ' + fmt(revenue) + ' × ' + v('r_card').toFixed(0) + '% = 全店充卡业绩 ' + fmt(revenue*v('r_card')/100) + ' 元');
  set('ex_zd_ratio', '总业绩 ' + fmt(revenue) + ' × ' + v('r_zd').toFixed(0) + '% = 全店指定客业绩 ' + fmt(revenue*v('r_zd')/100) + ' 元');

  window._cr = {
    revenue:revenue, varCost:varCost, fixCost:fixCost, storeItemComm:storeItemComm,
    storeCardComm:storeCardComm, storeZdComm:storeZdComm, storeTotalComm:storeTotalComm,
    totalSubsidy:totalSubsidy, baseProfit:baseProfit, pool:pool, finalProfit:finalProfit,
    totalSalary:totalSalary, staffData:staffData, hairScore:hairScore, washScore:washScore,
    hairPool:hairPool, washPool:washPool, cnt:cnt, margin:margin, commRate:commRate,
    storeCardCash:storeCardCash, storeCashCash:storeCashCash, bonusPct:bonusPct,
    hairRankMap:hairRankMap, washRankMap:washRankMap
  };

  renderTiers();
  renderDividendDetail();
  updateAdvice();
  updateReconcile();
  renderPersonal();
  renderSpeech();
  validate();
}

// ============ 五-B:分红考核机制详解 ============
function renderDividendDetail(){
  var cr = window._cr; if(!cr) return;

  // ---------- 1. 分红池推导 ----------
  var deriveBox = document.getElementById('derive_pool');
  if(deriveBox){
    var html = '';
    html += '<div class="row">① 门店营业收入:<b>¥' + fmt(cr.revenue) + '</b></div>';
    html += '<div class="row">② 减去变动成本(' + v('p_var_rate') + '%):<span class="minus">-¥' + fmt(cr.varCost) + '</span></div>';
    html += '<div class="row">③ 减去固定成本(房租+其他):<span class="minus">-¥' + fmt(cr.fixCost) + '</span></div>';
    html += '<div class="row">④ 减去全店员工总提成:<span class="minus">-¥' + fmt(cr.storeTotalComm) + '</span></div>';
    html += '<div class="row" style="padding-left:20px;font-size:11px;color:#666;">其中:项目提成 ¥' + fmt(cr.storeItemComm) + ' | 充卡提成 ¥' + fmt(cr.storeCardComm) + ' | 指定客提成 ¥' + fmt(cr.storeZdComm) + '</div>';
    html += '<div class="row">⑤ 减去员工补贴(' + cr.staffData.length + '人 × ¥' + v('p_subsidy') + '):<span class="minus">-¥' + fmt(cr.totalSubsidy) + '</span></div>';
    html += '<div class="sep"></div>';
    html += '<div class="row"><b>= 门店基础纯利:<span class="plus">¥' + fmt(cr.baseProfit) + '</span></b></div>';
    html += '<div class="sep"></div>';
    html += '<div class="row">⑥ 乘以分红提取比例(' + v('p_pool_rate') + '%):<b>¥' + fmt(cr.baseProfit) + ' × ' + v('p_pool_rate') + '% = ¥' + fmt(cr.pool) + '</b></div>';
    html += '<div class="result">';
    html += '<b>💰 分红池总额 = ¥' + fmt(cr.pool) + '</b><br>';
    html += '<b style="color:#bf360c;">→ 发型组分红池 = ¥' + fmt(cr.pool) + ' × ' + v('p_hair_share') + '% = ¥' + fmt(cr.hairPool) + '</b><br>';
    html += '<b style="color:#1565c0;">→ 洗护组分红池(含助理)= ¥' + fmt(cr.pool) + ' × ' + v('p_wash_share') + '% = ¥' + fmt(cr.washPool) + '</b>';
    if(cr.baseProfit <= 0) html += '<br><span style="color:#c62828;">⚠️ 基础纯利为负,分红池为 0,本月无分红</span>';
    html += '</div>';
    deriveBox.innerHTML = html;
  }

  // ---------- 2. 实时算分演示 ----------
  var scoreBody = document.getElementById('score_demo_body');
  if(scoreBody){
    // 分组算最大值
    function calcGroup(rows){
      var maxRev = 0, maxCard = 0;
      rows.forEach(function(sd){ if(sd.base > maxRev) maxRev = sd.base; if(sd.cardCash > maxCard) maxCard = sd.cardCash; });
      return {maxRev: maxRev, maxCard: maxCard};
    }
    var hairArr = cr.staffData.filter(function(s){ return s.role === 'hair'; });
    var washArr = cr.staffData.filter(function(s){ return s.role === 'wash' || s.role === 'assist'; });
    var hairStats = calcGroup(hairArr);
    var washStats = calcGroup(washArr);
    var targetRate = v('p_zd_target');

    function renderScoreRow(sd, groupName, stats){
      var isHair = sd.role === 'hair';
      var stat = isHair ? hairStats : washStats;
      var labor = stat.maxRev > 0 ? (sd.base / stat.maxRev) * 30 : 0;
      var cardS = stat.maxCard > 0 ? (sd.cardCash / stat.maxCard) * 20 : 0;
      var zdRate = sd.base > 0 ? sd.zdPerf / sd.base : 0;
      var zdS = targetRate > 0 ? Math.min(20, (zdRate * 100 / targetRate) * 20) : 20;
      var total = labor + cardS + zdS + sd.repScore + sd.manageScore;
      var roleCls = isHair ? 'hl-yellow' : (sd.role === 'wash' ? 'hl-blue' : '');
      return '<tr>' +
        '<td class="' + roleCls + '">' + groupName + '</td>' +
        '<td><b>' + sd.name + '</b><br><small style="color:#666;">' + ROLE_NAME[sd.role] + '</small></td>' +
        '<td>¥' + fmt(sd.base) + '</td>' +
        '<td style="background:#e8f5e9;">' + labor.toFixed(1) + '分<span class="calc-detail">(' + fmt(sd.base) + '÷' + fmt(stat.maxRev) + '×30)</span></td>' +
        '<td>¥' + fmt(sd.cardCash) + '</td>' +
        '<td style="background:#e8f5e9;">' + cardS.toFixed(1) + '分<span class="calc-detail">(' + fmt(sd.cardCash) + '÷' + fmt(stat.maxCard) + '×20)</span></td>' +
        '<td>¥' + fmt(sd.zdPerf) + '</td>' +
        '<td>' + (zdRate*100).toFixed(1) + '%</td>' +
        '<td style="background:#e8f5e9;">' + zdS.toFixed(1) + '分<span class="calc-detail">(' + (zdRate*100).toFixed(1) + '%÷' + targetRate + '%×20)</span></td>' +
        '<td>' + sd.repScore.toFixed(1) + '</td>' +
        '<td>' + sd.manageScore.toFixed(1) + '</td>' +
        '<td style="background:#ffe0b2;font-weight:bold;color:#bf360c;">' + total.toFixed(1) + '</td>' +
      '</tr>';
    }
    var html = '';
    hairArr.forEach(function(sd){ html += renderScoreRow(sd, '发型组', hairStats); });
    washArr.forEach(function(sd){ html += renderScoreRow(sd, '洗护组', washStats); });
    scoreBody.innerHTML = html;
  }

  // ---------- 3. 排名与分红 ----------
  var rankBody = document.getElementById('rank_demo_body');
  if(rankBody){
    function renderRankRows(arr, groupName, rankMap, groupScore, groupPool){
      // 按排名排序
      var sorted = arr.slice().sort(function(a,b){
        var ra = rankMap[a.tr] ? rankMap[a.tr].rank : 999;
        var rb = rankMap[b.tr] ? rankMap[b.tr].rank : 999;
        return ra - rb;
      });
      var html = '';
      sorted.forEach(function(sd){
        var rankInfo = rankMap[sd.tr];
        var rank = rankInfo ? rankInfo.rank : '-';
        var ratio = groupScore > 0 ? sd.score / groupScore : 0;
        var bonus = groupPool * ratio;
        var rankCls = rank === 1 ? 'rank-1' : rank === 2 ? 'rank-2' : rank === 3 ? 'rank-3' : '';
        // 检查并列
        var isTie = false;
        sorted.forEach(function(o){ if(o !== sd && rankMap[o.tr] && rankMap[o.tr].rank === rank) isTie = true; });
        html += '<tr>' +
          '<td>' + groupName + '</td>' +
          '<td class="' + rankCls + '" style="font-size:14px;">第' + rank + '名' + (isTie ? '(并列)' : '') + '</td>' +
          '<td><b>' + sd.name + '</b></td>' +
          '<td>' + sd.score.toFixed(1) + '分</td>' +
          '<td>' + (ratio*100).toFixed(1) + '%</td>' +
          '<td>¥' + fmt(groupPool) + '</td>' +
          '<td style="color:#2e7d32;font-weight:bold;font-size:14px;">¥' + fmt(bonus) + '</td>' +
        '</tr>';
      });
      return html;
    }
    var html = '';
    html += renderRankRows(hairArr, '发型组', cr.hairRankMap, cr.hairScore, cr.hairPool);
    html += renderRankRows(washArr, '洗护组', cr.washRankMap, cr.washScore, cr.washPool);
    rankBody.innerHTML = html;
  }

  // ---------- 4. 第一名详细说明 ----------
  var explainBox = document.getElementById('first_place_explain');
  if(explainBox){
    var html = '';
    var hairArr = cr.staffData.filter(function(s){ return s.role === 'hair'; });
    var washArr = cr.staffData.filter(function(s){ return s.role === 'wash' || s.role === 'assist'; });
    var hairFirst = null, washFirst = null;
    hairArr.forEach(function(sd){ if(!hairFirst || (cr.hairRankMap[sd.tr] && cr.hairRankMap[sd.tr].rank < cr.hairRankMap[hairFirst.tr].rank)) hairFirst = sd; });
    washArr.forEach(function(sd){ if(!washFirst || (cr.washRankMap[sd.tr] && cr.washRankMap[sd.tr].rank < cr.washRankMap[washFirst.tr].rank)) washFirst = sd; });

    if(hairFirst){
      var maxRev = 0, maxCard = 0;
      hairArr.forEach(function(sd){ if(sd.base > maxRev) maxRev = sd.base; if(sd.cardCash > maxCard) maxCard = sd.cardCash; });
      var targetRate = v('p_zd_target');
      var labor = maxRev > 0 ? (hairFirst.base / maxRev) * 30 : 0;
      var cardS = maxCard > 0 ? (hairFirst.cardCash / maxCard) * 20 : 0;
      var zdRate = hairFirst.base > 0 ? hairFirst.zdPerf / hairFirst.base : 0;
      var zdS = targetRate > 0 ? Math.min(20, (zdRate * 100 / targetRate) * 20) : 20;
      var total = labor + cardS + zdS + hairFirst.repScore + hairFirst.manageScore;
      var rankInfo = cr.hairRankMap[hairFirst.tr];
      html += '<b style="color:#bf360c;font-size:14px;">🏆 发型组第一名:' + hairFirst.name + '</b>(总分 ' + total.toFixed(1) + ' 分,第 ' + (rankInfo?rankInfo.rank:'?') + ' 名)<br><br>';
      html += '📌 <b>他/她是怎么拿到第一名的?</b><br>';
      html += '① <b>劳动业绩分</b>:个人业绩 ¥' + fmt(hairFirst.base) + ' ÷ 组内最高业绩 ¥' + fmt(maxRev) + ' × 30 = <b>' + labor.toFixed(1) + ' 分</b><br>';
      html += '② <b>充卡业绩分</b>:个人充卡 ¥' + fmt(hairFirst.cardCash) + ' ÷ 组内最高充卡 ¥' + fmt(maxCard) + ' × 20 = <b>' + cardS.toFixed(1) + ' 分</b><br>';
      html += '③ <b>指定客占比分</b>:实际占比 ' + (zdRate*100).toFixed(1) + '% ÷ 目标 ' + targetRate + '% × 20 = <b>' + zdS.toFixed(1) + ' 分</b><br>';
      html += '④ <b>口碑分</b>:<b>' + hairFirst.repScore.toFixed(1) + ' 分</b>(手动填)<br>';
      html += '⑤ <b>管理分</b>:<b>' + hairFirst.manageScore.toFixed(1) + ' 分</b>(老板打分)<br>';
      html += '<b style="color:#bf360c;">总分 = ' + labor.toFixed(1) + ' + ' + cardS.toFixed(1) + ' + ' + zdS.toFixed(1) + ' + ' + hairFirst.repScore.toFixed(1) + ' + ' + hairFirst.manageScore.toFixed(1) + ' = ' + total.toFixed(1) + ' 分</b><br><br>';
      html += '💰 <b>他/她的分红怎么算?</b><br>';
      html += '发型组分红池 = <b>¥' + fmt(cr.hairPool) + '</b><br>';
      html += '组内总分合计 = <b>' + cr.hairScore.toFixed(1) + ' 分</b><br>';
      html += '个人分红 = ' + fmt(cr.hairPool) + ' × (' + total.toFixed(1) + ' ÷ ' + cr.hairScore.toFixed(1) + ') = <b style="color:#2e7d32;font-size:15px;">¥' + fmt(hairFirst.bonus) + '</b><br>';
    }

    if(washFirst){
      html += '<br><hr style="border:none;border-top:2px dashed #ccc;margin:15px 0;"><br>';
      var maxRev = 0, maxCard = 0;
      washArr.forEach(function(sd){ if(sd.base > maxRev) maxRev = sd.base; if(sd.cardCash > maxCard) maxCard = sd.cardCash; });
      var targetRate = v('p_zd_target');
      var labor = maxRev > 0 ? (washFirst.base / maxRev) * 30 : 0;
      var cardS = maxCard > 0 ? (washFirst.cardCash / maxCard) * 20 : 0;
      var zdRate = washFirst.base > 0 ? washFirst.zdPerf / washFirst.base : 0;
      var zdS = targetRate > 0 ? Math.min(20, (zdRate * 100 / targetRate) * 20) : 20;
      var total = labor + cardS + zdS + washFirst.repScore + washFirst.manageScore;
      var rankInfo = cr.washRankMap[washFirst.tr];
      html += '<b style="color:#1565c0;font-size:14px;">🏆 洗护组第一名(含助理):' + washFirst.name + '</b>(总分 ' + total.toFixed(1) + ' 分,第 ' + (rankInfo?rankInfo.rank:'?') + ' 名)<br><br>';
      html += '📌 <b>他/她是怎么拿到第一名的?</b><br>';
      html += '① <b>劳动业绩分</b>:个人业绩 ¥' + fmt(washFirst.base) + ' ÷ 组内最高业绩 ¥' + fmt(maxRev) + ' × 30 = <b>' + labor.toFixed(1) + ' 分</b><br>';
      html += '② <b>充卡业绩分</b>:个人充卡 ¥' + fmt(washFirst.cardCash) + ' ÷ 组内最高充卡 ¥' + fmt(maxCard) + ' × 20 = <b>' + cardS.toFixed(1) + ' 分</b><br>';
      html += '③ <b>指定客占比分</b>:实际占比 ' + (zdRate*100).toFixed(1) + '% ÷ 目标 ' + targetRate + '% × 20 = <b>' + zdS.toFixed(1) + ' 分</b><br>';
      html += '④ <b>口碑分</b>:<b>' + washFirst.repScore.toFixed(1) + ' 分</b><br>';
      html += '⑤ <b>管理分</b>:<b>' + washFirst.manageScore.toFixed(1) + ' 分</b><br>';
      html += '<b style="color:#1565c0;">总分 = ' + labor.toFixed(1) + ' + ' + cardS.toFixed(1) + ' + ' + zdS.toFixed(1) + ' + ' + washFirst.repScore.toFixed(1) + ' + ' + washFirst.manageScore.toFixed(1) + ' = ' + total.toFixed(1) + ' 分</b><br><br>';
      html += '💰 <b>他/她的分红怎么算?</b><br>';
      html += '洗护组分红池 = <b>¥' + fmt(cr.washPool) + '</b><br>';
      html += '组内总分合计 = <b>' + cr.washScore.toFixed(1) + ' 分</b><br>';
      html += '个人分红 = ' + fmt(cr.washPool) + ' × (' + total.toFixed(1) + ' ÷ ' + cr.washScore.toFixed(1) + ') = <b style="color:#2e7d32;font-size:15px;">¥' + fmt(washFirst.bonus) + '</b><br>';
    }

    if(!hairFirst && !washFirst) html = '暂无员工数据';
    explainBox.innerHTML = html;
  }
}

// ============ 健康区间 ============
function updateAdvice(){
  var cr = window._cr; if(!cr) return;
  var revenue = cr.revenue;
  var varRate = v('p_var_rate')/100;
  var rent = v('p_rent'), other = v('p_other'), subsidy = v('p_subsidy');
  var poolRate = v('p_pool_rate')/100;
  var targetMargin = v('p_target_margin')/100;
  var warnMargin = v('p_warn_margin')/100;
  var minComm = v('p_min_comm_rate')/100;
  var staffCount = cr.staffData.length;
  var fixedCost = rent + other + subsidy * staffCount;
  var denomB = poolRate < 0.999 ? 1 - poolRate : 1;
  var curCommRate = revenue > 0 ? cr.storeTotalComm/revenue : 0;
  var tiers = [30000,50000,80000,100000,150000,200000];
  if(revenue > 0 && tiers.indexOf(revenue) === -1){ tiers.push(revenue); tiers.sort(function(a,b){return a-b;}); }
  var html = '';
  tiers.forEach(function(R){
    if(R <= 0) return;
    var fixedRate = fixedCost/R;
    var beRate = 1 - varRate - fixedRate;
    var healthRate = beRate - targetMargin/denomB;
    var warnRate = beRate - warnMargin/denomB;
    var recLow = Math.max(healthRate*0.7, minComm);
    var recHigh = healthRate;
    var recMid = (recLow + recHigh)/2;
    var recStr = '', recCls = '';
    if(healthRate <= 0){ recStr = '无法达成'; recCls = 'hl-red'; recLow = 0; recMid = 0; }
    else if(recLow >= recHigh){ recStr = '≤' + (healthRate*100).toFixed(1) + '%'; recCls = 'hl-orange'; recLow = 0; }
    else { recStr = (recLow*100).toFixed(1) + '% ~ ' + (recHigh*100).toFixed(1) + '%'; recCls = 'hl-green'; }
    var profit = (R - R*varRate - fixedCost - R*curCommRate) * denomB;
    var marginAt = profit/R;
    var grade = '', gradeCls = '';
    if(healthRate <= 0){ grade = '🔴 该档位无解'; gradeCls = 'hl-red'; }
    else if(curCommRate > warnRate){ grade = '🔴 过高'; gradeCls = 'hl-red'; }
    else if(curCommRate > healthRate){ grade = '🟠 偏高'; gradeCls = 'hl-orange'; }
    else if(recLow < recHigh && curCommRate < recLow){ grade = '🟡 偏低'; gradeCls = 'hl-orange'; }
    else { grade = '🟢 理想'; gradeCls = 'hl-green'; }
    var isCur = Math.abs(R - revenue) < 1;
    var rowCls = isCur ? ' style="background:#fff9c4;"' : '';
    html += '<tr' + rowCls + '>' +
      '<td>'+fmt(R)+(isCur?' 👈当前':'')+'</td>' +
      '<td>'+(fixedRate*100).toFixed(1)+'%</td>' +
      '<td>'+(beRate>0?(beRate*100).toFixed(1)+'%':'已亏损')+'</td>' +
      '<td>'+(healthRate>0?(healthRate*100).toFixed(1)+'%':'无法达成')+'</td>' +
      '<td>'+(warnRate>0?(warnRate*100).toFixed(1)+'%':'无法达成')+'</td>' +
      '<td class="'+recCls+'">'+recStr+'</td>' +
      '<td class="'+recCls+'">'+(recMid>0?(recMid*100).toFixed(1)+'%':'-')+'</td>' +
      '<td>'+(curCommRate*100).toFixed(1)+'%</td>' +
      '<td>¥'+fmt(profit)+'</td>' +
      '<td>'+(marginAt*100).toFixed(1)+'%</td>' +
      '<td class="'+gradeCls+'">'+grade+'</td></tr>';
  });
  var b = document.getElementById('advice_body'); if(b) b.innerHTML = html;
}

function fillRecommend(){
  var cr = window._cr; if(!cr) return;
  var varRate = v('p_var_rate')/100;
  var rent = v('p_rent'), other = v('p_other'), subsidy = v('p_subsidy');
  var poolRate = v('p_pool_rate')/100;
  var targetMargin = v('p_target_margin')/100;
  var minComm = v('p_min_comm_rate')/100;
  var staffCount = cr.staffData.length;
  var fixedCost = rent + other + subsidy * staffCount;
  var R = cr.revenue;
  if(R <= 0){ alert('请先填写门店总业绩'); return; }
  var denomB = poolRate < 0.999 ? 1 - poolRate : 1;
  var healthRate = 1 - varRate - fixedCost/R - targetMargin/denomB;
  var recLow = Math.max(healthRate*0.7, minComm);
  var recMid = (recLow+healthRate)/2;
  var inp = document.getElementById('scale_target');
  if(inp){
    if(recMid > 0) inp.value = (recMid*100).toFixed(1);
    else if(healthRate > 0) inp.value = (healthRate*100).toFixed(1);
    else alert('当前业绩无法达成目标利润率');
  }
}

function scaleCommissions(){
  var cr = window._cr; if(!cr) return;
  var inp = document.getElementById('scale_target');
  if(!inp) return;
  var target = parseFloat(inp.value)/100;
  if(isNaN(target) || target <= 0){ alert('请填写目标总提成率'); return; }
  var revenue = cr.revenue;
  if(revenue <= 0){ alert('门店业绩为0'); return; }
  var nonItem = (cr.storeCardComm + cr.storeZdComm)/revenue;
  var targetItem = target - nonItem;
  if(targetItem <= 0){ alert('目标总提成率低于充卡+指定客提成率,无法仅靠缩放项目提成实现'); return; }
  var curItem = cr.storeItemComm/revenue;
  if(curItem <= 0){ alert('当前项目提成为0'); return; }
  var k = targetItem / curItem;
  if(k > 3 || k < 0.2){ if(!confirm('缩放系数 '+k.toFixed(2)+',幅度较大,确认吗?')) return; }
  var ids = [];
  ITEMS.forEach(function(kk){ ids.push('h_'+kk, 'w_'+kk, 'a_'+kk); });
  var changed = 0;
  ids.forEach(function(id){
    var el = document.getElementById(id);
    if(el){ var val = parseFloat(el.value)||0; if(val > 0){ el.value = (val*k).toFixed(2); changed++; } }
  });
  calc();
  alert('已调整 '+changed+' 个提成比例。目标总提成率:'+(target*100).toFixed(1)+'%');
}

// ============ 对账 ============
function updateReconcile(){
  var box = document.getElementById('reconcile_box'); if(!box) return;
  var cr = window._cr; if(!cr) return;
  var empItem=0, empCard=0, empZd=0, empTotal=0, empBonus=0, empSalary=0;
  cr.staffData.forEach(function(sd){ empItem += sd.itemComm; empCard += sd.cardComm; empZd += sd.zdComm; empTotal += sd.totalComm; empBonus += sd.bonus; empSalary += sd.salary; });
  var empSub = cr.totalSubsidy;
  var empCommRate = cr.revenue > 0 ? empTotal/cr.revenue*100 : 0;
  var finCommRate = cr.revenue > 0 ? cr.storeTotalComm/cr.revenue*100 : 0;
  var theorySalary = empTotal + empSub + empBonus;
  var tol = 0.01 * cr.staffData.length + 0.02;
  function row(name, emp, fin, isPct, note){
    var diff = emp - fin; var ok = Math.abs(diff) <= tol; var cls = ok ? 'hl-green' : 'hl-red';
    var e = isPct ? emp.toFixed(2)+'%' : '¥'+fmt(emp);
    var f = isPct ? fin.toFixed(2)+'%' : '¥'+fmt(fin);
    var d = (diff>=0?'+':'') + (isPct ? diff.toFixed(4)+'%' : '¥'+fmt(diff));
    return '<tr><td>'+name+'</td><td>'+e+'</td><td>'+f+'</td><td class="'+cls+'">'+d+'</td><td class="'+cls+'">'+(ok?'✓':'⚠️')+'</td><td style="font-size:11px;color:#666;text-align:left;">'+(note||'')+'</td></tr>';
  }
  var html = '<div class="tip"><b>对账口径:</b>员工表「总提成」不含补贴和分红;「实发工资」= 总提成+补贴+分红。</div>';
  html += '<table><thead><tr><th>对账项</th><th>员工表合计</th><th>财务表数值</th><th>差额</th><th>状态</th><th>说明</th></tr></thead><tbody>';
  html += row('项目提成', empItem, cr.storeItemComm, false, '各行项目提成加总');
  html += row('充卡提成', empCard, cr.storeCardComm, false, '各行充卡提成加总');
  html += row('指定客提成', empZd, cr.storeZdComm, false, '各行指定客提成加总');
  html += row('总提成', empTotal, cr.storeTotalComm, false, '= 三项之和');
  html += row('补贴合计', empSub, cr.totalSubsidy, false, '员工数 × 补贴');
  html += row('分红合计', empBonus, cr.pool, false, '各行分红加总(含助理)');
  html += row('实发工资合计', empSalary, theorySalary, false, '= 总提成+补贴+分红');
  html += row('全店提成率', empCommRate, finCommRate, true, '应与财务表一致');
  html += '</tbody></table>';
  var allOk = Math.abs(empTotal - cr.storeTotalComm) <= tol && Math.abs(empSalary - theorySalary) <= tol && Math.abs(empCommRate - finCommRate) <= 0.02 && Math.abs(empBonus - cr.pool) <= tol;
  html += '<div class="'+(allOk?'ok-box':'err-box')+'">' + (allOk ? '✅ 员工表与财务表完全对得上。' : '⚠️ 存在对不上项。') + '</div>';
  html += '<div class="tip"><b>📌 实发工资 vs 全店提成:</b>实发工资合计 = <b>¥'+fmt(empSalary)+'</b>,全店总提成 = <b>¥'+fmt(empTotal)+'</b>,差额 = <b>¥'+fmt(empSalary-empTotal)+'</b>(= 补贴 ¥'+fmt(empSub)+' + 分红 ¥'+fmt(empBonus)+')。<b>两者不等是正常的。</b></div>';
  box.innerHTML = html;
}

// ============ 个人提成点 ============
function renderPersonal(){
  var cr = window._cr; var body = document.getElementById('personal_body'); var sumBox = document.getElementById('personal_summary');
  if(!cr || !body) return;
  var simRevenue = v('sim_revenue'); var poolRate = v('sim_pool_rate')/100;
  var includeAssist = document.getElementById('sim_include_assist').checked;
  if(simRevenue <= 0){ body.innerHTML = '<tr><td colspan="14" style="color:#999;">请输入测算业绩</td></tr>'; if(sumBox) sumBox.innerHTML = ''; return; }
  var curRevenue = cr.revenue; var scale = curRevenue > 0 ? simRevenue/curRevenue : 0;
  var simBonus = getBonusAt(simRevenue);
  var H={},W={},A={};
  for(var i=0;i<ITEMS.length;i++){ var k = ITEMS[i]; var h = v('h_'+k), w = v('w_'+k), a = v('a_'+k); H[k] = h > 0 ? (h+simBonus)/100 : 0; W[k] = w > 0 ? (w+simBonus)/100 : 0; A[k] = a > 0 ? (a+simBonus)/100 : 0; }
  var cardPerfRate = v('p_card_perf_rate')/100; var cardCommRate = v('p_card_comm')/100; var zdCommRate = v('p_zd_comm')/100;
  var h_share = v('p_hair_share')/100; var w_share = v('p_wash_share')/100;
  var varRate = v('p_var_rate')/100; var rent = v('p_rent'), other = v('p_other'), subsidy = v('p_subsidy');
  var staff = [];
  cr.staffData.forEach(function(sd){
    var tr = sd.tr; var role = sd.role; var rateMap = role === 'hair' ? H : role === 'wash' ? W : A;
    var itemComm=0, base=0;
    for(var kk=0;kk<ITEMS.length;kk++){ var key = ITEMS[kk]; var inp = tr.querySelector('.v_'+key); var val = (inp ? parseFloat(inp.value)||0 : 0) * scale; itemComm += val * (rateMap[key] || 0); base += val; }
    var cardCash = (sd.cardCash || 0) * scale; var cardComm = cardCash * cardPerfRate * cardCommRate;
    var zdPerf = sd.zdPerf * scale; var zdComm = zdPerf * zdCommRate;
    staff.push({name:sd.name, role:role, base:base, itemComm:itemComm, cardComm:cardComm, zdComm:zdComm, totalComm:itemComm+cardComm+zdComm, score:sd.score});
  });
  var totalScoreH=0, totalScoreW=0; var storeComm = 0;
  staff.forEach(function(s){ storeComm += s.totalComm; if(s.role==='hair') totalScoreH += s.score; else if(s.role==='wash' || (s.role==='assist' && includeAssist)) totalScoreW += s.score; });
  var fixedCost = rent + other + subsidy * staff.length;
  var baseProfit = simRevenue - simRevenue*varRate - fixedCost - storeComm;
  var pool = baseProfit > 0 ? baseProfit*poolRate : 0;
  var hPool = pool*h_share, wPool = pool*w_share;
  var html = ''; var sumSalary=0, sumBase=0;
  staff.forEach(function(s){
    var ratio=0, bonus=0, poolName='';
    if(s.role==='hair'){ poolName='发型组'; if(totalScoreH>0){ ratio=s.score/totalScoreH; bonus=hPool*ratio; } }
    else if(s.role==='wash'){ poolName='洗护组'; if(totalScoreW>0){ ratio=s.score/totalScoreW; bonus=wPool*ratio; } }
    else { poolName = includeAssist?'洗护组(含助理)':'不分红'; if(includeAssist && totalScoreW>0){ ratio=s.score/totalScoreW; bonus=wPool*ratio; } }
    var salary = s.totalComm + subsidy + bonus;
    var rateOnBase = s.base > 0 ? salary/s.base*100 : 0; var rateOnRev = simRevenue > 0 ? salary/simRevenue*100 : 0;
    sumSalary += salary; sumBase += s.base;
    var rowCls = s.role==='hair' ? ' style="background:#fff8e1;"' : s.role==='wash' ? ' style="background:#e3f2fd;"' : ' style="background:#fce4ec;"';
    html += '<tr'+rowCls+'><td>'+s.name+'</td><td>'+ROLE_NAME[s.role]+'</td><td>¥'+fmt(s.base)+'</td><td>¥'+fmt(s.itemComm)+'</td><td>¥'+fmt(s.cardComm)+'</td><td>¥'+fmt(s.zdComm)+'</td><td>¥'+fmt(s.totalComm)+'</td><td>'+s.score.toFixed(1)+'</td><td>'+poolName+'</td><td>¥'+fmt(bonus)+'</td><td>¥'+fmt(subsidy)+'</td><td style="font-weight:bold;color:#004d40;">¥'+fmt(salary)+'</td><td class="hl-orange">'+rateOnBase.toFixed(2)+'%</td><td>'+rateOnRev.toFixed(2)+'%</td></tr>';
  });
  body.innerHTML = html;
  var sHtml = '<div class="tip"><b>📊 测算汇总(门店业绩 ¥'+fmt(simRevenue)+',分红池 '+(poolRate*100).toFixed(0)+'%):</b><br>门店总提成 ¥'+fmt(storeComm)+'(提成率 '+(storeComm/simRevenue*100).toFixed(2)+'%)| 基础纯利 ¥'+fmt(baseProfit)+' | 分红池 ¥'+fmt(pool)+'<br>员工实发工资合计 ¥'+fmt(sumSalary)+' | 人均 ¥'+fmt(sumSalary/staff.length)+'</div>';
  if(sumBox) sumBox.innerHTML = sHtml;
}

// ============ 宣讲话术 ============
function renderSpeech(){
  var cr = window._cr; if(!cr) return;
  var box = document.getElementById('speech_box'); if(!box) return;
  var revenue = v('actual_revenue'); var tier = getTierAt(revenue); var bonusPct = tier ? tier.bonus : 0;
  var avgSalary = cr.staffData.length > 0 ? cr.totalSalary/cr.staffData.length : 0;
  var sortedTiers = TIERS.slice().sort(function(a,b){return a.threshold-b.threshold;});
  function simAvg(R){
    var scale = cr.revenue > 0 ? R/cr.revenue : 0; var b = getBonusAt(R);
    var H={},W={},A={};
    for(var i=0;i<ITEMS.length;i++){ var k = ITEMS[i]; H[k] = v('h_'+k) > 0 ? (v('h_'+k)+b)/100 : 0; W[k] = v('w_'+k) > 0 ? (v('w_'+k)+b)/100 : 0; A[k] = v('a_'+k) > 0 ? (v('a_'+k)+b)/100 : 0; }
    var totalComm=0;
    cr.staffData.forEach(function(sd){
      var tr = sd.tr; var role = sd.role; var rateMap = role==='hair'?H:role==='wash'?W:A;
      for(var kk=0;kk<ITEMS.length;kk++){ var key=ITEMS[kk]; var inp = tr.querySelector('.v_'+key); var val = (inp ? parseFloat(inp.value)||0 : 0) * scale; totalComm += val * (rateMap[key]||0); }
      var cardCash = (sd.cardCash||0)*scale; totalComm += cardCash * v('p_card_perf_rate')/100 * v('p_card_comm')/100;
      totalComm += sd.zdPerf * scale * v('p_zd_comm')/100;
    });
    var fixedCost = v('p_rent')+v('p_other')+v('p_subsidy')*cr.staffData.length;
    var baseProfit = R - R*v('p_var_rate')/100 - fixedCost - totalComm;
    var pool = baseProfit>0 ? baseProfit*v('p_pool_rate')/100 : 0;
    var totalSalary = totalComm + v('p_subsidy')*cr.staffData.length + pool;
    return {avg: cr.staffData.length>0 ? totalSalary/cr.staffData.length : 0, profit: baseProfit-pool};
  }
  var r3 = simAvg(30000); var r8 = simAvg(80000); var r12 = simAvg(120000);
  var html = '<div class="speech-quote">"我们的薪酬逻辑:<b>你的工资不是老板给的,是客人给的。</b><br><br>门店做到 <b>3万</b>,员工平均工资约 <b>¥'+fmt(r3.avg)+'</b>;<br>做到 <b>8万</b>,提点加成 <b>+'+getBonusAt(80000)+'%</b>,平均工资能到 <b>¥'+fmt(r8.avg)+'</b>'+(r3.avg>0?'(+'+( (r8.avg-r3.avg)/r3.avg*100 ).toFixed(0)+'%)':'')+';<br>做到 <b>12万</b>,加成 <b>+'+getBonusAt(120000)+'%</b>,平均工资 <b>¥'+fmt(r12.avg)+'</b>'+(r3.avg>0?'(+'+( (r12.avg-r3.avg)/r3.avg*100 ).toFixed(0)+'%)':'')+'。<br><br>门店纯利也从 <b>¥'+fmt(r3.profit)+'</b> 涨到 <b>¥'+fmt(r12.profit)+'</b>。<br><br>你想拿高工资,不是跟我谈底薪,是跟我一起把业绩做起来。<b>蛋糕做大,你分得更多,我也赚得更多。</b>"</div>';
  html += '<div class="tip"><b>📌 当前状态:</b>业绩 ¥'+fmt(revenue)+',档位 '+(tier?tier.name:'未达')+',加成 +'+bonusPct+'%,平均工资 ¥'+fmt(avgSalary)+'。<br><b>🎯 你的档位设置:</b>'+sortedTiers.map(function(t){ return t.name+'('+fmt(t.threshold)+'元起)'+'+'+t.bonus+'%'; }).join(' | ')+'</div>';
  box.innerHTML = html;
}

// ============ 校验 ============
function validate(){
  var box = document.getElementById('validate_box'); if(!box) return;
  var cr = window._cr; if(!cr) return;
  var issues = [], passes = [];
  var ratioSum = 0; ITEMS.forEach(function(k){ ratioSum += v('r_'+k); });
  if(Math.abs(ratioSum - 100) > 0.01) issues.push('项目占比合计 = '+ratioSum.toFixed(1)+'%,应等于 100%'); else passes.push('项目占比合计 = 100%');
  var shareSum = v('p_hair_share') + v('p_wash_share');
  if(Math.abs(shareSum - 100) > 0.01) issues.push('发型+洗护分配比 = '+shareSum.toFixed(1)+'%,应等于100%'); else passes.push('发型+洗护分配比 = 100%');
  var scoreLimits = {labor:30, card:20, zd:20, rep:10, manage:20};
  var scoreNames = {labor:'劳动分', card:'充卡分', zd:'指定分', rep:'口碑分', manage:'管理分'};
  cr.staffData.forEach(function(sd){
    var tr = sd.tr; var name = sd.name;
    for(var sk in scoreLimits){ var el = tr.querySelector('.v_s_'+sk); var sv = el ? parseFloat(el.value)||0 : 0; if(sv > scoreLimits[sk]) issues.push(name+' 的 '+scoreNames[sk]+' = '+sv+'(上限'+scoreLimits[sk]+')'); }
  });
  var bonusPaid = cr.staffData.reduce(function(a,sd){return a+sd.bonus;},0);
  if(Math.abs(cr.pool - bonusPaid) > 0.5) issues.push('理论分红池 ¥'+fmt(cr.pool)+' 与实发分红 ¥'+fmt(bonusPaid)+' 不一致');
  if(cr.storeCashCash > 0.01) issues.push('散客现金 ¥'+fmt(cr.storeCashCash)+',目前未计入营业收入');
  if(cr.commRate > 60) issues.push('总店提成率 '+cr.commRate.toFixed(2)+'% 超过60%,请检查');
  var rCard = v('r_card'), rZd = v('r_zd');
  if(rCard > 100) issues.push('充卡业绩占比 '+rCard+'% 超过100%');
  if(rZd > 100) issues.push('指定客业绩占比 '+rZd+'% 超过100%');
  if(rCard + rZd > 100) issues.push('充卡占比('+rCard+'%) + 指定客占比('+rZd+'%) 超过100%');
  var html = '';
  if(issues.length === 0) html = '<div class="ok-box">✅ 所有校验通过,当前数据口径一致,计算结果可信。</div>';
  else html = '<div class="err-box">⚠️ 发现 '+issues.length+' 个问题:<br>' + issues.map(function(i){return '• '+i;}).join('<br>') + '</div>';
  if(passes.length > 0) html += '<div class="tip" style="background:#e8f5e9;border-left-color:#43a047;color:#2e7d32;"><b>已通过项:</b>' + passes.join(' | ') + '</div>';
  html += '<div class="tip"><b>📌 校验项:</b>项目占比=100% | 分配比=100% | 考核分是否超限 | 分红池 vs 实发分红 | 散客现金 | 总提成率≤60% | 充卡/指定客占比合理。<br><b>💡 前提提醒:</b>员工各项目业绩加总应 ≈ 门店总业绩。</div>';
  box.innerHTML = html;
}

// ============ 初始化 ============
function init(){
  var body = document.getElementById('staff_body'); if(!body) return;
  if(body.children.length === 0){
    var defaults = [
      ['hair','发型师A',[30,20,20,10,20]], ['hair','发型师B',[25,10,20,8,18]], ['hair','发型师C',[20,0,15,5,20]],
      ['wash','洗护师A',[30,20,20,10,20]], ['wash','洗护师B',[25,10,20,8,18]], ['wash','洗护师C',[20,0,15,5,20]],
      ['assist','助理1',[10,0,0,5,10]]
    ];
    defaults.forEach(function(d){
      var tr = createRow(d[0], d[1]); body.appendChild(tr);
      var scores = d[2];
      ['labor','card','zd','rep','manage'].forEach(function(k, i){ var inp = tr.querySelector('.v_s_'+k); if(inp) inp.value = scores[i]; });
    });
  }
  autoAllocate();
}

init();

function downloadPage(){
  document.querySelectorAll('input').forEach(function(inp){ inp.setAttribute('value', inp.value); });
  var td = document.getElementById('tiers-data'); if(td) td.textContent = JSON.stringify(TIERS);
  var html = "<!DOCTYPE html>\n" + document.documentElement.outerHTML;
  var blob = new Blob([html], {type:'text/html;charset=utf-8'});
  var d = new Date();
  var stamp = d.getFullYear()+String(d.getMonth()+1).padStart(2,'0')+String(d.getDate()).padStart(2,'0');
  var a = document.createElement('a'); a.href = URL.createObjectURL(blob);
  a.download = '门店薪酬核算_'+stamp+'.html';
  document.body.appendChild(a); a.click(); document.body.removeChild(a);
  setTimeout(function(){ URL.revokeObjectURL(a.href); }, 1000);
}
</script>
</body>
</html>