// 【重要】必须使用 const Component 作为组件变量名 // ONE-OS 小程序 - 小羚羚(待办 / 业务 / 地图 / 我的) const { useState, useMemo, useCallback, useEffect, useRef } = React; 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 antd = (() => { const raw = window.antd; if (!raw) return {}; return raw.default && typeof raw.default === 'object' ? { ...raw, ...raw.default } : raw; })(); const message = antd.message || { info: () => {}, success: () => {}, warning: () => {} }; const Modal = antd.Modal; const Button = antd.Button; const Drawer = antd.Drawer; const Input = antd.Input; 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 TASK_THEME = { delivery: { accent: XLL_GREEN, soft: XLL_GREEN_SOFT, label: '交车' }, return: { accent: '#2563EB', soft: 'rgba(37, 99, 235, 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: '异动' }, }; const TODO_TASKS = [ { id: 't1', type: 'delivery', badge: null, title: '交车任务(3辆)', fields: [ { label: '项目名称', value: '嘉兴氢能示范项目' }, { label: '客户名称', value: '嘉兴某某物流有限公司' }, { label: '交车地点', value: '浙江省嘉兴市南湖区xx路xx号' }, { label: '交车时间', value: '2026-06-05 09:30' }, ], }, { id: 't2', type: 'return', badge: null, title: '还车任务(粤AGP5368)', fields: [ { label: '项目名称', value: '嘉兴腾4.5T租赁' }, { label: '客户名称', value: '嘉兴某某物流有限公司' }, { label: '还车时间', value: '2026-06-03 16:20' }, ], }, { id: 't3', type: 'inspection', badge: 4, title: '年审任务', fields: [ { label: '车牌号', value: '粤B58888F' }, { label: '年审/等评时间', value: '2026-05-28(已到期)', warn: true }, ], }, { id: 't4', type: 'transfer', badge: 2, title: '张三发起的调拨申请', fields: [ { label: '调拨日期', value: '2026-06-01' }, { label: '出发区域', value: '广东省深圳市' }, { label: '接收区域', value: '浙江省杭州市' }, { label: '车辆数', value: '40辆' }, ], }, { id: 't5', type: 'move', badge: 3, title: '异动申请(粤A08875F)', fields: [ { label: '异动类型', value: '保养' }, { label: '目的地', value: '嘉兴xx检测站' }, { label: '计划时间', value: '2026-06-02 08:00' }, ], }, ]; 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: '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-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}; } .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:12px; color:${COLOR_MUTED}; 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:0; top:12px; bottom:12px; width:3px; border-radius:0 3px 3px 0; background:var(--mod-accent, ${XLL_GREEN}); } .xll-mod-card-head { display:flex; justify-content:space-between; align-items:center; gap:10px; margin-bottom:8px; } .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-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:12px; } .xll-mod-meta-label { color:${COLOR_MUTED}; } .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:36px; 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; } .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-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-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:fixed; 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(72vh, 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(88vh, 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}; } .xll-mod-upload:active { background:${XLL_GREEN_SOFT}; 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-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 { 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-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-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:0; top:12px; bottom:12px; width:3px; border-radius:0 3px 3px 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-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-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:11px; color:${COLOR_MUTED}; cursor:pointer; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:3px; 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-left:3px solid ${XLL_GREEN}; padding:10px 12px; border-radius:0 8px 8px 0; 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-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-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-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-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-head, .hc-fee-table-row { display:grid; grid-template-columns:44px minmax(0,1fr) 96px; align-items:center; gap:0; } .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}; } .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-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-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-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-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: '#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_QUICK_FILTERS = ['合同审批', '提车应收款', '租赁账单', '车辆调拨']; 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: '186800.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', initiator: '李晓彤', initiateTime: '2026-04-18 09:00', arriveTime: '2026-04-18 09:30', finishTime: '', 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; return `${task.status}:${list.join(',')}`; }; 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 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_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: '赵小峰', 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: '何苗苗', 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: '系统', 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 DV_AUTHORIZED_PERSONS = [ { id: 'ap1', name: '李晓明', phone: '13800138001' }, { id: 'ap2', name: '王芳', phone: '13900139002' }, { id: 'ap3', name: '赵强', phone: '13700137003' }, { id: 'ap4', name: '陈静', phone: '13600136004' }, ]; const dvFindAuthorizedPerson = (id) => DV_AUTHORIZED_PERSONS.find((p) => p.id === id) || null; 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, 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: '—' }, ] : [ { 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 }) => (
{label}
{value || '—'}
); 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 (
{vehicle.plateNo || '待上牌'}
{vehicle.brand} · {vehicle.model}
#{vehicle.index}
本车实收合计 {formatMoneySymbol(vehicleActual)}
实收月租金 {formatYuan(vehicle.actualRent)}
应收保证金 {formatYuan(vehicle.receivableDeposit)}
实收服务费 {formatYuan(vehicle.actualService)}
减免金额 {formatYuan(vehicle.discountAmount)}
{(vehicle.serviceItems || []).length > 0 && ( <> {serviceOpen && (
{(vehicle.serviceItems || []).map((s, i) => (
{s.name} {formatMoneySymbol(s.actual)} 应收 {formatYuan(s.receivable)} · 减免 {formatYuan(s.discount)}
))}
)} )}
); }; 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 = '请上传不超过 20MB 的 png, jpg, jpeg, doc, docx, xlsx, xls, ppt, pdf 格式文件'; const emptyDecisionForm = () => ({ notifyEmail: false, notifySms: false, ccUsers: [], opinion: '', attachments: [], }); 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 handleDrawerClose = () => { 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(); }; if (!Drawer) return null; return ( {ccPickerOpen ? ( <>
{MOCK_CC_CANDIDATES.map((name) => ( ))}
) : ( <>
通知方式
附件上传
{UPLOAD_HINT}
{form.attachments.length > 0 && (
{form.attachments.map((f) => (
{f.name}
))}
)}
抄送人
{form.ccUsers.length > 0 && (
{form.ccUsers.map((name) => ( {name} ))}
)}
审批意见