// 【重要】必须使用 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-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; } .xll-mod-drawer-type-btn.active { border-color:${XLL_GREEN}; color:${XLL_GREEN_DEEP}; background:${XLL_GREEN_SOFT}; font-weight:600; } .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-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: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-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:0 14px 14px; } .xll-dv-filter-field { margin-bottom:14px; } .xll-dv-filter-label { display:block; font-size:13px; font-weight:600; color:${COLOR_TEXT}; margin-bottom:8px; } .xll-dv-filter-input { width:100%; min-height:44px; border:1px solid ${COLOR_LINE}; border-radius:10px; padding:0 12px; font-size:14px; box-sizing:border-box; outline:none; background:${COLOR_BG}; color:${COLOR_TEXT}; } .xll-dv-filter-input:focus { border-color:${XLL_GREEN}; box-shadow:0 0 0 2px ${XLL_GREEN_SOFT}; } .xll-dv-filter-date-row { display:flex; align-items:center; gap:8px; } .xll-dv-filter-date-row .xll-dv-filter-input { flex:1; min-width:0; } .xll-dv-filter-hint { font-size:12px; color:${COLOR_MUTED}; line-height:1.55; margin-top:8px; } .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; } .xll-biz-item:active { background:${COLOR_PAGE}; } .xll-biz-item:focus-visible { outline:2px solid ${XLL_GREEN}; outline-offset:2px; } .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:2px; right:calc(50% - 32px); 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); } .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)); } @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_RESERVE_PLATES = [ { plateNo: '浙F80088', parkingLot: '嘉兴港区氢能停车场', brand: '苏龙', model: '海格牌18吨双飞翼货车', vin: 'LKLG7C4E4NA774701' }, { plateNo: '浙F88601', parkingLot: '平湖指定停车场', brand: '现代', model: '帕力安牌4.5吨冷链车', vin: 'LNBSCPKB8RR123401' }, { plateNo: '浙F88602', parkingLot: '平湖指定停车场', brand: '现代', model: '帕力安牌4.5吨冷链车', vin: 'LNBSCPKB8RR123402' }, ]; const DV_FORM_STEPS = [ { key: 'info', label: '交车信息' }, { key: 'equip', label: '车辆信息' }, { key: 'metrics', label: '交车数据' }, { key: 'photos', label: '交车照片' }, { key: 'confirm', label: '确认提交' }, ]; const DV_PHOTO_SECTIONS = [ { key: 'body', label: '车身照片' }, { key: 'chassis', label: '底盘照片' }, { key: 'tire', label: '轮胎照片' }, { key: 'defect', label: '瑕疵照片' }, { key: 'other', label: '其他照片' }, ]; 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, hasAd: '无', hasTailgate: '有', spareTire: '有', driverTraining: '已完成' }, ], }, { 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: '有', driverTraining: '已完成' }, { vehicleKey: 2, seq: 2, vehicleType: '18吨双飞翼货车', brand: '苏龙', model: '海格牌18吨双飞翼货车', vin: 'LKLG7C4E4NA774702', plateNo: '沪A03802F', deliveryTime: '2025-11-20 09:30', deliveryPerson: '何苗苗', deliveryStatus: '已保存', deliveryMileage: null, deliveryH2: null, deliveryH2Unit: '%', deliveryElec: null, hasAd: '无', hasTailgate: '有', spareTire: '有', driverTraining: '已完成' }, ], }, { 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, hasAd: '有', hasTailgate: '有', spareTire: '有', driverTraining: '已完成', vehicleReturned: true }, { 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: '已完成', vehicleReturned: false }, ], }, ]; 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 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 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' }; return { text: status || '未开始', cls: 'neutral' }; }; const dvCardStatusClass = (status) => { if (status === '客户已签章') return 'ok'; if (status === '待客户签章') return 'info'; 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, hasAd: v.hasAd || '', hasTailgate: v.hasTailgate || '', spareTire: v.spareTire || '', driverTraining: v.driverTraining || '', vehicleReturned: v.vehicleReturned, }); }); }); return rows; }; const dvBuildEmptyForm = (row) => ({ plateNo: row.plateNo || '', brand: row.brand || '', model: row.model || '', vin: row.vin || '', hasAd: row.hasAd || '', hasTailgate: row.hasTailgate || '', spareTire: row.spareTire || '', driverTraining: row.driverTraining || '', deliveryMileage: row.deliveryMileage != null ? String(row.deliveryMileage) : '', deliveryH2: row.deliveryH2 != null ? String(row.deliveryH2) : '', deliveryH2Unit: row.deliveryH2Unit || '%', deliveryElec: row.deliveryElec != null ? String(row.deliveryElec) : '', deliveryTime: row.deliveryTime ? row.deliveryTime.replace(' ', 'T').slice(0, 16) : '', }); const dvFormatH2 = (v, unit) => (v == null || v === '' ? '—' : `${v} ${unit || '%'}`); const dvFormatMileage = (v) => (v == null || v === '' ? '—' : `${Number(v).toLocaleString()} 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} ))}
)}
审批意见