/**
 * @name 小羚羚小程序
 */

import React, { useState, useMemo, useCallback, useEffect, useRef } from 'react';
import { Modal } from 'antd';
import {
  AnnotationViewer,
  type AnnotationDirectoryRouteNode,
  type AnnotationSourceDocument,
  type AnnotationViewerOptions,
} from '@axhub/annotation';
import annotationSourceDocument from './annotation-source.json';
import { defineHashPageRoute, useHashPage } from '../../common/useHashPage';
import './style.css';

const XLL_GREEN = '#7AB929';
const XLL_GREEN_DEEP = '#6AA322';
const XLL_GREEN_SOFT = 'rgba(122, 185, 41, 0.14)';
const COLOR_TEXT = '#1D2129';
const COLOR_TEXT_SEC = '#4E5969';
const COLOR_MUTED = '#86909C';
const COLOR_LINE = '#E5E6EB';
const COLOR_BG = '#FFFFFF';
const COLOR_PAGE = '#F2F3F5';
const COLOR_DANGER = '#F53F3F';
const COLOR_WARN = '#FF7D00';
const COLOR_SUCCESS = '#00B42A';
const FONT_FAMILY = '-apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", STHeiti, sans-serif';

const mpToastListeners = new Set();
const mpToastStore = { items: [] };

function emitMpToastChange() {
  mpToastListeners.forEach((fn) => fn());
}

const mpToast = {
  dismiss(id) {
    mpToastStore.items = mpToastStore.items.filter((t) => t.id !== id);
    emitMpToastChange();
  },
  push(type, content, duration = 2400) {
    const id = `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
    mpToastStore.items = [...mpToastStore.items, { id, type, content }];
    emitMpToastChange();
    if (duration > 0) {
      window.setTimeout(() => mpToast.dismiss(id), duration);
    }
    return id;
  },
  success: (content) => mpToast.push('success', content),
  warning: (content) => mpToast.push('warning', content),
  info: (content) => mpToast.push('info', content),
  error: (content) => mpToast.push('error', content),
  loading: (content) => {
    const id = mpToast.push('loading', content, 0);
    return () => mpToast.dismiss(id);
  },
};

const MOCK_USER = '张明辉';
const formatMoneySymbol = (val) => {
  const n = parseFloat(val);
  if (Number.isNaN(n)) return '—';
  return `￥${n.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
};
const formatMoney = formatMoneySymbol;
const formatYuan = formatMoneySymbol;

const MAIN_TABS = [
  { key: 'todo', label: '工作台', navLabel: '待办' },
  { key: 'business', label: '业务', navLabel: '业务' },
  { key: 'map', label: '地图', navLabel: '地图' },
  { key: 'mine', label: '我的', navLabel: '我的' },
];

const XLL_PAGE_ROUTE = defineHashPageRoute([
  { id: 'todo', title: '工作台' },
  { id: 'business', title: '业务' },
  { id: 'map', title: '地图' },
  { id: 'mine', title: '我的' },
  { id: 'audit', title: '审批中心' },
  { id: 'inspection', title: '年审' },
  { id: 'vehicle', title: '车辆管理' },
  { id: 'delivery', title: '交车' },
  { id: 'third-return', title: '三方退车' },
  { id: 'replace', title: '替换车' },
], { defaultPageId: 'todo' });

const MAIN_TAB_PAGE_IDS = new Set(['todo', 'business', 'map', 'mine']);
const MODULE_PAGE_IDS = new Set(['audit', 'inspection', 'vehicle', 'delivery', 'third-return', 'replace']);

const BIZ_ENTRY_PAGE_MAP = {
  audit: 'audit',
  inspection: 'inspection',
  vehicle: 'vehicle',
  delivery: 'delivery',
  thirdReturn: 'third-return',
  replace: 'replace',
};

const MODULE_NAV_TITLES = {
  audit: '审批中心',
  inspection: '年审',
  vehicle: '车辆管理',
  delivery: '交车',
  'third-return': '三方退车',
  replace: '替换车',
};

const ANNOTATION_ROUTE_TO_PAGE = {
  todo: 'todo',
  business: 'business',
  map: 'map',
  mine: 'mine',
  audit: 'audit',
  delivery: 'delivery',
  inspection: 'inspection',
  vehicle: 'vehicle',
  thirdReturn: 'third-return',
  'third-return': 'third-return',
  replace: 'replace',
  'audit-return': 'audit-return',
};

const TASK_THEME = {
  delivery: { accent: XLL_GREEN, soft: XLL_GREEN_SOFT, label: '交车' },
  return: { accent: '#2563EB', soft: 'rgba(37, 99, 235, 0.12)', label: '还车' },
  thirdReturn: { accent: '#EA580C', soft: 'rgba(234, 88, 12, 0.12)', label: '三方退车' },
  inspection: { accent: COLOR_WARN, soft: 'rgba(255, 125, 0, 0.12)', label: '年审' },
  transfer: { accent: '#8B5CF6', soft: 'rgba(139, 92, 246, 0.12)', label: '调拨' },
  move: { accent: '#14B8A6', soft: 'rgba(20, 184, 166, 0.12)', label: '异动' },
  audit: { accent: '#9333EA', soft: 'rgba(147, 51, 234, 0.12)', label: '审批' },
};

const TODO_ACTION_META = {
  approve: { label: '需审批', tone: 'purple' },
  fill: { label: '需填报', tone: 'blue' },
  initiate: { label: '需发起', tone: 'orange' },
  execute: { label: '需执行', tone: 'green' },
  follow: { label: '需跟进', tone: 'slate' },
  notify: { label: '仅知会', tone: 'gray' },
};

const TODO_RISK_META = {
  high: { label: '高', color: '#F53F3F', bg: 'rgba(245, 63, 63, 0.1)' },
  medium: { label: '中', color: '#FF7D00', bg: 'rgba(255, 125, 0, 0.12)' },
  low: { label: '低', color: '#00B42A', bg: 'rgba(0, 180, 42, 0.1)' },
  none: { label: '无', color: '#86909C', bg: 'rgba(134, 144, 156, 0.12)' },
};

const TODO_ZONES = [
  { key: 'today', label: '今日必办' },
  { key: 'handle', label: '待我处理' },
  { key: 'follow', label: '待我跟进' },
  { key: 'notify', label: '仅知会' },
];

const TODO_TASKS = [
  {
    id: 't6', type: 'audit', zone: 'today', actionType: 'approve', riskLevel: 'high',
    title: '还车应结款 · 财务终审',
    riskHint: '高金额应退 ¥4,072.50',
    nextOwner: '赵敏',
    nextAction: '执行退款（2 工作日内）',
    slaRemain: '剩余 6h',
    cta: '去审批',
    targetPage: 'audit',
    fields: [
      { label: '车牌号', value: '粤B58888F' },
      { label: '应退金额', value: '¥4,072.50' },
      { label: '发起人', value: '陈高伟' },
    ],
  },
  {
    id: 't1', type: 'delivery', zone: 'today', actionType: 'execute', riskLevel: 'medium',
    title: '交车任务（3辆）',
    riskHint: '交车窗口 6h 内',
    nextOwner: '客户',
    nextAction: '签署交车单',
    slaRemain: '剩余 6h',
    cta: '去办理',
    fields: [
      { label: '项目名称', value: '嘉兴氢能示范项目' },
      { label: '客户名称', value: '嘉兴某某物流有限公司' },
      { label: '交车地点', value: '浙江省嘉兴市南湖区xx路xx号' },
      { label: '交车时间', value: '2026-06-05 09:30' },
    ],
  },
  {
    id: 't3', type: 'inspection', badge: 4, zone: 'handle', actionType: 'execute', riskLevel: 'high',
    title: '年审任务',
    riskHint: '证照已到期，阻塞运营',
    nextOwner: '张明辉',
    nextAction: '完成年审办理',
    slaRemain: '已超期 10 天',
    cta: '去办理',
    fields: [
      { label: '车牌号', value: '粤B58888F' },
      { label: '年审/等评时间', value: '2026-05-28（已到期）', warn: true },
    ],
  },
  {
    id: 't4', type: 'transfer', badge: 2, zone: 'handle', actionType: 'approve', riskLevel: 'medium',
    title: '张三发起的调拨申请',
    riskHint: '40 台车跨区调拨',
    nextOwner: '运维中心',
    nextAction: '安排运输与接车',
    slaRemain: '剩余 1 天',
    cta: '去审批',
    fields: [
      { label: '调拨日期', value: '2026-06-01' },
      { label: '出发区域', value: '广东省深圳市' },
      { label: '接收区域', value: '浙江省杭州市' },
      { label: '车辆数', value: '40辆' },
    ],
  },
  {
    id: 't5', type: 'move', badge: 3, zone: 'handle', actionType: 'approve', riskLevel: 'low',
    title: '异动申请（粤A08875F）',
    riskHint: '保养异动，无合规异常',
    nextOwner: '运维一部',
    nextAction: '现场确认异动结束',
    slaRemain: '剩余 2 天',
    cta: '去审批',
    fields: [
      { label: '异动类型', value: '保养' },
      { label: '目的地', value: '嘉兴xx检测站' },
      { label: '计划时间', value: '2026-06-02 08:00' },
    ],
  },
  {
    id: 't2', type: 'return', zone: 'handle', actionType: 'execute', riskLevel: 'low',
    title: '还车任务（粤AGP5368）',
    riskHint: '还车验车待完成',
    nextOwner: '运维组',
    nextAction: '完成验车与结算发起',
    slaRemain: '剩余 3 天',
    cta: '去办理',
    fields: [
      { label: '项目名称', value: '嘉兴腾4.5T租赁' },
      { label: '客户名称', value: '嘉兴某某物流有限公司' },
      { label: '还车时间', value: '2026-06-03 16:20' },
    ],
  },
  {
    id: 't7', type: 'audit', zone: 'follow', actionType: 'initiate', riskLevel: 'medium',
    title: '还车应结款 · 待发起主流程',
    riskHint: '四部门已全部通过，待你发起',
    nextOwner: '财务审核',
    nextAction: '主流程审批',
    slaRemain: '建议今日发起',
    cta: '去发起',
    targetPage: 'audit',
    fields: [
      { label: '车牌号', value: '苏E12345F' },
      { label: '当前进度', value: '四部门子流程已通过' },
      { label: '阻塞说明', value: '主流程未发起，退款未启动' },
    ],
  },
  {
    id: 't8', type: 'audit', zone: 'notify', actionType: 'notify', riskLevel: 'none',
    title: '租赁账单 · 抄送知会',
    riskHint: '无需操作，知悉即可',
    nextOwner: '—',
    nextAction: '—',
    slaRemain: '—',
    cta: '查看',
    targetPage: 'audit',
    fields: [
      { label: '客户名称', value: '嘉兴某某物流有限公司' },
      { label: '账单周期', value: '2026年6月' },
      { label: '当前节点', value: '业管主管审批中（李晓彤）' },
    ],
  },
];

const todoActionableCount = (tasks) => tasks.filter((t) => ['approve', 'fill', 'initiate', 'execute'].includes(t.actionType)).length;
const todoTasksByZone = (tasks, zoneKey) => tasks.filter((t) => t.zone === zoneKey);

const BUSINESS_SECTIONS = [
  {
    title: '运维管理',
    items: [
      { key: 'vehicle', label: '车辆管理', badge: 0 },
      { key: 'prepare', label: '备车', badge: 0 },
      { key: 'delivery', label: '交车', badge: 99 },
      { key: 'return', label: '还车', badge: 99 },
      { key: 'thirdReturn', label: '三方退车', badge: 99 },
      { key: 'replace', label: '替换车', badge: 99 },
      { key: 'move', label: '异动', badge: 0 },
      { key: 'transfer', label: '调拨', badge: 99 },
      { key: 'inspection', label: '年审', badge: 99 },
      { key: 'fault', label: '故障', badge: 0 },
      { key: 'training', label: '司机安全培训', badge: 0 },
    ],
  },
  {
    title: '审批管理',
    items: [{ key: 'audit', label: '审批中心', badge: 99 }],
  },
  {
    title: '数据可视化',
    items: [
      { key: 'stat-vehicle', label: '车辆统计', badge: 0 },
      { key: 'stat-h2-fee', label: '氢费统计', badge: 0 },
      { key: 'stat-h2-qty', label: '氢量汇总', badge: 0 },
      { key: 'stat-electric', label: '电量汇总', badge: 0 },
      { key: 'mileage-query', label: '里程查询', badge: 0 },
      { key: 'mileage-assess', label: '里程考核', badge: 0 },
    ],
  },
];

const PAGE_STYLE = `
.xll-root { height:100dvh; max-height:100dvh; overflow:hidden; background:linear-gradient(165deg,#e8ebef 0%,${COLOR_PAGE} 40%); display:flex; justify-content:center; align-items:center; padding:16px 12px; box-sizing:border-box; font-family:${FONT_FAMILY}; -webkit-font-smoothing:antialiased; }
.xll-phone { width:100%; max-width:390px; height:min(844px, calc(100dvh - 32px)); max-height:calc(100dvh - 32px); background:${COLOR_PAGE}; border-radius:28px; overflow:hidden; box-shadow:0 24px 48px rgba(15,23,42,.14), 0 0 0 1px rgba(15,23,42,.05); display:flex; flex-direction:column; position:relative; }
.xll-mp-toast-stack { position:absolute; left:14px; right:14px; top:58px; z-index:300; display:flex; flex-direction:column; gap:8px; pointer-events:none; }
.xll-mp-toast { padding:10px 14px; border-radius:10px; font-size:14px; line-height:1.45; color:#fff; box-shadow:0 4px 16px rgba(15,23,42,.18); animation:xll-mp-toast-in .2s ease; }
@keyframes xll-mp-toast-in { from { opacity:0; transform:translateY(-8px); } to { opacity:1; transform:translateY(0); } }
.xll-mp-toast--success { background:rgba(0,180,42,.94); }
.xll-mp-toast--warning { background:rgba(255,125,0,.94); }
.xll-mp-toast--info { background:rgba(29,33,41,.88); }
.xll-mp-toast--error { background:rgba(245,63,63,.94); }
.xll-mp-toast--loading { background:rgba(29,33,41,.88); }
.xll-chrome { flex-shrink:0; background:${COLOR_BG}; }
.xll-status { height:44px; padding:14px 20px 0; display:flex; align-items:center; justify-content:space-between; box-sizing:border-box; }
.xll-status-time { font-size:15px; font-weight:600; color:${COLOR_TEXT}; letter-spacing:-0.02em; }
.xll-status-icons { display:flex; align-items:center; gap:6px; color:${COLOR_TEXT}; }
.xll-navbar { height:48px; display:flex; align-items:center; padding:0 4px 0 8px; border-bottom:1px solid rgba(0,0,0,.05); position:relative; background:${COLOR_BG}; }
.xll-nav-left { display:flex; align-items:center; gap:4px; min-width:72px; z-index:2; }
.xll-nav-bell { position:relative; width:44px; height:44px; border:none; background:transparent; cursor:pointer; display:flex; align-items:center; justify-content:center; color:${COLOR_TEXT}; border-radius:10px; touch-action:manipulation; transition:background 0.15s ease; }
.xll-nav-bell:active { background:rgba(0,0,0,.05); }
.xll-nav-bell:focus-visible { outline:2px solid ${XLL_GREEN}; outline-offset:2px; }
.xll-nav-badge { position:absolute; top:6px; right:4px; min-width:18px; height:18px; padding:0 5px; border-radius:999px; background:${XLL_GREEN}; color:#fff; font-size:10px; font-weight:700; display:flex; align-items:center; justify-content:center; line-height:1; font-variant-numeric:tabular-nums; }
.xll-nav-title { position:absolute; left:50%; transform:translateX(-50%); font-size:17px; font-weight:700; color:${COLOR_TEXT}; max-width:42%; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.xll-nav-right { margin-left:auto; display:flex; align-items:center; gap:6px; z-index:2; }
.xll-prd-link { border:none; background:transparent; color:${XLL_GREEN_DEEP}; font-size:13px; font-weight:600; padding:8px 4px; min-height:44px; cursor:pointer; white-space:nowrap; touch-action:manipulation; border-radius:8px; transition:background 0.15s ease; }
.xll-prd-link:active { background:${XLL_GREEN_SOFT}; }
.xll-prd-link:focus-visible { outline:2px solid ${XLL_GREEN}; outline-offset:2px; }
.xll-back { width:44px; height:44px; border:none; background:transparent; cursor:pointer; display:flex; align-items:center; justify-content:center; color:${COLOR_TEXT}; border-radius:10px; touch-action:manipulation; transition:background 0.15s ease; }
.xll-back:active { background:rgba(0,0,0,.05); }
.xll-back:focus-visible { outline:2px solid ${XLL_GREEN}; outline-offset:2px; }
.xll-capsule { display:flex; align-items:center; height:32px; border-radius:16px; border:.5px solid rgba(0,0,0,.12); background:rgba(255,255,255,.92); overflow:hidden; }
.xll-capsule-btn { width:44px; height:32px; border:none; background:transparent; font-size:16px; cursor:pointer; color:${COLOR_TEXT}; touch-action:manipulation; }
.xll-capsule-divider { width:1px; height:18px; background:rgba(0,0,0,.12); flex-shrink:0; }
.xll-body { flex:1; min-height:0; overflow-y:auto; overflow-x:hidden; -webkit-overflow-scrolling:touch; overscroll-behavior:contain; padding-bottom:16px; }
.xll-body--login { overflow:hidden; display:flex; flex-direction:column; padding-bottom:0; }
.xll-body--map { padding-bottom:12px; }
.xll-body--module { padding:0; overflow:hidden; display:flex; flex-direction:column; flex:1; min-height:0; }
.xll-mod-root { flex:1; min-height:0; display:flex; flex-direction:column; overflow:hidden; background:${COLOR_PAGE}; position:relative; }
.xll-mod-tabs { display:flex; gap:6px; padding:10px 14px 8px; background:${COLOR_BG}; flex-shrink:0; overflow-x:auto; scrollbar-width:none; }
.xll-mod-tabs::-webkit-scrollbar { display:none; }
.xll-mod-tab { flex:1 0 auto; min-width:72px; min-height:44px; padding:8px 10px; border:none; border-radius:10px; background:${COLOR_PAGE}; color:${COLOR_MUTED}; font-size:13px; font-weight:500; cursor:pointer; touch-action:manipulation; }
.xll-mod-tab.active { background:${COLOR_BG}; color:${XLL_GREEN_DEEP}; font-weight:700; box-shadow:0 2px 8px rgba(15,23,42,.08); }
.xll-mod-tab-count { display:block; font-size:11px; font-weight:600; margin-top:2px; font-variant-numeric:tabular-nums; }
.xll-mod-toolbar { padding:0 14px 10px; flex-shrink:0; background:${COLOR_BG}; border-bottom:1px solid ${COLOR_LINE}; }
.xll-mod-search { display:flex; align-items:center; gap:8px; min-height:44px; padding:0 12px; background:${COLOR_PAGE}; border-radius:12px; border:1px solid transparent; }
.xll-mod-search:focus-within { border-color:rgba(122,185,41,.45); box-shadow:0 0 0 3px ${XLL_GREEN_SOFT}; background:${COLOR_BG}; }
.xll-mod-search input { flex:1; border:none; background:transparent; font-size:15px; outline:none; min-width:0; color:${COLOR_TEXT}; }
.xll-mod-chips { display:flex; gap:8px; margin-top:10px; overflow-x:auto; padding-bottom:2px; scrollbar-width:none; }
.xll-mod-chips::-webkit-scrollbar { display:none; }
.xll-mod-chip { flex-shrink:0; min-height:36px; padding:0 14px; border:1px solid ${COLOR_LINE}; background:${COLOR_BG}; border-radius:999px; font-size:13px; color:${COLOR_TEXT_SEC}; cursor:pointer; white-space:nowrap; touch-action:manipulation; }
.xll-mod-chip.active { border-color:${XLL_GREEN}; color:${XLL_GREEN_DEEP}; background:${XLL_GREEN_SOFT}; font-weight:600; }
.xll-mod-list-head { display:flex; justify-content:space-between; padding:12px 16px 4px; font-size:13px; color:${COLOR_TEXT_SEC}; flex-shrink:0; }
.xll-mod-list { flex:1; min-height:0; overflow-y:auto; padding:4px 14px 20px; -webkit-overflow-scrolling:touch; }
.xll-mod-card { position:relative; background:${COLOR_BG}; border-radius:14px; padding:14px 14px 12px 16px; margin-bottom:12px; box-shadow:0 2px 8px rgba(15,23,42,.05); border:1px solid rgba(0,0,0,.04); cursor:pointer; touch-action:manipulation; }
.xll-mod-card::before { content:''; position:absolute; left:12px; right:12px; top:0; height:2px; border-radius:2px 2px 0 0; background:var(--mod-accent, ${XLL_GREEN}); }
.xll-mod-card:focus-visible { outline:2px solid ${XLL_GREEN}; outline-offset:2px; }
.xll-mod-card-head { display:flex; justify-content:space-between; align-items:center; gap:10px; margin-bottom:8px; }
.xll-mod-card-head-tags { display:flex; align-items:center; gap:6px; flex-wrap:wrap; min-width:0; }
.xll-mod-card-type { font-size:11px; font-weight:600; color:var(--mod-accent, ${XLL_GREEN}); background:var(--mod-soft, ${XLL_GREEN_SOFT}); padding:2px 8px; border-radius:999px; }
.xll-mod-card-cc-tag { font-size:11px; font-weight:600; color:#64748B; background:rgba(100,116,139,.12); padding:2px 8px; border-radius:999px; flex-shrink:0; }
.xll-mod-card-status { display:inline-flex; align-items:center; justify-content:center; font-size:11px; font-weight:600; line-height:1; padding:4px 8px; border-radius:999px; box-sizing:border-box; flex-shrink:0; }
.xll-mod-card-status.pending { color:${COLOR_WARN}; background:rgba(255,125,0,.1); }
.xll-mod-card-status.with-approvers { max-width:58%; text-align:right; line-height:1.35; white-space:normal; }
.xll-mod-card-status.ok { color:${COLOR_SUCCESS}; background:rgba(0,180,42,.1); }
.xll-mod-card-status.reject { color:${COLOR_DANGER}; background:rgba(245,63,63,.1); }
.xll-mod-card-status.info { color:#2563EB; background:rgba(37,99,235,.1); }
.xll-mod-card-title { font-size:15px; font-weight:700; color:${COLOR_TEXT}; margin-bottom:4px; }
.xll-mod-card-sub { font-size:13px; color:${COLOR_TEXT_SEC}; line-height:1.5; margin-bottom:8px; }
.xll-mod-card-period { font-size:12px; color:${COLOR_TEXT_SEC}; line-height:1.45; margin-bottom:8px; display:flex; flex-wrap:wrap; align-items:center; gap:6px; }
.xll-mod-card-period-tag { font-size:11px; font-weight:700; color:#0EA5E9; background:rgba(14,165,233,.12); padding:2px 8px; border-radius:999px; flex-shrink:0; }
.xll-mod-card-vehicles { margin-bottom:8px; }
.xll-mod-card-vehicle-line { font-size:13px; color:${COLOR_TEXT_SEC}; line-height:1.5; }
.xll-mod-meta { display:grid; grid-template-columns:1fr 1fr; gap:6px 10px; font-size:13px; }
.xll-mod-meta-label { color:${COLOR_TEXT_SEC}; }
.xll-mod-meta-val { color:${COLOR_TEXT_SEC}; font-weight:500; }
.xll-mod-card-foot { display:flex; justify-content:space-between; align-items:center; margin-top:10px; padding-top:10px; border-top:1px solid ${COLOR_LINE}; }
.xll-mod-card-btn { min-height:44px; padding:0 14px; border-radius:10px; border:1px solid rgba(122,185,41,.35); background:${XLL_GREEN_SOFT}; color:${XLL_GREEN}; font-size:13px; font-weight:600; cursor:pointer; touch-action:manipulation; }
.xll-mod-card-btn--view { border-color:rgba(100,116,139,.35); background:rgba(100,116,139,.1); color:#475569; }
.xll-mod-card-btn:focus-visible { outline:2px solid ${XLL_GREEN}; outline-offset:2px; }
.xll-mod-empty { text-align:center; padding:48px 24px; color:${COLOR_MUTED}; font-size:14px; line-height:1.6; }
.xll-mod-scroll { flex:1; min-height:0; overflow-y:auto; -webkit-overflow-scrolling:touch; padding-bottom:88px; }
.xll-mod-hero { margin:12px 14px 0; border-radius:14px; padding:18px 16px; color:#fff; }
.xll-mod-hero.orange { background:linear-gradient(135deg,#F97316,#EA580C); }
.xll-mod-hero.purple { background:linear-gradient(135deg,#8B5CF6,#7C3AED); }
.xll-mod-hero.green { background:linear-gradient(135deg,${XLL_GREEN},${XLL_GREEN_DEEP}); }
.xll-mod-hero-label { font-size:13px; opacity:.9; margin-bottom:6px; }
.xll-mod-hero-amt { font-size:28px; font-weight:800; font-variant-numeric:tabular-nums; margin-bottom:8px; }
.xll-mod-hero-meta { font-size:13px; opacity:.92; line-height:1.5; }
.xll-tri-summary { padding:0!important; overflow:hidden; }
.xll-tri-summary .tc-hero { margin:0; border-radius:14px 14px 0 0; }
.xll-mod-section { margin:12px 14px 0; background:${COLOR_BG}; border-radius:14px; padding:14px 16px; box-shadow:0 2px 8px rgba(15,23,42,.04); border:1px solid rgba(0,0,0,.03); }
.xll-mod-section-title { font-size:14px; font-weight:700; color:${COLOR_TEXT}; margin-bottom:12px; }
.xll-mod-action-bar { position:absolute; left:0; right:0; bottom:0; display:flex; gap:10px; padding:10px 14px calc(10px + env(safe-area-inset-bottom,0)); background:${COLOR_BG}; border-top:1px solid ${COLOR_LINE}; z-index:20; }
.xll-mod-action-bar button { flex:1; min-height:44px; border-radius:12px; font-size:14px; font-weight:600; cursor:pointer; border:none; touch-action:manipulation; }
.xll-mod-btn-ghost { background:${COLOR_PAGE}; color:${COLOR_TEXT_SEC}; border:1px solid ${COLOR_LINE}; }
.xll-mod-btn-danger { background:rgba(245,63,63,.08); color:${COLOR_DANGER}; border:1px solid rgba(245,63,63,.25); }
.xll-mod-btn-primary { background:${XLL_GREEN}; color:#fff; box-shadow:0 4px 12px rgba(122,185,41,.3); }
.xll-mod-ar-plate { font-size:18px; font-weight:800; color:${COLOR_TEXT}; }
.xll-mod-ar-tag { display:inline-flex; font-size:11px; font-weight:600; padding:2px 8px; border-radius:999px; margin-left:8px; }
.xll-mod-ar-tag.warn { color:${COLOR_WARN}; background:rgba(255,125,0,.12); }
.xll-mod-ar-tag.danger { color:${COLOR_DANGER}; background:rgba(245,63,63,.1); }
.xll-mod-form-row { display:flex; justify-content:space-between; align-items:center; padding:12px 0; border-bottom:1px solid ${COLOR_LINE}; gap:12px; font-size:14px; }
.xll-mod-form-row:last-child { border-bottom:none; }
.xll-mod-form-label { color:${COLOR_MUTED}; flex-shrink:0; }
.xll-mod-form-value { color:${COLOR_TEXT}; text-align:right; flex:1; word-break:break-all; }
.xll-mod-form-input { flex:1; min-height:40px; border:1px solid ${COLOR_LINE}; border-radius:8px; padding:0 10px; font-size:14px; text-align:right; outline:none; }
.xll-mod-form-input:focus { border-color:${XLL_GREEN}; box-shadow:0 0 0 2px ${XLL_GREEN_SOFT}; }
.xll-mod-form-page select.xll-mod-form-input,
.xll-mod-form-page .xll-mod-form-row input.xll-mod-form-input,
.xll-mod-form-page .xll-mod-form-row input.xll-mod-form-input[type="date"],
.xll-mod-form-page .xll-mod-form-row input.xll-mod-form-input[type="datetime-local"],
.xll-mod-form-page .xll-mod-form-row input.xll-mod-form-input.xll-mod-form-picker,
.xll-mod-form-page .tc-section-form select.xll-mod-form-input,
.xll-mod-form-page select.xll-dv-metric-input,
.xll-mod-form-page input.xll-dv-metric-input[type="datetime-local"] { border:none; background:transparent; border-radius:0; box-shadow:none; padding-right:0; }
.xll-mod-form-page select.xll-mod-form-input:focus,
.xll-mod-form-page .xll-mod-form-row input.xll-mod-form-input:focus,
.xll-mod-form-page .xll-mod-form-row input.xll-mod-form-input[type="date"]:focus,
.xll-mod-form-page .xll-mod-form-row input.xll-mod-form-input[type="datetime-local"]:focus,
.xll-mod-form-page .xll-mod-form-row input.xll-mod-form-input.xll-mod-form-picker:focus,
.xll-mod-form-page .tc-section-form select.xll-mod-form-input:focus,
.xll-mod-form-page select.xll-dv-metric-input:focus,
.xll-mod-form-page input.xll-dv-metric-input[type="datetime-local"]:focus { border:none; box-shadow:none; }
.xll-vr-module.xll-mod-form-page select.xll-mod-form-input:focus,
.xll-vr-module.xll-mod-form-page .xll-mod-form-row input.xll-mod-form-input[type="date"]:focus,
.xll-vr-module.xll-mod-form-page .xll-mod-form-row input.xll-mod-form-input[type="datetime-local"]:focus,
.xll-vr-module.xll-mod-form-page .tc-section-form select.xll-mod-form-input:focus { border:none; box-shadow:none; }
.xll-mod-foot-btns { display:flex; gap:10px; padding:14px; flex-shrink:0; background:${COLOR_BG}; border-top:1px solid ${COLOR_LINE}; }
.xll-mod-foot-btns button { flex:1; min-height:48px; border-radius:12px; font-size:15px; font-weight:600; cursor:pointer; border:none; touch-action:manipulation; }
.xll-mod-detail-wrap { flex:1; min-height:0; display:flex; flex-direction:column; position:relative; overflow:hidden; }
.xll-mod-drawer-types { display:flex; flex-wrap:wrap; gap:8px; }
.xll-mod-drawer-type-btn { min-height:40px; padding:0 14px; border:1px solid ${COLOR_LINE}; border-radius:10px; background:${COLOR_BG}; font-size:13px; color:${COLOR_TEXT_SEC}; cursor:pointer; touch-action:manipulation; }
.xll-mod-drawer-type-btn.active { border-color:${XLL_GREEN}; color:${XLL_GREEN_DEEP}; background:${XLL_GREEN_SOFT}; font-weight:600; }
.xll-mod-drawer-section { margin-bottom:18px; }
.xll-mod-drawer-section-title { font-size:13px; font-weight:600; color:${COLOR_TEXT}; margin-bottom:10px; }
.xll-mod-drawer-form-card { background:${COLOR_PAGE}; border-radius:12px; padding:0 14px; border:1px solid ${COLOR_LINE}; }
.xll-mod-drawer-form-card .xll-mod-form-row { padding:12px 0; margin:0; }
.xll-mod-drawer-form-card .xll-mod-form-row input.xll-mod-form-input { min-height:36px; border:none; background:transparent; border-radius:0; box-shadow:none; text-align:right; padding:0; }
.xll-mod-drawer-form-card .xll-mod-form-row input.xll-mod-form-input:focus { box-shadow:none; }
.xll-mod-drawer-form-card .xll-mod-form-row input.xll-mod-form-input::placeholder { color:${COLOR_MUTED}; }
.xll-mod-drawer-date-row { display:flex; align-items:center; gap:8px; padding:12px 0; }
.xll-mod-drawer-date-row input { flex:1; min-width:0; min-height:36px; border:none; background:transparent; font-size:14px; text-align:center; outline:none; color:${COLOR_TEXT}; font-family:inherit; }
.xll-mod-drawer-date-sep { font-size:13px; color:${COLOR_MUTED}; flex-shrink:0; }
.xll-mp-picker-field { flex:1; min-width:0; min-height:36px; display:flex; align-items:center; justify-content:flex-end; gap:4px; border:none; background:transparent; padding:0; font-size:14px; color:${COLOR_TEXT}; cursor:pointer; touch-action:manipulation; font-family:inherit; text-align:right; }
.xll-mp-picker-field:active { opacity:.72; }
.xll-mp-picker-field .placeholder { color:${COLOR_MUTED}; }
.xll-mp-picker-chevron { flex-shrink:0; color:${COLOR_MUTED}; font-size:16px; line-height:1; transform:translateY(-1px); }
.xll-mod-drawer-date-row .xll-mp-picker-field { flex:1; text-align:center; justify-content:center; }
.xll-mod-drawer-form-card .xll-mod-form-row .xll-mp-picker-field { text-align:right; justify-content:flex-end; }
.xll-mp-picker-overlay { position:absolute; inset:0; z-index:220; display:flex; flex-direction:column; justify-content:flex-end; }
.xll-mp-picker-panel { position:relative; z-index:1; width:100%; background:${COLOR_BG}; border-radius:16px 16px 0 0; display:flex; flex-direction:column; box-shadow:0 -8px 32px rgba(15,23,42,.12); animation:xll-mod-sheet-up .24s ease; padding-bottom:env(safe-area-inset-bottom,0px); }
.xll-mp-picker-toolbar { display:flex; align-items:center; justify-content:space-between; min-height:48px; padding:0 12px; border-bottom:1px solid ${COLOR_LINE}; flex-shrink:0; }
.xll-mp-picker-toolbar-btn { min-width:56px; min-height:44px; border:none; background:transparent; font-size:15px; cursor:pointer; touch-action:manipulation; padding:0 8px; }
.xll-mp-picker-toolbar-btn--cancel { color:${COLOR_MUTED}; text-align:left; }
.xll-mp-picker-toolbar-btn--confirm { color:${XLL_GREEN_DEEP}; font-weight:600; text-align:right; }
.xll-mp-picker-toolbar-title { font-size:16px; font-weight:700; color:${COLOR_TEXT}; text-align:center; flex:1; min-width:0; }
.xll-mp-picker-columns-wrap { position:relative; height:220px; overflow:hidden; flex-shrink:0; }
.xll-mp-picker-columns-wrap::before, .xll-mp-picker-columns-wrap::after { content:''; position:absolute; left:0; right:0; height:88px; pointer-events:none; z-index:2; }
.xll-mp-picker-columns-wrap::before { top:0; background:linear-gradient(180deg, ${COLOR_BG} 0%, rgba(255,255,255,0) 100%); }
.xll-mp-picker-columns-wrap::after { bottom:0; background:linear-gradient(0deg, ${COLOR_BG} 0%, rgba(255,255,255,0) 100%); }
.xll-mp-picker-highlight { position:absolute; left:16px; right:16px; top:50%; transform:translateY(-50%); height:44px; border-radius:10px; background:${COLOR_PAGE}; border:1px solid ${COLOR_LINE}; pointer-events:none; z-index:0; }
.xll-mp-picker-columns { position:relative; z-index:1; display:flex; height:100%; }
.xll-mp-picker-column { flex:1; min-width:0; overflow-y:auto; scroll-snap-type:y mandatory; -webkit-overflow-scrolling:touch; scrollbar-width:none; position:relative; z-index:1; }
.xll-mp-picker-column::-webkit-scrollbar { display:none; }
.xll-mp-picker-column-spacer { height:88px; flex-shrink:0; }
.xll-mp-picker-item { height:44px; display:flex; align-items:center; justify-content:center; font-size:16px; color:${COLOR_MUTED}; scroll-snap-align:center; flex-shrink:0; }
.xll-mp-picker-item.active { color:${COLOR_TEXT}; font-weight:600; font-size:17px; }
.xll-mod-drawer-hint { font-size:12px; color:${COLOR_MUTED}; line-height:1.55; margin-top:8px; }
.xll-mod-drawer-actions { display:flex; gap:10px; margin-top:20px; }
.xll-mod-sheet-overlay { position:absolute; inset:0; z-index:200; display:flex; flex-direction:column; justify-content:flex-end; }
.xll-mod-sheet-mask { position:absolute; inset:0; border:none; padding:0; background:rgba(0,0,0,.45); cursor:pointer; }
.xll-mod-sheet-panel { position:relative; z-index:1; width:100%; max-height:min(72%, 460px); background:${COLOR_BG}; border-radius:16px 16px 0 0; display:flex; flex-direction:column; box-shadow:0 -8px 32px rgba(15,23,42,.12); animation:xll-mod-sheet-up .24s ease; padding-bottom:env(safe-area-inset-bottom,0px); }
@keyframes xll-mod-sheet-up { from { transform:translateY(100%); } to { transform:translateY(0); } }
.xll-mod-sheet-handle { width:36px; height:4px; border-radius:999px; background:${COLOR_LINE}; margin:8px auto 0; flex-shrink:0; }
.xll-mod-sheet-header { position:relative; display:flex; align-items:center; justify-content:center; min-height:48px; padding:8px 48px 10px; border-bottom:1px solid ${COLOR_LINE}; flex-shrink:0; }
.xll-mod-sheet-title { font-size:16px; font-weight:700; color:${COLOR_TEXT}; text-align:center; }
.xll-mod-sheet-close { position:absolute; right:8px; top:50%; transform:translateY(-50%); width:36px; height:36px; border:none; border-radius:999px; background:transparent; color:${COLOR_MUTED}; font-size:22px; line-height:1; cursor:pointer; touch-action:manipulation; }
.xll-mod-sheet-close:active { background:${COLOR_PAGE}; }
.xll-mod-sheet-body { flex:1; min-height:0; overflow-y:auto; -webkit-overflow-scrolling:touch; padding:4px 0 8px; }
.xll-mod-sheet-option { width:100%; display:flex; align-items:center; justify-content:space-between; gap:12px; min-height:52px; padding:12px 16px; border:none; border-bottom:1px solid ${COLOR_LINE}; background:transparent; text-align:left; cursor:pointer; touch-action:manipulation; box-sizing:border-box; }
.xll-mod-sheet-option:last-child { border-bottom:none; }
.xll-mod-sheet-option:active { background:${COLOR_PAGE}; }
.xll-mod-sheet-option.active { background:${XLL_GREEN_SOFT}; }
.xll-mod-sheet-option-text { flex:1; min-width:0; display:flex; flex-direction:column; gap:2px; }
.xll-mod-sheet-option-main { font-size:15px; font-weight:600; color:${COLOR_TEXT}; line-height:1.35; }
.xll-mod-sheet-option-sub { font-size:12px; color:${COLOR_MUTED}; line-height:1.4; }
.xll-mod-sheet-option-check { flex-shrink:0; width:22px; height:22px; border-radius:999px; background:${XLL_GREEN}; color:#fff; font-size:13px; font-weight:700; display:inline-flex; align-items:center; justify-content:center; }
.xll-mod-sheet-panel--filter { max-height:min(88%, 580px); padding-bottom:0; }
.xll-mod-sheet-scroll { flex:1; min-height:0; overflow-y:auto; -webkit-overflow-scrolling:touch; padding:12px 16px 8px; }
.xll-mod-sheet-footer { flex-shrink:0; display:flex; gap:10px; padding:12px 16px calc(12px + env(safe-area-inset-bottom,0px)); border-top:1px solid ${COLOR_LINE}; background:${COLOR_BG}; }
.xll-mod-sheet-footer button { flex:1; min-height:44px; border-radius:12px; font-size:15px; font-weight:600; touch-action:manipulation; }
.xll-mod-timeline { padding-left:4px; }
.xll-mod-step { display:flex; gap:12px; margin-bottom:14px; }
.xll-mod-step-dot { width:22px; height:22px; border-radius:50%; background:${COLOR_LINE}; color:#fff; font-size:12px; display:flex; align-items:center; justify-content:center; flex-shrink:0; }
.xll-mod-step-dot.done { background:${COLOR_SUCCESS}; }
.xll-mod-step-title { font-size:14px; font-weight:600; color:${COLOR_TEXT}; margin-bottom:4px; }
.xll-mod-step-meta { font-size:12px; color:${COLOR_MUTED}; line-height:1.55; }
.xll-mod-upload { border:1px dashed ${COLOR_LINE}; border-radius:10px; padding:20px; text-align:center; color:${COLOR_MUTED}; font-size:13px; cursor:pointer; background:${COLOR_PAGE}; touch-action:manipulation; }
.xll-mod-upload:active { background:${XLL_GREEN_SOFT}; border-color:${XLL_GREEN}; }
.xll-mod-upload:focus-visible { outline:2px solid ${XLL_GREEN}; outline-offset:2px; border-color:${XLL_GREEN}; }
.xll-dv-steps-wrap { flex-shrink:0; background:${COLOR_BG}; border-bottom:1px solid ${COLOR_LINE}; padding:10px 14px; }
.xll-dv-steps { display:flex; gap:6px; overflow-x:auto; scrollbar-width:none; -webkit-overflow-scrolling:touch; }
.xll-dv-steps::-webkit-scrollbar { display:none; }
.xll-dv-step { flex-shrink:0; font-size:11px; padding:5px 10px; border-radius:999px; background:${COLOR_PAGE}; color:${COLOR_MUTED}; border:1px solid transparent; }
.xll-dv-step.active { background:${XLL_GREEN_SOFT}; color:${XLL_GREEN_DEEP}; border-color:rgba(122,185,41,.35); font-weight:600; }
.xll-dv-step.done { color:${COLOR_SUCCESS}; background:rgba(0,180,42,.08); }
.xll-dv-step-nav { display:flex; align-items:center; gap:0; }
.xll-dv-step-nav-item { flex:1; min-width:0; display:flex; flex-direction:column; align-items:center; gap:4px; padding:4px 2px; border:none; background:transparent; cursor:pointer; touch-action:manipulation; }
.xll-dv-step-nav-item:active { opacity:.78; }
.xll-dv-step-nav-index { width:22px; height:22px; border-radius:50%; display:flex; align-items:center; justify-content:center; font-size:11px; font-weight:700; color:${COLOR_MUTED}; background:${COLOR_PAGE}; border:1px solid ${COLOR_LINE}; }
.xll-dv-step-nav-item.active .xll-dv-step-nav-index { color:#fff; background:${XLL_GREEN}; border-color:${XLL_GREEN}; }
.xll-dv-step-nav-item.done .xll-dv-step-nav-index { color:${COLOR_SUCCESS}; background:rgba(0,180,42,.1); border-color:rgba(0,180,42,.35); }
.xll-dv-step-nav-label { font-size:11px; color:${COLOR_MUTED}; line-height:1.3; text-align:center; white-space:nowrap; }
.xll-dv-step-nav-item.active .xll-dv-step-nav-label { color:${XLL_GREEN_DEEP}; font-weight:600; }
.xll-dv-step-nav-connector { flex:0 0 24px; height:2px; background:${COLOR_LINE}; margin-top:-14px; }
.xll-dv-step-nav-item.done + .xll-dv-step-nav-connector, .xll-dv-step-nav-connector.done { background:rgba(0,180,42,.35); }
.xll-dv-metric-unit-wrap { display:flex; align-items:stretch; gap:0; }
.xll-dv-metric-unit-wrap .xll-dv-metric-input { flex:1; border-top-right-radius:0; border-bottom-right-radius:0; }
.xll-dv-metric-unit-suffix { flex-shrink:0; min-width:44px; display:flex; align-items:center; justify-content:center; padding:0 10px; border:1px solid ${COLOR_LINE}; border-left:none; border-radius:0 10px 10px 0; background:${COLOR_PAGE}; font-size:13px; font-weight:600; color:${COLOR_TEXT_SEC}; }
.xll-dv-metric-input[type="number"] { -moz-appearance:textfield; appearance:textfield; }
.xll-dv-metric-input[type="number"]::-webkit-outer-spin-button, .xll-dv-metric-input[type="number"]::-webkit-inner-spin-button { -webkit-appearance:none; margin:0; }
.xll-dv-inspection-block { margin:0 14px 12px; border:1px solid ${COLOR_LINE}; border-radius:12px; overflow:hidden; background:${COLOR_BG}; }
.xll-dv-inspection-cat { padding:8px 12px; font-size:12px; font-weight:700; color:${COLOR_TEXT}; background:${COLOR_PAGE}; border-bottom:1px solid ${COLOR_LINE}; }
.xll-dv-inspection-row { display:flex; align-items:center; gap:10px; min-height:44px; padding:8px 12px; border-bottom:1px solid ${COLOR_LINE}; font-size:13px; }
.xll-dv-inspection-row:last-child { border-bottom:none; }
.xll-dv-inspection-item { flex:1; min-width:0; color:${COLOR_TEXT}; line-height:1.35; }
.xll-dv-inspection-status { flex-shrink:0; font-size:12px; font-weight:600; color:${COLOR_SUCCESS}; }
.xll-dv-inspection-status.off { color:${COLOR_DANGER}; }
.xll-dv-inspection-tread { width:64px; min-height:32px; border:1px solid ${COLOR_LINE}; border-radius:8px; padding:0 8px; font-size:13px; text-align:right; outline:none; background:#fff; }
.xll-dv-inspection-switch { width:44px; height:26px; border-radius:999px; border:none; background:${COLOR_LINE}; position:relative; cursor:pointer; flex-shrink:0; touch-action:manipulation; }
.xll-dv-inspection-switch.on { background:${XLL_GREEN}; }
.xll-dv-inspection-switch::after { content:''; position:absolute; top:3px; left:3px; width:20px; height:20px; border-radius:50%; background:#fff; box-shadow:0 1px 3px rgba(0,0,0,.15); transition:transform .15s ease; }
.xll-dv-inspection-switch.on::after { transform:translateX(18px); }
.xll-dv-inspection-controls { display:flex; align-items:center; gap:8px; flex-shrink:0; }
.xll-dv-inspection-remark { width:76px; min-height:32px; border:1px solid ${COLOR_LINE}; border-radius:8px; padding:0 8px; font-size:12px; text-align:right; outline:none; background:#fff; color:${COLOR_TEXT}; }
.xll-dv-inspection-remark::placeholder { color:${COLOR_MUTED}; }
.xll-dv-inspection-remark:focus { border-color:${XLL_GREEN}; box-shadow:0 0 0 2px ${XLL_GREEN_SOFT}; }
.xll-dv-inspection-remark-read { flex-shrink:0; max-width:88px; font-size:12px; color:${COLOR_TEXT_SEC}; text-align:right; word-break:break-all; }
.xll-dv-photo-capture-scroll { background:linear-gradient(180deg, ${XLL_GREEN_SOFT} 0%, ${COLOR_PAGE} 42%, ${COLOR_BG} 100%); }
.xll-dv-photo-capture-page { flex:1; display:flex; flex-direction:column; align-items:center; justify-content:center; padding:28px 20px 32px; min-height:min(58vh,420px); text-align:center; }
.xll-dv-photo-capture-card { width:100%; max-width:340px; padding:28px 22px 24px; border-radius:20px; background:${COLOR_BG}; border:1px solid rgba(122,185,41,.18); box-shadow:0 12px 40px rgba(29,33,41,.08), 0 2px 8px rgba(122,185,41,.08); }
.xll-dv-photo-capture-icon { width:72px; height:72px; margin:0 auto 18px; border-radius:50%; display:flex; align-items:center; justify-content:center; background:linear-gradient(145deg, ${XLL_GREEN_SOFT}, rgba(122,185,41,.22)); color:${XLL_GREEN_DEEP}; box-shadow:inset 0 0 0 1px rgba(122,185,41,.2); }
.xll-dv-photo-capture-icon svg { width:34px; height:34px; display:block; }
.xll-dv-photo-capture-title { margin:0 0 8px; font-size:20px; font-weight:700; color:${COLOR_TEXT}; line-height:1.4; letter-spacing:.02em; }
.xll-dv-photo-capture-sub { margin:0 0 20px; font-size:13px; color:${COLOR_TEXT_SEC}; line-height:1.6; }
.xll-dv-photo-capture-resume { margin-bottom:18px; padding:12px 14px; border-radius:12px; background:${COLOR_PAGE}; border:1px solid ${COLOR_LINE}; text-align:left; }
.xll-dv-photo-capture-progress-bar { height:6px; border-radius:999px; background:${COLOR_LINE}; overflow:hidden; margin-bottom:8px; }
.xll-dv-photo-capture-progress-bar > span { display:block; height:100%; border-radius:999px; background:linear-gradient(90deg, ${XLL_GREEN}, ${XLL_GREEN_DEEP}); transition:width .35s ease; }
.xll-dv-photo-capture-resume-text { font-size:12px; color:${COLOR_TEXT_SEC}; line-height:1.5; }
.xll-dv-photo-capture-resume-text strong { color:${XLL_GREEN_DEEP}; font-weight:700; }
.xll-dv-photo-capture-tips { margin:0; padding:0; list-style:none; text-align:left; }
.xll-dv-photo-capture-tips li { position:relative; padding-left:18px; font-size:12px; color:${COLOR_MUTED}; line-height:1.65; }
.xll-dv-photo-capture-tips li + li { margin-top:6px; }
.xll-dv-photo-capture-tips li::before { content:''; position:absolute; left:0; top:8px; width:6px; height:6px; border-radius:50%; background:${XLL_GREEN}; opacity:.75; }
.xll-dv-photo-capture-badge { display:inline-flex; align-items:center; min-height:24px; padding:0 10px; margin-bottom:14px; border-radius:999px; font-size:11px; font-weight:700; letter-spacing:.04em; }
.xll-dv-photo-capture-badge--body { color:${XLL_GREEN_DEEP}; background:${XLL_GREEN_SOFT}; border:1px solid rgba(122,185,41,.28); }
.xll-dv-photo-capture-badge--chassis { color:#0E7490; background:rgba(14,116,144,.1); border:1px solid rgba(14,116,144,.22); }
.xll-dv-photo-capture-badge--tire { color:#C2410C; background:rgba(194,65,12,.1); border:1px solid rgba(194,65,12,.2); }
.xll-dv-photo-capture-soon { margin:0 0 16px; font-size:14px; font-weight:600; color:${COLOR_TEXT_SEC}; letter-spacing:.12em; }
.xll-dv-photo-capture-countdown-ring { position:relative; width:108px; height:108px; margin:0 auto 18px; display:flex; align-items:center; justify-content:center; }
.xll-dv-photo-capture-countdown-ring::before { content:''; position:absolute; inset:0; border-radius:50%; background:conic-gradient(${XLL_GREEN} 0deg, ${XLL_GREEN_SOFT} 280deg, ${COLOR_LINE} 280deg); animation:xllPhotoRingSpin 3s linear infinite; }
.xll-dv-photo-capture-countdown-ring::after { content:''; position:absolute; inset:6px; border-radius:50%; background:${COLOR_BG}; box-shadow:inset 0 2px 8px rgba(29,33,41,.06); }
.xll-dv-photo-capture-countdown-num { position:relative; z-index:1; font-size:44px; font-weight:800; color:${XLL_GREEN_DEEP}; line-height:1; font-variant-numeric:tabular-nums; animation:xllPhotoCountPop .55s ease; }
.xll-dv-photo-capture-target { margin:0 0 10px; font-size:22px; font-weight:700; color:${COLOR_TEXT}; line-height:1.35; }
.xll-dv-photo-capture-countdown-tip { margin:0 0 16px; font-size:12px; color:${COLOR_MUTED}; line-height:1.5; }
.xll-dv-photo-capture-step-pill { display:inline-flex; align-items:center; min-height:30px; padding:0 14px; border-radius:999px; background:${COLOR_PAGE}; border:1px solid ${COLOR_LINE}; font-size:12px; font-weight:600; color:${COLOR_TEXT_SEC}; }
.xll-dv-photo-capture-step-pill em { font-style:normal; color:${XLL_GREEN_DEEP}; font-weight:700; }
@keyframes xllPhotoCountPop { 0% { transform:scale(.72); opacity:.35; } 55% { transform:scale(1.08); } 100% { transform:scale(1); opacity:1; } }
@keyframes xllPhotoRingSpin { from { transform:rotate(0deg); } to { transform:rotate(360deg); } }
.xll-dv-photo-done-list { margin:0 14px 12px; border:1px solid ${COLOR_LINE}; border-radius:12px; overflow:hidden; background:${COLOR_BG}; }
.xll-dv-photo-done-row { display:flex; align-items:center; justify-content:space-between; gap:10px; min-height:44px; padding:10px 12px; border-bottom:1px solid ${COLOR_LINE}; font-size:13px; }
.xll-dv-photo-done-row:last-child { border-bottom:none; }
.xll-dv-photo-done-ok { font-size:12px; font-weight:600; color:${COLOR_SUCCESS}; flex-shrink:0; }
.xll-mod-action-bar.xll-dv-photo-action-bar { padding-left:20px; padding-right:20px; }
.xll-mod-action-bar.xll-dv-photo-action-bar .xll-dv-photo-action-btn { flex:1; min-height:48px; border-radius:24px; font-size:16px; font-weight:600; }
.xll-dv-photo-reshoot-bar .tc-section-head { display:none; }
.xll-dv-photo-thumb { position:relative; aspect-ratio:1; border-radius:10px; overflow:hidden; border:1px solid ${COLOR_LINE}; background:${COLOR_PAGE}; cursor:pointer; touch-action:manipulation; }
.xll-dv-photo-thumb--empty { cursor:default; }
.xll-dv-photo-thumb:active:not(.xll-dv-photo-thumb--empty) { opacity:.92; }
.xll-dv-photo-thumb img { width:100%; height:100%; object-fit:cover; display:block; }
.xll-dv-photo-thumb-placeholder { width:100%; height:100%; display:flex; align-items:center; justify-content:center; font-size:11px; font-weight:600; color:${XLL_GREEN_DEEP}; background:${XLL_GREEN_SOFT}; }
.xll-dv-photo-thumb-placeholder--reshoot { color:${COLOR_WARN}; background:rgba(255,125,0,.08); box-shadow:inset 0 0 0 1px rgba(255,125,0,.35); }
.xll-dv-photo-thumb-del { position:absolute; top:6px; right:6px; width:22px; height:22px; border-radius:50%; border:1.5px solid rgba(255,255,255,.9); background:rgba(245,63,63,.94); color:#fff; font-size:13px; font-weight:700; line-height:1; cursor:pointer; display:flex; align-items:center; justify-content:center; box-shadow:0 2px 8px rgba(15,23,42,.22); z-index:2; touch-action:manipulation; padding:0; }
.xll-dv-photo-thumb-del:active { transform:scale(.94); background:rgba(220,38,38,.98); }
.xll-dv-photo-thumb-label { margin-top:8px; font-size:11px; color:${COLOR_TEXT_SEC}; line-height:1.35; text-align:center; }
.xll-dv-photo-thumb-tread { margin-top:3px; font-size:10px; color:${XLL_GREEN_DEEP}; text-align:center; font-weight:600; }
.xll-dv-photo-viewer { position:absolute; inset:0; z-index:80; display:flex; flex-direction:column; background:#0f1419; color:#fff; }
.xll-dv-photo-viewer-top { flex-shrink:0; display:flex; align-items:flex-start; justify-content:space-between; gap:10px; padding:12px 14px calc(10px + env(safe-area-inset-top,0px)); background:linear-gradient(180deg,rgba(0,0,0,.72),rgba(0,0,0,.2)); }
.xll-dv-photo-viewer-close { flex-shrink:0; min-height:32px; padding:0 12px; border-radius:999px; border:1px solid rgba(255,255,255,.22); background:rgba(255,255,255,.1); color:#fff; font-size:13px; font-weight:600; cursor:pointer; }
.xll-dv-photo-viewer-meta { flex:1; min-width:0; text-align:center; }
.xll-dv-photo-viewer-cat { font-size:11px; color:rgba(255,255,255,.65); margin-bottom:2px; }
.xll-dv-photo-viewer-title { font-size:15px; font-weight:700; line-height:1.35; }
.xll-dv-photo-viewer-counter { flex-shrink:0; min-height:28px; padding:0 10px; border-radius:999px; background:rgba(255,255,255,.12); font-size:12px; font-weight:700; display:flex; align-items:center; }
.xll-dv-photo-viewer-stage { flex:1; min-height:0; display:flex; align-items:center; justify-content:center; gap:8px; padding:0 8px; touch-action:pan-y; }
.xll-dv-photo-viewer-img-wrap { flex:1; min-width:0; height:100%; display:flex; align-items:center; justify-content:center; }
.xll-dv-photo-viewer-img-wrap img { max-width:100%; max-height:100%; object-fit:contain; display:block; border-radius:8px; }
.xll-dv-photo-viewer-nav { flex-shrink:0; width:40px; height:40px; border-radius:50%; border:1px solid rgba(255,255,255,.22); background:rgba(255,255,255,.1); color:#fff; font-size:24px; line-height:1; cursor:pointer; display:flex; align-items:center; justify-content:center; touch-action:manipulation; }
.xll-dv-photo-viewer-nav:disabled { opacity:.28; cursor:not-allowed; }
.xll-dv-photo-viewer-nav:not(:disabled):active { background:rgba(255,255,255,.2); }
.xll-dv-photo-viewer-foot { flex-shrink:0; padding:12px 16px calc(14px + env(safe-area-inset-bottom,0px)); text-align:center; background:rgba(0,0,0,.45); font-size:13px; color:rgba(255,255,255,.85); }
.xll-dv-photo-viewer-tread { font-weight:700; color:#86efac; }
.xll-dv-photo-viewer-hint { margin-top:4px; font-size:11px; color:rgba(255,255,255,.45); }
.xll-dv-photo-camera-overlay { position:absolute; inset:0; z-index:70; display:flex; flex-direction:column; background:#1a1f2e; color:#fff; }
.xll-dv-photo-camera-top { flex-shrink:0; padding:12px 16px; text-align:center; font-size:14px; font-weight:600; background:rgba(0,0,0,.35); }
.xll-dv-photo-camera-view { flex:1; min-height:0; position:relative; overflow:hidden; background:#2d3748; }
.xll-dv-photo-camera-view img { width:100%; height:100%; object-fit:cover; display:block; }
.xll-dv-photo-camera-placeholder { font-size:16px; color:rgba(255,255,255,.55); }
.xll-dv-camera-viewfinder { position:relative; width:100%; height:100%; overflow:hidden; background:#2d3748; touch-action:none; cursor:crosshair; }
.xll-dv-camera-preview { width:100%; height:100%; display:flex; align-items:center; justify-content:center; transition:transform .22s ease; will-change:transform; }
.xll-dv-camera-preview img { width:100%; height:100%; object-fit:cover; display:block; pointer-events:none; user-select:none; }
.xll-dv-camera-focus-ring { position:absolute; z-index:2; width:56px; height:56px; margin:-28px 0 0 -28px; border:2px solid #fff; border-radius:4px; box-shadow:0 0 0 1px rgba(0,0,0,.35); pointer-events:none; animation:xll-dv-focus-pulse .35s ease; }
@keyframes xll-dv-focus-pulse { from { transform:scale(1.12); opacity:.55; } to { transform:scale(1); opacity:1; } }
.xll-dv-camera-zoom { position:absolute; z-index:3; right:12px; top:50%; transform:translateY(-50%); display:flex; flex-direction:column; align-items:center; gap:6px; padding:8px 6px; border-radius:12px; background:rgba(0,0,0,.45); }
.xll-dv-camera-zoom-btn { width:32px; height:32px; border:none; border-radius:8px; background:rgba(255,255,255,.16); color:#fff; font-size:18px; font-weight:700; line-height:1; cursor:pointer; touch-action:manipulation; }
.xll-dv-camera-zoom-btn:active { background:rgba(255,255,255,.28); }
.xll-dv-camera-zoom-val { font-size:11px; font-weight:700; color:#fff; min-width:32px; text-align:center; font-variant-numeric:tabular-nums; }
.xll-dv-camera-focus-tip { position:absolute; z-index:3; left:50%; bottom:14px; transform:translateX(-50%); padding:4px 10px; border-radius:999px; background:rgba(0,0,0,.45); color:rgba(255,255,255,.88); font-size:11px; pointer-events:none; white-space:nowrap; }
.xll-dv-photo-camera-tread { display:flex; align-items:center; justify-content:space-between; gap:12px; min-height:52px; padding:0 16px; background:${COLOR_BG}; color:${COLOR_TEXT}; border-top:1px solid ${COLOR_LINE}; }
.xll-dv-photo-camera-tread-label { font-size:15px; color:${COLOR_MUTED}; flex-shrink:0; }
.xll-dv-photo-camera-actions { display:flex; gap:10px; padding:12px 16px calc(12px + env(safe-area-inset-bottom,0px)); background:${COLOR_BG}; border-top:1px solid ${COLOR_LINE}; }
.xll-dv-photo-camera-actions--single .xll-dv-spare-capture-retake { flex:1; min-width:0; }
.xll-dv-photo-camera-actions--dual .xll-dv-spare-capture-retake { flex:1; min-width:0; }
.xll-dv-photo-camera-actions--pair > .xll-dv-spare-capture-retake,
.xll-dv-photo-camera-actions--pair > .xll-dv-photo-album-btn { flex:1; min-width:0; }
.xll-dv-photo-camera-actions--triple > .xll-dv-spare-capture-retake,
.xll-dv-photo-camera-actions--triple > .xll-dv-photo-album-btn,
.xll-dv-photo-camera-actions--triple > .xll-dv-spare-capture-done { flex:1; min-width:0; }
.xll-dv-photo-album-float { position:absolute; z-index:4; right:12px; bottom:14px; min-width:44px; min-height:44px; padding:0 14px; border:1px solid rgba(255,255,255,.35); border-radius:999px; background:rgba(0,0,0,.45); color:#fff; font-size:13px; font-weight:600; cursor:pointer; touch-action:manipulation; backdrop-filter:blur(6px); }
.xll-dv-photo-album-float:active { background:rgba(0,0,0,.62); }
.xll-dv-photo-album-btn { flex:0 0 auto; min-width:88px; min-height:44px; padding:0 12px; border:1px solid ${COLOR_LINE}; border-radius:10px; background:#fff; color:${COLOR_TEXT_SEC}; font-size:14px; font-weight:600; cursor:pointer; touch-action:manipulation; }
.xll-dv-photo-album-btn:active { opacity:.88; }
.xll-dv-photo-camera-skip { flex:0 0 auto; min-width:88px; min-height:44px; padding:0 12px; border:1px solid ${COLOR_LINE}; border-radius:10px; background:#fff; color:${COLOR_TEXT_SEC}; font-size:14px; font-weight:600; cursor:pointer; }
.xll-dv-status { display:inline-flex; font-size:11px; font-weight:600; padding:2px 8px; border-radius:999px; margin-left:8px; vertical-align:middle; }
.xll-dv-status.neutral { color:${COLOR_TEXT_SEC}; background:${COLOR_PAGE}; }
.xll-dv-status.warn { color:${COLOR_WARN}; background:rgba(255,125,0,.12); }
.xll-dv-status.ok { color:${COLOR_SUCCESS}; background:rgba(0,180,42,.1); }
.xll-dv-status.info { color:#2563EB; background:rgba(37,99,235,.1); }
.xll-dv-plate-pending { color:${COLOR_WARN}; font-weight:700; }
.xll-dv-plate-row { display:flex; align-items:center; flex-wrap:wrap; gap:6px; flex:1; min-width:0; }
.xll-dv-replace-tag { display:inline-flex; align-items:center; justify-content:center; font-size:11px; font-weight:600; line-height:1; padding:3px 8px; border-radius:999px; color:#E11D48; background:rgba(244,63,94,.12); flex-shrink:0; }
.xll-dv-hint { font-size:12px; color:${COLOR_MUTED}; line-height:1.55; margin-bottom:10px; padding:8px 10px; background:${COLOR_PAGE}; border-radius:8px; }
.xll-dv-photo-block { margin-bottom:14px; }
.xll-dv-photo-title { font-size:13px; font-weight:600; color:${COLOR_TEXT}; margin-bottom:8px; }
.xll-dv-photo-grid { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:8px; }
.xll-dv-photo-slot { aspect-ratio:1; border-radius:10px; border:1px dashed ${COLOR_LINE}; background:${COLOR_PAGE}; display:flex; align-items:center; justify-content:center; font-size:11px; color:${COLOR_MUTED}; text-align:center; padding:4px; cursor:pointer; touch-action:manipulation; }
.xll-dv-photo-slot:active { background:${XLL_GREEN_SOFT}; border-color:${XLL_GREEN}; }
.xll-dv-view-val { color:#000 !important; }
.xll-dv-module .tc-section-form { padding:0 14px 14px; }
.xll-dv-module .tc-section-form .xll-mod-form-row { padding:10px 0; }
.xll-dv-module .tc-section-form .xll-mod-form-row:last-child { border-bottom:none; }
.xll-dv-module .tc-section-hint { padding:0 14px 12px; font-size:12px; color:${COLOR_MUTED}; line-height:1.55; }
.xll-dv-module .xll-dv-photo-block { margin-bottom:0; padding:12px 14px 14px; }
.xll-dv-step-label { font-size:13px; font-weight:700; color:${COLOR_TEXT}; margin-bottom:10px; }
.xll-dv-vehicle-picker { width:100%; min-height:44px; display:flex; align-items:center; justify-content:space-between; gap:10px; padding:0; border:none; border-radius:0; background:transparent; font-size:15px; color:${COLOR_TEXT}; cursor:pointer; touch-action:manipulation; text-align:left; }
.xll-dv-vehicle-picker:active { opacity:.72; }
.xll-dv-vehicle-picker.placeholder { color:${COLOR_MUTED}; }
.xll-dv-vehicle-picker-chevron { flex-shrink:0; color:${COLOR_MUTED}; font-size:18px; line-height:1; }
.xll-dv-pick-toolbar { flex-shrink:0; padding:10px 14px 0; background:${COLOR_BG}; }
.xll-dv-pick-parking { display:flex; align-items:center; gap:8px; margin-top:10px; width:100%; padding:10px 12px; border-radius:10px; background:${COLOR_PAGE}; border:1px solid ${COLOR_LINE}; font-size:13px; color:${COLOR_TEXT_SEC}; cursor:pointer; touch-action:manipulation; box-sizing:border-box; }
.xll-dv-pick-parking:active { background:${XLL_GREEN_SOFT}; border-color:${XLL_GREEN}; }
.xll-dv-pick-parking-label { flex-shrink:0; font-size:12px; color:${COLOR_MUTED}; }
.xll-dv-pick-parking strong { flex:1; min-width:0; color:${COLOR_TEXT}; font-weight:600; font-size:14px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.xll-dv-pick-parking-arrow { flex-shrink:0; color:${COLOR_MUTED}; font-size:12px; }
.xll-dv-pick-list { flex:1; min-height:0; overflow-y:auto; -webkit-overflow-scrolling:touch; padding:12px 14px calc(16px + env(safe-area-inset-bottom,0px)); }
.xll-dv-pick-card { margin-bottom:12px; opacity:1; }
.xll-dv-pick-card.blocked { opacity:.72; }
.xll-dv-pick-card.blocked .xll-mod-card { border-color:#FECACA; background:linear-gradient(180deg,#fff 0%,#FFF5F5 100%); }
.xll-dv-readiness-bar { display:flex; align-items:center; min-height:32px; padding:6px 12px; margin:8px 0 0; border-radius:8px; font-size:12px; font-weight:600; line-height:1.4; }
.xll-dv-readiness-bar.ready { color:#047857; background:rgba(0,180,42,.1); }
.xll-dv-readiness-bar.blocked { color:#DC2626; background:rgba(220,38,38,.08); }
.xll-dv-section-badge { display:inline-flex; align-items:center; justify-content:center; width:22px; height:22px; border-radius:8px; background:${XLL_GREEN_SOFT}; color:${XLL_GREEN_DEEP}; font-size:12px; font-weight:800; flex-shrink:0; margin-right:8px; }
.xll-dv-required-tag { display:inline-flex; align-items:center; margin-left:6px; padding:1px 6px; border-radius:4px; font-size:10px; font-weight:600; color:#E11D48; background:rgba(244,63,94,.1); vertical-align:middle; line-height:1.4; }
.xll-dv-section-head-title { display:flex; align-items:center; font-size:15px; font-weight:700; color:${COLOR_TEXT}; }
.xll-dv-section-actions { display:flex; align-items:center; gap:6px; flex-shrink:0; }
.xll-dv-section-action-btn { min-height:28px; padding:0 10px; border-radius:999px; font-size:12px; font-weight:600; cursor:pointer; touch-action:manipulation; white-space:nowrap; }
.xll-dv-section-action-btn--primary { border:none; background:${XLL_GREEN_SOFT}; color:${XLL_GREEN_DEEP}; }
.xll-dv-section-action-btn--primary:active { opacity:.82; }
.xll-dv-section-action-btn--ghost { border:1px solid ${COLOR_LINE}; background:${COLOR_PAGE}; color:${COLOR_TEXT_SEC}; }
.xll-dv-section-action-btn--ghost:active { opacity:.82; }
.xll-dv-validate-overlay { position:absolute; inset:0; z-index:50; display:flex; align-items:center; justify-content:center; padding:24px; box-sizing:border-box; }
.xll-dv-validate-mask { position:absolute; inset:0; background:rgba(0,0,0,.45); border:none; padding:0; cursor:pointer; }
.xll-dv-validate-card { position:relative; z-index:1; width:100%; max-width:320px; background:${COLOR_BG}; border-radius:14px; padding:18px 16px 14px; box-shadow:0 12px 40px rgba(15,23,42,.2); animation:xll-dv-validate-in .22s ease; }
@keyframes xll-dv-validate-in { from { opacity:0; transform:scale(.96); } to { opacity:1; transform:scale(1); } }
.xll-dv-validate-title { font-size:16px; font-weight:700; color:${COLOR_TEXT}; margin-bottom:6px; }
.xll-dv-validate-plate { font-size:13px; color:${COLOR_MUTED}; margin-bottom:12px; }
.xll-dv-validate-list { margin:0; padding:0 0 0 18px; font-size:14px; color:${COLOR_TEXT_SEC}; line-height:1.65; }
.xll-dv-validate-list li { margin-bottom:8px; }
.xll-dv-validate-list li:last-child { margin-bottom:0; }
.xll-dv-validate-ok { width:100%; min-height:44px; margin-top:16px; border:none; border-radius:12px; background:${XLL_GREEN}; color:#fff; font-size:15px; font-weight:600; cursor:pointer; touch-action:manipulation; }
.xll-dv-validate-ok:active { opacity:.88; }
.xll-dv-confirm-actions { display:flex; gap:10px; margin-top:16px; }
.xll-dv-confirm-cancel, .xll-dv-confirm-ok { flex:1; min-height:44px; border-radius:12px; font-size:15px; font-weight:600; cursor:pointer; touch-action:manipulation; }
.xll-dv-confirm-cancel { border:1px solid ${COLOR_LINE}; background:${COLOR_PAGE}; color:${COLOR_TEXT_SEC}; }
.xll-dv-confirm-cancel:active { opacity:.82; }
.xll-dv-confirm-ok { border:none; background:${XLL_GREEN}; color:#fff; }
.xll-dv-confirm-ok:active { opacity:.88; }
.xll-dv-equip-block { padding:0 14px 12px; }
.xll-dv-equip-row { display:flex; align-items:flex-start; justify-content:space-between; gap:12px; padding:10px 0; border-bottom:1px solid ${COLOR_LINE}; font-size:14px; }
.xll-dv-equip-row:last-child { border-bottom:none; }
.xll-dv-equip-label { color:${COLOR_MUTED}; flex-shrink:0; min-width:108px; line-height:1.45; }
.xll-dv-equip-val { flex:1; text-align:right; color:${COLOR_TEXT}; line-height:1.45; }
.xll-dv-equip-val strong { font-weight:700; }
.xll-dv-equip-switch-wrap { display:flex; align-items:center; justify-content:flex-end; gap:8px; flex:1; min-width:0; }
.xll-dv-equip-switch-label { font-size:13px; color:${COLOR_TEXT_SEC}; font-weight:600; min-width:16px; text-align:right; }
.xll-dv-equip-switch-label.is-active { color:${XLL_GREEN_DEEP}; font-weight:700; }
.xll-tri-fault-segment { display:inline-flex; align-items:stretch; padding:3px; border-radius:999px; background:${COLOR_PAGE}; border:1px solid ${COLOR_LINE}; gap:2px; flex-shrink:0; }
.xll-tri-fault-segment__btn { min-width:72px; min-height:36px; padding:0 14px; border:none; border-radius:999px; background:transparent; color:${COLOR_TEXT_SEC}; font-size:13px; font-weight:600; cursor:pointer; touch-action:manipulation; transition:background .2s ease,color .2s ease,box-shadow .2s ease; }
.xll-tri-fault-segment__btn.is-active { background:${XLL_GREEN}; color:#fff; box-shadow:0 2px 8px rgba(122,185,41,.28); }
.xll-tri-fault-segment__btn:active { opacity:.88; }
.xll-dv-equip-switch { position:relative; width:44px; height:26px; border-radius:999px; border:none; background:${COLOR_LINE}; cursor:pointer; padding:0; flex-shrink:0; transition:background .2s; touch-action:manipulation; }
.xll-dv-equip-switch.on { background:${XLL_GREEN}; }
.xll-dv-equip-switch:disabled { opacity:.55; cursor:default; }
.xll-dv-equip-switch::after { content:''; position:absolute; top:3px; left:3px; width:20px; height:20px; border-radius:50%; background:#fff; transition:transform .2s; box-shadow:0 1px 3px rgba(0,0,0,.2); }
.xll-dv-equip-switch.on::after { transform:translateX(18px); }
.xll-dv-equip-photo-status { display:block; font-size:12px; color:${COLOR_MUTED}; margin-top:4px; }
.xll-dv-equip-photo-status.done { color:${COLOR_SUCCESS}; }
.xll-dv-equip-photo-status.pending { color:${COLOR_WARN}; }
.xll-dv-equip-photo-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:8px; margin-top:10px; }
.xll-dv-equip-photo-item { min-width:0; }
.xll-dv-equip-photo-item-label { font-size:11px; color:${COLOR_MUTED}; margin-bottom:6px; }
.xll-dv-equip-photo-slot { aspect-ratio:4/3; border-radius:8px; border:1px dashed ${COLOR_LINE}; background:${COLOR_PAGE}; display:flex; align-items:center; justify-content:center; font-size:11px; color:${COLOR_MUTED}; text-align:center; padding:4px; cursor:pointer; touch-action:manipulation; }
.xll-dv-equip-photo-slot.done { border-style:solid; border-color:rgba(122,185,41,.35); color:${XLL_GREEN_DEEP}; background:${XLL_GREEN_SOFT}; }
.xll-dv-equip-photo-slot:active { opacity:.82; }
.xll-dv-spare-tire-block { margin-top:10px; }
.xll-dv-spare-tire-hint { font-size:11px; color:${COLOR_MUTED}; line-height:1.55; margin-bottom:8px; }
.xll-dv-spare-tread-field { margin-top:10px; }
.xll-dv-spare-tread-label { font-size:11px; color:${COLOR_MUTED}; margin-bottom:6px; display:flex; align-items:center; justify-content:space-between; gap:8px; }
.xll-dv-spare-tread-ocr-tag { font-size:10px; color:${XLL_GREEN_DEEP}; background:${XLL_GREEN_SOFT}; padding:2px 6px; border-radius:4px; font-weight:600; white-space:nowrap; }
.xll-dv-spare-tread-input { width:100%; min-height:40px; border:1px solid ${COLOR_LINE}; border-radius:8px; padding:0 12px; font-size:15px; color:${COLOR_TEXT}; background:#fff; outline:none; box-sizing:border-box; }
.xll-dv-spare-tread-input:focus { border-color:${XLL_GREEN}; box-shadow:0 0 0 2px rgba(122,185,41,.15); }
.xll-dv-spare-tread-input:disabled { background:${COLOR_PAGE}; color:${COLOR_TEXT_SEC}; }
.xll-dv-spare-photo-result { margin-top:10px; border-radius:10px; overflow:hidden; border:1px solid ${COLOR_LINE}; cursor:pointer; touch-action:manipulation; }
.xll-dv-spare-photo-preview { aspect-ratio:4/3; background:#2d3748; display:flex; align-items:center; justify-content:center; overflow:hidden; }
.xll-dv-spare-photo-preview img { width:100%; height:100%; object-fit:cover; display:block; }
.xll-dv-spare-photo-preview-placeholder { font-size:15px; color:rgba(255,255,255,.72); font-weight:500; }
.xll-dv-spare-tread-row { display:flex; align-items:center; justify-content:space-between; gap:12px; min-height:44px; padding:0 14px; background:${COLOR_BG}; border-top:1px solid ${COLOR_LINE}; }
.xll-dv-spare-tread-row-label { font-size:14px; color:${COLOR_MUTED}; flex-shrink:0; }
.xll-dv-spare-tread-row-val { font-size:15px; color:${COLOR_TEXT}; font-weight:600; font-variant-numeric:tabular-nums; }
.xll-dv-spare-capture { position:absolute; inset:0; z-index:60; display:flex; flex-direction:column; background:#1a1f2e; color:#fff; }
.xll-dv-spare-capture-photo { flex:1; min-height:0; display:flex; align-items:center; justify-content:center; background:#2d3748; margin:0; overflow:hidden; }
.xll-dv-spare-capture-photo img { width:100%; height:100%; object-fit:cover; display:block; }
.xll-dv-spare-capture-photo-placeholder { font-size:18px; color:rgba(255,255,255,.55); font-weight:500; }
.xll-dv-spare-capture-tread { display:flex; align-items:center; justify-content:space-between; gap:12px; min-height:52px; padding:0 16px; background:${COLOR_BG}; border-top:1px solid ${COLOR_LINE}; }
.xll-dv-spare-capture-tread-label { font-size:15px; color:${COLOR_MUTED}; flex-shrink:0; }
.xll-dv-spare-capture-tread-input-wrap { display:flex; align-items:center; gap:6px; flex:1; justify-content:flex-end; min-width:0; }
.xll-dv-spare-capture-tread-input { width:72px; min-height:36px; border:none; background:transparent; font-size:18px; font-weight:700; color:${COLOR_TEXT}; text-align:right; outline:none; font-variant-numeric:tabular-nums; }
.xll-dv-spare-capture-tread-unit { font-size:15px; color:${COLOR_MUTED}; flex-shrink:0; }
.xll-dv-spare-capture-actions { display:flex; gap:10px; padding:12px 16px calc(12px + env(safe-area-inset-bottom,0px)); background:${COLOR_BG}; border-top:1px solid ${COLOR_LINE}; }
.xll-dv-spare-capture-retake { flex:0 0 auto; min-width:108px; min-height:44px; padding:0 14px; border:1px solid ${XLL_GREEN}; border-radius:10px; background:#fff; color:${XLL_GREEN_DEEP}; font-size:15px; font-weight:600; cursor:pointer; touch-action:manipulation; }
.xll-dv-spare-capture-done { flex:1; min-height:44px; border:none; border-radius:10px; background:${XLL_GREEN}; color:#fff; font-size:15px; font-weight:600; cursor:pointer; touch-action:manipulation; }
.xll-dv-spare-capture-done:active, .xll-dv-spare-capture-retake:active { opacity:.88; }
.xll-dv-training-done-tag { display:inline-flex; align-items:center; min-height:28px; padding:0 10px; border-radius:999px; font-size:12px; font-weight:600; color:${COLOR_SUCCESS}; background:rgba(0,180,42,.1); white-space:nowrap; }
.xll-dv-training-pending-tag { display:inline-flex; align-items:center; min-height:28px; padding:0 10px; border-radius:999px; font-size:12px; font-weight:600; color:${COLOR_WARN}; background:rgba(255,125,0,.12); white-space:nowrap; }
.xll-dv-training-pending-panel { margin:0 14px 14px; padding:14px; border-radius:12px; background:rgba(255,125,0,.06); border:1px solid rgba(255,125,0,.2); }
.xll-dv-training-pending-hint { font-size:13px; color:${COLOR_TEXT_SEC}; line-height:1.55; margin-bottom:12px; }
.xll-dv-training-bound-kv { margin:0 14px 12px; padding:12px; border-radius:10px; background:${COLOR_PAGE}; border:1px solid ${COLOR_LINE}; }
.xll-dv-training-bound-row { display:flex; justify-content:space-between; gap:12px; font-size:13px; line-height:1.5; padding:4px 0; }
.xll-dv-training-bound-label { color:${COLOR_MUTED}; flex-shrink:0; }
.xll-dv-training-bound-val { color:${COLOR_TEXT}; font-weight:600; text-align:right; word-break:break-all; }
.xll-dv-training-cells { margin:0 14px 14px; border-radius:12px; overflow:hidden; background:${COLOR_BG}; border:1px solid ${COLOR_LINE}; }
.xll-dv-training-cell { width:100%; display:flex; align-items:center; gap:12px; min-height:56px; padding:12px 14px; border:none; border-bottom:1px solid ${COLOR_LINE}; background:${COLOR_BG}; cursor:pointer; touch-action:manipulation; text-align:left; box-sizing:border-box; }
.xll-dv-training-cell:last-child { border-bottom:none; }
.xll-dv-training-cell:active { background:${COLOR_PAGE}; }
.xll-dv-training-cell-icon { width:36px; height:36px; border-radius:10px; display:flex; align-items:center; justify-content:center; font-size:18px; flex-shrink:0; }
.xll-dv-training-cell-icon--scan { background:${XLL_GREEN_SOFT}; color:${XLL_GREEN_DEEP}; }
.xll-dv-training-cell-icon--edit { background:rgba(22,93,255,.1); color:#165DFF; }
.xll-dv-training-cell-main { flex:1; min-width:0; display:flex; flex-direction:column; gap:2px; }
.xll-dv-training-cell-title { font-size:15px; font-weight:600; color:${COLOR_TEXT}; line-height:1.35; }
.xll-dv-training-cell-desc { font-size:12px; color:${COLOR_MUTED}; line-height:1.4; }
.xll-dv-training-cell-arrow { flex-shrink:0; color:${COLOR_MUTED}; font-size:20px; line-height:1; font-weight:300; }
.xll-dv-training-qr-wrap { padding:0 14px 14px; }
.xll-dv-training-qr-card { padding:20px 16px 18px; border-radius:12px; background:${COLOR_BG}; border:1px solid ${COLOR_LINE}; text-align:center; }
.xll-dv-training-qr-img { width:200px; height:200px; margin:0 auto 14px; display:block; border-radius:8px; border:1px solid ${COLOR_LINE}; background:#fff; object-fit:contain; }
.xll-dv-training-qr-title { font-size:15px; font-weight:600; color:${COLOR_TEXT}; margin-bottom:6px; }
.xll-dv-training-qr-hint { font-size:13px; color:${COLOR_TEXT_SEC}; line-height:1.55; }
.xll-dv-training-qr-wechat { display:inline-flex; align-items:center; gap:4px; margin-top:8px; font-size:12px; color:${COLOR_MUTED}; }
.xll-dv-driver-manual-page .tc-scroll { padding-bottom:calc(88px + env(safe-area-inset-bottom,0px)); }
.xll-dv-driver-manual-photos { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:10px; padding:0 14px 14px; }
.xll-dv-driver-panel { margin:0 14px 14px; padding:12px 14px; border-radius:12px; background:${COLOR_PAGE}; border:1px solid ${COLOR_LINE}; }
.xll-dv-driver-kv { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:10px 14px; margin-bottom:12px; }
.xll-dv-driver-kv-item.full { grid-column:1 / -1; }
.xll-dv-driver-licenses { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:8px; }
.xll-dv-driver-licenses.cols-3 { grid-template-columns:repeat(3,minmax(0,1fr)); }
.xll-dv-driver-license-item { min-width:0; }
.xll-dv-driver-license-label { font-size:11px; color:${COLOR_MUTED}; margin-bottom:6px; }
.xll-dv-driver-license-thumb { aspect-ratio:4/3; border-radius:8px; border:1px solid ${COLOR_LINE}; background:linear-gradient(135deg,#f8fafc 0%,#eef2f7 100%); display:flex; align-items:center; justify-content:center; font-size:11px; color:${COLOR_TEXT_SEC}; text-align:center; padding:4px; overflow:hidden; }
.xll-dv-driver-license-thumb img { width:100%; height:100%; object-fit:cover; display:block; }
.xll-dv-driver-manual-photo-item { min-width:0; }
.xll-dv-driver-manual-photo-label { font-size:12px; color:${COLOR_MUTED}; margin-bottom:6px; display:flex; align-items:center; gap:6px; }
.xll-dv-driver-manual-photo-slot { aspect-ratio:4/3; border-radius:10px; border:1px dashed ${COLOR_LINE}; background:${COLOR_PAGE}; display:flex; align-items:center; justify-content:center; font-size:12px; color:${COLOR_TEXT_SEC}; cursor:pointer; touch-action:manipulation; overflow:hidden; }
.xll-dv-driver-manual-photo-slot.done { border-style:solid; border-color:${XLL_GREEN}; color:${XLL_GREEN_DEEP}; background:#fff; }
.xll-dv-driver-manual-photo-slot img { width:100%; height:100%; object-fit:cover; display:block; }
.xll-dv-kv-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:10px 14px; padding:12px 14px 14px; }
.xll-dv-kv-item { min-width:0; }
.xll-dv-kv-label { font-size:11px; color:${COLOR_MUTED}; margin-bottom:3px; }
.xll-dv-kv-val { font-size:13px; color:${COLOR_TEXT}; font-weight:500; line-height:1.4; word-break:break-all; }
.xll-dv-kv-item.full { grid-column:1 / -1; }
.xll-dv-selected-vehicle { margin:0 14px 12px; padding:12px 14px; border-radius:12px; border:1px solid rgba(122,185,41,.35); background:linear-gradient(135deg,#f0fdf4 0%,#fff 100%); }
.xll-dv-vehicle-pick-section .tc-section-head { border-bottom:none; padding:12px 14px 0; }
.xll-dv-vehicle-pick-section:not(.has-vehicle) .tc-section-head { padding-bottom:12px; }
.xll-dv-vehicle-pick-section .xll-dv-selected-vehicle { margin:12px 14px 12px; }
.xll-dv-selected-vehicle-plate { font-size:18px; font-weight:800; color:${COLOR_TEXT}; margin-bottom:4px; }
.xll-dv-selected-vehicle-sub { font-size:12px; color:${COLOR_TEXT_SEC}; line-height:1.45; }
.xll-dv-selected-vehicle-actions { display:flex; gap:8px; margin-top:10px; }
.xll-dv-selected-vehicle-actions button { flex:1; min-height:36px; border-radius:10px; font-size:13px; font-weight:600; cursor:pointer; touch-action:manipulation; }
.xll-dv-chip-group { display:flex; flex-wrap:wrap; gap:8px; padding:0 14px 14px; }
.xll-dv-chip-opt { min-height:36px; padding:0 14px; border:1px solid ${COLOR_LINE}; border-radius:999px; background:${COLOR_BG}; font-size:13px; color:${COLOR_TEXT_SEC}; cursor:pointer; touch-action:manipulation; }
.xll-dv-chip-opt.active { border-color:${XLL_GREEN}; color:${XLL_GREEN_DEEP}; background:${XLL_GREEN_SOFT}; font-weight:600; }
.xll-dv-metrics-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:10px; padding:12px 14px 14px; }
.xll-dv-metric-field { display:flex; flex-direction:column; gap:6px; }
.xll-dv-metric-field.full { grid-column:1 / -1; }
.xll-dv-metric-label { font-size:12px; font-weight:600; color:${COLOR_MUTED}; }
.xll-dv-metric-input { min-height:44px; border:1px solid ${COLOR_LINE}; border-radius:10px; padding:0 12px; font-size:15px; font-weight:600; color:${COLOR_TEXT}; outline:none; background:${COLOR_BG}; width:100%; box-sizing:border-box; }
.xll-dv-metric-input:focus { border-color:${XLL_GREEN}; box-shadow:0 0 0 2px ${XLL_GREEN_SOFT}; }
.xll-dv-metric-remark { width:100%; min-height:72px; border:1px solid ${COLOR_LINE}; border-radius:10px; padding:10px 12px; font-size:14px; color:${COLOR_TEXT}; resize:vertical; box-sizing:border-box; font-family:inherit; outline:none; background:${COLOR_BG}; }
.xll-dv-metric-remark:focus { border-color:${XLL_GREEN}; box-shadow:0 0 0 2px ${XLL_GREEN_SOFT}; }
.xll-dv-delivery-location { padding:0 14px 14px; }
.xll-dv-delivery-location-head { display:flex; align-items:center; justify-content:space-between; gap:8px; margin-bottom:8px; }
.xll-dv-delivery-location-label { font-size:12px; font-weight:600; color:${COLOR_MUTED}; }
.xll-dv-delivery-location-plate { font-size:12px; font-weight:600; color:${XLL_GREEN_DEEP}; }
.xll-dv-delivery-map { position:relative; height:220px; border-radius:12px; overflow:hidden; border:1px solid ${COLOR_LINE}; background:#e8f0e4; box-shadow:inset 0 1px 4px rgba(0,0,0,.04); }
.xll-dv-delivery-map-canvas { position:absolute; inset:0; background:linear-gradient(160deg,#dce8d4 0%,#e8f2e0 28%,#d0e0c8 52%,#c5d8bc 78%,#dbe8d2 100%); }
.xll-dv-delivery-map-water { position:absolute; top:8%; right:-6%; width:46%; height:34%; border-radius:42% 58% 60% 40%; background:linear-gradient(135deg,rgba(147,197,253,.55) 0%,rgba(96,165,250,.42) 100%); transform:rotate(-8deg); }
.xll-dv-delivery-map-road { position:absolute; background:rgba(255,255,255,.82); box-shadow:0 0 0 1px rgba(148,163,184,.25); }
.xll-dv-delivery-map-road--h { height:5px; left:-4%; right:-4%; top:46%; transform:rotate(-4deg); }
.xll-dv-delivery-map-road--v { width:5px; top:-6%; bottom:-6%; left:54%; transform:rotate(6deg); }
.xll-dv-delivery-map-road--d { height:4px; width:68%; left:12%; top:28%; transform:rotate(18deg); }
.xll-dv-delivery-map-marker { position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); z-index:3; display:flex; align-items:center; justify-content:center; }
.xll-dv-delivery-map-marker-pin { width:38px; height:38px; border-radius:50%; background:#2563EB; border:3px solid #fff; box-shadow:0 4px 14px rgba(37,99,235,.38); display:flex; align-items:center; justify-content:center; }
.xll-dv-delivery-map-marker-pin::after { content:''; width:10px; height:10px; border-radius:50%; background:#fff; }
.xll-dv-delivery-map-foot { position:absolute; left:0; right:0; bottom:0; z-index:2; display:flex; align-items:center; justify-content:space-between; gap:8px; padding:8px 10px; background:linear-gradient(180deg,rgba(255,255,255,0) 0%,rgba(255,255,255,.94) 35%,rgba(255,255,255,.98) 100%); font-size:12px; color:${COLOR_TEXT_SEC}; }
.xll-dv-delivery-map-foot strong { color:${COLOR_TEXT}; font-weight:600; }
.xll-dv-delivery-map .xll-map-brand { z-index:2; }
.xll-dv-delivery-map--pending .xll-dv-delivery-map-canvas { opacity:.72; }
.xll-dv-section-action-btn:disabled { opacity:.55; cursor:not-allowed; }
.xll-dv-summary-card { margin:14px 14px 14px; padding:14px; border-radius:12px; background:${COLOR_PAGE}; border:1px dashed ${COLOR_LINE}; font-size:13px; color:${COLOR_TEXT_SEC}; line-height:1.6; }
.xll-dv-borderless-picker { flex:1; min-height:40px; border:none; background:transparent; padding:0; font-size:14px; color:${COLOR_TEXT}; text-align:right; cursor:pointer; touch-action:manipulation; }
.xll-dv-borderless-picker.placeholder { color:${COLOR_MUTED}; }
.xll-dv-borderless-picker:active { opacity:.72; }
.xll-dv-sign-pending-hint { margin:0; padding:10px 12px; border-radius:10px; background:rgba(37,99,235,.08); color:#2563EB; font-size:12px; line-height:1.55; }
.xll-dv-sign-pending-foot { padding:12px 0 0; }
.xll-dv-sign-success-page { display:flex; flex-direction:column; height:100%; min-height:0; background:${COLOR_BG}; }
.xll-dv-sign-success-body { flex:1; min-height:0; display:flex; flex-direction:column; align-items:center; justify-content:center; padding:24px 20px; text-align:center; }
.xll-dv-sign-success-icon { width:72px; height:72px; border-radius:50%; background:${XLL_GREEN_SOFT}; color:${XLL_GREEN_DEEP}; display:flex; align-items:center; justify-content:center; font-size:36px; font-weight:700; margin-bottom:18px; }
.xll-dv-sign-success-title { font-size:20px; font-weight:700; color:${COLOR_TEXT}; line-height:1.4; margin-bottom:8px; }
.xll-dv-sign-success-desc { font-size:14px; color:${COLOR_TEXT_SEC}; line-height:1.6; max-width:280px; }
.xll-dv-sign-success-countdown { margin-top:14px; font-size:13px; color:${COLOR_MUTED}; }
.xll-dv-authorized-section .tc-section-head { border-bottom:none; padding:12px 14px 0; }
.xll-dv-authorized-panel { padding:12px 14px 14px; }
.xll-dv-authorized-section .xll-dv-summary-card { margin:0 0 12px; }
.xll-dv-authorized-hint { margin:0 0 10px; font-size:12px; color:${COLOR_TEXT_SEC}; line-height:1.55; }
.xll-dv-authorized-empty { margin:0; padding:12px 14px; border-radius:10px; background:rgba(255,125,0,.1); border:1px solid rgba(255,125,0,.28); color:#C2410C; font-size:13px; line-height:1.55; }
.xll-dv-authorized-empty strong { color:${COLOR_TEXT}; font-weight:700; }
.xll-dv-authorized-subtitle { margin:0 0 8px; font-size:12px; font-weight:600; color:${COLOR_MUTED}; }
.xll-dv-authorized-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:10px; }
.xll-dv-authorized-card { position:relative; display:flex; flex-direction:column; align-items:center; justify-content:center; min-height:96px; padding:14px 10px 12px; border-radius:12px; border:1.5px solid ${COLOR_LINE}; background:${COLOR_BG}; text-align:center; cursor:pointer; touch-action:manipulation; transition:border-color .15s ease, background .15s ease, box-shadow .15s ease; -webkit-appearance:none; appearance:none; font:inherit; color:inherit; }
.xll-dv-authorized-card:active:not(:disabled):not(.xll-dv-authorized-card--readonly) { opacity:.9; }
.xll-dv-authorized-card.active { border-color:${XLL_GREEN}; background:${XLL_GREEN_SOFT}; box-shadow:0 0 0 1px rgba(122,185,41,.25); }
.xll-dv-authorized-card:disabled { cursor:default; opacity:1; color:inherit; }
.xll-dv-authorized-card:not(.active):disabled { border-color:${COLOR_LINE}; background:${COLOR_PAGE}; }
.xll-dv-authorized-card--readonly { flex-direction:row; align-items:center; justify-content:flex-start; gap:12px; min-height:0; padding:12px 14px; text-align:left; cursor:default; grid-column:1 / -1; }
.xll-dv-authorized-avatar { flex-shrink:0; width:40px; height:40px; border-radius:50%; background:${COLOR_PAGE}; color:${COLOR_TEXT_SEC}; font-size:16px; font-weight:700; display:flex; align-items:center; justify-content:center; }
.xll-dv-authorized-card.active .xll-dv-authorized-avatar { background:${XLL_GREEN}; color:#fff; }
.xll-dv-authorized-card-body { min-width:0; flex:1; }
.xll-dv-authorized-card--readonly .xll-dv-authorized-avatar { width:44px; height:44px; }
.xll-dv-authorized-card-check { position:absolute; top:8px; right:8px; width:18px; height:18px; border-radius:50%; border:1.5px solid ${COLOR_LINE}; background:${COLOR_BG}; display:flex; align-items:center; justify-content:center; font-size:11px; font-weight:700; color:transparent; line-height:1; }
.xll-dv-authorized-card.active .xll-dv-authorized-card-check { border-color:${XLL_GREEN}; background:${XLL_GREEN}; color:#fff; }
.xll-dv-authorized-card-name { font-size:14px; font-weight:700; color:${COLOR_TEXT}; line-height:1.35; }
.xll-dv-authorized-card-phone { margin-top:4px; font-size:12px; color:${COLOR_TEXT_SEC}; line-height:1.4; }
.xll-dv-authorized-card.active .xll-dv-authorized-card-name { color:${XLL_GREEN_DEEP}; }
.xll-dv-authorized-card.active .xll-dv-authorized-card-phone { color:${XLL_GREEN_DEEP}; opacity:.88; }
.xll-dv-photo-readonly-empty { margin:0 14px 14px; padding:12px 14px; border-radius:10px; background:${COLOR_PAGE}; border:1px dashed ${COLOR_LINE}; font-size:12px; color:${COLOR_MUTED}; text-align:center; line-height:1.55; }
.xll-dv-photo-thumb-watermark { position:absolute; left:0; right:0; bottom:0; padding:4px 5px; background:linear-gradient(180deg, transparent, rgba(0,0,0,.72)); color:#fff; font-size:9px; line-height:1.35; text-align:left; pointer-events:none; }
.xll-dv-photo-thumb-watermark-loc { opacity:.92; margin-top:1px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.xll-dv-summary-card strong { color:${COLOR_TEXT}; }
.xll-dv-module .tc-section { margin-top:12px; }
.xll-dv-module .tc-scroll { padding-bottom:calc(88px + env(safe-area-inset-bottom,0px)); }
.xll-vr-module .xll-mod-tab.active { color:#E11D48; }
.xll-vr-module .xll-mod-chip.active { border-color:#F43F5E; color:#E11D48; background:rgba(244,63,94,.12); font-weight:600; }
.xll-vr-module .xll-mod-card-btn { border-color:rgba(244,63,94,.35); background:rgba(244,63,94,.1); color:#E11D48; }
.xll-vr-module .xll-vr-add-btn { flex-shrink:0; min-height:32px; padding:0 14px; border-radius:8px; border:1px solid #F43F5E; background:rgba(244,63,94,.1); color:#E11D48; font-size:13px; font-weight:600; cursor:pointer; touch-action:manipulation; }
.xll-vr-module .xll-mod-form-input:focus { border-color:#F43F5E; box-shadow:0 0 0 2px rgba(244,63,94,.12); }
.xll-vr-module .xll-vr-form-textarea { width:100%; min-height:72px; border:1px solid ${COLOR_LINE}; border-radius:8px; padding:8px 10px; font-size:14px; resize:vertical; outline:none; box-sizing:border-box; }
.xll-vr-module .xll-vr-form-textarea:focus { border-color:#F43F5E; box-shadow:0 0 0 2px rgba(244,63,94,.12); }
.xll-vr-module .xll-mod-btn-rose { background:#F43F5E; color:#fff; box-shadow:0 4px 12px rgba(244,63,94,.3); border:none; }
.xll-vr-module .tc-section-form { padding:0 14px 14px; }
.xll-vr-module .tc-section-form .xll-mod-form-row { padding:10px 0; }
.xll-vr-module .tc-section-form .xll-mod-form-row:last-child { border-bottom:none; }
.xll-vr-module .tc-section-hint { padding:0 14px 12px; font-size:12px; color:${COLOR_MUTED}; line-height:1.55; }
.xll-vr-module .tc-section-chips { padding:0 14px 14px; display:flex; flex-wrap:wrap; gap:8px; }
.xll-vm-online { display:inline-flex; align-items:center; gap:4px; font-size:11px; font-weight:600; }
.xll-vm-dot { width:6px; height:6px; border-radius:50%; background:${COLOR_MUTED}; }
.xll-vm-dot.on { background:${COLOR_SUCCESS}; }
.xll-vm-dot.off { background:${COLOR_MUTED}; }
.xll-vm-plate-row { display:flex; align-items:center; flex-wrap:wrap; gap:8px; }
.xll-vm-badge { font-size:11px; font-weight:600; padding:2px 8px; border-radius:999px; background:${XLL_GREEN_SOFT}; color:${XLL_GREEN_DEEP}; }
.xll-vm-badge.warn { background:rgba(255,125,0,.12); color:${COLOR_WARN}; }
.xll-vm-badge.danger { background:rgba(245,63,63,.1); color:${COLOR_DANGER}; }
.xll-vm-badge.neutral { background:${COLOR_PAGE}; color:${COLOR_TEXT_SEC}; }
.xll-vm-filter-btn { width:44px; height:44px; border:none; background:${COLOR_PAGE}; border-radius:10px; color:${COLOR_TEXT_SEC}; display:flex; align-items:center; justify-content:center; cursor:pointer; flex-shrink:0; }
.xll-vm-filter-btn.active { background:${XLL_GREEN_SOFT}; color:${XLL_GREEN_DEEP}; }
.xll-vm-detail-hero { margin:12px 14px 0; background:${COLOR_BG}; border-radius:14px; padding:16px; box-shadow:0 2px 8px rgba(15,23,42,.05); border:1px solid rgba(0,0,0,.04); }
.xll-vm-detail-plate { font-size:20px; font-weight:800; color:${COLOR_TEXT}; margin-bottom:6px; }
.xll-vm-detail-sub { font-size:13px; color:${COLOR_TEXT_SEC}; line-height:1.5; margin-bottom:10px; }
.xll-vm-detail-tags { display:flex; flex-wrap:wrap; gap:6px; }
.xll-vm-subtabs { display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:0; flex-shrink:0; background:${COLOR_BG}; border-bottom:1px solid ${COLOR_LINE}; position:sticky; top:0; z-index:5; }
.xll-vm-subtab { min-height:44px; padding:8px 4px; border:none; border-radius:0; background:transparent; color:${COLOR_MUTED}; font-size:12px; font-weight:500; cursor:pointer; touch-action:manipulation; border-bottom:2px solid transparent; margin-bottom:-1px; line-height:1.3; white-space:normal; word-break:keep-all; }
.xll-vm-subtab.active { color:${XLL_GREEN_DEEP}; border-bottom-color:${XLL_GREEN}; font-weight:700; background:rgba(122,185,41,.06); }
.xll-vm-detail-grid { display:grid; grid-template-columns:1fr 1fr; gap:10px 14px; }
.xll-vm-detail-kv { min-width:0; }
.xll-vm-detail-kv.full { grid-column:1 / -1; }
.xll-vm-detail-kv-label { font-size:11px; color:${COLOR_MUTED}; margin-bottom:3px; line-height:1.3; }
.xll-vm-detail-kv-val { font-size:13px; color:${COLOR_TEXT}; font-weight:500; line-height:1.45; word-break:break-word; overflow-wrap:anywhere; }
.xll-vm-cert-nav { display:flex; gap:6px; padding:10px 0 12px; overflow-x:auto; scrollbar-width:none; margin:0 -2px; }
.xll-vm-cert-nav::-webkit-scrollbar { display:none; }
.xll-vm-cert-nav-btn { flex-shrink:0; min-height:32px; padding:0 12px; border:1px solid ${COLOR_LINE}; border-radius:999px; background:${COLOR_BG}; font-size:12px; color:${COLOR_TEXT_SEC}; cursor:pointer; }
.xll-vm-cert-nav-btn.active { border-color:${XLL_GREEN}; color:${XLL_GREEN_DEEP}; background:${XLL_GREEN_SOFT}; font-weight:600; }
.xll-vm-photo-grid { display:grid; grid-template-columns:repeat(2,1fr); gap:8px; margin-bottom:12px; }
.xll-vm-photo { aspect-ratio:3/2; border-radius:10px; overflow:hidden; background:${COLOR_PAGE}; border:1px solid ${COLOR_LINE}; }
.xll-vm-photo img { width:100%; height:100%; object-fit:cover; display:block; }
.xll-vm-photo-empty { aspect-ratio:3/2; border-radius:10px; background:${COLOR_PAGE}; border:1px dashed ${COLOR_LINE}; display:flex; align-items:center; justify-content:center; font-size:12px; color:${COLOR_MUTED}; }
.xll-vm-ins-card { background:${COLOR_PAGE}; border-radius:12px; padding:12px 14px; margin-bottom:10px; border:1px solid rgba(0,0,0,.04); }
.xll-vm-ins-head { display:flex; justify-content:space-between; align-items:center; gap:8px; margin-bottom:8px; }
.xll-vm-ins-type { font-size:14px; font-weight:700; color:${COLOR_TEXT}; }
.xll-vm-ins-status { font-size:11px; font-weight:600; padding:2px 8px; border-radius:999px; background:${XLL_GREEN_SOFT}; color:${XLL_GREEN_DEEP}; }
.xll-vm-ins-status.warn { background:rgba(255,125,0,.12); color:${COLOR_WARN}; }
.xll-vm-ins-status.empty { background:${COLOR_PAGE}; color:${COLOR_MUTED}; }
.xll-vm-ins-row { display:flex; justify-content:space-between; gap:10px; font-size:12px; color:${COLOR_TEXT_SEC}; margin-bottom:4px; }
.xll-vm-ins-pdf { margin-top:10px; min-height:36px; padding:0 12px; border-radius:8px; border:1px solid rgba(122,185,41,.35); background:${XLL_GREEN_SOFT}; color:${XLL_GREEN}; font-size:12px; font-weight:600; cursor:pointer; width:100%; touch-action:manipulation; }
.xll-vm-event-list { position:relative; padding-left:18px; }
.xll-vm-event-item { position:relative; padding:0 0 16px 14px; }
.xll-vm-event-item:last-child { padding-bottom:0; }
.xll-vm-event-item::before { content:''; position:absolute; left:-18px; top:6px; bottom:-6px; width:2px; background:${COLOR_LINE}; }
.xll-vm-event-item:last-child::before { bottom:auto; height:6px; }
.xll-vm-event-dot { position:absolute; left:-23px; top:4px; width:10px; height:10px; border-radius:50%; background:${XLL_GREEN}; border:2px solid ${COLOR_BG}; box-shadow:0 0 0 1px ${XLL_GREEN}; }
.xll-vm-event-type { font-size:13px; font-weight:700; color:${COLOR_TEXT}; margin-bottom:4px; }
.xll-vm-event-meta { font-size:12px; color:${COLOR_MUTED}; line-height:1.55; }
.xll-vm-event-summary { font-size:13px; color:${COLOR_TEXT_SEC}; margin-top:4px; line-height:1.5; }
.xll-vm-ins-pdf { margin-top:10px; min-height:36px; padding:0 12px; border-radius:8px; border:1px solid rgba(122,185,41,.35); background:${XLL_GREEN_SOFT}; color:${XLL_GREEN}; font-size:12px; font-weight:600; cursor:pointer; width:100%; touch-action:manipulation; }
.xll-mod-form-row--stack { flex-direction:column; align-items:stretch; gap:6px; }
.xll-mod-form-row--stack .xll-mod-form-label { font-size:12px; line-height:1.4; }
.xll-mod-form-row--stack .xll-mod-form-value { text-align:left; line-height:1.55; word-break:break-word; overflow-wrap:anywhere; }
.xll-vm-meta-cell { min-width:0; }
.xll-vm-meta-cell .xll-mod-meta-val { display:block; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; margin-top:2px; }
.xll-vm-card-vin-wrap { flex:1; min-width:0; display:flex; align-items:center; gap:6px; padding-right:8px; }
.xll-vm-card-vin-label { flex-shrink:0; font-size:11px; color:${COLOR_MUTED}; }
.xll-vm-card-vin { flex:1; min-width:0; font-size:11px; color:${COLOR_TEXT_SEC}; font-family:ui-monospace,SFMono-Regular,Menlo,monospace; letter-spacing:.02em; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.xll-vm-customer-bar { display:flex; align-items:flex-start; gap:8px; margin-bottom:10px; padding:9px 10px; background:${COLOR_PAGE}; border-radius:10px; min-width:0; border:1px solid rgba(0,0,0,.04); }
.xll-vm-customer-bar--detail { margin:10px 0 0; background:${XLL_GREEN_SOFT}; border-color:rgba(122,185,41,.18); }
.xll-vm-customer-bar.is-long { cursor:pointer; touch-action:manipulation; }
.xll-vm-customer-bar.is-long:active { background:rgba(122,185,41,.12); }
.xll-vm-customer-icon { flex-shrink:0; width:24px; height:24px; border-radius:7px; background:${XLL_GREEN_SOFT}; color:${XLL_GREEN_DEEP}; font-size:11px; font-weight:700; display:flex; align-items:center; justify-content:center; margin-top:1px; }
.xll-vm-customer-bar--detail .xll-vm-customer-icon { background:rgba(255,255,255,.85); }
.xll-vm-customer-body { flex:1; min-width:0; }
.xll-vm-customer-label { display:block; font-size:11px; color:${COLOR_MUTED}; margin-bottom:3px; line-height:1.3; }
.xll-vm-customer-text { display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:2; overflow:hidden; font-size:13px; color:${COLOR_TEXT}; font-weight:600; line-height:1.45; word-break:break-all; }
.xll-vm-customer-bar:not(.is-long) .xll-vm-customer-text { -webkit-line-clamp:1; }
.xll-vm-customer-hint { display:block; margin-top:4px; font-size:11px; color:${XLL_GREEN}; font-weight:500; }
.xll-vm-name-modal-text { font-size:15px; color:${COLOR_TEXT}; line-height:1.6; word-break:break-word; overflow-wrap:anywhere; padding:4px 0; }
.xll-vm-kpi-row { display:flex; gap:8px; padding:10px 14px 2px; overflow-x:auto; scrollbar-width:none; -webkit-overflow-scrolling:touch; }
.xll-vm-kpi-row::-webkit-scrollbar { display:none; }
.xll-vm-kpi-chip { flex-shrink:0; min-width:76px; padding:10px 12px; border-radius:12px; background:${COLOR_BG}; border:1px solid ${COLOR_LINE}; cursor:pointer; touch-action:manipulation; text-align:left; transition:border-color .15s ease,background-color .15s ease,box-shadow .15s ease; }
.xll-vm-kpi-chip:active { transform:scale(0.98); }
.xll-vm-kpi-chip.active { border-color:${XLL_GREEN}; background:${XLL_GREEN_SOFT}; box-shadow:0 2px 8px rgba(122,185,41,.12); }
.xll-vm-kpi-chip-val { display:block; font-size:18px; font-weight:800; color:${COLOR_TEXT}; font-variant-numeric:tabular-nums; line-height:1.2; }
.xll-vm-kpi-chip-label { display:block; margin-top:2px; font-size:11px; color:${COLOR_MUTED}; font-weight:500; }
.xll-vm-section-card { margin:12px 14px 0; padding:14px; background:${COLOR_BG}; border-radius:14px; border:1px solid rgba(0,0,0,.04); box-shadow:0 2px 8px rgba(15,23,42,.04); }
.xll-vm-section-card .xll-mod-section-title { margin-bottom:12px; }
.xll-vm-location-head { display:flex; align-items:center; justify-content:space-between; gap:10px; margin-bottom:10px; }
.xll-vm-location-meta { margin-top:8px; font-size:12px; color:${COLOR_MUTED}; line-height:1.45; }
.xll-vm-location-map--compact { height:120px; border-radius:10px; }
.xll-vm-card-location { display:flex; align-items:flex-start; gap:8px; margin:0 0 10px; padding:9px 10px; background:${COLOR_PAGE}; border-radius:10px; border:1px solid rgba(0,0,0,.04); min-width:0; }
.xll-vm-location-pin { flex-shrink:0; width:18px; height:18px; margin-top:1px; border-radius:50%; background:${XLL_GREEN_SOFT}; position:relative; }
.xll-vm-location-pin::after { content:''; position:absolute; left:50%; top:50%; width:8px; height:8px; margin:-4px 0 0 -4px; border-radius:50%; background:${XLL_GREEN}; box-shadow:0 0 0 2px #fff; }
.xll-vm-card-location-body { flex:1; min-width:0; }
.xll-vm-card-location-text { display:block; font-size:12px; color:${COLOR_TEXT}; font-weight:600; line-height:1.45; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.xll-vm-card-location-time { display:block; margin-top:2px; font-size:11px; color:${COLOR_MUTED}; font-variant-numeric:tabular-nums; }
.xll-vm-list-card { position:relative; overflow:hidden; }
.xll-vm-list-card::before { content:''; position:absolute; left:12px; right:12px; top:0; height:2px; border-radius:2px 2px 0 0; background:var(--mod-accent, ${XLL_GREEN}); opacity:.85; }
.xll-vm-list-card .xll-mod-card-head { padding-top:4px; }
.xll-vm-subtab:focus-visible { outline:2px solid ${XLL_GREEN}; outline-offset:-2px; }
.xll-list-head { display:flex; align-items:center; justify-content:space-between; padding:12px 16px 4px; font-size:12px; color:${COLOR_MUTED}; }
.xll-list-count { font-variant-numeric:tabular-nums; font-weight:600; }
.xll-task-card { position:relative; margin:0 14px 12px; background:${COLOR_BG}; border-radius:14px; overflow:hidden; box-shadow:0 2px 8px rgba(15,23,42,.05); border:1px solid rgba(0,0,0,.04); animation:xll-card-in 0.35s ease both; }
.xll-task-card::before { content:''; position:absolute; left:12px; right:12px; top:0; height:2px; border-radius:2px 2px 0 0; background:var(--xll-accent, ${XLL_GREEN}); }
.xll-task-badge { position:absolute; top:10px; right:10px; min-width:22px; height:22px; padding:0 6px; border-radius:6px; background:rgba(255,229,143,.95); color:#AD6800; font-size:12px; font-weight:700; display:flex; align-items:center; justify-content:center; z-index:1; font-variant-numeric:tabular-nums; }
.xll-task-head { display:flex; align-items:flex-start; justify-content:space-between; padding:14px 14px 10px 16px; gap:10px; }
.xll-task-title-row { display:flex; align-items:center; gap:10px; flex:1; min-width:0; padding-right:24px; }
.xll-task-icon { width:40px; height:40px; border-radius:10px; display:flex; align-items:center; justify-content:center; flex-shrink:0; background:var(--xll-soft, ${XLL_GREEN_SOFT}); color:var(--xll-accent, ${XLL_GREEN}); }
.xll-task-title-wrap { min-width:0; flex:1; }
.xll-task-type-tag { display:inline-flex; font-size:11px; font-weight:600; padding:2px 8px; border-radius:999px; margin-bottom:4px; color:var(--xll-accent, ${XLL_GREEN}); background:var(--xll-soft, ${XLL_GREEN_SOFT}); }
.xll-task-title { font-size:15px; font-weight:700; color:${COLOR_TEXT}; line-height:1.35; }
.xll-task-action { flex-shrink:0; min-height:44px; padding:0 12px; font-size:14px; font-weight:600; color:${XLL_GREEN}; border:1px solid rgba(122,185,41,.35); background:${XLL_GREEN_SOFT}; border-radius:10px; cursor:pointer; touch-action:manipulation; transition:transform 0.15s ease, background 0.15s ease; white-space:nowrap; }
.xll-task-action:active { transform:scale(0.97); background:rgba(122,185,41,.22); }
.xll-task-action:focus-visible { outline:2px solid ${XLL_GREEN}; outline-offset:2px; }
.xll-task-body { padding:0 16px 14px; border-top:1px solid ${COLOR_LINE}; margin-top:0; padding-top:12px; }
.xll-kv { display:flex; gap:8px; font-size:13px; line-height:1.55; margin-bottom:6px; }
.xll-kv:last-child { margin-bottom:0; }
.xll-kv-label { color:${COLOR_MUTED}; flex-shrink:0; min-width:72px; }
.xll-kv-value { color:${COLOR_TEXT_SEC}; flex:1; word-break:break-all; }
.xll-kv-value.warn { color:${COLOR_DANGER}; font-weight:600; }
.xll-kv-value.warn::after { content:' '; }
.xll-warn-tag { display:inline-flex; align-items:center; gap:4px; font-size:11px; font-weight:600; color:${COLOR_DANGER}; background:rgba(245,63,63,.1); padding:1px 6px; border-radius:4px; margin-left:4px; vertical-align:middle; }
.xll-todo-summary { display:flex; flex-wrap:wrap; gap:8px; padding:0 16px 10px; }
.xll-todo-summary-chip { display:inline-flex; align-items:center; gap:6px; min-height:32px; padding:0 10px; border-radius:999px; border:1px solid ${COLOR_LINE}; background:${COLOR_BG}; font-size:12px; color:${COLOR_TEXT_SEC}; font-variant-numeric:tabular-nums; }
.xll-todo-summary-chip b { color:${COLOR_TEXT}; font-weight:700; }
.xll-todo-summary-chip--accent { border-color:rgba(122,185,41,.35); background:${XLL_GREEN_SOFT}; color:${XLL_GREEN_DEEP}; }
.xll-todo-zone { margin-bottom:4px; }
.xll-todo-zone-head { display:flex; align-items:center; justify-content:space-between; padding:8px 16px 6px; font-size:12px; font-weight:600; color:${COLOR_MUTED}; letter-spacing:0.02em; }
.xll-todo-zone-count { font-variant-numeric:tabular-nums; font-weight:700; color:${COLOR_TEXT_SEC}; }
.xll-task-meta-row { display:flex; flex-wrap:wrap; gap:6px; margin-bottom:8px; }
.xll-task-pill { display:inline-flex; align-items:center; min-height:22px; padding:0 8px; border-radius:999px; font-size:11px; font-weight:600; line-height:1.2; }
.xll-task-pill--purple { color:#7C3AED; background:rgba(124,58,237,.12); }
.xll-task-pill--blue { color:#2563EB; background:rgba(37,99,235,.12); }
.xll-task-pill--orange { color:#EA580C; background:rgba(234,88,12,.12); }
.xll-task-pill--green { color:${XLL_GREEN_DEEP}; background:${XLL_GREEN_SOFT}; }
.xll-task-pill--slate { color:#475569; background:rgba(71,85,105,.1); }
.xll-task-pill--gray { color:#64748B; background:rgba(100,116,139,.12); }
.xll-task-risk { font-variant-numeric:tabular-nums; }
.xll-task-risk--high { color:#F53F3F; background:rgba(245,63,63,.1); }
.xll-task-risk--medium { color:#FF7D00; background:rgba(255,125,0,.12); }
.xll-task-risk--low { color:#00B42A; background:rgba(0,180,42,.1); }
.xll-task-risk--none { color:#86909C; background:rgba(134,144,156,.12); }
.xll-task-risk-hint { display:flex; align-items:flex-start; gap:6px; margin:0 16px 10px; padding:8px 10px; border-radius:10px; background:${COLOR_PAGE}; border:1px solid rgba(0,0,0,.04); font-size:12px; line-height:1.45; color:${COLOR_TEXT_SEC}; }
.xll-task-risk-hint svg { flex-shrink:0; margin-top:1px; color:${COLOR_WARN}; }
.xll-task-chain { margin:0 16px 12px; padding:10px 12px; border-radius:10px; background:linear-gradient(135deg, rgba(122,185,41,.06) 0%, rgba(122,185,41,.02) 100%); border:1px solid rgba(122,185,41,.18); font-size:12px; line-height:1.5; color:${COLOR_TEXT_SEC}; }
.xll-task-chain strong { color:${COLOR_TEXT}; font-weight:600; }
.xll-task-chain-line { display:block; margin-top:4px; }
.xll-task-foot { display:flex; align-items:center; justify-content:space-between; gap:8px; padding:0 16px 14px; font-size:11px; color:${COLOR_MUTED}; font-variant-numeric:tabular-nums; }
.xll-task-sla { font-weight:600; }
.xll-task-sla.is-overdue { color:${COLOR_DANGER}; }
.xll-task-card--notify { opacity:.92; }
.xll-task-card--notify .xll-task-action { color:${COLOR_MUTED}; border-color:${COLOR_LINE}; background:${COLOR_PAGE}; }
.xll-biz-section { margin:0 14px 14px; background:${COLOR_BG}; border-radius:14px; padding:14px 12px 6px; box-shadow:0 2px 8px rgba(15,23,42,.04); border:1px solid rgba(0,0,0,.03); }
.xll-biz-section-title { font-size:13px; font-weight:600; color:${COLOR_MUTED}; margin-bottom:12px; padding-left:4px; letter-spacing:0.02em; }
.xll-biz-grid { display:grid; grid-template-columns:repeat(3,1fr); gap:12px 8px; }
.xll-biz-item { display:flex; flex-direction:column; align-items:center; gap:8px; padding:6px 2px 10px; border:none; background:transparent; cursor:pointer; touch-action:manipulation; position:relative; border-radius:12px; transition:background 0.15s ease; min-height:88px; -webkit-tap-highlight-color:transparent; }
.xll-biz-item:active { background:${COLOR_PAGE}; }
.xll-biz-item:focus-visible { outline:2px solid ${XLL_GREEN}; outline-offset:2px; }
.xll-biz-icon-wrap { position:relative; display:inline-flex; flex-shrink:0; overflow:visible; }
.xll-biz-icon { width:48px; height:48px; border-radius:12px; background:${COLOR_PAGE}; border:1px solid ${COLOR_LINE}; display:flex; align-items:center; justify-content:center; color:${XLL_GREEN_DEEP}; transition:transform 0.15s ease, box-shadow 0.15s ease; }
.xll-biz-item:active .xll-biz-icon { transform:scale(0.95); }
.xll-biz-label { font-size:12px; color:${COLOR_TEXT}; text-align:center; line-height:1.35; font-weight:500; }
.xll-biz-badge { position:absolute; top:-4px; right:-4px; min-width:18px; height:18px; padding:0 4px; border-radius:999px; background:${XLL_GREEN}; color:#fff; font-size:10px; font-weight:700; display:flex; align-items:center; justify-content:center; font-variant-numeric:tabular-nums; box-shadow:0 1px 4px rgba(122,185,41,.4); z-index:2; pointer-events:none; }
.xll-map-tabs { display:flex; background:${COLOR_BG}; border-bottom:1px solid ${COLOR_LINE}; }
.xll-map-tab { flex:1; min-height:44px; border:none; background:transparent; font-size:15px; color:${COLOR_TEXT_SEC}; cursor:pointer; position:relative; font-weight:500; touch-action:manipulation; transition:color 0.2s ease; }
.xll-map-tab.active { color:${XLL_GREEN}; font-weight:700; }
.xll-map-tab.active::after { content:''; position:absolute; left:20%; right:20%; bottom:0; height:3px; border-radius:3px 3px 0 0; background:${XLL_GREEN}; }
.xll-map-tab:focus-visible { outline:2px solid ${XLL_GREEN}; outline-offset:-2px; }
.xll-map-toolbar { display:flex; gap:10px; padding:10px 14px; background:${COLOR_BG}; align-items:center; border-bottom:1px solid ${COLOR_LINE}; }
.xll-map-search-wrap { flex:1; display:flex; align-items:center; gap:8px; min-height:44px; padding:0 12px; background:${COLOR_PAGE}; border-radius:12px; border:1px solid transparent; transition:border-color 0.2s ease, box-shadow 0.2s ease; }
.xll-map-search-wrap:focus-within { border-color:rgba(122,185,41,.45); box-shadow:0 0 0 3px ${XLL_GREEN_SOFT}; background:${COLOR_BG}; }
.xll-map-search-wrap svg { flex-shrink:0; color:${COLOR_MUTED}; }
.xll-map-search { flex:1; border:none; background:transparent; font-size:15px; color:${COLOR_TEXT}; outline:none; min-width:0; }
.xll-map-search::placeholder { color:${COLOR_MUTED}; }
.xll-map-filter { width:44px; height:44px; border:1px solid ${COLOR_LINE}; border-radius:12px; background:${COLOR_BG}; cursor:pointer; display:flex; align-items:center; justify-content:center; color:${COLOR_MUTED}; flex-shrink:0; touch-action:manipulation; transition:background 0.15s ease, border-color 0.15s ease; }
.xll-map-filter:active { background:${COLOR_PAGE}; border-color:${XLL_GREEN}; color:${XLL_GREEN}; }
.xll-map-filter:focus-visible { outline:2px solid ${XLL_GREEN}; outline-offset:2px; }
.xll-map-page { display:flex; flex-direction:column; min-height:min-content; }
.xll-map-area { margin:12px 14px 14px; height:min(420px, calc(100dvh - 320px)); min-height:240px; border-radius:14px; overflow:hidden; position:relative; background:linear-gradient(180deg,#e8f4e8 0%,#d4e8d4 50%,#c5dcc5 100%); border:1px solid ${COLOR_LINE}; box-shadow:inset 0 1px 4px rgba(0,0,0,.04); flex-shrink:0; }
.xll-map-placeholder { position:absolute; inset:0; display:flex; flex-direction:column; align-items:center; justify-content:center; color:${COLOR_MUTED}; font-size:13px; gap:8px; pointer-events:none; text-align:center; padding:0 24px; line-height:1.5; }
.xll-map-full { position:absolute; top:12px; left:12px; min-width:52px; min-height:52px; padding:8px 6px; border-radius:12px; background:rgba(255,255,255,.95); border:1px solid ${COLOR_LINE}; box-shadow:0 2px 8px rgba(0,0,0,.08); display:flex; flex-direction:column; align-items:center; justify-content:center; gap:4px; font-size:11px; font-weight:600; color:${XLL_GREEN}; cursor:pointer; z-index:2; touch-action:manipulation; transition:transform 0.15s ease; }
.xll-map-full:active { transform:scale(0.96); }
.xll-map-full:focus-visible { outline:2px solid ${XLL_GREEN}; outline-offset:2px; }
.xll-map-marker { position:absolute; width:36px; height:36px; border-radius:50%; background:${COLOR_BG}; border:2px solid ${XLL_GREEN}; box-shadow:0 2px 8px rgba(0,0,0,.15); display:flex; align-items:center; justify-content:center; color:${XLL_GREEN}; z-index:1; }
.xll-map-marker--station { border-color:#2563EB; color:#2563EB; }
.xll-map-marker--repair { border-color:#FF7D00; color:#FF7D00; }
.xll-map-marker--cluster { width:44px; height:44px; font-size:13px; font-weight:700; border-width:2px; cursor:pointer; z-index:3; }
.xll-map-marker-btn { border:none; padding:0; cursor:pointer; touch-action:manipulation; transition:transform 0.15s ease; }
.xll-map-marker-btn:active { transform:scale(0.94); }
.xll-map-marker-btn:focus-visible { outline:2px solid ${XLL_GREEN}; outline-offset:2px; }
.xll-map-mode-bar { display:flex; gap:8px; padding:10px 14px; background:${COLOR_BG}; border-bottom:1px solid ${COLOR_LINE}; overflow-x:auto; -webkit-overflow-scrolling:touch; }
.xll-map-mode-bar::-webkit-scrollbar { display:none; }
.xll-map-mode-btn { flex-shrink:0; min-height:36px; padding:0 14px; border-radius:999px; border:1px solid ${COLOR_LINE}; background:${COLOR_PAGE}; color:${COLOR_TEXT_SEC}; font-size:13px; font-weight:600; cursor:pointer; touch-action:manipulation; }
.xll-map-mode-btn.active { background:${XLL_GREEN_SOFT}; border-color:rgba(122,185,41,.45); color:${XLL_GREEN}; }
.xll-map-role-bar { display:flex; align-items:center; gap:8px; padding:8px 14px 10px; background:${COLOR_BG}; border-bottom:1px solid ${COLOR_LINE}; overflow-x:auto; -webkit-overflow-scrolling:touch; }
.xll-map-role-bar::-webkit-scrollbar { display:none; }
.xll-map-role-label { flex-shrink:0; font-size:12px; color:${COLOR_MUTED}; font-weight:600; }
.xll-map-role-chip { flex-shrink:0; min-height:32px; padding:0 12px; border-radius:999px; border:1px solid ${COLOR_LINE}; background:${COLOR_BG}; color:${COLOR_TEXT_SEC}; font-size:12px; font-weight:600; cursor:pointer; touch-action:manipulation; }
.xll-map-role-chip.active { background:${COLOR_TEXT}; border-color:${COLOR_TEXT}; color:#fff; }
.xll-map-role-chip--external.active { background:#2563EB; border-color:#2563EB; }
.xll-map-list { padding:0 14px 14px; display:flex; flex-direction:column; gap:10px; }
.xll-map-list-head { display:flex; align-items:center; justify-content:space-between; padding:12px 0 4px; font-size:13px; color:${COLOR_MUTED}; }
.xll-map-list-card { width:100%; text-align:left; border:1px solid ${COLOR_LINE}; border-radius:12px; background:${COLOR_BG}; padding:14px; cursor:pointer; touch-action:manipulation; box-shadow:0 1px 4px rgba(15,23,42,.04); }
.xll-map-list-card:active { background:${COLOR_PAGE}; }
.xll-map-list-card-title { font-size:16px; font-weight:700; color:${COLOR_TEXT}; margin-bottom:4px; }
.xll-map-list-card-sub { font-size:13px; color:${COLOR_TEXT_SEC}; margin-bottom:8px; }
.xll-map-list-card-meta { display:flex; align-items:center; justify-content:space-between; font-size:12px; color:${COLOR_MUTED}; }
.xll-map-stat-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:10px; padding:12px 14px 14px; }
.xll-map-stat-card { border:1px solid ${COLOR_LINE}; border-radius:12px; background:${COLOR_BG}; padding:14px; text-align:left; cursor:pointer; touch-action:manipulation; box-shadow:0 1px 4px rgba(15,23,42,.04); }
.xll-map-stat-card:active { background:${COLOR_PAGE}; }
.xll-map-stat-val { font-size:24px; font-weight:800; color:${COLOR_TEXT}; font-variant-numeric:tabular-nums; letter-spacing:-0.02em; margin-bottom:4px; }
.xll-map-stat-label { font-size:12px; color:${COLOR_MUTED}; font-weight:600; }
.xll-map-stat-hint { margin-top:6px; font-size:11px; color:${XLL_GREEN}; font-weight:600; }
.xll-map-detail { display:flex; flex-direction:column; min-height:min-content; }
.xll-map-detail-head { display:flex; align-items:center; gap:8px; padding:8px 14px; border-bottom:1px solid ${COLOR_LINE}; background:${COLOR_BG}; }
.xll-map-detail-back { min-width:44px; min-height:44px; border:none; background:transparent; color:${COLOR_TEXT}; cursor:pointer; display:flex; align-items:center; justify-content:center; touch-action:manipulation; }
.xll-map-detail-title { flex:1; min-width:0; font-size:16px; font-weight:700; color:${COLOR_TEXT}; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.xll-map-detail-hero { margin:12px 14px 0; padding:16px; border-radius:12px; background:linear-gradient(135deg,${XLL_GREEN_SOFT} 0%,${COLOR_BG} 100%); border:1px solid rgba(122,185,41,.2); }
.xll-map-detail-hero-name { font-size:18px; font-weight:800; color:${COLOR_TEXT}; margin-bottom:4px; }
.xll-map-detail-hero-sub { font-size:13px; color:${COLOR_TEXT_SEC}; margin-bottom:8px; }
.xll-map-detail-tag { display:inline-flex; align-items:center; min-height:24px; padding:0 10px; border-radius:999px; background:${XLL_GREEN_SOFT}; color:${XLL_GREEN}; font-size:12px; font-weight:700; }
.xll-map-detail-tabs { display:flex; gap:0; margin:12px 14px 0; border-bottom:1px solid ${COLOR_LINE}; overflow-x:auto; -webkit-overflow-scrolling:touch; }
.xll-map-detail-tab { flex-shrink:0; min-height:44px; padding:0 14px; border:none; background:transparent; font-size:14px; font-weight:600; color:${COLOR_MUTED}; cursor:pointer; position:relative; touch-action:manipulation; }
.xll-map-detail-tab.active { color:${XLL_GREEN}; }
.xll-map-detail-tab.active::after { content:''; position:absolute; left:14px; right:14px; bottom:0; height:3px; border-radius:3px 3px 0 0; background:${XLL_GREEN}; }
.xll-map-detail-body { padding:12px 14px 16px; }
.xll-map-detail-kv { display:flex; justify-content:space-between; gap:12px; padding:12px 0; border-bottom:1px solid ${COLOR_LINE}; font-size:14px; }
.xll-map-detail-kv:last-child { border-bottom:none; }
.xll-map-detail-kv-label { color:${COLOR_MUTED}; flex-shrink:0; }
.xll-map-detail-kv-value { color:${COLOR_TEXT}; font-weight:500; text-align:right; word-break:break-all; }
.xll-map-empty { padding:32px 24px; text-align:center; color:${COLOR_MUTED}; font-size:14px; line-height:1.6; }
.xll-map-filter-tag { display:inline-flex; align-items:center; gap:6px; margin:0 14px 8px; padding:6px 10px; border-radius:999px; background:${XLL_GREEN_SOFT}; color:${XLL_GREEN}; font-size:12px; font-weight:600; }
.xll-map-brand { position:absolute; left:8px; bottom:8px; font-size:10px; color:${COLOR_MUTED}; background:rgba(255,255,255,.85); padding:3px 8px; border-radius:6px; border:1px solid ${COLOR_LINE}; }
.xll-mine-hero { margin:12px 14px 0; padding:20px 16px; background:linear-gradient(135deg, ${XLL_GREEN} 0%, ${XLL_GREEN_DEEP} 100%); border-radius:14px; display:flex; align-items:center; gap:14px; box-shadow:0 4px 16px rgba(122,185,41,.3); }
.xll-mine-avatar { width:56px; height:56px; border-radius:50%; background:rgba(255,255,255,.25); border:2px solid rgba(255,255,255,.6); display:flex; align-items:center; justify-content:center; color:#fff; font-size:22px; font-weight:800; flex-shrink:0; }
.xll-mine-hero-info { min-width:0; }
.xll-mine-hero-name { font-size:18px; font-weight:700; color:#fff; margin-bottom:4px; }
.xll-mine-hero-role { font-size:13px; color:rgba(255,255,255,.85); }
.xll-mine-card { margin:12px 14px; background:${COLOR_BG}; border-radius:14px; overflow:hidden; box-shadow:0 2px 8px rgba(15,23,42,.04); border:1px solid rgba(0,0,0,.03); }
.xll-mine-row { display:flex; align-items:center; justify-content:space-between; padding:16px; border-bottom:1px solid ${COLOR_LINE}; font-size:15px; min-height:52px; }
.xll-mine-row:last-child { border-bottom:none; }
.xll-mine-label { color:${COLOR_MUTED}; font-size:14px; }
.xll-mine-value { color:${COLOR_TEXT}; font-weight:500; font-size:15px; text-align:right; max-width:60%; word-break:break-all; }
.xll-logout { display:block; width:calc(100% - 28px); margin:20px 14px 8px; min-height:48px; border:none; border-radius:999px; background:${COLOR_BG}; color:${COLOR_DANGER}; font-size:16px; font-weight:600; cursor:pointer; border:1px solid rgba(245,63,63,.25); touch-action:manipulation; transition:background 0.15s ease; }
.xll-logout:active { background:rgba(245,63,63,.06); }
.xll-logout:focus-visible { outline:2px solid ${COLOR_DANGER}; outline-offset:2px; }
.xll-tabbar { flex-shrink:0; display:flex; height:52px; padding-bottom:env(safe-area-inset-bottom,0); background:${COLOR_BG}; border-top:1px solid ${COLOR_LINE}; z-index:30; }
.xll-tabbar-btn { flex:1; border:none; background:transparent; font-size:13px; color:${COLOR_MUTED}; cursor:pointer; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:4px; padding-top:4px; touch-action:manipulation; transition:color 0.2s ease; min-height:52px; }
.xll-tabbar-btn.active { color:${XLL_GREEN}; font-weight:700; }
.xll-tabbar-btn:focus-visible { outline:2px solid ${XLL_GREEN}; outline-offset:-2px; }
.xll-tabbar-icon { display:flex; align-items:center; justify-content:center; width:24px; height:24px; }
.xll-login-wrap { flex:1; min-height:0; display:flex; flex-direction:column; align-items:center; justify-content:center; padding:32px 24px; }
.xll-login-logo { width:88px; height:88px; border-radius:22px; background:linear-gradient(135deg,${XLL_GREEN} 0%,${XLL_GREEN_DEEP} 100%); display:flex; align-items:center; justify-content:center; color:#fff; font-size:28px; font-weight:800; margin-bottom:16px; box-shadow:0 12px 28px rgba(122,185,41,.35); }
.xll-login-name { font-size:24px; font-weight:800; color:${COLOR_TEXT}; margin-bottom:8px; letter-spacing:-0.02em; }
.xll-login-desc { font-size:14px; color:${COLOR_MUTED}; margin-bottom:40px; text-align:center; line-height:1.6; }
.xll-login-btn { width:100%; max-width:280px; min-height:48px; border:none; border-radius:999px; background:${XLL_GREEN}; color:#fff; font-size:16px; font-weight:700; cursor:pointer; touch-action:manipulation; box-shadow:0 4px 14px rgba(122,185,41,.35); transition:transform 0.15s ease, opacity 0.15s ease; }
.xll-login-btn:active:not(:disabled) { transform:scale(0.98); }
.xll-login-btn:disabled { opacity:0.65; cursor:not-allowed; }
.xll-login-btn:focus-visible { outline:2px solid ${XLL_GREEN_DEEP}; outline-offset:3px; }
.xll-sub-page { padding:14px; }
.xll-sub-hero { background:linear-gradient(135deg, var(--xll-accent, ${XLL_GREEN}) 0%, ${XLL_GREEN_DEEP} 100%); border-radius:14px; padding:16px; margin-bottom:12px; color:#fff; box-shadow:0 4px 16px rgba(122,185,41,.25); }
.xll-sub-hero-tag { display:inline-flex; font-size:11px; font-weight:600; padding:2px 10px; border-radius:999px; background:rgba(255,255,255,.22); margin-bottom:8px; }
.xll-sub-hero-title { font-size:17px; font-weight:700; line-height:1.4; }
.xll-sub-card { background:${COLOR_BG}; border-radius:14px; padding:16px; margin-bottom:12px; box-shadow:0 2px 8px rgba(15,23,42,.04); border:1px solid rgba(0,0,0,.03); }
.xll-sub-section-title { font-size:13px; font-weight:600; color:${COLOR_MUTED}; margin-bottom:12px; }
.xll-sub-foot { display:flex; gap:10px; margin-top:16px; }
.xll-sub-btn { flex:1; min-height:48px; border-radius:12px; font-size:15px; font-weight:600; cursor:pointer; border:none; touch-action:manipulation; transition:transform 0.15s ease; }
.xll-sub-btn:active { transform:scale(0.98); }
.xll-sub-btn:focus-visible { outline:2px solid ${XLL_GREEN}; outline-offset:2px; }
.xll-sub-btn-primary { background:${XLL_GREEN}; color:#fff; box-shadow:0 4px 12px rgba(122,185,41,.3); }
.xll-sub-btn-ghost { background:${COLOR_PAGE}; color:${COLOR_TEXT_SEC}; border:1px solid ${COLOR_LINE}; }
.xll-prd-doc { font-size:13px; color:${COLOR_TEXT_SEC}; line-height:1.65; }
.xll-prd-h2 { font-size:15px; font-weight:700; color:${COLOR_TEXT}; margin:16px 0 8px; }
.xll-prd-h2:first-child { margin-top:0; }
.xll-prd-h3 { font-size:14px; font-weight:600; color:${COLOR_TEXT}; margin:12px 0 6px; }
.xll-prd-p { margin:0 0 8px; }
.xll-prd-ul { margin:0 0 8px; padding-left:18px; }
.xll-prd-li { margin-bottom:4px; }
.xll-prd-meta { font-size:12px; color:${COLOR_MUTED}; margin-bottom:12px; padding-bottom:12px; border-bottom:1px dashed ${COLOR_LINE}; }
.xll-prd-tag { display:inline-block; font-size:11px; font-weight:600; color:${XLL_GREEN_DEEP}; background:${XLL_GREEN_SOFT}; padding:2px 8px; border-radius:999px; margin-bottom:8px; }
.xll-prd-highlight { background:${XLL_GREEN_SOFT}; border-top:2px solid ${XLL_GREEN}; padding:10px 12px; border-radius:8px; margin:8px 0; font-size:13px; color:${COLOR_TEXT_SEC}; }
@keyframes xll-card-in { from { opacity:0; transform:translateY(8px); } to { opacity:1; transform:translateY(0); } }
.tc-scroll { flex:1; overflow-y:auto; -webkit-overflow-scrolling:touch; overscroll-behavior:contain; padding-bottom:88px; }
.tc-hero { margin:12px 14px 0; padding:18px 16px 16px; border-radius:16px; background:linear-gradient(135deg,#F97316 0%,#EA580C 100%); color:#fff; box-shadow:0 10px 28px rgba(249,115,22,.35); }
.tc-hero-label { font-size:13px; opacity:.92; margin-bottom:6px; }
.tc-hero-amount { font-size:36px; font-weight:800; line-height:1.1; font-variant-numeric:tabular-nums; letter-spacing:-.02em; }
.tc-hero-sub { margin-top:14px; padding-top:12px; border-top:1px solid rgba(255,255,255,.22); display:flex; align-items:center; justify-content:space-between; gap:12px; }
.tc-hero-compare { font-size:12px; opacity:.9; line-height:1.45; }
.tc-hero-detail-btn { flex-shrink:0; min-height:32px; padding:0 12px; border:1px solid rgba(255,255,255,.45); border-radius:999px; background:rgba(255,255,255,.14); color:#fff; font-size:12px; font-weight:600; cursor:pointer; }
.tc-hero-meta { display:flex; gap:16px; margin-top:10px; font-size:12px; opacity:.88; }
.tc-section { margin:12px 14px 0; background:${COLOR_BG}; border-radius:14px; overflow:hidden; box-shadow:0 2px 8px rgba(15,23,42,.04); border:1px solid rgba(0,0,0,.04); }
.tc-section-head { display:flex; align-items:center; justify-content:space-between; padding:12px 14px; border-bottom:1px solid ${COLOR_LINE}; }
.tc-section-head + .tc-vehicle-card { margin-top:12px; }
.tc-section-head + .hc-settle-body { margin-top:12px; }
.tc-section-title { font-size:15px; font-weight:700; color:${COLOR_TEXT}; }
.tc-section-badge { font-size:11px; font-weight:600; color:#F97316; background:rgba(249,115,22,.12); padding:2px 8px; border-radius:999px; }
.tc-kv-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:10px 14px; padding:12px 14px 14px; }
.tc-kv-item { min-width:0; }
.tc-kv-label { font-size:11px; color:${COLOR_MUTED}; margin-bottom:3px; }
.tc-kv-value { font-size:13px; color:${COLOR_TEXT}; font-weight:500; line-height:1.4; word-break:break-all; }
.tc-kv-value--full { grid-column:1 / -1; }
.tc-vehicle-card { margin:0 14px 10px; padding:12px 14px; background:${COLOR_PAGE}; border-radius:12px; border:1px solid ${COLOR_LINE}; }
.tc-vehicle-card:last-child { margin-bottom:14px; }
.tc-vehicle-head { display:flex; align-items:center; justify-content:space-between; gap:8px; margin-bottom:10px; }
.tc-vehicle-plate { font-size:15px; font-weight:700; color:${COLOR_TEXT}; }
.tc-vehicle-model { font-size:12px; color:${COLOR_MUTED}; margin-top:2px; }
.tc-vehicle-idx { font-size:11px; font-weight:700; color:#F97316; background:rgba(249,115,22,.12); padding:2px 8px; border-radius:999px; flex-shrink:0; }
.tc-vehicle-amount-row { display:flex; align-items:baseline; justify-content:space-between; gap:8px; padding:10px 12px; background:${COLOR_BG}; border-radius:10px; margin-bottom:8px; }
.tc-vehicle-amount-label { font-size:12px; color:${COLOR_MUTED}; }
.tc-vehicle-amount-val { font-size:18px; font-weight:800; color:#F97316; font-variant-numeric:tabular-nums; }
.tc-vehicle-kv { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:6px 10px; }
.tc-vehicle-kv-item { font-size:12px; color:${COLOR_TEXT_SEC}; }
.tc-vehicle-kv-item span { color:${COLOR_MUTED}; }
.tc-service-toggle { width:100%; margin-top:8px; min-height:32px; border:none; background:transparent; color:${XLL_GREEN_DEEP}; font-size:12px; font-weight:600; cursor:pointer; text-align:left; padding:0; }
.tc-service-list { margin-top:8px; padding-top:8px; border-top:1px dashed ${COLOR_LINE}; }
.tc-service-row { display:grid; grid-template-columns:1fr auto; gap:4px 8px; font-size:12px; padding:6px 0; border-bottom:1px solid rgba(0,0,0,.04); }
.tc-service-name { color:${COLOR_TEXT}; font-weight:500; }
.tc-service-amt { color:#F97316; font-weight:700; text-align:right; font-variant-numeric:tabular-nums; }
.tc-service-sub { grid-column:1 / -1; color:${COLOR_MUTED}; font-size:11px; }
.tc-timeline { padding:4px 14px 14px; }
.tc-flow { padding:4px 14px 16px; }
.tc-flow-step { display:flex; gap:12px; position:relative; padding-bottom:18px; }
.tc-flow-step:last-child { padding-bottom:4px; }
.tc-flow-step:not(:last-child)::before { content:''; position:absolute; left:17px; top:40px; bottom:0; width:2px; background:${COLOR_LINE}; }
.tc-flow-avatar { width:36px; height:36px; border-radius:50%; flex-shrink:0; display:flex; align-items:center; justify-content:center; font-size:11px; font-weight:700; color:#fff; background:linear-gradient(145deg,#3B82F6 0%,#2563EB 100%); letter-spacing:-0.02em; }
.tc-flow-avatar--pending { background:${COLOR_BG}; color:#2563EB; border:2px solid #2563EB; box-sizing:border-box; }
.tc-flow-body { flex:1; min-width:0; padding-top:2px; }
.tc-flow-head { display:flex; align-items:flex-start; justify-content:space-between; gap:10px; }
.tc-flow-title { font-size:14px; font-weight:700; color:${COLOR_TEXT}; line-height:1.4; flex:1; min-width:0; }
.tc-flow-tag { flex-shrink:0; display:inline-flex; align-items:center; justify-content:center; min-height:22px; padding:0 8px; border-radius:999px; font-size:11px; font-weight:600; line-height:1; white-space:nowrap; }
.tc-flow-tag--ok { color:${COLOR_SUCCESS}; background:rgba(0,180,42,.1); }
.tc-flow-tag--pending { color:#2563EB; background:${COLOR_BG}; border:1px solid #2563EB; }
.tc-flow-tag--reject { color:${COLOR_DANGER}; background:rgba(245,63,63,.1); }
.tc-flow-meta { margin-top:6px; font-size:12px; color:${COLOR_MUTED}; line-height:1.45; font-variant-numeric:tabular-nums; }
.tc-flow-comment { margin-top:10px; display:flex; align-items:flex-start; gap:8px; padding:10px 12px; border:1px solid ${COLOR_LINE}; border-radius:10px; background:${COLOR_BG}; font-size:13px; color:${COLOR_TEXT_SEC}; line-height:1.5; }
.tc-flow-comment-icon { flex-shrink:0; color:${COLOR_MUTED}; margin-top:1px; display:flex; }
.tc-step { display:flex; gap:12px; position:relative; padding-bottom:16px; }
.tc-step:not(:last-child)::before { content:''; position:absolute; left:9px; top:22px; bottom:0; width:2px; background:${COLOR_LINE}; }
.tc-step-dot { width:20px; height:20px; border-radius:50%; flex-shrink:0; display:flex; align-items:center; justify-content:center; font-size:10px; font-weight:700; margin-top:1px; }
.tc-step-dot.done { background:${COLOR_SUCCESS}; color:#fff; }
.tc-step-dot.wait { background:rgba(249,115,22,.12); color:#F97316; border:2px solid #F97316; box-sizing:border-box; }
.tc-step-title { font-size:14px; font-weight:600; color:${COLOR_TEXT}; }
.tc-step-meta { font-size:12px; color:${COLOR_MUTED}; margin-top:4px; line-height:1.5; }
.tc-action-bar { position:absolute; left:0; right:0; bottom:0; z-index:20; display:flex; gap:8px; padding:10px 12px calc(10px + env(safe-area-inset-bottom,0px)); background:rgba(255,255,255,.96); border-top:1px solid ${COLOR_LINE}; backdrop-filter:blur(8px); }
.tc-action-bar--view { justify-content:flex-end; }
.tc-btn { flex:1; min-height:44px; border-radius:12px; font-size:14px; font-weight:700; cursor:pointer; border:none; touch-action:manipulation; }
.tc-btn-comment { flex:0 0 auto; min-width:64px; background:${COLOR_BG}; color:${COLOR_TEXT_SEC}; border:1px solid ${COLOR_LINE}; }
.tc-btn-terminate { background:${COLOR_PAGE}; color:${COLOR_WARN}; border:1px solid rgba(255,125,0,.3); }
.tc-btn-reject { background:${COLOR_PAGE}; color:${COLOR_DANGER}; border:1px solid rgba(245,63,63,.25); }
.tc-btn-approve { background:linear-gradient(135deg,${XLL_GREEN} 0%,${XLL_GREEN_DEEP} 100%); color:#fff; box-shadow:0 4px 14px rgba(122,185,41,.3); }
.tc-btn:active { opacity:.92; transform:scale(.98); }
.tc-approval-form { display:flex; flex-direction:column; gap:16px; padding:4px 0 8px; }
.tc-form-field { display:flex; flex-direction:column; gap:8px; }
.tc-form-label { font-size:14px; color:${COLOR_TEXT}; font-weight:500; }
.tc-form-label-required::before { content:'*'; color:${COLOR_DANGER}; margin-right:4px; }
.tc-notify-group { display:flex; flex-wrap:wrap; gap:16px; }
.tc-notify-item { display:inline-flex; align-items:center; gap:6px; font-size:14px; color:${COLOR_TEXT_SEC}; cursor:pointer; min-height:32px; }
.tc-notify-item input { width:16px; height:16px; accent-color:${XLL_GREEN}; }
.tc-notify-item.disabled { opacity:.55; cursor:not-allowed; }
.tc-upload-btn { display:inline-flex; align-items:center; gap:6px; min-height:36px; padding:0 14px; border:1px dashed ${COLOR_LINE}; border-radius:8px; background:${COLOR_PAGE}; color:${COLOR_TEXT_SEC}; font-size:13px; cursor:pointer; }
.tc-upload-hint { font-size:12px; color:${COLOR_MUTED}; line-height:1.55; margin-top:4px; }
.tc-file-list { display:flex; flex-direction:column; gap:6px; margin-top:6px; }
.tc-file-item { display:flex; align-items:center; justify-content:space-between; gap:8px; padding:8px 10px; background:${COLOR_PAGE}; border-radius:8px; font-size:13px; color:${COLOR_TEXT}; }
.tc-file-remove { border:none; background:transparent; color:${COLOR_MUTED}; font-size:12px; cursor:pointer; padding:4px; }
.tc-select-person { min-height:44px; padding:0 14px; border:1px solid ${COLOR_LINE}; border-radius:10px; background:${COLOR_BG}; color:${COLOR_MUTED}; font-size:14px; text-align:left; cursor:pointer; width:100%; }
.tc-select-person.has-value { color:${COLOR_TEXT}; }
.tc-cc-chips { display:flex; flex-wrap:wrap; gap:8px; margin-top:8px; }
.tc-cc-chip { display:inline-flex; align-items:center; gap:4px; padding:4px 10px; border-radius:999px; background:${XLL_GREEN_SOFT}; color:${XLL_GREEN_DEEP}; font-size:12px; font-weight:600; }
.tc-cc-chip button { border:none; background:transparent; color:inherit; cursor:pointer; padding:0 2px; font-size:14px; line-height:1; }
.tc-textarea { width:100%; min-height:96px; padding:10px 12px; border:1px solid ${COLOR_LINE}; border-radius:10px; font-size:14px; color:${COLOR_TEXT}; resize:vertical; box-sizing:border-box; font-family:inherit; outline:none; }
.tc-textarea:focus { border-color:rgba(122,185,41,.55); box-shadow:0 0 0 3px rgba(122,185,41,.12); }
.tc-textarea-counter { text-align:right; font-size:12px; color:${COLOR_MUTED}; margin-top:4px; }
.tc-drawer-foot { display:flex; gap:10px; padding-top:12px; margin-top:4px; border-top:1px solid ${COLOR_LINE}; }
.tc-modal-foot { display:flex; justify-content:flex-end; gap:10px; padding-top:16px; margin-top:8px; border-top:1px solid ${COLOR_LINE}; }
.tc-modal-foot-btn { min-height:36px; min-width:72px; padding:0 16px; border-radius:6px; font-size:14px; font-weight:500; cursor:pointer; }
.tc-modal-foot-cancel { background:#fff; color:${COLOR_TEXT}; border:1px solid ${COLOR_LINE}; }
.tc-modal-foot-confirm { background:#1677ff; color:#fff; border:none; }
.tc-upload-hint strong { color:#1677ff; font-weight:600; }
.tc-drawer-foot-btn { flex:1; min-height:44px; border-radius:10px; font-size:15px; font-weight:600; cursor:pointer; border:none; }
.tc-drawer-foot-cancel { background:${COLOR_BG}; color:${COLOR_TEXT_SEC}; border:1px solid ${COLOR_LINE}; }
.tc-drawer-foot-confirm { background:#1677ff; color:#fff; }
.tc-person-list { display:flex; flex-direction:column; gap:8px; }
.tc-person-item { min-height:48px; padding:0 14px; border:1px solid ${COLOR_LINE}; border-radius:10px; background:${COLOR_BG}; font-size:14px; color:${COLOR_TEXT}; text-align:left; cursor:pointer; }
.tc-person-item.selected { border-color:${XLL_GREEN}; background:${XLL_GREEN_SOFT}; color:${XLL_GREEN_DEEP}; font-weight:600; }
.tc-drawer-row { display:flex; align-items:center; justify-content:space-between; gap:12px; padding:12px 0; border-bottom:1px solid ${COLOR_LINE}; font-size:14px; }
.tc-drawer-row-val.highlight { color:#F97316; font-weight:700; }
.tc-drawer-total { margin-top:8px; padding-top:12px; border-top:2px solid ${COLOR_LINE}; display:flex; justify-content:space-between; font-size:15px; font-weight:700; }
.tc-drawer-total span:last-child { color:#F97316; font-size:18px; font-variant-numeric:tabular-nums; }
.tc-hero--return { background:linear-gradient(135deg,#8B5CF6 0%,#7C3AED 100%); box-shadow:0 10px 28px rgba(139,92,246,.35); }
.tc-hero-plate-key-wrap { margin-top:10px; margin-bottom:2px; display:flex; flex-direction:column; align-items:flex-start; gap:6px; }
.tc-hero-plate-key-label { font-size:11px; font-weight:600; letter-spacing:0.06em; text-transform:uppercase; opacity:.88; }
.tc-hero-plate-key { display:inline-block; font-size:24px; font-weight:800; color:#fff; letter-spacing:0.06em; font-variant-numeric:tabular-nums; line-height:1.2; padding:6px 14px; border-radius:10px; background:rgba(255,255,255,.16); border:1.5px solid rgba(255,255,255,.42); box-shadow:0 4px 14px rgba(15,23,42,.12); }
.tc-section-badge--purple { color:#8B5CF6; background:rgba(139,92,246,.12); }
.hc-stat-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:10px; padding:12px 14px 14px; }
.hc-stat-card { padding:12px; background:${COLOR_PAGE}; border-radius:10px; border:1px solid ${COLOR_LINE}; }
.hc-stat-label { font-size:11px; color:${COLOR_MUTED}; margin-bottom:6px; }
.hc-stat-val { font-size:18px; font-weight:800; font-variant-numeric:tabular-nums; color:${COLOR_TEXT}; }
.hc-stat-val.warn { color:#F97316; }
.hc-stat-val.success { color:${COLOR_SUCCESS}; }
.hc-stat-val.danger { color:${COLOR_DANGER}; }
.hc-insurance-row { display:flex; gap:8px; margin-top:4px; }
.hc-insurance-item { flex:1; min-width:0; display:flex; align-items:center; justify-content:space-between; gap:4px; padding:6px 8px; background:rgba(255,255,255,.14); border-radius:8px; font-size:11px; line-height:1.2; }
.hc-insurance-icon { width:18px; height:18px; border-radius:999px; display:inline-flex; align-items:center; justify-content:center; font-size:11px; font-weight:700; flex-shrink:0; line-height:1; }
.hc-insurance-icon--yes { background:rgba(255,255,255,.32); color:#fff; }
.hc-insurance-icon--no { background:rgba(0,0,0,.18); color:rgba(255,255,255,.55); }
.hc-group-block { padding:0 14px 12px; }
.hc-group-head { display:flex; align-items:center; justify-content:space-between; gap:8px; padding:10px 0; cursor:pointer; touch-action:manipulation; }
.hc-group-title { font-size:14px; font-weight:700; color:${COLOR_TEXT}; }
.hc-group-meta { font-size:11px; color:${COLOR_MUTED}; margin-top:2px; }
.hc-fee-row { display:flex; align-items:center; justify-content:space-between; gap:10px; padding:10px 12px; background:${COLOR_PAGE}; border-radius:8px; margin-bottom:6px; font-size:13px; }
.hc-fee-row-name { color:${COLOR_TEXT_SEC}; flex:1; min-width:0; }
.hc-fee-row-amt { font-weight:700; color:#8B5CF6; font-variant-numeric:tabular-nums; flex-shrink:0; }
.hc-settle-body { padding:0 14px 4px; }
.hc-settle-total { margin:0 14px 12px; padding:12px 14px; background:rgba(139,92,246,.08); border-radius:10px; border:1px solid rgba(139,92,246,.14); display:flex; align-items:center; justify-content:space-between; gap:12px; }
.hc-settle-total-label { font-size:13px; font-weight:600; color:${COLOR_TEXT_SEC}; }
.hc-settle-total-val { font-size:18px; font-weight:800; color:#8B5CF6; font-variant-numeric:tabular-nums; flex-shrink:0; }
.hc-fee-table { border:1px solid ${COLOR_LINE}; border-radius:10px; overflow:hidden; margin-bottom:8px; }
.hc-fee-table-scroll { overflow-x:auto; -webkit-overflow-scrolling:touch; margin-bottom:8px; border-radius:10px; }
.hc-fee-table-scroll .hc-fee-table { margin-bottom:0; min-width:340px; }
.hc-fee-table-head, .hc-fee-table-row { display:grid; grid-template-columns:44px minmax(0,1fr) 96px; align-items:center; gap:0; }
.hc-fee-table--ops .hc-fee-table-head, .hc-fee-table--ops .hc-fee-table-row { grid-template-columns:32px minmax(96px,1.35fr) 82px 82px; align-items:stretch; }
.hc-fee-table-head { background:${COLOR_PAGE}; border-bottom:1px solid ${COLOR_LINE}; font-size:11px; font-weight:600; color:${COLOR_MUTED}; }
.hc-fee-table-row { border-bottom:1px solid ${COLOR_LINE}; font-size:12px; color:${COLOR_TEXT}; background:${COLOR_BG}; }
.hc-fee-table-row:last-child { border-bottom:none; }
.hc-fee-table-row--custom { background:rgba(139,92,246,.03); }
.hc-fee-table-col { padding:10px 8px; min-width:0; }
.hc-fee-table-col--seq { text-align:center; color:${COLOR_MUTED}; display:flex; align-items:center; justify-content:center; }
.hc-fee-table-col--item { display:flex; align-items:center; gap:6px; flex-wrap:wrap; line-height:1.4; }
.hc-fee-table-col--amt { text-align:right; font-weight:700; color:#8B5CF6; font-variant-numeric:tabular-nums; padding-right:10px; }
.hc-fee-table-col--money { display:flex; align-items:center; justify-content:flex-end; padding:8px 6px; }
.hc-fee-table-col--deduct-head { display:inline-flex; align-items:center; justify-content:flex-end; gap:4px; padding-right:4px; }
.hc-fee-money-field { display:flex; align-items:center; justify-content:flex-end; gap:4px; width:100%; min-width:0; }
.hc-fee-money-input { flex:1; min-width:0; max-width:64px; text-align:right; padding:6px 8px; border-radius:6px; border:1px solid ${COLOR_LINE}; background:${COLOR_PAGE}; font-size:11px; font-weight:600; color:${COLOR_TEXT}; font-variant-numeric:tabular-nums; line-height:1.2; }
.hc-fee-money-unit { flex-shrink:0; font-size:10px; color:${COLOR_MUTED}; }
.hc-fee-info-btn { display:inline-flex; align-items:center; justify-content:center; width:16px; height:16px; padding:0; border:none; background:transparent; color:${COLOR_MUTED}; cursor:pointer; flex-shrink:0; touch-action:manipulation; }
.hc-fee-info-btn:focus-visible { outline:2px solid #8B5CF6; outline-offset:2px; border-radius:50%; }
.hc-fee-table-tag { font-size:10px; font-weight:600; color:#8B5CF6; background:rgba(139,92,246,.1); padding:1px 6px; border-radius:999px; flex-shrink:0; }
.hc-fee-ops-footnote { margin-top:8px; font-size:11px; color:${COLOR_MUTED}; line-height:1.45; }
.hc-safety-stat-grid { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:8px; margin-bottom:4px; }
.hc-safety-stat-item { padding:12px 8px; background:${COLOR_PAGE}; border-radius:10px; border:1px solid ${COLOR_LINE}; text-align:center; }
.hc-safety-stat-label { font-size:11px; color:${COLOR_MUTED}; margin-bottom:6px; }
.hc-safety-stat-val { font-size:16px; font-weight:800; color:${COLOR_TEXT}; font-variant-numeric:tabular-nums; }
.hc-safety-stat-val.warn { color:#F97316; }
.hc-sub-block { margin-top:8px; padding-top:8px; border-top:1px dashed ${COLOR_LINE}; }
.hc-violation-card { margin:0 0 8px; padding:10px 12px; background:${COLOR_PAGE}; border-radius:10px; border:1px solid ${COLOR_LINE}; font-size:12px; color:${COLOR_TEXT_SEC}; line-height:1.55; }
.tc-hero--bill { background:linear-gradient(135deg,#0EA5E9 0%,#0284C7 100%); box-shadow:0 10px 28px rgba(14,165,233,.35); }
.tc-hero-period { margin-top:10px; font-size:12px; line-height:1.5; opacity:.92; display:flex; flex-wrap:wrap; align-items:center; gap:6px; }
.tc-hero-period-tag { display:inline-flex; align-items:center; padding:2px 8px; border-radius:999px; background:rgba(255,255,255,.22); font-size:11px; font-weight:700; flex-shrink:0; }
.tc-hero-foot { margin-top:12px; padding-top:12px; border-top:1px solid rgba(255,255,255,.22); font-size:12px; line-height:1.55; opacity:.9; }
.tc-hero-foot-row { margin-bottom:4px; }
.tc-hero-foot-row:last-child { margin-bottom:0; }
.tc-hero-foot-label { opacity:.85; margin-right:4px; }
.tc-hero-foot--compact { margin-top:6px; padding-top:0; border-top:none; }
.tc-section-badge--blue { color:#0EA5E9; background:rgba(14,165,233,.12); }
.tc-vehicle-idx--blue { color:#0EA5E9; background:rgba(14,165,233,.12); }
.tc-vehicle-amount-val--blue { color:#0EA5E9; }
.tc-service-amt--blue { color:#0EA5E9; }
.tc-step-dot.wait--blue { background:rgba(14,165,233,.12); color:#0EA5E9; border:2px solid #0EA5E9; box-sizing:border-box; }
.tc-drawer-row-val.highlight--blue { color:#0EA5E9; font-weight:700; }
.tc-drawer-total--blue span:last-child { color:#0EA5E9; }
.tc-drawer-row-val.highlight--purple { color:#8B5CF6; font-weight:700; }
.tc-drawer-total--purple span:last-child { color:#8B5CF6; }
.tc-hero--transfer { background:linear-gradient(135deg,#10B981 0%,#059669 100%); box-shadow:0 10px 28px rgba(16,185,129,.35); }
.tc-hero--replace { background:linear-gradient(135deg,#F43F5E 0%,#E11D48 100%); box-shadow:0 10px 28px rgba(244,63,94,.35); }
.tc-hero--delivery { background:linear-gradient(135deg,${XLL_GREEN} 0%,${XLL_GREEN_DEEP} 100%); box-shadow:0 10px 28px rgba(122,185,41,.35); }
.tc-section-badge--rose { color:#F43F5E; background:rgba(244,63,94,.12); }
.tc-step-dot.wait--rose { background:rgba(244,63,94,.12); color:#F43F5E; border:2px solid #F43F5E; box-sizing:border-box; }
.tc-vehicle-idx--rose { color:#F43F5E; background:rgba(244,63,94,.12); }
.vr-replace-swap { display:flex; align-items:stretch; gap:8px; margin:12px 14px 8px; }
.vr-replace-side { flex:1; min-width:0; padding:10px 10px 8px; border-radius:10px; }
.vr-replace-side--old { background:rgba(251,191,36,.08); border:1px solid rgba(251,191,36,.28); }
.vr-replace-side--new { background:rgba(16,185,129,.08); border:1px solid rgba(16,185,129,.22); }
.vr-replace-side-tag { display:inline-block; font-size:10px; font-weight:700; padding:2px 7px; border-radius:999px; margin-bottom:8px; letter-spacing:.02em; }
.vr-replace-side--old .vr-replace-side-tag { color:#B45309; background:rgba(251,191,36,.22); }
.vr-replace-side--new .vr-replace-side-tag { color:#047857; background:rgba(16,185,129,.18); }
.vr-replace-side .tc-vehicle-plate { font-size:14px; }
.vr-replace-side .tc-vehicle-model { margin-top:4px; }
.vr-replace-mid { display:flex; align-items:center; justify-content:center; flex-shrink:0; width:24px; }
.vr-replace-mid-icon { display:inline-flex; align-items:center; justify-content:center; width:24px; height:24px; border-radius:999px; font-size:13px; color:#F43F5E; font-weight:700; background:rgba(244,63,94,.1); }
.vr-replace-reason.tc-kv-grid { padding:0 14px 14px; gap:8px 14px; }
.tc-hero-route { margin-top:10px; font-size:13px; line-height:1.5; opacity:.95; display:flex; flex-wrap:wrap; align-items:center; gap:6px; }
.tc-hero-route-arrow { opacity:.75; font-size:12px; }
.tc-section-badge--green { color:#059669; background:rgba(16,185,129,.12); }
.tc-vehicle-idx--green { color:#059669; background:rgba(16,185,129,.12); }
.tc-step-dot.wait--green { background:rgba(16,185,129,.12); color:#059669; border:2px solid #059669; box-sizing:border-box; }
.zl-proof-list { display:flex; flex-direction:column; gap:4px; margin-top:4px; }
.zl-proof-link { border:none; background:transparent; padding:0; font-size:12px; color:#0EA5E9; font-weight:600; text-align:left; cursor:pointer; text-decoration:underline; }
.tc-mini-sheet { position:absolute; inset:0; z-index:40; display:flex; flex-direction:column; justify-content:flex-end; }
.tc-mini-sheet-mask { position:absolute; inset:0; background:rgba(0,0,0,.45); border:none; padding:0; cursor:pointer; }
.tc-mini-sheet-panel { position:relative; z-index:1; background:${COLOR_BG}; border-radius:16px 16px 0 0; max-height:min(72vh,520px); display:flex; flex-direction:column; box-shadow:0 -8px 28px rgba(15,23,42,.14); animation:tc-sheet-up .28s ease; }
.tc-mini-sheet-handle { width:36px; height:4px; background:rgba(0,0,0,.12); border-radius:999px; margin:10px auto 0; flex-shrink:0; }
.tc-mini-sheet-head { display:flex; align-items:center; justify-content:space-between; gap:12px; padding:8px 16px 12px; border-bottom:1px solid ${COLOR_LINE}; flex-shrink:0; }
.tc-mini-sheet-title { font-size:16px; font-weight:700; color:${COLOR_TEXT}; }
.tc-mini-sheet-close { width:32px; height:32px; border:none; background:${COLOR_PAGE}; border-radius:999px; font-size:20px; line-height:1; color:${COLOR_MUTED}; cursor:pointer; flex-shrink:0; }
.tc-mini-sheet-body { flex:1; min-height:0; overflow-y:auto; -webkit-overflow-scrolling:touch; padding:4px 20px calc(16px + env(safe-area-inset-bottom,0px)); }
.tc-mini-sheet-foot { flex-shrink:0; padding:10px 16px calc(10px + env(safe-area-inset-bottom,0px)); border-top:1px solid ${COLOR_LINE}; }
.tc-mini-sheet-actions { display:flex; gap:10px; }
.tc-mini-sheet-actions .tc-drawer-foot-btn { flex:1; min-height:44px; border-radius:10px; font-size:15px; font-weight:600; cursor:pointer; }
.tc-mini-sheet-body--form { padding-top:12px; padding-bottom:12px; }
.tc-mini-sheet-ok { width:100%; min-height:44px; border:none; border-radius:10px; font-size:15px; font-weight:600; cursor:pointer; background:${XLL_GREEN_DEEP}; color:#fff; touch-action:manipulation; }
.tc-mini-sheet-ok:active { opacity:.88; }
@keyframes tc-sheet-up { from { transform:translateY(100%); } to { transform:translateY(0); } }
@media (prefers-reduced-motion: reduce) {
  .tc-mini-sheet-panel { animation:none; }
  .xll-mod-sheet-panel { animation:none; }
  .xll-mp-picker-panel { animation:none; }
  .xll-task-card { animation:none; }
  .xll-task-action:active, .xll-biz-item:active .xll-biz-icon, .xll-map-full:active, .xll-sub-btn:active, .xll-login-btn:active { transform:none; }
}
`;

/* ── 审批中心 / 年审（内联模块，单页原型） ── */
const AC_TAB_ITEMS = [
  { key: 'initiated', label: '我发起的', short: '发起' },
  { key: 'todo', label: '我的待办', short: '待办' },
  { key: 'done', label: '我的已办', short: '已办' },
  { key: 'cc', label: '我的抄送', short: '抄送' },
];

const AC_FLOW_THEME = {
  合同审批: { accent: '#2563EB', soft: 'rgba(37, 99, 235, 0.12)' },
  提车应收款: { accent: '#F97316', soft: 'rgba(249, 115, 22, 0.12)' },
  租赁账单: { accent: '#0EA5E9', soft: 'rgba(14, 165, 233, 0.12)' },
  还车应结款: { accent: '#8B5CF6', soft: 'rgba(139, 92, 246, 0.12)' },
  '还车应结款-业务管理组': { accent: '#9333EA', soft: 'rgba(147, 51, 234, 0.12)' },
  '还车应结款-能源服务组': { accent: '#7C3AED', soft: 'rgba(124, 58, 237, 0.12)' },
  '还车应结款-运维组': { accent: '#6D28D9', soft: 'rgba(109, 40, 217, 0.12)' },
  '还车应结款-安全部': { accent: '#5B21B6', soft: 'rgba(91, 33, 182, 0.12)' },
  '氢费对账单（对站）': { accent: '#10B981', soft: 'rgba(16, 185, 129, 0.12)' },
  '氢费对账单（对客）': { accent: '#059669', soft: 'rgba(5, 150, 105, 0.12)' },
  车辆调拨: { accent: XLL_GREEN_DEEP, soft: XLL_GREEN_SOFT },
  替换车申请: { accent: '#F43F5E', soft: 'rgba(244, 63, 94, 0.12)' },
  车辆异动: { accent: '#14B8A6', soft: 'rgba(20, 184, 166, 0.12)' },
};

const AC_FLOW_TYPES = [
  '合同审批', '提车应收款', '租赁账单', '还车应结款',
  '还车应结款-业务管理组', '还车应结款-能源服务组', '还车应结款-运维组', '还车应结款-安全部',
  '氢费对账单（对站）', '氢费对账单（对客）', '车辆调拨', '替换车申请', '车辆异动',
];

const AC_RETURN_SETTLE_SUB_FLOW_TYPES = [
  '还车应结款-业务管理组',
  '还车应结款-能源服务组',
  '还车应结款-运维组',
  '还车应结款-安全部',
];

const AC_RETURN_SETTLE_FLOW_TYPES = [
  '还车应结款',
  ...AC_RETURN_SETTLE_SUB_FLOW_TYPES,
];

const AC_RETURN_SETTLE_DEPT_MAP = {
  '还车应结款-业务管理组': 'bizMgmt',
  '还车应结款-能源服务组': 'energy',
  '还车应结款-运维组': 'ops',
  '还车应结款-安全部': 'safety',
};

const acIsReturnSettleFlow = (flowType) => AC_RETURN_SETTLE_FLOW_TYPES.includes(flowType);

const acIsReturnSettleSubFlow = (flowType) => (
  typeof flowType === 'string' && flowType.startsWith('还车应结款-')
);

const acReturnSettleDeptScope = (flowType) => {
  if (flowType === '还车应结款') return 'all';
  return AC_RETURN_SETTLE_DEPT_MAP[flowType] || null;
};

/** 仅汇总「还车应结款」展示 Hero 金额；四部门子流程统一为「还车应结款-部门」+ 车牌重点样式 */
const acReturnSettleShowsHeroMoney = (flowType) => flowType === '还车应结款';

const acReturnSettleSubFlowStatuses = (plateNo, mainTask) => (
  AC_RETURN_SETTLE_SUB_FLOW_TYPES.map((flowType) => {
    const deptLabel = flowType.replace('还车应结款-', '');
    const matched = AC_MOCK_TASKS.find((t) => t.flowType === flowType && t.plateNo === plateNo);
    const fallbackPassed = mainTask?.flowType === '还车应结款' && mainTask?.status === '已通过';
    return {
      flowType,
      deptLabel,
      status: matched?.status || (fallbackPassed ? '已通过' : '未发起'),
      bizNo: matched?.bizNo || '—',
    };
  })
);

const acReturnSettleAllSubFlowsPassed = (plateNo, mainTask) => {
  const list = acReturnSettleSubFlowStatuses(plateNo, mainTask);
  return list.every((item) => item.status === '已通过');
};

/** 主流程须由业务管理组在四部门全过后主动发起，非自动生成 */
const acIsReturnSettleMainFlowInitiated = (task) => {
  if (task?.flowType !== '还车应结款') return true;
  if (task.mainFlowInitiated === true) return true;
  if (task.mainFlowInitiated === false || task.status === '待发起') return false;
  return task.status === '审批中' || task.status === '待审批' || task.status === '已通过' || task.status === '已驳回' || task.status === '已撤回';
};

/** 还车应结款主流程：四部门未全过或未发起前不在列表展示（已完结主流程除外） */
const acShouldShowReturnSettleMainInList = (task) => {
  if (task.flowType !== '还车应结款') return true;
  if (task.status === '已通过' || task.status === '已驳回' || task.status === '已撤回') return true;
  if (!acReturnSettleAllSubFlowsPassed(task.plateNo, task)) return false;
  return acIsReturnSettleMainFlowInitiated(task);
};

/** 部门结算卡片右上角状态：待提交 / 审批中 / 已提交 / 已驳回 */
const resolveHcSettleGroupBadge = (taskStatus) => {
  if (taskStatus === '已通过') return { label: '已提交', tone: 'green' };
  if (taskStatus === '已驳回' || taskStatus === '已撤回') return { label: '已驳回', tone: 'rose' };
  if (taskStatus === '审批中' || taskStatus === '待审批') return { label: '审批中', tone: 'purple' };
  return { label: '待提交', tone: null };
};

const RETURN_SETTLE_SUB_FLOW_META = {
  '还车应结款-业务管理组': { department: '业务管理组', approveTitle: '业务管理组负责人审批' },
  '还车应结款-能源服务组': { department: '能源服务组', approveTitle: '能源服务组负责人审批' },
  '还车应结款-运维组': { department: '运维组', approveTitle: '运维组负责人审批' },
  '还车应结款-安全部': { department: '安全部', approveTitle: '安全部负责人审批' },
};

const buildReturnSettleSubFlowApprovalSteps = (task) => {
  const meta = RETURN_SETTLE_SUB_FLOW_META[task?.flowType];
  if (!meta) return [];
  const isDone = task?.status === '已通过';
  const isPending = acIsPending(task?.status);
  return [
    {
      title: '部门费用填报',
      department: meta.department,
      status: '已通过',
      person: task?.initiator || '—',
      approveTime: task?.initiateTime || '—',
    },
    {
      title: meta.approveTitle,
      department: meta.department,
      status: isDone ? '已通过' : (isPending ? '待审批' : task?.status || '待审批'),
      person: isDone ? ((task?.handledBy || [])[0] || task?.currentAssignee || '—') : (task?.currentAssignee || '—'),
      approveTime: isDone ? (task?.finishTime || '—') : '—',
    },
  ];
};

const AC_QUICK_FILTERS = ['合同审批', '提车应收款', '租赁账单', '还车应结款', '车辆调拨'];

/** 还车应结款 mock 场景（开发/测试对照）
 * 场景 A · 粤B58888F（HC-2026-0418 / ap-14~20）：四部门已通过 → 业务管理组已发起主流程 → 主流程在待办（财务审核）
 * 场景 B · 浙F06901F（HC-2026-0625 / ap-21~25）：四部门子流程均在待办；主流程未满足展示条件
 * 场景 C · 苏E12345F（HC-2026-0610 / ap-26~30）：四部门已通过 → 业务管理组尚未发起主流程 → 主流程不出现在列表
 */
const AC_MOCK_TASKS = [
  { id: 'ap-1', flowType: '合同审批', bizNo: 'HT-ZL-2025-088', summary: '嘉兴氢能示范项目 · 正式合同', initiator: '张明辉', initiateTime: '2026-05-28 09:15', arriveTime: '2026-05-28 14:20', finishTime: '', currentNode: '法务审核', currentAssignee: '王法务', status: '审批中', ccUsers: ['李晓彤'], handledBy: [] },
  { id: 'ap-2', flowType: '提车应收款', bizNo: 'TC-2026-0312', summary: '上海迅杰物流 · 3 台提车收款', customerName: '上海迅杰物流有限公司', projectName: '上海氢能城际物流项目', vehicleCount: 3, actualAmount: '218510.00', initiator: '李晓彤', initiateTime: '2026-05-30 10:00', arriveTime: '2026-05-30 11:30', finishTime: '', currentNode: '财务审核', currentAssignee: '张明辉', status: '审批中', ccUsers: ['张明辉', '陈高伟'], handledBy: [] },
  { id: 'ap-3', flowType: '租赁账单', bizNo: 'ZD-2026-06-001', summary: '2026年6月 · 粤B58888F 等 5 车', customerName: '嘉兴某某物流有限公司', projectName: '嘉兴氢能示范项目', vehicleCount: 5, actualAmount: '142380.00', period: 6, billStartDate: '2026-06-01', billEndDate: '2026-06-30', initiator: '陈高伟', initiateTime: '2026-06-01 08:40', arriveTime: '2026-06-01 09:10', finishTime: '', currentNode: '业管主管', currentAssignee: '张明辉', approvers: ['张明辉', '李晓彤', '财务-赵敏'], status: '审批中', ccUsers: [], handledBy: [] },
  { id: 'ap-4', flowType: '还车应结款', bizNo: 'HC-2026-0520', summary: '沪A03561F 还车结算', plateNo: '沪A03561F', customerName: '上海迅杰物流有限公司', projectName: '上海氢能城际物流项目', pendingSettle: '927.50', depositAmount: '5000.00', refundTotal: '4072.50', payTotal: '0.00', actualRent: '0.00', initiator: '张明辉', initiateTime: '2026-05-20 16:00', arriveTime: '2026-05-21 09:00', finishTime: '2026-05-21 15:30', currentNode: '—', currentAssignee: '', status: '已通过', ccUsers: ['李晓彤'], handledBy: ['张明辉', '财务-赵敏'] },
  { id: 'ap-5', flowType: '氢费对账单（对站）', bizNo: 'H2-ST-202605', summary: '平湖加氢站 · 2026年5月对账', initiator: '能源部-周工', initiateTime: '2026-05-25 11:00', arriveTime: '2026-05-26 08:30', finishTime: '2026-05-26 17:00', currentNode: '—', currentAssignee: '', status: '已通过', ccUsers: ['张明辉'], handledBy: ['张明辉'] },
  { id: 'ap-6', flowType: '氢费对账单（对客）', bizNo: 'H2-CU-202605', summary: '嘉兴某某物流 · 5月氢费账单', initiator: '李晓彤', initiateTime: '2026-05-27 14:20', arriveTime: '2026-05-28 09:00', finishTime: '', currentNode: 'CEO审批', currentAssignee: 'CEO办公室', status: '审批中', ccUsers: ['张明辉', '陈高伟'], handledBy: ['张明辉'] },
  {
    id: 'ap-8', flowType: '车辆调拨', transferStage: 'create', bizNo: 'DB-2026-018',
    summary: '广东省-广州市 → 浙江省-杭州市 · 2 台 · 司机运输',
    transferDate: '2026-06-01 09:30', departRegion: '广东省-广州市', receiveRegion: '浙江省-杭州市',
    reason: '华东业务增量，需将华南车辆调至杭州节点保障运力。',
    vehicleCount: 2,
    transferInfo: {
      method: '司机运输', transportLeader: '李强', transportPhone: '13912345678',
      receivePerson: '赵强（运维一部-驻场）', transportCost: '0.00', contractFiles: [],
    },
    vehicles: [
      { id: 1, brand: '小鹏', model: 'P7', plateNo: '浙A11111', departParking: '天河智慧停车场', departMileageKm: '5620.00', departHydrogen: '78.50', departElectricKwh: '86.20', h2Unit: '%' },
      { id: 2, brand: '蔚来', model: 'ET5', plateNo: '浙B22222', departParking: '南山科技园停车场', departMileageKm: '4315.80', departHydrogen: '38.20', departElectricKwh: '72.60', h2Unit: 'MPa' },
    ],
    initiator: '王东东', initiateTime: '2026-06-01 08:00', arriveTime: '2026-06-01 08:45', finishTime: '',
    currentNode: '运维主管', currentAssignee: '张明辉', status: '审批中', ccUsers: ['张明辉'], handledBy: [],
  },
  {
    id: 'ap-16', flowType: '车辆调拨', transferStage: 'ops', bizNo: 'TP202603310001',
    summary: '广东省-广州市 → 浙江省-嘉兴市 · 2 台 · 第三方运输',
    transferDate: '2026-03-31 08:00', departRegion: '广东省-广州市', receiveRegion: '浙江省-嘉兴市',
    reason: '华南业务增量，需将车辆调至华东仓储节点保障运力。',
    vehicleCount: 2,
    transferInfo: {
      method: '第三方运输', transportLeader: '张明', transportPhone: '13800138000',
      receivePerson: '王芳（运维二部-调度）', transportCost: '2800.00',
      contractFiles: [{ name: '华南至华东调拨运输合同.pdf' }, { name: '运输费用确认单.docx' }],
    },
    vehicles: [
      { id: 1, brand: '小鹏', model: 'P7', plateNo: '浙A11111', departParking: '天河智慧停车场', departMileageKm: '12580.50', departHydrogen: '85.00', departElectricKwh: '92.30', h2Unit: '%' },
      { id: 2, brand: '蔚来', model: 'ET5', plateNo: '浙B22222', departParking: '白云维修基地停车场', departMileageKm: '8920.00', departHydrogen: '42.50', departElectricKwh: '68.00', h2Unit: 'MPa' },
    ],
    initiator: '王东东', initiateTime: '2026-03-31 07:40', arriveTime: '2026-03-31 08:10', finishTime: '',
    currentNode: '运维中心', currentAssignee: '张明辉', status: '审批中', ccUsers: ['陈高伟'], handledBy: ['运维主管-陈高伟'],
  },
  { id: 'ap-9', flowType: '车辆异动', bizNo: 'YD-2026-042', summary: '浙F06900F · 保养至检测站', initiator: '张明辉', initiateTime: '2026-05-18 13:20', arriveTime: '2026-05-18 14:00', finishTime: '2026-05-18 16:10', currentNode: '—', currentAssignee: '', status: '已驳回', ccUsers: ['李晓彤'], handledBy: ['运维主管-刘强'] },
  { id: 'ap-10', flowType: '合同审批', bizNo: 'HT-ZL-2024-066', summary: '上海迅杰物流 · 续签合同', initiator: '张明辉', initiateTime: '2026-04-10 10:00', arriveTime: '2026-04-10 11:00', finishTime: '2026-04-12 09:30', currentNode: '—', currentAssignee: '', status: '已通过', ccUsers: ['李晓彤', '王法务'], handledBy: ['法务-王法务', 'CEO办公室'] },
  { id: 'ap-11', flowType: '租赁账单', bizNo: 'ZD-2026-05-008', summary: '2026年5月 · 批量租赁账单', customerName: '嘉兴某某物流有限公司', projectName: '嘉兴氢能示范项目', vehicleCount: 3, actualAmount: '88600.00', period: 1, billStartDate: '2025-01-01', billEndDate: '2025-01-31', initiator: '陈高伟', initiateTime: '2026-05-05 09:00', arriveTime: '2026-05-05 09:30', finishTime: '2026-05-05 18:00', currentNode: '—', currentAssignee: '', status: '已通过', ccUsers: ['张明辉'], handledBy: ['张明辉'] },
  { id: 'ap-12', flowType: '提车应收款', bizNo: 'TC-2026-0228', summary: '嘉兴某某物流 · 2 台提车', customerName: '嘉兴某某物流有限公司', projectName: '嘉兴氢能示范项目', vehicleCount: 2, actualAmount: '98600.00', initiator: '张明辉', initiateTime: '2026-02-28 15:00', arriveTime: '2026-03-01 09:00', finishTime: '2026-03-01 11:20', currentNode: '—', currentAssignee: '', status: '已通过', ccUsers: ['财务-赵敏'], handledBy: ['财务-赵敏'] },
  { id: 'ap-14', flowType: '还车应结款', bizNo: 'HC-2026-0418', summary: '粤B58888F 还车应结', plateNo: '粤B58888F', customerName: '嘉兴某某物流有限公司', projectName: '嘉兴腾4.5T租赁', pendingSettle: '927.50', depositAmount: '5000.00', refundTotal: '4072.50', payTotal: '0.00', actualRent: '0.00', mainFlowInitiated: true, initiator: '陈高伟', initiateTime: '2026-04-18 11:30', arriveTime: '2026-04-18 11:45', finishTime: '', currentNode: '财务审核', currentAssignee: '张明辉', status: '审批中', ccUsers: ['李晓彤'], handledBy: [] },
  { id: 'ap-15', flowType: '还车应结款-业务管理组', bizNo: 'HC-2026-0418-BS', summary: '粤B58888F · 业务管理组结算', plateNo: '粤B58888F', customerName: '嘉兴某某物流有限公司', projectName: '嘉兴腾4.5T租赁', pendingSettle: '150.00', depositAmount: '5000.00', refundTotal: '0.00', payTotal: '0.00', actualRent: '0.00', parentBizNo: 'HC-2026-0418', initiator: '陈高伟', initiateTime: '2026-04-17 15:00', arriveTime: '2026-04-17 15:30', finishTime: '2026-04-17 18:00', currentNode: '—', currentAssignee: '', status: '已通过', ccUsers: ['李晓彤'], handledBy: ['张明辉'] },
  { id: 'ap-18', flowType: '还车应结款-能源服务组', bizNo: 'HC-2026-0418-EN', summary: '粤B58888F · 能源服务组结算', plateNo: '粤B58888F', customerName: '嘉兴某某物流有限公司', projectName: '嘉兴腾4.5T租赁', pendingSettle: '455.00', depositAmount: '5000.00', refundTotal: '0.00', payTotal: '0.00', actualRent: '0.00', parentBizNo: 'HC-2026-0418', initiator: '李四', initiateTime: '2026-04-17 16:30', arriveTime: '2026-04-17 17:30', finishTime: '2026-04-17 19:10', currentNode: '—', currentAssignee: '', status: '已通过', ccUsers: ['李晓彤'], handledBy: ['张明辉'] },
  { id: 'ap-19', flowType: '还车应结款-运维组', bizNo: 'HC-2026-0418-OP', summary: '粤B58888F · 运维组结算', plateNo: '粤B58888F', customerName: '嘉兴某某物流有限公司', projectName: '嘉兴腾4.5T租赁', pendingSettle: '452.50', depositAmount: '5000.00', refundTotal: '0.00', payTotal: '0.00', actualRent: '0.00', parentBizNo: 'HC-2026-0418', initiator: '王五', initiateTime: '2026-04-18 08:00', arriveTime: '2026-04-18 08:20', finishTime: '2026-04-18 10:00', currentNode: '—', currentAssignee: '', status: '已通过', ccUsers: ['李晓彤'], handledBy: ['张明辉'] },
  { id: 'ap-20', flowType: '还车应结款-安全部', bizNo: 'HC-2026-0418-SF', summary: '粤B58888F · 安全部结算', plateNo: '粤B58888F', customerName: '嘉兴某某物流有限公司', projectName: '嘉兴腾4.5T租赁', pendingSettle: '100.00', depositAmount: '5000.00', refundTotal: '0.00', payTotal: '0.00', actualRent: '0.00', parentBizNo: 'HC-2026-0418', initiator: '赵六', initiateTime: '2026-04-18 08:40', arriveTime: '2026-04-18 09:00', finishTime: '2026-04-18 11:20', currentNode: '—', currentAssignee: '', status: '已通过', ccUsers: ['李晓彤'], handledBy: ['张明辉'] },
  { id: 'ap-21', flowType: '还车应结款', bizNo: 'HC-2026-0625', summary: '浙F06901F 还车应结', plateNo: '浙F06901F', customerName: '杭州绿运科技有限公司', projectName: '杭州城配氢能项目', pendingSettle: '1280.00', depositAmount: '6000.00', refundTotal: '4720.00', payTotal: '0.00', actualRent: '0.00', mainFlowInitiated: false, initiator: '—', initiateTime: '', arriveTime: '', finishTime: '', currentNode: '—', currentAssignee: '', status: '待发起', ccUsers: [], handledBy: [] },
  { id: 'ap-22', flowType: '还车应结款-业务管理组', bizNo: 'HC-2026-0625-BS', summary: '浙F06901F · 业务管理组结算', plateNo: '浙F06901F', customerName: '杭州绿运科技有限公司', projectName: '杭州城配氢能项目', pendingSettle: '180.00', depositAmount: '6000.00', refundTotal: '0.00', payTotal: '0.00', actualRent: '0.00', parentBizNo: 'HC-2026-0625', initiator: '陈高伟', initiateTime: '2026-06-24 15:00', arriveTime: '2026-06-25 10:10', finishTime: '', currentNode: '业务管理组负责人审批', currentAssignee: '张明辉', status: '审批中', ccUsers: ['李晓彤'], handledBy: [] },
  { id: 'ap-23', flowType: '还车应结款-能源服务组', bizNo: 'HC-2026-0625-EN', summary: '浙F06901F · 能源服务组结算', plateNo: '浙F06901F', customerName: '杭州绿运科技有限公司', projectName: '杭州城配氢能项目', pendingSettle: '520.00', depositAmount: '6000.00', refundTotal: '0.00', payTotal: '0.00', actualRent: '0.00', parentBizNo: 'HC-2026-0625', initiator: '李四', initiateTime: '2026-06-24 16:30', arriveTime: '2026-06-25 10:05', finishTime: '', currentNode: '能源服务组负责人审批', currentAssignee: '张明辉', status: '审批中', ccUsers: ['李晓彤'], handledBy: [] },
  { id: 'ap-24', flowType: '还车应结款-运维组', bizNo: 'HC-2026-0625-OP', summary: '浙F06901F · 运维组结算', plateNo: '浙F06901F', customerName: '杭州绿运科技有限公司', projectName: '杭州城配氢能项目', pendingSettle: '480.00', depositAmount: '6000.00', refundTotal: '0.00', payTotal: '0.00', actualRent: '0.00', parentBizNo: 'HC-2026-0625', initiator: '王五', initiateTime: '2026-06-25 08:00', arriveTime: '2026-06-25 10:00', finishTime: '', currentNode: '运维组负责人审批', currentAssignee: '张明辉', status: '审批中', ccUsers: ['李晓彤'], handledBy: [] },
  { id: 'ap-25', flowType: '还车应结款-安全部', bizNo: 'HC-2026-0625-SF', summary: '浙F06901F · 安全部结算', plateNo: '浙F06901F', customerName: '杭州绿运科技有限公司', projectName: '杭州城配氢能项目', pendingSettle: '100.00', depositAmount: '6000.00', refundTotal: '0.00', payTotal: '0.00', actualRent: '0.00', parentBizNo: 'HC-2026-0625', initiator: '赵六', initiateTime: '2026-06-25 08:40', arriveTime: '2026-06-25 09:55', finishTime: '', currentNode: '安全部负责人审批', currentAssignee: '张明辉', status: '审批中', ccUsers: ['李晓彤'], handledBy: [] },
  { id: 'ap-26', flowType: '还车应结款', bizNo: 'HC-2026-0610', summary: '苏E12345F 还车应结', plateNo: '苏E12345F', customerName: '苏州顺达城配有限公司', projectName: '苏州园区氢能城配项目', pendingSettle: '860.00', depositAmount: '5500.00', refundTotal: '4640.00', payTotal: '0.00', actualRent: '0.00', mainFlowInitiated: false, initiator: '—', initiateTime: '', arriveTime: '', finishTime: '', currentNode: '—', currentAssignee: '', status: '待发起', ccUsers: [], handledBy: [] },
  { id: 'ap-27', flowType: '还车应结款-业务管理组', bizNo: 'HC-2026-0610-BS', summary: '苏E12345F · 业务管理组结算', plateNo: '苏E12345F', customerName: '苏州顺达城配有限公司', projectName: '苏州园区氢能城配项目', pendingSettle: '120.00', depositAmount: '5500.00', refundTotal: '0.00', payTotal: '0.00', actualRent: '0.00', parentBizNo: 'HC-2026-0610', initiator: '陈高伟', initiateTime: '2026-06-09 15:00', arriveTime: '2026-06-09 15:30', finishTime: '2026-06-09 18:00', currentNode: '—', currentAssignee: '', status: '已通过', ccUsers: ['李晓彤'], handledBy: ['张明辉'] },
  { id: 'ap-28', flowType: '还车应结款-能源服务组', bizNo: 'HC-2026-0610-EN', summary: '苏E12345F · 能源服务组结算', plateNo: '苏E12345F', customerName: '苏州顺达城配有限公司', projectName: '苏州园区氢能城配项目', pendingSettle: '380.00', depositAmount: '5500.00', refundTotal: '0.00', payTotal: '0.00', actualRent: '0.00', parentBizNo: 'HC-2026-0610', initiator: '李四', initiateTime: '2026-06-09 16:30', arriveTime: '2026-06-09 17:00', finishTime: '2026-06-09 19:10', currentNode: '—', currentAssignee: '', status: '已通过', ccUsers: ['李晓彤'], handledBy: ['张明辉'] },
  { id: 'ap-29', flowType: '还车应结款-运维组', bizNo: 'HC-2026-0610-OP', summary: '苏E12345F · 运维组结算', plateNo: '苏E12345F', customerName: '苏州顺达城配有限公司', projectName: '苏州园区氢能城配项目', pendingSettle: '260.00', depositAmount: '5500.00', refundTotal: '0.00', payTotal: '0.00', actualRent: '0.00', parentBizNo: 'HC-2026-0610', initiator: '王五', initiateTime: '2026-06-10 08:00', arriveTime: '2026-06-10 08:20', finishTime: '2026-06-10 10:00', currentNode: '—', currentAssignee: '', status: '已通过', ccUsers: ['李晓彤'], handledBy: ['张明辉'] },
  { id: 'ap-30', flowType: '还车应结款-安全部', bizNo: 'HC-2026-0610-SF', summary: '苏E12345F · 安全部结算', plateNo: '苏E12345F', customerName: '苏州顺达城配有限公司', projectName: '苏州园区氢能城配项目', pendingSettle: '100.00', depositAmount: '5500.00', refundTotal: '0.00', payTotal: '0.00', actualRent: '0.00', parentBizNo: 'HC-2026-0610', initiator: '赵六', initiateTime: '2026-06-10 08:40', arriveTime: '2026-06-10 09:00', finishTime: '2026-06-10 11:20', currentNode: '—', currentAssignee: '', status: '已通过', ccUsers: ['李晓彤'], handledBy: ['张明辉'] },
  {
    id: 'ap-17', flowType: '替换车申请', bizNo: 'TH-2026-0218',
    summary: '嘉兴氢能示范项目 · 2 辆车替换',
    customerName: '嘉兴某某物流有限公司', projectName: '嘉兴氢能示范项目', projectType: '租赁',
    contractCode: 'HT-ZL-2025-001', deliveryRegion: '浙江省-嘉兴市', vehicleCount: 2,
    pairs: [
      {
        id: 'pair_1', replaceType: '永久替换', replaceReason: '车辆原因',
        replaceReasonDesc: '原车故障需维修，临时用替换车保障客户用车。',
        originalPlate: '浙A12345', originalBrand: '东风', originalModel: 'DFH1180',
        replacePlate: '浙A67890', replaceBrand: '福田', replaceModel: 'BJ1180',
      },
      {
        id: 'pair_2', replaceType: '临时替换', replaceReason: '客户原因', replaceFee: '500.00',
        replaceReasonDesc: '',
        originalPlate: '浙A55555', originalBrand: '重汽', originalModel: 'ZZ1160',
        replacePlate: '浙A66666', replaceBrand: '江淮', replaceModel: 'HFC1190',
      },
    ],
    initiator: '王东东', initiateTime: '2026-02-18 09:30', arriveTime: '2026-02-18 10:00', finishTime: '',
    currentNode: '运维主管', currentAssignee: '张明辉', approvers: ['张明辉', '姚守涛'], status: '审批中',
    ccUsers: ['李晓彤'], handledBy: [],
  },
];

const acIsPending = (status) => status === '审批中' || status === '待审批';

const acFilterByTab = (task, tabKey, user) => {
  if (tabKey === 'initiated') return task.initiator === user;
  if (tabKey === 'todo') return task.currentAssignee === user && acIsPending(task.status);
  if (tabKey === 'done') return (task.handledBy || []).includes(user);
  if (tabKey === 'cc') return (task.ccUsers || []).includes(user);
  return false;
};

const acStatusClass = (status) => {
  if (status === '已通过') return 'ok';
  if (status === '已驳回' || status === '已撤回') return 'reject';
  return 'pending';
};

const acTaskStatusLabel = (task) => {
  if (!acIsPending(task.status)) return task.status;
  const list = task.approvers?.length
    ? task.approvers
    : task.currentAssignee
      ? [task.currentAssignee]
      : [];
  if (!list.length) return task.status;
  if (list.length <= 2) return `${task.status}：${list.join('、')}`;
  return `${task.status}：${list.slice(0, 2).join('、')} +${list.length - 2}`;
};

const acCardBoldTitle = (task) => {
  const flowType = task?.flowType;
  if (acIsReturnSettleSubFlow(flowType)) return task?.plateNo || '—';
  if (flowType === '还车应结款') return task?.plateNo || '—';
  if (flowType === '提车应收款' || flowType === '租赁账单' || flowType === '替换车申请') return task?.customerName || '—';
  if (flowType === '车辆调拨') return acTransferRouteTitle(task);
  return task?.summary || task?.bizNo || '—';
};

const acFormatInitiateTime = (value) => {
  const text = String(value || '').trim();
  if (!text) return '—';
  const matched = text.match(/^(\d{4}-\d{2}-\d{2})\s+(\d{1,2}):(\d{2})/);
  if (matched) {
    const hh = String(matched[2]).padStart(2, '0');
    return `${matched[1]} ${hh}:${matched[3]}`;
  }
  return text.length >= 16 ? text.slice(0, 16) : text;
};

const acTaskInitiateDay = (task) => String(task?.initiateTime || '').slice(0, 10);

const acMatchesInitiateDateRange = (task, start, end) => {
  if (!start && !end) return true;
  const day = acTaskInitiateDay(task);
  if (!day) return false;
  if (start && day < start) return false;
  if (end && day > end) return false;
  return true;
};

const acPickupListActualAmount = (task) => {
  const detail = buildPickupDetail(task);
  const totals = calcPickupTotals(detail.vehicles, false, detail.hydrogen);
  return totals.actualTotal;
};

const acPickupDisplayActualAmount = (task) => acPickupListActualAmount(task);

const AC_EMPTY_MORE_FILTER = { initiator: '', dateStart: '', dateEnd: '' };

const AC_INITIATOR_OPTIONS = ['', ...Array.from(new Set(AC_MOCK_TASKS.map((t) => t.initiator).filter(Boolean))).sort((a, b) => a.localeCompare(b, 'zh-CN'))];

const MP_PICKER_YEARS = Array.from({ length: 11 }, (_, i) => 2020 + i);
const MP_PICKER_MONTHS = Array.from({ length: 12 }, (_, i) => i + 1);
const MP_PICKER_ITEM_H = 44;
const mpPad2 = (n) => String(n).padStart(2, '0');
const mpDaysInMonth = (year, month) => new Date(year, month, 0).getDate();
const mpParseIsoDate = (value) => {
  if (value && /^\d{4}-\d{2}-\d{2}$/.test(value)) {
    const [year, month, day] = value.split('-').map(Number);
    return { year, month, day };
  }
  const now = new Date();
  return { year: now.getFullYear(), month: now.getMonth() + 1, day: now.getDate() };
};
const mpToIsoDate = (year, month, day) => `${year}-${mpPad2(month)}-${mpPad2(Math.min(day, mpDaysInMonth(year, month)))}`;
const mpFormatInitiatorLabel = (value) => (value ? value : '不限');

const MpPickerWheelColumn = ({ items, value, onChange, formatItem }) => {
  const colRef = useRef(null);
  const scrollTimer = useRef(null);
  const format = formatItem || ((item) => item);

  useEffect(() => {
    const idx = items.indexOf(value);
    if (colRef.current && idx >= 0) {
      colRef.current.scrollTop = idx * MP_PICKER_ITEM_H;
    }
  }, [items, value]);

  const snapToNearest = useCallback(() => {
    if (!colRef.current) return;
    const idx = Math.max(0, Math.min(items.length - 1, Math.round(colRef.current.scrollTop / MP_PICKER_ITEM_H)));
    colRef.current.scrollTop = idx * MP_PICKER_ITEM_H;
    if (items[idx] !== value) onChange(items[idx]);
  }, [items, onChange, value]);

  const handleScroll = () => {
    if (scrollTimer.current) clearTimeout(scrollTimer.current);
    scrollTimer.current = setTimeout(snapToNearest, 80);
  };

  useEffect(() => () => {
    if (scrollTimer.current) clearTimeout(scrollTimer.current);
  }, []);

  return (
    <div className="xll-mp-picker-column" ref={colRef} onScroll={handleScroll}>
      <div className="xll-mp-picker-column-spacer" aria-hidden />
      {items.map((item) => (
        <div key={String(item)} className={`xll-mp-picker-item${item === value ? ' active' : ''}`}>
          {format(item)}
        </div>
      ))}
      <div className="xll-mp-picker-column-spacer" aria-hidden />
    </div>
  );
};

const MpDatePickerSheet = ({ open, title, value, onClose, onConfirm }) => {
  const [draft, setDraft] = useState(() => mpParseIsoDate(value));
  useEffect(() => {
    if (open) setDraft(mpParseIsoDate(value));
  }, [open, value]);
  if (!open) return null;
  const dayItems = Array.from({ length: mpDaysInMonth(draft.year, draft.month) }, (_, i) => i + 1);
  const safeDay = Math.min(draft.day, dayItems.length);
  return (
    <div className="xll-mp-picker-overlay" role="dialog" aria-modal="true" aria-label={title}>
      <button type="button" className="xll-mod-sheet-mask" onClick={onClose} aria-label="关闭" />
      <div className="xll-mp-picker-panel">
        <div className="xll-mp-picker-toolbar">
          <button type="button" className="xll-mp-picker-toolbar-btn xll-mp-picker-toolbar-btn--cancel" onClick={onClose}>取消</button>
          <div className="xll-mp-picker-toolbar-title">{title}</div>
          <button
            type="button"
            className="xll-mp-picker-toolbar-btn xll-mp-picker-toolbar-btn--confirm"
            onClick={() => onConfirm(mpToIsoDate(draft.year, draft.month, safeDay))}
          >
            确定
          </button>
        </div>
        <div className="xll-mp-picker-columns-wrap">
          <div className="xll-mp-picker-highlight" aria-hidden />
          <div className="xll-mp-picker-columns">
            <MpPickerWheelColumn
              items={MP_PICKER_YEARS}
              value={draft.year}
              onChange={(year) => setDraft((prev) => {
                const day = Math.min(prev.day, mpDaysInMonth(year, prev.month));
                return { year, month: prev.month, day };
              })}
              formatItem={(y) => `${y}年`}
            />
            <MpPickerWheelColumn
              items={MP_PICKER_MONTHS}
              value={draft.month}
              onChange={(month) => setDraft((prev) => {
                const day = Math.min(prev.day, mpDaysInMonth(prev.year, month));
                return { year: prev.year, month, day };
              })}
              formatItem={(m) => `${m}月`}
            />
            <MpPickerWheelColumn
              items={dayItems}
              value={safeDay}
              onChange={(day) => setDraft((prev) => ({ ...prev, day }))}
              formatItem={(d) => `${d}日`}
            />
          </div>
        </div>
      </div>
    </div>
  );
};

const MpOptionPickerSheet = ({ open, title, options, value, formatOption, onClose, onConfirm }) => {
  const [draft, setDraft] = useState(value);
  useEffect(() => {
    if (open) setDraft(value);
  }, [open, value]);
  if (!open) return null;
  const format = formatOption || ((item) => item);
  return (
    <div className="xll-mp-picker-overlay" role="dialog" aria-modal="true" aria-label={title}>
      <button type="button" className="xll-mod-sheet-mask" onClick={onClose} aria-label="关闭" />
      <div className="xll-mp-picker-panel">
        <div className="xll-mp-picker-toolbar">
          <button type="button" className="xll-mp-picker-toolbar-btn xll-mp-picker-toolbar-btn--cancel" onClick={onClose}>取消</button>
          <div className="xll-mp-picker-toolbar-title">{title}</div>
          <button type="button" className="xll-mp-picker-toolbar-btn xll-mp-picker-toolbar-btn--confirm" onClick={() => onConfirm(draft)}>确定</button>
        </div>
        <div className="xll-mp-picker-columns-wrap">
          <div className="xll-mp-picker-highlight" aria-hidden />
          <div className="xll-mp-picker-columns">
            <MpPickerWheelColumn items={options} value={draft} onChange={setDraft} formatItem={format} />
          </div>
        </div>
      </div>
    </div>
  );
};

const acTransferRouteTitle = (task) => {
  if (task.departRegion && task.receiveRegion) {
    return `${task.departRegion}调拨到${task.receiveRegion}`;
  }
  return task.summary || '—';
};

const acTransferVehicleLines = (vehicles = []) => {
  const map = new Map();
  vehicles.forEach((v) => {
    const label = `${v.brand || '—'}/${v.model || '—'}`;
    map.set(label, (map.get(label) || 0) + 1);
  });
  return Array.from(map.entries()).map(([label, count]) => ({ label, count }));
};

const acReturnSettleDisplay = (task) => {
  const depositAmount = task?.depositAmount || '0';
  const pendingSettle = task?.pendingSettle || '0';
  const diff = (parseFloat(depositAmount) || 0) - (parseFloat(pendingSettle) || 0);
  const refundTotal = task?.refundTotal || (diff > 0 ? diff.toFixed(2) : '0.00');
  const payTotal = task?.payTotal || (diff < 0 ? Math.abs(diff).toFixed(2) : '0.00');
  const isRefund = parseFloat(depositAmount) >= parseFloat(pendingSettle);
  return {
    label: isRefund ? '应退还' : '应补缴',
    amount: isRefund ? refundTotal : payTotal,
  };
};

const AR_MOCK_TASKS = [
  { id: 'ar-1', plateNo: '粤B58888F', brand: '福田', model: '奥铃4.5吨冷藏车', operateStatus: '租赁', expireDate: '2026-07-20', daysLeft: 49, tab: 'pending', province: '广东省', city: '深圳市' },
  { id: 'ar-2', plateNo: '沪A03561F', brand: '宇通', model: '49吨牵引车头', operateStatus: '自营', expireDate: '2026-07-31', daysLeft: 60, tab: 'pending', province: '上海市', city: '上海市' },
  { id: 'ar-3', plateNo: '苏E33333', brand: '陕汽', model: '德龙X3000混动牵引车', operateStatus: '库存', expireDate: '2026-05-15', daysLeft: -17, tab: 'pending', province: '江苏省', city: '苏州市' },
  { id: 'ar-7', plateNo: '鲁Q88901', brand: '重汽', model: '豪沃T7H牵引车', operateStatus: '租赁', expireDate: '2026-04-10', daysLeft: -52, tab: 'pending', province: '山东省', city: '临沂市' },
  { id: 'ar-8', plateNo: '闽D55662', brand: '金龙', model: '凯歌纯电动厢货', operateStatus: '自营', expireDate: '2026-04-27', daysLeft: -35, tab: 'pending', province: '福建省', city: '厦门市' },
  { id: 'ar-4', plateNo: '浙A88888', brand: '宇通', model: '氢燃料电池大巴', operateStatus: '库存', expireDate: '2026-08-10', daysLeft: 70, tab: 'pending', province: '浙江省', city: '杭州市' },
  { id: 'ar-6', plateNo: '皖B66221', brand: '江淮', model: '格尔发A5', operateStatus: '库存', expireDate: '2026-06-28', daysLeft: 27, tab: 'pending', province: '安徽省', city: '合肥市' },
  { id: 'ar-h1', plateNo: '苏A88991', brand: '解放', model: 'J6P牵引车', operateStatus: '自营', expireDate: '2026-03-10', daysLeft: 0, tab: 'history', province: '江苏省', city: '南京市', executor: '张明辉', executeTime: '2026-03-08 14:20', newValidUntil: '2027-03-31', station: '南京机动车检测站', cost: '380.00' },
  { id: 'ar-h2', plateNo: '粤A11223', brand: '比亚迪', model: 'T5纯电轻卡', operateStatus: '库存', expireDate: '2026-02-20', daysLeft: 0, tab: 'history', province: '广东省', city: '广州市', executor: '李晓彤', executeTime: '2026-02-18 09:45', newValidUntil: '2027-02-28', station: '广州南沙检测站', cost: '420.00' },
  { id: 'ar-h3', plateNo: '京A55667', brand: '东风', model: '天龙KL', operateStatus: '租赁', expireDate: '2026-01-15', daysLeft: 0, tab: 'history', province: '广东省', city: '东莞市', executor: '王建国', executeTime: '2026-01-12 16:30', newValidUntil: '2027-01-31', station: '东莞厚街检测站', cost: '350.00' },
];

const arSortPending = (tasks) =>
  [...tasks].sort((a, b) => {
    const overdueA = a.daysLeft < 0 ? -a.daysLeft : 0;
    const overdueB = b.daysLeft < 0 ? -b.daysLeft : 0;
    if (overdueB !== overdueA) return overdueB - overdueA;
    return a.daysLeft - b.daysLeft;
  });

const arDaysTag = (task) => {
  if (task.tab === 'history') return null;
  if (task.daysLeft > 0) return { text: `剩余${task.daysLeft}天`, cls: 'warn' };
  return { text: `逾期${Math.abs(task.daysLeft)}天`, cls: 'danger' };
};

/* ── 交车（参照 web端/交车管理.jsx + Axhub 交车原型） ── */
const DV_OPERATOR_REGIONS = ['浙江省-嘉兴市'];

/** 运维人员权限下可操作的停车场（备车库） */
const DV_OPERATOR_PARKING_LOTS = [
  { key: 'all', label: '全部停车场' },
  { key: 'jiaxing', label: '嘉兴港区氢能停车场' },
  { key: 'pinghu', label: '平湖指定停车场' },
  { key: 'nanhu', label: '南湖科技大道停车场' },
];

/**
 * 交车选车候选（已备车 · 权限停车场内）
 * readiness: ready | ctp_expired | commercial_expired | license_expired
 */
const DV_DELIVERY_PICK_VEHICLES = [
  { plateNo: '浙F80088', parkingLot: '嘉兴港区氢能停车场', parkingKey: 'jiaxing', brand: '苏龙', model: '海格牌18吨双飞翼货车', vin: 'LKLG7C4E4NA774701', region: '浙江省 · 嘉兴市', operateStatus: '可运营', vehicleStatus: '已备车', onlineStatus: '在线', vehicleSource: '自有', insuranceStatus: '正常', licenseStatus: '正常', readiness: 'ready' },
  { plateNo: '浙F88601', parkingLot: '平湖指定停车场', parkingKey: 'pinghu', brand: '现代', model: '帕力安牌4.5吨冷链车', vin: 'LNBSCPKB8RR123401', region: '浙江省 · 嘉兴市', operateStatus: '可运营', vehicleStatus: '已备车', onlineStatus: '在线', vehicleSource: '自有', insuranceStatus: '正常', licenseStatus: '正常', readiness: 'ready' },
  { plateNo: '浙F88602', parkingLot: '平湖指定停车场', parkingKey: 'pinghu', brand: '现代', model: '帕力安牌4.5吨冷链车', vin: 'LNBSCPKB8RR123402', region: '浙江省 · 嘉兴市', operateStatus: '可运营', vehicleStatus: '已备车', onlineStatus: '离线', vehicleSource: '自有', insuranceStatus: '正常', licenseStatus: '正常', readiness: 'ready' },
  { plateNo: '浙F88603', parkingLot: '平湖指定停车场', parkingKey: 'pinghu', brand: '飞驰', model: '49吨牵引车头', vin: 'LNBSCPKB8RR123403', region: '浙江省 · 嘉兴市', operateStatus: '可运营', vehicleStatus: '已备车', onlineStatus: '在线', vehicleSource: '自有', insuranceStatus: '异常', licenseStatus: '正常', readiness: 'ctp_expired' },
  { plateNo: '浙F88604', parkingLot: '嘉兴港区氢能停车场', parkingKey: 'jiaxing', brand: '宇通', model: '18吨双飞翼货车', vin: 'LKLG7C4E4NA774702', region: '浙江省 · 嘉兴市', operateStatus: '可运营', vehicleStatus: '已备车', onlineStatus: '在线', vehicleSource: '外租', insuranceStatus: '异常', licenseStatus: '正常', readiness: 'commercial_expired' },
  { plateNo: '浙F88605', parkingLot: '南湖科技大道停车场', parkingKey: 'nanhu', brand: '东风', model: 'DFH1180厢式货车', vin: 'LKLG7C4E4NA774759', region: '浙江省 · 嘉兴市', operateStatus: '可运营', vehicleStatus: '已备车', onlineStatus: '离线', vehicleSource: '自有', insuranceStatus: '正常', licenseStatus: '异常', readiness: 'license_expired' },
  { plateNo: '浙F88606', parkingLot: '南湖科技大道停车场', parkingKey: 'nanhu', brand: '福田', model: '奥铃4.5吨冷藏车', vin: 'LKLG7C4E4NA774760', region: '浙江省 · 嘉兴市', operateStatus: '可运营', vehicleStatus: '已备车', onlineStatus: '在线', vehicleSource: '自有', insuranceStatus: '正常', licenseStatus: '正常', readiness: 'ready' },
];

const DV_READINESS_META = {
  ready: { label: '已就绪可交车', canPick: true, blocked: false },
  ctp_expired: { label: '交强险已到期无法交车', canPick: false, blocked: true },
  commercial_expired: { label: '商业险已到期无法交车', canPick: false, blocked: true },
  ctp_and_commercial_expired: { label: '交强险、商业险已到期无法交车', canPick: false, blocked: true },
  license_expired: { label: '行驶证已到期无法交车', canPick: false, blocked: true },
};

const dvGetReadinessMeta = (v) => DV_READINESS_META[v.readiness] || DV_READINESS_META.ready;

/** 交车位置坐标（原型 · 按备车库/交车地点） */
const DV_DELIVERY_COORD_BY_PARKING = {
  jiaxing: { lat: 30.7428, lng: 121.0562, label: '嘉兴港区氢能停车场' },
  pinghu: { lat: 30.6772, lng: 121.0153, label: '平湖指定停车场' },
  nanhu: { lat: 30.7465, lng: 120.7582, label: '南湖科技大道停车场' },
};

const dvGetDeliveryLocationMeta = (plateNo, row) => {
  const vehicle = DV_DELIVERY_PICK_VEHICLES.find((v) => v.plateNo === String(plateNo || '').trim());
  if (vehicle?.parkingKey && DV_DELIVERY_COORD_BY_PARKING[vehicle.parkingKey]) {
    const coord = DV_DELIVERY_COORD_BY_PARKING[vehicle.parkingKey];
    return {
      lat: coord.lat,
      lng: coord.lng,
      label: coord.label,
      address: vehicle.parkingLot || coord.label,
      plateNo: vehicle.plateNo,
    };
  }
  const addr = String(row?.deliveryAddress || '').trim();
  if (/港区|氢能/.test(addr)) {
    const coord = DV_DELIVERY_COORD_BY_PARKING.jiaxing;
    return { ...coord, address: addr || coord.label, plateNo: plateNo || '' };
  }
  if (/平湖/.test(addr)) {
    const coord = DV_DELIVERY_COORD_BY_PARKING.pinghu;
    return { ...coord, address: addr || coord.label, plateNo: plateNo || '' };
  }
  if (/南湖/.test(addr)) {
    const coord = DV_DELIVERY_COORD_BY_PARKING.nanhu;
    return { ...coord, address: addr || coord.label, plateNo: plateNo || '' };
  }
  return {
    lat: 30.7102,
    lng: 121.0208,
    label: row?.deliveryRegion || '嘉兴市',
    address: addr || row?.deliveryRegion || '交车区域',
    plateNo: plateNo || '',
  };
};

/** 车辆是否已接入 GPS（在线视为有 GPS 坐标） */
const dvVehicleHasGpsDevice = (plateNo) => {
  const vehicle = DV_DELIVERY_PICK_VEHICLES.find((v) => v.plateNo === String(plateNo || '').trim());
  if (!vehicle) return false;
  return vehicle.onlineStatus === '在线';
};

const dvResolveDeliveryLocation = (plateNo, row, formLocation) => {
  if (dvVehicleHasGpsDevice(plateNo)) {
    return { ...dvGetDeliveryLocationMeta(plateNo, row), source: 'vehicle' };
  }
  if (formLocation && formLocation.lat != null && formLocation.lng != null) {
    return {
      lat: formLocation.lat,
      lng: formLocation.lng,
      address: formLocation.address || '当前定位',
      label: formLocation.address || '当前定位',
      plateNo: plateNo || '',
      source: 'current',
    };
  }
  return {
    ...dvGetDeliveryLocationMeta(plateNo, row),
    address: '暂未定位，请点击获取当前定位',
    source: 'pending',
  };
};

/** 识别车牌校验：以下运营状态视为系统不可交车匹配（待运营走 2/3/4 明细校验） */
const DV_RECOGNIZE_OPERATE_NOT_FOUND = ['租赁', '自营', '退出运营'];

/** 识别车牌扩展车辆池（含各类校验场景，选车列表仍仅用 DV_DELIVERY_PICK_VEHICLES） */
const DV_RECOGNIZE_EXTRA_VEHICLES = [
  { plateNo: '浙F88701', parkingLot: '嘉兴港区氢能停车场', parkingKey: 'jiaxing', brand: '东风', model: 'DFH1180厢式货车', vin: 'LKLG7C4E4NA774801', region: '浙江省 · 嘉兴市', operateStatus: '可运营', vehicleStatus: '未备车', onlineStatus: '离线', vehicleSource: '自有', insuranceStatus: '正常', licenseStatus: '正常', readiness: 'ready' },
  { plateNo: '浙F88702', parkingLot: '平湖指定停车场', parkingKey: 'pinghu', brand: '现代', model: '帕力安牌4.5吨冷链车', vin: 'LNBSCPKB8RR123501', region: '浙江省 · 嘉兴市', operateStatus: '租赁', vehicleStatus: '已交车', onlineStatus: '在线', vehicleSource: '自有', insuranceStatus: '正常', licenseStatus: '正常', readiness: 'ready' },
  { plateNo: '浙F88704', parkingLot: '嘉兴港区氢能停车场', parkingKey: 'jiaxing', brand: '宇通', model: '18吨双飞翼货车', vin: 'LKLG7C4E4NA774804', region: '浙江省 · 嘉兴市', operateStatus: '可运营', vehicleStatus: '已备车', onlineStatus: '在线', vehicleSource: '外租', insuranceStatus: '异常', licenseStatus: '正常', readiness: 'ctp_and_commercial_expired', ctpExpired: true, commercialExpired: true },
  { plateNo: '浙F88706', parkingLot: '南湖科技大道停车场', parkingKey: 'nanhu', brand: '福田', model: '奥铃4.5吨冷藏车', vin: 'LKLG7C4E4NA774806', region: '浙江省 · 嘉兴市', operateStatus: '可运营', vehicleStatus: '已备车', onlineStatus: '在线', vehicleSource: '自有', insuranceStatus: '异常', licenseStatus: '异常', readiness: 'ctp_expired', ctpExpired: true },
  { plateNo: '浙F88707', parkingLot: '平湖指定停车场', parkingKey: 'pinghu', brand: '飞驰', model: '49吨牵引车头', vin: 'LNBSCPKB8RR123507', region: '浙江省 · 嘉兴市', operateStatus: '待运营', vehicleStatus: '已备车', onlineStatus: '在线', vehicleSource: '自有', insuranceStatus: '异常', licenseStatus: '正常', readiness: 'commercial_expired', commercialExpired: true },
];

const DV_RECOGNIZE_VEHICLE_POOL = [...DV_DELIVERY_PICK_VEHICLES, ...DV_RECOGNIZE_EXTRA_VEHICLES];

const dvFindRecognizeVehicle = (plateNo) => {
  const q = String(plateNo || '').trim().toUpperCase();
  if (!q) return null;
  return DV_RECOGNIZE_VEHICLE_POOL.find((v) => v.plateNo.toUpperCase() === q) || null;
};

const dvBuildInsuranceExpireMsg = (vehicle) => {
  if (!vehicle || vehicle.vehicleStatus !== '已备车') return null;
  const ctp = vehicle.readiness === 'ctp_expired' || vehicle.readiness === 'ctp_and_commercial_expired' || vehicle.ctpExpired === true;
  const commercial = vehicle.readiness === 'commercial_expired' || vehicle.readiness === 'ctp_and_commercial_expired' || vehicle.commercialExpired === true;
  if (ctp && commercial) return '该车辆交强险、商业险已到期，如已购买请联系采购部上传';
  if (ctp) return '该车辆交强险已到期，如已购买请联系采购部上传';
  if (commercial) return '该车辆商业险已到期，如已购买请联系采购部上传';
  if (vehicle.insuranceStatus === '异常') return '该车辆交强险、商业险已到期，如已购买请联系采购部上传';
  return null;
};

const dvValidateRecognizedPlate = (vehicle) => {
  if (!vehicle) {
    return { ok: false, messages: ['系统未匹配到该车辆，请联系管理员确认'] };
  }
  if (DV_RECOGNIZE_OPERATE_NOT_FOUND.includes(vehicle.operateStatus)) {
    return { ok: false, messages: ['系统未匹配到该车辆，请联系管理员确认'] };
  }
  const messages = [];
  if (vehicle.vehicleStatus !== '已备车') {
    messages.push('该车辆未备车，请先进行备车');
  } else {
    const insMsg = dvBuildInsuranceExpireMsg(vehicle);
    if (insMsg) messages.push(insMsg);
    if (vehicle.licenseStatus === '异常' || vehicle.readiness === 'license_expired') {
      messages.push('该车辆行驶证已到期，如已年审请联系运维部上传');
    }
  }
  return { ok: messages.length === 0, messages };
};

/** 原型：识别演示车牌序列（循环展示各类校验结果） */
const DV_OCR_DEMO_PLATES = ['浙F88601', '浙F99999', '浙F88702', '浙F88701', '浙F88603', '浙F88704', '浙F88605', '浙F88706', '浙F88707'];

/** 原型：备胎胎纹检测仪 OCR 演示读数（mm） */
const DV_SPARE_TREAD_OCR_DEMO = ['5.2', '4.8', '6.0', '3.6', '5.5', ''];
const DV_SPARE_TIRE_DEMO_PHOTO = 'https://picsum.photos/seed/spare-tire-tread/800/600';

/** 原型：驾驶培训证件演示图 */
const DV_DRIVER_DOC_DEMO = {
  idFront: 'https://picsum.photos/seed/driver-id-front/400/300',
  idBack: 'https://picsum.photos/seed/driver-id-back/400/300',
  licenseFront: 'https://picsum.photos/seed/driver-lic-front/400/300',
  licenseBack: 'https://picsum.photos/seed/driver-lic-back/400/300',
  qualification: 'https://picsum.photos/seed/driver-qual/400/300',
  portrait: 'https://picsum.photos/seed/driver-portrait/400/300',
};

/** 原型：驾驶培训视频二维码（微信扫码） */
const DV_DRIVER_TRAINING_QR = 'https://api.qrserver.com/v1/create-qr-code/?size=220x220&data=https%3A%2F%2Fone-os.driver-training.demo%2Fwatch';

const dvBuildDriverTrainingCodeUrl = (draft) => {
  const q = new URLSearchParams({
    mode: 'manual',
    phone: String(draft?.driverPhone || '').trim(),
    name: String(draft?.driverName || '').trim(),
    idNo: String(draft?.driverIdNo || '').trim(),
  });
  return `https://one-os.driver-training.demo/manual-sign?${q.toString()}`;
};

const dvDriverTrainingCodeQrUrl = (signUrl) => (
  `https://api.qrserver.com/v1/create-qr-code/?size=220x220&data=${encodeURIComponent(signUrl)}`
);

const dvValidateDriverManualDraft = (draft, heavy) => {
  if (!String(draft?.driverPhone || '').trim()) return { ok: false, message: '请输入手机号' };
  if (!String(draft?.driverName || '').trim()) return { ok: false, message: '请输入姓名' };
  if (!String(draft?.driverIdNo || '').trim()) return { ok: false, message: '请输入身份证号' };
  if (!draft?.driverIdFront || !draft?.driverIdBack) return { ok: false, message: '请上传身份证正反面' };
  if (!draft?.driverLicenseFront || !draft?.driverLicenseBack) return { ok: false, message: '请上传驾驶证正反面' };
  if (!draft?.driverFrontPhoto) return { ok: false, message: '请上传司机正面照片' };
  if (heavy && !draft?.driverQualification) return { ok: false, message: '请上传从业资格证' };
  return { ok: true, message: '' };
};

const DV_DRIVER_MANUAL_EMPTY = {
  driverPhone: '',
  driverName: '',
  driverIdNo: '',
  driverIdFront: false,
  driverIdBack: false,
  driverLicenseFront: false,
  driverLicenseBack: false,
  driverQualification: false,
  driverFrontPhoto: false,
  driverIdFrontUrl: '',
  driverIdBackUrl: '',
  driverLicenseFrontUrl: '',
  driverLicenseBackUrl: '',
  driverQualificationUrl: '',
  driverFrontPhotoUrl: '',
};

const DV_RESERVE_PLATES = DV_DELIVERY_PICK_VEHICLES.filter((v) => dvGetReadinessMeta(v).canPick);

/** 后装设备记录（按车牌，选车后只读反写） */
const DV_REAR_EQUIP_BY_PLATE = {
  浙F80088: { hasAd: false, hasBigWord: false, adPhotoDone: false, bigWordPhotoDone: false, hasTailgate: true, tailgatePhotoDone: true },
  浙F88601: { hasAd: false, hasBigWord: false, adPhotoDone: false, bigWordPhotoDone: false, hasTailgate: true, tailgatePhotoDone: true },
  浙F88602: { hasAd: false, hasBigWord: false, adPhotoDone: false, bigWordPhotoDone: false, hasTailgate: true, tailgatePhotoDone: false },
  浙F88603: { hasAd: true, hasBigWord: true, adPhotoDone: true, bigWordPhotoDone: false, hasTailgate: false, tailgatePhotoDone: false },
  浙F88604: { hasAd: false, hasBigWord: false, adPhotoDone: false, bigWordPhotoDone: false, hasTailgate: true, tailgatePhotoDone: true },
  浙F88605: { hasAd: true, hasBigWord: false, adPhotoDone: false, bigWordPhotoDone: false, hasTailgate: true, tailgatePhotoDone: true },
  浙F88606: { hasAd: true, hasBigWord: true, adPhotoDone: true, bigWordPhotoDone: true, hasTailgate: false, tailgatePhotoDone: false },
};

const dvIsHeavyVehicle = (vehicleType, model) => {
  const text = `${vehicleType || ''}${model || ''}`;
  const tonMatch = text.match(/(\d+(?:\.\d+)?)\s*吨/);
  if (tonMatch) return parseFloat(tonMatch[1]) >= 18;
  return /18\s*吨|18T|49\s*吨/i.test(text);
};

const dvGetRearEquipRecord = (plateNo, row) => {
  const key = String(plateNo || '').trim();
  if (key && DV_REAR_EQUIP_BY_PLATE[key]) return { ...DV_REAR_EQUIP_BY_PLATE[key] };
  const hasAd = row?.hasAd === '有';
  const hasTailgate = row?.hasTailgate === '有';
  return {
    hasAd,
    hasBigWord: hasAd,
    adPhotoDone: hasAd,
    bigWordPhotoDone: hasAd,
    hasTailgate,
    tailgatePhotoDone: hasTailgate,
  };
};

const dvMockDriverTrainingInfo = (heavy) => ({
  driverName: '王涛',
  driverPhone: '13812345678',
  driverIdNo: '330421199001011234',
  driverIdFront: true,
  driverIdBack: true,
  driverLicenseFront: true,
  driverLicenseBack: true,
  driverQualification: !!heavy,
  driverFrontPhoto: true,
  driverIdFrontUrl: DV_DRIVER_DOC_DEMO.idFront,
  driverIdBackUrl: DV_DRIVER_DOC_DEMO.idBack,
  driverLicenseFrontUrl: DV_DRIVER_DOC_DEMO.licenseFront,
  driverLicenseBackUrl: DV_DRIVER_DOC_DEMO.licenseBack,
  driverQualificationUrl: heavy ? DV_DRIVER_DOC_DEMO.qualification : '',
  driverFrontPhotoUrl: DV_DRIVER_DOC_DEMO.portrait,
});

/** 交车照片分类 */
const DV_PHOTO_CATEGORIES = [
  { key: 'body', label: '车身情况' },
  { key: 'chassis', label: '底盘情况' },
  { key: 'tire', label: '轮胎情况' },
  { key: 'defect', label: '瑕疵情况' },
  { key: 'other', label: '其他情况' },
];

/** 交车照片项（连拍顺序与分类） */
const DV_PHOTO_ITEMS = [
  { key: 'dashboard', label: '仪表盘', category: 'body', required: true, tread: false },
  { key: 'front', label: '车辆正面', category: 'body', required: true, tread: false },
  { key: 'front_bottom', label: '正前方底部', category: 'chassis', required: true, tread: false },
  { key: 'left_front', label: '车辆左前方', category: 'body', required: true, tread: false },
  { key: 'left_front_bottom', label: '左侧前方底部', category: 'chassis', required: true, tread: false },
  { key: 'left_front_tire', label: '左前轮', category: 'tire', required: true, tread: true },
  { key: 'left_rear', label: '车辆左后方', category: 'body', required: true, tread: false },
  { key: 'left_rear_bottom', label: '左侧后方底部', category: 'chassis', required: true, tread: false },
  { key: 'left_rear_tire_inner', label: '左后轮（内）', category: 'tire', required: true, tread: true },
  { key: 'left_rear_tire_outer', label: '左后轮（外）', category: 'tire', required: true, tread: true },
  { key: 'right_rear', label: '车辆右后方', category: 'body', required: true, tread: false },
  { key: 'right_rear_bottom', label: '右侧后方底部', category: 'chassis', required: true, tread: false },
  { key: 'right_rear_tire_inner', label: '右后轮（内）', category: 'tire', required: true, tread: true },
  { key: 'right_rear_tire_outer', label: '右后轮（外）', category: 'tire', required: true, tread: true },
  { key: 'right_front', label: '车辆右前方', category: 'body', required: true, tread: false },
  { key: 'right_front_bottom', label: '右侧前方底部', category: 'chassis', required: true, tread: false },
  { key: 'right_front_tire', label: '右前轮', category: 'tire', required: true, tread: true },
  { key: 'spare', label: '备胎', category: 'tire', required: true, tread: true },
];

const DV_PHOTO_CAPTURE_SEQUENCE = DV_PHOTO_ITEMS.map((item) => item.key);

const dvPhotoItemByKey = (key) => DV_PHOTO_ITEMS.find((item) => item.key === key);

const dvPhotoCategoryLabel = (categoryKey) => (
  DV_PHOTO_CATEGORIES.find((cat) => cat.key === categoryKey)?.label || ''
);

const dvPhotoCaptured = (photos, key) => {
  const val = photos?.[key];
  if (!val) return false;
  if (val === true) return true;
  return !!val.captured;
};

const dvGetPhotoRecord = (photos, key) => {
  const val = photos?.[key];
  if (!val || val === true) return val === true ? { captured: true } : null;
  return val;
};

const dvGetCaptureSequence = (formDraft) => {
  let list = DV_PHOTO_CAPTURE_SEQUENCE.map((key) => dvPhotoItemByKey(key)).filter(Boolean);
  if (formDraft?.spareTire === '无') {
    list = list.filter((item) => item.key !== 'spare');
  }
  return list;
};

const dvRequiredPhotosComplete = (photos, formDraft) => (
  dvGetCaptureSequence(formDraft)
    .filter((item) => dvIsPhotoItemRequired(item))
    .every((item) => dvPhotoCaptured(photos, item.key))
);

const dvCountCapturedPhotos = (photos, formDraft) => (
  dvGetCaptureSequence(formDraft).filter((item) => dvPhotoCaptured(photos, item.key)).length
);

const dvGetNextCaptureIndex = (photos, formDraft) => {
  const seq = dvGetCaptureSequence(formDraft);
  const idx = seq.findIndex((item) => !dvPhotoCaptured(photos, item.key));
  return idx >= 0 ? idx : seq.length;
};

const dvPhotoItemsByCategory = (categoryKey) => DV_PHOTO_ITEMS.filter((item) => item.category === categoryKey);

const DV_PHOTO_REQUIRED_CATEGORIES = new Set(['body', 'chassis', 'tire']);
const dvIsPhotoItemRequired = (item) => DV_PHOTO_REQUIRED_CATEGORIES.has(item.category);

/** 原型：交车照片演示图（按项固定 seed，避免加载失败） */
const dvGetPhotoDemoUrl = (photoKey) => `https://picsum.photos/seed/oneos-dv-${encodeURIComponent(photoKey)}/480/480`;

const DV_PHOTO_UPLOAD_ACCEPT = 'image/jpeg,image/jpg,image/png,image/gif,image/webp';

const dvReadImageFile = (file) => new Promise((resolve, reject) => {
  if (!file || !String(file.type || '').startsWith('image/')) {
    reject(new Error('not image'));
    return;
  }
  const reader = new FileReader();
  reader.onload = () => resolve(String(reader.result || ''));
  reader.onerror = () => reject(new Error('read failed'));
  reader.readAsDataURL(file);
});

const dvSimulatePhotoUpload = (photoKey, cameraDraft, formDraft, row) => {
  const loc = dvResolveDeliveryLocation(formDraft?.plateNo, row, formDraft?.deliveryLocation);
  const watermarkTime = dvFormatOpsSignTime();
  const watermarkAddress = loc.address || loc.label || '未知地点';
  const baseUrl = cameraDraft?.photoUrl || dvGetPhotoDemoUrl(photoKey);
  return {
    photoUrl: `${baseUrl.split('?')[0]}?wm=1&t=${encodeURIComponent(watermarkTime)}&loc=${encodeURIComponent(watermarkAddress)}`,
    watermarkTime,
    watermarkAddress,
    uploaded: true,
  };
};

/** 查看页：补齐已拍摄交车照片（车身/底盘/轮胎及瑕疵/其他） */
const dvEnsureViewDeliveryPhotos = (row, formDraft) => {
  const photos = { ...(formDraft?.deliveryPhotos || row?.deliveryPhotos || {}) };
  const ctx = formDraft || row;
  dvGetCaptureSequence(ctx).forEach((item) => {
    if (!dvPhotoCaptured(photos, item.key)) {
      const upload = dvSimulatePhotoUpload(item.key, {}, formDraft, row);
      photos[item.key] = {
        captured: true,
        photoUrl: upload.photoUrl,
        uploaded: true,
        watermarkTime: upload.watermarkTime,
        watermarkAddress: upload.watermarkAddress,
        ...(item.tread ? { treadDepth: '6.50' } : {}),
      };
    }
  });
  ['defect', 'other'].forEach((catKey) => {
    const hasExtra = Object.keys(photos).some((key) => key.indexOf(`${catKey}_extra_`) === 0 && dvPhotoCaptured(photos, key));
    if (!hasExtra) {
      const extraKey = `${catKey}_extra_1`;
      const upload = dvSimulatePhotoUpload(extraKey, { photoUrl: dvGetPhotoDemoUrl(extraKey) }, formDraft, row);
      photos[extraKey] = {
        captured: true,
        photoUrl: upload.photoUrl,
        uploaded: true,
        watermarkTime: upload.watermarkTime,
        watermarkAddress: upload.watermarkAddress,
      };
    }
  });
  return photos;
};

const dvResolvePhotoUrl = (photoKey, record) => {
  if (!photoKey) return '';
  const url = record?.photoUrl;
  if (url && typeof url === 'string' && !url.includes('&sig=') && !url.includes('&extra=')) return url;
  return dvGetPhotoDemoUrl(photoKey);
};

const dvBuildCategoryViewerItems = (categoryKey, photos, formDraft) => {
  if (categoryKey === 'defect' || categoryKey === 'other') {
    return Object.keys(photos || {})
      .filter((key) => key.indexOf(`${categoryKey}_extra_`) === 0 && dvPhotoCaptured(photos, key))
      .sort()
      .map((key, idx) => {
        const record = dvGetPhotoRecord(photos, key);
        return {
          key,
          label: `照片${idx + 1}`,
          photoUrl: dvResolvePhotoUrl(key, record),
          treadDepth: record?.treadDepth || '',
        };
      });
  }
  return dvPhotoItemsByCategory(categoryKey)
    .filter((item) => !(item.key === 'spare' && formDraft?.spareTire === '无'))
    .filter((item) => dvPhotoCaptured(photos, item.key))
    .map((item) => {
      const record = dvGetPhotoRecord(photos, item.key);
      return {
        key: item.key,
        label: item.label,
        photoUrl: dvResolvePhotoUrl(item.key, record),
        treadDepth: record?.treadDepth || '',
      };
    });
};

const dvExtraPhotoKeys = (categoryKey, photos) => (
  Object.keys(photos || {}).filter((key) => key.indexOf(`${categoryKey}_extra_`) === 0 && dvPhotoCaptured(photos, key))
);

/** 交车表单步骤 */
const DV_FORM_STEPS = [
  { key: 'vehicle', label: '车辆情况' },
  { key: 'inspection', label: '交车检查项' },
  { key: 'photos', label: '拍摄照片' },
];

/** 型号参数 · 仪表盘氢量单位（% / MPa） */
const DV_MODEL_GAUGE_UNIT = {
  '东风|DFH1180': 'MPa',
  '福田|BJ1180': '%',
  '现代|帕力安牌4.5吨冷链车': '%',
  '苏龙|海格牌18吨双飞翼货车': '%',
  '宇通|18吨双飞翼货车': 'MPa',
  '福田|奥铃4.5吨冷藏车': '%',
  '飞驰|49吨牵引车头': 'MPa',
};

const dvGetModelGaugeUnit = (brand, model) => {
  const key = `${String(brand || '').trim()}|${String(model || '').trim()}`;
  if (DV_MODEL_GAUGE_UNIT[key]) return DV_MODEL_GAUGE_UNIT[key];
  const m = String(model || '');
  if (/DFH1180|SX1180/.test(m)) return 'MPa';
  return '%';
};

/** 交车检查单类别与项目（对齐 web 交车检查单） */
const DV_INSPECTION_TIRE_CATEGORY = '轮胎检查';
const DV_INSPECTION_TIRE_TREAD_DEMO = ['13.05', '13.22', '13.01', '13.47', '13.09', '13.36'];

const DV_INSPECTION_SECTIONS = [
  {
    category: '证件信息',
    items: ['行驶证', '营运证', '加氢证', 'ETC设备', 'ETC卡', '前后车牌照', '通行证', 'GPS设备(服务中)'],
  },
  {
    category: '工具信息',
    items: ['钥匙', '备胎', '三角木', '千斤顶', '工具包', '三角警示牌', '灭火器', '其他'],
  },
  {
    category: '外观检查',
    items: [
      '检查玻璃无划痕、破裂',
      '检查座椅无划痕、破损',
      '检查车身漆面无划痕、变形',
      '检查货箱反光贴完好',
      '检查货箱防撞块完好',
      '检查所有灯光完好',
      '检查冷机工作(如有)',
      '车辆清洗',
      '其他',
    ],
  },
  {
    category: DV_INSPECTION_TIRE_CATEGORY,
    items: ['左前 (1轴)', '左后内 (2轴)', '左后外 (2轴)', '右前 (1轴)', '右后内 (2轴)', '右后外 (2轴)'],
    tread: true,
  },
];

const dvInspectionIsTireCategory = (category) => category === DV_INSPECTION_TIRE_CATEGORY;

const dvBuildInspectionList = () => {
  const list = [];
  let tireIdx = 0;
  DV_INSPECTION_SECTIONS.forEach((section, ci) => {
    (section.items || []).forEach((item, ji) => {
      const isTire = !!section.tread;
      list.push({
        key: `ins-${ci}-${ji}`,
        category: section.category,
        item,
        checked: item === '检查冷机工作(如有)' ? false : true,
        treadDepth: isTire ? (DV_INSPECTION_TIRE_TREAD_DEMO[tireIdx++] || '6.50') : '',
        remark: '',
      });
    });
  });
  return list;
};

/** 车辆情况步骤：校验必填项是否已填写 */
const dvValidateVehicleStep = (formDraft, row) => {
  if (!formDraft?.plateNo) return { ok: false, message: '请先选择交车车辆' };
  const rearEquip = formDraft.rearEquip || dvGetRearEquipRecord(formDraft.plateNo, row);
  if (rearEquip.hasAd) {
    if (!(formDraft.adPhotoUploaded || rearEquip.adPhotoDone)) {
      return { ok: false, message: '请拍摄车身广告照片' };
    }
    if (!(formDraft.bigWordPhotoUploaded || rearEquip.bigWordPhotoDone)) {
      return { ok: false, message: '请拍摄放大字照片' };
    }
  }
  const trainingDone = formDraft.driverTrainingDone || formDraft.driverTraining === '已完成';
  if (formDraft.driverTrainingPending) return { ok: false, message: '请等待司机微信扫码完成培训签字' };
  if (!trainingDone) return { ok: false, message: '请完成驾驶培训' };
  if (formDraft.deliveryMileage === '' || formDraft.deliveryH2 === '' || formDraft.deliveryElec === '') {
    return { ok: false, message: '请填写里程、氢量与电量' };
  }
  if (!dvVehicleHasGpsDevice(formDraft.plateNo)) {
    const loc = formDraft.deliveryLocation;
    if (!loc || loc.lat == null || loc.lng == null) {
      return { ok: false, message: '请先获取交车位置' };
    }
  }
  return { ok: true };
};

/** 交车检查项步骤：校验轮胎胎纹等必填项 */
const dvValidateInspectionStep = (formDraft) => {
  const list = formDraft?.inspectionList || [];
  for (const row of list) {
    if (!dvInspectionIsTireCategory(row.category)) continue;
    if (!String(row.treadDepth || '').trim()) {
      return { ok: false, message: `请填写${row.item}胎纹深度` };
    }
  }
  return { ok: true };
};

const dvFormatMetric2 = (v, suffix) => {
  if (v == null || v === '') return '—';
  const n = Number(v);
  if (!Number.isFinite(n)) return '—';
  const text = n.toFixed(2);
  return suffix ? `${text} ${suffix}` : text;
};

const dvFormatServiceFee = (v) => dvFormatMetric2(v, '元');

const dvParseMetric2 = (v) => {
  const s = String(v ?? '').trim();
  if (!s) return null;
  const n = parseFloat(s.replace(/,/g, ''));
  if (!Number.isFinite(n)) return null;
  return Math.round(n * 100) / 100;
};

const dvMetricInputChange = (raw) => {
  if (raw === '' || raw === '-') return raw;
  if (/^\d*\.?\d{0,2}$/.test(raw)) return raw;
  return null;
};

/** 原型：合同被授权人候选（客户方短信签章）；按订单挂载，允许为空 */
const DV_AUTHORIZED_PERSONS_SAMPLE = [
  { id: 'ap1', name: '李晓明', phone: '13800138001' },
  { id: 'ap2', name: '王芳', phone: '13900139002' },
  { id: 'ap3', name: '赵强', phone: '13700137003' },
  { id: 'ap4', name: '陈静', phone: '13600136004' },
];

const DV_MOCK_ORDERS = [
  {
    id: 'o1', expectedDate: '2025-02-28 至 2025-03-05', contractCode: 'LNZLHT 20260104001', projectName: '桐乡韵达租赁4.5T*10', customerName: '桐乡市丰韵快递有限责任公司', businessDept: '业务二部', businessOwner: '刘念念', taskSource: '替换车', bizType: '租赁', deliveryRegion: '浙江省-嘉兴市', deliveryAddress: '平湖指定停车场', createTime: '2026-06-04 11:28', createBy: '赵小峰',
    /** 演示：关联合同无被授权人 */
    authorizedPersons: [],
    vehicleList: [
      { vehicleKey: 1, seq: 1, vehicleType: '4.5吨冷链车', brand: '现代', model: '帕力安牌4.5吨冷链车', vin: 'LNBSCPKB8RR123401', replaceOldPlate: '浙A88601F', plateNo: '', deliveryTime: '', deliveryPerson: '', deliveryStatus: '未开始', deliveryMileage: null, deliveryH2: null, deliveryH2Unit: '%', deliveryElec: null, hasAd: '', hasTailgate: '', spareTire: '', driverTraining: '' },
      { vehicleKey: 2, seq: 2, vehicleType: '4.5吨冷链车', brand: '现代', model: '帕力安牌4.5吨冷链车', vin: 'LNBSCPKB8RR123402', replaceOldPlate: '浙A88602F', plateNo: '浙F88601', deliveryTime: '2026-06-03 14:20', deliveryPerson: '张明辉', deliveryStatus: '待客户签章', deliveryMileage: 12500, deliveryH2: 18, deliveryH2Unit: '%', deliveryElec: 76, serviceFee: 200, hasAd: '无', hasTailgate: '有', spareTire: '有', spareTirePhotoUploaded: true, spareTirePhotoUrl: DV_SPARE_TIRE_DEMO_PHOTO, spareTireTreadDepth: '5.2', driverTraining: '已完成', authorizedPersonId: 'ap1', authorizedPersonName: '李晓明', authorizedPersonPhone: '13800138001', signSent: true },
    ],
  },
  {
    id: 'o4', expectedDate: '2024-11-15', contractCode: 'LNZLHT2024111401', projectName: '聚德11月新增苏龙18T*2', customerName: '沈阳聚德物流有限公司', businessDept: '业务三部', businessOwner: '金可鹏', taskSource: '交车任务', bizType: '租赁', deliveryRegion: '浙江省-嘉兴市', deliveryAddress: '嘉兴港区氢能停车场', createTime: '2024-11-15 15:05', createBy: '何苗苗',
    authorizedPersons: DV_AUTHORIZED_PERSONS_SAMPLE,
    vehicleList: [
      { vehicleKey: 1, seq: 1, vehicleType: '18吨双飞翼货车', brand: '苏龙', model: '海格牌18吨双飞翼货车', vin: 'LKLG7C4E4NA774701', plateNo: '浙F80088', deliveryTime: '2026-06-02 16:00', deliveryPerson: '魏山', deliveryStatus: '待客户签章', deliveryMileage: 46200, deliveryH2: 21, deliveryH2Unit: '%', deliveryElec: 80, hasAd: '无', hasTailgate: '有', spareTire: '有', spareTirePhotoUploaded: true, spareTirePhotoUrl: DV_SPARE_TIRE_DEMO_PHOTO, spareTireTreadDepth: '4.8', driverTraining: '已完成', authorizedPersonId: 'ap2', authorizedPersonName: '王芳', authorizedPersonPhone: '13900139002', signSent: true },
      { vehicleKey: 2, seq: 2, vehicleType: '18吨双飞翼货车', brand: '苏龙', model: '海格牌18吨双飞翼货车', vin: 'LKLG7C4E4NA774702', plateNo: '沪A03802F', deliveryTime: '2026-06-01 10:15', deliveryPerson: '魏山', deliveryStatus: '待重新签章', deliveryMileage: 38800, deliveryH2: 24, deliveryH2Unit: '%', deliveryElec: 72, hasAd: '无', hasTailgate: '有', spareTire: '有', spareTirePhotoUploaded: true, spareTirePhotoUrl: DV_SPARE_TIRE_DEMO_PHOTO, spareTireTreadDepth: '5.0', driverTraining: '已完成', authorizedPersonId: '', authorizedPersonName: '', authorizedPersonPhone: '', signSent: false },
    ],
  },
  {
    id: 'o5', expectedDate: '2025-02-15', contractCode: 'HT-ZL-2024-001', projectName: '嘉兴氢能示范项目', customerName: '嘉兴某某物流有限公司', businessDept: '业务一部', businessOwner: '张经理', taskSource: '交车任务', bizType: '租赁', deliveryRegion: '浙江省-嘉兴市', deliveryAddress: '南湖科技大道停车场', createTime: '2025-02-10 09:00', createBy: '系统',
    authorizedPersons: DV_AUTHORIZED_PERSONS_SAMPLE.slice(0, 2),
    vehicleList: [
      { vehicleKey: 1, seq: 1, vehicleType: '厢式货车', brand: '东风', model: 'DFH1180', vin: 'LKLG7C4E4NA774759', plateNo: '京A12345', deliveryTime: '2025-02-15 10:30', deliveryPerson: '张三', deliveryStatus: '客户已签章', deliveryMileage: 12580, deliveryH2: 35, deliveryH2Unit: 'MPa', deliveryElec: 45, serviceFee: 150, hasAd: '有', hasTailgate: '有', spareTire: '有', driverTraining: '已完成', customerSignTime: '2025-02-15 11:45', vehicleReturned: true, vehicleReturnTime: '2025-03-01 16:30' },
      { vehicleKey: 2, seq: 2, vehicleType: '厢式货车', brand: '福田', model: 'BJ1180', vin: 'LKLG7C4E4NA774760', plateNo: '京C11111', deliveryTime: '2025-02-15 14:00', deliveryPerson: '李四', deliveryStatus: '客户已签章', deliveryMileage: 13200, deliveryH2: 68, deliveryH2Unit: '%', deliveryElec: 38, hasAd: '无', hasTailgate: '无', spareTire: '有', driverTraining: '已完成', customerSignTime: '2025-02-15 16:20', vehicleReturned: false },
    ],
  },
];

/** 取交车任务关联合同的被授权人列表（原型本地种子；正式环境来自租赁合同） */
const dvGetAuthorizedPersons = (row) => {
  if (Array.isArray(row?.authorizedPersons)) return row.authorizedPersons;
  return DV_AUTHORIZED_PERSONS_SAMPLE;
};
const dvFindAuthorizedPerson = (id, persons) => {
  const list = Array.isArray(persons) ? persons : DV_AUTHORIZED_PERSONS_SAMPLE;
  return list.find((p) => p.id === id) || null;
};
/** 合同无被授权人时的提示；业管姓名取订单 businessOwner（合同业务负责人） */
const dvFormatEmptyAuthorizedHint = (businessOwner) => {
  const name = String(businessOwner || '').trim() || '业管';
  return `无授权人，请联系${name}补充授权人信息`;
};
const dvPersonInitial = (name) => {
  const s = String(name || '').trim();
  return s ? s.slice(-1) : '?';
};

const DV_IN_PROGRESS_STATUSES = ['未开始', '已保存', '待客户签章', '待重新签章'];
const DV_STATUS_FILTER_OPTIONS = ['', '未开始', '已保存', '待客户签章', '待重新签章'];
const DV_LIST_TABS = [
  { key: 'inProgress', short: '进行中', label: '进行中' },
  { key: 'completed', short: '已完成', label: '已完成' },
  { key: 'all', short: '全部', label: '全部任务' },
];
const DV_EMPTY_MORE_FILTER = { customerName: '', projectName: '', dateStart: '', dateEnd: '' };
const DV_EMPTY_FILTER_DRAFT = { status: '', ...DV_EMPTY_MORE_FILTER };

const dvIsHistoryStatus = (s) => s === '客户已签章';
const dvIsInProgressStatus = (s) => DV_IN_PROGRESS_STATUSES.indexOf(s || '未开始') >= 0;
const dvDisplayPlate = (p) => (p && String(p).trim() ? p : '车辆待选');
const dvFormatExpectedDate = (expectedDate) => {
  if (!expectedDate || !String(expectedDate).trim()) return '—';
  return String(expectedDate).trim().replace(/\s*至\s*/g, ' - ');
};
const dvDisplayActualTime = (t) => (t && String(t).trim() ? String(t).trim() : '—');
const dvDisplayMinuteTime = (t) => {
  const s = dvDisplayActualTime(t);
  if (s === '—') return s;
  const m = s.match(/^(\d{4}-\d{2}-\d{2}\s\d{2}:\d{2})/);
  return m ? m[1] : s;
};
/** 运维人员完成 E签宝签字时间（提交签章时自动写入，不在表单中填写） */
const dvFormatOpsSignTime = (date = new Date()) => {
  const pad = (n) => String(n).padStart(2, '0');
  return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
};
const dvVehicleDesc = (row) => [row.brand, row.model].filter(Boolean).join('·') || '—';
const dvStatusTag = (status) => {
  if (status === '客户已签章') return { text: status, cls: 'ok' };
  if (status === '待客户签章') return { text: status, cls: 'info' };
  if (status === '待重新签章') return { text: status, cls: 'warn' };
  if (status === '已保存') return { text: status, cls: 'warn' };
  return { text: status || '未开始', cls: 'neutral' };
};
const dvCardStatusClass = (status) => {
  if (status === '客户已签章') return 'ok';
  if (status === '待客户签章') return 'info';
  if (status === '待重新签章') return 'pending';
  return 'pending';
};
const dvParseDateOnly = (value) => {
  if (!value || !String(value).trim()) return null;
  const raw = String(value).trim().replace('T', ' ').slice(0, 10);
  return /^\d{4}-\d{2}-\d{2}$/.test(raw) ? raw : null;
};
const dvIsDeliveredStatus = (status) => status === '待客户签章' || status === '客户已签章';
const dvGetDeliveryDateForFilter = (row) => {
  if (!dvIsDeliveredStatus(row.deliveryStatus)) return null;
  return dvParseDateOnly(row.deliveryTime);
};
const dvIsReplaceDeliveryTask = (row) => row.taskSource === '替换车';
const dvRowMatchesDateRange = (row, dateStart, dateEnd) => {
  if (!dateStart && !dateEnd) return true;
  const d = dvGetDeliveryDateForFilter(row);
  if (!d) return false;
  if (dateStart && d < dateStart) return false;
  if (dateEnd && d > dateEnd) return false;
  return true;
};

const dvFlattenOrders = (orders) => {
  const rows = [];
  orders.forEach((order) => {
    (order.vehicleList || []).forEach((v) => {
      if (DV_OPERATOR_REGIONS.indexOf(order.deliveryRegion) < 0) return;
      rows.push({
        id: `${order.id}-${v.vehicleKey}`,
        orderId: order.id,
        vehicleKey: v.vehicleKey,
        seq: v.seq,
        expectedDate: order.expectedDate,
        contractCode: order.contractCode,
        projectName: order.projectName,
        customerName: order.customerName,
        businessDept: order.businessDept,
        businessOwner: order.businessOwner,
        authorizedPersons: Array.isArray(order.authorizedPersons) ? order.authorizedPersons : DV_AUTHORIZED_PERSONS_SAMPLE,
        taskSource: order.taskSource,
        bizType: order.bizType,
        deliveryRegion: order.deliveryRegion,
        deliveryAddress: order.deliveryAddress,
        createTime: order.createTime,
        createBy: order.createBy,
        vehicleType: v.vehicleType,
        brand: v.brand,
        model: v.model,
        vin: v.vin,
        replaceOldPlate: v.replaceOldPlate,
        plateNo: v.plateNo || '',
        deliveryTime: v.deliveryTime || '',
        deliveryPerson: v.deliveryPerson || '',
        deliveryStatus: v.deliveryStatus || '未开始',
        deliveryMileage: v.deliveryMileage,
        deliveryH2: v.deliveryH2,
        deliveryH2Unit: v.deliveryH2Unit || '%',
        deliveryElec: v.deliveryElec,
        deliveryRemark: v.deliveryRemark || '',
        serviceFee: v.serviceFee,
        deliveryLocation: v.deliveryLocation || null,
        hasAd: v.hasAd || '',
        hasTailgate: v.hasTailgate || '',
        spareTire: v.spareTire || '',
        spareTirePhotoUploaded: !!v.spareTirePhotoUploaded,
        spareTirePhotoUrl: v.spareTirePhotoUrl || '',
        spareTireTreadDepth: v.spareTireTreadDepth || '',
        driverTraining: v.driverTraining || '',
        vehicleReturned: v.vehicleReturned,
        vehicleReturnTime: v.vehicleReturnTime || '',
        customerSignTime: v.customerSignTime || '',
        inspectionList: v.inspectionList,
        authorizedPersonId: v.authorizedPersonId || '',
        authorizedPersonName: v.authorizedPersonName || '',
        authorizedPersonPhone: v.authorizedPersonPhone || '',
        signSent: !!v.signSent,
      });
    });
  });
  return rows;
};

const dvBuildEmptyForm = (row) => {
  const plateNo = row.plateNo || '';
  const rearEquip = dvGetRearEquipRecord(plateNo, row);
  const heavy = dvIsHeavyVehicle(row.vehicleType, row.model);
  const trainingDone = row.driverTraining === '已完成';
  const driverInfo = trainingDone ? dvMockDriverTrainingInfo(heavy) : {
    ...DV_DRIVER_MANUAL_EMPTY,
  };
  return {
    plateNo,
  brand: row.brand || '',
  model: row.model || '',
  vin: row.vin || '',
    vehicleType: row.vehicleType || '',
    hasAd: rearEquip.hasAd ? '有' : '无',
    hasTailgate: rearEquip.hasTailgate ? '有' : '无',
  spareTire: row.spareTire || '',
    spareTirePhotoUploaded: !!row.spareTirePhotoUploaded,
    spareTirePhotoUrl: row.spareTirePhotoUrl || '',
    spareTireTreadDepth: row.spareTireTreadDepth || '',
    rearEquip,
    adPhotoUploaded: false,
    bigWordPhotoUploaded: false,
  driverTraining: row.driverTraining || '',
    driverTrainingDone: trainingDone,
    ...driverInfo,
  deliveryMileage: row.deliveryMileage != null ? String(row.deliveryMileage) : '',
  deliveryH2: row.deliveryH2 != null ? String(row.deliveryH2) : '',
    deliveryH2Unit: row.deliveryH2Unit || dvGetModelGaugeUnit(row.brand, row.model),
  deliveryElec: row.deliveryElec != null ? String(row.deliveryElec) : '',
    serviceFee: row.serviceFee != null ? String(row.serviceFee) : '',
    deliveryRemark: row.deliveryRemark || '',
    inspectionList: Array.isArray(row.inspectionList) && row.inspectionList.length ? row.inspectionList : dvBuildInspectionList(),
    deliveryPhotos: row.deliveryPhotos && typeof row.deliveryPhotos === 'object' ? { ...row.deliveryPhotos } : {},
    deliveryLocation: row.deliveryLocation || null,
    authorizedPersonId: row.authorizedPersonId || '',
    authorizedPersonName: row.authorizedPersonName || '',
    authorizedPersonPhone: row.authorizedPersonPhone || '',
    signSent: !!row.signSent,
  };
};

const dvMergeVehicleIntoForm = (prev, vehicle, row) => {
  const rearEquip = dvGetRearEquipRecord(vehicle.plateNo, row);
  const brand = vehicle.brand || prev.brand || row.brand;
  const model = vehicle.model || prev.model || row.model;
  return {
    ...prev,
    plateNo: vehicle.plateNo,
    brand,
    model,
    vin: vehicle.vin || prev.vin || row.vin,
    vehicleType: row.vehicleType || prev.vehicleType,
    hasAd: rearEquip.hasAd ? '有' : '无',
    hasTailgate: rearEquip.hasTailgate ? '有' : '无',
    rearEquip,
    deliveryH2Unit: dvGetModelGaugeUnit(brand, model),
    deliveryLocation: null,
    adPhotoUploaded: false,
    bigWordPhotoUploaded: false,
  };
};

const dvFormatH2 = (v, unit) => (v == null || v === '' ? '—' : `${dvFormatMetric2(v)} ${unit || '%'}`);
const dvFormatMileage = (v) => (v == null || v === '' ? '—' : `${dvFormatMetric2(v)} km`);

const buildPickupDetail = (task) => {
  const isShanghai = task?.bizNo === 'TC-2026-0312';
  const projectInfo = isShanghai
    ? {
        collectCode: 'HT-ZL-2025-088TC0001', contractCode: 'HT-ZL-2025-088', contractType: '正式合同',
        projectName: '上海氢能城际物流项目', customerName: '上海迅杰物流有限公司',
        paymentMethod: '预付', paymentCycle: '6个月', contractStart: '2025-03-01', contractEnd: '2026-02-28',
        businessDept: '业务2部', businessPerson: '李晓彤',
      }
    : {
        collectCode: 'HT-ZL-2025-001TC0001', contractCode: 'HT-ZL-2025-001', contractType: '正式合同',
        projectName: '嘉兴氢能示范项目', customerName: '嘉兴某某物流有限公司',
        paymentMethod: '预付', paymentCycle: '6个月', contractStart: '2025-01-15', contractEnd: '2026-01-14',
        businessDept: '业务1部', businessPerson: '张经理',
      };
  const vehicles = isShanghai
    ? [
        { key: 'v1', index: 1, brand: '东风', model: 'DFH1180', plateNo: '沪A88123', receivableRent: 62000, actualRent: '61200.00', rentRemark: '首期六期一次性付清', receivableDeposit: 12000, receivableService: 880, actualService: '860.00', discountAmount: '200.00', discountRemark: '首月优惠', serviceItems: [{ name: '代处理费用', receivable: 280, actual: '280.00', discount: '0.00', remark: '' }, { name: '保险上浮', receivable: 600, actual: '580.00', discount: '20.00', remark: '客户协商' }] },
        { key: 'v2', index: 2, brand: '福田', model: 'BJ1180', plateNo: '沪A88234', receivableRent: 58000, actualRent: '58000.00', rentRemark: '', receivableDeposit: 10000, receivableService: 500, actualService: '500.00', discountAmount: '0.00', discountRemark: '', serviceItems: [{ name: '保养费用', receivable: 500, actual: '500.00', discount: '0.00', remark: '含首保' }] },
        { key: 'v3', index: 3, brand: '重汽', model: 'ZZ1187', plateNo: '沪A88345', receivableRent: 64000, actualRent: '63600.00', rentRemark: '按合同约定', receivableDeposit: 12000, receivableService: 720, actualService: '700.00', discountAmount: '150.00', discountRemark: '批量提车减免', serviceItems: [{ name: '代处理费用', receivable: 220, actual: '220.00', discount: '0.00', remark: '' }, { name: '上牌服务', receivable: 500, actual: '480.00', discount: '20.00', remark: '已含上牌' }] },
      ]
    : [
        { key: 'v1', index: 1, brand: '东风', model: 'DFH1180', plateNo: '浙A12345', receivableRent: 30000, actualRent: '29800.00', rentRemark: '首期六期一次性付清', receivableDeposit: 10000, receivableService: 700, actualService: '680.00', discountAmount: '200.00', discountRemark: '首月优惠', serviceItems: [{ name: '代处理费用', receivable: 200, actual: '200.00', discount: '0.00', remark: '' }, { name: '保险上浮', receivable: 500, actual: '480.00', discount: '20.00', remark: '客户协商' }] },
        { key: 'v2', index: 2, brand: '福田', model: 'BJ1180', plateNo: '浙A23456', receivableRent: 27000, actualRent: '27000.00', rentRemark: '', receivableDeposit: 8000, receivableService: 300, actualService: '300.00', discountAmount: '0.00', discountRemark: '', serviceItems: [{ name: '保养费用', receivable: 300, actual: '300.00', discount: '0.00', remark: '含首保' }] },
      ];
  const hasHydrogenPrepay = true;
  const hydrogen = isShanghai
    ? { receivable: '4200.00', actual: '4100.00', discount: '100.00', discountRemark: '预付款批量减免' }
    : { receivable: '3580.00', actual: '3500.00', discount: '80.00', discountRemark: '预付款批量减免' };
  const approvalSteps = isShanghai
    ? [
        { title: '业管中心负责人审批', department: '业务2部', status: '已通过', person: '姚守涛', approveTime: '2026-05-30 10:30' },
        { title: '业务总负责人审批', department: '业务总部', status: '已通过', person: '尚建华', approveTime: '2026-05-30 10:05', comment: '合同里面有加里程吗？' },
        { title: '财务审批', department: '财务部', status: '待审批', person: '张明辉', approveTime: '—' },
      ]
    : [
        { title: '业务经理审批', department: '业务1部', status: '已通过', person: '张经理', approveTime: '2026-02-28 09:30' },
        { title: '财务审批', department: '财务部', status: '已通过', person: '李财务', approveTime: '2026-02-28 10:15' },
        { title: '事业部主管审批', department: '事业部', status: '已通过', person: '王总', approveTime: '2026-03-01 11:20' },
      ];
  return {
    projectInfo, vehicles, hasHydrogenPrepay, hydrogen, approvalSteps,
    invoiceMethod: '先开票后付款',
    invoiceRemark: isShanghai
      ? '增值税专用发票，税率13%，开票项目：*现代服务*车辆租赁费；备注：上海氢能城际物流项目-提车首付款'
      : '增值税专用发票，税率13%，开票项目：*现代服务*车辆租赁费；备注：嘉兴氢能示范项目-提车首付款',
  };
};

const calcPickupTotals = (vehicles, hasHydrogenPrepay, hydrogen) => {
  let receivableRent = 0; let actualRent = 0; let receivableDeposit = 0;
  let receivableService = 0; let actualService = 0; let discountTotal = 0;
  vehicles.forEach((v) => {
    receivableRent += Number(v.receivableRent) || 0;
    actualRent += parseFloat(v.actualRent) || 0;
    receivableDeposit += Number(v.receivableDeposit) || 0;
    receivableService += Number(v.receivableService) || 0;
    actualService += parseFloat(v.actualService) || 0;
    discountTotal += parseFloat(v.discountAmount) || 0;
  });
  const hydrogenReceivable = hasHydrogenPrepay ? (parseFloat(hydrogen.receivable) || 0) : 0;
  const hydrogenActual = hasHydrogenPrepay ? (parseFloat(hydrogen.actual) || 0) : 0;
  return {
    receivableRent: receivableRent.toFixed(2), actualRent: actualRent.toFixed(2),
    receivableDeposit: receivableDeposit.toFixed(2), receivableService: receivableService.toFixed(2),
    actualService: actualService.toFixed(2), discountTotal: discountTotal.toFixed(2),
    receivableTotal: (receivableRent + receivableDeposit + receivableService + hydrogenReceivable).toFixed(2),
    actualTotal: (actualRent + receivableDeposit + actualService - discountTotal + hydrogenActual).toFixed(2),
  };
};

const TcInfoRow = ({ label, value, full }) => (
  <div className={`tc-kv-item${full ? ' tc-kv-value--full' : ''}`}>
    <div className="tc-kv-label">{label}</div>
    <div className="tc-kv-value">{value || '—'}</div>
  </div>
);

const IconFlowComment = () => (
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" aria-hidden="true">
    <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" />
  </svg>
);

const approvalStepAvatarText = (person) => {
  const name = String(person || '').trim().replace(/^[^-\u4e00-\u9fa5a-zA-Z]+/u, '').replace(/.*-/u, '');
  if (!name || name === '—') return '—';
  return name.length <= 2 ? name : name.slice(-2);
};

const approvalStepStatusView = (status) => {
  if (status === '已通过') return { label: '通过', tagClass: 'tc-flow-tag--ok', pending: false };
  if (status === '已驳回' || status === '已撤回') {
    return { label: status, tagClass: 'tc-flow-tag--reject', pending: false };
  }
  return { label: '待审核', tagClass: 'tc-flow-tag--pending', pending: true };
};

const approvalStepTitle = (step) => step?.title || step?.department || '—';

const ApprovalTimeline = ({ steps = [] }) => (
  <div className="tc-section">
    <div className="tc-section-head"><span className="tc-section-title">审批情况</span></div>
    <div className="tc-flow">
      {steps.map((step, idx) => {
        const statusView = approvalStepStatusView(step.status);
        const showTime = step.approveTime && step.approveTime !== '—';
        return (
          <div className="tc-flow-step" key={`${approvalStepTitle(step)}-${idx}`}>
            <div className={`tc-flow-avatar${statusView.pending ? ' tc-flow-avatar--pending' : ''}`}>
              {approvalStepAvatarText(step.person)}
            </div>
            <div className="tc-flow-body">
              <div className="tc-flow-head">
                <div className="tc-flow-title">{approvalStepTitle(step)}</div>
                <span className={`tc-flow-tag ${statusView.tagClass}`}>{statusView.label}</span>
              </div>
              {(step.person || showTime) ? (
                <div className="tc-flow-meta">
                  {[step.person, showTime ? step.approveTime : ''].filter(Boolean).join(' ')}
                </div>
              ) : null}
              {step.comment ? (
                <div className="tc-flow-comment">
                  <span className="tc-flow-comment-icon"><IconFlowComment /></span>
                  <span>{step.comment}</span>
                </div>
              ) : null}
            </div>
          </div>
        );
      })}
    </div>
  </div>
);

const TcVehicleCard = ({ vehicle }) => {
  const [serviceOpen, setServiceOpen] = useState(false);
  const vehicleActual = (
    parseFloat(vehicle.actualRent || 0)
    + parseFloat(vehicle.receivableDeposit || 0)
    + parseFloat(vehicle.actualService || 0)
    - parseFloat(vehicle.discountAmount || 0)
  ).toFixed(2);
  return (
    <div className="tc-vehicle-card">
      <div className="tc-vehicle-head">
        <div>
          <div className="tc-vehicle-plate">{vehicle.plateNo || '待上牌'}</div>
          <div className="tc-vehicle-model">{vehicle.brand} · {vehicle.model}</div>
        </div>
        <span className="tc-vehicle-idx">#{vehicle.index}</span>
      </div>
      <div className="tc-vehicle-amount-row">
        <span className="tc-vehicle-amount-label">本车实收合计</span>
        <span className="tc-vehicle-amount-val">{formatMoneySymbol(vehicleActual)}</span>
      </div>
      <div className="tc-vehicle-kv">
        <div className="tc-vehicle-kv-item"><span>实收月租金 </span>{formatYuan(vehicle.actualRent)}</div>
        <div className="tc-vehicle-kv-item"><span>应收保证金 </span>{formatYuan(vehicle.receivableDeposit)}</div>
        <div className="tc-vehicle-kv-item"><span>实收服务费 </span>{formatYuan(vehicle.actualService)}</div>
        <div className="tc-vehicle-kv-item"><span>减免金额 </span>{formatYuan(vehicle.discountAmount)}</div>
      </div>
      {(vehicle.serviceItems || []).length > 0 && (
        <>
          <button type="button" className="tc-service-toggle" onClick={() => setServiceOpen((o) => !o)}>
            {serviceOpen ? '收起' : '查看'}服务费明细
          </button>
          {serviceOpen && (
            <div className="tc-service-list">
              {(vehicle.serviceItems || []).map((s, i) => (
                <div className="tc-service-row" key={i}>
                  <span className="tc-service-name">{s.name}</span>
                  <span className="tc-service-amt">{formatMoneySymbol(s.actual)}</span>
                  <span className="tc-service-sub">应收 {formatYuan(s.receivable)} · 减免 {formatYuan(s.discount)}</span>
                </div>
              ))}
            </div>
          )}
        </>
      )}
    </div>
  );
};

const APPROVAL_DECISION_META = {
  approve: { title: '审批通过', success: '审批已通过（原型）' },
  reject: { title: '审批驳回', success: '已驳回（原型）' },
  terminate: { title: '审批终止', success: '已终止（原型）' },
};

const MOCK_CC_CANDIDATES = ['李晓彤', '陈高伟', '王法务', '财务-赵敏'];
const UPLOAD_ACCEPT = '.png,.jpg,.jpeg,.doc,.docx,.xlsx,.xls,.ppt,.pdf';
const UPLOAD_HINT = (
  <>
    请上传不超过 <strong>20MB</strong> 的 <strong>png, jpg, jpeg, doc, docx, xlsx, xls, ppt, pdf</strong> 格式文件
  </>
);

const emptyDecisionForm = () => ({
  notifyEmail: false,
  notifySms: false,
  ccUsers: [],
  opinion: '',
  attachments: [],
});

const TcMiniSheetShell = ({ open, title, onClose, onMaskClose, footer, children, annotationId }) => {
  if (!open) return null;
  const handleMask = onMaskClose || onClose;
  return (
    <div
      className="tc-mini-sheet"
      role="dialog"
      aria-modal="true"
      aria-label={title}
      {...(annotationId ? { 'data-annotation-id': annotationId } : {})}
    >
      <button type="button" className="tc-mini-sheet-mask" onClick={handleMask} aria-label="关闭" />
      <div className="tc-mini-sheet-panel">
        <div className="tc-mini-sheet-handle" aria-hidden="true" />
        <div className="tc-mini-sheet-head">
          <span className="tc-mini-sheet-title">{title}</span>
          <button type="button" className="tc-mini-sheet-close" onClick={onClose} aria-label="关闭">×</button>
        </div>
        <div className="tc-mini-sheet-body tc-mini-sheet-body--form">{children}</div>
        {footer ? <div className="tc-mini-sheet-foot">{footer}</div> : null}
      </div>
    </div>
  );
};

const ApprovalDecisionDrawer = ({ open, actionType, onClose, onConfirm }) => {
  const meta = APPROVAL_DECISION_META[actionType] || APPROVAL_DECISION_META.approve;
  const [form, setForm] = useState(emptyDecisionForm);
  const [ccPickerOpen, setCcPickerOpen] = useState(false);
  const fileInputRef = useRef(null);

  useEffect(() => {
    if (open) {
      setForm(emptyDecisionForm());
      setCcPickerOpen(false);
    }
  }, [open, actionType]);

  const handleSheetClose = () => {
    if (ccPickerOpen) {
      setCcPickerOpen(false);
      return;
    }
    onClose();
  };

  const toggleCcUser = (name) => {
    setForm((p) => ({
      ...p,
      ccUsers: p.ccUsers.includes(name) ? p.ccUsers.filter((n) => n !== name) : [...p.ccUsers, name],
    }));
  };

  const handleUpload = (e) => {
    const files = Array.from(e.target.files || []);
    if (!files.length) return;
    setForm((p) => ({
      ...p,
      attachments: [
        ...p.attachments,
        ...files.map((f) => ({ id: `${f.name}-${Date.now()}`, name: f.name, size: f.size })),
      ],
    }));
    e.target.value = '';
  };

  const handleConfirm = () => {
    onConfirm?.({ actionType, ...form, notifyInternal: true });
    onClose();
  };

  const sheetFooter = ccPickerOpen ? (
    <div className="tc-mini-sheet-actions">
      <button type="button" className="tc-drawer-foot-btn tc-drawer-foot-cancel" onClick={() => setCcPickerOpen(false)}>返回</button>
      <button type="button" className="tc-drawer-foot-btn tc-drawer-foot-confirm" onClick={() => setCcPickerOpen(false)}>确定</button>
    </div>
  ) : (
    <div className="tc-mini-sheet-actions">
      <button type="button" className="tc-drawer-foot-btn tc-drawer-foot-cancel" onClick={onClose}>取消</button>
      <button type="button" className="tc-drawer-foot-btn tc-drawer-foot-confirm" onClick={handleConfirm}>确认</button>
    </div>
  );

  return (
    <TcMiniSheetShell
      open={open}
      title={ccPickerOpen ? '选择抄送人' : meta.title}
      onClose={handleSheetClose}
      onMaskClose={handleSheetClose}
      footer={sheetFooter}
      annotationId="tc-approval-decision-modal"
    >
      {ccPickerOpen ? (
        <div className="tc-person-list">
          {MOCK_CC_CANDIDATES.map((name) => (
            <button
              key={name}
              type="button"
              className={`tc-person-item${form.ccUsers.includes(name) ? ' selected' : ''}`}
              onClick={() => toggleCcUser(name)}
            >
              {name}
            </button>
          ))}
        </div>
      ) : (
        <div className="tc-approval-form">
          <div className="tc-form-field">
            <div className="tc-form-label">通知方式</div>
            <div className="tc-notify-group">
              <label className="tc-notify-item disabled">
                <input type="checkbox" checked disabled readOnly />站内信
              </label>
              <label className="tc-notify-item">
                <input
                  type="checkbox"
                  checked={form.notifyEmail}
                  onChange={(e) => setForm((p) => ({ ...p, notifyEmail: e.target.checked }))}
                />
                邮件
              </label>
              <label className="tc-notify-item">
                <input
                  type="checkbox"
                  checked={form.notifySms}
                  onChange={(e) => setForm((p) => ({ ...p, notifySms: e.target.checked }))}
                />
                短信
              </label>
            </div>
          </div>
          <div className="tc-form-field">
            <div className="tc-form-label">附件上传</div>
            <button type="button" className="tc-upload-btn" onClick={() => fileInputRef.current?.click()}>
              <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /><polyline points="17 8 12 3 7 8" /><line x1="12" y1="3" x2="12" y2="15" /></svg>
              上传
            </button>
            <input ref={fileInputRef} type="file" accept={UPLOAD_ACCEPT} multiple style={{ display: 'none' }} onChange={handleUpload} />
            <div className="tc-upload-hint">{UPLOAD_HINT}</div>
            {form.attachments.length > 0 && (
              <div className="tc-file-list">
                {form.attachments.map((f) => (
                  <div className="tc-file-item" key={f.id}>
                    <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{f.name}</span>
                    <button
                      type="button"
                      className="tc-file-remove"
                      onClick={() => setForm((p) => ({ ...p, attachments: p.attachments.filter((a) => a.id !== f.id) }))}
                    >
                      删除
                    </button>
                  </div>
                ))}
              </div>
            )}
          </div>
          <div className="tc-form-field">
            <div className="tc-form-label">抄送人</div>
            <button
              type="button"
              className={`tc-select-person${form.ccUsers.length ? ' has-value' : ''}`}
              onClick={() => setCcPickerOpen(true)}
            >
              {form.ccUsers.length ? `已选 ${form.ccUsers.length} 人` : '选择人员'}
            </button>
            {form.ccUsers.length > 0 && (
              <div className="tc-cc-chips">
                {form.ccUsers.map((name) => (
                  <span className="tc-cc-chip" key={name}>
                    {name}
                    <button type="button" aria-label={`移除${name}`} onClick={() => toggleCcUser(name)}>×</button>
                  </span>
                ))}
              </div>
            )}
          </div>
          <div className="tc-form-field">
            <div className="tc-form-label">审批意见</div>
            <textarea
              className="tc-textarea"
              placeholder="请输入"
              value={form.opinion}
              onChange={(e) => setForm((p) => ({ ...p, opinion: e.target.value }))}
              maxLength={500}
            />
          </div>
        </div>
      )}
    </TcMiniSheetShell>
  );
};

const ApprovalCommentDrawer = ({ open, onClose, onConfirm }) => {
  const [content, setContent] = useState('');

  useEffect(() => {
    if (open) setContent('');
  }, [open]);

  const handleConfirm = () => {
    if (!content.trim()) {
      mpToast.warning('请输入评论内容');
      return;
    }
    onConfirm?.({ content: content.trim() });
    onClose();
  };

  return (
    <TcMiniSheetShell
      open={open}
      title="添加评论"
      onClose={onClose}
      annotationId="tc-approval-comment-sheet"
      footer={(
        <div className="tc-mini-sheet-actions">
          <button type="button" className="tc-drawer-foot-btn tc-drawer-foot-cancel" onClick={onClose}>取消</button>
          <button type="button" className="tc-drawer-foot-btn tc-drawer-foot-confirm" onClick={handleConfirm}>确认</button>
        </div>
      )}
    >
      <div className="tc-form-field">
        <div className="tc-form-label tc-form-label-required">评论内容</div>
        <textarea
          className="tc-textarea"
          placeholder="请输入评论内容"
          value={content}
          onChange={(e) => setContent(e.target.value.slice(0, 500))}
          maxLength={500}
        />
        <div className="tc-textarea-counter">{content.length} / 500</div>
      </div>
    </TcMiniSheetShell>
  );
};

const TcMiniBottomSheet = ({ open, title, onClose, rows, totalLabel, totalValue, totalTheme }) => {
  if (!open) return null;
  const totalCls = totalTheme === 'blue' ? ' tc-drawer-total--blue' : totalTheme === 'purple' ? ' tc-drawer-total--purple' : '';
  const highlightCls = totalTheme === 'blue' ? ' highlight--blue' : totalTheme === 'purple' ? ' highlight--purple' : totalTheme === 'orange' ? ' highlight' : '';
  return (
    <div className="tc-mini-sheet" role="dialog" aria-modal="true" aria-label={title}>
      <button type="button" className="tc-mini-sheet-mask" onClick={onClose} aria-label="关闭" />
      <div className="tc-mini-sheet-panel">
        <div className="tc-mini-sheet-handle" aria-hidden="true" />
        <div className="tc-mini-sheet-head">
          <span className="tc-mini-sheet-title">{title}</span>
          <button type="button" className="tc-mini-sheet-close" onClick={onClose} aria-label="关闭">×</button>
        </div>
        <div className="tc-mini-sheet-body">
          {(rows || []).map((r) => (
            <div className="tc-drawer-row" key={r.label}>
              <span style={{ color: COLOR_TEXT_SEC, flex: 1, minWidth: 0, paddingRight: 8 }}>{r.label}</span>
              <span className={`tc-drawer-row-val${r.highlight ? highlightCls : ''}`} style={{ fontWeight: 700, flexShrink: 0, textAlign: 'right' }}>{r.value}</span>
            </div>
          ))}
          <div className={`tc-drawer-total${totalCls}`}>
            <span>{totalLabel}</span>
            <span>{totalValue}</span>
          </div>
        </div>
      </div>
    </div>
  );
};

const ApprovalActionBar = ({ mode, onComment, onTerminate, onReject, onApprove }) => {
  if (mode === 'approve') {
    return (
      <div className="tc-action-bar">
        <button type="button" className="tc-btn tc-btn-comment" onClick={onComment}>评论</button>
        <button type="button" className="tc-btn tc-btn-terminate" onClick={onTerminate}>终止</button>
        <button type="button" className="tc-btn tc-btn-reject" onClick={onReject}>驳回</button>
        <button type="button" className="tc-btn tc-btn-approve" data-annotation-id="tc-btn-approve" onClick={onApprove}>通过</button>
      </div>
    );
  }
  return (
    <div className="tc-action-bar tc-action-bar--view">
      <button type="button" className="tc-btn tc-btn-comment" style={{ flex: '0 0 88px' }} onClick={onComment}>评论</button>
    </div>
  );
};

const PickupReceivableApprovePage = ({ task, mode, onBack }) => {
  const detail = useMemo(() => buildPickupDetail(task), [task]);
  const { projectInfo, vehicles, hasHydrogenPrepay, hydrogen, approvalSteps, invoiceMethod, invoiceRemark } = detail;
  const totals = useMemo(() => calcPickupTotals(vehicles, hasHydrogenPrepay, hydrogen), [vehicles, hasHydrogenPrepay, hydrogen]);
  const [actualDrawerOpen, setActualDrawerOpen] = useState(false);
  const displayActualTotal = acPickupDisplayActualAmount(task);
  const diff = (parseFloat(totals.receivableTotal) - parseFloat(displayActualTotal)).toFixed(2);

  const actualRows = [
    { label: '总计实收车辆月租金', value: formatYuan(totals.actualRent) },
    { label: '总计应收车辆保证金', value: formatYuan(totals.receivableDeposit) },
    { label: '总计实收服务费', value: formatYuan(totals.actualService) },
    { label: '总计减免金额', value: `- ${formatYuan(totals.discountTotal)}` },
  ];
  if (hasHydrogenPrepay) actualRows.push({ label: '氢费预充值实收金额', value: formatYuan(hydrogen.actual), highlight: true });

  const [decisionOpen, setDecisionOpen] = useState(false);
  const [decisionType, setDecisionType] = useState('approve');
  const [commentOpen, setCommentOpen] = useState(false);
  const showActions = mode === 'approve' || mode === 'view';

  const openDecision = (type) => {
    setDecisionType(type);
    setDecisionOpen(true);
  };

  const handleDecisionConfirm = (payload) => {
    const meta = APPROVAL_DECISION_META[payload.actionType];
    if (payload.actionType === 'reject' || payload.actionType === 'terminate') {
      mpToast.warning(meta?.success || '操作成功（原型）');
    } else {
      mpToast.success(meta?.success || '操作成功（原型）');
    }
    if (mode === 'approve') onBack();
  };

  const handleCommentConfirm = () => {
    mpToast.success('评论已添加（原型）');
  };

  return (
    <>
      <div className="tc-scroll">
        <div className="tc-hero" data-annotation-id="page-audit-pickup">
          <div className="tc-hero-label">实收款总额</div>
          <div className="tc-hero-amount">{formatMoneySymbol(displayActualTotal)}</div>
          <div className="tc-hero-meta">
            <span>{projectInfo.customerName}</span>
          </div>
          <div className="tc-hero-foot tc-hero-foot--compact">
            <div className="tc-hero-foot-row"><span className="tc-hero-foot-label">合同编码</span>{projectInfo.contractCode || '—'}</div>
            <div className="tc-hero-foot-row"><span className="tc-hero-foot-label">项目名称</span>{projectInfo.projectName || '—'}</div>
          </div>
          <div className="tc-hero-period">
            <span className="tc-hero-period-tag">{vehicles.length} 台车</span>
          </div>
          <div className="tc-hero-sub">
            <div className="tc-hero-compare">
              应收款总额 {formatMoneySymbol(totals.receivableTotal)}
              <br />
              较应收减免 {formatMoneySymbol(diff)}
            </div>
            <button type="button" className="tc-hero-detail-btn" onClick={() => setActualDrawerOpen(true)}>实收明细</button>
          </div>
        </div>
        <div className="tc-section">
          <div className="tc-section-head">
            <span className="tc-section-title">提车应收款 · 车辆明细</span>
            <span className="tc-section-badge">{vehicles.length} 台</span>
          </div>
          {vehicles.map((v) => <TcVehicleCard key={v.key} vehicle={v} />)}
        </div>
        {hasHydrogenPrepay && (
          <div className="tc-section">
            <div className="tc-section-head"><span className="tc-section-title">氢费预付款</span></div>
            <div className="tc-kv-grid">
              <TcInfoRow label="应收金额" value={formatYuan(hydrogen.receivable)} />
              <TcInfoRow label="实收金额" value={formatYuan(hydrogen.actual)} />
              <TcInfoRow label="减免金额" value={formatYuan(hydrogen.discount)} />
              <TcInfoRow label="减免备注" value={hydrogen.discountRemark} full />
            </div>
          </div>
        )}
        <div className="tc-section">
          <div className="tc-section-head"><span className="tc-section-title">开票信息</span></div>
          <div className="tc-kv-grid">
            <TcInfoRow label="开票方式" value={invoiceMethod} />
            <TcInfoRow label="开票备注" value={invoiceRemark} full />
          </div>
        </div>
        <ApprovalTimeline steps={approvalSteps} />
        <div style={{ height: showActions ? 16 : 24 }} />
      </div>
      {showActions && !decisionOpen && !commentOpen && (
        <ApprovalActionBar
          mode={mode}
          onComment={() => setCommentOpen(true)}
          onTerminate={() => openDecision('terminate')}
          onReject={() => openDecision('reject')}
          onApprove={() => openDecision('approve')}
        />
      )}
      <ApprovalDecisionDrawer
        open={decisionOpen}
        actionType={decisionType}
        onClose={() => setDecisionOpen(false)}
        onConfirm={handleDecisionConfirm}
      />
      <ApprovalCommentDrawer
        open={commentOpen}
        onClose={() => setCommentOpen(false)}
        onConfirm={handleCommentConfirm}
      />
      <TcMiniBottomSheet
        open={actualDrawerOpen}
        title="实收款明细"
        onClose={() => setActualDrawerOpen(false)}
        rows={actualRows}
        totalLabel="实收款总额"
        totalValue={formatMoneySymbol(displayActualTotal)}
        totalTheme="orange"
      />
    </>
  );
};

const HC_CUSTOMER_SERVICE_FIXED = ['ETC-客户未缴费用', 'ETC卡缺损费', 'ETC设备缺损费', '租金'];
const HC_SAFETY_FIXED = ['违章处理违约金', '保险上浮', '其他违规费用'];
const HC_OPERATION_FIXED = ['清洗费', '未结算保养', '未结算维修', '车损费用', '工具损坏丢失费用', '证件丢失费用', '广告损坏丢失费用', '送车服务费', '接车服务费', '轮胎磨损费用'];

const buildHcFeeRows = (fixedItems, amountMap, customRows) => {
  const fixed = fixedItems.map((name, i) => ({
    key: `fixed-${i}-${name}`,
    seq: i + 1,
    feeItem: name,
    amount: amountMap[name] != null ? amountMap[name] : '0.00',
    isCustom: false,
  }));
  const custom = (customRows || []).map((r, i) => ({
    key: r.key || `custom-${i}`,
    seq: fixed.length + i + 1,
    feeItem: r.feeItem,
    amount: r.amount || '0.00',
    isCustom: true,
  }));
  return [...fixed, ...custom];
};

const sumFeeRows = (rows) => rows.reduce((s, r) => s + (parseFloat(r.amount) || 0), 0).toFixed(2);

const formatFeeCellAmount = (val) => {
  const n = parseFloat(val);
  if (Number.isNaN(n)) return '0.00';
  return n.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
};

const buildHcOperationFeeRows = (fixedItems, rowMap, customRows) => {
  const normalizeRow = (name, data, isCustom, key, seq) => {
    const src = data && typeof data === 'object' ? data : { amount: data };
    return {
      key: key || `op-${seq}-${name}`,
      seq,
      feeItem: name,
      amount: src.amount != null ? String(src.amount) : '0.00',
      worryFreeDeduction: src.worryFreeDeduction != null ? String(src.worryFreeDeduction) : '0.00',
      feeItemTip: src.feeItemTip || '',
      isCustom,
    };
  };
  const fixed = fixedItems.map((name, i) => normalizeRow(name, rowMap[name], false, `op-fixed-${i}-${name}`, i + 1));
  const custom = (customRows || []).map((r, i) => normalizeRow(
    r.feeItem,
    r,
    true,
    r.key || `op-custom-${i}`,
    fixed.length + i + 1,
  ));
  return [...fixed, ...custom];
};

const sumOperationFeeRows = (rows) => (
  rows.reduce((s, r) => {
    const amt = parseFloat(r.amount) || 0;
    const ded = parseFloat(r.worryFreeDeduction) || 0;
    return s + Math.max(0, amt - ded);
  }, 0).toFixed(2)
);

const HcFeeMoneyCell = ({ value }) => (
  <div className="hc-fee-money-field">
    <span className="hc-fee-money-input" aria-label={`金额 ${formatFeeCellAmount(value)} 元`}>{formatFeeCellAmount(value)}</span>
    <span className="hc-fee-money-unit">元</span>
  </div>
);

const HcFeeInfoButton = ({ label, onExplain }) => (
  <button type="button" className="hc-fee-info-btn" aria-label={label} onClick={onExplain}>
    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" aria-hidden="true">
      <circle cx="12" cy="12" r="9" stroke="currentColor" strokeWidth="1.8" />
      <path d="M12 11v5" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" />
      <circle cx="12" cy="8" r="1" fill="currentColor" />
    </svg>
  </button>
);

const HcOperationFeeTable = ({ rows }) => (
  <>
    <div className="hc-fee-table-scroll">
      <div className="hc-fee-table hc-fee-table--ops">
        <div className="hc-fee-table-head">
          <span className="hc-fee-table-col hc-fee-table-col--seq">序号</span>
          <span className="hc-fee-table-col hc-fee-table-col--item">费用项</span>
          <span className="hc-fee-table-col hc-fee-table-col--money">金额</span>
          <span className="hc-fee-table-col hc-fee-table-col--deduct-head">
            无忧包减免
            <HcFeeInfoButton
              label="无忧包减免说明"
              onExplain={() => mpToast.info('无忧包可抵扣的运维类费用，减免后计入运维组应结算金额（原型）')}
            />
          </span>
        </div>
        {(rows || []).map((r) => (
          <div className={`hc-fee-table-row${r.isCustom ? ' hc-fee-table-row--custom' : ''}`} key={r.key}>
            <span className="hc-fee-table-col hc-fee-table-col--seq">{r.seq}</span>
            <span className="hc-fee-table-col hc-fee-table-col--item">
              {r.feeItem}
              {r.feeItemTip ? (
                <HcFeeInfoButton
                  label={r.feeItemTip}
                  onExplain={() => mpToast.info(r.feeItemTip)}
                />
              ) : null}
              {r.isCustom ? <span className="hc-fee-table-tag">手动</span> : null}
            </span>
            <span className="hc-fee-table-col hc-fee-table-col--money">
              <HcFeeMoneyCell value={r.amount} />
            </span>
            <span className="hc-fee-table-col hc-fee-table-col--money">
              <HcFeeMoneyCell value={r.worryFreeDeduction} />
            </span>
          </div>
        ))}
      </div>
    </div>
    <div className="hc-fee-ops-footnote">应结算金额 = 各费用项金额 − 无忧包减免</div>
  </>
);

const calcSafetyInfo = (violations) => {
  const list = violations || [];
  let paid = 0;
  let unpaid = 0;
  list.forEach((v) => {
    const amt = parseFloat(v.penaltyAmount) || 0;
    const status = v.paymentStatus || '';
    if (status === '已缴费' || status === '已缴') paid += amt;
    else unpaid += amt;
  });
  return {
    violationCount: list.length,
    paidAmount: paid.toFixed(2),
    unpaidAmount: unpaid.toFixed(2),
  };
};

const HcFeeItemTable = ({ rows }) => (
  <div className="hc-fee-table">
    <div className="hc-fee-table-head">
      <span className="hc-fee-table-col hc-fee-table-col--seq">序号</span>
      <span className="hc-fee-table-col hc-fee-table-col--item">费用项</span>
      <span className="hc-fee-table-col hc-fee-table-col--amt">金额</span>
    </div>
    {(rows || []).map((r) => (
      <div className={`hc-fee-table-row${r.isCustom ? ' hc-fee-table-row--custom' : ''}`} key={r.key}>
        <span className="hc-fee-table-col hc-fee-table-col--seq">{r.seq}</span>
        <span className="hc-fee-table-col hc-fee-table-col--item">
          {r.feeItem}
          {r.isCustom ? <span className="hc-fee-table-tag">手动</span> : null}
        </span>
        <span className="hc-fee-table-col hc-fee-table-col--amt">{formatYuan(r.amount)}</span>
      </div>
    ))}
  </div>
);

const buildReturnSettlementDetail = (task) => {
  const isHangzhou = String(task?.bizNo || '').startsWith('HC-2026-0625') || task?.plateNo === '浙F06901F';
  const isGuangzhou = String(task?.bizNo || '').startsWith('HC-2026-0418') || task?.plateNo === '粤B58888F';
  const vehicle = isHangzhou
    ? {
        plateNo: '浙F06901F', contractCode: 'LNZLHT20260315002', projectName: '杭州城配氢能项目',
        customerName: '杭州绿运科技有限公司', deliveryTime: '2026-03-15 10:00', returnTime: '2026-06-24 14:30',
        fragileInsurance: '是', tireInsurance: '是', maintenanceInsurance: '否',
      }
    : isGuangzhou
    ? {
        plateNo: '粤B58888F', contractCode: 'LNZLHT20251106001', projectName: '嘉兴腾4.5T租赁',
        customerName: '嘉兴某某物流有限公司', deliveryTime: '2026-02-01 09:30', returnTime: '2026-02-27 16:20',
        fragileInsurance: '是', tireInsurance: '否', maintenanceInsurance: '是',
      }
    : {
        plateNo: '沪A03561F', contractCode: 'LNZLHT20251012003', projectName: '上海氢能城际物流项目',
        customerName: '上海迅杰物流有限公司', deliveryTime: '2026-04-01 10:00', returnTime: '2026-05-18 15:40',
        fragileInsurance: '是', tireInsurance: '是', maintenanceInsurance: '是',
      };

  const businessServiceRows = buildHcFeeRows(
    HC_CUSTOMER_SERVICE_FIXED,
    {
      'ETC-客户未缴费用': '100.00',
      ETC卡缺损费: '0.00',
      ETC设备缺损费: '0.00',
      租金: '50.00',
    },
    [],
  );
  const billInfo = { receivedRent: '0.00', actualRent: task?.actualRent || '0.00', shouldRefundRent: '0.00' };
  const energy = {
    deliveryHydrogen: '85.00', returnHydrogen: '72.00', hydrogenUnitPrice: '35.00',
    hydrogenSupplement: '455.00', hydrogenFee: '0.00', electricFee: '0.00', prepayRefund: '0.00', userBalance: '1200.00',
  };
  const operationRows = buildHcOperationFeeRows(
    HC_OPERATION_FIXED,
    {
      清洗费: { amount: '0.00', worryFreeDeduction: '0.00' },
      未结算保养: { amount: '372.50', worryFreeDeduction: '0.00' },
      未结算维修: { amount: '0.00', worryFreeDeduction: '0.00' },
      车损费用: { amount: '0.00', worryFreeDeduction: '0.00' },
      工具损坏丢失费用: { amount: '0.00', worryFreeDeduction: '0.00' },
      证件丢失费用: { amount: '0.00', worryFreeDeduction: '0.00' },
      广告损坏丢失费用: { amount: '0.00', worryFreeDeduction: '0.00' },
      送车服务费: { amount: '0.00', worryFreeDeduction: '0.00' },
      接车服务费: { amount: '0.00', worryFreeDeduction: '0.00' },
      轮胎磨损费用: { amount: '0.00', worryFreeDeduction: '0.00' },
    },
    [{ key: 'op-custom-1', feeItem: '补办行驶证', amount: '80.00', worryFreeDeduction: '0.00' }],
  );
  const violations = [
    {
      code: 'WZ202602010001', plateNo: vehicle.plateNo, violationBehavior: '闯红灯',
      violationTime: '2026-02-01', penaltyAmount: '100.00', paymentStatus: '未缴费', handleStatus: '未处理',
    },
    {
      code: 'WZ202602150002', plateNo: vehicle.plateNo, violationBehavior: '违停',
      violationTime: '2026-02-15', penaltyAmount: '200.00', paymentStatus: '已缴费', handleStatus: '已处理',
    },
  ];
  const safetyServiceRows = buildHcFeeRows(
    HC_SAFETY_FIXED,
    {
      违章处理违约金: '100.00',
      保险上浮: '0.00',
      其他违规费用: '0.00',
    },
    [],
  );
  const safetyInfo = calcSafetyInfo(violations);
  const approvalSteps = acIsReturnSettleSubFlow(task?.flowType)
    ? buildReturnSettleSubFlowApprovalSteps(task)
    : (isGuangzhou
      ? [
          { title: '财务汇总审批', department: '财务部', status: '待审批', person: '张明辉', approveTime: '—' },
        ]
      : [
          { title: '业务管理组审批', department: '业务管理组', status: '已通过', person: '陈高伟', approveTime: '2026-05-19 14:00' },
          { title: '能源服务组审批', department: '能源服务组', status: '已通过', person: '李四', approveTime: '2026-05-19 15:00' },
          { title: '运维组审批', department: '运维组', status: '已通过', person: '王五', approveTime: '2026-05-19 16:00' },
          { title: '安全部审批', department: '安全部', status: '已通过', person: '赵六', approveTime: '2026-05-19 16:30' },
          { title: '财务审批', department: '财务部', status: '已通过', person: '李财务', approveTime: '2026-05-21 15:30' },
        ]);

  const businessServiceTotal = sumFeeRows(businessServiceRows);
  const customerServiceTotal = (parseFloat(businessServiceTotal) + parseFloat(billInfo.shouldRefundRent || 0)).toFixed(2);
  const operationTotal = sumOperationFeeRows(operationRows);
  const energySettleTotal = (
    (parseFloat(energy.hydrogenSupplement) || 0) - (parseFloat(energy.prepayRefund) || 0)
  ).toFixed(2);
  const safetyTotal = sumFeeRows(safetyServiceRows);
  const depositAmount = task?.depositAmount || '5000.00';
  const pendingSettle = task?.pendingSettle || (
    parseFloat(customerServiceTotal) + parseFloat(energySettleTotal)
    + parseFloat(operationTotal) + parseFloat(safetyTotal)
  ).toFixed(2);
  const diff = (parseFloat(depositAmount) || 0) - (parseFloat(pendingSettle) || 0);
  const refundTotal = task?.refundTotal || (diff > 0 ? diff.toFixed(2) : '0.00');
  const payTotal = task?.payTotal || (diff < 0 ? Math.abs(diff).toFixed(2) : '0.00');

  const settleBreakdown = [
    { label: '保证金', value: formatMoneySymbol(depositAmount) },
    { label: '业务管理组总计', value: formatYuan(customerServiceTotal) },
    { label: '能源服务组总计', value: formatYuan(energySettleTotal) },
    { label: '运维组总计', value: formatYuan(operationTotal) },
    { label: '安全部总计', value: formatYuan(safetyTotal) },
  ];

  return {
    vehicle, businessServiceRows, billInfo, energy, operationRows, violations, safetyInfo, safetyServiceRows,
    approvalSteps, businessServiceTotal, customerServiceTotal, operationTotal, energySettleTotal, safetyTotal,
    depositAmount, pendingSettle, refundTotal, payTotal, settleBreakdown,
  };
};

const HcInsuranceBadge = ({ label, value }) => {
  const yes = value === '是';
  return (
    <div className="hc-insurance-item">
      <span>{label}</span>
      <span className={`hc-insurance-icon${yes ? ' hc-insurance-icon--yes' : ' hc-insurance-icon--no'}`} aria-label={value || '否'} title={value || '否'}>
        {yes ? '✓' : '×'}
      </span>
    </div>
  );
};

const HcSettleGroupCard = ({ title, statusLabel, statusTone, settleTotal, children }) => (
  <div className="tc-section">
    <div className="tc-section-head">
      <span className="tc-section-title">{title}</span>
      {statusLabel ? (
        <span
          className={`tc-section-badge${statusTone ? ` tc-section-badge--${statusTone}` : ''}`}
          data-annotation-id="hc-settle-group-status"
        >
          {statusLabel}
        </span>
      ) : null}
    </div>
    <div className="hc-settle-body">{children}</div>
    <div className="hc-settle-total">
      <span className="hc-settle-total-label">应结算总额</span>
      <span className="hc-settle-total-val">{formatYuan(settleTotal)}</span>
    </div>
  </div>
);

const ReturnSettlementApprovePage = ({ task, mode, onBack }) => {
  const detail = useMemo(() => buildReturnSettlementDetail(task), [task]);
  const {
    vehicle, businessServiceRows, billInfo, energy, operationRows, safetyInfo, safetyServiceRows, approvalSteps,
    businessServiceTotal, customerServiceTotal, operationTotal, energySettleTotal, safetyTotal,
    depositAmount, pendingSettle, refundTotal, payTotal, settleBreakdown,
  } = detail;

  const deptScope = acReturnSettleDeptScope(task?.flowType);
  const showHeroMoney = acReturnSettleShowsHeroMoney(task?.flowType);
  const deptHeroTitle = task?.flowType?.startsWith('还车应结款-')
    ? task.flowType
    : '还车应结款';

  const [settleDrawerOpen, setSettleDrawerOpen] = useState(false);
  const [decisionOpen, setDecisionOpen] = useState(false);
  const [decisionType, setDecisionType] = useState('approve');
  const [commentOpen, setCommentOpen] = useState(false);
  const showActions = mode === 'approve' || mode === 'view';
  const displayPending = task?.pendingSettle || pendingSettle;
  const isDepositCoverPending = parseFloat(depositAmount) >= parseFloat(displayPending);
  const heroSettleLabel = isDepositCoverPending ? '应退还总额' : '应补缴总额';
  const heroSettleAmount = isDepositCoverPending ? refundTotal : payTotal;

  const subFlowStatuses = useMemo(
    () => acReturnSettleSubFlowStatuses(vehicle.plateNo, task),
    [vehicle.plateNo, task],
  );
  const allSubFlowsPassed = useMemo(
    () => acReturnSettleAllSubFlowsPassed(vehicle.plateNo, task),
    [vehicle.plateNo, task],
  );
  const settleBadges = useMemo(() => {
    if (task?.flowType === '还车应结款') {
      const submitted = { label: '已提交', tone: 'green' };
      return { bizMgmt: submitted, energy: submitted, ops: submitted, safety: submitted };
    }
    const statusByFlow = Object.fromEntries(subFlowStatuses.map((item) => [item.flowType, item.status]));
    const resolve = (flowType) => resolveHcSettleGroupBadge(statusByFlow[flowType] || '未发起');
    return {
      bizMgmt: resolve('还车应结款-业务管理组'),
      energy: resolve('还车应结款-能源服务组'),
      ops: resolve('还车应结款-运维组'),
      safety: resolve('还车应结款-安全部'),
    };
  }, [subFlowStatuses, task?.flowType]);
  const isMainReturnSettle = task?.flowType === '还车应结款';
  const mainApproveBlocked = isMainReturnSettle && mode === 'approve' && !allSubFlowsPassed;

  const openDecision = (type) => {
    if (type === 'approve' && mainApproveBlocked) {
      mpToast.warning('四个部门子流程尚未全部审批通过，主流程暂不可提交（原型）');
      return;
    }
    setDecisionType(type);
    setDecisionOpen(true);
  };
  const handleDecisionConfirm = (payload) => {
    if (payload.actionType === 'approve' && mainApproveBlocked) {
      mpToast.warning('四个部门子流程尚未全部审批通过，主流程暂不可提交（原型）');
      return;
    }
    const meta = APPROVAL_DECISION_META[payload.actionType];
    if (payload.actionType === 'reject' || payload.actionType === 'terminate') mpToast.warning(meta?.success || '操作成功（原型）');
    else mpToast.success(meta?.success || '操作成功（原型）');
    if (mode === 'approve') onBack();
  };

  return (
    <>
      <div className="tc-scroll">
        <div className="tc-hero tc-hero--return" data-annotation-id="page-audit-return">
          {showHeroMoney ? (
            <>
              <div className="tc-hero-label">{heroSettleLabel}</div>
              <div className="tc-hero-amount">{formatMoneySymbol(heroSettleAmount)}</div>
            </>
          ) : (
            <>
              <div className="tc-hero-label">{deptHeroTitle}</div>
              <div className="tc-hero-plate-key-wrap">
                <span className="tc-hero-plate-key-label">车牌号</span>
                <span className="tc-hero-plate-key">{vehicle.plateNo || '—'}</span>
              </div>
            </>
          )}
          <div className="tc-hero-meta">
            <span>{vehicle.customerName}</span>
          </div>
          <div className="tc-hero-foot tc-hero-foot--compact">
            {showHeroMoney ? (
              <div className="tc-hero-foot-row"><span className="tc-hero-foot-label">车牌号</span>{vehicle.plateNo || '—'}</div>
            ) : null}
            <div className="tc-hero-foot-row"><span className="tc-hero-foot-label">合同编码</span>{vehicle.contractCode || '—'}</div>
            <div className="tc-hero-foot-row"><span className="tc-hero-foot-label">项目名称</span>{vehicle.projectName || '—'}</div>
            <div className="hc-insurance-row">
              <HcInsuranceBadge label="易损保" value={vehicle.fragileInsurance} />
              <HcInsuranceBadge label="轮胎保" value={vehicle.tireInsurance} />
              <HcInsuranceBadge label="养护保" value={vehicle.maintenanceInsurance} />
            </div>
          </div>
          <div className="tc-hero-period">
            <span>交车：{vehicle.deliveryTime}</span>
            <span>还车：{vehicle.returnTime}</span>
          </div>
          {showHeroMoney ? (
            <div className="tc-hero-sub">
              <div className="tc-hero-compare">
                保证金 {formatMoneySymbol(depositAmount)}
                <br />
                待结算总额 {formatMoneySymbol(displayPending)}
                <br />
                车辆实际租金 {formatYuan(billInfo.actualRent)}
              </div>
              <button type="button" className="tc-hero-detail-btn" onClick={() => setSettleDrawerOpen(true)}>结算明细</button>
            </div>
          ) : null}
        </div>

        {(deptScope === 'all' || deptScope === 'bizMgmt') && (
        <HcSettleGroupCard title="业务管理组" statusLabel={settleBadges.bizMgmt.label} statusTone={settleBadges.bizMgmt.tone} settleTotal={customerServiceTotal}>
          <HcFeeItemTable rows={businessServiceRows} />
        </HcSettleGroupCard>
        )}

        {(deptScope === 'all' || deptScope === 'energy') && (
        <HcSettleGroupCard title="能源服务组" statusLabel={settleBadges.energy.label} statusTone={settleBadges.energy.tone} settleTotal={energySettleTotal}>
          <div className="tc-kv-grid" style={{ padding: '0 0 8px' }}>
            <TcInfoRow label="氢量差补缴" value={formatYuan(energy.hydrogenSupplement)} />
            <TcInfoRow label="交车氢量" value={`${energy.deliveryHydrogen} MPa`} />
            <TcInfoRow label="还车氢量" value={`${energy.returnHydrogen} MPa`} />
            <TcInfoRow label="退还氢气单价" value={formatYuan(energy.hydrogenUnitPrice)} />
            <TcInfoRow label="预付款退费" value={formatYuan(energy.prepayRefund)} />
          </div>
        </HcSettleGroupCard>
        )}

        {(deptScope === 'all' || deptScope === 'ops') && (
        <HcSettleGroupCard title="运维组" statusLabel={settleBadges.ops.label} statusTone={settleBadges.ops.tone} settleTotal={operationTotal}>
          <HcOperationFeeTable rows={operationRows} />
        </HcSettleGroupCard>
        )}

        {(deptScope === 'all' || deptScope === 'safety') && (
        <HcSettleGroupCard title="安全部" statusLabel={settleBadges.safety.label} statusTone={settleBadges.safety.tone} settleTotal={safetyTotal}>
          <HcFeeItemTable rows={safetyServiceRows} />
          <div className="hc-safety-stat-grid" style={{ marginTop: 12 }}>
            <div className="hc-safety-stat-item">
              <div className="hc-safety-stat-label">违章次数</div>
              <div className="hc-safety-stat-val">{safetyInfo.violationCount}</div>
            </div>
            <div className="hc-safety-stat-item">
              <div className="hc-safety-stat-label">已缴金额</div>
              <div className="hc-safety-stat-val">{formatYuan(safetyInfo.paidAmount)}</div>
            </div>
            <div className="hc-safety-stat-item">
              <div className="hc-safety-stat-label">未缴金额</div>
              <div className="hc-safety-stat-val warn">{formatYuan(safetyInfo.unpaidAmount)}</div>
            </div>
          </div>
        </HcSettleGroupCard>
        )}

        <ApprovalTimeline steps={approvalSteps} />
        <div style={{ height: showActions ? 16 : 24 }} />
      </div>

      {showActions && !decisionOpen && !commentOpen && (
        <ApprovalActionBar mode={mode} onComment={() => setCommentOpen(true)} onTerminate={() => openDecision('terminate')} onReject={() => openDecision('reject')} onApprove={() => openDecision('approve')} />
      )}
      <ApprovalDecisionDrawer open={decisionOpen} actionType={decisionType} onClose={() => setDecisionOpen(false)} onConfirm={handleDecisionConfirm} />
      <ApprovalCommentDrawer open={commentOpen} onClose={() => setCommentOpen(false)} onConfirm={() => mpToast.success('评论已添加（原型）')} />

      <TcMiniBottomSheet
        open={settleDrawerOpen && showHeroMoney}
        title="结算明细"
        onClose={() => setSettleDrawerOpen(false)}
        rows={settleBreakdown}
        totalLabel={heroSettleLabel}
        totalValue={formatMoneySymbol(heroSettleAmount)}
        totalTheme="purple"
      />
    </>
  );
};

const WEB_BILL_VEHICLES = [
  { key: 'v1', index: 1, brand: '东风', model: 'DFH1180', plateNo: '浙A12345', receivableRent: 30000, actualRent: '29800.00', rentRemark: '首期六期一次性付清', discountAmount: '200.00', discountRemark: '首月优惠', discountProof: [{ name: '优惠审批单.pdf' }], receivableDeposit: 10000, serviceItems: [{ name: '代处理费用', receivable: 200, actual: '200.00', discount: '0.00', remark: '' }, { name: '保险上浮', receivable: 500, actual: '480.00', discount: '20.00', remark: '客户协商' }], receivableService: 700, actualService: '680.00' },
  { key: 'v2', index: 2, brand: '福田', model: 'BJ1180', plateNo: '浙A23456', receivableRent: 27000, actualRent: '27000.00', rentRemark: '', discountAmount: '0.00', discountRemark: '', discountProof: [], receivableDeposit: 8000, serviceItems: [{ name: '保养费用', receivable: 300, actual: '300.00', discount: '0.00', remark: '含首保' }], receivableService: 300, actualService: '300.00' },
  { key: 'v3', index: 3, brand: '重汽', model: 'ZZ1187', plateNo: '浙A34567', receivableRent: 31200, actualRent: '31200.00', rentRemark: '', discountAmount: '0.00', discountRemark: '', discountProof: [], receivableDeposit: 10000, serviceItems: [{ name: '代处理费用', receivable: 180, actual: '180.00', discount: '0.00', remark: '' }, { name: '上牌服务', receivable: 400, actual: '400.00', discount: '0.00', remark: '' }], receivableService: 580, actualService: '580.00' },
];

const buildLeaseBillDetail = (task) => {
  const isJuneBill = task?.bizNo === 'ZD-2026-06-001';
  const billInfo = isJuneBill
    ? {
        contractCode: 'HT-ZL-2025-001', contractType: '正式合同', projectName: '嘉兴氢能示范项目',
        customerName: '嘉兴某某物流有限公司', deliveryTaskCode: 'JT-2025-001-A',
        billCode: 'HT-ZL-2025-0010006', period: 6, billStartDate: '2026-06-01', billEndDate: '2026-06-30',
      }
    : {
        contractCode: 'HT-ZL-2025-001', contractType: '正式合同', projectName: '嘉兴氢能示范项目',
        customerName: '嘉兴某某物流有限公司', deliveryTaskCode: 'JT-2025-001-A',
        billCode: 'HT-ZL-2025-0010001', period: 1, billStartDate: '2025-01-01', billEndDate: '2025-01-31',
      };
  const vehicles = isJuneBill
    ? [
        { key: 'v1', index: 1, brand: '东风', model: 'DFH1180', plateNo: '粤B58888F', receivableRent: 30000, actualRent: '29800.00', rentRemark: '', discountAmount: '200.00', discountRemark: '批量优惠', discountProof: [], receivableDeposit: 0, serviceItems: [{ name: '代处理费用', receivable: 200, actual: '200.00', discount: '0.00', remark: '' }], receivableService: 200, actualService: '200.00' },
        { key: 'v2', index: 2, brand: '福田', model: 'BJ1180', plateNo: '粤B59999F', receivableRent: 28500, actualRent: '28500.00', rentRemark: '', discountAmount: '0.00', discountRemark: '', discountProof: [], receivableDeposit: 0, serviceItems: [{ name: '保养费用', receivable: 300, actual: '300.00', discount: '0.00', remark: '' }], receivableService: 300, actualService: '300.00' },
        { key: 'v3', index: 3, brand: '重汽', model: 'ZZ1187', plateNo: '粤B60001F', receivableRent: 29200, actualRent: '29000.00', rentRemark: '按合同约定', discountAmount: '200.00', discountRemark: '协商减免', discountProof: [{ name: '减免说明.pdf' }], receivableDeposit: 0, serviceItems: [{ name: '保险上浮', receivable: 500, actual: '480.00', discount: '20.00', remark: '' }], receivableService: 500, actualService: '480.00' },
        { key: 'v4', index: 4, brand: '东风', model: 'DFH1180', plateNo: '粤B61112F', receivableRent: 27800, actualRent: '27800.00', rentRemark: '', discountAmount: '0.00', discountRemark: '', discountProof: [], receivableDeposit: 0, serviceItems: [], receivableService: 0, actualService: '0.00' },
        { key: 'v5', index: 5, brand: '福田', model: 'BJ1180', plateNo: '粤B62223F', receivableRent: 26880, actualRent: '26880.00', rentRemark: '', discountAmount: '0.00', discountRemark: '', discountProof: [], receivableDeposit: 0, serviceItems: [{ name: '代处理费用', receivable: 180, actual: '180.00', discount: '0.00', remark: '' }], receivableService: 180, actualService: '180.00' },
      ]
    : WEB_BILL_VEHICLES;
  const isFirstPeriod = billInfo.period === 1;
  const hydrogen = { receivable: '3580.00', actual: '3500.00', discount: '80.00', discountRemark: '预付款批量减免' };
  const approvalSteps = isJuneBill
    ? [
        { title: '业务经理审批', department: '业务管理组', status: '已通过', person: '陈高伟', approveTime: '2026-06-01 09:00' },
        { title: '业管主管审批', department: '业管主管', status: '待审批', person: '张明辉', approveTime: '—' },
      ]
    : [
        { title: '业务经理审批', department: '业务管理组', status: '已通过', person: '陈高伟', approveTime: '2026-05-05 09:20' },
        { title: '业管主管审批', department: '业管主管', status: '已通过', person: '张明辉', approveTime: '2026-05-05 14:30', comment: '本期减免已与财务确认。' },
        { title: '财务审批', department: '财务部', status: '已通过', person: '赵敏', approveTime: '2026-05-05 18:00' },
      ];
  return { billInfo, vehicles, isFirstPeriod, hydrogen, approvalSteps };
};

const calcLeaseBillTotals = (vehicles, hydrogen, isFirstPeriod) => {
  let receivableRent = 0; let actualRent = 0; let receivableDeposit = 0;
  let receivableService = 0; let actualService = 0; let discountTotal = 0; let serviceDiscountTotal = 0;
  vehicles.forEach((v) => {
    receivableRent += Number(v.receivableRent) || 0;
    actualRent += parseFloat(v.actualRent) || 0;
    receivableDeposit += isFirstPeriod ? (Number(v.receivableDeposit) || 0) : 0;
    receivableService += Number(v.receivableService) || 0;
    actualService += parseFloat(v.actualService) || 0;
    discountTotal += parseFloat(v.discountAmount) || 0;
    (v.serviceItems || []).forEach((s) => { serviceDiscountTotal += parseFloat(s.discount) || 0; });
  });
  const hydrogenReceivable = isFirstPeriod ? (parseFloat(hydrogen.receivable) || 0) : 0;
  const hydrogenActual = isFirstPeriod ? (parseFloat(hydrogen.actual) || 0) : 0;
  const hydrogenDiscount = isFirstPeriod ? (parseFloat(hydrogen.discount) || 0) : 0;
  const receivableTotal = (receivableRent + receivableDeposit + receivableService + hydrogenReceivable).toFixed(2);
  const actualTotal = (actualRent + receivableDeposit + actualService - discountTotal - serviceDiscountTotal + hydrogenActual - hydrogenDiscount).toFixed(2);
  const invoiceTotal = (actualRent + actualService - discountTotal - serviceDiscountTotal + hydrogenActual - hydrogenDiscount).toFixed(2);
  return {
    receivableRent: receivableRent.toFixed(2), actualRent: actualRent.toFixed(2),
    receivableDeposit: receivableDeposit.toFixed(2), receivableService: receivableService.toFixed(2),
    actualService: actualService.toFixed(2), discountTotal: discountTotal.toFixed(2),
    serviceDiscountTotal: serviceDiscountTotal.toFixed(2),
    hydrogenReceivable: hydrogenReceivable.toFixed(2), hydrogenActual: hydrogenActual.toFixed(2),
    hydrogenDiscount: hydrogenDiscount.toFixed(2),
    receivableTotal, actualTotal, invoiceTotal,
  };
};

const ZlVehicleCard = ({ vehicle, isFirstPeriod }) => {
  const [serviceOpen, setServiceOpen] = useState(false);
  const serviceDiscount = (vehicle.serviceItems || []).reduce((s, item) => s + (parseFloat(item.discount) || 0), 0);
  const deposit = isFirstPeriod ? (Number(vehicle.receivableDeposit) || 0) : 0;
  const vehicleActual = (
    parseFloat(vehicle.actualRent || 0) + deposit + parseFloat(vehicle.actualService || 0)
    - parseFloat(vehicle.discountAmount || 0) - serviceDiscount
  ).toFixed(2);
  return (
    <div className="tc-vehicle-card">
      <div className="tc-vehicle-head">
        <div>
          <div className="tc-vehicle-plate">{vehicle.plateNo || '—'}</div>
          <div className="tc-vehicle-model">{vehicle.brand} · {vehicle.model}</div>
        </div>
        <span className="tc-vehicle-idx tc-vehicle-idx--blue">#{vehicle.index}</span>
      </div>
      <div className="tc-vehicle-amount-row">
        <span className="tc-vehicle-amount-label">本车实收合计</span>
        <span className="tc-vehicle-amount-val tc-vehicle-amount-val--blue">{formatMoneySymbol(vehicleActual)}</span>
      </div>
      <div className="tc-vehicle-kv">
        <div className="tc-vehicle-kv-item"><span>应收月租金 </span>{formatYuan(vehicle.receivableRent)}</div>
        <div className="tc-vehicle-kv-item"><span>实收月租金 </span>{formatYuan(vehicle.actualRent)}</div>
        <div className="tc-vehicle-kv-item"><span>应收保证金 </span>{formatYuan(isFirstPeriod ? vehicle.receivableDeposit : 0)}</div>
        <div className="tc-vehicle-kv-item"><span>减免金额 </span>{formatYuan(vehicle.discountAmount)}</div>
        <div className="tc-vehicle-kv-item"><span>应收服务费 </span>{formatYuan(vehicle.receivableService)}</div>
        <div className="tc-vehicle-kv-item"><span>实收服务费 </span>{formatYuan(vehicle.actualService)}</div>
      </div>
      {vehicle.rentRemark ? (
        <div className="tc-vehicle-kv-item" style={{ marginTop: 6, fontSize: 12, color: COLOR_TEXT_SEC }}>
          <span style={{ color: COLOR_MUTED }}>租金备注 </span>{vehicle.rentRemark}
        </div>
      ) : null}
      {vehicle.discountRemark ? (
        <div className="tc-vehicle-kv-item" style={{ marginTop: 4, fontSize: 12, color: COLOR_TEXT_SEC }}>
          <span style={{ color: COLOR_MUTED }}>减免备注 </span>{vehicle.discountRemark}
        </div>
      ) : null}
      {(vehicle.discountProof || []).length > 0 && (
        <div style={{ marginTop: 6 }}>
          <div style={{ fontSize: 11, color: COLOR_MUTED, marginBottom: 4 }}>减免证明</div>
          <div className="zl-proof-list">
            {(vehicle.discountProof || []).map((p, i) => (
              <button key={i} type="button" className="zl-proof-link" onClick={() => mpToast.info(`预览：${p.name || '附件'}`)}>
                {p.name || '附件'}
              </button>
            ))}
          </div>
        </div>
      )}
      {(vehicle.serviceItems || []).length > 0 && (
        <>
          <button type="button" className="tc-service-toggle" onClick={() => setServiceOpen((o) => !o)}>
            {serviceOpen ? '收起' : '查看'}服务费明细
          </button>
          {serviceOpen && (
            <div className="tc-service-list">
              {(vehicle.serviceItems || []).map((s, i) => (
                <div className="tc-service-row" key={i}>
                  <span className="tc-service-name">{s.name}</span>
                  <span className="tc-service-amt tc-service-amt--blue">{formatMoneySymbol(s.actual)}</span>
                  <span className="tc-service-sub">应收 {formatYuan(s.receivable)} · 减免 {formatYuan(s.discount)}{s.remark ? ` · ${s.remark}` : ''}</span>
                </div>
              ))}
            </div>
          )}
        </>
      )}
    </div>
  );
};

const DEFAULT_TRANSFER_APPROVAL_STEPS = [
  { title: '运维主管审批', department: '运维主管', status: '已通过', person: '陈高伟', approveTime: '2026-06-01 08:30' },
  { title: '运维中心审批', department: '运维中心', status: '审批中', person: '张明辉', approveTime: '—' },
];

const parseTransferInfo = (task) => {
  const info = task?.transferInfo || {};
  return {
    method: info.method || '—',
    transportLeader: info.transportLeader || '—',
    transportPhone: info.transportPhone || '—',
    receivePerson: info.receivePerson || '—',
    transportCost: info.transportCost || '',
    contractFiles: info.contractFiles || [],
  };
};

const buildTransferCreateDetail = (task) => ({
  transferDate: task?.transferDate || '2026-06-01 09:30',
  departRegion: task?.departRegion || '广东省-广州市',
  receiveRegion: task?.receiveRegion || '浙江省-杭州市',
  reason: task?.reason || '—',
  transferInfo: parseTransferInfo(task),
  vehicles: (task?.vehicles || []).map((v, i) => ({ ...v, index: i + 1 })),
  approvalSteps: task?.approvalSteps || DEFAULT_TRANSFER_APPROVAL_STEPS,
});

const buildTransferOpsDetail = (task) => ({
    transferDate: task?.transferDate || '2026-03-31 08:00',
    departRegion: task?.departRegion || '广东省-广州市',
    receiveRegion: task?.receiveRegion || '浙江省-嘉兴市',
    reason: task?.reason || '—',
    transferInfo: parseTransferInfo(task),
    vehicles: (task?.vehicles || []).map((v, i) => ({ ...v, index: i + 1 })),
    approvalSteps: task?.approvalSteps || [
      { title: '运维主管审批', department: '运维主管', status: '已通过', person: '陈高伟', approveTime: '2026-03-31 08:00' },
      { title: '运维中心审批', department: '运维中心', status: '审批中', person: '张明辉', approveTime: '—' },
    ],
});

const TransferInfoSection = ({ transferInfo }) => (
  <div className="tc-section">
    <div className="tc-section-head"><span className="tc-section-title">调拨信息</span></div>
    <div className="tc-kv-grid">
      <TcInfoRow label="调拨方式" value={transferInfo.method} />
      <TcInfoRow label="运输负责人" value={transferInfo.transportLeader} />
      <TcInfoRow label="运输方联系方式" value={transferInfo.transportPhone} full />
      <TcInfoRow label="接收人员" value={transferInfo.receivePerson} full />
      <TcInfoRow label="运输费用" value={transferInfo.transportCost ? formatMoneySymbol(transferInfo.transportCost) : '—'} />
      {(transferInfo.contractFiles || []).length > 0 ? (
        <div className="tc-kv-item tc-kv-value--full">
          <div className="tc-kv-label">运输合同附件</div>
          <div className="zl-proof-list">
            {(transferInfo.contractFiles || []).map((f, i) => (
              <button key={i} type="button" className="zl-proof-link" onClick={() => mpToast.info(`预览：${f.name || '附件'}`)}>{f.name || '附件'}</button>
            ))}
          </div>
        </div>
      ) : null}
    </div>
  </div>
);

const DbVehicleCard = ({ vehicle }) => (
  <div className="tc-vehicle-card">
    <div className="tc-vehicle-head">
      <div>
        <div className="tc-vehicle-plate">{vehicle.plateNo || '—'}</div>
        <div className="tc-vehicle-model">{vehicle.brand} · {vehicle.model}</div>
      </div>
      <span className="tc-vehicle-idx tc-vehicle-idx--green">#{vehicle.index}</span>
    </div>
    <div className="tc-vehicle-kv">
      <div className="tc-vehicle-kv-item"><span>出发停车场 </span>{vehicle.departParking || '—'}</div>
      <div className="tc-vehicle-kv-item"><span>出发里程 </span>{vehicle.departMileageKm ? `${vehicle.departMileageKm} km` : '—'}</div>
      <div className="tc-vehicle-kv-item"><span>出发氢量 </span>{vehicle.departHydrogen ? `${vehicle.departHydrogen} ${vehicle.h2Unit || '%'}` : '—'}</div>
      <div className="tc-vehicle-kv-item"><span>出发电量 </span>{vehicle.departElectricKwh ? `${vehicle.departElectricKwh} kWh` : '—'}</div>
    </div>
  </div>
);

const TransferCreateApprovePage = ({ task, mode, onBack }) => {
  const detail = useMemo(() => buildTransferCreateDetail(task), [task]);
  const { transferDate, departRegion, receiveRegion, reason, transferInfo, vehicles, approvalSteps } = detail;
  const [decisionOpen, setDecisionOpen] = useState(false);
  const [decisionType, setDecisionType] = useState('approve');
  const [commentOpen, setCommentOpen] = useState(false);
  const showActions = mode === 'approve' || mode === 'view';

  const openDecision = (type) => { setDecisionType(type); setDecisionOpen(true); };
  const handleDecisionConfirm = (payload) => {
    const meta = APPROVAL_DECISION_META[payload.actionType];
    if (payload.actionType === 'reject' || payload.actionType === 'terminate') mpToast.warning(meta?.success || '操作成功（原型）');
    else mpToast.success(meta?.success || '操作成功（原型）');
    if (mode === 'approve') onBack();
  };

  return (
    <>
      <div className="tc-scroll">
        <div className="tc-hero tc-hero--transfer">
          <div className="tc-hero-label">调拨车辆</div>
          <div className="tc-hero-amount">{vehicles.length} 台</div>
          <div className="tc-hero-route">
            <span>{departRegion}</span>
            <span className="tc-hero-route-arrow">→</span>
            <span>{receiveRegion}</span>
          </div>
          <div className="tc-hero-foot tc-hero-foot--compact">
            <div className="tc-hero-foot-row"><span className="tc-hero-foot-label">调拨日期</span>{transferDate}</div>
          </div>
          <div className="tc-hero-period">
            <span className="tc-hero-period-tag">调拨申请</span>
          </div>
        </div>

        <div className="tc-section">
          <div className="tc-section-head"><span className="tc-section-title">调拨情况</span></div>
          <div className="tc-kv-grid">
            <TcInfoRow label="调拨日期" value={transferDate} full />
            <TcInfoRow label="出发区域" value={departRegion} />
            <TcInfoRow label="接收区域" value={receiveRegion} />
            <TcInfoRow label="调拨原因" value={reason} full />
          </div>
        </div>

        <TransferInfoSection transferInfo={transferInfo} />

        <div className="tc-section">
          <div className="tc-section-head">
            <span className="tc-section-title">调拨车辆清单</span>
            <span className="tc-section-badge tc-section-badge--green">{vehicles.length} 台</span>
          </div>
          {vehicles.map((v) => <DbVehicleCard key={v.id || v.index} vehicle={v} />)}
        </div>

        <ApprovalTimeline steps={approvalSteps} />
        <div style={{ height: showActions ? 16 : 24 }} />
      </div>

      {showActions && !decisionOpen && !commentOpen && (
        <ApprovalActionBar mode={mode} onComment={() => setCommentOpen(true)} onTerminate={() => openDecision('terminate')} onReject={() => openDecision('reject')} onApprove={() => openDecision('approve')} />
      )}
      <ApprovalDecisionDrawer open={decisionOpen} actionType={decisionType} onClose={() => setDecisionOpen(false)} onConfirm={handleDecisionConfirm} />
      <ApprovalCommentDrawer open={commentOpen} onClose={() => setCommentOpen(false)} onConfirm={() => mpToast.success('评论已添加（原型）')} />
    </>
  );
};

const TransferOpsApprovePage = ({ task, mode, onBack }) => {
  const detail = useMemo(() => buildTransferOpsDetail(task), [task]);
  const { transferDate, departRegion, receiveRegion, reason, transferInfo, vehicles, approvalSteps } = detail;
  const [decisionOpen, setDecisionOpen] = useState(false);
  const [decisionType, setDecisionType] = useState('approve');
  const [commentOpen, setCommentOpen] = useState(false);
  const showActions = mode === 'approve' || mode === 'view';

  const openDecision = (type) => { setDecisionType(type); setDecisionOpen(true); };
  const handleDecisionConfirm = (payload) => {
    const meta = APPROVAL_DECISION_META[payload.actionType];
    if (payload.actionType === 'reject' || payload.actionType === 'terminate') mpToast.warning(meta?.success || '操作成功（原型）');
    else mpToast.success(meta?.success || '操作成功（原型）');
    if (mode === 'approve') onBack();
  };

  return (
    <>
      <div className="tc-scroll">
        <div className="tc-hero tc-hero--transfer">
          <div className="tc-hero-label">调拨车辆</div>
          <div className="tc-hero-amount">{vehicles.length} 台</div>
          <div className="tc-hero-route">
            <span>{departRegion}</span>
            <span className="tc-hero-route-arrow">→</span>
            <span>{receiveRegion}</span>
          </div>
          <div className="tc-hero-foot tc-hero-foot--compact">
            <div className="tc-hero-foot-row"><span className="tc-hero-foot-label">调拨日期</span>{transferDate}</div>
          </div>
          <div className="tc-hero-period">
            <span className="tc-hero-period-tag">运维调拨方</span>
          </div>
        </div>

        <div className="tc-section">
          <div className="tc-section-head"><span className="tc-section-title">调拨情况</span></div>
          <div className="tc-kv-grid">
            <TcInfoRow label="调拨日期" value={transferDate} full />
            <TcInfoRow label="出发区域" value={departRegion} />
            <TcInfoRow label="接收区域" value={receiveRegion} />
            <TcInfoRow label="调拨原因" value={reason} full />
          </div>
        </div>

        <TransferInfoSection transferInfo={transferInfo} />

        <div className="tc-section">
          <div className="tc-section-head">
            <span className="tc-section-title">调拨车辆清单</span>
            <span className="tc-section-badge tc-section-badge--green">{vehicles.length} 台</span>
          </div>
          {vehicles.map((v) => <DbVehicleCard key={v.id || v.index} vehicle={v} />)}
        </div>

        <ApprovalTimeline steps={approvalSteps} />
        <div style={{ height: showActions ? 16 : 24 }} />
      </div>

      {showActions && !decisionOpen && !commentOpen && (
        <ApprovalActionBar mode={mode} onComment={() => setCommentOpen(true)} onTerminate={() => openDecision('terminate')} onReject={() => openDecision('reject')} onApprove={() => openDecision('approve')} />
      )}
      <ApprovalDecisionDrawer open={decisionOpen} actionType={decisionType} onClose={() => setDecisionOpen(false)} onConfirm={handleDecisionConfirm} />
      <ApprovalCommentDrawer open={commentOpen} onClose={() => setCommentOpen(false)} onConfirm={() => mpToast.success('评论已添加（原型）')} />
    </>
  );
};

const LeaseBillApprovePage = ({ task, mode, onBack }) => {
  const detail = useMemo(() => buildLeaseBillDetail(task), [task]);
  const { billInfo, vehicles, isFirstPeriod, hydrogen, approvalSteps } = detail;
  const totals = useMemo(() => calcLeaseBillTotals(vehicles, hydrogen, isFirstPeriod), [vehicles, hydrogen, isFirstPeriod]);
  const [receivableDrawerOpen, setReceivableDrawerOpen] = useState(false);
  const [actualDrawerOpen, setActualDrawerOpen] = useState(false);
  const displayActualTotal = task?.actualAmount || totals.actualTotal;

  const receivableRows = [
    { label: '总计应收车辆月租金', value: formatYuan(totals.receivableRent) },
    { label: '总计应收车辆保证金', value: formatYuan(totals.receivableDeposit) },
    { label: '总计应收服务费', value: formatYuan(totals.receivableService) },
  ];
  if (isFirstPeriod) receivableRows.push({ label: '氢费预付款应收金额', value: formatYuan(totals.hydrogenReceivable), highlight: true });

  const actualRows = [
    { label: '总计实收车辆月租金', value: formatYuan(totals.actualRent) },
    { label: '总计应收车辆保证金', value: formatYuan(totals.receivableDeposit) },
    { label: '总计实收服务费', value: formatYuan(totals.actualService) },
    { label: '总计减免金额', value: formatYuan(totals.discountTotal) },
    { label: '服务费各项减免费用总和', value: formatYuan(totals.serviceDiscountTotal) },
  ];
  if (isFirstPeriod) {
    actualRows.push({ label: '氢费预付款实收金额', value: formatYuan(totals.hydrogenActual), highlight: true });
    actualRows.push({ label: '氢费预付款减免金额', value: formatYuan(totals.hydrogenDiscount) });
  }

  const [decisionOpen, setDecisionOpen] = useState(false);
  const [decisionType, setDecisionType] = useState('approve');
  const [commentOpen, setCommentOpen] = useState(false);
  const showActions = mode === 'approve' || mode === 'view';

  const openDecision = (type) => { setDecisionType(type); setDecisionOpen(true); };
  const handleDecisionConfirm = (payload) => {
    const meta = APPROVAL_DECISION_META[payload.actionType];
    if (payload.actionType === 'reject' || payload.actionType === 'terminate') mpToast.warning(meta?.success || '操作成功（原型）');
    else mpToast.success(meta?.success || '操作成功（原型）');
    if (mode === 'approve') onBack();
  };

  return (
    <>
      <div className="tc-scroll">
        <div className="tc-hero tc-hero--bill">
          <div className="tc-hero-label">实收款总额</div>
          <div className="tc-hero-amount">{formatMoneySymbol(displayActualTotal)}</div>
          <div className="tc-hero-meta">
            <span>{billInfo.customerName}</span>
          </div>
          <div className="tc-hero-foot tc-hero-foot--compact">
            <div className="tc-hero-foot-row"><span className="tc-hero-foot-label">合同编码</span>{billInfo.contractCode || '—'}</div>
            <div className="tc-hero-foot-row"><span className="tc-hero-foot-label">项目名称</span>{billInfo.projectName || '—'}</div>
          </div>
          <div className="tc-hero-period">
            <span>账单周期：{billInfo.billStartDate}至{billInfo.billEndDate}</span>
            {billInfo.period != null && <span className="tc-hero-period-tag">第{billInfo.period}期</span>}
          </div>
          <div className="tc-hero-sub">
            <div className="tc-hero-compare">
              应收款总额 {formatMoneySymbol(totals.receivableTotal)}
              <br />
              开票总额 {formatMoneySymbol(totals.invoiceTotal)}
            </div>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 6, flexShrink: 0 }}>
              <button type="button" className="tc-hero-detail-btn" onClick={() => setReceivableDrawerOpen(true)}>应收明细</button>
              <button type="button" className="tc-hero-detail-btn" onClick={() => setActualDrawerOpen(true)}>实收明细</button>
            </div>
          </div>
        </div>

        <div className="tc-section">
          <div className="tc-section-head">
            <span className="tc-section-title">账单明细 · 车辆列表</span>
            <span className="tc-section-badge tc-section-badge--blue">{vehicles.length} 台</span>
          </div>
          {vehicles.map((v) => <ZlVehicleCard key={v.key} vehicle={v} isFirstPeriod={isFirstPeriod} />)}
        </div>

        {isFirstPeriod && (
          <div className="tc-section">
            <div className="tc-section-head"><span className="tc-section-title">氢费预付款情况</span></div>
            <div className="tc-kv-grid">
              <TcInfoRow label="氢费预付款应收金额" value={formatYuan(hydrogen.receivable)} />
              <TcInfoRow label="氢费预付款实收金额" value={formatYuan(hydrogen.actual)} />
              <TcInfoRow label="减免金额" value={formatYuan(hydrogen.discount)} />
              <TcInfoRow label="减免金额备注" value={hydrogen.discountRemark} full />
            </div>
          </div>
        )}

        <ApprovalTimeline steps={approvalSteps} />
        <div style={{ height: showActions ? 16 : 24 }} />
      </div>

      {showActions && !decisionOpen && !commentOpen && (
        <ApprovalActionBar
          mode={mode}
          onComment={() => setCommentOpen(true)}
          onTerminate={() => openDecision('terminate')}
          onReject={() => openDecision('reject')}
          onApprove={() => openDecision('approve')}
        />
      )}
      <ApprovalDecisionDrawer open={decisionOpen} actionType={decisionType} onClose={() => setDecisionOpen(false)} onConfirm={handleDecisionConfirm} />
      <ApprovalCommentDrawer open={commentOpen} onClose={() => setCommentOpen(false)} onConfirm={() => mpToast.success('评论已添加（原型）')} />

      <TcMiniBottomSheet
        open={receivableDrawerOpen}
        title="应收款明细"
        onClose={() => setReceivableDrawerOpen(false)}
        rows={receivableRows}
        totalLabel="应收款总额"
        totalValue={formatMoneySymbol(totals.receivableTotal)}
        totalTheme="blue"
      />
      <TcMiniBottomSheet
        open={actualDrawerOpen}
        title="实收款明细"
        onClose={() => setActualDrawerOpen(false)}
        rows={actualRows}
        totalLabel="实收款总额"
        totalValue={formatMoneySymbol(displayActualTotal)}
        totalTheme="blue"
      />
    </>
  );
};

const DEFAULT_REPLACE_PAIRS = [
  {
    id: 'pair_1', replaceType: '永久替换', replaceReason: '车辆原因',
    replaceReasonDesc: '原车故障需维修，临时用替换车保障客户用车。',
    originalPlate: '浙A12345', originalBrand: '东风', originalModel: 'DFH1180',
    replacePlate: '浙A67890', replaceBrand: '福田', replaceModel: 'BJ1180',
  },
  {
    id: 'pair_2', replaceType: '临时替换', replaceReason: '客户原因', replaceFee: '500.00',
    replaceReasonDesc: '',
    originalPlate: '浙A55555', originalBrand: '重汽', originalModel: 'ZZ1160',
    replacePlate: '浙A66666', replaceBrand: '江淮', replaceModel: 'HFC1190',
  },
];

const buildReplaceVehicleDetail = (task) => {
  const pairs = (task?.pairs && task.pairs.length) ? task.pairs : DEFAULT_REPLACE_PAIRS;
  const projectInfo = {
    customerName: task?.customerName || '嘉兴某某物流有限公司',
    projectName: task?.projectName || '嘉兴氢能示范项目',
    projectType: task?.projectType || '租赁',
    contractCode: task?.contractCode || 'HT-ZL-2025-001',
    deliveryRegion: task?.deliveryRegion || '浙江省-嘉兴市',
  };
  const approvalSteps = task?.approvalSteps || [
    { title: '业务经理审批', department: '业务部主管', status: '已通过', person: '姚守涛', approveTime: '2026-02-18 10:05' },
    { title: '业务总负责人审批', department: '事业部主管', status: '已通过', person: '尚建华', approveTime: '2026-02-18 10:40', comment: '合同里面有加里程吗？' },
    { title: '运维主管审批', department: '运维主管', status: '待审批', person: '张明辉', approveTime: '—' },
  ];
  return { pairs, projectInfo, approvalSteps };
};

const VrReplaceVehicleCard = ({ pair, index }) => (
  <div className="tc-section">
    <div className="tc-section-head">
      <span className="tc-section-title">车辆替换 · #{index + 1}</span>
      <span className="tc-section-badge tc-section-badge--rose">{pair.replaceType || '—'}</span>
    </div>
    <div className="vr-replace-swap">
      <div className="vr-replace-side vr-replace-side--old">
        <span className="vr-replace-side-tag">被替换</span>
        <div className="tc-vehicle-plate">{pair.originalPlate || '—'}</div>
        <div className="tc-vehicle-model">{pair.originalBrand || '—'} · {pair.originalModel || '—'}</div>
      </div>
      <div className="vr-replace-mid" aria-hidden="true">
        <span className="vr-replace-mid-icon">→</span>
      </div>
      <div className="vr-replace-side vr-replace-side--new">
        <span className="vr-replace-side-tag">替换为</span>
        <div className="tc-vehicle-plate">{pair.replacePlate || '—'}</div>
        <div className="tc-vehicle-model">{pair.replaceBrand || '—'} · {pair.replaceModel || '—'}</div>
      </div>
    </div>
    <div className="tc-kv-grid vr-replace-reason">
      <TcInfoRow label="替换原因" value={pair.replaceReason} />
      {pair.replaceReason === '客户原因' ? (
        <TcInfoRow label="替换费用" value={pair.replaceFee ? formatMoneySymbol(pair.replaceFee) : '—'} />
      ) : null}
      <TcInfoRow label="替换原因说明" value={pair.replaceReasonDesc || '—'} full />
    </div>
  </div>
);

const getAuditPrdKey = (task) => {
  if (!task) return 'audit';
  if (task.flowType === '提车应收款') return 'audit-pickup';
  if (task.flowType === '还车应结款' || acIsReturnSettleFlow(task.flowType)) return 'audit-return';
  if (task.flowType === '租赁账单') return 'audit-lease-bill';
  if (task.flowType === '替换车申请') return 'audit-replace';
  if (task.flowType === '车辆调拨') return task.transferStage === 'ops' ? 'audit-transfer-ops' : 'audit-transfer-create';
  return 'audit';
};

const buildGenericApprovalSteps = (task) => {
  const steps = [
    { title: '发起申请', status: '已通过', person: task?.initiator || '—', approveTime: task?.initiateTime || '—' },
  ];
  if (acIsPending(task?.status)) {
    steps.push({ title: task?.currentNode || '待审批', status: '待审批', person: task?.currentAssignee || '—', approveTime: '—' });
  } else if (task?.status === '已通过') {
    steps.push({
      title: task?.currentNode || '审批完成',
      status: '已通过',
      person: (task?.handledBy || []).slice(-1)[0] || task?.currentAssignee || '—',
      approveTime: task?.finishTime || '—',
    });
  } else if (task?.status === '已驳回' || task?.status === '已撤回') {
    steps.push({
      title: task?.currentNode || '审批驳回',
      status: task?.status,
      person: (task?.handledBy || []).slice(-1)[0] || '—',
      approveTime: task?.finishTime || '—',
    });
  }
  return steps;
};

const GenericWorkflowApprovePage = ({ task, mode, onBack }) => {
  const theme = AC_FLOW_THEME[task?.flowType] || { accent: XLL_GREEN_DEEP, soft: XLL_GREEN_SOFT };
  const approvalSteps = useMemo(() => buildGenericApprovalSteps(task), [task]);
  const [decisionOpen, setDecisionOpen] = useState(false);
  const [decisionType, setDecisionType] = useState('approve');
  const [commentOpen, setCommentOpen] = useState(false);
  const showActions = mode === 'approve' || mode === 'view';

  const openDecision = (type) => { setDecisionType(type); setDecisionOpen(true); };
  const handleDecisionConfirm = (payload) => {
    const meta = APPROVAL_DECISION_META[payload.actionType];
    if (payload.actionType === 'reject' || payload.actionType === 'terminate') mpToast.warning(meta?.success || '操作成功（原型）');
    else mpToast.success(meta?.success || '操作成功（原型）');
    if (mode === 'approve') onBack();
  };

  return (
    <>
      <div className="tc-scroll">
        <div className="tc-hero" style={{ background: `linear-gradient(135deg, ${theme.accent} 0%, ${theme.accent}dd 100%)` }}>
          <div className="tc-hero-label">{task?.flowType || '审批'}</div>
          <div className="tc-hero-amount" style={{ fontSize: 20, lineHeight: 1.35 }}>{task?.summary || task?.bizNo || '—'}</div>
          <div className="tc-hero-foot tc-hero-foot--compact">
            <div className="tc-hero-foot-row"><span className="tc-hero-foot-label">单据号</span>{task?.bizNo || '—'}</div>
            <div className="tc-hero-foot-row"><span className="tc-hero-foot-label">发起人</span>{task?.initiator || '—'}</div>
            <div className="tc-hero-foot-row"><span className="tc-hero-foot-label">当前节点</span>{task?.currentNode || '—'}</div>
          </div>
        </div>
        <div className="tc-section">
          <div className="tc-section-head"><span className="tc-section-title">基本信息</span></div>
          <div className="tc-kv-grid">
            <TcInfoRow label="流程类型" value={task?.flowType || '—'} />
            <TcInfoRow label="到达时间" value={task?.arriveTime || '—'} />
            <TcInfoRow label="状态" value={task?.status || '—'} />
            {task?.customerName ? <TcInfoRow label="客户名称" value={task.customerName} full /> : null}
            {task?.projectName ? <TcInfoRow label="项目名称" value={task.projectName} full /> : null}
            {task?.plateNo ? <TcInfoRow label="车牌号" value={task.plateNo} /> : null}
          </div>
        </div>
        <ApprovalTimeline steps={approvalSteps} />
      </div>
      {showActions && (
        <ApprovalActionBar
          mode={mode}
          onComment={() => setCommentOpen(true)}
          onTerminate={() => openDecision('terminate')}
          onReject={() => openDecision('reject')}
          onApprove={() => openDecision('approve')}
        />
      )}
      <ApprovalDecisionDrawer open={decisionOpen} actionType={decisionType} onClose={() => setDecisionOpen(false)} onConfirm={handleDecisionConfirm} />
      <ApprovalCommentDrawer open={commentOpen} onClose={() => setCommentOpen(false)} onConfirm={() => mpToast.success('评论已添加（原型）')} />
    </>
  );
};

const resolveAuditDetailPage = (task) => {
  const flowType = task?.flowType;
  if (flowType === '提车应收款') return PickupReceivableApprovePage;
  if (acIsReturnSettleFlow(flowType)) return ReturnSettlementApprovePage;
  if (flowType === '租赁账单') return LeaseBillApprovePage;
  if (flowType === '车辆调拨') return task.transferStage === 'ops' ? TransferOpsApprovePage : TransferCreateApprovePage;
  if (flowType === '替换车申请') return ReplaceVehicleApprovePage;
  return GenericWorkflowApprovePage;
};

const ReplaceVehicleApprovePage = ({ task, mode, onBack }) => {
  const detail = useMemo(() => buildReplaceVehicleDetail(task), [task]);
  const { pairs, projectInfo, approvalSteps } = detail;
  const [decisionOpen, setDecisionOpen] = useState(false);
  const [decisionType, setDecisionType] = useState('approve');
  const [commentOpen, setCommentOpen] = useState(false);
  const showActions = mode === 'approve' || mode === 'view';

  const openDecision = (type) => { setDecisionType(type); setDecisionOpen(true); };
  const handleDecisionConfirm = (payload) => {
    const meta = APPROVAL_DECISION_META[payload.actionType];
    if (payload.actionType === 'reject' || payload.actionType === 'terminate') mpToast.warning(meta?.success || '操作成功（原型）');
    else mpToast.success(meta?.success || '操作成功（原型）');
    if (mode === 'approve') onBack();
  };

  return (
    <>
      <div className="tc-scroll">
        <div className="tc-hero tc-hero--replace">
          <div className="tc-hero-label">替换车辆</div>
          <div className="tc-hero-amount">{pairs.length} 台</div>
          <div className="tc-hero-meta">
            <span>{projectInfo.customerName}</span>
          </div>
          <div className="tc-hero-foot tc-hero-foot--compact">
            <div className="tc-hero-foot-row"><span className="tc-hero-foot-label">合同编码</span>{projectInfo.contractCode || '—'}</div>
            <div className="tc-hero-foot-row"><span className="tc-hero-foot-label">项目名称</span>{projectInfo.projectName || '—'}</div>
            <div className="tc-hero-foot-row"><span className="tc-hero-foot-label">交车区域</span>{projectInfo.deliveryRegion || '—'}</div>
          </div>
          <div className="tc-hero-period">
            <span className="tc-hero-period-tag">{projectInfo.projectType || '租赁'}</span>
          </div>
        </div>

        {pairs.map((pair, index) => (
          <VrReplaceVehicleCard key={pair.id || index} pair={pair} index={index} />
        ))}

        <ApprovalTimeline steps={approvalSteps} />
        <div style={{ height: showActions ? 16 : 24 }} />
      </div>

      {showActions && !decisionOpen && !commentOpen && (
        <ApprovalActionBar mode={mode} onComment={() => setCommentOpen(true)} onTerminate={() => openDecision('terminate')} onReject={() => openDecision('reject')} onApprove={() => openDecision('approve')} />
      )}
      <ApprovalDecisionDrawer open={decisionOpen} actionType={decisionType} onClose={() => setDecisionOpen(false)} onConfirm={handleDecisionConfirm} />
      <ApprovalCommentDrawer open={commentOpen} onClose={() => setCommentOpen(false)} onConfirm={() => mpToast.success('评论已添加（原型）')} />
    </>
  );
};

const ApprovalCenterModule = ({ onRegisterBack, onPrdKeyChange }) => {
  const [mainTab, setMainTab] = useState('todo');
  const [flowFilter, setFlowFilter] = useState('');
  const [searchKey, setSearchKey] = useState('');
  const [filterDrawerOpen, setFilterDrawerOpen] = useState(false);
  const [moreFilterDrawerOpen, setMoreFilterDrawerOpen] = useState(false);
  const [moreFilter, setMoreFilter] = useState(AC_EMPTY_MORE_FILTER);
  const [moreFilterDraft, setMoreFilterDraft] = useState(AC_EMPTY_MORE_FILTER);
  const [initiatorPickerOpen, setInitiatorPickerOpen] = useState(false);
  const [datePickerTarget, setDatePickerTarget] = useState(null);
  const [detailTask, setDetailTask] = useState(null);

  const activeMoreFilterCount = useMemo(() => {
    let n = 0;
    if (moreFilter.initiator.trim()) n += 1;
    if (moreFilter.dateStart || moreFilter.dateEnd) n += 1;
    return n;
  }, [moreFilter]);

  const applyMoreFilter = useCallback(() => {
    setMoreFilter({
      initiator: moreFilterDraft.initiator.trim(),
      dateStart: moreFilterDraft.dateStart,
      dateEnd: moreFilterDraft.dateEnd,
    });
    setMoreFilterDrawerOpen(false);
  }, [moreFilterDraft]);

  useEffect(() => {
    onPrdKeyChange?.(getAuditPrdKey(detailTask));
    return () => onPrdKeyChange?.(null);
  }, [detailTask, onPrdKeyChange]);

  const tabCounts = useMemo(() => {
    const counts = { initiated: 0, todo: 0, done: 0, cc: 0 };
    AC_MOCK_TASKS.forEach((t) => {
      if (!acShouldShowReturnSettleMainInList(t)) return;
      AC_TAB_ITEMS.forEach((tab) => {
        if (acFilterByTab(t, tab.key, MOCK_USER)) counts[tab.key] += 1;
      });
    });
    return counts;
  }, []);

  const filteredList = useMemo(() => {
    const q = searchKey.trim().toLowerCase();
    let list = AC_MOCK_TASKS.filter((t) => acFilterByTab(t, mainTab, MOCK_USER) && acShouldShowReturnSettleMainInList(t));
    if (flowFilter) {
      list = list.filter((t) => (
        flowFilter === '还车应结款' ? acIsReturnSettleFlow(t.flowType) : t.flowType === flowFilter
      ));
    }
    if (moreFilter.initiator.trim()) {
      list = list.filter((t) => String(t.initiator || '') === moreFilter.initiator.trim());
    }
    if (moreFilter.dateStart || moreFilter.dateEnd) {
      list = list.filter((t) => acMatchesInitiateDateRange(t, moreFilter.dateStart, moreFilter.dateEnd));
    }
    if (q) {
      list = list.filter((t) => (
        acCardBoldTitle(t).toLowerCase().includes(q)
        || String(t.flowType || '').toLowerCase().includes(q)
      ));
    }
    return [...list].sort((a, b) => String(b.arriveTime || b.initiateTime || '').localeCompare(String(a.arriveTime || a.initiateTime || '')));
  }, [mainTab, flowFilter, searchKey, moreFilter]);

  const openTaskDetail = useCallback((task) => {
    setDetailTask(task);
    return true;
  }, []);

  const handleCardClick = useCallback((task) => {
    openTaskDetail(task);
  }, [openTaskDetail]);

  useEffect(() => {
    if (!onRegisterBack) return undefined;
    onRegisterBack(() => {
      if (detailTask) { setDetailTask(null); return true; }
      if (filterDrawerOpen) { setFilterDrawerOpen(false); return true; }
      if (moreFilterDrawerOpen) { setMoreFilterDrawerOpen(false); return true; }
      return false;
    });
    return () => onRegisterBack(null);
  }, [detailTask, filterDrawerOpen, moreFilterDrawerOpen, onRegisterBack]);

  if (detailTask) {
    const Detail = resolveAuditDetailPage(detailTask);
    return (
      <div className="xll-mod-root">
        <div className="xll-mod-detail-wrap">
          <Detail task={detailTask} mode={mainTab === 'todo' ? 'approve' : 'view'} onBack={() => setDetailTask(null)} />
        </div>
      </div>
    );
  }

  const activeTabLabel = AC_TAB_ITEMS.find((t) => t.key === mainTab)?.label || '';

  return (
    <div className="xll-mod-root" data-annotation-id="page-audit">
      <div className="xll-mod-tabs" role="tablist" aria-label="审批列表分类">
        {AC_TAB_ITEMS.map((tab) => (
          <button
            key={tab.key}
            type="button"
            role="tab"
            aria-selected={mainTab === tab.key}
            className={`xll-mod-tab${mainTab === tab.key ? ' active' : ''}`}
            onClick={() => setMainTab(tab.key)}
          >
            {tab.short}
            <span className="xll-mod-tab-count">{tabCounts[tab.key]} 条</span>
          </button>
        ))}
      </div>
      <div className="xll-mod-toolbar">
        <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
          <div className="xll-mod-search" style={{ flex: 1 }} data-annotation-id="page-audit-search">
            <IconSearch />
            <input
              type="search"
              placeholder="搜索工作流标题"
              value={searchKey}
              onChange={(e) => setSearchKey(e.target.value)}
              aria-label="搜索工作流标题"
            />
          </div>
          <button
            type="button"
            className={`xll-vm-filter-btn${activeMoreFilterCount ? ' active' : ''}`}
            onClick={() => {
              setMoreFilterDraft(moreFilter);
              setInitiatorPickerOpen(false);
              setDatePickerTarget(null);
              setMoreFilterDrawerOpen(true);
            }}
            aria-label="更多筛选"
            data-annotation-id="page-audit-more-filter"
          >
            <IconFilter />
          </button>
        </div>
        <div className="xll-mod-chips" role="group" aria-label="流程类型筛选">
          <button type="button" className={`xll-mod-chip${!flowFilter ? ' active' : ''}`} onClick={() => setFlowFilter('')}>全部</button>
          {AC_QUICK_FILTERS.map((type) => (
            <button key={type} type="button" className={`xll-mod-chip${flowFilter === type ? ' active' : ''}`} onClick={() => setFlowFilter(flowFilter === type ? '' : type)}>{type}</button>
          ))}
          <button type="button" className="xll-mod-chip" style={{ borderStyle: 'dashed' }} onClick={() => setFilterDrawerOpen(true)}>更多类型</button>
        </div>
        {activeMoreFilterCount > 0 && (
          <div className="xll-mod-chips" style={{ marginTop: 10 }}>
            {moreFilter.initiator.trim() && (
              <button type="button" className="xll-mod-chip active" onClick={() => setMoreFilter((f) => ({ ...f, initiator: '' }))}>
                发起人：{moreFilter.initiator} ×
              </button>
            )}
            {(moreFilter.dateStart || moreFilter.dateEnd) && (
              <button type="button" className="xll-mod-chip active" onClick={() => setMoreFilter((f) => ({ ...f, dateStart: '', dateEnd: '' }))}>
                发起时间：{moreFilter.dateStart || '…'} 至 {moreFilter.dateEnd || '…'} ×
              </button>
            )}
          </div>
        )}
      </div>
      <div className="xll-mod-list-head"><span>{activeTabLabel}</span><span>共 {filteredList.length} 条</span></div>
      <div className="xll-mod-list">
        {filteredList.length === 0 ? (
          <div className="xll-mod-empty">暂无{activeTabLabel}任务<br />{searchKey || flowFilter || activeMoreFilterCount ? '试试清空搜索或切换筛选条件' : '新的审批任务到达后将在此展示'}</div>
        ) : (
          filteredList.map((task) => {
            const theme = AC_FLOW_THEME[task.flowType]
              || (acIsReturnSettleFlow(task.flowType) ? AC_FLOW_THEME['还车应结款'] : null)
              || { accent: XLL_GREEN_DEEP, soft: XLL_GREEN_SOFT };
            const stCls = acStatusClass(task.status);
            const isPickup = task.flowType === '提车应收款';
            const isReturn = acIsReturnSettleFlow(task.flowType);
            const isBill = task.flowType === '租赁账单';
            const isTransfer = task.flowType === '车辆调拨';
            const isReplace = task.flowType === '替换车申请';
            const isCustomerFlow = isBill || isPickup || isReturn || isReplace;
            const returnSettle = isReturn && acReturnSettleShowsHeroMoney(task.flowType)
              ? acReturnSettleDisplay(task)
              : null;
            const statusLabel = acTaskStatusLabel(task);
            const cardTitle = acCardBoldTitle(task);
            const pickupActualAmount = isPickup ? acPickupListActualAmount(task) : '';
            const isCcRecord = mainTab === 'cc';
            const showApproveBtn = mainTab === 'todo' && acIsPending(task.status);
            const showViewBtn = isCcRecord;
            return (
              <div
                key={task.id}
                className="xll-mod-card"
                style={{ '--mod-accent': theme.accent, '--mod-soft': theme.soft }}
                role="button"
                tabIndex={0}
                onClick={() => handleCardClick(task)}
                onKeyDown={(e) => e.key === 'Enter' && handleCardClick(task)}
                data-annotation-id="page-audit-card"
              >
                <div className="xll-mod-card-head">
                  <div className="xll-mod-card-head-tags">
                    <span className="xll-mod-card-type">{task.flowType}</span>
                    {isCcRecord && <span className="xll-mod-card-cc-tag">抄送给我</span>}
                  </div>
                  <span className={`xll-mod-card-status ${stCls}${statusLabel !== task.status ? ' with-approvers' : ''}`}>{statusLabel}</span>
                </div>
                <div className="xll-mod-card-title">{cardTitle}</div>
                {!isTransfer && (
                  <div className="xll-mod-card-sub">
                    {isReturn
                      ? (task.customerName || '—')
                      : isCustomerFlow
                        ? (task.projectName || '—')
                        : task.summary}
                  </div>
                )}
                {isReturn && task.projectName ? (
                  <div className="xll-mod-card-sub" style={{ marginTop: 2, color: COLOR_MUTED, fontSize: 12 }}>
                    {task.projectName}
                  </div>
                ) : null}
                {isBill && task.billStartDate && task.billEndDate && (
                  <div className="xll-mod-card-period">
                    <span>账单周期：{task.billStartDate}至{task.billEndDate}</span>
                    {task.period != null && <span className="xll-mod-card-period-tag">第{task.period}期</span>}
                  </div>
                )}
                {isTransfer && (
                  <>
                    <div className="xll-mod-card-vehicles">
                      {acTransferVehicleLines(task.vehicles).map((row) => (
                        <div key={row.label} className="xll-mod-card-vehicle-line">{row.label} {row.count}台</div>
                      ))}
                    </div>
                    {task.transferDate && (
                      <div className="xll-mod-card-period">
                        <span>调拨日期：{task.transferDate}</span>
                      </div>
                    )}
                  </>
                )}
                {isReplace && (task.pairs || []).length > 0 && (
                  <div className="xll-mod-card-vehicles">
                    {(task.pairs || []).map((p) => (
                      <div key={p.id || p.originalPlate} className="xll-mod-card-vehicle-line">{p.originalPlate} → {p.replacePlate}</div>
                    ))}
                  </div>
                )}
                <div className="xll-mod-meta">
                  <div><span className="xll-mod-meta-label">发起人 </span><span className="xll-mod-meta-val">{task.initiator || '—'}</span></div>
                  {isPickup && <div><span className="xll-mod-meta-label">实收金额 </span><span className="xll-mod-meta-val" style={{ color: '#F97316', fontWeight: 700 }}>{formatMoney(pickupActualAmount)}</span></div>}
                  {isBill && <div><span className="xll-mod-meta-label">实收金额 </span><span className="xll-mod-meta-val" style={{ color: '#0EA5E9', fontWeight: 700 }}>{formatMoney(task.actualAmount)}</span></div>}
                  {returnSettle && <div><span className="xll-mod-meta-label">{returnSettle.label} </span><span className="xll-mod-meta-val" style={{ color: '#8B5CF6', fontWeight: 700 }}>{formatMoney(returnSettle.amount)}</span></div>}
                  {isReplace && <div><span className="xll-mod-meta-label">替换车辆 </span><span className="xll-mod-meta-val" style={{ color: '#F43F5E', fontWeight: 700 }}>{(task.pairs || []).length || task.vehicleCount || 0} 台</span></div>}
                </div>
                <div className="xll-mod-card-foot">
                  <span style={{ fontSize: 12, color: COLOR_MUTED }}>发起时间 {acFormatInitiateTime(task.initiateTime)}</span>
                  {showApproveBtn && (
                    <button type="button" className="xll-mod-card-btn" onClick={(e) => { e.stopPropagation(); handleCardClick(task); }}>去审批</button>
                  )}
                  {showViewBtn && (
                    <button type="button" className="xll-mod-card-btn xll-mod-card-btn--view" onClick={(e) => { e.stopPropagation(); handleCardClick(task); }}>去查看</button>
                  )}
                </div>
              </div>
            );
          })
        )}
      </div>
      {filterDrawerOpen ? (
        <div className="xll-mod-sheet-overlay" role="dialog" aria-modal="true" aria-label="选择流程类型" data-annotation-id="page-audit-more-type-sheet">
          <button type="button" className="xll-mod-sheet-mask" onClick={() => setFilterDrawerOpen(false)} aria-label="关闭" />
          <div className="xll-mod-sheet-panel">
            <div className="xll-mod-sheet-handle" aria-hidden />
            <div className="xll-mod-sheet-header">
              <div className="xll-mod-sheet-title">选择流程类型</div>
              <button type="button" className="xll-mod-sheet-close" onClick={() => setFilterDrawerOpen(false)} aria-label="关闭">×</button>
            </div>
            <div className="xll-mod-sheet-body">
              <div className="xll-mod-drawer-types">
                <button type="button" className={`xll-mod-drawer-type-btn${!flowFilter ? ' active' : ''}`} onClick={() => { setFlowFilter(''); setFilterDrawerOpen(false); }}>全部类型</button>
                {AC_FLOW_TYPES.map((type) => (
                  <button key={type} type="button" className={`xll-mod-drawer-type-btn${flowFilter === type ? ' active' : ''}`} onClick={() => { setFlowFilter(type); setFilterDrawerOpen(false); }}>{type}</button>
                ))}
              </div>
            </div>
          </div>
        </div>
      ) : null}
      {moreFilterDrawerOpen ? (
        <div className="xll-mod-sheet-overlay" role="dialog" aria-modal="true" aria-label="更多筛选" data-annotation-id="page-audit-more-filter-sheet">
          <button type="button" className="xll-mod-sheet-mask" onClick={() => setMoreFilterDrawerOpen(false)} aria-label="关闭" />
          <div className="xll-mod-sheet-panel xll-mod-sheet-panel--filter">
            <div className="xll-mod-sheet-handle" aria-hidden />
            <div className="xll-mod-sheet-header">
              <div className="xll-mod-sheet-title">更多筛选</div>
              <button type="button" className="xll-mod-sheet-close" onClick={() => setMoreFilterDrawerOpen(false)} aria-label="关闭">×</button>
            </div>
            <div className="xll-mod-sheet-scroll">
              <div className="xll-mod-drawer-section">
                <div className="xll-mod-drawer-section-title">发起人</div>
                <div className="xll-mod-drawer-form-card">
                  <div className="xll-mod-form-row">
                    <span className="xll-mod-form-label">发起人</span>
                    <button
                      type="button"
                      className="xll-mp-picker-field"
                      onClick={() => setInitiatorPickerOpen(true)}
                      aria-label="选择发起人"
                      data-annotation-id="page-audit-filter-initiator"
                    >
                      <span className={moreFilterDraft.initiator ? '' : 'placeholder'}>
                        {moreFilterDraft.initiator ? moreFilterDraft.initiator : '请选择'}
                      </span>
                      <span className="xll-mp-picker-chevron" aria-hidden>›</span>
                    </button>
                  </div>
                </div>
              </div>
              <div className="xll-mod-drawer-section" style={{ marginBottom: 0 }}>
                <div className="xll-mod-drawer-section-title">发起时间</div>
                <div className="xll-mod-drawer-form-card">
                  <div className="xll-mod-drawer-date-row" data-annotation-id="page-audit-filter-date">
                    <button
                      type="button"
                      className="xll-mp-picker-field"
                      onClick={() => setDatePickerTarget('start')}
                      aria-label="选择开始日期"
                    >
                      <span className={moreFilterDraft.dateStart ? '' : 'placeholder'}>
                        {moreFilterDraft.dateStart || '开始日期'}
                      </span>
                    </button>
                    <span className="xll-mod-drawer-date-sep">至</span>
                    <button
                      type="button"
                      className="xll-mp-picker-field"
                      onClick={() => setDatePickerTarget('end')}
                      aria-label="选择结束日期"
                    >
                      <span className={moreFilterDraft.dateEnd ? '' : 'placeholder'}>
                        {moreFilterDraft.dateEnd || '结束日期'}
                      </span>
                    </button>
                  </div>
                </div>
              </div>
            </div>
            <div className="xll-mod-sheet-footer">
              <button type="button" className="xll-mod-btn-ghost" onClick={() => { setMoreFilterDraft(AC_EMPTY_MORE_FILTER); setMoreFilter(AC_EMPTY_MORE_FILTER); setMoreFilterDrawerOpen(false); }}>重置</button>
              <button type="button" className="xll-mod-btn-primary" onClick={applyMoreFilter}>确定</button>
            </div>
          </div>
        </div>
      ) : null}
      <MpOptionPickerSheet
        open={initiatorPickerOpen}
        title="选择发起人"
        options={AC_INITIATOR_OPTIONS}
        value={moreFilterDraft.initiator}
        formatOption={mpFormatInitiatorLabel}
        onClose={() => setInitiatorPickerOpen(false)}
        onConfirm={(value) => {
          setMoreFilterDraft((f) => ({ ...f, initiator: value }));
          setInitiatorPickerOpen(false);
        }}
      />
      <MpDatePickerSheet
        open={datePickerTarget === 'start'}
        title="选择开始日期"
        value={moreFilterDraft.dateStart}
        onClose={() => setDatePickerTarget(null)}
        onConfirm={(value) => {
          setMoreFilterDraft((f) => ({ ...f, dateStart: value }));
          setDatePickerTarget(null);
        }}
      />
      <MpDatePickerSheet
        open={datePickerTarget === 'end'}
        title="选择结束日期"
        value={moreFilterDraft.dateEnd}
        onClose={() => setDatePickerTarget(null)}
        onConfirm={(value) => {
          setMoreFilterDraft((f) => ({ ...f, dateEnd: value }));
          setDatePickerTarget(null);
        }}
      />
    </div>
  );
};

const AnnualReviewModule = ({ onRegisterBack }) => {
  const [mainTab, setMainTab] = useState('pending');
  const [searchKey, setSearchKey] = useState('');
  const [operateTask, setOperateTask] = useState(null);
  const [historyTask, setHistoryTask] = useState(null);
  const [form, setForm] = useState({ station: '嘉兴机动车检测站', cost: '380', validUntil: '2027-07-31', remark: '' });
  const [tasks, setTasks] = useState(AR_MOCK_TASKS);

  const filteredList = useMemo(() => {
    const q = searchKey.trim().toUpperCase();
    let list = tasks.filter((t) => t.tab === mainTab);
    if (q) list = list.filter((t) => t.plateNo.toUpperCase().includes(q));
    return mainTab === 'pending' ? arSortPending(list) : list;
  }, [tasks, mainTab, searchKey]);

  const tabCounts = useMemo(() => ({
    pending: tasks.filter((t) => t.tab === 'pending').length,
    history: tasks.filter((t) => t.tab === 'history').length,
  }), [tasks]);

  const handleCardClick = useCallback((task) => {
    if (task.tab === 'pending') {
      setForm({ station: '嘉兴机动车检测站', cost: '380', validUntil: '2027-07-31', remark: '' });
      setOperateTask(task);
      return;
    }
    setHistoryTask(task);
  }, []);

  const handleSubmit = useCallback(() => {
    if (!form.station || !form.cost || !form.validUntil) {
      mpToast.warning('请填写检测服务站、费用和检验有效期');
      return;
    }
    setTasks((prev) => prev.filter((t) => t.id !== operateTask.id).concat({
      ...operateTask,
      tab: 'history',
      executor: MOCK_USER,
      executeTime: '2026-06-01 10:00',
      newValidUntil: form.validUntil,
      station: form.station,
      cost: form.cost,
    }));
    mpToast.success('年审办理完成（原型）');
    setOperateTask(null);
  }, [form, operateTask]);

  useEffect(() => {
    if (!onRegisterBack) return undefined;
    onRegisterBack(() => {
      if (operateTask) { setOperateTask(null); return true; }
      if (historyTask) { setHistoryTask(null); return true; }
      return false;
    });
    return () => onRegisterBack(null);
  }, [operateTask, historyTask, onRegisterBack]);

  if (historyTask) {
    const t = historyTask;
    return (
      <div className="xll-mod-root">
        <div className="xll-mod-scroll" style={{ paddingBottom: 24 }}>
          <div className="xll-mod-hero green">
            <div className="xll-mod-hero-label">检验有效期</div>
            <div className="xll-mod-hero-amt" style={{ fontSize: 22 }}>{t.newValidUntil || t.expireDate}</div>
            <div className="xll-mod-hero-meta">{t.plateNo} · {t.brand} {t.model}</div>
          </div>
          <div className="xll-mod-section">
            <div className="xll-mod-section-title">办理记录</div>
            <div className="xll-mod-form-row"><span className="xll-mod-form-label">办理人</span><span className="xll-mod-form-value">{t.executor}</span></div>
            <div className="xll-mod-form-row"><span className="xll-mod-form-label">完成时间</span><span className="xll-mod-form-value">{t.executeTime}</span></div>
            <div className="xll-mod-form-row"><span className="xll-mod-form-label">检测服务站</span><span className="xll-mod-form-value">{t.station || '—'}</span></div>
            <div className="xll-mod-form-row"><span className="xll-mod-form-label">费用</span><span className="xll-mod-form-value">{t.cost ? formatMoneySymbol(t.cost) : '—'}</span></div>
            <div className="xll-mod-form-row"><span className="xll-mod-form-label">运营区域</span><span className="xll-mod-form-value">{t.province} {t.city}</span></div>
          </div>
        </div>
      </div>
    );
  }

  if (operateTask) {
    const t = operateTask;
    return (
      <div className="xll-mod-root xll-mod-form-page">
        <div className="xll-mod-scroll">
          <div className="xll-mod-section">
            <div className="xll-mod-section-title">车辆信息</div>
            <div className="xll-mod-form-row"><span className="xll-mod-form-label">车牌号</span><span className="xll-mod-form-value xll-mod-ar-plate">{t.plateNo}</span></div>
            <div className="xll-mod-form-row"><span className="xll-mod-form-label">品牌型号</span><span className="xll-mod-form-value">{t.brand} · {t.model}</span></div>
            <div className="xll-mod-form-row"><span className="xll-mod-form-label">检验有效期</span><span className="xll-mod-form-value">{t.expireDate}</span></div>
            <div className="xll-mod-form-row"><span className="xll-mod-form-label">运营状态</span><span className="xll-mod-form-value">{t.operateStatus}</span></div>
          </div>
          <div className="xll-mod-section">
            <div className="xll-mod-section-title">更新行驶证</div>
            <div className="xll-mod-upload" onClick={() => mpToast.info('上传行驶证照片（原型）')} onKeyDown={(e) => e.key === 'Enter' && mpToast.info('上传行驶证照片（原型）')} role="button" tabIndex={0}>点击上传行驶证照片<br /><span style={{ fontSize: 12 }}>上传后自动识别检验有效期</span></div>
            <div className="xll-mod-form-row">
              <span className="xll-mod-form-label">检验有效期</span>
              <input className="xll-mod-form-input" type="date" value={form.validUntil} onChange={(e) => setForm((f) => ({ ...f, validUntil: e.target.value }))} />
            </div>
          </div>
          <div className="xll-mod-section">
            <div className="xll-mod-section-title">检测服务站信息</div>
            <div className="xll-mod-form-row">
              <span className="xll-mod-form-label">检测服务站</span>
              <input className="xll-mod-form-input xll-mod-form-picker" value={form.station} onChange={(e) => setForm((f) => ({ ...f, station: e.target.value }))} placeholder="请选择" />
            </div>
            <div className="xll-mod-form-row">
              <span className="xll-mod-form-label">费用（￥）</span>
              <input className="xll-mod-form-input" type="number" value={form.cost} onChange={(e) => setForm((f) => ({ ...f, cost: e.target.value }))} placeholder="0.00" />
            </div>
            <div className="xll-mod-form-row">
              <span className="xll-mod-form-label">备注</span>
              <input className="xll-mod-form-input" value={form.remark} onChange={(e) => setForm((f) => ({ ...f, remark: e.target.value }))} placeholder="检测备注" />
            </div>
          </div>
        </div>
        <div className="xll-mod-foot-btns">
          <button type="button" style={{ background: COLOR_PAGE, color: COLOR_TEXT_SEC, border: `1px solid ${COLOR_LINE}` }} onClick={() => { mpToast.success('草稿已保存（原型）'); }}>保存</button>
          <button type="button" style={{ background: XLL_GREEN, color: '#fff' }} onClick={handleSubmit}>提交</button>
        </div>
      </div>
    );
  }

  return (
    <div className="xll-mod-root" data-annotation-id="page-inspection">
      <div className="xll-mod-tabs" role="tablist">
        <button type="button" role="tab" aria-selected={mainTab === 'pending'} className={`xll-mod-tab${mainTab === 'pending' ? ' active' : ''}`} onClick={() => setMainTab('pending')}>
          待处理<span className="xll-mod-tab-count">{tabCounts.pending} 辆</span>
        </button>
        <button type="button" role="tab" aria-selected={mainTab === 'history'} className={`xll-mod-tab${mainTab === 'history' ? ' active' : ''}`} onClick={() => setMainTab('history')}>
          历史记录<span className="xll-mod-tab-count">{tabCounts.history} 辆</span>
        </button>
      </div>
      <div className="xll-mod-toolbar">
        <div className="xll-mod-search">
          <IconSearch />
          <input type="search" placeholder="搜索车牌号" value={searchKey} onChange={(e) => setSearchKey(e.target.value)} aria-label="搜索车牌" />
        </div>
      </div>
      <div className="xll-mod-list-head"><span>{mainTab === 'pending' ? '待办任务' : '历史记录'}</span><span>共 {filteredList.length} 辆</span></div>
      <div className="xll-mod-list">
        {filteredList.length === 0 ? (
          <div className="xll-mod-empty">暂无{mainTab === 'pending' ? '待办' : '历史'}年审任务</div>
        ) : (
          filteredList.map((task) => {
            const tag = arDaysTag(task);
            return (
              <div key={task.id} className="xll-mod-card" style={{ '--mod-accent': COLOR_WARN, '--mod-soft': 'rgba(255,125,0,.12)' }} role="button" tabIndex={0} onClick={() => handleCardClick(task)} onKeyDown={(e) => e.key === 'Enter' && handleCardClick(task)}>
                <div className="xll-mod-card-head">
                  <span className="xll-mod-ar-plate">{task.plateNo}{tag ? <span className={`xll-mod-ar-tag ${tag.cls}`}>{tag.text}</span> : null}</span>
                </div>
                <div className="xll-mod-meta">
                  <div><span className="xll-mod-meta-label">运营状态 </span><span className="xll-mod-meta-val">{task.operateStatus}</span></div>
                  <div><span className="xll-mod-meta-label">运营区域 </span><span className="xll-mod-meta-val">{task.province} {task.city}</span></div>
                  <div><span className="xll-mod-meta-label">到期时间 </span><span className="xll-mod-meta-val">{task.expireDate}</span></div>
                  {task.tab === 'history' && (
                    <div><span className="xll-mod-meta-label">办理人 </span><span className="xll-mod-meta-val">{task.executor}</span></div>
                  )}
                </div>
                {task.tab === 'pending' && (
                  <div className="xll-mod-card-foot">
                    <span style={{ fontSize: 12, color: COLOR_MUTED }}>{task.brand} · {task.model}</span>
                    <button type="button" className="xll-mod-card-btn" onClick={(e) => { e.stopPropagation(); handleCardClick(task); }}>去办理</button>
                  </div>
                )}
              </div>
            );
          })
        )}
      </div>
    </div>
  );
};

const DV_EQUIP_PHOTO_DEMO = {
  ad: 'https://picsum.photos/seed/dv-ad-photo/400/300',
  bigWord: 'https://picsum.photos/seed/dv-bigword-photo/400/300',
};

const DvCameraViewfinder = ({
  photoUrl,
  alt = '',
  placeholder = '相机取景中…',
  zoom = 1,
  onZoomChange,
  focusPoint,
  onFocusTap,
  onImgError,
  showFocusTip = true,
}) => {
  const wrapRef = useRef(null);
  const handleTap = (e) => {
    if (!onFocusTap || !wrapRef.current) return;
    const rect = wrapRef.current.getBoundingClientRect();
    if (!rect.width || !rect.height) return;
    onFocusTap({
      x: Math.max(0, Math.min(100, ((e.clientX - rect.left) / rect.width) * 100)),
      y: Math.max(0, Math.min(100, ((e.clientY - rect.top) / rect.height) * 100)),
    });
  };
  const origin = focusPoint || { x: 50, y: 50 };
  return (
    <div className="xll-dv-camera-viewfinder" ref={wrapRef} onClick={handleTap} role="presentation">
      <div
        className="xll-dv-camera-preview"
        style={{ transform: `scale(${zoom})`, transformOrigin: `${origin.x}% ${origin.y}%` }}
      >
        {photoUrl ? (
          <img src={photoUrl} alt={alt} onError={onImgError} />
        ) : (
          <span className="xll-dv-photo-camera-placeholder">{placeholder}</span>
        )}
      </div>
      {focusPoint ? (
        <span className="xll-dv-camera-focus-ring" style={{ left: `${focusPoint.x}%`, top: `${focusPoint.y}%` }} aria-hidden />
      ) : null}
      {onZoomChange ? (
        <div className="xll-dv-camera-zoom" onClick={(e) => e.stopPropagation()} role="group" aria-label="调焦倍数">
          <button type="button" className="xll-dv-camera-zoom-btn" onClick={() => onZoomChange(Math.max(1, Math.round((zoom - 0.1) * 10) / 10))} aria-label="缩小">−</button>
          <span className="xll-dv-camera-zoom-val">{zoom.toFixed(1)}×</span>
          <button type="button" className="xll-dv-camera-zoom-btn" onClick={() => onZoomChange(Math.min(3, Math.round((zoom + 0.1) * 10) / 10))} aria-label="放大">+</button>
        </div>
      ) : null}
      {showFocusTip ? <span className="xll-dv-camera-focus-tip">点击画面调焦</span> : null}
    </div>
  );
};

const DeliveryModule = ({ onRegisterBack }) => {
  const [orders, setOrders] = useState(DV_MOCK_ORDERS);
  const [listFilter, setListFilter] = useState('inProgress');
  const [statusFilter, setStatusFilter] = useState('');
  const [searchKey, setSearchKey] = useState('');
  const [filterDrawerOpen, setFilterDrawerOpen] = useState(false);
  const [moreFilter, setMoreFilter] = useState(DV_EMPTY_MORE_FILTER);
  const [filterDraft, setFilterDraft] = useState(DV_EMPTY_FILTER_DRAFT);
  const [activeRow, setActiveRow] = useState(null);
  const [formDraft, setFormDraft] = useState(null);
  const [vehiclePickOpen, setVehiclePickOpen] = useState(false);
  const [vehiclePickSearch, setVehiclePickSearch] = useState('');
  const [vehiclePickParking, setVehiclePickParking] = useState('all');
  const [parkingSheetOpen, setParkingSheetOpen] = useState(false);
  const [plateValidateModal, setPlateValidateModal] = useState(null);
  const [clearSignConfirmOpen, setClearSignConfirmOpen] = useState(false);
  const [spareTireCaptureOpen, setSpareTireCaptureOpen] = useState(false);
  const [spareTireCaptureDraft, setSpareTireCaptureDraft] = useState({ photoUrl: '', treadDepth: '' });
  const [dvCameraFocus, setDvCameraFocus] = useState(null);
  const [dvCameraZoom, setDvCameraZoom] = useState(1);
  const [dvPhotoCamera, setDvPhotoCamera] = useState(null);
  const [photoSourceSheet, setPhotoSourceSheet] = useState(null);
  const [driverManualOpen, setDriverManualOpen] = useState(false);
  const [driverManualStep, setDriverManualStep] = useState('form');
  const [driverETrainingOpen, setDriverETrainingOpen] = useState(false);
  const [driverManualDraft, setDriverManualDraft] = useState(DV_DRIVER_MANUAL_EMPTY);
  const [deliveryFormStep, setDeliveryFormStep] = useState('vehicle');
  const [photoCapturePhase, setPhotoCapturePhase] = useState('ready');
  const [photoCaptureMode, setPhotoCaptureMode] = useState('continuous');
  const [photoCaptureIndex, setPhotoCaptureIndex] = useState(0);
  const [photoCountdown, setPhotoCountdown] = useState(3);
  const [photoCameraDraft, setPhotoCameraDraft] = useState({ photoUrl: '', treadDepth: '', treadOcrOk: false, captured: false });
  const [photoViewer, setPhotoViewer] = useState(null);
  const [deliverySignFlow, setDeliverySignFlow] = useState(null);
  const [deliverySignSuccessCd, setDeliverySignSuccessCd] = useState(3);
  const deliverySignTimerRef = useRef(null);
  const photoViewerTouchRef = useRef({ x: 0, y: 0 });
  const ocrDemoIdxRef = useRef(0);
  const spareTreadOcrIdxRef = useRef(0);
  const photoCaptureBusyRef = useRef(false);
  const photoTreadOcrIdxRef = useRef(0);
  const dvAlbumInputRef = useRef(null);
  const dvAlbumPickTargetRef = useRef({ mode: 'none' });
  const deliveryLocAutoRef = useRef('');
  const [locationFetching, setLocationFetching] = useState(false);

  const rows = useMemo(() => dvFlattenOrders(orders), [orders]);

  const kpi = useMemo(() => ({
    all: rows.length,
    inProgress: rows.filter((r) => dvIsInProgressStatus(r.deliveryStatus)).length,
    completed: rows.filter((r) => dvIsHistoryStatus(r.deliveryStatus)).length,
  }), [rows]);

  const activeMoreFilterCount = useMemo(() => {
    let n = 0;
    if (moreFilter.customerName.trim()) n += 1;
    if (moreFilter.projectName.trim()) n += 1;
    if (moreFilter.dateStart || moreFilter.dateEnd) n += 1;
    return n;
  }, [moreFilter]);

  const activeFilterCount = useMemo(() => activeMoreFilterCount + (statusFilter ? 1 : 0), [activeMoreFilterCount, statusFilter]);

  const filteredList = useMemo(() => {
    const q = searchKey.trim().toUpperCase();
    const customerQ = moreFilter.customerName.trim();
    const projectQ = moreFilter.projectName.trim();
    let list = rows;
    if (listFilter === 'inProgress') list = list.filter((r) => dvIsInProgressStatus(r.deliveryStatus));
    if (listFilter === 'completed') list = list.filter((r) => dvIsHistoryStatus(r.deliveryStatus));
    if (statusFilter) list = list.filter((r) => r.deliveryStatus === statusFilter);
    if (q) {
      list = list.filter((r) => {
        const plate = dvDisplayPlate(r.plateNo).toUpperCase();
        const customer = (r.customerName || '').toUpperCase();
        const project = (r.projectName || '').toUpperCase();
        return plate.includes(q) || customer.includes(q) || project.includes(q);
      });
    }
    if (customerQ) list = list.filter((r) => (r.customerName || '').includes(customerQ));
    if (projectQ) list = list.filter((r) => (r.projectName || '').includes(projectQ));
    if (moreFilter.dateStart || moreFilter.dateEnd) {
      list = list.filter((r) => dvRowMatchesDateRange(r, moreFilter.dateStart, moreFilter.dateEnd));
    }
    return list;
  }, [rows, listFilter, statusFilter, searchKey, moreFilter]);

  const isHistoryView = !!(activeRow && dvIsHistoryStatus(activeRow.deliveryStatus));
  const isSignPendingView = !!(activeRow && activeRow.deliveryStatus === '待客户签章' && formDraft?.deliveryStatus !== '待重新签章');
  const isResignPendingView = !!(activeRow && (activeRow.deliveryStatus === '待重新签章' || formDraft?.deliveryStatus === '待重新签章'));
  const readOnly = isHistoryView || isSignPendingView || isResignPendingView;
  const canEditAuthorizedPerson = isResignPendingView;

  const vehiclePickList = useMemo(() => {
    const q = vehiclePickSearch.trim().toUpperCase();
    let list = DV_DELIVERY_PICK_VEHICLES.filter((v) => v.vehicleStatus === '已备车');
    if (vehiclePickParking && vehiclePickParking !== 'all') {
      list = list.filter((v) => v.parkingKey === vehiclePickParking);
    }
    if (q) list = list.filter((v) => v.plateNo.toUpperCase().includes(q));
    return list.sort((a, b) => {
      const ab = dvGetReadinessMeta(a).blocked ? 1 : 0;
      const bb = dvGetReadinessMeta(b).blocked ? 1 : 0;
      if (ab !== bb) return ab - bb;
      return a.plateNo.localeCompare(b.plateNo, 'zh-CN');
    });
  }, [vehiclePickSearch, vehiclePickParking]);

  const vehiclePickParkingLabel = useMemo(() => {
    const found = DV_OPERATOR_PARKING_LOTS.find((p) => p.key === vehiclePickParking);
    return found ? found.label : '全部停车场';
  }, [vehiclePickParking]);

  const patchRow = useCallback((rowId, patch) => {
    setOrders((prev) => prev.map((order) => {
      const has = (order.vehicleList || []).some((v) => `${order.id}-${v.vehicleKey}` === rowId);
      if (!has) return order;
      return {
        ...order,
        vehicleList: order.vehicleList.map((v) => {
          if (`${order.id}-${v.vehicleKey}` !== rowId) return v;
          return { ...v, ...patch };
        }),
      };
    }));
  }, []);

  const resetDvCameraAssist = useCallback(() => {
    setDvCameraFocus(null);
    setDvCameraZoom(1);
  }, []);

  const clearDeliverySignTimer = useCallback(() => {
    if (deliverySignTimerRef.current) {
      window.clearTimeout(deliverySignTimerRef.current);
      deliverySignTimerRef.current = null;
    }
  }, []);

  const resetDeliverySignFlow = useCallback(() => {
    clearDeliverySignTimer();
    setDeliverySignFlow(null);
    setDeliverySignSuccessCd(3);
  }, [clearDeliverySignTimer]);

  const openRow = useCallback((row) => {
    setActiveRow(row);
    const viewOnly = row.deliveryStatus === '待客户签章' || row.deliveryStatus === '待重新签章' || row.deliveryStatus === '客户已签章';
    const resignPending = row.deliveryStatus === '待重新签章';
    let form = dvBuildEmptyForm(row);
    if (viewOnly) {
      form = { ...form, deliveryPhotos: dvEnsureViewDeliveryPhotos(row, form) };
    }
    setFormDraft(form);
    setDeliveryFormStep(resignPending ? 'photos' : 'vehicle');
    setPhotoCapturePhase(viewOnly ? 'done' : 'ready');
    setPhotoCaptureMode('continuous');
    setPhotoCaptureIndex(0);
    setPhotoCountdown(3);
    setPhotoCameraDraft({ photoUrl: '', treadDepth: '', treadOcrOk: false, captured: false });
    resetDeliverySignFlow();
  }, [resetDeliverySignFlow]);

  const closeForm = useCallback(() => {
    resetDeliverySignFlow();
    setActiveRow(null);
    setFormDraft(null);
    setDeliveryFormStep('vehicle');
    setPhotoCapturePhase('ready');
    setPhotoCaptureMode('continuous');
    setPhotoCaptureIndex(0);
    setPhotoCountdown(3);
    setPhotoCameraDraft({ photoUrl: '', treadDepth: '', treadOcrOk: false, captured: false });
    setPhotoViewer(null);
    deliveryLocAutoRef.current = '';
    setVehiclePickOpen(false);
    setVehiclePickSearch('');
    setVehiclePickParking('all');
    setParkingSheetOpen(false);
    setPlateValidateModal(null);
    setClearSignConfirmOpen(false);
  }, [resetDeliverySignFlow]);

  const syncActiveRow = useCallback(() => {
    if (!activeRow) return;
    const next = dvFlattenOrders(orders).find((r) => r.id === activeRow.id);
    if (next) setActiveRow(next);
  }, [activeRow, orders]);

  useEffect(() => { syncActiveRow(); }, [orders, syncActiveRow]);

  const handlePlatePick = useCallback((vehicle) => {
    if (!vehicle) return;
    const meta = dvGetReadinessMeta(vehicle);
    if (!meta.canPick) {
      mpToast.warning(meta.label);
      return;
    }
    deliveryLocAutoRef.current = '';
    setFormDraft((f) => dvMergeVehicleIntoForm(f, vehicle, activeRow));
    setVehiclePickOpen(false);
    setVehiclePickSearch('');
    mpToast.success(`已选择 ${vehicle.plateNo}`);
  }, [activeRow]);

  const applyRecognizedVehicle = useCallback((vehicle) => {
    deliveryLocAutoRef.current = '';
    setFormDraft((f) => dvMergeVehicleIntoForm(f, vehicle, activeRow));
    mpToast.success(`已识别 ${vehicle.plateNo}`);
  }, [activeRow]);

  const handleScanPickupCode = useCallback(() => {
    if (!activeRow || !formDraft) return;
    const heavy = dvIsHeavyVehicle(activeRow.vehicleType || formDraft.vehicleType, formDraft.model || activeRow.model);
    const hide = mpToast.loading('正在识别提车码…', 0);
    window.setTimeout(() => {
      hide();
      const info = dvMockDriverTrainingInfo(heavy);
    setFormDraft((f) => ({
      ...f,
        driverTrainingDone: true,
        driverTraining: '已完成',
        ...info,
      }));
      setDriverETrainingOpen(false);
      mpToast.success('驾驶培训已完成');
    }, 900);
  }, [activeRow, formDraft]);

  const openDriverETraining = useCallback(() => {
    setDriverETrainingOpen(true);
  }, []);

  const openDriverManualRecord = useCallback(() => {
    if (!formDraft) return;
    const draft = {
      driverPhone: formDraft.driverPhone || '',
      driverName: formDraft.driverName || '',
      driverIdNo: formDraft.driverIdNo || '',
      driverIdFront: !!formDraft.driverIdFront,
      driverIdBack: !!formDraft.driverIdBack,
      driverLicenseFront: !!formDraft.driverLicenseFront,
      driverLicenseBack: !!formDraft.driverLicenseBack,
      driverQualification: !!formDraft.driverQualification,
      driverFrontPhoto: !!formDraft.driverFrontPhoto,
      driverIdFrontUrl: formDraft.driverIdFrontUrl || '',
      driverIdBackUrl: formDraft.driverIdBackUrl || '',
      driverLicenseFrontUrl: formDraft.driverLicenseFrontUrl || '',
      driverLicenseBackUrl: formDraft.driverLicenseBackUrl || '',
      driverQualificationUrl: formDraft.driverQualificationUrl || '',
      driverFrontPhotoUrl: formDraft.driverFrontPhotoUrl || '',
    };
    setDriverManualDraft(draft);
    setDriverManualStep(formDraft.driverTrainingPending && formDraft.driverTrainingCodeGenerated ? 'qr' : 'form');
    setDriverManualOpen(true);
  }, [formDraft]);

  const closeDriverManualRecord = useCallback(() => {
    setDriverManualOpen(false);
    setDriverManualStep('form');
  }, []);

  const uploadDriverManualPhoto = useCallback((field, url) => {
    setDriverManualDraft((d) => ({ ...d, [field]: true, [`${field}Url`]: url }));
    mpToast.success('已上传（原型）');
  }, []);

  const generateDriverTrainingCode = useCallback(() => {
    if (!activeRow || !formDraft) return;
    const heavy = dvIsHeavyVehicle(activeRow.vehicleType || formDraft.vehicleType, formDraft.model || activeRow.model);
    const result = dvValidateDriverManualDraft(driverManualDraft, heavy);
    if (!result.ok) {
      mpToast.warning(result.message);
      return;
    }
    setFormDraft((f) => ({
      ...f,
      ...driverManualDraft,
      driverTrainingCodeGenerated: true,
      driverTrainingPending: true,
      driverTrainingDone: false,
      driverTraining: '待司机确认',
    }));
    setDriverManualStep('qr');
    mpToast.success('培训码已生成，请司机微信扫码完成培训');
  }, [activeRow, formDraft, driverManualDraft]);

  const refreshDriverTrainingStatus = useCallback(() => {
    if (!activeRow || !formDraft) return;
    const hide = mpToast.loading('正在查询培训状态…', 0);
    window.setTimeout(() => {
      hide();
      setFormDraft((f) => ({
        ...f,
        ...driverManualDraft,
        driverTrainingPending: false,
        driverTrainingCodeGenerated: true,
        driverTrainingDone: true,
        driverTraining: '已完成',
      }));
      closeDriverManualRecord();
      mpToast.success('司机已完成培训签字，信息已同步至安全培训记录（原型）');
    }, 900);
  }, [activeRow, formDraft, driverManualDraft, closeDriverManualRecord]);

  const runPlateRecognize = useCallback(() => {
    const hide = mpToast.loading('正在识别车牌…', 0);
    const plate = DV_OCR_DEMO_PLATES[ocrDemoIdxRef.current % DV_OCR_DEMO_PLATES.length];
    ocrDemoIdxRef.current += 1;
    window.setTimeout(() => {
      hide();
      const vehicle = dvFindRecognizeVehicle(plate);
      const result = dvValidateRecognizedPlate(vehicle);
      if (result.ok && vehicle) {
        applyRecognizedVehicle(vehicle);
        return;
      }
      setPlateValidateModal({ plateNo: plate, messages: result.messages });
    }, 900);
  }, [applyRecognizedVehicle]);

  const handleRecognizePlate = useCallback(() => {
    setPhotoSourceSheet({
      title: '识别车牌号',
      options: [
        {
          key: 'camera',
          label: '拍照识别',
          desc: '调用相机拍摄车牌',
          onSelect: () => {
            mpToast.info('调用相机拍摄车牌（原型）');
            window.setTimeout(() => runPlateRecognize(), 480);
          },
        },
        {
          key: 'album',
          label: '相册识别',
          desc: '从相册选择车牌照片',
          onSelect: () => {
            const hide = mpToast.loading('正在打开相册…', 0);
            window.setTimeout(() => {
              hide();
              mpToast.success('已从相册选择车牌照片（原型）');
              runPlateRecognize();
            }, 420);
          },
        },
      ],
    });
  }, [runPlateRecognize]);

  const renderPlateValidateModal = () => {
    if (!plateValidateModal) return null;
    const { plateNo, messages } = plateValidateModal;
    return (
      <div className="xll-dv-validate-overlay" role="dialog" aria-modal="true" aria-label="车辆校验提示">
        <button type="button" className="xll-dv-validate-mask" onClick={() => setPlateValidateModal(null)} aria-label="关闭" />
        <div className="xll-dv-validate-card">
          <div className="xll-dv-validate-title">无法选择该车辆</div>
          {plateNo ? <div className="xll-dv-validate-plate">识别车牌：{plateNo}</div> : null}
          <ul className="xll-dv-validate-list">
            {messages.map((msg) => <li key={msg}>{msg}</li>)}
          </ul>
          <button type="button" className="xll-dv-validate-ok" onClick={() => setPlateValidateModal(null)}>我知道了</button>
        </div>
      </div>
    );
  };

  const renderClearSignConfirmOverlay = () => {
    if (!clearSignConfirmOpen) return null;
    return (
      <div className="xll-dv-validate-overlay" role="dialog" aria-modal="true" aria-label="清除签章确认">
        <button type="button" className="xll-dv-validate-mask" onClick={() => setClearSignConfirmOpen(false)} aria-label="关闭" />
        <div className="xll-dv-validate-card">
          <div className="xll-dv-validate-title">清除签章后需要重新发起，是否确认</div>
          <div className="xll-dv-confirm-actions">
            <button type="button" className="xll-dv-confirm-cancel" onClick={() => setClearSignConfirmOpen(false)}>取消</button>
            <button
              type="button"
              className="xll-dv-confirm-ok"
              onClick={() => {
                setClearSignConfirmOpen(false);
                performClearSign();
              }}
            >
              确认
            </button>
          </div>
        </div>
      </div>
    );
  };

  const buildFormPatch = useCallback((draft) => ({
    ...draft,
    deliveryMileage: dvParseMetric2(draft.deliveryMileage),
    deliveryH2: dvParseMetric2(draft.deliveryH2),
    deliveryElec: dvParseMetric2(draft.deliveryElec),
    serviceFee: draft.serviceFee === '' ? null : dvParseMetric2(draft.serviceFee),
    deliveryLocation: draft.deliveryLocation || null,
    authorizedPersonId: draft.authorizedPersonId || '',
    authorizedPersonName: draft.authorizedPersonName || '',
    authorizedPersonPhone: draft.authorizedPersonPhone || '',
    signSent: !!draft.signSent,
  }), []);

  const completeDeliveryOpsSign = useCallback((draft) => {
    if (!activeRow || !draft) return;
    patchRow(activeRow.id, {
      ...buildFormPatch({ ...draft, signSent: true }),
      deliveryTime: dvFormatOpsSignTime(),
      deliveryPerson: MOCK_USER,
      deliveryStatus: '待客户签章',
    });
    setFormDraft((d) => (d ? { ...d, signSent: true, deliveryStatus: '待客户签章' } : d));
  }, [activeRow, patchRow, buildFormPatch]);

  const initPhotoCaptureOnEnter = useCallback((photos, draft) => {
    const seq = dvGetCaptureSequence(draft);
    const nextIdx = dvGetNextCaptureIndex(photos, draft);
    const allDone = nextIdx >= seq.length;
    const capturedCount = dvCountCapturedPhotos(photos, draft);

    setPhotoCaptureIndex(allDone ? 0 : nextIdx);
    setPhotoCountdown(3);
    setPhotoCameraDraft({ photoUrl: '', treadDepth: '', treadOcrOk: false, captured: false });

    if (allDone) {
      setPhotoCaptureMode('continuous');
      setPhotoCapturePhase('done');
      return;
    }
    setPhotoCaptureMode('continuous');
    if (capturedCount > 0) {
      setPhotoCapturePhase('countdown');
      return;
    }
    setPhotoCapturePhase('ready');
  }, []);

  const validateDeliveryBeforeSign = useCallback(() => {
    if (!activeRow || !formDraft) return false;
    if (!formDraft.plateNo) {
      mpToast.warning('请先选择交车车辆');
      return false;
    }
    if (formDraft.deliveryMileage === '' || formDraft.deliveryH2 === '' || formDraft.deliveryElec === '') {
      mpToast.warning('请填写里程、氢量与电量');
      return false;
    }
    const requiredPhotos = dvGetCaptureSequence(formDraft).filter((item) => dvIsPhotoItemRequired(item));
    const missingPhoto = requiredPhotos.find((item) => !dvPhotoCaptured(formDraft.deliveryPhotos, item.key));
    if (missingPhoto) {
      mpToast.warning(`请先完成交车照片连续拍摄（缺少：${missingPhoto.label}）`);
      setDeliveryFormStep('photos');
      initPhotoCaptureOnEnter(formDraft.deliveryPhotos || {}, formDraft);
      return false;
    }
    const authorizedPersons = dvGetAuthorizedPersons(activeRow);
    if (!authorizedPersons.length) {
      mpToast.warning(dvFormatEmptyAuthorizedHint(activeRow.businessOwner));
      return false;
    }
    if (!formDraft.authorizedPersonId) {
      mpToast.warning('请选择被授权人');
      return false;
    }
    return true;
  }, [activeRow, formDraft, initPhotoCaptureOnEnter]);

  const handleAuthorizedPersonPick = useCallback((person) => {
    if (!person || !canEditAuthorizedPerson) return;
    setFormDraft((d) => ({
      ...d,
      authorizedPersonId: person.id,
      authorizedPersonName: person.name,
      authorizedPersonPhone: person.phone,
    }));
  }, [canEditAuthorizedPerson]);

  const persistDeliveryDraftFrom = useCallback((draft, silent = true) => {
    if (!activeRow || !draft) return false;
    const nextStatus = draft.deliveryStatus === '待重新签章'
      ? '待重新签章'
      : (!draft.deliveryStatus || draft.deliveryStatus === '未开始') ? '已保存' : draft.deliveryStatus;
    patchRow(activeRow.id, {
      ...buildFormPatch(draft),
      deliveryStatus: nextStatus,
    });
    if (!silent) mpToast.success('交车单已保存（原型）');
    return true;
  }, [activeRow, patchRow, buildFormPatch]);

  const persistDeliveryDraft = useCallback((silent = true) => {
    return persistDeliveryDraftFrom(formDraft, silent);
  }, [formDraft, persistDeliveryDraftFrom]);

  const handleSendSignDoc = useCallback(() => {
    if (!validateDeliveryBeforeSign() || !activeRow || !formDraft || deliverySignFlow) return;
    const nextDraft = { ...formDraft, signSent: true };
    setFormDraft(nextDraft);
    if (!formDraft.signSent) {
      const interimStatus = formDraft.deliveryStatus === '待重新签章'
        ? '待重新签章'
        : formDraft.deliveryStatus === '待客户签章'
          ? '待客户签章'
          : '已保存';
      patchRow(activeRow.id, {
        ...buildFormPatch(nextDraft),
        deliveryStatus: interimStatus,
      });
    }
    mpToast.info('跳转E签宝完成运维人员签字后跳转成功页');
    setDeliverySignFlow('jumping');
    clearDeliverySignTimer();
    deliverySignTimerRef.current = window.setTimeout(() => {
      deliverySignTimerRef.current = null;
      completeDeliveryOpsSign(nextDraft);
      setDeliverySignFlow('success');
      setDeliverySignSuccessCd(3);
    }, 3000);
  }, [validateDeliveryBeforeSign, activeRow, formDraft, deliverySignFlow, patchRow, buildFormPatch, clearDeliverySignTimer, completeDeliveryOpsSign]);

  useEffect(() => () => clearDeliverySignTimer(), [clearDeliverySignTimer]);

  useEffect(() => {
    if (deliverySignFlow !== 'success') return undefined;
    setDeliverySignSuccessCd(3);
    const tick = window.setInterval(() => {
      setDeliverySignSuccessCd((c) => (c > 1 ? c - 1 : 1));
    }, 1000);
    const autoClose = window.setTimeout(() => closeForm(), 3000);
    return () => {
      window.clearInterval(tick);
      window.clearTimeout(autoClose);
    };
  }, [deliverySignFlow, closeForm]);

  const performClearSign = useCallback(() => {
    if (!activeRow || !formDraft) return;
    const cleared = {
      ...formDraft,
      signSent: false,
      deliveryStatus: '待重新签章',
      authorizedPersonId: '',
      authorizedPersonName: '',
      authorizedPersonPhone: '',
    };
    setFormDraft(cleared);
    patchRow(activeRow.id, {
      ...buildFormPatch(cleared),
      signSent: false,
      deliveryStatus: '待重新签章',
    });
    setDeliveryFormStep('photos');
    setPhotoCapturePhase('done');
    mpToast.success('已清除签章，请重新选择被授权人后发起签章');
  }, [activeRow, formDraft, patchRow, buildFormPatch]);

  const handleClearSign = useCallback(() => {
    if (!activeRow || !formDraft) return;
    setClearSignConfirmOpen(true);
  }, [activeRow, formDraft]);

  const handleDownloadSignFile = useCallback(() => {
    mpToast.success('下载签章 PDF（原型）');
  }, []);

  const handlePreviewSignFile = useCallback(() => {
    mpToast.info('预览签章 PDF（原型）');
  }, []);

  const handleSave = useCallback(() => {
    persistDeliveryDraft(false);
  }, [persistDeliveryDraft]);

  const handleVehicleNext = useCallback(() => {
    const result = dvValidateVehicleStep(formDraft, activeRow);
    if (!result.ok) {
      mpToast.warning(result.message);
      return;
    }
    persistDeliveryDraft(true);
    setDeliveryFormStep('inspection');
  }, [formDraft, activeRow, persistDeliveryDraft]);

  const handleInspectionNext = useCallback(() => {
    const result = dvValidateInspectionStep(formDraft);
    if (!result.ok) {
      mpToast.warning(result.message);
      return;
    }
    persistDeliveryDraft(true);
    const photos = formDraft?.deliveryPhotos || {};
    initPhotoCaptureOnEnter(photos, formDraft);
    setDeliveryFormStep('photos');
  }, [formDraft, initPhotoCaptureOnEnter, persistDeliveryDraft]);

  const getCurrentCaptureItem = useCallback(() => {
    const seq = dvGetCaptureSequence(formDraft);
    return seq[photoCaptureIndex] || null;
  }, [formDraft, photoCaptureIndex]);

  const simulateCameraShutter = useCallback(() => {
    const item = getCurrentCaptureItem();
    if (!item) return;
    let tread = '';
    let treadOcrOk = true;
    if (item.tread) {
      const ocrIdx = photoTreadOcrIdxRef.current % DV_SPARE_TREAD_OCR_DEMO.length;
      tread = DV_SPARE_TREAD_OCR_DEMO[ocrIdx];
      photoTreadOcrIdxRef.current += 1;
      treadOcrOk = !!String(tread || '').trim();
    }
    setPhotoCameraDraft({
      photoUrl: dvGetPhotoDemoUrl(item.key),
      treadDepth: tread,
      treadOcrOk,
      captured: true,
    });
    if (item.tread) {
      if (treadOcrOk) {
        mpToast.success(`已识别胎纹深度 ${tread} mm，可编辑后点击完成`);
      } else {
        mpToast.warning('胎纹识别失败，请重新拍摄');
      }
    } else {
      mpToast.success(`已拍摄「${item.label}」（原型）`);
    }
  }, [getCurrentCaptureItem]);

  const enterCameraPhase = useCallback(() => {
    resetDvCameraAssist();
    setPhotoCameraDraft({ photoUrl: '', treadDepth: '', treadOcrOk: false, captured: false });
    setPhotoCapturePhase('camera');
  }, [resetDvCameraAssist]);

  const advancePhotoCapture = useCallback(() => {
    const seq = dvGetCaptureSequence(formDraft);
    const item = seq[photoCaptureIndex];
    if (!item) return;
    const upload = dvSimulatePhotoUpload(item.key, photoCameraDraft, formDraft, activeRow);
    const nextDraft = {
      ...formDraft,
      deliveryPhotos: {
        ...(formDraft.deliveryPhotos || {}),
        [item.key]: {
          captured: true,
          photoUrl: upload.photoUrl,
          treadDepth: item.tread ? String(photoCameraDraft.treadDepth || '').trim() : '',
          uploaded: true,
          watermarkTime: upload.watermarkTime,
          watermarkAddress: upload.watermarkAddress,
        },
      },
    };
    setFormDraft(nextDraft);
    persistDeliveryDraftFrom(nextDraft, true);
    if (photoCaptureMode === 'single') {
      setPhotoCapturePhase('done');
      setPhotoCameraDraft({ photoUrl: '', treadDepth: '', treadOcrOk: false, captured: false });
      return;
    }
    const nextIdx = photoCaptureIndex + 1;
    if (nextIdx >= seq.length) {
      setPhotoCapturePhase('done');
      setPhotoCameraDraft({ photoUrl: '', treadDepth: '', treadOcrOk: false, captured: false });
    } else {
      setPhotoCaptureIndex(nextIdx);
      setPhotoCountdown(3);
      setPhotoCapturePhase('countdown');
      setPhotoCameraDraft({ photoUrl: '', treadDepth: '', treadOcrOk: false, captured: false });
    }
  }, [formDraft, photoCaptureIndex, photoCameraDraft, photoCaptureMode, activeRow, persistDeliveryDraftFrom]);

  const startPhotoCaptureAtKey = useCallback((photoKey) => {
    const seq = dvGetCaptureSequence(formDraft);
    const idx = seq.findIndex((item) => item.key === photoKey);
    if (idx < 0) return;
    setPhotoCaptureMode('single');
    setPhotoCaptureIndex(idx);
    setPhotoCountdown(3);
    setPhotoCameraDraft({ photoUrl: '', treadDepth: '', treadOcrOk: false, captured: false });
    setPhotoCapturePhase('countdown');
  }, [formDraft]);

  const startPhotoCapture = useCallback(() => {
    const photos = formDraft?.deliveryPhotos || {};
    const seq = dvGetCaptureSequence(formDraft);
    const nextIdx = dvGetNextCaptureIndex(photos, formDraft);
    if (nextIdx >= seq.length) return;
    setPhotoCaptureMode('continuous');
    setPhotoCaptureIndex(nextIdx);
    setPhotoCountdown(3);
    setPhotoCameraDraft({ photoUrl: '', treadDepth: '', treadOcrOk: false, captured: false });
    setPhotoCapturePhase('countdown');
  }, [formDraft]);

  const confirmPhotoCameraCapture = useCallback(() => {
    const item = getCurrentCaptureItem();
    if (!item) return;
    if (!photoCameraDraft.captured || !photoCameraDraft.photoUrl) {
      mpToast.warning('请先拍照或从相册选择照片');
      return;
    }
    if (item.tread) {
      if (!photoCameraDraft.treadOcrOk || !String(photoCameraDraft.treadDepth || '').trim()) {
        mpToast.warning('胎纹未识别成功，请重新拍摄后再继续');
        return;
      }
    }
    advancePhotoCapture();
  }, [getCurrentCaptureItem, photoCameraDraft, advancePhotoCapture]);

  useEffect(() => {
    if (photoCapturePhase !== 'countdown') return undefined;
    if (photoCountdown <= 0) {
      enterCameraPhase();
      return undefined;
    }
    const timer = window.setTimeout(() => setPhotoCountdown((c) => c - 1), 1000);
    return () => window.clearTimeout(timer);
  }, [photoCapturePhase, photoCountdown, enterCameraPhase]);

  const capturePhotoNow = useCallback(() => {
    if (photoCapturePhase === 'countdown') {
      enterCameraPhase();
      return;
    }
    if (photoCapturePhase === 'camera') {
      confirmPhotoCameraCapture();
    }
  }, [photoCapturePhase, enterCameraPhase, confirmPhotoCameraCapture]);

  const updateInspectionRow = useCallback((key, patch) => {
    setFormDraft((d) => ({
      ...d,
      inspectionList: (d.inspectionList || []).map((r) => (r.key === key ? { ...r, ...patch } : r)),
    }));
  }, []);

  const handleMetricFieldChange = useCallback((field, raw) => {
    const next = dvMetricInputChange(raw);
    if (next === null) return;
    setFormDraft((d) => ({ ...d, [field]: next }));
  }, []);

  const applyCurrentDeliveryLocation = useCallback((loc) => {
    setFormDraft((d) => ({
      ...d,
      deliveryLocation: {
        lat: loc.lat,
        lng: loc.lng,
        address: loc.address || '当前定位',
        source: 'current',
      },
    }));
  }, []);

  const handleGetCurrentLocation = useCallback((silent) => {
    if (!formDraft?.plateNo || dvVehicleHasGpsDevice(formDraft.plateNo)) return;
    setLocationFetching(true);
    const finish = (loc, toast) => {
      applyCurrentDeliveryLocation(loc);
      setLocationFetching(false);
      if (toast) mpToast.success(toast);
    };
    const fallback = () => finish({
      lat: 30.7286,
      lng: 121.0125,
      address: '当前定位（运维手机）',
    }, silent ? null : '已获取当前定位（原型）');

    if (typeof navigator !== 'undefined' && navigator.geolocation) {
      navigator.geolocation.getCurrentPosition(
        (pos) => finish({
          lat: Math.round(pos.coords.latitude * 10000) / 10000,
          lng: Math.round(pos.coords.longitude * 10000) / 10000,
          address: '当前定位（运维手机）',
        }, silent ? null : '已获取当前定位'),
        () => fallback(),
        { enableHighAccuracy: true, timeout: 8000, maximumAge: 60000 }
      );
      return;
    }
    fallback();
  }, [formDraft, applyCurrentDeliveryLocation]);

  useEffect(() => {
    if (!formDraft?.plateNo || readOnly) return undefined;
    if (dvVehicleHasGpsDevice(formDraft.plateNo)) return undefined;
    if (formDraft.deliveryLocation?.source === 'current') return undefined;
    if (deliveryLocAutoRef.current === formDraft.plateNo) return undefined;
    deliveryLocAutoRef.current = formDraft.plateNo;
    handleGetCurrentLocation(true);
    return undefined;
  }, [formDraft?.plateNo, formDraft?.deliveryLocation?.source, readOnly, handleGetCurrentLocation]);

  const openDeliveryPhotoViewer = useCallback((categoryKey, photoKey) => {
    const photos = formDraft?.deliveryPhotos || {};
    const items = dvBuildCategoryViewerItems(categoryKey, photos, formDraft);
    const index = items.findIndex((item) => item.key === photoKey);
    if (index < 0) return;
    setPhotoViewer({
      categoryKey,
      index,
      items,
      categoryLabel: dvPhotoCategoryLabel(categoryKey),
    });
  }, [formDraft]);

  useEffect(() => {
    if (typeof window === 'undefined') return undefined;
    window.__DV_CAPTURE__ = {
      openDeliveryCameraDemo: () => {
        if (!formDraft) return false;
        setDeliveryFormStep('photos');
        setPhotoCaptureIndex(0);
        setPhotoCapturePhase('camera');
        setPhotoCameraDraft({
          photoUrl: dvGetPhotoDemoUrl('dashboard'),
          treadDepth: '',
          treadOcrOk: false,
          captured: true,
        });
        setDvCameraFocus({ x: 42, y: 38 });
        setDvCameraZoom(1.4);
        return true;
      },
    };
    return () => { delete window.__DV_CAPTURE__; };
  }, [formDraft]);

  useEffect(() => {
    if (!onRegisterBack) return undefined;
    onRegisterBack(() => {
      if (photoViewer) { setPhotoViewer(null); return true; }
      if (dvPhotoCamera) { setDvPhotoCamera(null); resetDvCameraAssist(); return true; }
      if (spareTireCaptureOpen) { setSpareTireCaptureOpen(false); resetDvCameraAssist(); return true; }
      if (photoSourceSheet) { setPhotoSourceSheet(null); return true; }
      if (driverManualOpen) {
        if (driverManualStep === 'qr') { setDriverManualStep('form'); return true; }
        closeDriverManualRecord();
        return true;
      }
      if (driverETrainingOpen) { setDriverETrainingOpen(false); return true; }
      if (plateValidateModal) { setPlateValidateModal(null); return true; }
      if (clearSignConfirmOpen) { setClearSignConfirmOpen(false); return true; }
      if (filterDrawerOpen) { setFilterDrawerOpen(false); return true; }
      if (parkingSheetOpen) { setParkingSheetOpen(false); return true; }
      if (vehiclePickOpen) { setVehiclePickOpen(false); setParkingSheetOpen(false); return true; }
      if (deliverySignFlow === 'success') { closeForm(); return true; }
      if (activeRow && deliveryFormStep === 'photos' && (photoCapturePhase === 'countdown' || photoCapturePhase === 'camera')) {
        if (photoCapturePhase === 'camera') {
          resetDvCameraAssist();
          setPhotoCapturePhase('countdown');
          setPhotoCameraDraft({ photoUrl: '', treadDepth: '', treadOcrOk: false, captured: false });
        } else {
          setPhotoCapturePhase('ready');
          setPhotoCountdown(3);
        }
        return true;
      }
      if (activeRow) { closeForm(); return true; }
      return false;
    });
    return () => onRegisterBack(null);
  }, [activeRow, vehiclePickOpen, parkingSheetOpen, filterDrawerOpen, plateValidateModal, clearSignConfirmOpen, photoSourceSheet, spareTireCaptureOpen, dvPhotoCamera, driverManualOpen, driverManualStep, driverETrainingOpen, deliveryFormStep, photoCapturePhase, photoViewer, deliverySignFlow, closeForm, closeDriverManualRecord, resetDvCameraAssist, onRegisterBack]);

  useEffect(() => {
    if (activeRow && !formDraft && !vehiclePickOpen && !driverManualOpen && !driverETrainingOpen && deliverySignFlow !== 'success') {
      closeForm();
    }
  }, [activeRow, formDraft, vehiclePickOpen, driverManualOpen, driverETrainingOpen, deliverySignFlow, closeForm]);

  const nextPhotoTreadOcr = useCallback(() => {
    const ocrIdx = photoTreadOcrIdxRef.current % DV_SPARE_TREAD_OCR_DEMO.length;
    const tread = DV_SPARE_TREAD_OCR_DEMO[ocrIdx];
    photoTreadOcrIdxRef.current += 1;
    return tread;
  }, []);

  const openDvAlbumPicker = useCallback((target) => {
    dvAlbumPickTargetRef.current = target;
    const input = dvAlbumInputRef.current;
    if (input) {
      const allowMultiple = target.mode === 'dvPhotoCamera'
        && (target.session?.categoryKey === 'defect' || target.session?.categoryKey === 'other');
      if (allowMultiple) {
        input.setAttribute('multiple', '');
      } else {
        input.removeAttribute('multiple');
      }
    }
    input?.click();
  }, []);

  const handleDvAlbumFileChange = useCallback(async (e) => {
    const fileList = Array.from(e.target.files || []);
    e.target.value = '';
    if (!fileList.length) return;

    const imageFiles = fileList.filter((file) => String(file.type || '').startsWith('image/'));
    if (!imageFiles.length) {
      mpToast.warning('请选择图片文件');
      return;
    }
    if (imageFiles.some((file) => file.size > 5 * 1024 * 1024)) {
      mpToast.warning('单张不超过 5MB');
      return;
    }

    const target = dvAlbumPickTargetRef.current || { mode: 'none' };

    try {
      if (target.mode === 'continuous') {
        const seq = dvGetCaptureSequence(formDraft);
        const startIdx = photoCaptureIndex;
        const item = seq[startIdx];
        if (!item) return;

        if (imageFiles.length > 1) {
          mpToast.warning('连拍环节每次只能从相册选择 1 张照片');
          return;
        }

        const photoUrl = await dvReadImageFile(imageFiles[0]);
        let tread = '';
        let treadOcrOk = true;
        if (item.tread) {
          tread = nextPhotoTreadOcr();
          treadOcrOk = !!String(tread || '').trim();
          if (!treadOcrOk) mpToast.warning('胎纹识别失败，请重新选择照片');
        }
        resetDvCameraAssist();
        setPhotoCameraDraft({ photoUrl, treadDepth: tread, treadOcrOk, captured: true });
        mpToast.success(`已从相册选择「${item.label}」`);
        return;
      }

      if (target.mode === 'dvPhotoCamera') {
        const session = target.session;
        if (!session) return;

        if (imageFiles.length === 1) {
          const photoUrl = await dvReadImageFile(imageFiles[0]);
          resetDvCameraAssist();
          setDvPhotoCamera((prev) => (prev ? { ...prev, photoUrl, captured: true } : prev));
          mpToast.success(`已从相册选择「${session.label}」`);
          return;
        }

        const catKey = session.categoryKey;
        const isExtra = catKey === 'defect' || catKey === 'other';
        const photoUrls = await Promise.all(imageFiles.map((file) => dvReadImageFile(file)));
        const nextPhotos = { ...(formDraft?.deliveryPhotos || {}) };
        let applied = 0;

        const applyPhoto = (photoKey, photoUrl) => {
          const upload = dvSimulatePhotoUpload(photoKey, { photoUrl }, formDraft, activeRow);
          nextPhotos[photoKey] = {
            captured: true,
            photoUrl: upload.photoUrl,
            uploaded: true,
            watermarkTime: upload.watermarkTime,
            watermarkAddress: upload.watermarkAddress,
          };
          applied += 1;
        };

        applyPhoto(session.key, photoUrls[0]);

        if (isExtra) {
          for (let index = 1; index < photoUrls.length; index += 1) {
            const extraCount = Object.keys(nextPhotos).filter((key) => key.indexOf(`${catKey}_extra_`) === 0).length;
            if (extraCount >= 6) {
              mpToast.warning('最多上传 6 张');
              break;
            }
            const nextKey = `${catKey}_extra_${extraCount + 1}`;
            applyPhoto(nextKey, photoUrls[index]);
          }
        }

        const nextDraft = { ...formDraft, deliveryPhotos: nextPhotos };
        setFormDraft(nextDraft);
        persistDeliveryDraftFrom(nextDraft, true);
        setDvPhotoCamera(null);
        resetDvCameraAssist();
        mpToast.success(`已从相册上传 ${applied} 张照片`);
        return;
      }

      if (target.mode === 'spareTire') {
        const photoUrl = await dvReadImageFile(imageFiles[0]);
        const tread = nextPhotoTreadOcr();
        setSpareTireCaptureDraft({ photoUrl, treadDepth: tread });
        mpToast.success('已从相册选择备胎照片');
      }
    } catch {
      mpToast.error('读取照片失败');
    }
  }, [
    formDraft,
    photoCaptureIndex,
    activeRow,
    nextPhotoTreadOcr,
    resetDvCameraAssist,
    persistDeliveryDraftFrom,
  ]);

  const pickContinuousPhotoFromAlbum = useCallback(() => {
    if (!getCurrentCaptureItem()) return;
    openDvAlbumPicker({ mode: 'continuous' });
  }, [getCurrentCaptureItem, openDvAlbumPicker]);

  const renderPhotoCaptureActionBar = ({
    captured,
    onShutter,
    onAlbum,
    onComplete,
    completeDisabled = false,
  }) => (
    <div className={`xll-dv-photo-camera-actions${captured ? ' xll-dv-photo-camera-actions--triple' : ' xll-dv-photo-camera-actions--pair'}`}>
      {!captured ? (
        <>
          <button type="button" className="xll-dv-spare-capture-retake" onClick={onShutter}>拍照</button>
          <button type="button" className="xll-dv-photo-album-btn" onClick={onAlbum}>相册</button>
        </>
      ) : (
        <>
          <button type="button" className="xll-dv-spare-capture-retake" onClick={onShutter}>重新拍摄</button>
          <button type="button" className="xll-dv-photo-album-btn" onClick={onAlbum}>相册</button>
          <button type="button" className="xll-dv-spare-capture-done" onClick={onComplete} disabled={completeDisabled}>完成</button>
        </>
      )}
    </div>
  );

  const renderPhotoSourceSheet = () => {
    if (!photoSourceSheet) return null;
    return (
      <div className="xll-mod-sheet-overlay" role="dialog" aria-modal="true" aria-label={photoSourceSheet.title}>
        <button type="button" className="xll-mod-sheet-mask" onClick={() => setPhotoSourceSheet(null)} aria-label="关闭" />
        <div className="xll-mod-sheet-panel">
          <div className="xll-mod-sheet-handle" aria-hidden />
          <div className="xll-mod-sheet-header">
            <div className="xll-mod-sheet-title">{photoSourceSheet.title}</div>
            <button type="button" className="xll-mod-sheet-close" onClick={() => setPhotoSourceSheet(null)} aria-label="关闭">×</button>
          </div>
          <div className="xll-mod-sheet-body">
            {photoSourceSheet.options.map((opt) => (
              <button
                key={opt.key}
                type="button"
                className="xll-mod-sheet-option"
                onClick={() => {
                  setPhotoSourceSheet(null);
                  opt.onSelect();
                }}
              >
                <span className="xll-mod-sheet-option-text">
                  <span className="xll-mod-sheet-option-main">{opt.label}</span>
                  {opt.desc ? <span className="xll-mod-sheet-option-sub">{opt.desc}</span> : null}
                </span>
                <span style={{ color: COLOR_MUTED, fontSize: 20, lineHeight: 1 }} aria-hidden>›</span>
              </button>
            ))}
          </div>
        </div>
      </div>
    );
  };

  const renderRequiredTag = () => (!readOnly ? <span className="xll-dv-required-tag">必填</span> : null);

  const renderDriverTrainingQrBlock = ({ title, hint, qrSrc, boundDraft } = {}) => {
    const d = boundDraft || null;
    return (
      <div className="xll-dv-training-qr-wrap">
        {d ? (
          <div className="xll-dv-training-bound-kv">
            <div className="xll-dv-training-bound-row"><span className="xll-dv-training-bound-label">司机姓名</span><span className="xll-dv-training-bound-val">{d.driverName || '—'}</span></div>
            <div className="xll-dv-training-bound-row"><span className="xll-dv-training-bound-label">手机号</span><span className="xll-dv-training-bound-val">{d.driverPhone || '—'}</span></div>
            <div className="xll-dv-training-bound-row"><span className="xll-dv-training-bound-label">身份证号</span><span className="xll-dv-training-bound-val">{d.driverIdNo || '—'}</span></div>
          </div>
        ) : null}
        <div className="xll-dv-training-qr-card">
          <img className="xll-dv-training-qr-img" src={qrSrc || DV_DRIVER_TRAINING_QR} alt="驾驶培训二维码" />
          <div className="xll-dv-training-qr-title">{title || '驾驶培训视频'}</div>
          <div className="xll-dv-training-qr-hint">{hint || '请司机使用微信扫描二维码，观看完整培训视频'}</div>
          <div className="xll-dv-training-qr-wechat">
            <span aria-hidden>微信扫一扫</span>
          </div>
        </div>
      </div>
    );
  };

  const renderDriverDocThumb = (uploaded, url) => {
    if (url) return <img src={url} alt="" />;
    return uploaded ? '已上传' : '—';
  };

  const renderDriverManualPhotoSlot = (label, uploaded, url, field, demoUrl, required) => (
    <div className="xll-dv-driver-manual-photo-item">
      <div className="xll-dv-driver-manual-photo-label">
        <span>{label}</span>
        {required ? renderRequiredTag() : null}
      </div>
      <div
        className={`xll-dv-driver-manual-photo-slot${uploaded ? ' done' : ''}`}
        role="button"
        tabIndex={0}
        onClick={() => {
          openDvPhotoCamera({
            key: field,
            label,
            demoUrl,
            photoUrl: url,
            onComplete: () => uploadDriverManualPhoto(field, demoUrl),
          });
        }}
        onKeyDown={(e) => e.key === 'Enter' && openDvPhotoCamera({
          key: field,
          label,
          demoUrl,
          photoUrl: url,
          onComplete: () => uploadDriverManualPhoto(field, demoUrl),
        })}
      >
        {url ? <img src={url} alt={label} /> : (uploaded ? '已上传' : '拍照/相册')}
      </div>
    </div>
  );

  const renderDriverETrainingPage = () => (
    <div className="xll-mod-root xll-dv-module xll-mod-form-page xll-dv-driver-manual-page">
      <div className="xll-mod-detail-wrap">
        <div className="tc-scroll">
          <div className="tc-section">
            <div className="tc-section-head"><span className="xll-dv-section-head-title"><span className="xll-dv-section-badge">1</span>电子培训</span></div>
            <div className="tc-section-hint" style={{ padding: '0 14px 12px' }}>司机完成视频培训后，点击下方按钮扫描提车码</div>
            {renderDriverTrainingQrBlock({
              title: '驾驶培训视频',
              hint: '请司机使用微信扫描二维码，观看完整培训视频',
            })}
          </div>
          <div style={{ height: 16 }} />
        </div>
        <div className="xll-mod-action-bar">
          <button type="button" className="xll-mod-btn-primary" onClick={handleScanPickupCode}>识别提车码</button>
        </div>
      </div>
    </div>
  );

  const renderDriverManualPage = () => {
    const d = driverManualDraft;
    const heavy = dvIsHeavyVehicle(activeRow?.vehicleType || formDraft?.vehicleType, formDraft?.model || activeRow?.model);
    const trainingCodeUrl = dvBuildDriverTrainingCodeUrl(d);
    const trainingCodeQr = dvDriverTrainingCodeQrUrl(trainingCodeUrl);

    if (driverManualStep === 'qr') {
      return (
        <div className="xll-mod-root xll-dv-module xll-mod-form-page xll-dv-driver-manual-page">
          <div className="xll-mod-detail-wrap">
            <div className="tc-scroll">
              <div className="tc-section">
                <div className="tc-section-head"><span className="xll-dv-section-head-title"><span className="xll-dv-section-badge">1</span>培训码</span></div>
                <div className="tc-section-hint" style={{ padding: '0 14px 12px' }}>培训码已与司机信息绑定，请司机使用微信扫描二维码完成安全培训与签字</div>
                {renderDriverTrainingQrBlock({
                  title: '安全培训码',
                  hint: '请司机使用微信扫描二维码；司机签字完成后可点击下方「刷新培训状态」',
                  qrSrc: trainingCodeQr,
                  boundDraft: d,
                })}
              </div>
              <div style={{ height: 16 }} />
            </div>
            <div className="xll-mod-action-bar">
              <button type="button" className="xll-mod-btn-ghost" onClick={() => setDriverManualStep('form')}>返回修改</button>
              <button type="button" className="xll-mod-btn-primary" onClick={refreshDriverTrainingStatus}>刷新培训状态</button>
            </div>
          </div>
        </div>
      );
    }

    return (
      <div className="xll-mod-root xll-dv-module xll-mod-form-page xll-dv-driver-manual-page">
        <div className="xll-mod-detail-wrap">
          <div className="tc-scroll">
            <div className="tc-section">
              <div className="tc-section-head"><span className="xll-dv-section-head-title"><span className="xll-dv-section-badge">1</span>司机信息</span></div>
              <div className="tc-section-form">
    <div className="xll-mod-form-row">
                  <span className="xll-mod-form-label">手机号{renderRequiredTag()}</span>
                  <input
                    className="xll-mod-form-input"
                    type="tel"
                    inputMode="tel"
                    value={d.driverPhone}
                    onChange={(e) => setDriverManualDraft((prev) => ({ ...prev, driverPhone: e.target.value }))}
                    placeholder="请输入"
                  />
                </div>
                <div className="xll-mod-form-row">
                  <span className="xll-mod-form-label">姓名{renderRequiredTag()}</span>
                  <input
                    className="xll-mod-form-input"
                    value={d.driverName}
                    onChange={(e) => setDriverManualDraft((prev) => ({ ...prev, driverName: e.target.value }))}
                    placeholder="请输入"
                  />
                </div>
                <div className="xll-mod-form-row">
                  <span className="xll-mod-form-label">身份证号{renderRequiredTag()}</span>
                  <input
                    className="xll-mod-form-input"
                    value={d.driverIdNo}
                    onChange={(e) => setDriverManualDraft((prev) => ({ ...prev, driverIdNo: e.target.value }))}
                    placeholder="请输入"
                  />
                </div>
              </div>
            </div>
            <div className="tc-section">
              <div className="tc-section-head"><span className="xll-dv-section-head-title"><span className="xll-dv-section-badge">2</span>证件照片</span></div>
              <div className="tc-section-hint" style={{ padding: '0 14px 12px' }}>支持拍照或从相册选择；重卡须上传从业资格证</div>
              <div className="xll-dv-driver-manual-photos">
                {renderDriverManualPhotoSlot('身份证（正面）', d.driverIdFront, d.driverIdFrontUrl, 'driverIdFront', DV_DRIVER_DOC_DEMO.idFront, true)}
                {renderDriverManualPhotoSlot('身份证（反面）', d.driverIdBack, d.driverIdBackUrl, 'driverIdBack', DV_DRIVER_DOC_DEMO.idBack, true)}
                {renderDriverManualPhotoSlot('驾驶证（正面）', d.driverLicenseFront, d.driverLicenseFrontUrl, 'driverLicenseFront', DV_DRIVER_DOC_DEMO.licenseFront, true)}
                {renderDriverManualPhotoSlot('驾驶证（反面）', d.driverLicenseBack, d.driverLicenseBackUrl, 'driverLicenseBack', DV_DRIVER_DOC_DEMO.licenseBack, true)}
                {renderDriverManualPhotoSlot('司机正面照片', d.driverFrontPhoto, d.driverFrontPhotoUrl, 'driverFrontPhoto', DV_DRIVER_DOC_DEMO.portrait, true)}
                {renderDriverManualPhotoSlot(`从业资格证${heavy ? '' : '（选填）'}`, d.driverQualification, d.driverQualificationUrl, 'driverQualification', DV_DRIVER_DOC_DEMO.qualification, heavy)}
              </div>
            </div>
            <div style={{ height: 16 }} />
          </div>
          <div className="xll-mod-action-bar">
            <button type="button" className="xll-mod-btn-primary" onClick={generateDriverTrainingCode}>生成培训码</button>
          </div>
          {renderDvPhotoCameraOverlay()}
        </div>
    </div>
  );
  };

  const renderDvChipGroup = (field, options) => {
    if (readOnly) {
      const val = formDraft[field] || '—';
      return <div className="xll-dv-kv-grid" style={{ paddingTop: 0 }}><div className="xll-dv-kv-item"><div className="xll-dv-kv-val">{val}</div></div></div>;
    }
    return (
      <div className="xll-dv-chip-group">
        {options.map((opt) => (
          <button
            key={opt.value || 'empty'}
            type="button"
            className={`xll-dv-chip-opt${formDraft[field] === opt.value ? ' active' : ''}`}
            onClick={() => setFormDraft((d) => ({ ...d, [field]: opt.value }))}
          >
            {opt.label}
          </button>
        ))}
      </div>
    );
  };

  const renderDeliverySignSuccessPage = () => {
    const person = dvFindAuthorizedPerson(formDraft?.authorizedPersonId, dvGetAuthorizedPersons(activeRow));
    const personName = person?.name || formDraft?.authorizedPersonName || '—';
    return (
      <div className="xll-mod-root xll-dv-module xll-dv-sign-success-page">
        <div className="xll-dv-sign-success-body">
          <div className="xll-dv-sign-success-icon" aria-hidden>✓</div>
          <div className="xll-dv-sign-success-title">运维人员签字成功</div>
          <div className="xll-dv-sign-success-desc">
            签章文件已发送，短信已推送至被授权人 {personName}，等待客户完成 E签宝签章
          </div>
          <div className="xll-dv-sign-success-countdown">{deliverySignSuccessCd} 秒后自动返回列表</div>
        </div>
        <div className="xll-mod-action-bar">
          <button type="button" className="xll-mod-btn-primary" onClick={closeForm}>返回列表</button>
        </div>
      </div>
    );
  };

  const renderSectionHead = (num, title, extra, required) => (
    <div className="tc-section-head">
      <span className="xll-dv-section-head-title">
        <span className="xll-dv-section-badge">{num}</span>
        {title}
        {required ? renderRequiredTag() : null}
      </span>
      {extra || null}
    </div>
  );

  const renderDeliveryLocationMap = (plateNo, row, formLocation) => {
    const loc = dvResolveDeliveryLocation(plateNo, row, formLocation);
    const pending = loc.source === 'pending';
    return (
      <div className="xll-dv-delivery-location">
        <div
          className={`xll-dv-delivery-map${pending ? ' xll-dv-delivery-map--pending' : ''}`}
          role="img"
          aria-label={`交车位置地图，${loc.address}`}
        >
          <div className="xll-dv-delivery-map-canvas" aria-hidden>
            <div className="xll-dv-delivery-map-water" />
            <div className="xll-dv-delivery-map-road xll-dv-delivery-map-road--h" />
            <div className="xll-dv-delivery-map-road xll-dv-delivery-map-road--v" />
            <div className="xll-dv-delivery-map-road xll-dv-delivery-map-road--d" />
          </div>
          {!pending ? (
            <span className="xll-dv-delivery-map-marker" title={loc.plateNo || '当前车辆位置'}>
              <span className="xll-dv-delivery-map-marker-pin" />
            </span>
          ) : null}
          <div className="xll-dv-delivery-map-foot">
            <span><strong>{loc.address}</strong></span>
            {!pending ? <span>{loc.lat.toFixed(4)}, {loc.lng.toFixed(4)}</span> : null}
          </div>
          <span className="xll-map-brand">腾讯地图</span>
        </div>
      </div>
    );
  };

  const renderEquipSwitch = (on, onChange, label) => (
    <div className="xll-dv-equip-switch-wrap">
      {readOnly ? (
        <span className="xll-dv-equip-switch-label">{on ? '有' : '无'}</span>
      ) : (
        <>
          <span className="xll-dv-equip-switch-label">{on ? '有' : '无'}</span>
          <button
            type="button"
            className={`xll-dv-equip-switch${on ? ' on' : ''}`}
            onClick={() => onChange(!on)}
            aria-label={label}
            aria-pressed={on}
          />
        </>
      )}
    </div>
  );

  const toggleRearEquipAd = useCallback((checked) => {
    setFormDraft((d) => {
      const base = d.rearEquip || dvGetRearEquipRecord(d.plateNo, activeRow);
      return {
        ...d,
        hasAd: checked ? '有' : '无',
        rearEquip: {
          ...base,
          hasAd: checked,
          hasBigWord: checked,
          ...(checked ? {} : { adPhotoDone: false, bigWordPhotoDone: false }),
        },
        adPhotoUploaded: checked ? d.adPhotoUploaded : false,
        bigWordPhotoUploaded: checked ? d.bigWordPhotoUploaded : false,
      };
    });
  }, [activeRow]);

  const toggleRearEquipTailgate = useCallback((checked) => {
    setFormDraft((d) => {
      const base = d.rearEquip || dvGetRearEquipRecord(d.plateNo, activeRow);
      return {
        ...d,
        hasTailgate: checked ? '有' : '无',
        rearEquip: { ...base, hasTailgate: checked },
      };
    });
  }, [activeRow]);

  const handleSpareTireChange = useCallback((value) => {
    setFormDraft((d) => {
      const nextPhotos = { ...(d.deliveryPhotos || {}) };
      if (value !== '有') delete nextPhotos.spare;
      return {
        ...d,
        spareTire: value,
        deliveryPhotos: nextPhotos,
        ...(value === '有' ? {} : {
          spareTirePhotoUploaded: false,
          spareTirePhotoUrl: '',
          spareTireTreadDepth: '',
        }),
      };
    });
  }, []);

  const simulateSpareTireCapture = useCallback(() => {
    const tread = DV_SPARE_TREAD_OCR_DEMO[spareTreadOcrIdxRef.current % DV_SPARE_TREAD_OCR_DEMO.length];
    spareTreadOcrIdxRef.current += 1;
    setSpareTireCaptureDraft({ photoUrl: DV_SPARE_TIRE_DEMO_PHOTO, treadDepth: tread });
    mpToast.success(`已识别胎纹深度 ${tread} mm，可编辑后点击完成`);
  }, []);

  const openDvPhotoCamera = useCallback((session) => {
    resetDvCameraAssist();
    setDvPhotoCamera({
      ...session,
      photoUrl: session.photoUrl || '',
      captured: !!session.photoUrl,
    });
    if (!session.photoUrl) {
      mpToast.info(`拍摄${session.label}（支持拍照或相册）`);
    }
  }, [resetDvCameraAssist]);

  const shutterDvPhotoCamera = useCallback(() => {
    if (!dvPhotoCamera) return;
    resetDvCameraAssist();
    setDvPhotoCamera((prev) => (prev ? { ...prev, photoUrl: prev.demoUrl, captured: true } : prev));
    mpToast.success(`已拍摄「${dvPhotoCamera.label}」（原型）`);
  }, [dvPhotoCamera, resetDvCameraAssist]);

  const pickDvPhotoFromAlbum = useCallback(() => {
    if (!dvPhotoCamera) return;
    openDvAlbumPicker({
      mode: 'dvPhotoCamera',
      session: { ...dvPhotoCamera },
    });
  }, [dvPhotoCamera, openDvAlbumPicker]);

  const completeDvPhotoCamera = useCallback(() => {
    if (!dvPhotoCamera?.captured || !dvPhotoCamera.photoUrl) {
      mpToast.warning('请先拍照或从相册选择照片');
      return;
    }
    dvPhotoCamera.onComplete?.();
    setDvPhotoCamera(null);
    resetDvCameraAssist();
  }, [dvPhotoCamera, resetDvCameraAssist]);

  const openSpareTireCapture = useCallback((existing) => {
    resetDvCameraAssist();
    if (existing?.spareTirePhotoUploaded) {
      setSpareTireCaptureDraft({
        photoUrl: existing.spareTirePhotoUrl || DV_SPARE_TIRE_DEMO_PHOTO,
        treadDepth: existing.spareTireTreadDepth || '',
      });
      setSpareTireCaptureOpen(true);
      return;
    }
    setSpareTireCaptureDraft({ photoUrl: '', treadDepth: '' });
    setSpareTireCaptureOpen(true);
    mpToast.info('拍摄备胎（支持拍照或相册）');
  }, [resetDvCameraAssist]);

  const pickSpareTireFromAlbum = useCallback(() => {
    openDvAlbumPicker({ mode: 'spareTire' });
  }, [openDvAlbumPicker]);

  const completeSpareTireCapture = useCallback(() => {
    const tread = String(spareTireCaptureDraft.treadDepth || '').trim();
    if (!spareTireCaptureDraft.photoUrl) {
      mpToast.warning('请先拍照或从相册选择照片');
      return;
    }
    setFormDraft((d) => ({
      ...d,
      spareTirePhotoUploaded: true,
      spareTirePhotoUrl: spareTireCaptureDraft.photoUrl,
      spareTireTreadDepth: tread,
    }));
    setSpareTireCaptureOpen(false);
  }, [spareTireCaptureDraft]);

  const renderDeliveryStepBar = () => {
    const stepOrder = DV_FORM_STEPS.map((s) => s.key);
    const currentIdx = Math.max(0, stepOrder.indexOf(deliveryFormStep));
    const goStep = (key) => {
      const targetIdx = stepOrder.indexOf(key);
      if (targetIdx < 0) return;

      const enterPhotosStep = () => {
        initPhotoCaptureOnEnter(formDraft?.deliveryPhotos || {}, formDraft);
        setDeliveryFormStep('photos');
      };

      if (readOnly) {
        if (key === 'photos') initPhotoCaptureOnEnter(formDraft?.deliveryPhotos || {}, formDraft);
        setDeliveryFormStep(key);
        return;
      }

      if (targetIdx <= currentIdx) {
        if (key === 'photos') initPhotoCaptureOnEnter(formDraft?.deliveryPhotos || {}, formDraft);
        setDeliveryFormStep(key);
        return;
      }

      if (targetIdx >= 1) {
        const vehicleResult = dvValidateVehicleStep(formDraft, activeRow);
        if (!vehicleResult.ok) {
          mpToast.warning(vehicleResult.message);
          return;
        }
      }
      if (targetIdx >= 2) {
        const inspectionResult = dvValidateInspectionStep(formDraft);
        if (!inspectionResult.ok) {
          mpToast.warning(inspectionResult.message);
          return;
        }
      }

      if (key === 'photos') {
        enterPhotosStep();
        return;
      }
      setDeliveryFormStep(key);
    };
    return (
      <div className="xll-dv-steps-wrap">
        <div className="xll-dv-step-nav" role="tablist" aria-label="交车办理步骤">
          {DV_FORM_STEPS.map((s, i) => (
            <React.Fragment key={s.key}>
              <button
                type="button"
                role="tab"
                aria-selected={deliveryFormStep === s.key}
                className={`xll-dv-step-nav-item${deliveryFormStep === s.key ? ' active' : ''}${i < currentIdx ? ' done' : ''}`}
                onClick={() => goStep(s.key)}
              >
                <span className="xll-dv-step-nav-index">{i + 1}</span>
                <span className="xll-dv-step-nav-label">{s.label}</span>
              </button>
              {i < DV_FORM_STEPS.length - 1 ? (
                <span className={`xll-dv-step-nav-connector${i < currentIdx ? ' done' : ''}`} aria-hidden />
              ) : null}
            </React.Fragment>
          ))}
        </div>
      </div>
    );
  };

  const renderDeliveryInspection = (f) => {
    const list = f.inspectionList || [];
    const categories = [...new Set(list.map((row) => row.category))];
    return (
      <div className="tc-section">
        {renderSectionHead(1, '交车检查单')}
        {categories.map((cat) => (
          <div key={cat} className="xll-dv-inspection-block">
            <div className="xll-dv-inspection-cat">{cat}</div>
            {list.filter((row) => row.category === cat).map((row) => (
              <div key={row.key} className="xll-dv-inspection-row">
                <span className="xll-dv-inspection-item">{row.item}</span>
                <div className="xll-dv-inspection-controls">
                  {dvInspectionIsTireCategory(row.category) ? (
                    readOnly ? (
                      <span className="xll-dv-inspection-status">{row.treadDepth ? `${row.treadDepth} mm` : '—'}</span>
                    ) : (
                      <input
                        className="xll-dv-inspection-tread"
                        type="text"
                        inputMode="decimal"
                        value={row.treadDepth}
                        onChange={(e) => {
                          const next = dvMetricInputChange(e.target.value);
                          if (next !== null) updateInspectionRow(row.key, { treadDepth: next });
                        }}
                        placeholder="mm"
                        aria-label={`${row.item}胎纹深度`}
                      />
                    )
                  ) : readOnly ? (
                    <span className={`xll-dv-inspection-status${row.checked ? '' : ' off'}`}>{row.checked ? '正常' : '异常'}</span>
                  ) : (
                    <button
                      type="button"
                      className={`xll-dv-inspection-switch${row.checked ? ' on' : ''}`}
                      onClick={() => updateInspectionRow(row.key, { checked: !row.checked })}
                      aria-label={`${row.item}检查情况`}
                      aria-pressed={row.checked}
                    />
                  )}
                  {readOnly ? (
                    row.remark ? <span className="xll-dv-inspection-remark-read">{row.remark}</span> : null
                  ) : (
                    <input
                      className="xll-dv-inspection-remark"
                      type="text"
                      value={row.remark || ''}
                      onChange={(e) => updateInspectionRow(row.key, { remark: e.target.value })}
                      placeholder="请输入"
                      aria-label={`${row.item}备注`}
                      maxLength={40}
                    />
                  )}
                </div>
              </div>
            ))}
          </div>
        ))}
      </div>
    );
  };

  const renderPhotoViewerOverlay = () => {
    if (!photoViewer) return null;
    const { items, index, categoryLabel } = photoViewer;
    const current = items[index];
    if (!current) return null;
    const goPrev = () => {
      setPhotoViewer((v) => (v && v.index > 0 ? { ...v, index: v.index - 1 } : v));
    };
    const goNext = () => {
      setPhotoViewer((v) => (v && v.index < v.items.length - 1 ? { ...v, index: v.index + 1 } : v));
    };
    const onTouchStart = (e) => {
      const t = e.changedTouches?.[0] || e.touches?.[0];
      if (!t) return;
      photoViewerTouchRef.current = { x: t.clientX, y: t.clientY };
    };
    const onTouchEnd = (e) => {
      const t = e.changedTouches?.[0];
      if (!t) return;
      const dx = t.clientX - photoViewerTouchRef.current.x;
      const dy = t.clientY - photoViewerTouchRef.current.y;
      if (Math.abs(dx) < 48 || Math.abs(dx) < Math.abs(dy)) return;
      if (dx > 0) goPrev();
      else goNext();
    };
    return (
      <div className="xll-dv-photo-viewer" role="dialog" aria-modal="true" aria-label="照片预览">
        <div className="xll-dv-photo-viewer-top">
          <button type="button" className="xll-dv-photo-viewer-close" onClick={() => setPhotoViewer(null)}>关闭</button>
          <div className="xll-dv-photo-viewer-meta">
            <div className="xll-dv-photo-viewer-cat">{categoryLabel}</div>
            <div className="xll-dv-photo-viewer-title">{current.label}</div>
          </div>
          <span className="xll-dv-photo-viewer-counter">{index + 1}/{items.length}</span>
        </div>
        <div
          className="xll-dv-photo-viewer-stage"
          onTouchStart={onTouchStart}
          onTouchEnd={onTouchEnd}
        >
          <button type="button" className="xll-dv-photo-viewer-nav xll-dv-photo-viewer-nav--prev" disabled={index <= 0} onClick={goPrev} aria-label="上一张">‹</button>
          <div className="xll-dv-photo-viewer-img-wrap">
            <img
              key={current.key}
              src={current.photoUrl}
              alt={current.label}
              onError={(e) => { e.currentTarget.src = dvGetPhotoDemoUrl(current.key); }}
            />
          </div>
          <button type="button" className="xll-dv-photo-viewer-nav xll-dv-photo-viewer-nav--next" disabled={index >= items.length - 1} onClick={goNext} aria-label="下一张">›</button>
        </div>
        <div className="xll-dv-photo-viewer-foot">
          {current.treadDepth ? <div className="xll-dv-photo-viewer-tread">胎纹 {current.treadDepth} mm</div> : null}
          <div className="xll-dv-photo-viewer-hint">左右滑动或点击箭头切换</div>
        </div>
      </div>
    );
  };

  const renderPhotoCameraOverlay = () => {
    if (photoCapturePhase !== 'camera' || deliveryFormStep !== 'photos') return null;
    const item = getCurrentCaptureItem();
    if (!item) return null;
    const seq = dvGetCaptureSequence(formDraft);
    return (
      <div className="xll-dv-photo-camera-overlay" role="dialog" aria-modal="true" aria-label={`拍摄${item.label}`}>
        <div className="xll-dv-photo-camera-top">
          {item.label}
          <span style={{ marginLeft: 8, fontSize: 12, opacity: 0.75 }}>{photoCaptureIndex + 1}/{seq.length}</span>
        </div>
        <div className="xll-dv-photo-camera-view">
          <DvCameraViewfinder
            photoUrl={photoCameraDraft.photoUrl}
            alt={item.label}
            placeholder="相机取景中…"
            zoom={dvCameraZoom}
            onZoomChange={setDvCameraZoom}
            focusPoint={dvCameraFocus}
            onFocusTap={setDvCameraFocus}
            onImgError={(e) => { e.currentTarget.src = dvGetPhotoDemoUrl(item.key); }}
            showFocusTip={!photoCameraDraft.captured}
          />
        </div>
        {item.tread && photoCameraDraft.captured ? (
          <div className="xll-dv-photo-camera-tread">
            <span className="xll-dv-photo-camera-tread-label">{item.label}胎纹</span>
            <div className="xll-dv-spare-capture-tread-input-wrap">
              <input
                className="xll-dv-spare-capture-tread-input"
                type="text"
                inputMode="decimal"
                value={photoCameraDraft.treadDepth}
                onChange={(e) => {
                  const next = dvMetricInputChange(e.target.value);
                  if (next !== null) {
                    setPhotoCameraDraft((d) => ({
                      ...d,
                      treadDepth: next,
                      treadOcrOk: !!String(next || '').trim(),
                    }));
                  }
                }}
                placeholder="—"
                aria-label={`${item.label}胎纹深度`}
              />
              <span className="xll-dv-spare-capture-tread-unit">mm</span>
            </div>
          </div>
        ) : null}
        {item.tread && photoCameraDraft.captured && !photoCameraDraft.treadOcrOk ? (
          <div style={{ padding: '8px 16px', fontSize: 12, color: COLOR_DANGER, background: 'rgba(245,63,63,.08)' }}>
            胎纹未识别成功，请重新拍摄后再继续
          </div>
        ) : null}
        {renderPhotoCaptureActionBar({
          captured: !!photoCameraDraft.captured,
          onShutter: () => { resetDvCameraAssist(); simulateCameraShutter(); },
          onAlbum: pickContinuousPhotoFromAlbum,
          onComplete: confirmPhotoCameraCapture,
        })}
      </div>
    );
  };

  const renderSpareTireCaptureOverlay = () => {
    if (!spareTireCaptureOpen) return null;
    return (
      <div className="xll-dv-spare-capture" role="dialog" aria-modal="true" aria-label="备胎拍照">
        <div className="xll-dv-spare-capture-photo">
          <DvCameraViewfinder
            photoUrl={spareTireCaptureDraft.photoUrl}
            alt="备胎照片"
            placeholder="照片"
            zoom={dvCameraZoom}
            onZoomChange={setDvCameraZoom}
            focusPoint={dvCameraFocus}
            onFocusTap={setDvCameraFocus}
            showFocusTip={!spareTireCaptureDraft.photoUrl}
          />
        </div>
        <div className="xll-dv-spare-capture-tread">
          <span className="xll-dv-spare-capture-tread-label">备胎胎纹</span>
          <div className="xll-dv-spare-capture-tread-input-wrap">
            <input
              className="xll-dv-spare-capture-tread-input"
              type="number"
              inputMode="decimal"
              step="0.1"
              min="0"
              value={spareTireCaptureDraft.treadDepth}
              onChange={(e) => setSpareTireCaptureDraft((d) => ({ ...d, treadDepth: e.target.value }))}
              placeholder="—"
            />
            <span className="xll-dv-spare-capture-tread-unit">mm</span>
          </div>
        </div>
        {renderPhotoCaptureActionBar({
          captured: !!spareTireCaptureDraft.photoUrl,
          onShutter: () => { resetDvCameraAssist(); simulateSpareTireCapture(); },
          onAlbum: pickSpareTireFromAlbum,
          onComplete: completeSpareTireCapture,
        })}
      </div>
    );
  };

  const renderDvPhotoCameraOverlay = () => {
    if (!dvPhotoCamera) return null;
    return (
      <div className="xll-dv-photo-camera-overlay" role="dialog" aria-modal="true" aria-label={`拍摄${dvPhotoCamera.label}`}>
        <div className="xll-dv-photo-camera-top">{dvPhotoCamera.label}</div>
        <div className="xll-dv-photo-camera-view">
          <DvCameraViewfinder
            photoUrl={dvPhotoCamera.photoUrl}
            alt={dvPhotoCamera.label}
            placeholder="相机取景中…"
            zoom={dvCameraZoom}
            onZoomChange={setDvCameraZoom}
            focusPoint={dvCameraFocus}
            onFocusTap={setDvCameraFocus}
            showFocusTip={!dvPhotoCamera.captured}
          />
        </div>
        {renderPhotoCaptureActionBar({
          captured: !!dvPhotoCamera.captured,
          onShutter: shutterDvPhotoCamera,
          onAlbum: pickDvPhotoFromAlbum,
          onComplete: completeDvPhotoCamera,
        })}
      </div>
    );
  };

  const renderDeliveryFormPage = () => {
    if (!activeRow || !formDraft) return null;
    const r = activeRow;
    const f = formDraft;
      const st = dvStatusTag(r.deliveryStatus);
    const pickedVehicle = f.plateNo ? DV_DELIVERY_PICK_VEHICLES.find((v) => v.plateNo === f.plateNo) : null;
    const rearEquip = f.rearEquip || dvGetRearEquipRecord(f.plateNo, r);
    const showDriverPanel = f.driverTrainingDone || f.driverTraining === '已完成';
    const showDriverPending = !!(f.driverTrainingPending && !showDriverPanel);

    const renderDeliveryHero = () => (
          <div className="tc-hero tc-hero--delivery">
            <div className="tc-hero-amount" style={{ fontSize: 22 }}>{r.customerName || '—'}</div>
            <div className="tc-hero-meta"><span>{r.projectName || '—'}</span></div>
            <div className="tc-hero-foot tc-hero-foot--compact">
              <div className="tc-hero-foot-row"><span className="tc-hero-foot-label">交车区域</span>{r.deliveryRegion || '—'}</div>
              <div className="tc-hero-foot-row"><span className="tc-hero-foot-label">计划交车</span>{dvFormatExpectedDate(r.expectedDate)}</div>
          {r.deliveryAddress ? <div className="tc-hero-foot-row"><span className="tc-hero-foot-label">交车地点</span>{r.deliveryAddress}</div> : null}
          {readOnly ? (
            <>
              <div className="tc-hero-foot-row"><span className="tc-hero-foot-label">交车人</span>{r.deliveryPerson || '—'}</div>
              <div className="tc-hero-foot-row"><span className="tc-hero-foot-label">交车时间</span>{dvDisplayActualTime(r.deliveryTime)}</div>
            </>
          ) : null}
            </div>
            <div className="tc-hero-period">
              <span className="tc-hero-period-tag">{r.bizType || '租赁'}</span>
          <span className={`xll-dv-status ${st.cls}`} style={{ background: 'rgba(255,255,255,.22)', color: '#fff' }}>{st.text}</span>
              {r.replaceOldPlate ? <span>替换旧车 {r.replaceOldPlate}</span> : null}
            </div>
          </div>
    );

    const renderVehicleStep = () => (
      <>
        {renderDeliveryHero()}

        <div className={`tc-section xll-dv-vehicle-pick-section${f.plateNo ? ' has-vehicle' : ''}`}>
          {renderSectionHead(1, '选择车辆', !readOnly ? (
            <div className="xll-dv-section-actions">
              <button type="button" className="xll-dv-section-action-btn xll-dv-section-action-btn--primary" onClick={() => setVehiclePickOpen(true)}>选择车辆</button>
              <button type="button" className="xll-dv-section-action-btn xll-dv-section-action-btn--ghost" onClick={handleRecognizePlate}>识别车牌号</button>
            </div>
          ) : null)}
          {(f.plateNo || readOnly) ? (
            <div className="xll-dv-selected-vehicle">
              <div className="xll-dv-selected-vehicle-plate">{dvDisplayPlate(f.plateNo)}</div>
              <div className="xll-dv-selected-vehicle-sub">
                {f.brand || r.brand} · {f.model || r.model}
                {pickedVehicle ? <><br />{pickedVehicle.parkingLot}</> : null}
              </div>
            </div>
          ) : null}
        </div>

        {f.plateNo ? (
        <>
          <div className="tc-section">
          {renderSectionHead(2, '车辆配置')}
          <div className="xll-dv-equip-block">
            <div className="xll-dv-equip-row">
              <span className="xll-dv-equip-label">车身广告及放大字{renderRequiredTag()}</span>
              {renderEquipSwitch(rearEquip.hasAd, toggleRearEquipAd, '车身广告及放大字')}
            </div>
            {rearEquip.hasAd ? (
              <div className="xll-dv-equip-photo-grid">
                <div className="xll-dv-equip-photo-item">
                  <div className="xll-dv-equip-photo-item-label">车身广告照片</div>
                  <div
                    className={`xll-dv-equip-photo-slot${f.adPhotoUploaded || (readOnly && rearEquip.adPhotoDone) ? ' done' : ''}`}
                    role={readOnly ? undefined : 'button'}
                    tabIndex={readOnly ? undefined : 0}
                    onClick={() => !readOnly && openDvPhotoCamera({
                      key: 'equip-ad',
                      label: '车身广告照片',
                      demoUrl: DV_EQUIP_PHOTO_DEMO.ad,
                      photoUrl: f.adPhotoUploaded ? DV_EQUIP_PHOTO_DEMO.ad : '',
                      onComplete: () => setFormDraft((d) => ({ ...d, adPhotoUploaded: true })),
                    })}
                    onKeyDown={(e) => e.key === 'Enter' && !readOnly && openDvPhotoCamera({
                      key: 'equip-ad',
                      label: '车身广告照片',
                      demoUrl: DV_EQUIP_PHOTO_DEMO.ad,
                      photoUrl: f.adPhotoUploaded ? DV_EQUIP_PHOTO_DEMO.ad : '',
                      onComplete: () => setFormDraft((d) => ({ ...d, adPhotoUploaded: true })),
                    })}
                  >
                    {f.adPhotoUploaded || (readOnly && rearEquip.adPhotoDone) ? '已拍摄' : '拍照/相册'}
            </div>
          </div>
                <div className="xll-dv-equip-photo-item">
                  <div className="xll-dv-equip-photo-item-label">放大字照片</div>
                  <div
                    className={`xll-dv-equip-photo-slot${f.bigWordPhotoUploaded || (readOnly && rearEquip.bigWordPhotoDone) ? ' done' : ''}`}
                    role={readOnly ? undefined : 'button'}
                    tabIndex={readOnly ? undefined : 0}
                    onClick={() => !readOnly && openDvPhotoCamera({
                      key: 'equip-bigword',
                      label: '放大字照片',
                      demoUrl: DV_EQUIP_PHOTO_DEMO.bigWord,
                      photoUrl: f.bigWordPhotoUploaded ? DV_EQUIP_PHOTO_DEMO.bigWord : '',
                      onComplete: () => setFormDraft((d) => ({ ...d, bigWordPhotoUploaded: true })),
                    })}
                    onKeyDown={(e) => e.key === 'Enter' && !readOnly && openDvPhotoCamera({
                      key: 'equip-bigword',
                      label: '放大字照片',
                      demoUrl: DV_EQUIP_PHOTO_DEMO.bigWord,
                      photoUrl: f.bigWordPhotoUploaded ? DV_EQUIP_PHOTO_DEMO.bigWord : '',
                      onComplete: () => setFormDraft((d) => ({ ...d, bigWordPhotoUploaded: true })),
                    })}
                  >
                    {f.bigWordPhotoUploaded || (readOnly && rearEquip.bigWordPhotoDone) ? '已拍摄' : '拍照/相册'}
                  </div>
                </div>
              </div>
            ) : null}
            <div className="xll-dv-equip-row" style={{ marginTop: rearEquip.hasAd ? 12 : 0 }}>
              <span className="xll-dv-equip-label">尾板{renderRequiredTag()}</span>
              {renderEquipSwitch(rearEquip.hasTailgate, toggleRearEquipTailgate, '尾板')}
            </div>
            <div className="xll-dv-equip-row">
              <span className="xll-dv-equip-label">备胎</span>
              {renderEquipSwitch(f.spareTire === '有', (checked) => handleSpareTireChange(checked ? '有' : '无'), '备胎')}
            </div>
          </div>
        </div>

        <div className="tc-section">
          {renderSectionHead(3, '驾驶培训', showDriverPanel ? (
            <span className="xll-dv-training-done-tag">已完成驾驶培训</span>
          ) : showDriverPending ? (
            <span className="xll-dv-training-pending-tag">待司机确认</span>
          ) : null, true)}
          {!showDriverPanel && !showDriverPending && !readOnly ? (
            <div className="xll-dv-training-cells">
              <button type="button" className="xll-dv-training-cell" onClick={openDriverETraining}>
                <span className="xll-dv-training-cell-icon xll-dv-training-cell-icon--scan" aria-hidden>▣</span>
                <span className="xll-dv-training-cell-main">
                  <span className="xll-dv-training-cell-title">电子培训</span>
                  <span className="xll-dv-training-cell-desc">司机微信扫码观看培训视频，完成后扫描提车码</span>
                </span>
                <span className="xll-dv-training-cell-arrow" aria-hidden>›</span>
              </button>
              <button type="button" className="xll-dv-training-cell" onClick={openDriverManualRecord}>
                <span className="xll-dv-training-cell-icon xll-dv-training-cell-icon--edit" aria-hidden>✎</span>
                <span className="xll-dv-training-cell-main">
                  <span className="xll-dv-training-cell-title">手动记录</span>
                  <span className="xll-dv-training-cell-desc">录入司机与证件信息后生成培训码，司机微信扫码完成培训签字</span>
                </span>
                <span className="xll-dv-training-cell-arrow" aria-hidden>›</span>
              </button>
          </div>
          ) : null}
          {showDriverPending && !readOnly ? (
            <div className="xll-dv-training-pending-panel">
              <div className="xll-dv-training-pending-hint">培训码已生成，等待司机微信扫码阅读安全培训文件并签字确认</div>
              <button type="button" className="xll-mod-btn-ghost" style={{ width: '100%', minHeight: 40, borderRadius: 10 }} onClick={openDriverManualRecord}>查看培训码</button>
        </div>
          ) : null}
          {showDriverPanel ? (
            <div className="xll-dv-driver-panel">
              <div className="xll-dv-driver-kv">
                <div className="xll-dv-driver-kv-item"><div className="xll-dv-kv-label">司机姓名</div><div className="xll-dv-kv-val">{f.driverName || '—'}</div></div>
                <div className="xll-dv-driver-kv-item"><div className="xll-dv-kv-label">司机手机号</div><div className="xll-dv-kv-val">{f.driverPhone || '—'}</div></div>
                <div className="xll-dv-driver-kv-item full"><div className="xll-dv-kv-label">身份证号</div><div className="xll-dv-kv-val">{f.driverIdNo || '—'}</div></div>
              </div>
              <div className="xll-dv-driver-licenses cols-3">
                <div className="xll-dv-driver-license-item">
                  <div className="xll-dv-driver-license-label">身份证（正面）</div>
                  <div className="xll-dv-driver-license-thumb">{renderDriverDocThumb(f.driverIdFront, f.driverIdFrontUrl)}</div>
                </div>
                <div className="xll-dv-driver-license-item">
                  <div className="xll-dv-driver-license-label">身份证（反面）</div>
                  <div className="xll-dv-driver-license-thumb">{renderDriverDocThumb(f.driverIdBack, f.driverIdBackUrl)}</div>
                </div>
                <div className="xll-dv-driver-license-item">
                  <div className="xll-dv-driver-license-label">驾驶证（正面）</div>
                  <div className="xll-dv-driver-license-thumb">{renderDriverDocThumb(f.driverLicenseFront, f.driverLicenseFrontUrl)}</div>
                </div>
                <div className="xll-dv-driver-license-item">
                  <div className="xll-dv-driver-license-label">驾驶证（反面）</div>
                  <div className="xll-dv-driver-license-thumb">{renderDriverDocThumb(f.driverLicenseBack, f.driverLicenseBackUrl)}</div>
                </div>
                <div className="xll-dv-driver-license-item">
                  <div className="xll-dv-driver-license-label">司机正面照片</div>
                  <div className="xll-dv-driver-license-thumb">{renderDriverDocThumb(f.driverFrontPhoto, f.driverFrontPhotoUrl)}</div>
                </div>
                <div className="xll-dv-driver-license-item">
                  <div className="xll-dv-driver-license-label">从业资格证</div>
                  <div className="xll-dv-driver-license-thumb">{renderDriverDocThumb(f.driverQualification, f.driverQualificationUrl)}</div>
                </div>
              </div>
            </div>
          ) : null}
        </div>

        <div className="tc-section">
          {renderSectionHead(4, '交车数据', null, true)}
          {readOnly ? (
            <div className="xll-dv-kv-grid">
              <div className="xll-dv-kv-item"><div className="xll-dv-kv-label">里程</div><div className="xll-dv-kv-val">{dvFormatMileage(f.deliveryMileage)}</div></div>
              <div className="xll-dv-kv-item"><div className="xll-dv-kv-label">氢量</div><div className="xll-dv-kv-val">{dvFormatH2(f.deliveryH2, f.deliveryH2Unit)}</div></div>
              <div className="xll-dv-kv-item"><div className="xll-dv-kv-label">电量</div><div className="xll-dv-kv-val">{dvFormatMetric2(f.deliveryElec, '%')}</div></div>
              <div className="xll-dv-kv-item"><div className="xll-dv-kv-label">送车服务费</div><div className="xll-dv-kv-val">{dvFormatServiceFee(f.serviceFee)}</div></div>
              {f.deliveryRemark ? <div className="xll-dv-kv-item full"><div className="xll-dv-kv-label">备注</div><div className="xll-dv-kv-val">{f.deliveryRemark}</div></div> : null}
              {r.deliveryPerson ? <div className="xll-dv-kv-item"><div className="xll-dv-kv-label">交车人</div><div className="xll-dv-kv-val">{r.deliveryPerson}</div></div> : null}
            </div>
          ) : (
            <div className="xll-dv-metrics-grid">
              <div className="xll-dv-metric-field">
                <label className="xll-dv-metric-label">里程 (km){renderRequiredTag()}</label>
                <input className="xll-dv-metric-input" type="text" inputMode="decimal" value={f.deliveryMileage} onChange={(e) => handleMetricFieldChange('deliveryMileage', e.target.value)} placeholder="0.00" />
              </div>
              <div className="xll-dv-metric-field">
                <label className="xll-dv-metric-label">电量 (%){renderRequiredTag()}</label>
                <input className="xll-dv-metric-input" type="text" inputMode="decimal" value={f.deliveryElec} onChange={(e) => handleMetricFieldChange('deliveryElec', e.target.value)} placeholder="0.00" />
              </div>
              <div className="xll-dv-metric-field">
                <label className="xll-dv-metric-label">氢量{renderRequiredTag()}</label>
                <div className="xll-dv-metric-unit-wrap">
                  <input className="xll-dv-metric-input" type="text" inputMode="decimal" value={f.deliveryH2} onChange={(e) => handleMetricFieldChange('deliveryH2', e.target.value)} placeholder="0.00" />
                  <span className="xll-dv-metric-unit-suffix" aria-label="氢量单位">{f.deliveryH2Unit || dvGetModelGaugeUnit(f.brand || r.brand, f.model || r.model)}</span>
                </div>
              </div>
              <div className="xll-dv-metric-field">
                <label className="xll-dv-metric-label">送车服务费</label>
                <div className="xll-dv-metric-unit-wrap">
                  <input className="xll-dv-metric-input" type="text" inputMode="decimal" value={f.serviceFee} onChange={(e) => handleMetricFieldChange('serviceFee', e.target.value)} placeholder="0.00" aria-label="送车服务费" />
                  <span className="xll-dv-metric-unit-suffix" aria-label="金额单位">元</span>
                </div>
              </div>
              <div className="xll-dv-metric-field full">
                <label className="xll-dv-metric-label">备注</label>
                <textarea
                  className="xll-dv-metric-remark"
                  value={f.deliveryRemark || ''}
                  onChange={(e) => setFormDraft((d) => ({ ...d, deliveryRemark: e.target.value }))}
                  placeholder="可补充其他备注说明"
                  rows={3}
                  aria-label="交车备注"
                />
              </div>
            </div>
          )}
        </div>

        <div className="tc-section">
          {renderSectionHead(5, '交车位置', (
            <div className="xll-dv-section-actions">
              {f.plateNo ? <span className="xll-dv-delivery-location-plate">{f.plateNo}</span> : null}
              {!readOnly && f.plateNo && !dvVehicleHasGpsDevice(f.plateNo) ? (
                <button
                  type="button"
                  className="xll-dv-section-action-btn xll-dv-section-action-btn--primary"
                  onClick={() => handleGetCurrentLocation(false)}
                  disabled={locationFetching}
                >
                  {locationFetching ? '定位中…' : '获取当前定位'}
                </button>
              ) : null}
            </div>
          ))}
          {renderDeliveryLocationMap(f.plateNo, r, f.deliveryLocation)}
        </div>
        </>
        ) : null}
        <div style={{ height: 16 }} />
      </>
    );

    const renderPhotosStep = () => {
      const photos = f.deliveryPhotos || {};
      const seq = dvGetCaptureSequence(f);
      const capturedCount = dvCountCapturedPhotos(photos, f);
      const currentItem = seq[photoCaptureIndex] || null;
      const sparePhotoTread = dvGetPhotoRecord(photos, 'spare')?.treadDepth;

      const removePhoto = (photoKey) => {
        setFormDraft((d) => {
          const next = { ...(d.deliveryPhotos || {}) };
          delete next[photoKey];
          return { ...d, deliveryPhotos: next };
        });
        setPhotoViewer((v) => (v?.items?.some((item) => item.key === photoKey) ? null : v));
      };

      const renderPhotoThumbCell = (photoKey, label, categoryKey) => {
        const record = dvGetPhotoRecord(photos, photoKey);
        const captured = dvPhotoCaptured(photos, photoKey);
        const resolvedUrl = captured ? dvResolvePhotoUrl(photoKey, record) : '';
        const canPreview = !!resolvedUrl;
        const isExtraCategory = categoryKey === 'defect' || categoryKey === 'other';
        const isReshootSlot = !readOnly && photoCapturePhase === 'done' && !captured && !isExtraCategory;
        const allowDelete = !readOnly && captured && (isExtraCategory || photoCapturePhase === 'done');
        const placeholderText = isReshootSlot ? '补拍' : (captured ? 'PHOTO' : '+ 添加');
        const handleThumbClick = () => {
          if (canPreview) {
            openDeliveryPhotoViewer(categoryKey, photoKey);
            return;
          }
          if (isReshootSlot) startPhotoCaptureAtKey(photoKey);
        };
      return (
          <div key={photoKey}>
            <div
              className={`xll-dv-photo-thumb${canPreview || isReshootSlot ? '' : ' xll-dv-photo-thumb--empty'}`}
              role={canPreview || isReshootSlot ? 'button' : undefined}
              tabIndex={canPreview || isReshootSlot ? 0 : undefined}
              onClick={handleThumbClick}
              onKeyDown={(e) => { if ((canPreview || isReshootSlot) && e.key === 'Enter') handleThumbClick(); }}
            >
              {resolvedUrl ? (
                <>
                  <img
                    src={resolvedUrl}
                    alt={label}
                    onError={(e) => { e.currentTarget.src = dvGetPhotoDemoUrl(photoKey); }}
                  />
                  {record?.watermarkTime ? (
                    <div className="xll-dv-photo-thumb-watermark" aria-hidden>
                      <div>{record.watermarkTime}</div>
                      {record.watermarkAddress ? <div className="xll-dv-photo-thumb-watermark-loc">{record.watermarkAddress}</div> : null}
          </div>
                  ) : null}
                </>
              ) : (
                <span className={`xll-dv-photo-thumb-placeholder${isReshootSlot ? ' xll-dv-photo-thumb-placeholder--reshoot' : ''}`}>{placeholderText}</span>
              )}
              {allowDelete ? (
                <button
                  type="button"
                  className="xll-dv-photo-thumb-del"
                  onClick={(e) => { e.stopPropagation(); removePhoto(photoKey); }}
                  aria-label={`删除${label}`}
                >
                  ×
                </button>
              ) : null}
            </div>
            <div className="xll-dv-photo-thumb-label">{label}</div>
            {record?.treadDepth ? <div className="xll-dv-photo-thumb-tread">胎纹 {record.treadDepth} mm</div> : null}
        </div>
      );
      };

      const addExtraPhoto = (categoryKey) => {
        const keys = Object.keys(photos).filter((key) => key.indexOf(`${categoryKey}_extra_`) === 0);
        if (keys.length >= 6) {
          mpToast.warning('最多上传 6 张');
          return;
        }
        const nextKey = `${categoryKey}_extra_${keys.length + 1}`;
        const label = `照片${keys.length + 1}`;
        openDvPhotoCamera({
          key: nextKey,
          label,
          categoryKey: cat.key,
          demoUrl: dvGetPhotoDemoUrl(nextKey),
          onComplete: () => {
            const upload = dvSimulatePhotoUpload(nextKey, { photoUrl: dvGetPhotoDemoUrl(nextKey) }, f, r);
            setFormDraft((d) => ({
              ...d,
              deliveryPhotos: {
                ...(d.deliveryPhotos || {}),
                [nextKey]: {
                  captured: true,
                  photoUrl: upload.photoUrl,
                  uploaded: true,
                  watermarkTime: upload.watermarkTime,
                  watermarkAddress: upload.watermarkAddress,
                },
              },
            }));
            mpToast.success('已添加照片（原型）');
          },
        });
      };

      const renderPhotoCategorySection = (cat, sectionNum) => {
        let items = [];
        if (cat.key === 'defect' || cat.key === 'other') {
          items = Object.keys(photos)
            .filter((key) => key.indexOf(`${cat.key}_extra_`) === 0 && dvPhotoCaptured(photos, key))
            .sort()
            .map((key, idx) => ({ key, label: `照片${idx + 1}` }));
        } else {
          items = dvPhotoItemsByCategory(cat.key).filter((item) => {
            if (item.key === 'spare' && f.spareTire === '无') return false;
            if (readOnly) return dvPhotoCaptured(photos, item.key);
            return true;
          });
        }
        if (readOnly && items.length === 0 && cat.key !== 'defect' && cat.key !== 'other') return null;
      return (
          <div key={cat.key} className="tc-section xll-dv-photo-cat-section">
            {renderSectionHead(sectionNum, cat.label)}
            <div className="xll-dv-photo-block">
              <div className="xll-dv-photo-grid">
                {items.map((item) => renderPhotoThumbCell(item.key, item.label, cat.key))}
                {!readOnly && (cat.key === 'defect' || cat.key === 'other') ? (
                  <div>
                  <div
                    className="xll-dv-photo-slot"
                    role="button"
                    tabIndex={0}
                      onClick={() => addExtraPhoto(cat.key)}
                      onKeyDown={(e) => e.key === 'Enter' && addExtraPhoto(cat.key)}
                  >
                      + 添加
                  </div>
              </div>
                ) : null}
            </div>
            </div>
          </div>
        );
      };

      if (!readOnly && photoCapturePhase === 'ready') {
        const progressPct = seq.length ? Math.round((capturedCount / seq.length) * 100) : 0;
        return (
          <div className="xll-dv-photo-capture-page xll-dv-photo-capture-page--ready">
            <div className="xll-dv-photo-capture-card">
              <div className="xll-dv-photo-capture-icon" aria-hidden>
                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
                  <path d="M4 7h3l2-3h6l2 3h3a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2z" />
                  <circle cx="12" cy="13" r="3.5" />
                </svg>
              </div>
              <h2 className="xll-dv-photo-capture-title">请准备好连续拍摄</h2>
              <p className="xll-dv-photo-capture-sub">请确认车辆与拍摄环境就绪，连拍将自动按顺序进行</p>
              {capturedCount > 0 ? (
                <div className="xll-dv-photo-capture-resume">
                  <div className="xll-dv-photo-capture-progress-bar" aria-hidden>
                    <span style={{ width: `${progressPct}%` }} />
                  </div>
                  <div className="xll-dv-photo-capture-resume-text">
                    已完成 <strong>{capturedCount}/{seq.length}</strong> 张，点击底部按钮继续拍摄
                  </div>
                </div>
              ) : (
                <ul className="xll-dv-photo-capture-tips">
                  <li>请保持画面清晰、光线充足</li>
                  <li>连拍过程中请勿退出，下次将自动续拍</li>
                </ul>
              )}
            </div>
        </div>
      );
    }

      if (!readOnly && photoCapturePhase === 'countdown') {
        const catKey = currentItem?.category || 'body';
        const catLabel = dvPhotoCategoryLabel(catKey);
        return (
          <div className="xll-dv-photo-capture-page xll-dv-photo-capture-page--countdown">
            <div className="xll-dv-photo-capture-card">
              {catLabel ? (
                <span className={`xll-dv-photo-capture-badge xll-dv-photo-capture-badge--${catKey}`}>{catLabel}</span>
              ) : null}
              <p className="xll-dv-photo-capture-soon">即将拍摄</p>
              <div className="xll-dv-photo-capture-countdown-ring" aria-live="polite" aria-label={`${photoCountdown}秒后开始拍摄`}>
                <span key={photoCountdown} className="xll-dv-photo-capture-countdown-num">{photoCountdown}</span>
              </div>
              <h2 className="xll-dv-photo-capture-target">{currentItem?.label || '—'}</h2>
              <p className="xll-dv-photo-capture-countdown-tip">秒后自动开始 · 可点底部「立即拍摄」</p>
              <div className="xll-dv-photo-capture-step-pill">
                第 <em>{photoCaptureIndex + 1}</em> / {seq.length} 项
              </div>
            </div>
          </div>
        );
      }

      if (!readOnly && photoCapturePhase === 'camera') {
        return null;
      }

      const photoCategoryNodes = DV_PHOTO_CATEGORIES.map((cat, idx) => renderPhotoCategorySection(cat, idx + 1));

      const renderAuthorizedPersonBlock = () => {
        const authorizedPersons = dvGetAuthorizedPersons(activeRow);
        const ownerName = String(activeRow?.businessOwner || '').trim() || '业管';
        if (readOnly && !canEditAuthorizedPerson) {
          const person = dvFindAuthorizedPerson(f.authorizedPersonId, authorizedPersons);
          const name = person?.name || f.authorizedPersonName;
          const phone = person?.phone || f.authorizedPersonPhone;
          if (!name) {
            return (
              <p className="xll-dv-authorized-empty" role="status">
                无授权人，请联系<strong>{ownerName}</strong>补充授权人信息
              </p>
            );
          }
          return (
            <>
              <div className="xll-dv-authorized-subtitle">被授权人</div>
              <div className="xll-dv-authorized-grid">
                <div className="xll-dv-authorized-card xll-dv-authorized-card--readonly active">
                  <span className="xll-dv-authorized-avatar" aria-hidden>{dvPersonInitial(name)}</span>
                  <div className="xll-dv-authorized-card-body">
                    <div className="xll-dv-authorized-card-name">{name}</div>
                    <div className="xll-dv-authorized-card-phone">{phone || '—'}</div>
                  </div>
                </div>
              </div>
            </>
          );
        }
        if (!authorizedPersons.length) {
          return (
            <p className="xll-dv-authorized-empty" role="status">
              无授权人，请联系<strong>{ownerName}</strong>补充授权人信息
            </p>
          );
        }
        return (
          <>
            <div className="xll-dv-authorized-hint">选择客户方被授权人，签章短信将发送至其手机</div>
            <div className="xll-dv-authorized-grid">
              {authorizedPersons.map((person) => {
                const selected = f.authorizedPersonId === person.id;
                return (
                  <button
                    key={person.id}
                    type="button"
                    className={`xll-dv-authorized-card${selected ? ' active' : ''}`}
                    disabled={!canEditAuthorizedPerson}
                    onClick={() => handleAuthorizedPersonPick(person)}
                  >
                    <span className="xll-dv-authorized-card-check" aria-hidden>{selected ? '✓' : ''}</span>
                    <span className="xll-dv-authorized-avatar" aria-hidden>{dvPersonInitial(person.name)}</span>
                    <div className="xll-dv-authorized-card-body">
                      <div className="xll-dv-authorized-card-name">{person.name}</div>
                      <div className="xll-dv-authorized-card-phone">{person.phone || '—'}</div>
                    </div>
                  </button>
                );
              })}
            </div>
          </>
        );
      };

      return (
        <>
          {photoCategoryNodes}
          {!readOnly && !dvRequiredPhotosComplete(photos, f) ? (
            <div className="tc-section xll-dv-photo-reshoot-bar">
              <div style={{ padding: '14px' }}>
                <button type="button" className="xll-mod-btn-ghost" style={{ width: '100%', minHeight: 40, borderRadius: 10 }} onClick={startPhotoCapture}>
                  继续连拍
                </button>
              </div>
            </div>
          ) : null}

          <div className="tc-section xll-dv-authorized-section">
            {renderSectionHead(6, '选择被授权人')}
            <div className="xll-dv-authorized-panel">
              {renderAuthorizedPersonBlock()}
              {isSignPendingView && f.signSent ? (
                <div className="xll-dv-sign-pending-foot">
                  <div className="xll-dv-sign-pending-hint">签章文件已发送，等待被授权人通过短信完成 E签宝签章</div>
                </div>
              ) : null}
              {isResignPendingView ? (
                <div className="xll-dv-sign-pending-foot">
                  <div className="xll-dv-sign-pending-hint">签章已清除，交车信息已锁定；请重新选择被授权人后发起签章</div>
                </div>
              ) : null}
            </div>
          </div>

          {isHistoryView ? (
          <div className="tc-section">
              {renderSectionHead(7, 'E签宝签章文件')}
              <div className="tc-section-form" style={{ padding: '0 14px 14px' }}>
                <div className="xll-mod-form-row">
                  <span className="xll-mod-form-label">客户完成签章时间</span>
                  <span className="xll-mod-form-value">{dvDisplayMinuteTime(r.customerSignTime)}</span>
              </div>
                <div style={{ display: 'flex', gap: 10, marginTop: 10 }}>
                  <button
                    type="button"
                    className="xll-mod-btn-ghost"
                    style={{ flex: 1, minHeight: 40, borderRadius: 10 }}
                    onClick={handlePreviewSignFile}
                  >
                    预览
                  </button>
                  <button
                    type="button"
                    className="xll-mod-btn-primary"
                    style={{ flex: 1, minHeight: 40, borderRadius: 10 }}
                    onClick={handleDownloadSignFile}
                  >
                    下载
                  </button>
            </div>
                {isHistoryView && r.vehicleReturned != null ? (
                  <>
                    <div className="xll-mod-form-row" style={{ marginTop: 10 }}>
                      <span className="xll-mod-form-label">是否归还</span>
                      <span className="xll-mod-form-value">{r.vehicleReturned ? '已归还' : '未归还'}</span>
          </div>
                    {r.vehicleReturned && r.vehicleReturnTime ? (
                      <div className="xll-mod-form-row">
                        <span className="xll-mod-form-label">归还时间</span>
                        <span className="xll-mod-form-value">{dvDisplayMinuteTime(r.vehicleReturnTime)}</span>
                      </div>
                    ) : null}
                  </>
                ) : null}
              </div>
            </div>
          ) : null}
        <div style={{ height: 16 }} />
      </>
    );
    };

    if (deliveryFormStep === 'inspection') {
      return (
        <>
          {readOnly ? renderDeliveryHero() : null}
          {renderDeliveryInspection(f)}
          <div style={{ height: 16 }} />
        </>
      );
    }
    if (deliveryFormStep === 'photos') {
      return (
        <>
          {readOnly ? renderDeliveryHero() : null}
          {renderPhotosStep()}
        </>
      );
    }
    return renderVehicleStep();
  };

  const renderDeliveryActionBar = () => {
    if (deliverySignFlow) return null;

    if (isSignPendingView) {
      return (
        <div className="xll-mod-action-bar">
          <button type="button" className="xll-mod-btn-ghost" onClick={closeForm}>返回</button>
          <button type="button" className="xll-mod-btn-primary" onClick={handleClearSign}>清除签章</button>
        </div>
      );
    }

    if (isResignPendingView) {
      return (
        <div className="xll-mod-action-bar">
          <button type="button" className="xll-mod-btn-ghost" onClick={closeForm}>返回</button>
          <button type="button" className="xll-mod-btn-primary" onClick={handleSendSignDoc}>重新发起签章</button>
        </div>
      );
    }

    if (isHistoryView) {
      return (
        <div className="xll-mod-action-bar">
          <button type="button" className="xll-mod-btn-ghost" onClick={closeForm}>返回</button>
          <button type="button" className="xll-mod-btn-primary" onClick={handleDownloadSignFile}>下载签章文件</button>
        </div>
      );
    }

    if (readOnly) return null;

    if (deliveryFormStep === 'vehicle') {
      return (
        <div className="xll-mod-action-bar">
          <button type="button" className="xll-mod-btn-ghost" onClick={closeForm}>返回</button>
          <button type="button" className="xll-mod-btn-ghost" onClick={handleSave}>保存</button>
          <button type="button" className="xll-mod-btn-primary" onClick={handleVehicleNext}>下一步</button>
        </div>
      );
    }

    if (deliveryFormStep === 'inspection') {
      return (
        <div className="xll-mod-action-bar">
          <button type="button" className="xll-mod-btn-ghost" onClick={closeForm}>返回</button>
          <button type="button" className="xll-mod-btn-ghost" onClick={handleSave}>保存</button>
          <button type="button" className="xll-mod-btn-primary" onClick={handleInspectionNext}>下一步</button>
        </div>
      );
    }

    if (deliveryFormStep === 'photos') {
      if (photoCapturePhase === 'camera') return null;
      if (photoCapturePhase === 'ready') {
        return (
          <div className="xll-mod-action-bar xll-dv-photo-action-bar">
            <button type="button" className="xll-mod-btn-ghost" onClick={closeForm}>返回</button>
            <button type="button" className="xll-mod-btn-primary xll-dv-photo-action-btn" onClick={startPhotoCapture}>开始拍摄</button>
          </div>
        );
      }
      if (photoCapturePhase === 'countdown') {
        return (
          <div className="xll-mod-action-bar xll-dv-photo-action-bar">
            <button type="button" className="xll-mod-btn-ghost" onClick={closeForm}>返回</button>
            <button type="button" className="xll-mod-btn-primary xll-dv-photo-action-btn" onClick={capturePhotoNow}>立即拍摄</button>
          </div>
        );
      }
      return (
        <div className="xll-mod-action-bar">
          <button type="button" className="xll-mod-btn-ghost" onClick={closeForm}>返回</button>
          <button type="button" className="xll-mod-btn-ghost" onClick={handleSave}>保存</button>
          <button type="button" className="xll-mod-btn-primary" onClick={handleSendSignDoc}>发送签章文件</button>
        </div>
      );
    }

    return null;
  };

  const activeTabLabel = DV_LIST_TABS.find((t) => t.key === listFilter)?.label || '';

  const renderParkingSheetOverlay = () => {
    if (!parkingSheetOpen) return null;
    return (
      <div className="xll-mod-sheet-overlay" role="dialog" aria-modal="true" aria-label="选择停车场备车库">
        <button type="button" className="xll-mod-sheet-mask" onClick={() => setParkingSheetOpen(false)} aria-label="关闭" />
        <div className="xll-mod-sheet-panel">
          <div className="xll-mod-sheet-handle" aria-hidden />
          <div className="xll-mod-sheet-header">
            <div className="xll-mod-sheet-title">选择停车场备车库</div>
            <button type="button" className="xll-mod-sheet-close" onClick={() => setParkingSheetOpen(false)} aria-label="关闭">×</button>
          </div>
          <div className="xll-mod-sheet-body">
            {DV_OPERATOR_PARKING_LOTS.map((lot) => {
              const active = vehiclePickParking === lot.key;
              return (
                <button
                  key={lot.key}
                  type="button"
                  className={`xll-mod-sheet-option${active ? ' active' : ''}`}
                  onClick={() => { setVehiclePickParking(lot.key); setParkingSheetOpen(false); }}
                >
                  <span className="xll-mod-sheet-option-text">
                    <span className="xll-mod-sheet-option-main">{lot.label}</span>
                    {lot.key === 'all' ? <span className="xll-mod-sheet-option-sub">权限下全部备车库</span> : null}
                  </span>
                  {active ? <span className="xll-mod-sheet-option-check" aria-hidden>✓</span> : null}
                </button>
              );
            })}
            </div>
          </div>
          </div>
    );
  };

  const renderVehiclePickPage = () => (
    <div className="xll-mod-root xll-dv-module" style={{ display: 'flex', flexDirection: 'column', height: '100%', minHeight: 0 }}>
      <div className="xll-dv-pick-toolbar">
        <div className="xll-mod-search">
          <IconSearch />
          <input
            type="search"
            placeholder="输入车牌号搜索"
            value={vehiclePickSearch}
            onChange={(e) => setVehiclePickSearch(e.target.value)}
            aria-label="输入车牌号搜索"
          />
        </div>
        <button
          type="button"
          className="xll-dv-pick-parking"
          onClick={() => setParkingSheetOpen(true)}
          aria-label="切换停车场备车库"
        >
          <span className="xll-dv-pick-parking-label">停车场</span>
          <strong>{vehiclePickParkingLabel}</strong>
          <span className="xll-dv-pick-parking-arrow">切换 ›</span>
        </button>
      </div>
      <div className="xll-mod-list-head" style={{ padding: '0 14px' }}>
        <span>备车库车辆</span>
        <span>共 {vehiclePickList.length} 辆</span>
      </div>
      <div className="xll-dv-pick-list">
        {vehiclePickList.length === 0 ? (
          <div className="xll-mod-empty">暂无匹配车辆<br />试试切换停车场或清空搜索</div>
        ) : (
          vehiclePickList.map((v) => {
            const meta = dvGetReadinessMeta(v);
            return (
              <div
                key={v.plateNo}
                className={`xll-dv-pick-card${meta.blocked ? ' blocked' : ''}`}
                role={meta.canPick ? 'button' : undefined}
                tabIndex={meta.canPick ? 0 : undefined}
                onClick={() => meta.canPick && handlePlatePick(v)}
                onKeyDown={(e) => e.key === 'Enter' && meta.canPick && handlePlatePick(v)}
              >
                <div
                  className="xll-mod-card"
                  style={{ '--mod-accent': meta.blocked ? COLOR_DANGER : XLL_GREEN, '--mod-soft': meta.blocked ? 'rgba(244,63,94,.1)' : XLL_GREEN_SOFT, marginBottom: 0 }}
                >
                  <div className="xll-mod-card-head">
                    <span className="xll-mod-ar-plate">{v.plateNo}</span>
                    <span className={`xll-vm-online${v.onlineStatus === '在线' ? '' : ''}`}>
                      <span className={`xll-vm-dot ${v.onlineStatus === '在线' ? 'on' : 'off'}`} />
                      {v.onlineStatus}
                    </span>
            </div>
                  <div className="xll-mod-card-sub">{v.brand} · {v.model}</div>
                  <div className={`xll-dv-readiness-bar ${meta.blocked ? 'blocked' : 'ready'}`}>{meta.label}</div>
                  <div className="xll-mod-meta">
                    <div className="xll-vm-meta-cell"><span className="xll-mod-meta-label">停车场</span><span className="xll-mod-meta-val">{v.parkingLot}</span></div>
                    <div className="xll-vm-meta-cell"><span className="xll-mod-meta-label">运营城市</span><span className="xll-mod-meta-val">{v.region || '—'}</span></div>
                    <div className="xll-vm-meta-cell"><span className="xll-mod-meta-label">车辆状态</span><span className="xll-mod-meta-val">{v.vehicleStatus}</span></div>
                    <div className="xll-vm-meta-cell"><span className="xll-mod-meta-label">保险状态</span><span className="xll-mod-meta-val" style={{ color: v.insuranceStatus === '异常' ? COLOR_DANGER : COLOR_TEXT_SEC }}>{v.insuranceStatus}</span></div>
                    <div className="xll-vm-meta-cell"><span className="xll-mod-meta-label">证照状态</span><span className="xll-mod-meta-val" style={{ color: v.licenseStatus === '异常' ? COLOR_DANGER : COLOR_TEXT_SEC }}>{v.licenseStatus}</span></div>
                    <div className="xll-vm-meta-cell"><span className="xll-mod-meta-label">车辆来源</span><span className="xll-mod-meta-val">{v.vehicleSource || '—'}</span></div>
            </div>
                  <div className="xll-mod-card-foot">
                    <div className="xll-vm-card-vin-wrap">
                      <span className="xll-vm-card-vin-label">车辆识别代码</span>
                      <span className="xll-vm-card-vin" title={v.vin}>{v.vin}</span>
                    </div>
                    {meta.canPick ? (
                      <button type="button" className="xll-mod-card-btn" onClick={(e) => { e.stopPropagation(); handlePlatePick(v); }}>选择</button>
                    ) : (
                      <span style={{ fontSize: 12, color: COLOR_DANGER, fontWeight: 600 }}>不可交车</span>
                    )}
            </div>
                </div>
              </div>
            );
          })
          )}
        </div>
      {renderParkingSheetOverlay()}
      </div>
    );

  const handleApplyDeliveryFilter = useCallback(() => {
    if (filterDraft.dateStart && filterDraft.dateEnd && filterDraft.dateStart > filterDraft.dateEnd) {
      mpToast.warning('开始日期不能晚于结束日期');
      return;
    }
    setStatusFilter(filterDraft.status);
    setMoreFilter({
      customerName: filterDraft.customerName,
      projectName: filterDraft.projectName,
      dateStart: filterDraft.dateStart,
      dateEnd: filterDraft.dateEnd,
    });
    setFilterDrawerOpen(false);
  }, [filterDraft]);

  const renderDeliveryFilterSheet = () => {
    if (!filterDrawerOpen) return null;
    return (
      <div className="xll-mod-sheet-overlay" role="dialog" aria-modal="true" aria-label="筛选交车">
        <button type="button" className="xll-mod-sheet-mask" onClick={() => setFilterDrawerOpen(false)} aria-label="关闭" />
        <div className="xll-mod-sheet-panel xll-mod-sheet-panel--filter">
          <div className="xll-mod-sheet-handle" aria-hidden />
          <div className="xll-mod-sheet-header">
            <div className="xll-mod-sheet-title">筛选交车</div>
            <button type="button" className="xll-mod-sheet-close" onClick={() => setFilterDrawerOpen(false)} aria-label="关闭">×</button>
          </div>
          <div className="xll-mod-sheet-scroll">
            {(listFilter === 'all' || listFilter === 'inProgress') ? (
              <div className="xll-mod-drawer-section">
                <div className="xll-mod-drawer-section-title">交车状态</div>
                <div className="xll-mod-drawer-types">
                  {DV_STATUS_FILTER_OPTIONS.map((st) => (
                    <button
                      key={st || 'all-status'}
                      type="button"
                      className={`xll-mod-drawer-type-btn${filterDraft.status === st ? ' active' : ''}`}
                      onClick={() => setFilterDraft((f) => ({ ...f, status: st }))}
                    >
                      {st || '全部状态'}
                    </button>
                  ))}
                </div>
              </div>
            ) : null}
            <div className="xll-mod-drawer-section">
              <div className="xll-mod-drawer-section-title">客户与项目</div>
              <div className="xll-mod-drawer-form-card">
                <div className="xll-mod-form-row">
                  <span className="xll-mod-form-label">客户名称</span>
                  <input
                    className="xll-mod-form-input"
                    type="search"
                    placeholder="请输入"
                    value={filterDraft.customerName}
                    onChange={(e) => setFilterDraft((f) => ({ ...f, customerName: e.target.value }))}
                    aria-label="客户名称"
                  />
                </div>
                <div className="xll-mod-form-row">
                  <span className="xll-mod-form-label">项目名称</span>
                  <input
                    className="xll-mod-form-input"
                    type="search"
                    placeholder="请输入"
                    value={filterDraft.projectName}
                    onChange={(e) => setFilterDraft((f) => ({ ...f, projectName: e.target.value }))}
                    aria-label="项目名称"
                  />
                </div>
              </div>
            </div>
            <div className="xll-mod-drawer-section" style={{ marginBottom: 0 }}>
              <div className="xll-mod-drawer-section-title">交车日期</div>
              <div className="xll-mod-drawer-form-card">
                <div className="xll-mod-drawer-date-row">
                  <input
                    type="date"
                    aria-label="交车开始日期"
                    value={filterDraft.dateStart}
                    onChange={(e) => setFilterDraft((f) => ({ ...f, dateStart: e.target.value }))}
                  />
                  <span className="xll-mod-drawer-date-sep">至</span>
                  <input
                    type="date"
                    aria-label="交车结束日期"
                    value={filterDraft.dateEnd}
                    onChange={(e) => setFilterDraft((f) => ({ ...f, dateEnd: e.target.value }))}
                  />
                </div>
              </div>
              <div className="xll-mod-drawer-hint">实际交车时间=运维人员完成 E签宝 签字的时间；仅「待客户签章」及之后状态参与日期筛选。</div>
            </div>
          </div>
          <div className="xll-mod-sheet-footer">
            <button type="button" className="xll-mod-btn-ghost" onClick={() => setFilterDraft(DV_EMPTY_FILTER_DRAFT)}>重置</button>
            <button type="button" className="xll-mod-btn-primary" onClick={handleApplyDeliveryFilter}>确定</button>
          </div>
        </div>
      </div>
    );
  };

  if (activeRow && driverManualOpen) {
    return renderDriverManualPage();
  }

  if (activeRow && driverETrainingOpen) {
    return renderDriverETrainingPage();
  }

  if (activeRow && deliverySignFlow === 'success') {
    return renderDeliverySignSuccessPage();
  }

  if (activeRow && vehiclePickOpen) {
    return renderVehiclePickPage();
  }

  if (activeRow && formDraft) {
    const hideStepBar = deliveryFormStep === 'photos' && (photoCapturePhase === 'countdown' || photoCapturePhase === 'camera');
    const photoCaptureLayout = deliveryFormStep === 'photos' && (photoCapturePhase === 'ready' || photoCapturePhase === 'countdown');
    return (
      <div className="xll-mod-root xll-dv-module xll-mod-form-page">
        <div className="xll-mod-detail-wrap">
          {!hideStepBar ? renderDeliveryStepBar() : null}
          <div className={`tc-scroll${photoCaptureLayout ? ' xll-dv-photo-capture-scroll' : ''}`} style={photoCaptureLayout ? { display: 'flex', flexDirection: 'column' } : undefined}>
            {renderDeliveryFormPage()}
          </div>
          {renderDeliveryActionBar()}
          {renderPlateValidateModal()}
          {renderClearSignConfirmOverlay()}
          {renderPhotoSourceSheet()}
          {renderPhotoCameraOverlay()}
          {renderPhotoViewerOverlay()}
          {renderSpareTireCaptureOverlay()}
          {renderDvPhotoCameraOverlay()}
          <input
            ref={dvAlbumInputRef}
            type="file"
            accept={DV_PHOTO_UPLOAD_ACCEPT}
            style={{ display: 'none' }}
            onChange={handleDvAlbumFileChange}
          />
        </div>
      </div>
    );
  }

  return (
    <div className="xll-mod-root xll-dv-module" data-annotation-id="page-delivery">
      <div className="xll-mod-tabs" role="tablist" aria-label="交车列表分类">
        {DV_LIST_TABS.map((tab) => (
          <button
            key={tab.key}
            type="button"
            role="tab"
            aria-selected={listFilter === tab.key}
            className={`xll-mod-tab${listFilter === tab.key ? ' active' : ''}`}
            onClick={() => { setListFilter(tab.key); if (tab.key === 'completed') setStatusFilter(''); }}
          >
            {tab.short}
            <span className="xll-mod-tab-count">
              {tab.key === 'inProgress' ? kpi.inProgress : tab.key === 'completed' ? kpi.completed : kpi.all} 辆
            </span>
          </button>
        ))}
      </div>
      <div className="xll-mod-toolbar">
        <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
          <div className="xll-mod-search" style={{ flex: 1 }}>
            <IconSearch />
            <input
              type="search"
              placeholder="搜索车牌、项目、客户"
              value={searchKey}
              onChange={(e) => setSearchKey(e.target.value)}
              aria-label="搜索交车任务"
            />
          </div>
          <button
            type="button"
            className={`xll-vm-filter-btn${activeFilterCount ? ' active' : ''}`}
            onClick={() => {
              setFilterDraft({
                status: statusFilter,
                customerName: moreFilter.customerName,
                projectName: moreFilter.projectName,
                dateStart: moreFilter.dateStart,
                dateEnd: moreFilter.dateEnd,
              });
              setFilterDrawerOpen(true);
            }}
            aria-label="筛选交车"
          >
            <IconFilter />
          </button>
        </div>
        {(listFilter === 'all' || listFilter === 'inProgress') && (
          <div className="xll-mod-chips" role="group" aria-label="交车状态快捷筛选">
            {DV_STATUS_FILTER_OPTIONS.map((st) => (
              <button
                key={st || 'all-status'}
                type="button"
                className={`xll-mod-chip${statusFilter === st ? ' active' : ''}`}
                onClick={() => setStatusFilter(statusFilter === st ? '' : st)}
              >
                {st || '全部状态'}
              </button>
            ))}
          </div>
        )}
        {activeFilterCount > 0 && (
          <div className="xll-mod-chips" style={{ marginTop: (listFilter === 'all' || listFilter === 'inProgress') ? 0 : 10 }}>
            {statusFilter && (listFilter === 'completed' || !DV_STATUS_FILTER_OPTIONS.includes(statusFilter)) ? (
              <button type="button" className="xll-mod-chip active" onClick={() => setStatusFilter('')}>
                状态：{statusFilter} ×
              </button>
            ) : null}
            {moreFilter.customerName.trim() && (
              <button type="button" className="xll-mod-chip active" onClick={() => setMoreFilter((f) => ({ ...f, customerName: '' }))}>
                客户：{moreFilter.customerName} ×
              </button>
            )}
            {moreFilter.projectName.trim() && (
              <button type="button" className="xll-mod-chip active" onClick={() => setMoreFilter((f) => ({ ...f, projectName: '' }))}>
                项目：{moreFilter.projectName} ×
              </button>
            )}
            {(moreFilter.dateStart || moreFilter.dateEnd) && (
              <button type="button" className="xll-mod-chip active" onClick={() => setMoreFilter((f) => ({ ...f, dateStart: '', dateEnd: '' }))}>
                交车日期：{moreFilter.dateStart || '…'} 至 {moreFilter.dateEnd || '…'} ×
              </button>
            )}
          </div>
        )}
      </div>
      <div className="xll-mod-list-head"><span>{activeTabLabel}</span><span>共 {filteredList.length} 辆</span></div>
      <div className="xll-mod-list">
        {filteredList.length === 0 ? (
          <div className="xll-mod-empty">
            暂无{activeTabLabel}交车任务
            <br />
            {searchKey || statusFilter || activeFilterCount ? '试试清空搜索或切换筛选' : '新的交车任务到达后将在此展示'}
          </div>
        ) : (
          filteredList.map((row) => {
            const st = dvStatusTag(row.deliveryStatus);
            const pendingPlate = !row.plateNo || !String(row.plateNo).trim();
            const inProgress = dvIsInProgressStatus(row.deliveryStatus);
            const isSignPending = row.deliveryStatus === '待客户签章';
            const isResignPending = row.deliveryStatus === '待重新签章';
            const actionLabel = row.deliveryStatus === '未开始'
              ? '去办理'
              : isSignPending
                ? '查看'
                : isResignPending
                  ? '继续办理'
                  : '继续办理';
            return (
              <div
                key={row.id}
                className="xll-mod-card"
                style={{ '--mod-accent': XLL_GREEN, '--mod-soft': XLL_GREEN_SOFT }}
                role="button"
                tabIndex={0}
                onClick={() => openRow(row)}
                onKeyDown={(e) => e.key === 'Enter' && openRow(row)}
              >
                <div className="xll-mod-card-head">
                  <span className="xll-dv-plate-row">
                    <span className={`xll-mod-ar-plate${pendingPlate ? ' xll-dv-plate-pending' : ''}`}>{dvDisplayPlate(row.plateNo)}</span>
                    {dvIsReplaceDeliveryTask(row) ? <span className="xll-dv-replace-tag">替换车交车</span> : null}
                  </span>
                  <span className={`xll-mod-card-status ${dvCardStatusClass(row.deliveryStatus)}`}>{st.text}</span>
                </div>
                <div className="xll-mod-card-title" style={{ fontSize: 14, fontWeight: 600 }}>{dvVehicleDesc(row)}</div>
                <div className="xll-mod-card-sub" style={{ fontSize: 13, marginBottom: 2 }}>{row.customerName || '—'}</div>
                <div className="xll-mod-card-sub" style={{ fontSize: 12, color: COLOR_MUTED, marginBottom: 8 }}>{row.projectName || '—'}</div>
                <div className="xll-mod-card-period">
                  <span>计划交车 {dvFormatExpectedDate(row.expectedDate)}</span>
                </div>
                <div className="xll-mod-meta">
                  <div><span className="xll-mod-meta-label">交车区域 </span><span className="xll-mod-meta-val">{row.deliveryRegion || '—'}</span></div>
                  <div><span className="xll-mod-meta-label">交车地点 </span><span className="xll-mod-meta-val">{row.deliveryAddress || '—'}</span></div>
                </div>
                {inProgress ? (
                  <div className="xll-mod-card-foot" style={{ justifyContent: isSignPending ? 'space-between' : 'flex-end' }}>
                    {isSignPending && row.deliveryTime ? (
                      <span style={{ fontSize: 12, color: COLOR_MUTED, lineHeight: 1.45 }}>
                        实际交车 <span style={{ color: '#2563EB', fontWeight: 600 }}>{dvDisplayActualTime(row.deliveryTime)}</span>
                      </span>
                    ) : null}
                    <button type="button" className="xll-mod-card-btn" onClick={(e) => { e.stopPropagation(); openRow(row); }}>
                      {actionLabel}
                    </button>
                  </div>
                ) : null}
              </div>
            );
          })
        )}
      </div>
      {renderDeliveryFilterSheet()}
    </div>
  );
};

/* ── 替换车（参照 web端/替换车管理-新增.jsx） ── */
const VR_ACCENT = '#F43F5E';
const VR_SOFT = 'rgba(244, 63, 94, 0.12)';

const VR_ACTIVE_CONTRACTS = [
  { contractId: 'c1', projectName: '嘉兴氢能示范项目', projectType: '租赁', contractCode: 'HT-ZL-2025-001', customerName: '嘉兴某某物流有限公司', deliveryRegion: '浙江省-嘉兴市' },
  { contractId: 'c2', projectName: '上海物流租赁项目', projectType: '租赁', contractCode: 'HT-ZL-2025-002', customerName: '上海某某运输公司', deliveryRegion: '上海市-上海市' },
  { contractId: 'c3', projectName: '杭州城配自营项目', projectType: '自营', contractCode: 'HT-ZY-2025-003', customerName: '杭州某某租赁有限公司', deliveryRegion: '浙江省-杭州市' },
];

const VR_DELIVERED_VEHICLES = [
  { plateNo: '浙A12345', brand: '东风', model: 'DFH1180', contractId: 'c1' },
  { plateNo: '浙A55555', brand: '重汽', model: 'ZZ1160', contractId: 'c1' },
  { plateNo: '沪B11111', brand: '江淮', model: 'HFC1180', contractId: 'c2' },
  { plateNo: '浙C33333', brand: '东风', model: 'DFH1190', contractId: 'c3' },
];

const VR_PREPARED_BY_REGION = {
  '浙江省-嘉兴市': [
    { plateNo: '浙A67890', brand: '福田', model: 'BJ1180' },
    { plateNo: '浙A66666', brand: '江淮', model: 'HFC1190' },
    { plateNo: '浙F88888', brand: '东风', model: 'DFH1180' },
  ],
  '上海市-上海市': [
    { plateNo: '沪B22222', brand: '重汽', model: 'ZZ1180' },
    { plateNo: '沪B33333', brand: '福田', model: 'BJ1190' },
  ],
  '浙江省-杭州市': [
    { plateNo: '浙C44444', brand: '东风', model: 'DFH1180' },
  ],
};

const VR_CONTRACT_MAP = VR_ACTIVE_CONTRACTS.reduce((m, c) => { m[c.contractId] = c; return m; }, {});

const VR_MOCK_APPLICATIONS = [
  {
    id: 'vr-o1', bizNo: 'TH-2026-0315', replaceDate: '2026-03-15', approvalStatus: '审批中', currentApprover: '张明辉',
    projectName: '嘉兴氢能示范项目', customerName: '嘉兴某某物流有限公司', deliveryRegion: '浙江省-嘉兴市',
    pairs: [{ originalPlate: '浙A12345', replacePlate: '浙A67890', replaceType: '永久替换' }],
    creator: '王东东', createTime: '2026-03-14 10:00',
  },
  {
    id: 'vr-o2', bizNo: 'TH-2026-0310', replaceDate: '2026-03-10', approvalStatus: '未提交', currentApprover: '—',
    projectName: '上海物流租赁项目', customerName: '上海某某运输公司', deliveryRegion: '上海市-上海市',
    pairs: [{ originalPlate: '沪B11111', replacePlate: '沪B22222', replaceType: '临时替换' }],
    creator: '李四', createTime: '2026-03-09 15:30',
  },
  {
    id: 'vr-h1', bizNo: 'TH-2026-0218', replaceDate: '2026-02-18', approvalStatus: '审批完成', currentApprover: '—',
    projectName: '嘉兴氢能示范项目', customerName: '嘉兴某某物流有限公司', deliveryRegion: '浙江省-嘉兴市',
    pairs: [
      { originalPlate: '浙A10001', replacePlate: '浙A10002', replaceType: '永久替换' },
      { originalPlate: '浙A55555', replacePlate: '浙A66666', replaceType: '临时替换' },
    ],
    creator: '王东东', createTime: '2026-02-17 09:20',
  },
];

const VR_LIST_TABS = [
  { key: 'ongoing', label: '进行中', short: '进行中' },
  { key: 'history', label: '历史记录', short: '历史' },
];

const VR_STATUS_CHIPS = ['', '未提交', '待审批', '审批中', '审批驳回'];

const vrIsOngoing = (status) => ['待审批', '审批中', '审批驳回', '未提交', '撤回'].includes(status);
const vrIsHistory = (status) => status === '审批完成';

const vrBizStatusClass = (status) => {
  if (status === '审批完成') return 'ok';
  if (status === '审批驳回' || status === '撤回') return 'reject';
  return 'pending';
};

const vrBizStatusLabel = (row) => {
  const status = row.approvalStatus;
  if ((status === '审批中' || status === '待审批') && row.currentApprover && row.currentApprover !== '—') {
    return `${status}：${row.currentApprover}`;
  }
  return status || '—';
};

const vrRowToTask = (row) => {
  const contract = VR_ACTIVE_CONTRACTS.find((c) => c.projectName === row.projectName) || {};
  return {
  flowType: '替换车申请',
  bizNo: row.bizNo,
  customerName: row.customerName,
  projectName: row.projectName,
  projectType: row.projectType || contract.projectType,
  contractCode: row.contractCode || contract.contractCode,
  deliveryRegion: row.deliveryRegion || contract.deliveryRegion,
  pairs: row.pairs,
  vehicleCount: (row.pairs || []).length,
  initiator: row.creator,
  initiateTime: row.createTime,
  status: row.approvalStatus,
  currentAssignee: row.currentApprover,
  approvers: row.currentApprover && row.currentApprover !== '—' ? [row.currentApprover] : [],
  approvalSteps: row.approvalSteps,
};
};

let vrPairIdSeed = 1;
const vrCreateEmptyPair = () => {
  vrPairIdSeed += 1;
  return {
    id: `pair_${vrPairIdSeed}`,
    replaceType: '',
    replaceReason: '',
    replaceFee: '',
    replaceReasonDesc: '',
    originalPlate: '',
    originalBrand: '',
    originalModel: '',
    contractId: '',
    replacePlate: '',
    replaceBrand: '',
    replaceModel: '',
  };
};

/** 三方退车（采购退租申请审批完成后按车生成任务） */
const TRI_RETURN_TASKS_KEY = 'oneos_tri_return_tasks_v1';

const TRI_FORM_STEPS = [
  { key: 'fault', label: '故障修复' },
  { key: 'inspection', label: '退车检查' },
  { key: 'photos', label: '车辆照片' },
];

const TRI_LICENSE_PHOTO_ITEMS = [
  { key: 'license_main', label: '行驶证主页' },
  { key: 'license_sub', label: '行驶证副页' },
  { key: 'operating_permit', label: '营运证' },
  { key: 'h2_permit', label: '加氢证' },
];

const renderDvSectionHead = (num, title, extra, required, readOnly) => (
  <div className="tc-section-head">
    <span className="xll-dv-section-head-title">
      <span className="xll-dv-section-badge">{num}</span>
      {title}
      {required && !readOnly ? <span className="xll-dv-required-tag">必填</span> : null}
    </span>
    {extra || null}
  </div>
);

const renderDvInspectionPanel = (f, { readOnly, onUpdateRow, title }) => {
  const list = f.inspectionList || [];
  const categories = [...new Set(list.map((row) => row.category))];
  return (
    <div className="tc-section">
      {renderDvSectionHead(1, title, null, false, readOnly)}
      {categories.map((cat) => (
        <div key={cat} className="xll-dv-inspection-block">
          <div className="xll-dv-inspection-cat">{cat}</div>
          {list.filter((row) => row.category === cat).map((row) => (
            <div key={row.key} className="xll-dv-inspection-row">
              <span className="xll-dv-inspection-item">{row.item}</span>
              <div className="xll-dv-inspection-controls">
                {dvInspectionIsTireCategory(row.category) ? (
                  readOnly ? (
                    <span className="xll-dv-inspection-status">{row.treadDepth ? `${row.treadDepth} mm` : '—'}</span>
                  ) : (
                    <input
                      className="xll-dv-inspection-tread"
                      type="text"
                      inputMode="decimal"
                      value={row.treadDepth}
                      onChange={(e) => {
                        const next = dvMetricInputChange(e.target.value);
                        if (next !== null) onUpdateRow(row.key, { treadDepth: next });
                      }}
                      placeholder="mm"
                      aria-label={`${row.item}胎纹深度`}
                    />
                  )
                ) : readOnly ? (
                  <span className={`xll-dv-inspection-status${row.checked ? '' : ' off'}`}>{row.checked ? '正常' : '异常'}</span>
                ) : (
                  <button
                    type="button"
                    className={`xll-dv-inspection-switch${row.checked ? ' on' : ''}`}
                    onClick={() => onUpdateRow(row.key, { checked: !row.checked })}
                    aria-label={`${row.item}检查情况`}
                    aria-pressed={row.checked}
                  />
                )}
                {readOnly ? (
                  row.remark ? <span className="xll-dv-inspection-remark-read">{row.remark}</span> : null
                ) : (
                  <input
                    className="xll-dv-inspection-remark"
                    type="text"
                    value={row.remark || ''}
                    onChange={(e) => onUpdateRow(row.key, { remark: e.target.value })}
                    placeholder="请输入"
                    aria-label={`${row.item}备注`}
                    maxLength={40}
                  />
                )}
              </div>
            </div>
          ))}
        </div>
      ))}
    </div>
  );
};

const triBuildExecution = (formDraft) => ({
  hasFault: formDraft.hasFault,
  faultDescription: formDraft.faultDescription || '',
  returnMileage: formDraft.returnMileage,
  returnElectric: formDraft.returnElectric,
  returnHydrogen: formDraft.returnHydrogen,
  returnH2Unit: formDraft.returnH2Unit,
  returnRemark: formDraft.returnRemark || '',
  inspectionList: formDraft.inspectionList,
  returnPhotos: formDraft.returnPhotos,
  licensePhotos: formDraft.licensePhotos,
  lessorContactName: formDraft.lessorContactName || '',
  lessorContactPhone: formDraft.lessorContactPhone || '',
});

const triResetPhotoCaptureState = (setters, readOnlyView) => {
  setters.setPhotoCapturePhase(readOnlyView ? 'done' : 'ready');
  setters.setPhotoCaptureMode('continuous');
  setters.setPhotoCaptureIndex(0);
  setters.setPhotoCountdown(3);
  setters.setPhotoCameraDraft({ photoUrl: '', treadDepth: '', treadOcrOk: false, captured: false });
};

const triLoadTasks = () => {
  try {
    const raw = localStorage.getItem(TRI_RETURN_TASKS_KEY);
    return raw ? JSON.parse(raw) : [];
  } catch { return []; }
};

const triSaveTasks = (tasks) => {
  try { localStorage.setItem(TRI_RETURN_TASKS_KEY, JSON.stringify(tasks || [])); } catch { /* noop */ }
};

const triIsInProgress = (st) => st === '待办理' || st === '已保存';
const triIsCompleted = (st) => st === '已完成';

const triTaskRegionText = (task) => task.deliveryRegionText || task.returnRegion || '—';
const triTaskLocationText = (task) => task.returnLocation || task.deliveryAddress || task.deliveryRegionText || '—';
const triVehicleDesc = (task) => {
  const brand = (task.brand || '').trim();
  const model = (task.model || '').trim();
  if (brand && model) return `${brand} · ${model}`;
  return brand || model || task.plateNo || '—';
};
const triStatusHeroLabel = (st) => {
  if (st === '已完成') return '已完成';
  if (st === '已保存') return '已保存';
  if (st === '待办理') return '未开始';
  return st || '—';
};

const renderTriReturnHeroCard = (task, opts = {}) => {
  const { footExtra = null } = opts;
  const customerName = task.lesseeName || '—';
  return (
    <div className="tc-hero tc-hero--delivery">
      <div className="tc-hero-amount" style={{ fontSize: 22, fontVariantNumeric: 'tabular-nums' }}>{task.plateNo || '—'}</div>
      <div className="tc-hero-meta"><span>{triVehicleDesc(task)}</span></div>
      <div className="tc-hero-foot tc-hero-foot--compact">
        <div className="tc-hero-foot-row"><span className="tc-hero-foot-label">预计退租</span>{dvFormatExpectedDate(task.expectedReturnDate)}</div>
        <div className="tc-hero-foot-row"><span className="tc-hero-foot-label">退车区域</span>{triTaskRegionText(task)}</div>
        {triTaskLocationText(task) !== '—' ? (
          <div className="tc-hero-foot-row"><span className="tc-hero-foot-label">退车地点</span>{triTaskLocationText(task)}</div>
        ) : null}
        {footExtra}
      </div>
      <div className="tc-hero-period">
        <span className="tc-hero-period-tag">三方退租</span>
        <span className="tc-hero-period-tag">{triStatusHeroLabel(task.taskStatus)}</span>
        {customerName !== '—' ? <span>{customerName}</span> : null}
      </div>
    </div>
  );
};

const triBuildFormFromTask = (task) => ({
  hasFault: task?.execution?.hasFault ?? null,
  faultDescription: task?.execution?.faultDescription ?? '',
  returnMileage: task?.execution?.returnMileage ?? '',
  returnElectric: task?.execution?.returnElectric ?? '',
  returnHydrogen: task?.execution?.returnHydrogen ?? '',
  returnH2Unit: task?.execution?.returnH2Unit || dvGetModelGaugeUnit(task?.brand, task?.model),
  inspectionList: (task?.execution?.inspectionList && task.execution.inspectionList.length)
    ? task.execution.inspectionList.map((r) => ({ ...r }))
    : dvBuildInspectionList(),
  returnPhotos: task?.execution?.returnPhotos ? { ...task.execution.returnPhotos } : {},
  licensePhotos: task?.execution?.licensePhotos ? { ...task.execution.licensePhotos } : {},
  lessorContactName: task?.execution?.lessorContactName ?? '',
  lessorContactPhone: task?.execution?.lessorContactPhone ?? '',
  returnRemark: task?.execution?.returnRemark ?? '',
});

const triValidateFaultStep = (form) => {
  if (form.hasFault !== true && form.hasFault !== false) {
    return { ok: false, message: '请选择是否有故障' };
  }
  if (form.hasFault === true && !String(form.faultDescription || '').trim()) {
    return { ok: false, message: '请填写故障描述' };
  }
  if (form.returnMileage === '' || form.returnHydrogen === '' || form.returnElectric === '') {
    return { ok: false, message: '请填写退车里程、氢量与电量' };
  }
  return { ok: true };
};

const triValidateInspectionStep = (form) => dvValidateInspectionStep(form);

const triValidatePhotosStep = (form) => {
  const requiredPhotos = dvGetCaptureSequence(form).filter((item) => dvIsPhotoItemRequired(item));
  const missingPhoto = requiredPhotos.find((item) => !dvPhotoCaptured(form.returnPhotos, item.key));
  if (missingPhoto) {
    return { ok: false, message: `请先完成车辆照片连续拍摄（缺少：${missingPhoto.label}）` };
  }
  const missingLicense = TRI_LICENSE_PHOTO_ITEMS.filter((item) => !dvPhotoCaptured(form.licensePhotos, item.key));
  if (missingLicense.length) return { ok: false, message: '请上传车辆证件照片' };
  if (!String(form.lessorContactName || '').trim()) {
    return { ok: false, message: '请填写出租方联系人' };
  }
  if (!String(form.lessorContactPhone || '').trim()) {
    return { ok: false, message: '请填写出租方联系电话' };
  }
  return { ok: true };
};

const ThirdReturnModule = ({ onRegisterBack }) => {
  const [tasks, setTasks] = useState(() => triLoadTasks());
  const [listFilter, setListFilter] = useState('inProgress');
  const [searchKey, setSearchKey] = useState('');
  const [activeTask, setActiveTask] = useState(null);
  const [formDraft, setFormDraft] = useState(null);
  const [formStep, setFormStep] = useState('fault');
  const [photoCapturePhase, setPhotoCapturePhase] = useState('ready');
  const [photoCaptureIndex, setPhotoCaptureIndex] = useState(0);
  const [photoCountdown, setPhotoCountdown] = useState(3);
  const [photoCameraDraft, setPhotoCameraDraft] = useState({ photoUrl: '', treadDepth: '', treadOcrOk: false, captured: false });
  const [photoCaptureMode, setPhotoCaptureMode] = useState('continuous');
  const [photoViewer, setPhotoViewer] = useState(null);
  const [triPhotoCamera, setTriPhotoCamera] = useState(null);
  const [dvCameraZoom, setDvCameraZoom] = useState(1);
  const [dvCameraFocus, setDvCameraFocus] = useState(null);
  const triFileInputRef = useRef(null);
  const triUploadTargetRef = useRef({ bucket: 'vehicle', key: '' });
  const photoTreadOcrIdxRef = useRef(0);
  const photoViewerTouchRef = useRef({ x: 0, y: 0 });

  const resetTriCameraAssist = useCallback(() => {
    setDvCameraFocus(null);
    setDvCameraZoom(1);
  }, []);

  const resetTriPhotoCapture = useCallback((readOnlyView = false) => {
    triResetPhotoCaptureState({
      setPhotoCapturePhase,
      setPhotoCaptureMode,
      setPhotoCaptureIndex,
      setPhotoCountdown,
      setPhotoCameraDraft,
    }, readOnlyView);
  }, []);

  useEffect(() => {
    triSaveTasks(tasks);
  }, [tasks]);

  const kpi = useMemo(() => ({
    all: tasks.length,
    inProgress: tasks.filter((t) => triIsInProgress(t.taskStatus)).length,
    completed: tasks.filter((t) => triIsCompleted(t.taskStatus)).length,
  }), [tasks]);

  const filteredList = useMemo(() => {
    const q = searchKey.trim().toUpperCase();
    let list = tasks.slice();
    if (listFilter === 'inProgress') list = list.filter((t) => triIsInProgress(t.taskStatus));
    if (listFilter === 'completed') list = list.filter((t) => triIsCompleted(t.taskStatus));
    if (q) {
      list = list.filter((t) => {
        const plate = (t.plateNo || '').toUpperCase();
        const biz = (t.applicationBizNo || '').toUpperCase();
        return plate.includes(q) || biz.includes(q);
      });
    }
    return list;
  }, [tasks, listFilter, searchKey]);

  const readOnly = !!(activeTask && triIsCompleted(activeTask.taskStatus));

  const openTask = useCallback((task) => {
    const viewOnly = task.taskStatus === '已完成';
    setActiveTask(task);
    setFormDraft(triBuildFormFromTask(task));
    setFormStep('fault');
    resetTriPhotoCapture(viewOnly);
    setPhotoViewer(null);
    setTriPhotoCamera(null);
    resetTriCameraAssist();
  }, [resetTriPhotoCapture, resetTriCameraAssist]);

  const closeForm = useCallback(() => {
    setActiveTask(null);
    setFormDraft(null);
    setFormStep('fault');
    resetTriPhotoCapture(false);
    setPhotoViewer(null);
    setTriPhotoCamera(null);
    resetTriCameraAssist();
  }, [resetTriPhotoCapture, resetTriCameraAssist]);

  const patchTask = useCallback((taskId, patch) => {
    setTasks((prev) => prev.map((t) => (t.id === taskId ? { ...t, ...patch } : t)));
  }, []);

  const triPersistDraft = useCallback((silent = true) => {
    if (!activeTask || !formDraft) return false;
    patchTask(activeTask.id, {
      taskStatus: activeTask.taskStatus === '已完成' ? '已完成' : '已保存',
      execution: triBuildExecution(formDraft),
      returnPerson: '张明辉',
    });
    if (!silent) mpToast.success('已保存');
    return true;
  }, [activeTask, formDraft, patchTask]);

  const handleSave = useCallback(() => {
    triPersistDraft(false);
  }, [triPersistDraft]);

  const handleSign = useCallback(() => {
    if (!activeTask || !formDraft) return;
    const checks = [triValidateFaultStep, triValidateInspectionStep, triValidatePhotosStep];
    for (const fn of checks) {
      const r = fn(formDraft);
      if (!r.ok) { mpToast.warning(r.message); return; }
    }
    const now = new Date().toLocaleString('zh-CN', { hour12: false }).slice(0, 16);
    patchTask(activeTask.id, {
      taskStatus: '已完成',
      execution: triBuildExecution(formDraft),
      returnPerson: '张明辉',
      completedTime: now,
    });
    mpToast.success(`签章已提交，E签宝 链接已发送至 ${formDraft.lessorContactPhone}（原型）`);
    closeForm();
  }, [activeTask, formDraft, patchTask, closeForm]);

  const triInitPhotoCaptureOnEnter = useCallback((photos, draft, readOnlyView = false) => {
    const seq = dvGetCaptureSequence(draft);
    const nextIdx = dvGetNextCaptureIndex(photos, draft);
    const allDone = nextIdx >= seq.length;
    const capturedCount = dvCountCapturedPhotos(photos, draft);
    setPhotoCaptureIndex(allDone ? 0 : nextIdx);
    setPhotoCountdown(3);
    setPhotoCameraDraft({ photoUrl: '', treadDepth: '', treadOcrOk: false, captured: false });
    if (allDone || readOnlyView) {
      setPhotoCaptureMode('continuous');
      setPhotoCapturePhase('done');
      return;
    }
    setPhotoCaptureMode('continuous');
    if (capturedCount > 0) {
      setPhotoCapturePhase('countdown');
      return;
    }
    setPhotoCapturePhase('ready');
  }, []);

  const handleFaultNext = useCallback(() => {
    const r = triValidateFaultStep(formDraft);
    if (!r.ok) { mpToast.warning(r.message); return; }
    triPersistDraft(true);
    setFormStep('inspection');
  }, [formDraft, triPersistDraft]);

  const handleInspectionNext = useCallback(() => {
    const r = triValidateInspectionStep(formDraft);
    if (!r.ok) { mpToast.warning(r.message); return; }
    triPersistDraft(true);
    triInitPhotoCaptureOnEnter(formDraft?.returnPhotos || {}, formDraft, readOnly);
    setFormStep('photos');
  }, [formDraft, triPersistDraft, triInitPhotoCaptureOnEnter, readOnly]);

  const triUpdateInspectionRow = useCallback((key, patch) => {
    setFormDraft((d) => ({
      ...d,
      inspectionList: (d.inspectionList || []).map((r) => (r.key === key ? { ...r, ...patch } : r)),
    }));
  }, []);

  const triGetCurrentCaptureItem = useCallback(() => {
    const seq = dvGetCaptureSequence(formDraft);
    return seq[photoCaptureIndex] || null;
  }, [formDraft, photoCaptureIndex]);

  const triSimulateCameraShutter = useCallback(() => {
    const item = triGetCurrentCaptureItem();
    if (!item) return;
    let tread = '';
    let treadOcrOk = true;
    if (item.tread) {
      const ocrIdx = photoTreadOcrIdxRef.current % DV_INSPECTION_TIRE_TREAD_DEMO.length;
      tread = DV_INSPECTION_TIRE_TREAD_DEMO[ocrIdx];
      photoTreadOcrIdxRef.current += 1;
      treadOcrOk = !!String(tread || '').trim();
    }
    setPhotoCameraDraft({
      photoUrl: dvGetPhotoDemoUrl(item.key),
      treadDepth: tread,
      treadOcrOk,
      captured: true,
    });
    if (item.tread) {
      if (treadOcrOk) mpToast.success(`已识别胎纹深度 ${tread} mm，可编辑后点击下一张`);
      else mpToast.warning('胎纹识别失败，请重新拍摄');
    } else {
      mpToast.success(`已拍摄「${item.label}」（原型）`);
    }
  }, [triGetCurrentCaptureItem]);

  const triEnterCameraPhase = useCallback(() => {
    resetTriCameraAssist();
    setPhotoCameraDraft({ photoUrl: '', treadDepth: '', treadOcrOk: false, captured: false });
    setPhotoCapturePhase('camera');
  }, [resetTriCameraAssist]);

  const triAdvancePhotoCapture = useCallback(() => {
    const seq = dvGetCaptureSequence(formDraft);
    const item = seq[photoCaptureIndex];
    if (!item || !activeTask) return;
    const upload = dvSimulatePhotoUpload(item.key, photoCameraDraft, formDraft, activeTask);
    setFormDraft((d) => ({
      ...d,
      returnPhotos: {
        ...(d.returnPhotos || {}),
        [item.key]: {
          captured: true,
          photoUrl: upload.photoUrl,
          treadDepth: item.tread ? String(photoCameraDraft.treadDepth || '').trim() : '',
          uploaded: true,
          watermarkTime: upload.watermarkTime,
          watermarkAddress: upload.watermarkAddress,
        },
      },
    }));
    if (photoCaptureMode === 'single') {
      setPhotoCapturePhase('done');
      setPhotoCameraDraft({ photoUrl: '', treadDepth: '', treadOcrOk: false, captured: false });
      return;
    }
    const nextIdx = photoCaptureIndex + 1;
    if (nextIdx >= seq.length) {
      setPhotoCapturePhase('done');
      setPhotoCameraDraft({ photoUrl: '', treadDepth: '', treadOcrOk: false, captured: false });
    } else {
      setPhotoCaptureIndex(nextIdx);
      setPhotoCountdown(3);
      setPhotoCapturePhase('countdown');
      setPhotoCameraDraft({ photoUrl: '', treadDepth: '', treadOcrOk: false, captured: false });
    }
  }, [formDraft, photoCaptureIndex, photoCameraDraft, photoCaptureMode, activeTask]);

  const triStartPhotoCaptureAtKey = useCallback((photoKey) => {
    const seq = dvGetCaptureSequence(formDraft);
    const idx = seq.findIndex((item) => item.key === photoKey);
    if (idx < 0) return;
    setPhotoCaptureMode('single');
    setPhotoCaptureIndex(idx);
    setPhotoCountdown(3);
    setPhotoCameraDraft({ photoUrl: '', treadDepth: '', treadOcrOk: false, captured: false });
    setPhotoCapturePhase('countdown');
  }, [formDraft]);

  const triStartPhotoCapture = useCallback(() => {
    const photos = formDraft?.returnPhotos || {};
    const seq = dvGetCaptureSequence(formDraft);
    const nextIdx = dvGetNextCaptureIndex(photos, formDraft);
    if (nextIdx >= seq.length) return;
    setPhotoCaptureMode('continuous');
    setPhotoCaptureIndex(nextIdx);
    setPhotoCountdown(3);
    setPhotoCameraDraft({ photoUrl: '', treadDepth: '', treadOcrOk: false, captured: false });
    setPhotoCapturePhase('countdown');
  }, [formDraft]);

  const triConfirmPhotoCameraCapture = useCallback(() => {
    const item = triGetCurrentCaptureItem();
    if (!item) return;
    if (!photoCameraDraft.captured || !photoCameraDraft.photoUrl) {
      mpToast.warning('请先拍照或从相册选择照片');
      return;
    }
    if (item.tread && (!photoCameraDraft.treadOcrOk || !String(photoCameraDraft.treadDepth || '').trim())) {
      mpToast.warning('胎纹未识别成功，请重新拍摄后再继续');
      return;
    }
    triAdvancePhotoCapture();
  }, [triGetCurrentCaptureItem, photoCameraDraft, triAdvancePhotoCapture]);

  useEffect(() => {
    if (photoCapturePhase !== 'countdown' || formStep !== 'photos') return undefined;
    if (photoCountdown <= 0) {
      triEnterCameraPhase();
      return undefined;
    }
    const timer = window.setTimeout(() => setPhotoCountdown((c) => c - 1), 1000);
    return () => window.clearTimeout(timer);
  }, [photoCapturePhase, photoCountdown, formStep, triEnterCameraPhase]);

  const triCapturePhotoNow = useCallback(() => {
    if (photoCapturePhase === 'countdown') {
      triEnterCameraPhase();
      return;
    }
    if (photoCapturePhase === 'camera') triConfirmPhotoCameraCapture();
  }, [photoCapturePhase, triEnterCameraPhase, triConfirmPhotoCameraCapture]);

  const triRunAlbumPick = useCallback((applyPhoto, label) => {
    const hide = mpToast.loading('正在打开相册…', 0);
    window.setTimeout(() => {
      hide();
      applyPhoto();
      mpToast.success(`已从相册选择${label}（原型）`);
    }, 420);
  }, []);

  const triPickContinuousPhotoFromAlbum = useCallback(() => {
    const item = triGetCurrentCaptureItem();
    if (!item) return;
    triRunAlbumPick(() => {
      let tread = '';
      let treadOcrOk = true;
      if (item.tread) {
        const ocrIdx = photoTreadOcrIdxRef.current % DV_INSPECTION_TIRE_TREAD_DEMO.length;
        tread = DV_INSPECTION_TIRE_TREAD_DEMO[ocrIdx];
        photoTreadOcrIdxRef.current += 1;
        treadOcrOk = !!String(tread || '').trim();
      }
      setPhotoCameraDraft({
        photoUrl: dvGetPhotoDemoUrl(item.key),
        treadDepth: tread,
        treadOcrOk,
        captured: true,
      });
      if (item.tread && !treadOcrOk) mpToast.warning('胎纹识别失败，请重新选择照片');
    }, `「${item.label}」`);
  }, [triGetCurrentCaptureItem, triRunAlbumPick]);

  const triOpenPhotoViewer = useCallback((categoryKey, photoKey) => {
    const photos = formDraft?.returnPhotos || {};
    const items = dvBuildCategoryViewerItems(categoryKey, photos, formDraft);
    const index = items.findIndex((item) => item.key === photoKey);
    if (index < 0) return;
    setPhotoViewer({
      categoryKey,
      index,
      items,
      categoryLabel: dvPhotoCategoryLabel(categoryKey),
    });
  }, [formDraft]);

  const openTriPhotoCamera = useCallback((session) => {
    resetTriCameraAssist();
    setTriPhotoCamera({
      ...session,
      photoUrl: session.photoUrl || '',
      captured: !!session.photoUrl,
    });
    if (!session.photoUrl) mpToast.info(`拍摄${session.label}（支持拍照或相册）`);
  }, [resetTriCameraAssist]);

  const triShutterPhotoCamera = useCallback(() => {
    if (!triPhotoCamera) return;
    resetTriCameraAssist();
    setTriPhotoCamera((prev) => (prev ? { ...prev, photoUrl: prev.demoUrl, captured: true } : prev));
    mpToast.success(`已拍摄「${triPhotoCamera.label}」（原型）`);
  }, [triPhotoCamera, resetTriCameraAssist]);

  const triPickPhotoCameraFromAlbum = useCallback(() => {
    if (!triPhotoCamera) return;
    triRunAlbumPick(() => {
      resetTriCameraAssist();
      setTriPhotoCamera((prev) => (prev ? { ...prev, photoUrl: prev.demoUrl, captured: true } : prev));
    }, `「${triPhotoCamera.label}」`);
  }, [triPhotoCamera, resetTriCameraAssist, triRunAlbumPick]);

  const triCompletePhotoCamera = useCallback(() => {
    if (!triPhotoCamera?.captured || !triPhotoCamera.photoUrl) {
      mpToast.warning('请先拍照或从相册选择照片');
      return;
    }
    triPhotoCamera.onComplete?.();
    setTriPhotoCamera(null);
    resetTriCameraAssist();
  }, [triPhotoCamera, resetTriCameraAssist]);

  const triApplyPhotoUpload = useCallback((bucket, key, photoUrl) => {
    setFormDraft((prev) => {
      if (!prev) return prev;
      const target = bucket === 'license' ? 'licensePhotos' : 'returnPhotos';
      return {
        ...prev,
        [target]: {
          ...prev[target],
          [key]: { captured: true, photoUrl, uploaded: true },
        },
      };
    });
    mpToast.success('已上传');
  }, []);

  const triPickPhotoFile = useCallback((bucket, key) => {
    triUploadTargetRef.current = { bucket, key };
    triFileInputRef.current?.click();
  }, []);

  const triHandleFileChange = useCallback((e) => {
    const file = e.target.files && e.target.files[0];
    e.target.value = '';
    if (!file) return;
    if (file.size > 5 * 1024 * 1024) {
      mpToast.warning('单张不超过 5MB');
      return;
    }
    const reader = new FileReader();
    reader.onload = () => {
      const { bucket, key } = triUploadTargetRef.current;
      triApplyPhotoUpload(bucket, key, reader.result);
    };
    reader.onerror = () => mpToast.error('上传失败');
    reader.readAsDataURL(file);
  }, [triApplyPhotoUpload]);

  const triAddExtraPhoto = useCallback((categoryKey) => {
    const photos = formDraft?.returnPhotos || {};
    const keys = Object.keys(photos).filter((k) => k.indexOf(`${categoryKey}_extra_`) === 0);
    if (keys.length >= 6) {
      mpToast.warning('最多上传 6 张');
      return;
    }
    const nextKey = `${categoryKey}_extra_${keys.length + 1}`;
    const label = `照片${keys.length + 1}`;
    openTriPhotoCamera({
      key: nextKey,
      label,
      demoUrl: dvGetPhotoDemoUrl(nextKey),
      onComplete: () => {
        const upload = dvSimulatePhotoUpload(nextKey, { photoUrl: dvGetPhotoDemoUrl(nextKey) }, formDraft, activeTask);
        setFormDraft((d) => ({
          ...d,
          returnPhotos: {
            ...(d.returnPhotos || {}),
            [nextKey]: {
              captured: true,
              photoUrl: upload.photoUrl,
              uploaded: true,
              watermarkTime: upload.watermarkTime,
              watermarkAddress: upload.watermarkAddress,
            },
          },
        }));
        mpToast.success('已添加照片（原型）');
      },
    });
  }, [formDraft, activeTask, openTriPhotoCamera]);

  const triRemovePhoto = useCallback((bucket, key) => {
    setFormDraft((prev) => {
      if (!prev) return prev;
      const target = bucket === 'license' ? 'licensePhotos' : 'returnPhotos';
      const next = { ...(prev[target] || {}) };
      delete next[key];
      return { ...prev, [target]: next };
    });
  }, []);

  const triHandleMetricChange = useCallback((field, raw) => {
    const next = dvMetricInputChange(raw);
    if (next === null) return;
    setFormDraft((f) => (f ? { ...f, [field]: next } : f));
  }, []);

  const triRenderFaultSwitch = useCallback((hasFault, onChange) => {
    const isHasFault = hasFault === true;
    const isNoFault = hasFault === false;
    if (readOnly) {
      return (
        <span className="xll-dv-equip-switch-label">{isHasFault ? '有故障' : isNoFault ? '无故障' : '—'}</span>
      );
    }
    return (
      <div className="xll-tri-fault-segment" role="group" aria-label="是否有故障">
        <button
          type="button"
          className={`xll-tri-fault-segment__btn${isHasFault ? ' is-active' : ''}`}
          aria-pressed={isHasFault}
          onClick={() => onChange(true)}
        >
          有故障
        </button>
        <button
          type="button"
          className={`xll-tri-fault-segment__btn${isNoFault ? ' is-active' : ''}`}
          aria-pressed={isNoFault}
          onClick={() => onChange(false)}
        >
          无故障
        </button>
      </div>
    );
  }, [readOnly]);

  const triToggleHasFault = useCallback((hasFault) => {
    setFormDraft((prev) => {
      if (!prev) return prev;
      return {
        ...prev,
        hasFault,
        ...(hasFault ? {} : { faultDescription: '' }),
      };
    });
  }, []);

  useEffect(() => {
    if (!onRegisterBack) return undefined;
    onRegisterBack(() => {
      if (photoViewer) { setPhotoViewer(null); return true; }
      if (triPhotoCamera) { setTriPhotoCamera(null); resetTriCameraAssist(); return true; }
      if (activeTask && formStep === 'photos' && (photoCapturePhase === 'countdown' || photoCapturePhase === 'camera')) {
        if (photoCapturePhase === 'camera') {
          resetTriCameraAssist();
          setPhotoCapturePhase('countdown');
          setPhotoCameraDraft({ photoUrl: '', treadDepth: '', treadOcrOk: false, captured: false });
        } else {
          setPhotoCapturePhase('ready');
          setPhotoCountdown(3);
        }
        return true;
      }
      if (activeTask) { closeForm(); return true; }
      return false;
    });
    return () => onRegisterBack(null);
  }, [onRegisterBack, activeTask, formStep, photoCapturePhase, photoViewer, triPhotoCamera, closeForm, resetTriCameraAssist]);

  const renderSummaryCard = (task) => (
    <div className="xll-mod-section xll-tri-summary">
      {renderTriReturnHeroCard(task, {
        footExtra: (
          <div className="tc-hero-foot-row"><span className="tc-hero-foot-label">出租方</span>{task.lessorName || '—'}</div>
        ),
      })}
    </div>
  );

  const renderTriPhotoAlbumFloat = (onAlbum) => (
    <button type="button" className="xll-dv-photo-album-float" onClick={onAlbum} aria-label="从相册选择">相册</button>
  );

  const renderTriPhotoCaptureActionBar = ({ captured, onShutter, onNext, nextDisabled = false }) => (
    <div className={`xll-dv-photo-camera-actions${captured ? ' xll-dv-photo-camera-actions--dual' : ' xll-dv-photo-camera-actions--single'}`}>
      {!captured ? (
        <button type="button" className="xll-dv-spare-capture-retake" onClick={onShutter}>拍照</button>
      ) : (
        <>
          <button type="button" className="xll-dv-spare-capture-retake" onClick={onShutter}>重新拍摄</button>
          <button type="button" className="xll-dv-spare-capture-done" onClick={onNext} disabled={nextDisabled}>下一张</button>
        </>
      )}
    </div>
  );

  const renderTriPhotoCameraOverlay = () => {
    if (photoCapturePhase !== 'camera' || formStep !== 'photos') return null;
    const item = triGetCurrentCaptureItem();
    if (!item) return null;
    const seq = dvGetCaptureSequence(formDraft);
    return (
      <div className="xll-dv-photo-camera-overlay" role="dialog" aria-modal="true" aria-label={`拍摄${item.label}`}>
        <div className="xll-dv-photo-camera-top">
          {item.label}
          <span style={{ marginLeft: 8, fontSize: 12, opacity: 0.75 }}>{photoCaptureIndex + 1}/{seq.length}</span>
        </div>
        <div className="xll-dv-photo-camera-view">
          <DvCameraViewfinder
            photoUrl={photoCameraDraft.photoUrl}
            alt={item.label}
            placeholder="相机取景中…"
            zoom={dvCameraZoom}
            onZoomChange={setDvCameraZoom}
            focusPoint={dvCameraFocus}
            onFocusTap={setDvCameraFocus}
            onImgError={(e) => { e.currentTarget.src = dvGetPhotoDemoUrl(item.key); }}
            showFocusTip={!photoCameraDraft.captured}
          />
          {renderTriPhotoAlbumFloat(triPickContinuousPhotoFromAlbum)}
        </div>
        {item.tread && photoCameraDraft.captured ? (
          <div className="xll-dv-photo-camera-tread">
            <span className="xll-dv-photo-camera-tread-label">{item.label}胎纹</span>
            <div className="xll-dv-spare-capture-tread-input-wrap">
              <input
                className="xll-dv-spare-capture-tread-input"
                type="text"
                inputMode="decimal"
                value={photoCameraDraft.treadDepth}
                onChange={(e) => {
                  const next = dvMetricInputChange(e.target.value);
                  if (next !== null) {
                    setPhotoCameraDraft((d) => ({ ...d, treadDepth: next, treadOcrOk: !!String(next || '').trim() }));
                  }
                }}
                placeholder="—"
                aria-label={`${item.label}胎纹深度`}
              />
              <span className="xll-dv-spare-capture-tread-unit">mm</span>
            </div>
          </div>
        ) : null}
        {item.tread && photoCameraDraft.captured && !photoCameraDraft.treadOcrOk ? (
          <div style={{ padding: '8px 16px', fontSize: 12, color: COLOR_DANGER, background: 'rgba(245,63,63,.08)' }}>
            胎纹未识别成功，请重新拍摄后再继续
          </div>
        ) : null}
        {renderTriPhotoCaptureActionBar({
          captured: !!photoCameraDraft.captured,
          onShutter: () => { resetTriCameraAssist(); triSimulateCameraShutter(); },
          onNext: triConfirmPhotoCameraCapture,
          nextDisabled: !!(item.tread && photoCameraDraft.captured && !photoCameraDraft.treadOcrOk),
        })}
      </div>
    );
  };

  const renderTriStandalonePhotoCameraOverlay = () => {
    if (!triPhotoCamera) return null;
    return (
      <div className="xll-dv-photo-camera-overlay" role="dialog" aria-modal="true" aria-label={`拍摄${triPhotoCamera.label}`}>
        <div className="xll-dv-photo-camera-top">{triPhotoCamera.label}</div>
        <div className="xll-dv-photo-camera-view">
          <DvCameraViewfinder
            photoUrl={triPhotoCamera.photoUrl}
            alt={triPhotoCamera.label}
            placeholder="相机取景中…"
            zoom={dvCameraZoom}
            onZoomChange={setDvCameraZoom}
            focusPoint={dvCameraFocus}
            onFocusTap={setDvCameraFocus}
            showFocusTip={!triPhotoCamera.captured}
          />
          {renderTriPhotoAlbumFloat(triPickPhotoCameraFromAlbum)}
        </div>
        {renderTriPhotoCaptureActionBar({
          captured: !!triPhotoCamera.captured,
          onShutter: triShutterPhotoCamera,
          onNext: triCompletePhotoCamera,
        })}
      </div>
    );
  };

  const renderTriPhotoViewerOverlay = () => {
    if (!photoViewer) return null;
    const { items, index, categoryLabel } = photoViewer;
    const current = items[index];
    if (!current) return null;
    const goPrev = () => setPhotoViewer((v) => (v && v.index > 0 ? { ...v, index: v.index - 1 } : v));
    const goNext = () => setPhotoViewer((v) => (v && v.index < v.items.length - 1 ? { ...v, index: v.index + 1 } : v));
    const onTouchStart = (e) => {
      const t = e.changedTouches?.[0] || e.touches?.[0];
      if (!t) return;
      photoViewerTouchRef.current = { x: t.clientX, y: t.clientY };
    };
    const onTouchEnd = (e) => {
      const t = e.changedTouches?.[0];
      if (!t) return;
      const dx = t.clientX - photoViewerTouchRef.current.x;
      const dy = t.clientY - photoViewerTouchRef.current.y;
      if (Math.abs(dx) < 48 || Math.abs(dx) < Math.abs(dy)) return;
      if (dx > 0) goPrev();
      else goNext();
    };
    return (
      <div className="xll-dv-photo-viewer" role="dialog" aria-modal="true" aria-label="照片预览">
        <div className="xll-dv-photo-viewer-top">
          <button type="button" className="xll-dv-photo-viewer-close" onClick={() => setPhotoViewer(null)}>关闭</button>
          <div className="xll-dv-photo-viewer-meta">
            <div className="xll-dv-photo-viewer-cat">{categoryLabel}</div>
            <div className="xll-dv-photo-viewer-title">{current.label}</div>
          </div>
          <span className="xll-dv-photo-viewer-counter">{index + 1}/{items.length}</span>
        </div>
        <div className="xll-dv-photo-viewer-stage" onTouchStart={onTouchStart} onTouchEnd={onTouchEnd}>
          <button type="button" className="xll-dv-photo-viewer-nav xll-dv-photo-viewer-nav--prev" disabled={index <= 0} onClick={goPrev} aria-label="上一张">‹</button>
          <div className="xll-dv-photo-viewer-img-wrap">
            <img key={current.key} src={current.photoUrl} alt={current.label} onError={(e) => { e.currentTarget.src = dvGetPhotoDemoUrl(current.key); }} />
          </div>
          <button type="button" className="xll-dv-photo-viewer-nav xll-dv-photo-viewer-nav--next" disabled={index >= items.length - 1} onClick={goNext} aria-label="下一张">›</button>
        </div>
        <div className="xll-dv-photo-viewer-foot">
          {current.treadDepth ? <span>胎纹 {current.treadDepth} mm</span> : null}
        </div>
      </div>
    );
  };

  const renderTriActionBar = () => {
    if (readOnly) return null;
    if (formStep === 'fault') {
      return (
        <div className="xll-mod-action-bar">
          <button type="button" className="xll-mod-btn-ghost" onClick={handleSave}>保存</button>
          <button type="button" className="xll-mod-btn-primary" onClick={handleFaultNext}>下一步</button>
        </div>
      );
    }
    if (formStep === 'inspection') {
      return (
        <div className="xll-mod-action-bar">
          <button type="button" className="xll-mod-btn-ghost" onClick={handleSave}>保存</button>
          <button type="button" className="xll-mod-btn-primary" onClick={handleInspectionNext}>下一步</button>
        </div>
      );
    }
    if (formStep === 'photos') {
      if (photoCapturePhase === 'camera') return null;
      if (photoCapturePhase === 'ready') {
        return (
          <div className="xll-mod-action-bar xll-dv-photo-action-bar">
            <button type="button" className="xll-mod-btn-ghost" onClick={handleSave}>保存</button>
            <button type="button" className="xll-mod-btn-primary xll-dv-photo-action-btn" onClick={triStartPhotoCapture}>开始拍摄</button>
          </div>
        );
      }
      if (photoCapturePhase === 'countdown') {
        return (
          <div className="xll-mod-action-bar xll-dv-photo-action-bar">
            <button type="button" className="xll-mod-btn-ghost" onClick={handleSave}>保存</button>
            <button type="button" className="xll-mod-btn-primary xll-dv-photo-action-btn" onClick={triCapturePhotoNow}>立即拍摄</button>
          </div>
        );
      }
      return (
        <div className="xll-mod-action-bar">
          <button type="button" className="xll-mod-btn-ghost" onClick={handleSave}>保存</button>
          <button type="button" className="xll-mod-btn-primary" onClick={handleSign}>签章</button>
        </div>
      );
    }
    return null;
  };

  const renderFaultStep = () => {
    const f = formDraft || {};
    const h2Unit = f.returnH2Unit || dvGetModelGaugeUnit(activeTask?.brand, activeTask?.model);
    const hasFault = f.hasFault === true;

    return (
      <>
        <div className="xll-mod-section" style={{ margin: '12px 14px 0' }}>
          <div className="xll-mod-form-row">
            <span className="xll-mod-form-label" style={{ color: COLOR_TEXT, fontWeight: 600 }}>是否有故障<span style={{ color: COLOR_DANGER, marginLeft: 2 }}>*</span></span>
            {triRenderFaultSwitch(f.hasFault, triToggleHasFault)}
          </div>
        </div>
        {hasFault ? (
          <div className="xll-mod-section" style={{ margin: '12px 14px 0' }}>
            <div className="xll-mod-section-title">故障描述<span style={{ color: COLOR_DANGER, marginLeft: 4 }}>*</span></div>
            {readOnly ? (
              <div style={{ fontSize: 14, color: COLOR_TEXT_SEC, lineHeight: 1.6 }}>{f.faultDescription || '—'}</div>
            ) : (
              <textarea
                className="xll-dv-metric-remark"
                value={f.faultDescription || ''}
                onChange={(e) => setFormDraft((prev) => (prev ? { ...prev, faultDescription: e.target.value } : prev))}
                placeholder="请描述车辆故障情况"
                rows={3}
                aria-label="故障描述"
              />
            )}
          </div>
        ) : null}
        <div className="xll-mod-section" style={{ margin: '12px 14px 0' }}>
          <div className="xll-mod-section-title">退车数据</div>
          {readOnly ? (
            <div className="xll-dv-kv-grid">
              <div className="xll-dv-kv-item"><div className="xll-dv-kv-label">退车里程</div><div className="xll-dv-kv-val">{dvFormatMileage(f.returnMileage)}</div></div>
              <div className="xll-dv-kv-item"><div className="xll-dv-kv-label">退车电量</div><div className="xll-dv-kv-val">{dvFormatMetric2(f.returnElectric, '%')}</div></div>
              <div className="xll-dv-kv-item"><div className="xll-dv-kv-label">退车氢量</div><div className="xll-dv-kv-val">{dvFormatH2(f.returnHydrogen, h2Unit)}</div></div>
            </div>
          ) : (
            <div className="xll-dv-metrics-grid">
              <div className="xll-dv-metric-field">
                <label className="xll-dv-metric-label">退车里程 (km)<span style={{ color: COLOR_DANGER, marginLeft: 2 }}>*</span></label>
                <input className="xll-dv-metric-input" type="text" inputMode="decimal" value={f.returnMileage} onChange={(e) => triHandleMetricChange('returnMileage', e.target.value)} placeholder="0.00" />
              </div>
              <div className="xll-dv-metric-field">
                <label className="xll-dv-metric-label">退车电量 (%)<span style={{ color: COLOR_DANGER, marginLeft: 2 }}>*</span></label>
                <input className="xll-dv-metric-input" type="text" inputMode="decimal" value={f.returnElectric} onChange={(e) => triHandleMetricChange('returnElectric', e.target.value)} placeholder="0.00" />
              </div>
              <div className="xll-dv-metric-field">
                <label className="xll-dv-metric-label">退车氢量<span style={{ color: COLOR_DANGER, marginLeft: 2 }}>*</span></label>
                <div className="xll-dv-metric-unit-wrap">
                  <input className="xll-dv-metric-input" type="text" inputMode="decimal" value={f.returnHydrogen} onChange={(e) => triHandleMetricChange('returnHydrogen', e.target.value)} placeholder="0.00" />
                  <span className="xll-dv-metric-unit-suffix" aria-label="氢量单位">{h2Unit}</span>
                </div>
              </div>
            </div>
          )}
        </div>
        <div className="xll-mod-section" style={{ margin: '12px 14px 0' }}>
          <div className="xll-mod-section-title">备注</div>
          {readOnly ? (
            <div style={{ fontSize: 14, color: COLOR_TEXT_SEC, lineHeight: 1.6 }}>{f.returnRemark || '—'}</div>
          ) : (
            <textarea
              className="xll-dv-metric-remark"
              value={f.returnRemark || ''}
              onChange={(e) => setFormDraft((prev) => (prev ? { ...prev, returnRemark: e.target.value } : prev))}
              placeholder="可补充退车相关说明"
              rows={3}
              aria-label="退车备注"
            />
          )}
        </div>
      </>
    );
  };

  const renderInspectionStep = () => renderDvInspectionPanel(formDraft || {}, {
    readOnly,
    onUpdateRow: triUpdateInspectionRow,
    title: '退车检查单',
  });

  const renderPhotosStep = () => {
    const f = formDraft || {};
    const photos = f.returnPhotos || {};
    const seq = dvGetCaptureSequence(f);
    const capturedCount = dvCountCapturedPhotos(photos, f);
    const currentItem = seq[photoCaptureIndex] || null;

    const removePhoto = (photoKey) => {
      setFormDraft((d) => {
        const next = { ...(d.returnPhotos || {}) };
        delete next[photoKey];
        return { ...d, returnPhotos: next };
      });
      setPhotoViewer((v) => (v?.items?.some((item) => item.key === photoKey) ? null : v));
    };

    const renderPhotoThumbCell = (photoKey, label, categoryKey) => {
      const record = dvGetPhotoRecord(photos, photoKey);
      const captured = dvPhotoCaptured(photos, photoKey);
      const resolvedUrl = captured ? dvResolvePhotoUrl(photoKey, record) : '';
      const canPreview = !!resolvedUrl;
      const isExtraCategory = categoryKey === 'defect' || categoryKey === 'other';
      const isReshootSlot = !readOnly && photoCapturePhase === 'done' && !captured && !isExtraCategory;
      const allowDelete = !readOnly && captured && (isExtraCategory || photoCapturePhase === 'done');
      const placeholderText = isReshootSlot ? '补拍' : (captured ? 'PHOTO' : '+ 添加');
      const handleThumbClick = () => {
        if (canPreview) { triOpenPhotoViewer(categoryKey, photoKey); return; }
        if (isReshootSlot) triStartPhotoCaptureAtKey(photoKey);
      };
      return (
        <div key={photoKey}>
          <div
            className={`xll-dv-photo-thumb${canPreview || isReshootSlot ? '' : ' xll-dv-photo-thumb--empty'}`}
            role={canPreview || isReshootSlot ? 'button' : undefined}
            tabIndex={canPreview || isReshootSlot ? 0 : undefined}
            onClick={handleThumbClick}
            onKeyDown={(e) => { if ((canPreview || isReshootSlot) && e.key === 'Enter') handleThumbClick(); }}
          >
            {resolvedUrl ? (
              <>
                <img src={resolvedUrl} alt={label} onError={(e) => { e.currentTarget.src = dvGetPhotoDemoUrl(photoKey); }} />
                {record?.watermarkTime ? (
                  <div className="xll-dv-photo-thumb-watermark" aria-hidden>
                    <div>{record.watermarkTime}</div>
                    {record.watermarkAddress ? <div className="xll-dv-photo-thumb-watermark-loc">{record.watermarkAddress}</div> : null}
                  </div>
                ) : null}
              </>
            ) : (
              <span className={`xll-dv-photo-thumb-placeholder${isReshootSlot ? ' xll-dv-photo-thumb-placeholder--reshoot' : ''}`}>{placeholderText}</span>
            )}
            {allowDelete ? (
              <button type="button" className="xll-dv-photo-thumb-del" onClick={(e) => { e.stopPropagation(); removePhoto(photoKey); }} aria-label={`删除${label}`}>×</button>
            ) : null}
          </div>
          <div className="xll-dv-photo-thumb-label">{label}</div>
          {record?.treadDepth ? <div className="xll-dv-photo-thumb-tread">胎纹 {record.treadDepth} mm</div> : null}
        </div>
      );
    };

    const renderPhotoCategorySection = (cat, sectionNum) => {
      let items = [];
      if (cat.key === 'defect' || cat.key === 'other') {
        items = Object.keys(photos)
          .filter((key) => key.indexOf(`${cat.key}_extra_`) === 0 && dvPhotoCaptured(photos, key))
          .sort()
          .map((key, idx) => ({ key, label: `照片${idx + 1}` }));
      } else {
        items = dvPhotoItemsByCategory(cat.key).filter((item) => {
          if (readOnly) return dvPhotoCaptured(photos, item.key);
          return true;
        });
      }
      if (readOnly && items.length === 0 && cat.key !== 'defect' && cat.key !== 'other') return null;
      return (
        <div key={cat.key} className="tc-section xll-dv-photo-cat-section">
          {renderDvSectionHead(sectionNum, cat.label, null, false, readOnly)}
          <div className="xll-dv-photo-block">
            <div className="xll-dv-photo-grid">
              {items.map((item) => renderPhotoThumbCell(item.key, item.label, cat.key))}
              {!readOnly && (cat.key === 'defect' || cat.key === 'other') ? (
                <div>
                  <div className="xll-dv-photo-slot" role="button" tabIndex={0} onClick={() => triAddExtraPhoto(cat.key)} onKeyDown={(e) => e.key === 'Enter' && triAddExtraPhoto(cat.key)}>+ 添加</div>
                </div>
              ) : null}
            </div>
          </div>
        </div>
      );
    };

    if (!readOnly && photoCapturePhase === 'ready') {
      const progressPct = seq.length ? Math.round((capturedCount / seq.length) * 100) : 0;
      return (
        <div className="xll-dv-photo-capture-page xll-dv-photo-capture-page--ready">
          <div className="xll-dv-photo-capture-card">
            <div className="xll-dv-photo-capture-icon" aria-hidden>
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
                <path d="M4 7h3l2-3h6l2 3h3a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2z" />
                <circle cx="12" cy="13" r="3.5" />
              </svg>
            </div>
            <h2 className="xll-dv-photo-capture-title">请准备好连续拍摄</h2>
            <p className="xll-dv-photo-capture-sub">请确认车辆与拍摄环境就绪，连拍将自动按顺序进行</p>
            {capturedCount > 0 ? (
              <div className="xll-dv-photo-capture-resume">
                <div className="xll-dv-photo-capture-progress-bar" aria-hidden><span style={{ width: `${progressPct}%` }} /></div>
                <div className="xll-dv-photo-capture-resume-text">已完成 <strong>{capturedCount}/{seq.length}</strong> 张，点击底部按钮继续拍摄</div>
              </div>
            ) : (
              <ul className="xll-dv-photo-capture-tips">
                <li>请保持画面清晰、光线充足</li>
                <li>连拍过程中请勿退出，下次将自动续拍</li>
              </ul>
            )}
          </div>
        </div>
      );
    }

    if (!readOnly && photoCapturePhase === 'countdown') {
      const catKey = currentItem?.category || 'body';
      const catLabel = dvPhotoCategoryLabel(catKey);
      return (
        <div className="xll-dv-photo-capture-page xll-dv-photo-capture-page--countdown">
          <div className="xll-dv-photo-capture-card">
            {catLabel ? <span className={`xll-dv-photo-capture-badge xll-dv-photo-capture-badge--${catKey}`}>{catLabel}</span> : null}
            <p className="xll-dv-photo-capture-soon">即将拍摄</p>
            <div className="xll-dv-photo-capture-countdown-ring" aria-live="polite" aria-label={`${photoCountdown}秒后开始拍摄`}>
              <span key={photoCountdown} className="xll-dv-photo-capture-countdown-num">{photoCountdown}</span>
            </div>
            <h2 className="xll-dv-photo-capture-target">{currentItem?.label || '—'}</h2>
            <p className="xll-dv-photo-capture-countdown-tip">秒后自动开始 · 可点底部「立即拍摄」</p>
            <div className="xll-dv-photo-capture-step-pill">第 <em>{photoCaptureIndex + 1}</em> / {seq.length} 项</div>
          </div>
        </div>
      );
    }

    if (!readOnly && photoCapturePhase === 'camera') return null;

    const photoCategoryNodes = DV_PHOTO_CATEGORIES.map((cat, idx) => renderPhotoCategorySection(cat, idx + 1));

    return (
      <>
        {photoCategoryNodes}
        {!readOnly && !dvRequiredPhotosComplete(photos, f) ? (
          <div className="tc-section xll-dv-photo-reshoot-bar">
            <div style={{ padding: '14px' }}>
              <button type="button" className="xll-mod-btn-ghost" style={{ width: '100%', minHeight: 40, borderRadius: 10 }} onClick={triStartPhotoCapture}>继续连拍</button>
            </div>
          </div>
        ) : null}
        <div className="tc-section xll-dv-photo-cat-section">
          {renderDvSectionHead(6, '车辆证件', null, true, readOnly)}
          <div className="xll-dv-photo-block">
            <div className="xll-dv-photo-grid">
              {TRI_LICENSE_PHOTO_ITEMS.map((item) => {
                const captured = dvPhotoCaptured(f.licensePhotos, item.key);
                const record = dvGetPhotoRecord(f.licensePhotos, item.key);
                const resolvedUrl = captured ? dvResolvePhotoUrl(item.key, record) : '';
                return (
                  <div key={item.key}>
                    <div
                      className={`xll-dv-photo-thumb${resolvedUrl ? '' : ' xll-dv-photo-thumb--empty'}`}
                      role={!readOnly || resolvedUrl ? 'button' : undefined}
                      tabIndex={!readOnly || resolvedUrl ? 0 : undefined}
                      onClick={() => {
                        if (readOnly && resolvedUrl) {
                          setPhotoViewer({ categoryKey: 'license', index: 0, items: [{ key: item.key, label: item.label, photoUrl: resolvedUrl }], categoryLabel: '车辆证件' });
                          return;
                        }
                        if (!readOnly) {
                          openTriPhotoCamera({
                            key: item.key,
                            label: item.label,
                            demoUrl: dvGetPhotoDemoUrl(item.key),
                            photoUrl: resolvedUrl || '',
                            onComplete: () => {
                              const upload = dvSimulatePhotoUpload(item.key, { photoUrl: dvGetPhotoDemoUrl(item.key) }, f, activeTask);
                              setFormDraft((d) => ({
                                ...d,
                                licensePhotos: {
                                  ...(d.licensePhotos || {}),
                                  [item.key]: { captured: true, photoUrl: upload.photoUrl, uploaded: true, watermarkTime: upload.watermarkTime, watermarkAddress: upload.watermarkAddress },
                                },
                              }));
                              mpToast.success('已上传（原型）');
                            },
                          });
                        }
                      }}
                    >
                      {resolvedUrl ? <img src={resolvedUrl} alt={item.label} onError={(e) => { e.currentTarget.src = dvGetPhotoDemoUrl(item.key); }} /> : <span className="xll-dv-photo-thumb-placeholder">+ 添加</span>}
                      {!readOnly && captured ? (
                        <button type="button" className="xll-dv-photo-thumb-del" onClick={(e) => { e.stopPropagation(); triRemovePhoto('license', item.key); }} aria-label={`删除${item.label}`}>×</button>
                      ) : null}
                    </div>
                    <div className="xll-dv-photo-thumb-label">{item.label}</div>
                  </div>
                );
              })}
            </div>
          </div>
        </div>
        <div className="tc-section xll-dv-photo-cat-section">
          {renderDvSectionHead(7, '出租方信息', null, true, readOnly)}
          <div className="tc-section-form">
            {readOnly ? (
              <div className="xll-dv-kv-grid">
                <div className="xll-dv-kv-item"><div className="xll-dv-kv-label">出租方联系人</div><div className="xll-dv-kv-val">{f.lessorContactName || '—'}</div></div>
                <div className="xll-dv-kv-item"><div className="xll-dv-kv-label">出租方联系电话</div><div className="xll-dv-kv-val">{f.lessorContactPhone || '—'}</div></div>
              </div>
            ) : (
              <>
                <div className="xll-mod-form-row">
                  <span className="xll-mod-form-label">出租方联系人<span style={{ color: COLOR_DANGER, marginLeft: 2 }}>*</span></span>
                  <input
                    className="xll-mod-form-input"
                    type="text"
                    value={f.lessorContactName || ''}
                    onChange={(e) => setFormDraft((prev) => (prev ? { ...prev, lessorContactName: e.target.value } : prev))}
                    placeholder="请输入联系人姓名"
                    aria-label="出租方联系人"
                  />
                </div>
                <div className="xll-mod-form-row">
                  <span className="xll-mod-form-label">出租方联系电话<span style={{ color: COLOR_DANGER, marginLeft: 2 }}>*</span></span>
                  <input
                    className="xll-mod-form-input"
                    type="tel"
                    inputMode="tel"
                    value={f.lessorContactPhone || ''}
                    onChange={(e) => setFormDraft((prev) => (prev ? { ...prev, lessorContactPhone: e.target.value.replace(/[^\d+\-\s]/g, '') } : prev))}
                    placeholder="请输入手机号码"
                    aria-label="出租方联系电话"
                  />
                </div>
              </>
            )}
          </div>
        </div>
        <div style={{ height: 16 }} />
      </>
    );
  };

  if (activeTask && formDraft) {
    const stepIdx = TRI_FORM_STEPS.findIndex((s) => s.key === formStep);
    const hideStepBar = formStep === 'photos' && (photoCapturePhase === 'countdown' || photoCapturePhase === 'camera');
    const photoCaptureLayout = formStep === 'photos' && (photoCapturePhase === 'ready' || photoCapturePhase === 'countdown');
    const goTriStep = (key) => {
      const stepOrder = TRI_FORM_STEPS.map((s) => s.key);
      const targetIdx = stepOrder.indexOf(key);
      const currentIdx = Math.max(0, stepOrder.indexOf(formStep));
      if (targetIdx < 0) return;
      if (readOnly) {
        if (key === 'photos') triInitPhotoCaptureOnEnter(formDraft?.returnPhotos || {}, formDraft, true);
        setFormStep(key);
        return;
      }
      if (targetIdx <= currentIdx) {
        if (key === 'photos') triInitPhotoCaptureOnEnter(formDraft?.returnPhotos || {}, formDraft, false);
        setFormStep(key);
        return;
      }
      if (targetIdx >= 1) {
        const faultResult = triValidateFaultStep(formDraft);
        if (!faultResult.ok) { mpToast.warning(faultResult.message); return; }
      }
      if (targetIdx >= 2) {
        const inspectionResult = triValidateInspectionStep(formDraft);
        if (!inspectionResult.ok) { mpToast.warning(inspectionResult.message); return; }
      }
      if (key === 'photos') {
        triInitPhotoCaptureOnEnter(formDraft?.returnPhotos || {}, formDraft, false);
        setFormStep('photos');
        return;
      }
      setFormStep(key);
    };
    return (
      <div className="xll-mod-root xll-dv-module xll-mod-form-page">
        <input ref={triFileInputRef} type="file" accept="image/jpeg,image/jpg,image/png,image/gif,image/webp" style={{ display: 'none' }} onChange={triHandleFileChange} />
        <div className="xll-mod-detail-wrap">
          {!hideStepBar ? (
            <div className="xll-dv-steps-wrap">
              <div className="xll-dv-step-nav" role="tablist" aria-label="三方退车办理步骤">
                {TRI_FORM_STEPS.map((s, i) => (
                  <React.Fragment key={s.key}>
                    <button
                      type="button"
                      role="tab"
                      aria-selected={formStep === s.key}
                      className={`xll-dv-step-nav-item${formStep === s.key ? ' active' : ''}${i < stepIdx ? ' done' : ''}`}
                      onClick={() => goTriStep(s.key)}
                    >
                      <span className="xll-dv-step-nav-index">{i + 1}</span>
                      <span className="xll-dv-step-nav-label">{s.label}</span>
                    </button>
                    {i < TRI_FORM_STEPS.length - 1 ? (
                      <span className={`xll-dv-step-nav-connector${i < stepIdx ? ' done' : ''}`} aria-hidden />
                    ) : null}
                  </React.Fragment>
                ))}
              </div>
            </div>
          ) : null}
          <div className={`tc-scroll${photoCaptureLayout ? ' xll-dv-photo-capture-scroll' : ''}`} style={photoCaptureLayout ? { display: 'flex', flexDirection: 'column' } : undefined}>
            {formStep === 'fault' ? renderSummaryCard(activeTask) : null}
            {formStep === 'fault' ? renderFaultStep() : null}
            {formStep === 'inspection' ? renderInspectionStep() : null}
            {formStep === 'photos' ? renderPhotosStep() : null}
          </div>
          {renderTriActionBar()}
          {renderTriPhotoCameraOverlay()}
          {renderTriStandalonePhotoCameraOverlay()}
          {renderTriPhotoViewerOverlay()}
        </div>
      </div>
    );
  }

  return (
    <div className="xll-mod-root" data-annotation-id="page-third-return">
      <div className="xll-mod-tabs" role="tablist">
        {[
          { key: 'all', label: '全部', count: kpi.all },
          { key: 'inProgress', label: '进行中', count: kpi.inProgress },
          { key: 'completed', label: '已完成', count: kpi.completed },
        ].map((tab) => (
          <button key={tab.key} type="button" role="tab" aria-selected={listFilter === tab.key} className={`xll-mod-tab${listFilter === tab.key ? ' active' : ''}`} onClick={() => setListFilter(tab.key)}>
            {tab.label}
            <span className="xll-mod-tab-count">{tab.count}</span>
          </button>
        ))}
      </div>
      <div className="xll-mod-toolbar">
        <div className="xll-mod-search">
          <IconSearch />
          <input type="search" placeholder="搜索车牌" value={searchKey} onChange={(e) => setSearchKey(e.target.value)} aria-label="搜索三方退车任务" />
        </div>
      </div>
      <div className="xll-mod-list-head"><span>退车任务</span><span>共 {filteredList.length} 辆</span></div>
      <div className="xll-mod-list">
        {filteredList.length === 0 ? (
          <div className="xll-mod-empty">暂无三方退车任务<br />采购退租申请审批通过后将自动生成</div>
        ) : filteredList.map((task) => {
          const inProgress = triIsInProgress(task.taskStatus);
          const stCls = triIsCompleted(task.taskStatus) ? 'ok' : inProgress ? 'pending' : 'info';
          return (
            <div
              key={task.id}
              className="xll-mod-card"
              style={{ '--mod-accent': '#EA580C', '--mod-soft': 'rgba(234, 88, 12, 0.12)' }}
              role="button"
              tabIndex={0}
              onClick={() => openTask(task)}
              onKeyDown={(e) => e.key === 'Enter' && openTask(task)}
            >
              <div className="xll-mod-card-head">
                <span className="xll-mod-ar-plate">{task.plateNo || '—'}</span>
                <span className={`xll-mod-card-status ${stCls}`}>{task.taskStatus}</span>
              </div>
              <div className="xll-mod-card-sub">{task.brand} · {task.model}</div>
              <div className="xll-mod-card-sub" style={{ fontSize: 12, color: COLOR_MUTED }}>出租方 {task.lessorName || '—'}</div>
              <div className="xll-mod-meta">
                <div><span className="xll-mod-meta-label">预计退租 </span><span className="xll-mod-meta-val">{task.expectedReturnDate || '—'}</span></div>
                <div><span className="xll-mod-meta-label">退车地点 </span><span className="xll-mod-meta-val">{task.deliveryRegionText || task.returnRegion || task.returnLocation || '—'}</span></div>
              </div>
              <div className="xll-mod-card-foot">
                <span style={{ fontSize: 12, color: COLOR_MUTED }}>{inProgress ? '待运维办理' : task.completedTime || '—'}</span>
                <button type="button" className="xll-mod-card-btn" onClick={(e) => { e.stopPropagation(); openTask(task); }}>{inProgress ? '去办理' : '查看'}</button>
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
};

const ReplaceModule = ({ onRegisterBack }) => {
  const [applications, setApplications] = useState(VR_MOCK_APPLICATIONS);
  const [listFilter, setListFilter] = useState('ongoing');
  const [statusFilter, setStatusFilter] = useState('');
  const [searchKey, setSearchKey] = useState('');
  const [addMode, setAddMode] = useState(false);
  const [detailRow, setDetailRow] = useState(null);
  const [pairs, setPairs] = useState([]);
  const [edited, setEdited] = useState(false);

  const selectedOriginalPlates = useMemo(() => pairs.map((p) => p.originalPlate).filter(Boolean), [pairs]);

  const projectInfo = useMemo(() => {
    const anchor = pairs.find((p) => p.originalPlate && p.contractId);
    if (!anchor) return null;
    return VR_CONTRACT_MAP[anchor.contractId] || null;
  }, [pairs]);

  const multiOldPlateOptions = useMemo(() => {
    const lockedContractId = projectInfo?.contractId;
    return VR_DELIVERED_VEHICLES
      .filter((v) => !lockedContractId || v.contractId === lockedContractId)
      .map((v) => v.plateNo);
  }, [projectInfo]);

  const getDeliveredVehicle = useCallback((plateNo) => VR_DELIVERED_VEHICLES.find((v) => v.plateNo === plateNo) || null, []);

  const getUsedPlates = useCallback((pairsList, field, exceptPairId) => {
    const set = {};
    pairsList.forEach((p) => {
      if (p.id === exceptPairId) return;
      if (p[field]) set[p[field]] = true;
    });
    return set;
  }, []);

  const buildPairForPlate = useCallback((plateNo, existing) => {
    const vehicle = getDeliveredVehicle(plateNo);
    if (!vehicle) return null;
    const row = existing ? { ...existing } : vrCreateEmptyPair();
    row.originalPlate = plateNo;
    row.originalBrand = vehicle.brand;
    row.originalModel = vehicle.model;
    row.contractId = vehicle.contractId;
    if (!existing) {
      row.replacePlate = '';
      row.replaceBrand = '';
      row.replaceModel = '';
    }
    return row;
  }, [getDeliveredVehicle]);

  const onMultiOriginalPlateChange = useCallback((plateList) => {
    const list = Array.isArray(plateList) ? plateList : [];
    if (list.length === 0) {
      setEdited(true);
      setPairs([]);
      return;
    }
    let anchorContractId = null;
    const validPlates = [];
    let rejected = false;
    list.forEach((plate) => {
      const vehicle = getDeliveredVehicle(plate);
      if (!vehicle || !VR_CONTRACT_MAP[vehicle.contractId]) return;
      if (!anchorContractId) anchorContractId = vehicle.contractId;
      if (vehicle.contractId !== anchorContractId) {
        rejected = true;
        return;
      }
      validPlates.push(plate);
    });
    if (rejected) mpToast.warning('多台替换须为同一客户、同一项目，已忽略不同项目的车辆');
    setEdited(true);
    setPairs((prev) => {
      const prevByPlate = {};
      prev.forEach((p) => { if (p.originalPlate) prevByPlate[p.originalPlate] = p; });
      return validPlates.map((plate) => buildPairForPlate(plate, prevByPlate[plate])).filter(Boolean);
    });
  }, [getDeliveredVehicle, buildPairForPlate]);

  const toggleOriginalPlate = useCallback((plate) => {
    const next = selectedOriginalPlates.includes(plate)
      ? selectedOriginalPlates.filter((p) => p !== plate)
      : [...selectedOriginalPlates, plate];
    onMultiOriginalPlateChange(next);
  }, [selectedOriginalPlates, onMultiOriginalPlateChange]);

  const updatePair = useCallback((pairId, patch) => {
    setEdited(true);
    setPairs((prev) => prev.map((p) => (p.id === pairId ? { ...p, ...patch } : p)));
  }, []);

  const getNewOptionsForPair = useCallback((pair) => {
    if (!projectInfo?.deliveryRegion) return [];
    const used = getUsedPlates(pairs, 'replacePlate', pair.id);
    return (VR_PREPARED_BY_REGION[projectInfo.deliveryRegion] || [])
      .filter((v) => !used[v.plateNo])
      .map((v) => v.plateNo);
  }, [pairs, projectInfo, getUsedPlates]);

  const onReplacePlateChange = useCallback((pairId, plateNo) => {
    if (!plateNo) {
      updatePair(pairId, { replacePlate: '', replaceBrand: '', replaceModel: '' });
      return;
    }
    const pair = pairs.find((p) => p.id === pairId);
    if (!pair?.originalPlate) {
      mpToast.info('请先选择被替换车辆');
      return;
    }
    const vehicle = (VR_PREPARED_BY_REGION[projectInfo?.deliveryRegion] || []).find((v) => v.plateNo === plateNo);
    if (!vehicle) return;
    const used = getUsedPlates(pairs, 'replacePlate', pairId);
    if (used[plateNo]) {
      mpToast.warning('该新车已在其他替换项中选择');
      return;
    }
    updatePair(pairId, { replacePlate: plateNo, replaceBrand: vehicle.brand, replaceModel: vehicle.model });
  }, [updatePair, pairs, projectInfo, getUsedPlates]);

  const openAdd = useCallback(() => {
    setPairs([]);
    setEdited(false);
    setAddMode(true);
    setDetailRow(null);
  }, []);

  const closeAdd = useCallback((force) => {
    if (!force && edited) {
      if (Modal?.confirm) {
        Modal.confirm({
          title: '取消将会丢失所有已填写内容，是否确认？',
          okText: '确认',
          cancelText: '返回',
          centered: true,
          onOk: () => { setAddMode(false); setPairs([]); setEdited(false); },
        });
        return;
      }
    }
    setAddMode(false);
    setPairs([]);
    setEdited(false);
  }, [edited]);

  const handleSave = useCallback(() => {
    mpToast.success('已保存，该条数据仅您可查看并编辑（原型）');
    setEdited(false);
  }, []);

  const handleSubmit = useCallback(() => {
    if (!pairs.length || !projectInfo?.contractId) {
      mpToast.warning('请选择被替换车辆并完善替换信息');
      return;
    }
    const incomplete = pairs.find((p) => {
      if (!p.originalPlate || !p.replacePlate || !p.replaceType || !p.replaceReason) return true;
      if (p.replaceReason === '客户原因' && !(p.replaceFee || '').toString().trim()) return true;
      return false;
    });
    if (incomplete) {
      mpToast.warning('请完善每条替换的新车、替换类型、替换原因及客户原因下的替换费用');
      return;
    }
    const newApp = {
      id: `vr-new-${Date.now()}`,
      bizNo: `TH-2026-${String(applications.length + 1).padStart(4, '0')}`,
      replaceDate: new Date().toISOString().slice(0, 10),
      approvalStatus: '待审批',
      currentApprover: '业务部主管',
      projectName: projectInfo.projectName,
      customerName: projectInfo.customerName,
      deliveryRegion: projectInfo.deliveryRegion,
      pairs: pairs.map((p) => ({
        originalPlate: p.originalPlate,
        replacePlate: p.replacePlate,
        replaceType: p.replaceType,
        replaceReason: p.replaceReason,
        replaceFee: p.replaceFee,
        replaceReasonDesc: p.replaceReasonDesc,
        originalBrand: p.originalBrand,
        originalModel: p.originalModel,
        replaceBrand: p.replaceBrand,
        replaceModel: p.replaceModel,
      })),
      creator: MOCK_USER,
      createTime: new Date().toISOString().slice(0, 16).replace('T', ' '),
    };
    setApplications((prev) => [newApp, ...prev]);
    mpToast.success(`已提交 ${pairs.length} 条替换车申请（原型）`);
    setAddMode(false);
    setPairs([]);
    setEdited(false);
    setListFilter('ongoing');
  }, [pairs, projectInfo, applications.length]);

  const kpi = useMemo(() => ({
    ongoing: applications.filter((a) => vrIsOngoing(a.approvalStatus)).length,
    history: applications.filter((a) => vrIsHistory(a.approvalStatus)).length,
  }), [applications]);

  const filteredList = useMemo(() => {
    const q = searchKey.trim().toUpperCase();
    let list = applications;
    if (listFilter === 'ongoing') list = list.filter((a) => vrIsOngoing(a.approvalStatus));
    if (listFilter === 'history') list = list.filter((a) => vrIsHistory(a.approvalStatus));
    if (statusFilter) list = list.filter((a) => a.approvalStatus === statusFilter);
    if (q) {
      list = list.filter((a) =>
        (a.bizNo || '').toUpperCase().includes(q)
        || (a.projectName || '').toUpperCase().includes(q)
        || (a.customerName || '').toUpperCase().includes(q)
        || (a.pairs || []).some((p) => (p.originalPlate || '').toUpperCase().includes(q) || (p.replacePlate || '').toUpperCase().includes(q))
      );
    }
    return list;
  }, [applications, listFilter, statusFilter, searchKey]);

  useEffect(() => {
    if (!onRegisterBack) return undefined;
    onRegisterBack(() => {
      if (addMode) { closeAdd(); return true; }
      if (detailRow) { setDetailRow(null); return true; }
      return false;
    });
    return () => onRegisterBack(null);
  }, [addMode, detailRow, closeAdd, onRegisterBack]);

  const renderFormRow = (label, value, editor) => (
    <div className="xll-mod-form-row">
      <span className="xll-mod-form-label">{label}</span>
      {editor || <span className="xll-mod-form-value">{value || '—'}</span>}
    </div>
  );

  const renderPairForm = (pair, index) => {
    const newOptions = getNewOptionsForPair(pair);
    return (
      <div className="tc-section" key={pair.id}>
        <div className="tc-section-head">
          <span className="tc-section-title">车辆替换 · #{index + 1}</span>
          <span className="tc-section-badge tc-section-badge--rose">{pair.replaceType || '待填写'}</span>
        </div>
        <div className="vr-replace-swap">
          <div className="vr-replace-side vr-replace-side--old">
            <span className="vr-replace-side-tag">被替换</span>
            <div className="tc-vehicle-plate">{pair.originalPlate || '—'}</div>
            <div className="tc-vehicle-model">{pair.originalBrand || '—'} · {pair.originalModel || '—'}</div>
          </div>
          <div className="vr-replace-mid" aria-hidden="true"><span className="vr-replace-mid-icon">→</span></div>
          <div className="vr-replace-side vr-replace-side--new">
            <span className="vr-replace-side-tag">替换为</span>
            <div className="tc-vehicle-plate">{pair.replacePlate || '待选择'}</div>
            <div className="tc-vehicle-model">{pair.replaceBrand ? `${pair.replaceBrand} · ${pair.replaceModel}` : '选择新车后自动显示'}</div>
          </div>
        </div>
        <div className="tc-section-form">
          {renderFormRow('替换类型', null, (
            <select className="xll-mod-form-input" value={pair.replaceType} onChange={(e) => updatePair(pair.id, { replaceType: e.target.value })} aria-label="替换类型">
              <option value="">请选择</option>
              <option value="永久替换">永久替换</option>
              <option value="临时替换">临时替换</option>
            </select>
          ))}
          {renderFormRow('替换原因', null, (
            <select
              className="xll-mod-form-input"
              value={pair.replaceReason}
              onChange={(e) => updatePair(pair.id, { replaceReason: e.target.value, replaceFee: e.target.value === '客户原因' ? pair.replaceFee : '' })}
              aria-label="替换原因"
            >
              <option value="">请选择</option>
              <option value="客户原因">客户原因</option>
              <option value="车辆原因">车辆原因</option>
            </select>
          ))}
          {pair.replaceReason === '客户原因' && renderFormRow('替换费用', null, (
            <input
              className="xll-mod-form-input"
              value={pair.replaceFee}
              placeholder="请输入"
              onChange={(e) => {
                let val = e.target.value.replace(/[^\d.]/g, '');
                const parts = val.split('.');
                if (parts.length > 2) val = `${parts[0]}.${parts.slice(1).join('')}`;
                updatePair(pair.id, { replaceFee: val });
              }}
              aria-label="替换费用"
            />
          ))}
          <div className="xll-mod-form-row" style={{ flexDirection: 'column', alignItems: 'stretch', borderBottom: 'none' }}>
            <span className="xll-mod-form-label" style={{ marginBottom: 8 }}>替换原因说明</span>
            <textarea
              className="xll-vr-form-textarea"
              value={pair.replaceReasonDesc}
              maxLength={500}
              placeholder="请说明替换原因"
              onChange={(e) => updatePair(pair.id, { replaceReasonDesc: e.target.value })}
            />
          </div>
          {renderFormRow('新车', null, (
            <select
              className="xll-mod-form-input"
              value={pair.replacePlate}
              disabled={!pair.originalPlate}
              onChange={(e) => onReplacePlateChange(pair.id, e.target.value)}
              aria-label="新车"
            >
              <option value="">{projectInfo?.deliveryRegion ? `交车区域：${projectInfo.deliveryRegion}` : '请先选择被替换车辆'}</option>
              {newOptions.map((plate) => <option key={plate} value={plate}>{plate}</option>)}
            </select>
          ))}
        </div>
      </div>
    );
  };

  const activeTabLabel = VR_LIST_TABS.find((t) => t.key === listFilter)?.label || '';

  if (addMode) {
    return (
      <div className="xll-mod-root xll-vr-module xll-mod-form-page">
        <div className="xll-mod-detail-wrap">
          <div className="tc-scroll">
            <div className="tc-hero tc-hero--replace">
              <div className="tc-hero-label">新增替换车</div>
              <div className="tc-hero-amount">{pairs.length ? `${pairs.length} 台` : '—'}</div>
              <div className="tc-hero-meta">
                <span>{projectInfo ? projectInfo.customerName : '请选择被替换车辆'}</span>
              </div>
              {projectInfo && (
                <>
                  <div className="tc-hero-foot tc-hero-foot--compact">
                    <div className="tc-hero-foot-row"><span className="tc-hero-foot-label">合同编码</span>{projectInfo.contractCode || '—'}</div>
                    <div className="tc-hero-foot-row"><span className="tc-hero-foot-label">项目名称</span>{projectInfo.projectName || '—'}</div>
                    <div className="tc-hero-foot-row"><span className="tc-hero-foot-label">交车区域</span>{projectInfo.deliveryRegion || '—'}</div>
                  </div>
                  <div className="tc-hero-period">
                    <span className="tc-hero-period-tag">{projectInfo.projectType || '租赁'}</span>
                  </div>
                </>
              )}
            </div>
            <div className="tc-section">
              <div className="tc-section-head"><span className="tc-section-title">被替换车辆</span></div>
              <div className="tc-section-hint">车牌支持多选，每选中一辆生成一条替换明细；须为同一客户、同一项目。</div>
              <div className="tc-section-chips">
                {multiOldPlateOptions.map((plate) => (
                  <button
                    key={plate}
                    type="button"
                    className={`xll-mod-chip${selectedOriginalPlates.includes(plate) ? ' active' : ''}`}
                    onClick={() => toggleOriginalPlate(plate)}
                  >
                    {plate}
                  </button>
                ))}
              </div>
            </div>
            {pairs.length > 0 ? pairs.map(renderPairForm) : (
              <div className="xll-mod-empty" style={{ padding: '32px 24px' }}>请在上方选择被替换车辆车牌号，将自动生成替换明细</div>
            )}
            <div style={{ height: 16 }} />
          </div>
          <div className="xll-mod-action-bar">
            <button type="button" className="xll-mod-btn-ghost" onClick={() => closeAdd()}>取消</button>
            <button type="button" className="xll-mod-btn-ghost" onClick={handleSave}>保存</button>
            <button type="button" className="xll-mod-btn-rose" onClick={handleSubmit}>提交审核</button>
          </div>
        </div>
      </div>
    );
  }

  if (detailRow) {
    return (
      <div className="xll-mod-root xll-vr-module">
        <div className="xll-mod-detail-wrap">
          <ReplaceVehicleApprovePage task={vrRowToTask(detailRow)} mode="view" onBack={() => setDetailRow(null)} />
        </div>
      </div>
    );
  }

  return (
    <div className="xll-mod-root xll-vr-module" data-annotation-id="page-replace">
      <div className="xll-mod-tabs" role="tablist" aria-label="替换车列表分类">
        {VR_LIST_TABS.map((tab) => (
          <button
            key={tab.key}
            type="button"
            role="tab"
            aria-selected={listFilter === tab.key}
            className={`xll-mod-tab${listFilter === tab.key ? ' active' : ''}`}
            onClick={() => { setListFilter(tab.key); if (tab.key === 'history') setStatusFilter(''); }}
          >
            {tab.short}
            <span className="xll-mod-tab-count">{tab.key === 'ongoing' ? kpi.ongoing : kpi.history} 条</span>
          </button>
        ))}
      </div>
      <div className="xll-mod-toolbar">
        <div className="xll-mod-search">
          <IconSearch />
          <input type="search" placeholder="搜索单号、项目、车牌" value={searchKey} onChange={(e) => setSearchKey(e.target.value)} aria-label="搜索替换车" />
        </div>
        <div className="xll-mod-chips" role="group" aria-label="审批状态筛选">
          <button type="button" className={`xll-mod-chip${!statusFilter ? ' active' : ''}`} onClick={() => setStatusFilter('')}>全部</button>
          {listFilter === 'ongoing' && VR_STATUS_CHIPS.filter(Boolean).map((st) => (
            <button key={st} type="button" className={`xll-mod-chip${statusFilter === st ? ' active' : ''}`} onClick={() => setStatusFilter(statusFilter === st ? '' : st)}>{st}</button>
          ))}
        </div>
      </div>
      <div className="xll-mod-list-head">
        <span>{activeTabLabel}</span>
        <span style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
          <span>共 {filteredList.length} 条</span>
          <button type="button" className="xll-vr-add-btn" onClick={openAdd}>+ 新增</button>
        </span>
      </div>
      <div className="xll-mod-list">
        {filteredList.length === 0 ? (
          <div className="xll-mod-empty">
            暂无{activeTabLabel}申请
            <br />
            {searchKey || statusFilter ? '试试清空搜索或切换筛选' : '点击下方按钮发起替换车申请'}
            {!searchKey && !statusFilter && (
              <div style={{ marginTop: 12 }}>
                <button type="button" className="xll-vr-add-btn" onClick={openAdd}>+ 新增替换车</button>
              </div>
            )}
          </div>
        ) : (
          filteredList.map((row) => {
            const stCls = vrBizStatusClass(row.approvalStatus);
            const statusLabel = vrBizStatusLabel(row);
            const inProgress = vrIsOngoing(row.approvalStatus);
            const actionLabel = row.approvalStatus === '未提交' ? '继续编辑' : '查看';
            return (
              <div
                key={row.id}
                className="xll-mod-card"
                style={{ '--mod-accent': VR_ACCENT, '--mod-soft': VR_SOFT }}
                role="button"
                tabIndex={0}
                onClick={() => setDetailRow(row)}
                onKeyDown={(e) => e.key === 'Enter' && setDetailRow(row)}
              >
                <div className="xll-mod-card-head">
                  <span className="xll-mod-card-type">替换车申请</span>
                  <span className={`xll-mod-card-status ${stCls}${statusLabel !== row.approvalStatus ? ' with-approvers' : ''}`}>{statusLabel}</span>
                </div>
                <div className="xll-mod-card-title">{row.customerName || '—'}</div>
                <div className="xll-mod-card-sub">{row.projectName || '—'}</div>
                {(row.pairs || []).length > 0 && (
                  <div className="xll-mod-card-vehicles">
                    {(row.pairs || []).map((p) => (
                      <div key={p.id || `${p.originalPlate}-${p.replacePlate}`} className="xll-mod-card-vehicle-line">{p.originalPlate} → {p.replacePlate}</div>
                    ))}
                  </div>
                )}
                <div className="xll-mod-meta">
                  <div><span className="xll-mod-meta-label">发起人 </span><span className="xll-mod-meta-val">{row.creator}</span></div>
                  <div><span className="xll-mod-meta-label">替换车辆 </span><span className="xll-mod-meta-val" style={{ color: VR_ACCENT, fontWeight: 700 }}>{(row.pairs || []).length} 台</span></div>
                </div>
                <div className="xll-mod-card-foot">
                  <span style={{ fontSize: 12, color: COLOR_MUTED }}>发起时间 {row.createTime}</span>
                  {inProgress && (
                    <button type="button" className="xll-mod-card-btn" onClick={(e) => { e.stopPropagation(); row.approvalStatus === '未提交' ? openAdd() : setDetailRow(row); }}>
                      {actionLabel}
                    </button>
                  )}
                </div>
              </div>
            );
          })
        )}
      </div>
    </div>
  );
};

/* ── 车辆管理（参照 web端/车辆管理.jsx） ── */
const VM_OPERATE_STATUSES = ['租赁', '自营', '可运营', '待运营', '退出运营'];
const VM_VEHICLE_STATUSES = ['待验车', '未备车', '已备车', '待交车', '已交车', '待还车', '销售中', '替换中', '调拨中', '异动中'];
const VM_DETAIL_TABS = [
  { key: 'detail', label: '车辆详情' },
  { key: 'license', label: '证照信息' },
  { key: 'insurance', label: '保险信息' },
  { key: 'events', label: '车辆事件' },
];

const VM_CERT_NAV = [
  { key: 'driverLicense', label: '行驶证' },
  { key: 'transportLicense', label: '道路运输证' },
  { key: 'registrationCert', label: '登记证' },
  { key: 'specialEquipCert', label: '特种设备登记证' },
  { key: 'specialEquipDecal', label: '特种设备标识' },
  { key: 'hydrogenCard', label: '加氢卡' },
  { key: 'safetyValve', label: '安全阀' },
  { key: 'pressureGauge', label: '压力表' },
];

const VM_INSURANCE_TYPES = ['交强险', '商业险', '超赔险', '货物险', '乘意险'];

const VM_LICENSE_DATA = {
  '沪A03561F': {
    driverLicense: { photos: ['https://picsum.photos/seed/license1/600/400', 'https://picsum.photos/seed/license2/600/400'], regDate: '2024-06-05', issueDate: '2024-06-05', scrapDate: '2039-06-04', expireDate: '2026-06-30', updateUser: '李明辉', shNextEvaluation: '2026-12-05' },
    transportLicense: { photos: ['https://picsum.photos/seed/transport/600/400'], licenseNo: '交字310115102345号', issueDate: '2024-07-12', expireDate: '2026-07-31', inspectValidUntil: '2026-07-20', updateUser: '陈高伟' },
    registrationCert: { photos: ['https://picsum.photos/seed/regcert1/600/400'] },
    specialEquipCert: { photos: ['https://picsum.photos/seed/spec1/600/400'] },
    specialEquipDecal: { photos: ['https://picsum.photos/seed/spec2/600/400'], nextInspectDate: '2026-07-20' },
    hydrogenCard: { cardNo: 'H2-9988-7766-5544', cardType: '中石化加氢卡', balance: 12850.5, issueDate: '2025-01-15 14:30', issueUser: '能源管理部-张晓' },
    safetyValve: { photos: ['https://picsum.photos/seed/valve/600/400'], inspectDate: '2025-10-10', nextInspectDate: '2026-10-09' },
    pressureGauge: { photos: ['https://picsum.photos/seed/gauge/600/400'], inspectDate: '2025-12-15', nextInspectDate: '2026-06-14' },
  },
  '粤B58888F': {
    driverLicense: { photos: [], regDate: '', issueDate: '', scrapDate: '', expireDate: '' },
    transportLicense: { photos: ['https://picsum.photos/seed/trans_ocr/600/400'], licenseNo: '粤字440301102947号', issueDate: '2024-07-20', expireDate: '2026-07-20', inspectValidUntil: '2026-08-15', updateUser: '黄志杰' },
    registrationCert: { photos: [] },
    specialEquipCert: { photos: [] },
    specialEquipDecal: { photos: [], nextInspectDate: '' },
    hydrogenCard: { cardNo: 'H2-5566-4433-2211', cardType: '中石化加氢卡', balance: 5200, issueDate: '2025-03-10 10:15', issueUser: '能源管理部-张晓' },
    safetyValve: { photos: [], inspectDate: '', nextInspectDate: '' },
    pressureGauge: { photos: [], inspectDate: '', nextInspectDate: '' },
  },
  '苏E33333': {
    driverLicense: { photos: ['https://picsum.photos/seed/su_lic/600/400'], regDate: '2024-05-16', issueDate: '2024-05-16', scrapDate: '2039-05-15', expireDate: '2026-05-15', updateUser: '王东东' },
    transportLicense: { photos: ['https://picsum.photos/seed/su_trans/600/400'], licenseNo: '苏字320501104829号', issueDate: '2024-08-10', expireDate: '2026-08-10', inspectValidUntil: '2026-08-10', updateUser: '王东东' },
    registrationCert: { photos: [] },
    specialEquipCert: { photos: [] },
    specialEquipDecal: { photos: [], nextInspectDate: '' },
    hydrogenCard: { cardNo: '', cardType: '中石化加氢卡', balance: 0, issueDate: '', issueUser: '' },
    safetyValve: { photos: [], inspectDate: '', nextInspectDate: '' },
    pressureGauge: { photos: [], inspectDate: '', nextInspectDate: '' },
  },
  '浙F06900F': {
    driverLicense: { photos: ['https://picsum.photos/seed/zjf-lic/600/400'], regDate: '2025-07-01', issueDate: '2025-07-01', scrapDate: '2038-12-31', expireDate: '2027-07-31', updateUser: '金可鹏' },
    transportLicense: { photos: ['https://picsum.photos/seed/zjf-trans/600/400'], licenseNo: '浙字330482001234号', issueDate: '2025-07-15', expireDate: '2026-07-14', inspectValidUntil: '2026-07-20', updateUser: '金可鹏' },
    registrationCert: { photos: ['https://picsum.photos/seed/zjf-reg/600/400'] },
    specialEquipCert: { photos: [] },
    specialEquipDecal: { photos: ['https://picsum.photos/seed/zjf-decal/600/400'], nextInspectDate: '2026-07-20' },
    hydrogenCard: { cardNo: 'H2-3304-0690-0088', cardType: '中石化加氢卡', balance: 8650.5, issueDate: '2025-03-10 10:15', issueUser: '能源管理部-张晓' },
    safetyValve: { photos: ['https://picsum.photos/seed/zjf-valve/600/400'], inspectDate: '2025-05-10', nextInspectDate: '2026-05-09' },
    pressureGauge: { photos: ['https://picsum.photos/seed/zjf-gauge/600/400'], inspectDate: '2025-12-15', nextInspectDate: '2026-06-14' },
  },
};

const VM_EMPTY_LICENSE = {
  driverLicense: { photos: [], regDate: '', issueDate: '', scrapDate: '', expireDate: '' },
  transportLicense: { photos: [], licenseNo: '', issueDate: '', expireDate: '', inspectValidUntil: '' },
  registrationCert: { photos: [] },
  specialEquipCert: { photos: [] },
  specialEquipDecal: { photos: [], nextInspectDate: '' },
  hydrogenCard: { cardNo: '', cardType: '', balance: 0, issueDate: '', issueUser: '' },
  safetyValve: { photos: [], inspectDate: '', nextInspectDate: '' },
  pressureGauge: { photos: [], inspectDate: '', nextInspectDate: '' },
};

const vmGetLicenseBundle = (plateNo) => VM_LICENSE_DATA[plateNo] || VM_EMPTY_LICENSE;

const vmGetInsurancePolicies = (v) => {
  const base = v.plateNo.slice(-4);
  const premiums = { 交强险: '950.00', 商业险: '12800.50', 超赔险: '3200.00', 货物险: '1800.00', 乘意险: '560.00' };
  const ranges = {
    交强险: ['2025-01-01', '2025-12-31'],
    商业险: ['2025-01-01', '2025-12-31'],
    超赔险: ['2025-07-01', '2026-06-30'],
    货物险: ['2025-03-15', '2026-03-14'],
    乘意险: ['2025-09-01', '2026-08-31'],
  };
  return VM_INSURANCE_TYPES.map((type, idx) => ({
    type,
    company: '中国人民财产保险股份有限公司',
    policyNo: `PD${base}2025${String(idx + 1).padStart(3, '0')}`,
    startDate: ranges[type][0],
    endDate: ranges[type][1],
    premium: premiums[type],
    hasPdf: true,
  }));
};

const vmInsuranceStatus = (endDate) => {
  if (!endDate) return { label: '未投保', cls: 'empty' };
  const end = new Date(endDate);
  const now = new Date('2026-06-01');
  const days = Math.ceil((end - now) / (86400000));
  if (days < 0) return { label: '已过期', cls: 'warn' };
  if (days <= 30) return { label: '即将到期', cls: 'warn' };
  return { label: '保障中', cls: '' };
};

const VM_EVENTS_BY_PLATE = {
  '粤B58888F': [
    { type: '入库', time: '2023-06-15 10:00', summary: '采购入库完成', operator: '仓储-刘工', extra: '入库单号 RK-2023-0615' },
    { type: '备车', time: '2023-07-01 14:30', summary: '整备完成，状态变更为已备车', operator: '运维-王东东' },
    { type: '交车', time: '2024-01-10 09:30', summary: '交付嘉兴某某物流有限公司', operator: '张三', extra: '交车里程 12000 KM' },
    { type: '保养', time: '2024-08-20 11:00', summary: '二保完成 · 嘉兴机动车检测站', operator: '李四' },
    { type: '年审', time: '2025-07-18 15:20', summary: '年审办理完成，检验有效期至 2026-07', operator: '张明辉' },
    { type: '故障', time: '2026-03-05 08:45', summary: '氢系统压力异常 · 已提报维修', operator: '司机-赵某' },
  ],
  '沪A03561F': [
    { type: '入库', time: '2022-08-20 09:00', summary: '采购入库', operator: '系统' },
    { type: '交车', time: '2024-01-05 10:00', summary: '交付上海迅杰氢能物流运输有限公司', operator: '李晓彤', extra: '合同 HT-2024-002' },
    { type: '还车', time: '2024-01-20 16:00', summary: '客户临时还车', operator: '李晓彤', extra: '还车里程 25600 KM' },
    { type: '维修', time: '2025-02-12 13:30', summary: '更换电堆冷却液', operator: '维修站-周工' },
    { type: '年审', time: '2025-09-10 09:15', summary: '年审完成', operator: '陈高伟' },
  ],
};

const vmGetVehicleEvents = (v) => VM_EVENTS_BY_PLATE[v.plateNo] || [
  { type: '入库', time: v.purchaseDate ? `${v.purchaseDate} 09:00` : '—', summary: '车辆采购入库', operator: '系统' },
  { type: '交车', time: v.lastDeliveryTime || '—', summary: vmHasCustomer(v.customer) ? `交付 ${vmFormatStatus(v.customer)}` : '暂无交车记录', operator: vmFormatStatus(v.manager), extra: v.lastDeliveryMile ? `交车里程 ${v.lastDeliveryMile} KM` : '' },
  { type: '还车', time: v.lastReturnTime || '—', summary: '最近一次还车', operator: '—', extra: v.lastReturnMile ? `还车里程 ${v.lastReturnMile} KM` : '' },
];

const VmDetailKv = ({ label, value, full }) => (
  <div className={`xll-vm-detail-kv${full ? ' full' : ''}`}>
    <div className="xll-vm-detail-kv-label">{label}</div>
    <div className="xll-vm-detail-kv-val">{value || '—'}</div>
  </div>
);

const VM_MOCK_VEHICLES = [
  { id: '1', region: '广东省/深圳市', vin: 'LGHXCAE28M6789012', plateNo: '粤B58888F', vehicleNo: '22FHD0001', vehicleType: '厢式货车', brand: '福田', model: '奥铃4.5吨冷藏车', color: '白色', parking: '福田停车场', customer: '嘉兴某某物流有限公司', department: '华南区', manager: '张三', operateStatus: '租赁', vehicleStatus: '已交车', outStatus: '无', licenseStatus: '正常', insuranceStatus: '正常', ownership: '浙江羚牛氢能科技有限公司', operateCompany: '羚牛运营（嘉兴）', vehicleSource: '自有', leaseCompany: '嘉兴某某物流有限公司', onlineStatus: '在线', year: '2023', mileage: '12580.50', purchaseDate: '2023-06-15', regDate: '2023-07-01', inspectExpire: '2026-07', lastDeliveryTime: '2024-01-10', lastDeliveryMile: '12000.00', lastReturnTime: '2024-02-01', lastReturnMile: '12580.50', scrapDate: '2038-12-31', contractNo: 'HT-ZL-2025-088', location: '广东省深圳市南山区科技园南路', gpsTime: '2026-06-01 14:30', fuelType: '氢' },
  { id: '2', region: '上海市/上海市', vin: 'LMRKH9AC0R1004086', plateNo: '沪A03561F', vehicleNo: '22FHD0002', vehicleType: '牵引车头', brand: '宇通', model: '49吨牵引车头', color: '白色', parking: '浦东停车场', customer: '上海迅杰氢能物流运输有限公司', department: '华东区', manager: '李晓彤', operateStatus: '租赁', vehicleStatus: '已交车', outStatus: '无', licenseStatus: '正常', insuranceStatus: '正常', ownership: '浙江羚牛氢能科技有限公司', operateCompany: '羚牛运营（上海）', vehicleSource: '自有', leaseCompany: '上海迅杰氢能物流运输有限公司', onlineStatus: '在线', year: '2022', mileage: '25600.00', purchaseDate: '2022-08-20', regDate: '2022-09-01', inspectExpire: '2026-09', lastDeliveryTime: '2024-01-05', lastDeliveryMile: '25500.00', lastReturnTime: '2024-01-20', lastReturnMile: '25600.00', scrapDate: '2037-09-30', contractNo: 'HT-2024-002', location: '上海市浦东新区张江高科路500号', gpsTime: '2026-06-01 09:45', fuelType: '氢' },
  { id: '3', region: '江苏省/苏州市', vin: 'LSXCH9AE8M1094857', plateNo: '苏E33333', vehicleNo: 'V003', vehicleType: '牵引车', brand: '陕汽', model: '德龙X3000混动牵引车', color: '灰色', parking: '-', customer: '无', department: '无', manager: '-', operateStatus: '可运营', vehicleStatus: '未备车', outStatus: '无', licenseStatus: '正常', insuranceStatus: '正常', ownership: '浙江羚牛氢能科技有限公司', operateCompany: '羚牛运营（嘉兴）', vehicleSource: '外租', leaseCompany: '-', onlineStatus: '离线', year: '2022', mileage: '8320.00', purchaseDate: '2023-09-01', regDate: '2023-09-20', inspectExpire: '2026-05', lastDeliveryTime: '2024-02-05', lastDeliveryMile: '8100.00', lastReturnTime: '2024-02-10', lastReturnMile: '8320.00', scrapDate: '2039-09-30', contractNo: '-', location: '江苏省苏州市工业园区', gpsTime: '2026-05-28 10:15', fuelType: '氢' },
  { id: '4', region: '浙江省/嘉兴市', vin: 'LA9HE60A0NBAF4031', plateNo: '浙F06900F', vehicleNo: '22FHD0007', vehicleType: '18吨双飞翼货车', brand: '苏龙', model: '海格牌KLQ5180XYKFCEV', color: '白色', parking: '嘉兴港区停车场', customer: '上海迅杰物流有限公司', department: '业务三部', manager: '金可鹏', operateStatus: '租赁', vehicleStatus: '已交车', outStatus: '无', licenseStatus: '正常', insuranceStatus: '正常', ownership: '浙江羚牛氢能科技有限公司', operateCompany: '羚牛运营（嘉兴）', vehicleSource: '自有', leaseCompany: '上海迅杰物流有限公司', onlineStatus: '在线', year: '2025', mileage: '5600.00', purchaseDate: '2025-03-08', regDate: '2025-04-01', inspectExpire: '2027-09', lastDeliveryTime: '2026-02-02', lastDeliveryMile: '5200.00', lastReturnTime: '2026-02-11', lastReturnMile: '5600.00', scrapDate: '2038-04-30', contractNo: 'LNZLHTSH2023071301', location: '浙江省嘉兴市平湖市港区', gpsTime: '2026-06-01 11:20', fuelType: '氢' },
  { id: '5', region: '广东省/广州市', vin: 'LSJA24U70PS001234', plateNo: '粤A88K88', vehicleNo: 'V005', vehicleType: '厢式货车', brand: '比亚迪', model: 'T5纯电轻卡', color: '灰色', parking: '黄埔停车场', customer: '客户A', department: '华南区', manager: '王五', operateStatus: '租赁', vehicleStatus: '已交车', outStatus: '无', licenseStatus: '正常', insuranceStatus: '正常', ownership: '某某租赁公司', operateCompany: '羚牛运营（广东）', vehicleSource: '挂靠', leaseCompany: '第三方融资租赁有限公司', onlineStatus: '在线', year: '2023', mileage: '45200.80', purchaseDate: '2021-05-20', regDate: '2021-06-15', inspectExpire: '2026-06', lastDeliveryTime: '2024-02-01', lastDeliveryMile: '44800.00', lastReturnTime: '2024-02-08', lastReturnMile: '45200.80', scrapDate: '2036-06-30', contractNo: 'HT-2024-003', location: '广东省广州市黄埔区开泰大道200号', gpsTime: '2026-06-01 09:45', fuelType: '电' },
  { id: '6', region: '广东省/深圳市', vin: '5YJ3E1EA1NF123456', plateNo: '粤B12345', vehicleNo: '-', vehicleType: '小型轿车', brand: '特斯拉', model: 'Model 3', color: '红色', parking: '福田停车场', customer: '客户B', department: '华南区', manager: '李四', operateStatus: '租赁', vehicleStatus: '已交车', outStatus: '无', licenseStatus: '正常', insuranceStatus: '正常', ownership: '某某租赁公司', operateCompany: '羚牛运营（广东）', vehicleSource: '挂靠', leaseCompany: '某某科技有限公司', onlineStatus: '在线', year: '2023', mileage: '5600.00', purchaseDate: '2023-03-08', regDate: '2023-04-01', inspectExpire: '2025-04', lastDeliveryTime: '2024-02-02', lastDeliveryMile: '5200.00', lastReturnTime: '2024-02-11', lastReturnMile: '5600.00', scrapDate: '2038-04-30', contractNo: 'HT-2024-004', location: '广东省深圳市福田区福华路188号', gpsTime: '2026-06-01 11:20', fuelType: '电' },
  { id: '7', region: '广东省/广州市', vin: 'LGWEF4A59NS234567', plateNo: '粤A99A99', vehicleNo: 'V007', vehicleType: '小型轿车', brand: '比亚迪', model: '汉EV', color: '黑色', parking: '天河停车场', customer: '客户A', department: '华南区', manager: '张三', operateStatus: '可运营', vehicleStatus: '待还车', outStatus: '无', licenseStatus: '正常', insuranceStatus: '正常', ownership: '某某租赁公司', operateCompany: '羚牛运营（嘉兴）', vehicleSource: '自有', leaseCompany: '第三方融资租赁有限公司', onlineStatus: '离线', year: '2022', mileage: '22100.30', purchaseDate: '2022-07-15', regDate: '2022-08-01', inspectExpire: '2026-08', lastDeliveryTime: '2024-01-20', lastDeliveryMile: '21800.00', lastReturnTime: '2024-02-05', lastReturnMile: '22100.30', scrapDate: '2037-08-31', contractNo: '-', location: '广东省广州市天河区体育西路200号', gpsTime: '2026-05-31 18:30', fuelType: '电' },
  { id: '8', region: '山东省/临沂市', vin: 'LZZ5CLSB8NC778899', plateNo: '鲁Q88901', vehicleNo: 'V008', vehicleType: '牵引车', brand: '重汽', model: '豪沃T7H牵引车', color: '蓝色', parking: '-', customer: '无', department: '无', manager: '-', operateStatus: '租赁', vehicleStatus: '调拨中', outStatus: '无', licenseStatus: '异常', insuranceStatus: '正常', ownership: '某某租赁公司', operateCompany: '羚牛运营（嘉兴）', vehicleSource: '自有', leaseCompany: '-', onlineStatus: '离线', year: '2021', mileage: '67800.25', purchaseDate: '2020-06-10', regDate: '2020-07-01', inspectExpire: '2026-04', lastDeliveryTime: '2024-01-25', lastDeliveryMile: '67500.00', lastReturnTime: '2024-02-09', lastReturnMile: '67800.25', scrapDate: '2035-07-31', contractNo: 'HT-2024-011', location: '山东省临沂市兰山区', gpsTime: '2026-05-30 08:20', fuelType: '氢' },
  { id: '9', region: '福建省/厦门市', vin: 'LFWNHXSD8P1122334', plateNo: '闽D55662', vehicleNo: 'V009', vehicleType: '厢式货车', brand: '金龙', model: '凯歌纯电动厢货', color: '白色', parking: '厦门停车场', customer: '客户C', department: '华东区', manager: '赵六', operateStatus: '自营', vehicleStatus: '已备车', outStatus: '无', licenseStatus: '正常', insuranceStatus: '正常', ownership: '某某科技有限公司', operateCompany: '羚牛运营（上海）', vehicleSource: '外租', leaseCompany: '某某租赁公司', onlineStatus: '在线', year: '2022', mileage: '19800.00', purchaseDate: '2022-12-01', regDate: '2023-01-05', inspectExpire: '2026-04', lastDeliveryTime: '2024-02-07', lastDeliveryMile: '19500.00', lastReturnTime: '2024-02-12', lastReturnMile: '19800.00', scrapDate: '2038-01-31', contractNo: 'HT-2024-008', location: '福建省厦门市思明区', gpsTime: '2026-06-01 15:45', fuelType: '电' },
  { id: '10', region: '安徽省/合肥市', vin: 'LZZ5CLSB8NA123456', plateNo: '皖B66221', vehicleNo: 'V010', vehicleType: '厢式货车', brand: '江淮', model: '格尔发A5', color: '白色', parking: '-', customer: '无', department: '无', manager: '-', operateStatus: '可运营', vehicleStatus: '未备车', outStatus: '无', licenseStatus: '正常', insuranceStatus: '正常', ownership: '某某租赁公司', operateCompany: '羚牛运营（广东）', vehicleSource: '挂靠', leaseCompany: '-', onlineStatus: '离线', year: '2020', mileage: '52100.00', purchaseDate: '2020-09-01', regDate: '2020-10-01', inspectExpire: '2026-06', lastDeliveryTime: '2024-01-12', lastDeliveryMile: '51800.00', lastReturnTime: '2024-01-30', lastReturnMile: '52100.00', scrapDate: '2035-10-31', contractNo: '-', location: '安徽省合肥市蜀山区', gpsTime: '2026-05-29 11:00', fuelType: '氢' },
  { id: '11', region: '广东省/广州市', vin: 'LSJA24U70PS555666', plateNo: '粤A11B22', vehicleNo: '-', vehicleType: '小型轿车', brand: '蔚来', model: 'ET5', color: '蓝色', parking: '番禺停车场', customer: '客户A', department: '华南区', manager: '王五', operateStatus: '租赁', vehicleStatus: '替换中', outStatus: '无', licenseStatus: '正常', insuranceStatus: '正常', ownership: '某某租赁公司', operateCompany: '羚牛运营（上海）', vehicleSource: '外租', leaseCompany: '第三方融资租赁有限公司', onlineStatus: '在线', year: '2023', mileage: '7200.50', purchaseDate: '2023-07-20', regDate: '2023-08-05', inspectExpire: '2025-08', lastDeliveryTime: '2024-02-03', lastDeliveryMile: '7000.00', lastReturnTime: '2024-02-11', lastReturnMile: '7200.50', scrapDate: '2038-08-31', contractNo: 'HT-2024-007', location: '广东省广州市番禺区市桥街100号', gpsTime: '2026-06-01 12:00', fuelType: '电' },
  { id: '12', region: '北京市/北京市', vin: 'LSJA24U70PS999000', plateNo: '京H88888', vehicleNo: '-', vehicleType: '小型轿车', brand: '蔚来', model: 'ET5', color: '白色', parking: '昌平停车场', customer: '客户D', department: '华北区', manager: '孙七', operateStatus: '退出运营', vehicleStatus: '无', outStatus: '报废出库', licenseStatus: '无', insuranceStatus: '正常', ownership: '某某科技有限公司', operateCompany: '羚牛运营（上海）', vehicleSource: '外租', leaseCompany: '-', onlineStatus: '离线', year: '2022', mileage: '15600.00', purchaseDate: '2022-06-20', regDate: '2022-07-10', inspectExpire: '2024-07', lastDeliveryTime: '2024-01-10', lastDeliveryMile: '15300.00', lastReturnTime: '2024-01-28', lastReturnMile: '15600.00', scrapDate: '2037-07-31', contractNo: '-', location: '北京市昌平区回龙观西大街100号', gpsTime: '2026-05-28 12:30', fuelType: '电' },
];

const vmFormatStatus = (val) => (val === '无' || val === '-' ? '—' : (val || '—'));

const vmHasCustomer = (name) => vmFormatStatus(name) !== '—';

const VM_LONG_TEXT_LABELS = new Set(['客户名称', '登记所有权', '运营公司', '租赁公司', '当前位置', '型号', '车辆类型']);

const vmOperateBadgeClass = (status) => {
  if (status === '租赁') return 'xll-vm-badge';
  if (status === '自营') return 'xll-vm-badge warn';
  if (status === '退出运营') return 'xll-vm-badge danger';
  return 'xll-vm-badge neutral';
};

const VM_REGION_COORD = {
  '广东省/深圳市': { lat: 22.5431, lng: 114.0579 },
  '上海市/上海市': { lat: 31.2304, lng: 121.4737 },
  '江苏省/苏州市': { lat: 31.2989, lng: 120.5853 },
  '浙江省/嘉兴市': { lat: 30.7428, lng: 121.0562 },
  '广东省/广州市': { lat: 23.1291, lng: 113.2644 },
  '山东省/临沂市': { lat: 35.1047, lng: 118.3564 },
  '福建省/厦门市': { lat: 24.4798, lng: 118.0894 },
  '安徽省/合肥市': { lat: 31.8206, lng: 117.2272 },
  '北京市/北京市': { lat: 39.9042, lng: 116.4074 },
};

const vmResolveVehicleLocation = (vehicle) => {
  const v = vehicle || {};
  const address = String(v.location || '').trim() || (v.region ? v.region.replace(/\//g, '') : '暂未获取定位');
  const base = VM_REGION_COORD[v.region] || { lat: 30.7428, lng: 121.0562 };
  const idNum = Number(v.id) || 1;
  const online = v.onlineStatus === '在线';
  return {
    address,
    lat: base.lat + ((idNum % 7) - 3) * 0.008,
    lng: base.lng + ((idNum % 5) - 2) * 0.009,
    plateNo: v.plateNo || '',
    gpsTime: v.gpsTime || '—',
    online,
    pending: !online,
  };
};

const VmVehicleLocationMap = ({ vehicle, compact = false }) => {
  const loc = vmResolveVehicleLocation(vehicle);
  return (
    <div className="xll-dv-delivery-location">
      {!compact ? (
        <div className="xll-vm-location-head">
          <div className="xll-mod-section-title" style={{ margin: 0 }}>车辆位置</div>
          <span className={`xll-vm-online${loc.online ? '' : ''}`}>
            <span className={`xll-vm-dot ${loc.online ? 'on' : 'off'}`} />
            {loc.online ? 'GPS 在线' : 'GPS 离线'}
          </span>
        </div>
      ) : null}
      <div
        className={`xll-dv-delivery-map xll-vm-location-map${compact ? ' xll-vm-location-map--compact' : ''}${loc.pending ? ' xll-dv-delivery-map--pending' : ''}`}
        role="img"
        aria-label={`车辆位置地图，${loc.address}`}
      >
        <div className="xll-dv-delivery-map-canvas" aria-hidden>
          <div className="xll-dv-delivery-map-water" />
          <div className="xll-dv-delivery-map-road xll-dv-delivery-map-road--h" />
          <div className="xll-dv-delivery-map-road xll-dv-delivery-map-road--v" />
          <div className="xll-dv-delivery-map-road xll-dv-delivery-map-road--d" />
        </div>
        {!loc.pending ? (
          <span className="xll-dv-delivery-map-marker" title={loc.plateNo || '当前车辆位置'}>
            <span className="xll-dv-delivery-map-marker-pin" />
          </span>
        ) : null}
        <div className="xll-dv-delivery-map-foot">
          <span><strong>{loc.address}</strong></span>
          {!loc.pending ? <span>{loc.lat.toFixed(4)}, {loc.lng.toFixed(4)}</span> : null}
        </div>
        <span className="xll-map-brand">腾讯地图</span>
      </div>
      {!compact && loc.gpsTime && loc.gpsTime !== '—' ? (
        <div className="xll-vm-location-meta">GPS 上传时间：{loc.gpsTime}</div>
      ) : null}
    </div>
  );
};

const VmCardLocation = ({ vehicle }) => {
  const loc = vmResolveVehicleLocation(vehicle);
  if (!vehicle?.location && !vehicle?.region) return null;
  return (
    <div className="xll-vm-card-location">
      <span className="xll-vm-location-pin" aria-hidden="true" />
      <div className="xll-vm-card-location-body">
        <span className="xll-vm-card-location-text" title={loc.address}>{loc.address}</span>
        <span className="xll-vm-card-location-time">
          {loc.online ? 'GPS 在线' : 'GPS 离线'}
          {loc.gpsTime && loc.gpsTime !== '—' ? ` · ${loc.gpsTime}` : ''}
        </span>
      </div>
    </div>
  );
};

const VmCustomerBar = ({ name, variant = 'list', onExpand }) => {
  const display = vmFormatStatus(name);
  if (!vmHasCustomer(name)) return null;
  const isLong = display.length > 14;
  const handleExpand = (e) => {
    if (!isLong || !onExpand) return;
    e.stopPropagation();
    onExpand(display);
  };
  return (
    <div
      className={`xll-vm-customer-bar xll-vm-customer-bar--${variant}${isLong && onExpand ? ' is-long' : ''}`}
      onClick={handleExpand}
      onKeyDown={(e) => e.key === 'Enter' && handleExpand(e)}
      role={isLong && onExpand ? 'button' : undefined}
      tabIndex={isLong && onExpand ? 0 : undefined}
    >
      <span className="xll-vm-customer-icon" aria-hidden="true">客</span>
      <div className="xll-vm-customer-body">
        <span className="xll-vm-customer-label">客户名称</span>
        <span className="xll-vm-customer-text" title={display}>{display}</span>
        {isLong && onExpand && variant === 'list' ? <span className="xll-vm-customer-hint">点击查看全称</span> : null}
      </div>
    </div>
  );
};

const VmInfoRow = ({ label, value, onClick, longText }) => (
  <div className={`xll-mod-form-row${longText ? ' xll-mod-form-row--stack' : ''}`}>
    <span className="xll-mod-form-label">{label}</span>
    {onClick ? (
      <button type="button" className="xll-mod-form-value" style={{ border: 'none', background: 'transparent', color: XLL_GREEN, textAlign: longText ? 'left' : 'right', cursor: 'pointer', padding: 0 }} onClick={onClick}>{value}</button>
    ) : (
      <span className="xll-mod-form-value">{value}</span>
    )}
  </div>
);

const VehicleManagementModule = ({ onRegisterBack }) => {
  const vehicles = VM_MOCK_VEHICLES;
  const [searchKey, setSearchKey] = useState('');
  const [operateFilter, setOperateFilter] = useState('');
  const [vehicleStatusFilter, setVehicleStatusFilter] = useState('');
  const [onlineFilter, setOnlineFilter] = useState('');
  const [filterDrawerOpen, setFilterDrawerOpen] = useState(false);
  const [detailVehicle, setDetailVehicle] = useState(null);
  const [detailTab, setDetailTab] = useState('detail');
  const [licenseNav, setLicenseNav] = useState('driverLicense');
  const [customerNamePreview, setCustomerNamePreview] = useState(null);

  const baseFilteredList = useMemo(() => {
    const q = searchKey.trim().toLowerCase();
    return vehicles.filter((v) => {
      if (operateFilter && v.operateStatus !== operateFilter) return false;
      if (vehicleStatusFilter && v.vehicleStatus !== vehicleStatusFilter) return false;
      if (!q) return true;
      return (
        (v.plateNo && v.plateNo.toLowerCase().includes(q))
        || (v.vin && v.vin.toLowerCase().includes(q))
        || (v.brand && v.brand.toLowerCase().includes(q))
        || (v.model && v.model.toLowerCase().includes(q))
        || (v.customer && v.customer.toLowerCase().includes(q))
        || (v.region && v.region.toLowerCase().includes(q))
        || (v.location && v.location.toLowerCase().includes(q))
      );
    });
  }, [vehicles, searchKey, operateFilter, vehicleStatusFilter]);

  const vmSummary = useMemo(() => ({
    total: baseFilteredList.length,
    online: baseFilteredList.filter((v) => v.onlineStatus === '在线').length,
    offline: baseFilteredList.filter((v) => v.onlineStatus === '离线').length,
    licenseWarn: baseFilteredList.filter((v) => v.licenseStatus === '异常').length,
    insuranceWarn: baseFilteredList.filter((v) => v.insuranceStatus === '异常').length,
  }), [baseFilteredList]);

  const filteredList = useMemo(() => {
    if (!onlineFilter) return baseFilteredList;
    return baseFilteredList.filter((v) => v.onlineStatus === onlineFilter);
  }, [baseFilteredList, onlineFilter]);

  const activeFilterCount = (operateFilter ? 1 : 0) + (vehicleStatusFilter ? 1 : 0) + (onlineFilter ? 1 : 0);

  const openDetail = useCallback((v) => {
    setDetailTab('detail');
    setLicenseNav('driverLicense');
    setDetailVehicle(v);
  }, []);

  useEffect(() => {
    if (!onRegisterBack) return undefined;
    onRegisterBack(() => {
      if (customerNamePreview) { setCustomerNamePreview(null); return true; }
      if (detailVehicle) { setDetailVehicle(null); return true; }
      if (filterDrawerOpen) { setFilterDrawerOpen(false); return true; }
      return false;
    });
    return () => onRegisterBack(null);
  }, [detailVehicle, customerNamePreview, filterDrawerOpen, onRegisterBack]);

  const renderDetailTab = (v) => (
    <>
      <div className="xll-vm-section-card" data-annotation-id="page-vehicle-detail-location">
        <VmVehicleLocationMap vehicle={v} />
      </div>
      <div className="xll-vm-section-card">
        <div className="xll-mod-section-title">运营信息</div>
        <div className="xll-vm-detail-grid">
          <VmDetailKv label="运营城市" value={v.region ? v.region.replace(/\//g, ' · ') : '—'} />
          <VmDetailKv label="运营状态" value={v.operateStatus} />
          <VmDetailKv label="车辆状态" value={vmFormatStatus(v.vehicleStatus)} />
          <VmDetailKv label="出库状态" value={vmFormatStatus(v.outStatus)} />
          <VmDetailKv label="证照状态" value={vmFormatStatus(v.licenseStatus)} />
          <VmDetailKv label="保险状态" value={vmFormatStatus(v.insuranceStatus)} />
          <VmDetailKv label="在线状态" value={v.onlineStatus} />
          <VmDetailKv label="客户名称" value={vmFormatStatus(v.customer)} full />
          <VmDetailKv label="业务部门" value={vmFormatStatus(v.department)} />
          <VmDetailKv label="业务负责人" value={vmFormatStatus(v.manager)} />
          <VmDetailKv label="合同编号" value={v.contractNo && v.contractNo !== '-' ? v.contractNo : '—'} full />
        </div>
      </div>
      <div className="xll-vm-section-card">
        <div className="xll-mod-section-title">车辆档案</div>
        <div className="xll-vm-detail-grid">
          <VmDetailKv label="车辆识别代号" value={v.vin} full />
          <VmDetailKv label="车牌号" value={v.plateNo} />
          <VmDetailKv label="车辆编号" value={vmFormatStatus(v.vehicleNo)} />
          <VmDetailKv label="品牌" value={v.brand} />
          <VmDetailKv label="型号" value={v.model} full />
          <VmDetailKv label="车辆类型" value={v.vehicleType} />
          <VmDetailKv label="车身颜色" value={v.color} />
          <VmDetailKv label="燃料种类" value={v.fuelType || '—'} />
          <VmDetailKv label="出厂年份" value={v.year} />
          <VmDetailKv label="行驶公里数" value={v.mileage ? `${v.mileage} KM` : '—'} />
        </div>
      </div>
      <div className="xll-vm-section-card">
        <div className="xll-mod-section-title">权属与停放</div>
        <div className="xll-vm-detail-grid">
          <VmDetailKv label="登记所有权" value={v.ownership || '—'} full />
          <VmDetailKv label="运营公司" value={v.operateCompany} full />
          <VmDetailKv label="车辆来源" value={v.vehicleSource} />
          <VmDetailKv label="租赁公司" value={vmFormatStatus(v.leaseCompany)} full />
          <VmDetailKv label="归属停车场" value={vmFormatStatus(v.parking)} />
          <VmDetailKv label="采购入库时间" value={v.purchaseDate} />
          <VmDetailKv label="行驶证注册" value={v.regDate} />
          <VmDetailKv label="检验有效期" value={v.inspectExpire} />
          <VmDetailKv label="强制报废日期" value={v.scrapDate} />
        </div>
      </div>
      <div className="xll-vm-section-card" style={{ marginBottom: 12 }}>
        <div className="xll-mod-section-title">交还车记录</div>
        <div className="xll-vm-detail-grid">
          <VmDetailKv label="上次交车时间" value={v.lastDeliveryTime || '—'} />
          <VmDetailKv label="上次交车里程" value={v.lastDeliveryMile ? `${v.lastDeliveryMile} KM` : '—'} />
          <VmDetailKv label="上次还车时间" value={v.lastReturnTime || '—'} />
          <VmDetailKv label="上次还车里程" value={v.lastReturnMile ? `${v.lastReturnMile} KM` : '—'} />
        </div>
      </div>
    </>
  );

  const renderCertFields = (navKey, data) => {
    const d = data[navKey] || {};
    const photos = d.photos || [];
    const rows = [];
    if (navKey === 'driverLicense') {
      rows.push(['注册日期', d.regDate], ['发证日期', d.issueDate], ['强制报废期', d.scrapDate], ['检验有效期', d.expireDate], ['上海下次等评', d.shNextEvaluation], ['更新人', d.updateUser]);
    } else if (navKey === 'transportLicense') {
      rows.push(['证件编号', d.licenseNo], ['发证日期', d.issueDate], ['证件有效期', d.expireDate], ['审验有效期', d.inspectValidUntil], ['更新人', d.updateUser]);
    } else if (navKey === 'hydrogenCard') {
      rows.push(['卡号', d.cardNo], ['卡类型', d.cardType], ['余额（￥）', d.balance != null && d.balance !== '' ? formatMoneySymbol(d.balance) : ''], ['发卡时间', d.issueDate], ['发卡人', d.issueUser]);
    } else if (navKey === 'safetyValve' || navKey === 'pressureGauge') {
      rows.push(['检验日期', d.inspectDate], ['下次检验日期', d.nextInspectDate]);
    } else if (navKey === 'specialEquipDecal') {
      rows.push(['下次检验日期', d.nextInspectDate], ['更新人', d.updateUser]);
    }
    return (
      <>
        {navKey !== 'hydrogenCard' && (
          photos.length > 0 ? (
            <div className="xll-vm-photo-grid">
              {photos.map((url, i) => (
                <div className="xll-vm-photo" key={i}><img src={url} alt="" /></div>
              ))}
            </div>
          ) : (
            <div className="xll-vm-photo-empty">暂无证照影像</div>
          )
        )}
        {rows.map(([label, val]) => (
          <VmInfoRow key={label} label={label} value={val || '—'} longText={label.includes('编号') || label.includes('卡号')} />
        ))}
      </>
    );
  };

  const renderLicenseTab = (v) => {
    const bundle = vmGetLicenseBundle(v.plateNo);
    const navLabel = VM_CERT_NAV.find((n) => n.key === licenseNav)?.label || '';
    return (
      <div className="xll-mod-section" style={{ margin: '12px 14px 0' }}>
        <div className="xll-mod-section-title">证照档案 · {v.plateNo}</div>
        <div className="xll-vm-cert-nav" role="tablist">
          {VM_CERT_NAV.map((item) => (
            <button key={item.key} type="button" role="tab" aria-selected={licenseNav === item.key} className={`xll-vm-cert-nav-btn${licenseNav === item.key ? ' active' : ''}`} onClick={() => setLicenseNav(item.key)}>{item.label}</button>
          ))}
        </div>
        <div className="xll-mod-section-title" style={{ fontSize: 13, marginBottom: 10 }}>{navLabel}</div>
        {renderCertFields(licenseNav, bundle)}
      </div>
    );
  };

  const renderInsuranceTab = (v) => {
    const policies = vmGetInsurancePolicies(v);
    return (
      <div style={{ margin: '12px 14px 0' }}>
        {policies.map((p) => {
          const st = vmInsuranceStatus(p.endDate);
          return (
            <div className="xll-vm-ins-card" key={p.type}>
              <div className="xll-vm-ins-head">
                <span className="xll-vm-ins-type">{p.type}</span>
                <span className={`xll-vm-ins-status ${st.cls}`}>{st.label}</span>
              </div>
              {p.startDate ? (
                <>
                  <div className="xll-vm-ins-row"><span>保险期间</span><span>{p.startDate} 至 {p.endDate}</span></div>
                  <div className="xll-vm-ins-row"><span>承保公司</span><span style={{ textAlign: 'right', maxWidth: '58%' }}>{p.company}</span></div>
                  <div className="xll-vm-ins-row"><span>保单号</span><span>{p.policyNo}</span></div>
                  <div className="xll-vm-ins-row"><span>保费</span><span>{p.premium ? formatMoneySymbol(p.premium) : '—'}</span></div>
                  {p.hasPdf && (
                    <button type="button" className="xll-vm-ins-pdf" onClick={() => mpToast.success(`${p.type} PDF 保单下载已开始（原型）`)}>下载 PDF 保单</button>
                  )}
                </>
              ) : (
                <div className="xll-vm-ins-row"><span>暂无有效保单</span></div>
              )}
            </div>
          );
        })}
      </div>
    );
  };

  const renderEventsTab = (v) => {
    const events = vmGetVehicleEvents(v);
    return (
      <div className="xll-mod-section" style={{ margin: '12px 14px 0' }}>
        <div className="xll-mod-section-title">全生命周期事件</div>
        <div className="xll-vm-event-list">
          {events.map((ev, i) => (
            <div className="xll-vm-event-item" key={`${ev.type}-${i}`}>
              <span className="xll-vm-event-dot" aria-hidden="true" />
              <div className="xll-vm-event-type">{ev.type}</div>
              <div className="xll-vm-event-meta">{ev.time} · {ev.operator}</div>
              <div className="xll-vm-event-summary">{ev.summary}</div>
              {ev.extra ? <div className="xll-vm-event-meta" style={{ marginTop: 4 }}>{ev.extra}</div> : null}
            </div>
          ))}
        </div>
      </div>
    );
  };

  const renderNamePreviewModal = () => (Modal ? (
    <Modal
      title="客户名称"
      open={!!customerNamePreview}
      centered
      footer={null}
      onCancel={() => setCustomerNamePreview(null)}
    >
      <div className="xll-vm-name-modal-text">{customerNamePreview}</div>
      <button
        type="button"
        className="xll-mod-btn-primary"
        style={{ width: '100%', minHeight: 44, borderRadius: 12, marginTop: 16 }}
        onClick={() => { mpToast.success('已复制（原型）'); setCustomerNamePreview(null); }}
      >
        复制名称
      </button>
    </Modal>
  ) : null);

  if (detailVehicle) {
    const v = detailVehicle;
    return (
      <div className="xll-mod-root">
        <div className="xll-mod-scroll" style={{ paddingBottom: 24 }}>
          <div className="xll-vm-detail-hero">
            <div className="xll-vm-plate-row">
              <span className="xll-vm-detail-plate">{v.plateNo}</span>
              <span className={vmOperateBadgeClass(v.operateStatus)}>{v.operateStatus}</span>
              <span className={`xll-vm-online${v.onlineStatus === '在线' ? '' : ''}`}>
                <span className={`xll-vm-dot ${v.onlineStatus === '在线' ? 'on' : 'off'}`} />
                {v.onlineStatus}
              </span>
            </div>
            <div className="xll-vm-detail-sub">{v.brand} · {v.model}<br />{v.vin}</div>
            <VmCustomerBar name={v.customer} variant="detail" onExpand={setCustomerNamePreview} />
            <div className="xll-vm-detail-tags">
              <span className="xll-vm-badge neutral">{vmFormatStatus(v.vehicleStatus)}</span>
              {v.licenseStatus === '异常' && <span className="xll-vm-badge danger">证照异常</span>}
              {v.insuranceStatus === '异常' && <span className="xll-vm-badge danger">保险异常</span>}
            </div>
          </div>
          <div className="xll-vm-subtabs" role="tablist">
            {VM_DETAIL_TABS.map((tab) => (
              <button key={tab.key} type="button" role="tab" aria-selected={detailTab === tab.key} className={`xll-vm-subtab${detailTab === tab.key ? ' active' : ''}`} onClick={() => setDetailTab(tab.key)}>{tab.label}</button>
            ))}
          </div>
          {detailTab === 'detail' && renderDetailTab(v)}
          {detailTab === 'license' && renderLicenseTab(v)}
          {detailTab === 'insurance' && renderInsuranceTab(v)}
          {detailTab === 'events' && renderEventsTab(v)}
        </div>
        {renderNamePreviewModal()}
      </div>
    );
  }

  return (
    <div className="xll-mod-root" data-annotation-id="page-vehicle">
      <div className="xll-mod-toolbar">
        <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
          <div className="xll-mod-search" style={{ flex: 1 }}>
            <IconSearch />
            <input type="search" placeholder="搜索车牌、VIN、品牌、客户" value={searchKey} onChange={(e) => setSearchKey(e.target.value)} aria-label="搜索车辆" />
          </div>
          <button type="button" className={`xll-vm-filter-btn${activeFilterCount ? ' active' : ''}`} onClick={() => setFilterDrawerOpen(true)} aria-label="筛选">
            <IconFilter />
          </button>
        </div>
        {(operateFilter || vehicleStatusFilter || onlineFilter) && (
          <div className="xll-mod-chips" style={{ marginTop: 10 }}>
            {operateFilter && (
              <button type="button" className="xll-mod-chip active" onClick={() => setOperateFilter('')}>运营：{operateFilter} ×</button>
            )}
            {vehicleStatusFilter && (
              <button type="button" className="xll-mod-chip active" onClick={() => setVehicleStatusFilter('')}>状态：{vehicleStatusFilter} ×</button>
            )}
            {onlineFilter && (
              <button type="button" className="xll-mod-chip active" onClick={() => setOnlineFilter('')}>GPS：{onlineFilter} ×</button>
            )}
          </div>
        )}
      </div>
      <div className="xll-vm-kpi-row" role="group" aria-label="车辆概览">
        {[
          { key: '', label: '全部', val: vmSummary.total },
          { key: '在线', label: 'GPS在线', val: vmSummary.online },
          { key: '离线', label: 'GPS离线', val: vmSummary.offline },
        ].map((item) => (
          <button
            key={item.key || 'all'}
            type="button"
            className={`xll-vm-kpi-chip${onlineFilter === item.key ? ' active' : ''}`}
            onClick={() => setOnlineFilter((prev) => (prev === item.key ? '' : item.key))}
          >
            <span className="xll-vm-kpi-chip-val">{item.val}</span>
            <span className="xll-vm-kpi-chip-label">{item.label}</span>
          </button>
        ))}
        <div className="xll-vm-kpi-chip" style={{ cursor: 'default' }}>
          <span className="xll-vm-kpi-chip-val" style={{ color: vmSummary.licenseWarn ? COLOR_DANGER : COLOR_TEXT }}>{vmSummary.licenseWarn}</span>
          <span className="xll-vm-kpi-chip-label">证照异常</span>
        </div>
        <div className="xll-vm-kpi-chip" style={{ cursor: 'default' }}>
          <span className="xll-vm-kpi-chip-val" style={{ color: vmSummary.insuranceWarn ? COLOR_DANGER : COLOR_TEXT }}>{vmSummary.insuranceWarn}</span>
          <span className="xll-vm-kpi-chip-label">保险异常</span>
        </div>
      </div>
      <div className="xll-mod-list-head"><span>全部车辆</span><span>共 {filteredList.length} 辆</span></div>
      <div className="xll-mod-list">
        {filteredList.length === 0 ? (
          <div className="xll-mod-empty">暂无匹配车辆<br />试试清空搜索或筛选条件</div>
        ) : (
          filteredList.map((v) => (
            <div
              key={v.id}
              className="xll-mod-card xll-vm-list-card"
              style={{ '--mod-accent': XLL_GREEN, '--mod-soft': XLL_GREEN_SOFT }}
              role="button"
              tabIndex={0}
              onClick={() => openDetail(v)}
              onKeyDown={(e) => e.key === 'Enter' && openDetail(v)}
            >
              <div className="xll-mod-card-head">
                <span className="xll-mod-ar-plate">{v.plateNo}</span>
                <span className={vmOperateBadgeClass(v.operateStatus)}>{v.operateStatus}</span>
                <span className={`xll-vm-online${v.onlineStatus === '在线' ? '' : ''}`}>
                  <span className={`xll-vm-dot ${v.onlineStatus === '在线' ? 'on' : 'off'}`} />
                  {v.onlineStatus}
                </span>
              </div>
              <div className="xll-mod-card-sub">{v.brand} · {v.model}</div>
              <VmCardLocation vehicle={v} />
              <VmCustomerBar name={v.customer} variant="list" onExpand={setCustomerNamePreview} />
              <div className="xll-mod-meta">
                <div className="xll-vm-meta-cell"><span className="xll-mod-meta-label">运营城市</span><span className="xll-mod-meta-val">{v.region ? v.region.replace(/\//g, ' · ') : '—'}</span></div>
                <div className="xll-vm-meta-cell"><span className="xll-mod-meta-label">运营状态</span><span className="xll-mod-meta-val">{v.operateStatus}</span></div>
                <div className="xll-vm-meta-cell"><span className="xll-mod-meta-label">车辆状态</span><span className="xll-mod-meta-val">{vmFormatStatus(v.vehicleStatus)}</span></div>
                <div className="xll-vm-meta-cell"><span className="xll-mod-meta-label">保险状态</span><span className="xll-mod-meta-val" style={{ color: v.insuranceStatus === '异常' ? COLOR_DANGER : COLOR_TEXT_SEC }}>{v.insuranceStatus}</span></div>
                <div className="xll-vm-meta-cell"><span className="xll-mod-meta-label">证照状态</span><span className="xll-mod-meta-val" style={{ color: v.licenseStatus === '异常' ? COLOR_DANGER : COLOR_TEXT_SEC }}>{vmFormatStatus(v.licenseStatus)}</span></div>
                <div className="xll-vm-meta-cell"><span className="xll-mod-meta-label">车辆来源</span><span className="xll-mod-meta-val">{v.vehicleSource || '—'}</span></div>
              </div>
              <div className="xll-mod-card-foot">
                <div className="xll-vm-card-vin-wrap">
                  <span className="xll-vm-card-vin-label">车辆识别代码</span>
                  <span className="xll-vm-card-vin" title={v.vin}>{v.vin}</span>
                </div>
                <button type="button" className="xll-mod-card-btn" onClick={(e) => { e.stopPropagation(); openDetail(v); }}>查看</button>
              </div>
            </div>
          ))
        )}
      </div>
      {filterDrawerOpen ? (
        <div className="xll-mod-sheet-overlay" role="dialog" aria-modal="true" aria-label="筛选车辆" data-annotation-id="page-vehicle-filter-sheet">
          <button type="button" className="xll-mod-sheet-mask" onClick={() => setFilterDrawerOpen(false)} aria-label="关闭" />
          <div className="xll-mod-sheet-panel xll-mod-sheet-panel--filter">
            <div className="xll-mod-sheet-handle" aria-hidden />
            <div className="xll-mod-sheet-header">
              <div className="xll-mod-sheet-title">筛选车辆</div>
              <button type="button" className="xll-mod-sheet-close" onClick={() => setFilterDrawerOpen(false)} aria-label="关闭">×</button>
            </div>
            <div className="xll-mod-sheet-scroll">
              <div className="xll-mod-drawer-section">
                <div className="xll-mod-drawer-section-title">运营状态</div>
                <div className="xll-mod-drawer-types">
                  <button type="button" className={`xll-mod-drawer-type-btn${!operateFilter ? ' active' : ''}`} onClick={() => setOperateFilter('')}>全部</button>
                  {VM_OPERATE_STATUSES.map((s) => (
                    <button key={s} type="button" className={`xll-mod-drawer-type-btn${operateFilter === s ? ' active' : ''}`} onClick={() => setOperateFilter(s)}>{s}</button>
                  ))}
                </div>
              </div>
              <div className="xll-mod-drawer-section" style={{ marginBottom: 0 }}>
                <div className="xll-mod-drawer-section-title">车辆状态</div>
                <div className="xll-mod-drawer-types">
                  <button type="button" className={`xll-mod-drawer-type-btn${!vehicleStatusFilter ? ' active' : ''}`} onClick={() => setVehicleStatusFilter('')}>全部</button>
                  {VM_VEHICLE_STATUSES.map((s) => (
                    <button key={s} type="button" className={`xll-mod-drawer-type-btn${vehicleStatusFilter === s ? ' active' : ''}`} onClick={() => setVehicleStatusFilter(s)}>{s}</button>
                  ))}
                </div>
              </div>
            </div>
            <div className="xll-mod-sheet-footer">
              <button type="button" className="xll-mod-btn-ghost" onClick={() => { setOperateFilter(''); setVehicleStatusFilter(''); setOnlineFilter(''); }}>重置</button>
              <button type="button" className="xll-mod-btn-primary" onClick={() => { setFilterDrawerOpen(false); mpToast.success('筛选已应用'); }}>确定</button>
            </div>
          </div>
        </div>
      ) : null}
      {renderNamePreviewModal()}
    </div>
  );
};

const IconBack = () => (
  <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" aria-hidden="true">
    <polyline points="15 18 9 12 15 6" />
  </svg>
);
const IconSearch = () => (
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true">
    <circle cx="11" cy="11" r="8" /><line x1="21" y1="21" x2="16.65" y2="16.65" />
  </svg>
);
const IconFilter = () => (
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true">
    <polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3" />
  </svg>
);
const IconGlobe = () => (
  <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true">
    <circle cx="12" cy="12" r="10" /><line x1="2" y1="12" x2="22" y2="12" /><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
  </svg>
);
const IconTruck = () => (
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true">
    <rect x="1" y="3" width="15" height="13" rx="1" /><path d="M16 8h4l3 3v5h-7V8z" /><circle cx="5.5" cy="18.5" r="2.5" /><circle cx="18.5" cy="18.5" r="2.5" />
  </svg>
);
const IconStation = () => (
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true">
    <path d="M12 2v4" /><path d="M12 18v4" /><path d="M4.93 4.93l2.83 2.83" /><path d="M16.24 16.24l2.83 2.83" /><path d="M2 12h4" /><path d="M18 12h4" /><path d="M4.93 19.07l2.83-2.83" /><path d="M16.24 7.76l2.83-2.83" /><circle cx="12" cy="12" r="4" />
  </svg>
);
const IconRepair = () => (
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true">
    <path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z" />
  </svg>
);
const IconStatusSignal = () => (
  <svg width="18" height="12" viewBox="0 0 18 12" fill="currentColor" aria-hidden="true">
    <rect x="0" y="7" width="3" height="5" rx="0.5" /><rect x="5" y="5" width="3" height="7" rx="0.5" /><rect x="10" y="2" width="3" height="10" rx="0.5" /><rect x="15" y="0" width="3" height="12" rx="0.5" />
  </svg>
);
const IconStatusWifi = () => (
  <svg width="16" height="12" viewBox="0 0 16 12" fill="none" stroke="currentColor" strokeWidth="1.6" aria-hidden="true">
    <path d="M8 10.5a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z" fill="currentColor" stroke="none" />
    <path d="M4.5 7.2a4.8 4.8 0 0 1 7 0" strokeLinecap="round" /><path d="M1.5 4.2a8.5 8.5 0 0 1 13 0" strokeLinecap="round" />
  </svg>
);
const IconStatusBattery = () => (
  <svg width="26" height="12" viewBox="0 0 26 12" fill="none" aria-hidden="true">
    <rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="currentColor" strokeOpacity="0.45" />
    <rect x="2.5" y="2.5" width="16" height="7" rx="1.5" fill="currentColor" />
    <path d="M23 4.5v3c.8-.45 1.3-1.15 1.3-1.5S23.8 4.95 23 4.5Z" fill="currentColor" fillOpacity="0.45" />
  </svg>
);
const IconTabTodo = ({ active }) => (
  <svg width="22" height="22" viewBox="0 0 24 24" fill={active ? XLL_GREEN : 'none'} stroke={active ? XLL_GREEN : COLOR_MUTED} strokeWidth="2" strokeLinecap="round" aria-hidden="true">
    <path d="M9 11l3 3L22 4" /><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11" />
  </svg>
);
const IconTabBusiness = ({ active }) => (
  <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke={active ? XLL_GREEN : COLOR_MUTED} strokeWidth="2" aria-hidden="true">
    <rect x="3" y="3" width="7" height="7" rx="1" /><rect x="14" y="3" width="7" height="7" rx="1" /><rect x="3" y="14" width="7" height="7" rx="1" /><rect x="14" y="14" width="7" height="7" rx="1" />
  </svg>
);
const IconTabMap = ({ active }) => (
  <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke={active ? XLL_GREEN : COLOR_MUTED} strokeWidth="2" strokeLinecap="round" aria-hidden="true">
    <polygon points="1 6 1 22 8 18 16 22 23 18 23 2 16 6 8 2 1 6" /><line x1="8" y1="2" x2="8" y2="18" /><line x1="16" y1="6" x2="16" y2="22" />
  </svg>
);
const IconTabMine = ({ active }) => (
  <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke={active ? XLL_GREEN : COLOR_MUTED} strokeWidth="2" strokeLinecap="round" aria-hidden="true">
    <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" /><circle cx="12" cy="7" r="4" />
  </svg>
);
const IconWarn = () => (
  <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" aria-hidden="true">
    <path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" /><line x1="12" y1="9" x2="12" y2="13" /><line x1="12" y1="17" x2="12.01" y2="17" />
  </svg>
);

const TASK_ICONS = {
  delivery: () => (
    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true">
      <path d="M5 17h14v-5H5v5z" /><path d="M5 12V7h10l3 5" /><circle cx="7.5" cy="17" r="1.5" /><circle cx="16.5" cy="17" r="1.5" />
    </svg>
  ),
  return: () => (
    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true">
      <polyline points="1 4 1 10 7 10" /><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10" />
    </svg>
  ),
  inspection: () => (
    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true">
      <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" /><polyline points="14 2 14 8 20 8" /><line x1="16" y1="13" x2="8" y2="13" /><line x1="16" y1="17" x2="8" y2="17" />
    </svg>
  ),
  transfer: () => (
    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true">
      <line x1="5" y1="12" x2="19" y2="12" /><polyline points="12 5 19 12 12 19" />
    </svg>
  ),
  move: () => (
    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true">
      <circle cx="12" cy="12" r="10" /><polyline points="12 6 12 12 16 14" />
    </svg>
  ),
  audit: () => (
    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true">
      <path d="M9 11l3 3L22 4" /><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11" />
    </svg>
  ),
};

const BIZ_ICONS = {
  vehicle: () => (<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true"><rect x="1" y="3" width="15" height="13" rx="1" /><path d="M16 8h4l3 3v5h-7V8z" /><circle cx="5.5" cy="18.5" r="2.5" /><circle cx="18.5" cy="18.5" r="2.5" /></svg>),
  prepare: () => (<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z" /></svg>),
  delivery: () => (<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true"><path d="M5 17h14v-5H5v5z" /><path d="M5 12V7h10l3 5" /><circle cx="7.5" cy="17" r="1.5" /><circle cx="16.5" cy="17" r="1.5" /></svg>),
  return: () => (<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true"><polyline points="1 4 1 10 7 10" /><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10" /></svg>),
  replace: () => (<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true"><polyline points="17 1 21 5 17 9" /><path d="M3 11V9a4 4 0 0 1 4-4h14" /><polyline points="7 23 3 19 7 15" /><path d="M21 13v2a4 4 0 0 1-4 4H3" /></svg>),
  move: () => (<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true"><circle cx="12" cy="12" r="10" /><polyline points="12 6 12 12 16 14" /></svg>),
  transfer: () => (<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true"><line x1="5" y1="12" x2="19" y2="12" /><polyline points="12 5 19 12 12 19" /></svg>),
  inspection: () => (<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" /><polyline points="14 2 14 8 20 8" /><line x1="16" y1="13" x2="8" y2="13" /></svg>),
  fault: () => (<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" /><line x1="12" y1="9" x2="12" y2="13" /><line x1="12" y1="17" x2="12.01" y2="17" /></svg>),
  training: () => (<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z" /><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z" /></svg>),
  audit: () => (<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true"><path d="M9 11l3 3L22 4" /><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11" /></svg>),
  'stat-vehicle': () => (<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true"><line x1="18" y1="20" x2="18" y2="10" /><line x1="12" y1="20" x2="12" y2="4" /><line x1="6" y1="20" x2="6" y2="14" /></svg>),
  'stat-h2-fee': () => (<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true"><line x1="12" y1="1" x2="12" y2="23" /><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6" /></svg>),
  'stat-h2-qty': () => (<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true"><path d="M12 2.69l5.66 5.66a8 8 0 1 1-11.31 0z" /></svg>),
  'stat-electric': () => (<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2" /></svg>),
  'mileage-query': () => (<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true"><circle cx="11" cy="11" r="8" /><line x1="21" y1="21" x2="16.65" y2="16.65" /></svg>),
  'mileage-assess': () => (<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" /><polyline points="22 4 12 14.01 9 11.01" /></svg>),
};

const TAB_ICON_MAP = {
  todo: IconTabTodo,
  business: IconTabBusiness,
  map: IconTabMap,
  mine: IconTabMine,
};

const PRD_DOCS = {
  login: {
    title: '登录页 · 产品需求说明',
    body: (
      <div className="xll-prd-doc">
        <span className="xll-prd-tag">小羚羚小程序</span>
        <div className="xll-prd-meta">版本 V1.0 · 模块：账号登录 · 适用：全体企业微信/小程序用户</div>
        <div className="xll-prd-h2">一、目标</div>
        <p className="xll-prd-p">提供企业微信授权或账号密码登录入口，登录成功后进入「工作台」默认 Tab，并拉取待办数量与消息未读数。</p>
        <div className="xll-prd-highlight">登录按钮点击后应展示 Loading 状态，防止重复提交；成功后 Toast 提示并跳转工作台。</div>
        <div className="xll-prd-h2">二、页面元素</div>
        <ul className="xll-prd-ul">
          <li className="xll-prd-li">品牌 Logo + 产品名「小羚羚」。</li>
          <li className="xll-prd-li">主按钮「企业微信一键登录」（原型模拟）。</li>
          <li className="xll-prd-li">需求文档通过左侧<strong>标注目录</strong>（PRD 00 / PRD 01）查看；登录页源码保留独立入口，默认导航不暴露。</li>
        </ul>
        <div className="xll-prd-h2">三、交互</div>
        <ul className="xll-prd-ul">
          <li className="xll-prd-li">登录成功 → 进入主框架，默认展示工作台待办列表。</li>
          <li className="xll-prd-li">登录态本地缓存，下次打开免登（原型省略）。</li>
        </ul>
      </div>
    ),
  },
  todo: {
    title: '待办 · 产品需求说明',
    body: (
      <div className="xll-prd-doc">
        <span className="xll-prd-tag">工作台 / 待办</span>
        <div className="xll-prd-meta">版本 V1.0 · 参照设计图1 · 聚合运维待办任务</div>
        <div className="xll-prd-h2">一、目标</div>
        <p className="xll-prd-p">首屏聚合<strong>交车、还车、年审、调拨、异动</strong>等待处理任务，按卡片展示关键字段，支持「去处理」进入办理页。原型默认进入本页，无需登录。</p>
        <div className="xll-prd-h2">二、列表卡片</div>
        <ul className="xll-prd-ul">
          <li className="xll-prd-li">左侧色条 + 类型图标区分任务类别。</li>
          <li className="xll-prd-li">右上角黄色数字角标：同类型待办条数（可选）。</li>
          <li className="xll-prd-li">年审到期显示红色警告标签（不仅靠颜色）。</li>
          <li className="xll-prd-li">右侧绿色「去处理」按钮，触控区域 ≥44px。</li>
        </ul>
        <div className="xll-prd-h2">三、导航</div>
        <ul className="xll-prd-ul">
          <li className="xll-prd-li">左上返回：子模块内先退子页，再退回工作台。</li>
          <li className="xll-prd-li">底部 Tab：图标 + 文字标签，当前页高亮。</li>
        </ul>
      </div>
    ),
  },
  'todo-detail': {
    title: '待办办理 · 产品需求说明',
    body: (
      <div className="xll-prd-doc">
        <span className="xll-prd-tag">待办子页</span>
        <div className="xll-prd-h2">一、通用规则</div>
        <p className="xll-prd-p">从待办卡片「去处理」进入，顶部 Hero 展示任务类型与标题，正文只读展示任务详情，底部「确认办理」提交。</p>
        <div className="xll-prd-h2">二、分类型差异</div>
        <ul className="xll-prd-ul">
          <li className="xll-prd-li"><strong>交车</strong>：进入内嵌交车管理（列表 + 分步办理表单）。</li>
          <li className="xll-prd-li"><strong>还车</strong>：还车验车、费用预检入口。</li>
          <li className="xll-prd-li"><strong>年审</strong>：进入内嵌年审管理（待处理 / 办理 / 历史记录）。</li>
          <li className="xll-prd-li"><strong>调拨/异动</strong>：审批前核对路线、车辆与申请人信息。</li>
        </ul>
      </div>
    ),
  },
  business: {
    title: '业务 · 产品需求说明',
    body: (
      <div className="xll-prd-doc">
        <span className="xll-prd-tag">业务入口</span>
        <div className="xll-prd-meta">版本 V1.0 · 参照设计图2-3 · 功能宫格导航</div>
        <div className="xll-prd-h2">一、信息架构</div>
        <ul className="xll-prd-ul">
          <li className="xll-prd-li"><strong>运维管理</strong>：车辆管理、备车、交/还/替换车、异动、调拨、年审、故障、司机安全培训。</li>
          <li className="xll-prd-li"><strong>审批管理</strong>：审批中心（对接 ONE-OS 审批中心）。</li>
          <li className="xll-prd-li"><strong>数据可视化</strong>：车辆/氢费/氢量/电量统计、里程查询与考核。</li>
        </ul>
        <div className="xll-prd-h2">二、角标规则</div>
        <p className="xll-prd-p">绿色圆形角标表示待处理数量；99+ 时显示 99。无待办则不展示角标。</p>
        <div className="xll-prd-h2">三、交互</div>
        <p className="xll-prd-p">点击「审批中心」「年审」「交车」「车辆管理」在小羚羚壳内嵌打开完整模块（绿色主题统一）；其余宫格 Toast 提示（原型）。</p>
      </div>
    ),
  },
  delivery: {
    title: '交车 · 产品需求说明',
    body: (
      <div className="xll-prd-doc">
        <span className="xll-prd-tag">运维管理 / 交车</span>
        <div className="xll-prd-meta">版本 V1.3.2 · 2026-07-01 · 参照 web 交车管理 + Axhub「交车（完成）」原型 · 默认区域权限：浙江省-嘉兴市</div>
        <div className="xll-prd-h2">一、列表页</div>
        <ul className="xll-prd-ul">
          <li className="xll-prd-li">KPI：全部 / 进行中 / 已完成，点击切换筛选。</li>
          <li className="xll-prd-li">搜索：车牌、项目、客户名称。</li>
          <li className="xll-prd-li">卡片：车牌（未选车显示「车辆待选」）、交车状态 Tag（未开始 / 已保存 / 待客户签章 / 待重新签章 / 客户已签章）、项目、客户、交车区域。</li>
          <li className="xll-prd-li">进行中 Tab 下提供状态筛选 Chip：全部状态、未开始、已保存、待客户签章、待重新签章。</li>
          <li className="xll-prd-li">进行中任务展示「去办理 / 继续办理」按钮。</li>
        </ul>
        <div className="xll-prd-h2">二、分步表单（顶部步骤条）</div>
        <ul className="xll-prd-ul">
          <li className="xll-prd-li">顶部步骤条：<strong>车辆情况</strong> → <strong>交车检查项</strong> → <strong>拍摄照片</strong>，可点击切换。</li>
          <li className="xll-prd-li"><strong>车辆情况</strong>：Hero 展示客户名称、项目名称与交车区域；含选择车辆、车辆配置、驾驶培训、交车数据、交车位置（独立卡片）。</li>
          <li className="xll-prd-li"><strong>识别车牌号</strong>：拍照 OCR 识别车牌后，根据识别结果匹配车辆并校验交车资格，须同时满足 <strong>车辆状态=已备车</strong>、<strong>保险状态=正常</strong>、<strong>证照状态=正常</strong>。任一条件不满足则报错阻断，不可选车；报错提示文案与「选择车辆」页车辆卡片底部 readiness 提示条一致（如「交强险已到期无法交车」「商业险已到期无法交车」「交强险、商业险已到期无法交车」「行驶证已到期无法交车」；未备车时「该车辆未备车，请先进行备车」）。支持<strong>拍照识别</strong>或<strong>相册选取</strong>车牌照片。</li>
          <li className="xll-prd-li"><strong>相册上传</strong>：交车模块内<strong>所有拍照环节</strong>均支持从手机相册本地上传图片，包括：识别车牌号、车辆配置（车身广告/放大字）、驾驶培训手动记录证件照、交车连拍/补拍、瑕疵与其他照片添加、备胎拍摄等；相机页底部提供「拍照」「相册」操作。<strong>连拍环节相册为单选模式</strong>（V1.3.2，每次只能选 1 张）；瑕疵/其他补充照片仍支持相册多选批量添加。</li>
          <li className="xll-prd-li"><strong>车辆配置</strong>：车身广告及放大字、尾板（必填，有则须上传照片）；备胎（选填，仅选择有/无）。</li>
          <li className="xll-prd-li"><strong>驾驶培训</strong>（必填）：支持电子培训 / 手动记录两种模式。电子培训：展示培训二维码（微信扫码观看视频），完成后识别提车码。手动记录（运维端）：先录入司机手机号、姓名、身份证号，并上传身份证正反面、驾驶证正反面、司机正面照片、从业资格证（重卡必填），点击底部「生成培训码」；培训码与录入信息绑定，展示二维码供司机微信扫码；生成后交车页标记「待司机确认」，可「查看培训码」或「刷新培训状态」；司机签字完成并同步后标记「已完成驾驶培训」。</li>
          <li className="xll-prd-li"><strong>手动记录 · 司机微信端</strong>（仅需求说明，本期原型不实现）：司机微信扫描二维码后，首先阅读并同意安全培训文件（倒计时 10 秒后可点确定）；随后在司机微信浏览器展示运维登记的司机「姓名」「手机号」「身份证号」及身份证正反面、驾驶证正反面、司机正面照片、从业资格证照片；下方点击「确认签字」唤起签名板，完成签字后提示培训成功，相关信息同步至安全培训记录。</li>
          <li className="xll-prd-li"><strong>交车数据</strong>（必填）：里程、氢量、电量均支持 2 位小数输入（无数字步进器）；氢量单位按「型号参数」仪表盘单位随车型自动匹配（%/MPa，不可手动切换）；送车服务费为选填，精确至 2 位小数（元）；氢量下方备注为选填。不含交车时间字段。</li>
          <li className="xll-prd-li"><strong>交车位置</strong>：如果车辆 GPS 在线，交车位置取车辆 GPS 当前定位；如果车辆 GPS 离线，交车位置取手机当前定位。独立卡片展示地图、蓝点标记坐标；GPS 离线时支持手动「获取当前定位」。</li>
          <li className="xll-prd-li"><strong>交车检查项</strong>：证件信息 → 工具信息 → 外观检查 → 轮胎检查；前 3 类开关表示检查情况并可填备注，轮胎检查项填写胎纹深度 (mm)。</li>
          <li className="xll-prd-li"><strong>实际交车时间</strong>：运维提交 E签宝签章时系统自动记录；仅在交车列表「待客户签章」卡片展示。</li>
          <li className="xll-prd-li"><strong>拍摄照片</strong>：分车身/底盘/轮胎/瑕疵/其他五类；连拍严格按顺序拍摄且不可跳过；中途退出后再次进入自动从下一项续拍；模拟相机页支持<strong>拍照或相册上传</strong>，轮胎项下方录入胎纹；完成后可补拍瑕疵/其他。<strong>所有拍照场景均支持调焦</strong>：点击取景区域设定对焦点，右侧 +/- 调整 1.0×～3.0× 数码变焦（连拍、备胎、车身广告/放大字、手动记录证件照）。</li>
          <li className="xll-prd-li"><strong>备胎照片</strong>：从车辆情况步骤移至拍摄照片「轮胎情况」连拍末项（有备胎时必拍）；车辆情况仅保留是否有备胎。</li>
          <li className="xll-prd-li"><strong>拍照操作条（V1.3）</strong>：未拍摄时「拍照」「相册」等宽平铺；已拍摄时「重新拍摄」「相册」「完成」三按钮等宽平铺。</li>
          <li className="xll-prd-li"><strong>分步操作条</strong>：车辆情况、交车检查项点击「下一步」时自动保存；拍摄照片完成后为「保存」「发送签章文件」。</li>
          <li className="xll-prd-li"><strong>连拍自动保存（V1.3）</strong>：连拍每次点「完成」进入下一项时强制静默自动保存草稿（未开始 → 已保存）。</li>
          <li className="xll-prd-li"><strong>照片上传</strong>：连拍/单拍每次点击「完成」继续时，上一张自动上传并在服务端打水印（时间、地点）。</li>
          <li className="xll-prd-li"><strong>胎纹 OCR</strong>：胎纹未反写成功时禁止继续拍摄下一张。</li>
          <li className="xll-prd-li"><strong>选择被授权人</strong>：多卡片展示姓名与手机号，选中高亮；发送签章文件后跳转 E签宝 完成运维签字。若关联合同无被授权人，展示「无授权人，请联系业管姓名补充授权人信息」（业管自动取合同业务负责人），并阻断发送签章。详见 <code>.spec/authorized-person.md</code>。</li>
          <li className="xll-prd-li"><strong>待客户签章</strong>：只读查看各步骤，底部「返回」「清除签章」；确认后记录变为「待重新签章」。</li>
          <li className="xll-prd-li"><strong>待重新签章</strong>：交车信息全部锁定，仅可重选被授权人；底部「返回」「重新发起签章」，完成 E签宝 运维签字后恢复「待客户签章」。</li>
          <li className="xll-prd-li"><strong>客户签章</strong>：被授权人短信完成签章后进入「已完成」列表，状态「客户已签章」。</li>
        </ul>
        <div className="xll-prd-h2">三、查看态</div>
        <ul className="xll-prd-ul">
          <li className="xll-prd-li">客户已签章 / 待客户签章：只读浏览各步骤，不可编辑。</li>
          <li className="xll-prd-li">待重新签章：各步骤只读，仅「选择被授权人」可改。</li>
          <li className="xll-prd-li">已完成可下载 E签宝签章 PDF；展示是否归还。</li>
        </ul>
        <div className="xll-prd-h2">四、导航</div>
        <p className="xll-prd-p">表单内左上返回先退出办理页；再次返回退出交车模块。步骤条可点击跳转。</p>
      </div>
    ),
  },
  map: {
    title: '地图 · 产品需求说明',
    body: (
      <div className="xll-prd-doc">
        <span className="xll-prd-tag">地图</span>
        <div className="xll-prd-meta">版本 V2.0 · 三类资源 · 角色权限 · 地图/列表/统计</div>
        <div className="xll-prd-h2">一、核心资源分类</div>
        <ul className="xll-prd-ul">
          <li className="xll-prd-li"><strong>氢能车</strong>：新能源车辆实时位置与运营状态。</li>
          <li className="xll-prd-li"><strong>加氢站</strong>：合作加氢站 POI 与运营数据。</li>
          <li className="xll-prd-li"><strong>维修站</strong>：氢能维保网点与工单概况。</li>
        </ul>
        <div className="xll-prd-h2">二、分级角色权限</div>
        <ul className="xll-prd-ul">
          <li className="xll-prd-li"><strong>内部角色</strong>：管理人员、运维人员、其他内部授权人员 — 可见全量资源。</li>
          <li className="xll-prd-li"><strong>外部角色</strong>：客户、司机、车队 — 按授权范围过滤可见数据。</li>
        </ul>
        <div className="xll-prd-h2">三、统一浏览范式</div>
        <ul className="xll-prd-ul">
          <li className="xll-prd-li"><strong>地图模式</strong>：聚合点位 → 展开标记 → 进入详情；详情支持多层信息 Tab 切换。</li>
          <li className="xll-prd-li"><strong>列表模式</strong>：卡片列表 + 搜索/筛选，点击条目穿透详情。</li>
          <li className="xll-prd-li"><strong>统计模式</strong>：多维 KPI 卡片，点击下钻至列表明细。</li>
        </ul>
        <div className="xll-prd-h2">四、信息穿透</div>
        <p className="xll-prd-p">地图聚合点、列表条目、统计卡片均可无断点下钻至单个资源详情页。</p>
      </div>
    ),
  },
  mine: {
    title: '我的 · 产品需求说明',
    body: (
      <div className="xll-prd-doc">
        <span className="xll-prd-tag">我的</span>
        <div className="xll-prd-meta">版本 V1.0 · 参照设计图5 · 个人中心</div>
        <div className="xll-prd-h2">一、展示字段</div>
        <ul className="xll-prd-ul">
          <li className="xll-prd-li">顶部 Hero 展示头像、姓名与岗位。</li>
          <li className="xll-prd-li">姓名、账号、手机号（脱敏展示）。</li>
        </ul>
        <div className="xll-prd-h2">二、说明</div>
        <p className="xll-prd-p">原型阶段省略退出登录；正式版可在此提供退出与账号切换入口。</p>
      </div>
    ),
  },
  audit: {
    title: '审批中心 · 产品需求说明',
    body: (
      <div className="xll-prd-doc">
        <span className="xll-prd-tag">审批管理 / 审批中心</span>
        <div className="xll-prd-meta">版本 V1.4 · 对接 ONE-OS 审批中心 · 适用：有审批权限的业务/财务/管理人员</div>
        <div className="xll-prd-h2">一、目标</div>
        <p className="xll-prd-p">在小程序内聚合「我发起的 / 我的待办 / 我的已办 / 抄送我的」四类审批任务，支持按流程类型筛选、更多筛选与标题搜索，点击进入对应流程的办理或查看页。</p>
        <div className="xll-prd-h2">二、列表 Tab</div>
        <ul className="xll-prd-ul">
          <li className="xll-prd-li"><strong>我发起的</strong>：当前用户作为发起人的流程。</li>
          <li className="xll-prd-li"><strong>我的待办</strong>：待当前用户审批，卡片展示当前节点与处理人（最多 2 人，超出 +N）。</li>
          <li className="xll-prd-li"><strong>我的已办</strong>：当前用户已处理过的历史记录。</li>
          <li className="xll-prd-li"><strong>抄送我的</strong>：仅知会、无需操作的任务。</li>
        </ul>
        <div className="xll-prd-h2">三、列表卡片字段</div>
        <ul className="xll-prd-ul">
          <li className="xll-prd-li">流程类型色条 + 类型名称（合同审批、提车应收款、租赁账单、还车应结款及四部门子类型、车辆调拨、替换车申请等）。</li>
          <li className="xll-prd-li">加粗标题按流程类型：提车/账单/替换车为客户全称；还车主流程为车牌；四部门子流程为「还车应结款-部门名」；调拨为路线摘要。</li>
          <li className="xll-prd-li">元信息：发起人、实收/应退应补金额（按业务）；底部发起时间精确至分钟。</li>
          <li className="xll-prd-li">状态：审批中 + 当前节点审批人；已通过 / 已驳回 / 已终止。</li>
        </ul>
        <div className="xll-prd-h2">四、筛选与搜索</div>
        <ul className="xll-prd-ul">
          <li className="xll-prd-li">快捷 Chip：全部、合同审批、提车应收款、租赁账单、还车应结款（含四部门子类型）、车辆调拨；「更多类型」抽屉展示全部流程类型。</li>
          <li className="xll-prd-li"><strong>搜索</strong>：仅按卡片加粗标题（工作流标题）模糊匹配。</li>
          <li className="xll-prd-li"><strong>更多筛选</strong>（搜索框右侧筛选按钮）：发起人、发起时间（起止日期）。抽屉与选择器均为小程序式，限定在手机预览框内弹出。</li>
          <li className="xll-prd-li">发起人：点击「请选择 ›」→ 底部滚轮，选项含「不限」。</li>
          <li className="xll-prd-li">发起时间：「开始日期 至 结束日期」分别点击 → 年/月/日三列滚轮，取消/确定。</li>
        </ul>
        <div className="xll-prd-h2">五、跳转规则</div>
        <ul className="xll-prd-ul">
          <li className="xll-prd-li">已接入详情页：提车应收款、还车应结款（含业务管理组/能源服务组/运维组/安全部子流程）、租赁账单、车辆调拨（申请/运维）、替换车申请。</li>
          <li className="xll-prd-li">列表占位（Toast）：合同审批、氢费对账单、车辆异动等。</li>
          <li className="xll-prd-li">待办 Tab 进入办理态（底部展示通过/驳回/终止/评论）；其余 Tab 为只读查看态。</li>
          <li className="xll-prd-li">左上返回：详情页先退回列表，再退回业务入口。</li>
        </ul>
        <div className="xll-prd-h2">六、需求文档入口</div>
        <p className="xll-prd-p">导航栏不设「需求说明」按钮；完整 PRD 见标注目录「PRD 01｜审批中心改版说明」及本页标注节点。</p>
      </div>
    ),
  },
  'audit-pickup': {
    title: '提车应收款审批 · 产品需求说明',
    body: (
      <div className="xll-prd-doc">
        <span className="xll-prd-tag">审批中心 / 提车应收款</span>
        <div className="xll-prd-meta">版本 V1.1 · 数据源自提车应收款业务单 · 审批节点：业管 → 财务</div>
        <div className="xll-prd-h2">一、页面目标</div>
        <p className="xll-prd-p">审批人核对本次提车实收金额是否与合同约定一致，确认车辆明细、氢费预付款（如有）及开票信息后方可通过。</p>
        <div className="xll-prd-h2">二、Hero 区</div>
        <ul className="xll-prd-ul">
          <li className="xll-prd-li">主金额：<strong>实收款总额</strong>（大字号，橙色主题）。</li>
          <li className="xll-prd-li">副信息：客户名称、合同编码、项目名称、提车台数。</li>
          <li className="xll-prd-li">对比行：应收款总额、较应收减免差额。</li>
          <li className="xll-prd-li">「实收明细」底部抽屉：分项展示月租金、保证金、服务费、减免及氢费预收（如有）。</li>
        </ul>
        <div className="xll-prd-h2">三、正文区块</div>
        <ul className="xll-prd-ul">
          <li className="xll-prd-li"><strong>车辆明细</strong>：逐车展示品牌型号、车牌、应收/实收租金、保证金、服务费及减免凭证；区块标题与首张车辆卡片之间保留 12px 上间距。</li>
          <li className="xll-prd-li"><strong>氢费预付款</strong>（首期有）：应收、实收、减免金额与备注。</li>
          <li className="xll-prd-li"><strong>开票信息</strong>：开票方式、开票备注。</li>
          <li className="xll-prd-li"><strong>审批情况</strong>：纵向流程节点，展示节点名称、通过/待审核状态、审批人与时间；支持评论气泡展示。</li>
        </ul>
        <div className="xll-prd-h2">四、底部操作（待办态）</div>
        <ul className="xll-prd-ul">
          <li className="xll-prd-li">评论：填写意见，不改变流程状态。</li>
          <li className="xll-prd-li">终止 / 驳回：需填写原因，流程结束并通知发起人。</li>
          <li className="xll-prd-li">通过：确认后流转下一节点；末节点通过后单据完结。</li>
        </ul>
      </div>
    ),
  },
  'audit-return': {
    title: '还车应结款审批 · 产品需求说明',
    body: (
      <div className="xll-prd-doc">
        <span className="xll-prd-tag">审批中心 / 还车应结款</span>
        <div className="xll-prd-meta">版本 V1.4 · 数据源自还车结算单 · 多部门分组提交后由业务管理组发起汇总审批</div>
        <div className="xll-prd-h2">一、页面目标</div>
        <p className="xll-prd-p">还车完成后，业务管理组、能源服务组、运维组、安全部分别填报费用并生成部门子流程。四个子流程全部审批通过后，<strong>不会自动生成</strong>主流程任务；须由<strong>业务管理组主动发起</strong>主流程「还车应结款」审批，任务进入审批列表后由财务等环节整体审批，核对全部分组明细与汇总金额，判定保证金抵扣后应退或应补。</p>
        <div className="xll-prd-highlight">仅主流程「还车应结款」Hero 展示应退/应补总额；四部门子流程 Hero 统一为「还车应结款-部门名」+ 车牌重点样式，不展示保证金与汇总金额。四部门未全部通过前，主流程不可发起；四部门全过但业务管理组未发起前，主流程任务不出现在审批列表；进入主流程后，四张分组卡片右上角均展示「已提交」。</div>
        <div className="xll-prd-h2">二、Hero 区（紫色主题）</div>
        <ul className="xll-prd-ul">
          <li className="xll-prd-li">主流程「还车应结款」：主金额应退还总额 / 应补缴总额；保证金、待结算总额、车辆实际租金及「结算明细」抽屉。</li>
          <li className="xll-prd-li">四部门子流程：Hero 展示「还车应结款-部门名」与车牌重点样式，不展示汇总金额类字段。</li>
          <li className="xll-prd-li">车辆信息：合同编码、项目名称、三项保险状态（易损/轮胎/养护）。</li>
          <li className="xll-prd-li">租期：交车时间、还车时间。</li>
        </ul>
        <div className="xll-prd-h2">三、分组结算卡片</div>
        <ul className="xll-prd-ul">
          <li className="xll-prd-li"><strong>业务管理组</strong>：固定费项表格（ETC 费用、停车费等），不含违章处理违约金与保险上浮，底部应结算总额。</li>
          <li className="xll-prd-li"><strong>能源服务组</strong>：氢量差补缴、交/还车氢量、退还单价、预付款退费。</li>
          <li className="xll-prd-li"><strong>运维组</strong>：清洗、保养、维修、车损、工具/证件/广告丢失、送接车服务、轮胎磨损等费项；含「金额」「无忧包减免」两列，应结算 = 金额 − 减免。</li>
          <li className="xll-prd-li"><strong>安全部</strong>：固定费项三行（违章处理违约金、保险上浮、其他违规费用），附违章次数与已缴/未缴金额统计。</li>
        </ul>
        <div className="xll-prd-h2">四、审批与操作</div>
        <p className="xll-prd-p">审批时间轴 + 底部评论/终止/驳回/通过，规则同提车应收款。查看态隐藏操作栏，仅浏览。</p>
      </div>
    ),
  },
  'audit-lease-bill': {
    title: '租赁账单审批 · 产品需求说明',
    body: (
      <div className="xll-prd-doc">
        <span className="xll-prd-tag">审批中心 / 租赁账单</span>
        <div className="xll-prd-meta">版本 V1.0 · 数据源自月度租赁账单 · 审批节点：业务管理组 → 业管主管 → 财务</div>
        <div className="xll-prd-h2">一、页面目标</div>
        <p className="xll-prd-p">审批人核对本期账单实收金额、逐车租金与服务费减免是否合理，首期账单需额外核对氢费预付款。</p>
        <div className="xll-prd-h2">二、Hero 区（蓝色主题）</div>
        <ul className="xll-prd-ul">
          <li className="xll-prd-li">主金额：实收款总额。</li>
          <li className="xll-prd-li">客户、合同编码、项目名称。</li>
          <li className="xll-prd-li">账单周期起止日期、期数 Tag（第 N 期）。</li>
          <li className="xll-prd-li">应收款总额、开票总额；「应收明细」「实收明细」两个底部抽屉。</li>
        </ul>
        <div className="xll-prd-h2">三、正文区块</div>
        <ul className="xll-prd-ul">
          <li className="xll-prd-li"><strong>车辆列表</strong>：逐车展示应收/实收租金、减免金额与凭证、服务费明细。</li>
          <li className="xll-prd-li"><strong>氢费预付款</strong>（仅第 1 期展示）：应收、实收、减免及备注。</li>
          <li className="xll-prd-li"><strong>审批情况</strong>：各部门节点时间轴。</li>
        </ul>
        <div className="xll-prd-h2">四、金额计算口径（开发）</div>
        <ul className="xll-prd-ul">
          <li className="xll-prd-li">应收总额 = 月租金 + 保证金（首期）+ 服务费 + 氢费预收（首期）。</li>
          <li className="xll-prd-li">实收总额 = 实收租金 + 保证金 + 实收服务费 − 租金减免 − 服务费减免 + 氢费实收 − 氢费减免。</li>
          <li className="xll-prd-li">开票总额不含保证金。</li>
        </ul>
      </div>
    ),
  },
  'audit-transfer-create': {
    title: '车辆调拨审批（申请方） · 产品需求说明',
    body: (
      <div className="xll-prd-doc">
        <span className="xll-prd-tag">审批中心 / 车辆调拨 · 申请方</span>
        <div className="xll-prd-meta">版本 V1.0 · transferStage=create · 区域间车辆调配申请</div>
        <div className="xll-prd-h2">一、页面目标</div>
        <p className="xll-prd-p">发起区域申请将车辆调拨至目标区域，审批人核对路线、运输方式、车辆清单及出发时车况数据。</p>
        <div className="xll-prd-h2">二、Hero 区（绿色主题）</div>
        <ul className="xll-prd-ul">
          <li className="xll-prd-li">调拨车辆台数、出发区域 → 接收区域路线展示。</li>
          <li className="xll-prd-li">调拨日期；Tag 标识「调拨申请」。</li>
        </ul>
        <div className="xll-prd-h2">三、正文区块</div>
        <ul className="xll-prd-ul">
          <li className="xll-prd-li"><strong>调拨情况</strong>：日期、出发/接收区域、调拨原因。</li>
          <li className="xll-prd-li"><strong>运输信息</strong>：运输方式（司机运输/板车运输等）、承运人、预计到达。</li>
          <li className="xll-prd-li"><strong>车辆清单</strong>：逐车车牌、品牌型号、出发里程/氢量/电量。</li>
          <li className="xll-prd-li"><strong>审批情况</strong>：节点时间轴。</li>
        </ul>
        <div className="xll-prd-h2">四、与运维方审批的差异</div>
        <p className="xll-prd-p">申请方侧重「为何调拨、调哪些车」；运维方（ops）侧重接收区域验收与到达车况，Hero Tag 为「运维调拨方」。</p>
      </div>
    ),
  },
  'audit-transfer-ops': {
    title: '车辆调拨审批（运维方） · 产品需求说明',
    body: (
      <div className="xll-prd-doc">
        <span className="xll-prd-tag">审批中心 / 车辆调拨 · 运维方</span>
        <div className="xll-prd-meta">版本 V1.0 · transferStage=ops · 接收区域运维确认调拨到达</div>
        <div className="xll-prd-h2">一、页面目标</div>
        <p className="xll-prd-p">接收区域运维负责人在车辆到达后审批确认，核对运输过程信息与车辆到达时里程、氢量、电量是否与申请一致。</p>
        <div className="xll-prd-h2">二、页面结构</div>
        <p className="xll-prd-p">布局与申请方审批页一致，Hero Tag 改为「运维调拨方」。车辆卡片除出发数据外，需展示到达里程/氢量/电量（如有）。</p>
        <div className="xll-prd-h2">三、审批要点</div>
        <ul className="xll-prd-ul">
          <li className="xll-prd-li">确认车辆已全部到达接收区域。</li>
          <li className="xll-prd-li">核对车况数据异常是否已在备注中说明。</li>
          <li className="xll-prd-li">通过后更新车辆归属区域，流程完结。</li>
        </ul>
      </div>
    ),
  },
  'audit-replace': {
    title: '替换车申请审批 · 产品需求说明',
    body: (
      <div className="xll-prd-doc">
        <span className="xll-prd-tag">审批中心 / 替换车申请</span>
        <div className="xll-prd-meta">版本 V1.0 · 数据源自替换车业务单 · 审批节点：业务主管 → 事业部主管 → 运维主管</div>
        <div className="xll-prd-h2">一、页面目标</div>
        <p className="xll-prd-p">客户用车期间需更换车辆时，审批人核对替换类型、原/替换车辆信息及替换原因，客户原因替换需确认费用。</p>
        <div className="xll-prd-h2">二、Hero 区（玫红主题）</div>
        <ul className="xll-prd-ul">
          <li className="xll-prd-li">替换车辆台数、客户名称。</li>
          <li className="xll-prd-li">合同编码、项目名称、交车区域、项目类型 Tag。</li>
        </ul>
        <div className="xll-prd-h2">三、替换对卡片</div>
        <ul className="xll-prd-ul">
          <li className="xll-prd-li">左右对照：被替换车（原车牌/品牌型号）→ 替换为（新车牌/品牌型号）。</li>
          <li className="xll-prd-li">替换类型 Tag：永久替换 / 临时替换。</li>
          <li className="xll-prd-li">替换原因、原因说明；原因为「客户原因」时展示替换费用。</li>
        </ul>
        <div className="xll-prd-h2">四、审批与操作</div>
        <p className="xll-prd-p">支持多组替换对逐条展示；底部操作栏规则与其他审批页一致。通过后由运维安排交/还车衔接。</p>
      </div>
    ),
  },
};

const IphoneStatusBar = () => (
  <div className="xll-status">
    <span className="xll-status-time">9:41</span>
    <div className="xll-status-icons">
      <IconStatusSignal />
      <IconStatusWifi />
      <IconStatusBattery />
    </div>
  </div>
);

const MpCapsule = () => (
  <div className="xll-capsule" role="group" aria-label="小程序菜单">
    <button type="button" className="xll-capsule-btn" aria-label="更多" onClick={() => mpToast.info('更多菜单（原型）')}>···</button>
    <span className="xll-capsule-divider" aria-hidden="true" />
    <button type="button" className="xll-capsule-btn" aria-label="关闭" onClick={() => mpToast.warning('退出小程序（原型）')}>×</button>
  </div>
);

const PrdModal = ({ prdKey, onClose }) => {
  const doc = PRD_DOCS[prdKey];
  if (!doc || !prdKey) return null;
  return (
    <div className="tc-mini-sheet" role="dialog" aria-modal="true" aria-label={doc.title}>
      <button type="button" className="tc-mini-sheet-mask" onClick={onClose} aria-label="关闭" />
      <div className="tc-mini-sheet-panel">
        <div className="tc-mini-sheet-handle" aria-hidden="true" />
        <div className="tc-mini-sheet-head">
          <span className="tc-mini-sheet-title">{doc.title}</span>
          <button type="button" className="tc-mini-sheet-close" onClick={onClose} aria-label="关闭">×</button>
        </div>
        <div className="tc-mini-sheet-body">{doc.body}</div>
        <div className="tc-mini-sheet-foot">
          <button type="button" className="tc-mini-sheet-ok" onClick={onClose}>知道了</button>
        </div>
      </div>
    </div>
  );
};

const LoginPage = ({ onLogin, onOpenPrd }) => {
  const [loading, setLoading] = useState(false);
  const handleLogin = () => {
    if (loading) return;
    setLoading(true);
    setTimeout(() => {
      setLoading(false);
      onLogin();
    }, 800);
  };
  return (
    <div className="xll-login-wrap" data-annotation-id="page-login">
      <div className="xll-login-logo" aria-hidden="true">羚</div>
      <div className="xll-login-name">小羚羚</div>
      <div className="xll-login-desc">氢能车辆运营移动端<br />企业微信授权后即可使用</div>
      <button type="button" className="xll-login-btn" onClick={handleLogin} disabled={loading} aria-busy={loading}>
        {loading ? '登录中…' : '企业微信一键登录'}
      </button>
      <button type="button" className="xll-prd-link" style={{ marginTop: 24 }} onClick={() => onOpenPrd('login')}>需求说明</button>
    </div>
  );
};

const TaskCard = ({ task, index, onProcess }) => {
  const theme = TASK_THEME[task.type] || TASK_THEME.delivery;
  const TaskIcon = TASK_ICONS[task.type] || TASK_ICONS.delivery;
  const actionMeta = TODO_ACTION_META[task.actionType] || TODO_ACTION_META.execute;
  const riskMeta = TODO_RISK_META[task.riskLevel] || TODO_RISK_META.none;
  const ctaLabel = task.cta || (task.actionType === 'notify' ? '查看' : '去处理');
  const slaOverdue = typeof task.slaRemain === 'string' && task.slaRemain.includes('超期');
  return (
    <div
      className={`xll-task-card${task.actionType === 'notify' ? ' xll-task-card--notify' : ''}`}
      style={{ '--xll-accent': theme.accent, '--xll-soft': theme.soft, animationDelay: `${index * 40}ms` }}
    >
      {task.badge ? <span className="xll-task-badge" aria-label={`${task.badge}条待办`}>{task.badge}</span> : null}
      <div className="xll-task-head">
        <div className="xll-task-title-row">
          <span className="xll-task-icon"><TaskIcon /></span>
          <div className="xll-task-title-wrap">
            <div className="xll-task-meta-row">
              <span className={`xll-task-pill xll-task-pill--${actionMeta.tone}`}>{actionMeta.label}</span>
              <span className={`xll-task-pill xll-task-risk xll-task-risk--${task.riskLevel || 'none'}`}>风险{riskMeta.label}</span>
              <span className="xll-task-type-tag">{theme.label}</span>
            </div>
            <div className="xll-task-title">{task.title}</div>
          </div>
        </div>
        <button type="button" className="xll-task-action" onClick={() => onProcess(task)} aria-label={`${ctaLabel}${task.title}`}>{ctaLabel}</button>
      </div>
      {task.riskHint ? (
        <div className="xll-task-risk-hint">
          <IconWarn />
          <span>{task.riskHint}</span>
        </div>
      ) : null}
      {task.nextOwner && task.nextAction && task.actionType !== 'notify' ? (
        <div className="xll-task-chain">
          <span>通过后责任链</span>
          <span className="xll-task-chain-line">→ 下一责任：<strong>{task.nextOwner}</strong> · {task.nextAction}</span>
        </div>
      ) : null}
      <div className="xll-task-body">
        {task.fields.map((f) => (
          <div className="xll-kv" key={f.label}>
            <span className="xll-kv-label">{f.label}</span>
            <span className={`xll-kv-value${f.warn ? ' warn' : ''}`}>
              {f.warn ? f.value.replace('（已到期）', '') : f.value}
              {f.warn && <span className="xll-warn-tag"><IconWarn />已到期</span>}
            </span>
          </div>
        ))}
      </div>
      {task.slaRemain ? (
        <div className="xll-task-foot">
          <span className={`xll-task-sla${slaOverdue ? ' is-overdue' : ''}`}>{task.slaRemain}</span>
          {task.actionType === 'notify' ? <span>抄送知会，无需操作</span> : <span>完成后状态将同步更新</span>}
        </div>
      ) : null}
    </div>
  );
};

const TodoPage = ({ onProcess }) => {
  const actionableCount = todoActionableCount(TODO_TASKS);
  return (
    <div data-annotation-id="page-todo">
      <div className="xll-list-head">
        <span>待办任务</span>
        <span className="xll-list-count">{actionableCount} 条需处理</span>
      </div>
      <div className="xll-todo-summary">
        <span className="xll-todo-summary-chip xll-todo-summary-chip--accent"><b>{todoTasksByZone(TODO_TASKS, 'today').length}</b> 今日必办</span>
        <span className="xll-todo-summary-chip"><b>{todoTasksByZone(TODO_TASKS, 'handle').length}</b> 待我处理</span>
        <span className="xll-todo-summary-chip"><b>{todoTasksByZone(TODO_TASKS, 'follow').length}</b> 待我跟进</span>
        <span className="xll-todo-summary-chip"><b>{todoTasksByZone(TODO_TASKS, 'notify').length}</b> 仅知会</span>
      </div>
      {TODO_ZONES.map((zone) => {
        const zoneTasks = todoTasksByZone(TODO_TASKS, zone.key);
        if (!zoneTasks.length) return null;
        return (
          <section className="xll-todo-zone" key={zone.key} aria-label={zone.label}>
            <div className="xll-todo-zone-head">
              <span>{zone.label}</span>
              <span className="xll-todo-zone-count">{zoneTasks.length}</span>
            </div>
            {zoneTasks.map((task, i) => (
              <TaskCard key={task.id} task={task} index={i} onProcess={onProcess} />
            ))}
          </section>
        );
      })}
    </div>
  );
};

const BusinessPage = ({ onEntry }) => (
  <div data-annotation-id="page-business">
    {BUSINESS_SECTIONS.map((sec) => (
      <div className="xll-biz-section" key={sec.title}>
        <div className="xll-biz-section-title">{sec.title}</div>
        <div className="xll-biz-grid">
          {sec.items.map((item) => {
            const BizIcon = BIZ_ICONS[item.key] || BIZ_ICONS.vehicle;
            return (
              <button key={item.key} type="button" className="xll-biz-item" onClick={() => onEntry(item)} aria-label={item.label}>
                <span className="xll-biz-icon-wrap">
                <span className="xll-biz-icon"><BizIcon /></span>
                  {item.badge > 0 ? <span className="xll-biz-badge" aria-hidden="true">{item.badge > 99 ? 99 : item.badge}</span> : null}
                </span>
                <span className="xll-biz-label">{item.label}</span>
              </button>
            );
          })}
        </div>
      </div>
    ))}
  </div>
);

const MAP_ROLE_OPTIONS = [
  { key: 'manager', label: '管理人员', group: 'internal' },
  { key: 'ops', label: '运维人员', group: 'internal' },
  { key: 'internal', label: '内部授权', group: 'internal' },
  { key: 'customer', label: '客户', group: 'external' },
  { key: 'driver', label: '司机', group: 'external' },
  { key: 'fleet', label: '车队', group: 'external' },
];

const MAP_RESOURCE_META = {
  vehicle: { label: '氢能车', searchPlaceholder: '车牌号 / VIN / 客户', markerClass: '', statFilterKey: 'status' },
  station: { label: '加氢站', searchPlaceholder: '站名 / 区域', markerClass: 'xll-map-marker--station', statFilterKey: 'status' },
  repair: { label: '维修站', searchPlaceholder: '站名 / 地址', markerClass: 'xll-map-marker--repair', statFilterKey: 'status' },
};

const MAP_VIEW_MODES = [
  { key: 'map', label: '地图' },
  { key: 'list', label: '列表' },
  { key: 'stats', label: '统计' },
];

const MAP_MOCK_RESOURCES = [
  {
    id: 'v1', type: 'vehicle', name: '粤B58888F', subtitle: '宇通 · 49T氢牵引', region: '广东 · 深圳', status: '在线', statGroup: '在线',
    top: 28, left: 22, roles: ['manager', 'ops', 'internal', 'customer', 'fleet'],
    layers: [
      { key: 'basic', label: '基础信息', fields: [['车牌号', '粤B58888F'], ['VIN', 'LZYTBAB58N0001234'], ['品牌型号', '宇通 · 49T氢牵引'], ['运营城市', '广东 · 深圳']] },
      { key: 'ops', label: '运营数据', fields: [['在线状态', '在线'], ['当前里程', '12,480 km'], ['氢量', '68%'], ['GPS 时间', '2026-07-01 14:32:08']] },
      { key: 'owner', label: '权属客户', fields: [['客户名称', '深圳绿运物流'], ['合同编号', 'HT-2025-SZ-018'], ['运维负责人', '张明辉']] },
    ],
  },
  {
    id: 'v2', type: 'vehicle', name: '粤AGP5368', subtitle: '飞驰 · 49T氢牵引', region: '广东 · 广州', status: '在线', statGroup: '在线',
    top: 35, left: 48, roles: ['manager', 'ops', 'internal', 'fleet'],
    layers: [
      { key: 'basic', label: '基础信息', fields: [['车牌号', '粤AGP5368'], ['VIN', 'LZYTBAB58M0005678'], ['品牌型号', '飞驰 · 49T氢牵引'], ['运营城市', '广东 · 广州']] },
      { key: 'ops', label: '运营数据', fields: [['在线状态', '在线'], ['当前里程', '8,920 km'], ['氢量', '54%'], ['GPS 时间', '2026-07-01 14:30:51']] },
      { key: 'owner', label: '权属客户', fields: [['客户名称', '广州氢能运输'], ['合同编号', 'HT-2025-GZ-006'], ['运维负责人', '李运维']] },
    ],
  },
  {
    id: 'v3', type: 'vehicle', name: '浙F06900F', subtitle: '宇通 · 49T氢牵引', region: '浙江 · 嘉兴', status: '离线', statGroup: '离线',
    top: 52, left: 65, roles: ['manager', 'ops', 'internal', 'driver'],
    layers: [
      { key: 'basic', label: '基础信息', fields: [['车牌号', '浙F06900F'], ['VIN', 'LZYTBAB58K0009012'], ['品牌型号', '宇通 · 49T氢牵引'], ['运营城市', '浙江 · 嘉兴']] },
      { key: 'ops', label: '运营数据', fields: [['在线状态', '离线'], ['当前里程', '21,305 km'], ['氢量', '—'], ['GPS 时间', '2026-06-30 22:18:04']] },
      { key: 'owner', label: '权属客户', fields: [['客户名称', '嘉兴港务运输'], ['合同编号', 'HT-2024-JX-012'], ['运维负责人', '王师傅']] },
    ],
  },
  {
    id: 'v4', type: 'vehicle', name: '苏E12345F', subtitle: '飞驰 · 49T氢牵引', region: '江苏 · 苏州', status: '证照异常', statGroup: '证照异常',
    top: 44, left: 38, roles: ['manager', 'ops', 'internal'],
    layers: [
      { key: 'basic', label: '基础信息', fields: [['车牌号', '苏E12345F'], ['VIN', 'LZYTBAB58L0003456'], ['品牌型号', '飞驰 · 49T氢牵引'], ['运营城市', '江苏 · 苏州']] },
      { key: 'ops', label: '运营数据', fields: [['在线状态', '在线'], ['当前里程', '5,120 km'], ['氢量', '72%'], ['证照状态', '检验有效期已过期']] },
      { key: 'owner', label: '权属客户', fields: [['客户名称', '苏州氢运'], ['合同编号', 'HT-2025-SU-003'], ['运维负责人', '赵运维']] },
    ],
  },
  {
    id: 's1', type: 'station', name: '平湖加氢站', subtitle: '浙江省嘉兴市平湖市', region: '浙江 · 嘉兴', status: '运营中', statGroup: '运营中',
    top: 40, left: 55, roles: ['manager', 'ops', 'internal', 'customer', 'driver', 'fleet'],
    layers: [
      { key: 'basic', label: '站点信息', fields: [['站名', '平湖加氢站'], ['地址', '浙江省嘉兴市平湖市乍嘉公路'], ['运营状态', '运营中'], ['营业时间', '08:00 - 22:00']] },
      { key: 'ops', label: '运营数据', fields: [['今日加氢量', '1,280 kg'], ['可用枪位', '3 / 4'], ['排队车辆', '1 辆'], ['最近维护', '2026-06-20']] },
      { key: 'contact', label: '联系信息', fields: [['站长', '刘站长'], ['联系电话', '0573-8888****'], ['所属区域', '华东 · 嘉兴']] },
    ],
  },
  {
    id: 's2', type: 'station', name: '嘉兴港区加氢站', subtitle: '浙江省嘉兴市港区', region: '浙江 · 嘉兴', status: '运营中', statGroup: '运营中',
    top: 30, left: 30, roles: ['manager', 'ops', 'internal', 'customer', 'driver', 'fleet'],
    layers: [
      { key: 'basic', label: '站点信息', fields: [['站名', '嘉兴港区加氢站'], ['地址', '浙江省嘉兴市港区东方大道'], ['运营状态', '运营中'], ['营业时间', '24 小时']] },
      { key: 'ops', label: '运营数据', fields: [['今日加氢量', '2,040 kg'], ['可用枪位', '4 / 4'], ['排队车辆', '0 辆'], ['最近维护', '2026-06-28']] },
      { key: 'contact', label: '联系信息', fields: [['站长', '陈站长'], ['联系电话', '0573-6666****'], ['所属区域', '华东 · 嘉兴']] },
    ],
  },
  {
    id: 's3', type: 'station', name: '深圳坪山加氢站', subtitle: '广东省深圳市坪山区', region: '广东 · 深圳', status: '暂停', statGroup: '暂停',
    top: 58, left: 18, roles: ['manager', 'ops', 'internal', 'customer', 'fleet'],
    layers: [
      { key: 'basic', label: '站点信息', fields: [['站名', '深圳坪山加氢站'], ['地址', '广东省深圳市坪山区比亚迪路'], ['运营状态', '暂停'], ['暂停原因', '设备维保']] },
      { key: 'ops', label: '运营数据', fields: [['今日加氢量', '0 kg'], ['可用枪位', '0 / 3'], ['预计恢复', '2026-07-05'], ['最近维护', '2026-07-01']] },
      { key: 'contact', label: '联系信息', fields: [['站长', '周站长'], ['联系电话', '0755-7777****'], ['所属区域', '华南 · 深圳']] },
    ],
  },
  {
    id: 'r1', type: 'repair', name: '嘉兴氢能维保中心', subtitle: '浙江省嘉兴市南湖区', region: '浙江 · 嘉兴', status: '营业中', statGroup: '营业中',
    top: 46, left: 72, roles: ['manager', 'ops', 'internal', 'customer', 'driver', 'fleet'],
    layers: [
      { key: 'basic', label: '站点信息', fields: [['站名', '嘉兴氢能维保中心'], ['地址', '浙江省嘉兴市南湖区科技大道'], ['营业状态', '营业中'], ['服务类型', '氢系统 / 底盘 / 轮胎']] },
      { key: 'ops', label: '运营数据', fields: [['今日工单', '6 单'], ['在修车辆', '3 辆'], ['平均等待', '2.5 小时'], ['满意度', '98%']] },
      { key: 'contact', label: '联系信息', fields: [['负责人', '孙工'], ['联系电话', '0573-5555****'], ['所属区域', '华东 · 嘉兴']] },
    ],
  },
  {
    id: 'r2', type: 'repair', name: '广州氢车快修站', subtitle: '广东省广州市黄埔区', region: '广东 · 广州', status: '营业中', statGroup: '营业中',
    top: 62, left: 42, roles: ['manager', 'ops', 'internal', 'fleet'],
    layers: [
      { key: 'basic', label: '站点信息', fields: [['站名', '广州氢车快修站'], ['地址', '广东省广州市黄埔区开源大道'], ['营业状态', '营业中'], ['服务类型', '快修 / 保养 / 证照']] },
      { key: 'ops', label: '运营数据', fields: [['今日工单', '4 单'], ['在修车辆', '1 辆'], ['平均等待', '1.2 小时'], ['满意度', '96%']] },
      { key: 'contact', label: '联系信息', fields: [['负责人', '黄工'], ['联系电话', '020-8888****'], ['所属区域', '华南 · 广州']] },
    ],
  },
  {
    id: 'r3', type: 'repair', name: '深圳氢能专修站', subtitle: '广东省深圳市宝安区', region: '广东 · 深圳', status: '维保中', statGroup: '维保中',
    top: 24, left: 58, roles: ['manager', 'ops', 'internal', 'customer'],
    layers: [
      { key: 'basic', label: '站点信息', fields: [['站名', '深圳氢能专修站'], ['地址', '广东省深圳市宝安区航城大道'], ['营业状态', '维保中'], ['预计恢复', '2026-07-03']] },
      { key: 'ops', label: '运营数据', fields: [['今日工单', '0 单'], ['在修车辆', '0 辆'], ['暂停原因', '举升设备检修'], ['最近完工', '2026-06-29']] },
      { key: 'contact', label: '联系信息', fields: [['负责人', '吴工'], ['联系电话', '0755-9999****'], ['所属区域', '华南 · 深圳']] },
    ],
  },
];

const MAP_STAT_DEFS = {
  vehicle: [
    { key: 'all', label: '资源总数', filter: () => true },
    { key: 'online', label: '在线车辆', filter: (item) => item.statGroup === '在线' },
    { key: 'offline', label: '离线车辆', filter: (item) => item.statGroup === '离线' },
    { key: 'license', label: '证照异常', filter: (item) => item.statGroup === '证照异常' },
  ],
  station: [
    { key: 'all', label: '站点总数', filter: () => true },
    { key: 'running', label: '运营中', filter: (item) => item.statGroup === '运营中' },
    { key: 'paused', label: '暂停服务', filter: (item) => item.statGroup === '暂停' },
    { key: 'volume', label: '今日加氢 >1t', filter: (item) => item.layers?.[1]?.fields?.some(([k, v]) => k === '今日加氢量' && parseInt(String(v).replace(/,/g, ''), 10) >= 1000) },
  ],
  repair: [
    { key: 'all', label: '维修站总数', filter: () => true },
    { key: 'open', label: '营业中', filter: (item) => item.statGroup === '营业中' },
    { key: 'maint', label: '维保中', filter: (item) => item.statGroup === '维保中' },
    { key: 'busy', label: '在修 ≥2 辆', filter: (item) => {
      const row = item.layers?.[1]?.fields?.find(([k]) => k === '在修车辆');
      return row && parseInt(String(row[1]), 10) >= 2;
    } },
  ],
};

function mapResourcesByRole(roleKey) {
  if (['manager', 'ops', 'internal'].includes(roleKey)) {
    return MAP_MOCK_RESOURCES;
  }
  return MAP_MOCK_RESOURCES.filter((item) => item.roles.includes(roleKey));
}

function mapResourceIcon(type) {
  if (type === 'station') return IconStation;
  if (type === 'repair') return IconRepair;
  return IconTruck;
}

const MapPage = () => {
  const [resourceType, setResourceType] = useState('vehicle');
  const [viewMode, setViewMode] = useState('map');
  const [mapRole, setMapRole] = useState('manager');
  const [search, setSearch] = useState('');
  const [clusterExpanded, setClusterExpanded] = useState(false);
  const [detailItem, setDetailItem] = useState(null);
  const [detailLayer, setDetailLayer] = useState('basic');
  const [statDrill, setStatDrill] = useState(null);

  const roleResources = useMemo(() => mapResourcesByRole(mapRole), [mapRole]);

  const typeResources = useMemo(
    () => roleResources.filter((item) => item.type === resourceType),
    [roleResources, resourceType],
  );

  const filteredResources = useMemo(() => {
    const q = search.trim().toLowerCase();
    let list = typeResources;
    if (statDrill?.filter) {
      list = list.filter(statDrill.filter);
    }
    if (!q) return list;
    return list.filter((item) => (
      item.name.toLowerCase().includes(q)
      || item.subtitle.toLowerCase().includes(q)
      || item.region.toLowerCase().includes(q)
      || item.status.toLowerCase().includes(q)
    ));
  }, [typeResources, search, statDrill]);

  const statCards = useMemo(() => {
    const defs = MAP_STAT_DEFS[resourceType] || [];
    return defs.map((def) => ({
      ...def,
      value: typeResources.filter(def.filter).length,
    }));
  }, [resourceType, typeResources]);

  const openDetail = useCallback((item) => {
    setDetailItem(item);
    setDetailLayer(item.layers?.[0]?.key || 'basic');
  }, []);

  const closeDetail = useCallback(() => {
    setDetailItem(null);
  }, []);

  const handleResourceTypeChange = useCallback((type) => {
    setResourceType(type);
    setClusterExpanded(false);
    setDetailItem(null);
    setStatDrill(null);
    setSearch('');
  }, []);

  const handleViewModeChange = useCallback((mode) => {
    setViewMode(mode);
    setClusterExpanded(false);
    setDetailItem(null);
    if (mode !== 'list') setStatDrill(null);
  }, []);

  const handleStatDrill = useCallback((def) => {
    setStatDrill(def);
    setViewMode('list');
    setDetailItem(null);
    mpToast.info(`下钻查看「${def.label}」明细（${typeResources.filter(def.filter).length} 条）`);
  }, [typeResources]);

  const clusterCenter = useMemo(() => {
    if (!filteredResources.length) return { top: 45, left: 50 };
    const top = filteredResources.reduce((sum, item) => sum + item.top, 0) / filteredResources.length;
    const left = filteredResources.reduce((sum, item) => sum + item.left, 0) / filteredResources.length;
    return { top, left };
  }, [filteredResources]);

  if (detailItem) {
    const activeLayer = detailItem.layers.find((layer) => layer.key === detailLayer) || detailItem.layers[0];
    return (
      <div className="xll-map-detail" data-annotation-id="page-map">
        <div className="xll-map-detail-head">
          <button type="button" className="xll-map-detail-back" onClick={closeDetail} aria-label="返回地图列表">
            <IconBack />
          </button>
          <span className="xll-map-detail-title">{MAP_RESOURCE_META[detailItem.type]?.label}详情</span>
        </div>
        <div className="xll-map-detail-hero">
          <div className="xll-map-detail-hero-name">{detailItem.name}</div>
          <div className="xll-map-detail-hero-sub">{detailItem.subtitle}</div>
          <span className="xll-map-detail-tag">{detailItem.status}</span>
        </div>
        <div className="xll-map-detail-tabs" role="tablist" aria-label="详情信息层">
          {detailItem.layers.map((layer) => (
            <button
              key={layer.key}
              type="button"
              role="tab"
              aria-selected={detailLayer === layer.key}
              className={`xll-map-detail-tab${detailLayer === layer.key ? ' active' : ''}`}
              onClick={() => setDetailLayer(layer.key)}
            >
              {layer.label}
            </button>
          ))}
        </div>
        <div className="xll-map-detail-body">
          {activeLayer.fields.map(([label, value]) => (
            <div className="xll-map-detail-kv" key={label}>
              <span className="xll-map-detail-kv-label">{label}</span>
              <span className="xll-map-detail-kv-value">{value}</span>
            </div>
          ))}
        </div>
      </div>
    );
  }

  return (
    <div className="xll-map-page" data-annotation-id="page-map">
      <div className="xll-map-tabs" role="tablist" aria-label="资源类型">
        {Object.entries(MAP_RESOURCE_META).map(([key, meta]) => (
          <button
            key={key}
            type="button"
            role="tab"
            aria-selected={resourceType === key}
            className={`xll-map-tab${resourceType === key ? ' active' : ''}`}
            onClick={() => handleResourceTypeChange(key)}
          >
            {meta.label}
          </button>
        ))}
      </div>

      <div className="xll-map-role-bar" aria-label="角色权限预览">
        <span className="xll-map-role-label">当前角色</span>
        {MAP_ROLE_OPTIONS.map((role) => (
          <button
            key={role.key}
            type="button"
            className={`xll-map-role-chip${mapRole === role.key ? ' active' : ''}${role.group === 'external' ? ' xll-map-role-chip--external' : ''}`}
            onClick={() => {
              setMapRole(role.key);
              setClusterExpanded(false);
              setDetailItem(null);
              setStatDrill(null);
            }}
          >
            {role.label}
          </button>
        ))}
      </div>

      <div className="xll-map-mode-bar" role="tablist" aria-label="浏览模式">
        {MAP_VIEW_MODES.map((mode) => (
          <button
            key={mode.key}
            type="button"
            role="tab"
            aria-selected={viewMode === mode.key}
            className={`xll-map-mode-btn${viewMode === mode.key ? ' active' : ''}`}
            onClick={() => handleViewModeChange(mode.key)}
          >
            {mode.label}
          </button>
        ))}
      </div>

      {(viewMode === 'map' || viewMode === 'list') && (
        <div className="xll-map-toolbar">
          <div className="xll-map-search-wrap">
            <IconSearch />
            <input
              className="xll-map-search"
              placeholder={MAP_RESOURCE_META[resourceType].searchPlaceholder}
              value={search}
              onChange={(e) => setSearch(e.target.value)}
              aria-label="资源搜索"
            />
          </div>
          <button type="button" className="xll-map-filter" aria-label="筛选" onClick={() => mpToast.info('打开筛选（原型）：区域 / 运营状态')}>
            <IconFilter />
          </button>
        </div>
      )}

      {statDrill && viewMode === 'list' ? (
        <div className="xll-map-filter-tag">
          统计下钻：{statDrill.label}
          <button type="button" style={{ border: 'none', background: 'transparent', color: 'inherit', cursor: 'pointer', fontWeight: 700 }} onClick={() => setStatDrill(null)} aria-label="清除统计下钻">×</button>
        </div>
      ) : null}

      {viewMode === 'map' && (
        <div className="xll-map-area" role="img" aria-label={`${MAP_RESOURCE_META[resourceType].label}分布地图`}>
          <button type="button" className="xll-map-full" onClick={() => { setClusterExpanded(false); mpToast.info('恢复全图视野（原型）'); }} aria-label="恢复全图视野">
            <IconGlobe />
            全图
          </button>
          {!filteredResources.length ? (
            <div className="xll-map-placeholder">
              <span>当前角色暂无可见{MAP_RESOURCE_META[resourceType].label}</span>
            </div>
          ) : !clusterExpanded ? (
            <button
              type="button"
              className={`xll-map-marker xll-map-marker--cluster ${MAP_RESOURCE_META[resourceType].markerClass}`.trim()}
              style={{ top: `${clusterCenter.top}%`, left: `${clusterCenter.left}%` }}
              onClick={() => setClusterExpanded(true)}
              aria-label={`展开 ${filteredResources.length} 个聚合点`}
            >
              {filteredResources.length}
            </button>
          ) : (
            filteredResources.map((item) => {
              const ItemIcon = mapResourceIcon(item.type);
              return (
                <button
                  key={item.id}
                  type="button"
                  className={`xll-map-marker xll-map-marker-btn ${MAP_RESOURCE_META[item.type].markerClass}`.trim()}
                  style={{ top: `${item.top}%`, left: `${item.left}%` }}
                  title={item.name}
                  aria-label={`查看${item.name}`}
                  onClick={() => openDetail(item)}
                >
                  <ItemIcon />
                </button>
              );
            })
          )}
          <div className="xll-map-placeholder">
            <span>{MAP_RESOURCE_META[resourceType].label} · {clusterExpanded ? '点击标记进入详情' : '点击聚合点展开标记'}</span>
            {search && <span>搜索：{search}</span>}
          </div>
          <span className="xll-map-brand">腾讯地图</span>
        </div>
      )}

      {viewMode === 'list' && (
        <div className="xll-map-list">
          <div className="xll-map-list-head">
            <span>{MAP_RESOURCE_META[resourceType].label}列表</span>
            <span>共 {filteredResources.length} 条</span>
          </div>
          {!filteredResources.length ? (
            <div className="xll-map-empty">当前角色权限下暂无{MAP_RESOURCE_META[resourceType].label}数据</div>
          ) : filteredResources.map((item) => (
            <button key={item.id} type="button" className="xll-map-list-card" onClick={() => openDetail(item)}>
              <div className="xll-map-list-card-title">{item.name}</div>
              <div className="xll-map-list-card-sub">{item.subtitle}</div>
              <div className="xll-map-list-card-meta">
                <span>{item.region}</span>
                <span>{item.status}</span>
              </div>
            </button>
          ))}
        </div>
      )}

      {viewMode === 'stats' && (
        <div className="xll-map-stat-grid">
          {statCards.map((card) => (
            <button key={card.key} type="button" className="xll-map-stat-card" onClick={() => handleStatDrill(card)}>
              <div className="xll-map-stat-val">{card.value}</div>
              <div className="xll-map-stat-label">{card.label}</div>
              <div className="xll-map-stat-hint">点击查看明细 →</div>
            </button>
          ))}
        </div>
      )}
    </div>
  );
};

const MinePage = () => (
  <div data-annotation-id="page-mine">
    <div className="xll-mine-hero">
      <div className="xll-mine-avatar" aria-hidden="true">张</div>
      <div className="xll-mine-hero-info">
        <div className="xll-mine-hero-name">张明辉</div>
        <div className="xll-mine-hero-role">运维主管 · ONE-OS</div>
      </div>
    </div>
    <div className="xll-mine-card">
      <div className="xll-mine-row"><span className="xll-mine-label">姓名</span><span className="xll-mine-value">张明辉</span></div>
      <div className="xll-mine-row"><span className="xll-mine-label">账号</span><span className="xll-mine-value">zhangmh@one-os.com</span></div>
      <div className="xll-mine-row"><span className="xll-mine-label">手机号</span><span className="xll-mine-value">138****6688</span></div>
    </div>
  </div>
);

const TodoDetailPage = ({ task, onBack, onDone }) => {
  const theme = TASK_THEME[task.type] || TASK_THEME.delivery;
  return (
    <div className="xll-sub-page">
      <div className="xll-sub-hero" style={{ '--xll-accent': theme.accent }}>
        <span className="xll-sub-hero-tag">{theme.label}任务</span>
        <div className="xll-sub-hero-title">{task.title}</div>
      </div>
      <div className="xll-sub-card">
        <div className="xll-sub-section-title">任务详情</div>
        {task.fields.map((f) => (
          <div className="xll-kv" key={f.label} style={{ marginBottom: 10 }}>
            <span className="xll-kv-label">{f.label}</span>
            <span className={`xll-kv-value${f.warn ? ' warn' : ''}`}>
              {f.warn ? f.value.replace('（已到期）', '') : f.value}
              {f.warn && <span className="xll-warn-tag"><IconWarn />已到期</span>}
            </span>
          </div>
        ))}
        <p style={{ fontSize: 13, color: COLOR_MUTED, marginTop: 12, lineHeight: 1.55 }}>
          请核对以上信息并完成现场办理。提交后将同步至 ONE-OS 后台对应模块。
        </p>
        <div className="xll-sub-foot">
          <button type="button" className="xll-sub-btn xll-sub-btn-ghost" onClick={onBack}>返回</button>
          <button type="button" className="xll-sub-btn xll-sub-btn-primary" onClick={onDone}>确认办理</button>
        </div>
      </div>
    </div>
  );
};

const MpToastHost = () => {
  const [items, setItems] = useState(mpToastStore.items);
  useEffect(() => {
    const sync = () => setItems([...mpToastStore.items]);
    mpToastListeners.add(sync);
    return () => mpToastListeners.delete(sync);
  }, []);
  if (!items.length) return null;
  return (
    <div className="xll-mp-toast-stack" aria-live="polite">
      {items.map((t) => (
        <div key={t.id} className={`xll-mp-toast xll-mp-toast--${t.type}`} role="status">{t.content}</div>
      ))}
    </div>
  );
};

export default function XiaoLingLingMiniApp() {
  const { page, setPage } = useHashPage(XLL_PAGE_ROUTE);
  const [todoDetail, setTodoDetail] = useState(null);
  const [prdKey, setPrdKey] = useState(null);
  const [auditPrdKey, setAuditPrdKey] = useState(null);

  const handleAuditPrdKeyChange = useCallback((key) => {
    setAuditPrdKey(key || null);
  }, []);

  useEffect(() => {
    setTodoDetail(null);
  }, [page]);

  const navTitle = useMemo(() => {
    if (todoDetail) return '任务办理';
    if (MODULE_PAGE_IDS.has(page)) return MODULE_NAV_TITLES[page] || '小羚羚';
    const tab = MAIN_TABS.find((t) => t.key === page);
    return tab?.navLabel || '小羚羚';
  }, [page, todoDetail]);

  const currentPrdKey = useMemo(() => {
    if (page === 'delivery') return 'delivery';
    if (page === 'third-return') return 'thirdReturn';
    if (page === 'replace') return 'replace';
    if (page === 'audit') return auditPrdKey || 'audit';
    if (page === 'inspection' || page === 'vehicle') return 'business';
    if (todoDetail) return 'todo-detail';
    return page;
  }, [page, todoDetail, auditPrdKey]);

  const handleOpenPrd = useCallback((key) => setPrdKey(key || currentPrdKey), [currentPrdKey]);

  const moduleBackRef = useRef(null);
  const registerModuleBack = useCallback((handler) => {
    moduleBackRef.current = handler;
  }, []);

  const handleNavBack = useCallback(() => {
    if (MODULE_PAGE_IDS.has(page) && typeof moduleBackRef.current === 'function' && moduleBackRef.current()) return;
    if (MODULE_PAGE_IDS.has(page)) {
      setPage('business');
      return;
    }
    if (todoDetail) setTodoDetail(null);
  }, [page, todoDetail, setPage]);

  const handleProcessTask = useCallback((task) => {
    if (task.targetPage === 'audit') {
      setPage('audit');
      return;
    }
    if (task.type === 'inspection') {
      setPage('inspection');
      return;
    }
    if (task.type === 'delivery') {
      setPage('delivery');
      return;
    }
    setTodoDetail(task);
  }, [setPage]);

  const handleBusinessEntry = useCallback((item) => {
    const nextPage = BIZ_ENTRY_PAGE_MAP[item.key];
    if (nextPage) {
      setPage(nextPage);
      return;
    }
    mpToast.info(`进入「${item.label}」模块（原型）`);
  }, [setPage]);

  const showTabBar = MAIN_TAB_PAGE_IDS.has(page) && !todoDetail;
  const showBack = MODULE_PAGE_IDS.has(page) || !!todoDetail;

  const bodyClass = useMemo(() => {
    const parts = ['xll-body'];
    if (MODULE_PAGE_IDS.has(page)) parts.push('xll-body--module');
    else if (page === 'map' && !todoDetail) parts.push('xll-body--map');
    return parts.join(' ');
  }, [page, todoDetail]);

  const navigateByAnnotationRoute = useCallback((route) => {
    const r = String(route || '');
    if (r === 'login') {
      setPage('todo');
      setTodoDetail(null);
      setAuditPrdKey(null);
      return;
    }
    setTodoDetail(null);
    if (ANNOTATION_ROUTE_TO_PAGE[r]) {
      setPage(ANNOTATION_ROUTE_TO_PAGE[r]);
      setAuditPrdKey(null);
      return;
    }
    if (r.startsWith('audit-')) {
      setPage('audit');
      setAuditPrdKey(r);
    }
  }, [setPage]);

  const annotationViewerOptions = useMemo(() => ({
    currentPageId: currentPrdKey,
    toolbarEdge: 'right',
    showToolbar: true,
    showThemeToggle: true,
    showColorFilter: true,
    emptyWhenNoData: false,
    onDirectoryRoute: (node) => {
      if (typeof node.route === 'string') {
        navigateByAnnotationRoute(node.route);
      }
    },
  }), [currentPrdKey, navigateByAnnotationRoute]);

  const renderPageBody = () => {
    if (page === 'audit') {
      return <ApprovalCenterModule onRegisterBack={registerModuleBack} onPrdKeyChange={handleAuditPrdKeyChange} />;
    }
    if (page === 'inspection') {
      return <AnnualReviewModule onRegisterBack={registerModuleBack} />;
    }
    if (page === 'vehicle') {
      return <VehicleManagementModule onRegisterBack={registerModuleBack} />;
    }
    if (page === 'delivery') {
      return <DeliveryModule onRegisterBack={registerModuleBack} />;
    }
    if (page === 'third-return') {
      return <ThirdReturnModule onRegisterBack={registerModuleBack} />;
    }
    if (page === 'replace') {
      return <ReplaceModule onRegisterBack={registerModuleBack} />;
    }
    if (todoDetail) {
      return (
        <TodoDetailPage
          task={todoDetail}
          onBack={() => setTodoDetail(null)}
          onDone={() => { mpToast.success('办理完成（原型）'); setTodoDetail(null); }}
        />
      );
    }
    if (page === 'todo') return <TodoPage onProcess={handleProcessTask} />;
    if (page === 'business') return <BusinessPage onEntry={handleBusinessEntry} />;
    if (page === 'map') return <MapPage />;
    return <MinePage />;
  };

  return (
    <>
    <div className="xll-root">
      <style>{PAGE_STYLE}</style>
      <div className="xll-phone">
        <div className="xll-chrome">
          <IphoneStatusBar />
          <div className="xll-navbar">
            <div className="xll-nav-left">
              {showBack ? (
                <button type="button" className="xll-back" onClick={handleNavBack} aria-label="返回"><IconBack /></button>
              ) : null}
            </div>
            <span className="xll-nav-title">{navTitle}</span>
            <div className="xll-nav-right">
              <MpCapsule />
            </div>
          </div>
        </div>

        <div className={bodyClass}>
          {renderPageBody()}
        </div>

        {showTabBar && (
          <div className="xll-tabbar" role="tablist" aria-label="主导航" data-annotation-id="main-tabbar">
            {MAIN_TABS.map((tab) => {
              const TabIcon = TAB_ICON_MAP[tab.key];
              const active = page === tab.key;
              return (
                <button
                  key={tab.key}
                  type="button"
                  role="tab"
                  aria-selected={active}
                  aria-label={tab.label}
                  className={`xll-tabbar-btn${active ? ' active' : ''}`}
                  onClick={() => setPage(tab.key)}
                >
                  <span className="xll-tabbar-icon"><TabIcon active={active} /></span>
                  {tab.label}
                </button>
              );
            })}
          </div>
        )}

        <MpToastHost />
        <PrdModal prdKey={prdKey} onClose={() => setPrdKey(null)} />
      </div>
    </div>
    <AnnotationViewer
      source={annotationSourceDocument as unknown as AnnotationSourceDocument}
      options={annotationViewerOptions as AnnotationViewerOptions}
    />
    </>
  );
};

