// 【重要】必须使用 const Component 作为组件变量名 // ONE-OS 小程序 - 提车应收款审批办理(参照 web 端提车应收款-查看,适配移动端) const { useState, useMemo, useCallback, useRef, useEffect } = React; const moment = window.moment || window.dayjs; const COLOR_PRIMARY = '#16D1A1'; const COLOR_PRIMARY_DEEP = '#00BFA5'; const COLOR_PRIMARY_SOFT = 'rgba(22, 209, 161, 0.12)'; const COLOR_ACCENT = '#F97316'; const COLOR_ACCENT_DEEP = '#EA580C'; const COLOR_ACCENT_SOFT = 'rgba(249, 115, 22, 0.12)'; 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_SUCCESS = '#00B42A'; const COLOR_DANGER = '#F53F3F'; const COLOR_WARN = '#FF7D00'; const FONT_FAMILY = '-apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", STHeiti, sans-serif'; const resolveAntdBundle = () => { const raw = window.antd; if (!raw) return {}; if (raw.default && typeof raw.default === 'object') return { ...raw, ...raw.default }; return raw; }; const antd = resolveAntdBundle(); const message = antd.message || { info: () => {}, success: () => {}, warning: () => {} }; const Drawer = antd.Drawer; const Modal = antd.Modal; const formatMoney = (val, withSymbol = true) => { const n = parseFloat(val); if (Number.isNaN(n)) return val || '—'; const s = n.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); return withSymbol ? `¥${s}` : s; }; const formatYuan = (val) => { const n = parseFloat(val); if (Number.isNaN(n)) return '—'; return `${n.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} 元`; }; /** 根据审批任务构建详情 mock(与 web 端字段对齐) */ 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%,开票项目:*现代服务*车辆租赁费;备注:嘉兴氢能示范项目-提车首付款', task: task || { bizNo: 'TC-2026-0312', status: '审批中' }, }; }; const calcTotals = (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; const receivableTotal = ( receivableRent + receivableDeposit + receivableService + hydrogenReceivable ).toFixed(2); const actualTotal = ( actualRent + receivableDeposit + actualService - discountTotal + hydrogenActual ).toFixed(2); return { receivableRent: receivableRent.toFixed(2), actualRent: actualRent.toFixed(2), receivableDeposit: receivableDeposit.toFixed(2), receivableService: receivableService.toFixed(2), actualService: actualService.toFixed(2), discountTotal: discountTotal.toFixed(2), receivableTotal, actualTotal, }; }; const PAGE_STYLE = ` .tc-mini-root { min-height: 100dvh; background: linear-gradient(165deg, #e8ebef 0%, ${COLOR_PAGE} 40%); display: flex; justify-content: center; padding: 16px 12px 32px; box-sizing: border-box; font-family: ${FONT_FAMILY}; -webkit-font-smoothing: antialiased; } .tc-phone { width: 100%; max-width: 390px; min-height: 844px; background: ${COLOR_PAGE}; border-radius: 28px; overflow: hidden; box-shadow: 0 24px 48px rgba(15, 23, 42, 0.14), 0 0 0 1px rgba(15, 23, 42, 0.05); display: flex; flex-direction: column; position: relative; } .tc-chrome { flex-shrink: 0; background: ${COLOR_BG}; } .tc-status-bar { height: 44px; padding: 14px 24px 0; display: flex; align-items: center; justify-content: space-between; box-sizing: border-box; } .tc-status-time { font-size: 15px; font-weight: 600; color: ${COLOR_TEXT}; } .tc-mp-navbar { height: 48px; display: flex; align-items: center; padding: 0 8px 0 4px; border-bottom: 1px solid rgba(0,0,0,.05); position: relative; background: ${COLOR_BG}; } .tc-mp-back { width: 40px; height: 40px; border: none; background: transparent; color: ${COLOR_TEXT}; border-radius: 8px; cursor: pointer; display: flex; align-items: center; justify-content: center; flex-shrink: 0; } .tc-mp-back:active { background: rgba(0,0,0,.05); } .tc-mp-navbar-title { position: absolute; left: 50%; transform: translateX(-50%); font-size: 17px; font-weight: 700; color: ${COLOR_TEXT}; max-width: 56%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .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, ${COLOR_ACCENT} 0%, ${COLOR_ACCENT_DEEP} 100%); color: #fff; box-shadow: 0 10px 28px rgba(249, 115, 22, 0.35); } .tc-hero-label { font-size: 13px; opacity: 0.92; margin-bottom: 6px; } .tc-hero-amount { font-size: 36px; font-weight: 800; line-height: 1.1; font-variant-numeric: tabular-nums; letter-spacing: -0.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: 0.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-detail-btn:active { background: rgba(255,255,255,.24); } .tc-hero-meta { display: flex; gap: 16px; margin-top: 10px; font-size: 12px; opacity: 0.88; } .tc-section { margin: 12px 14px 0; background: ${COLOR_BG}; border-radius: 14px; overflow: hidden; box-shadow: 0 2px 8px rgba(15, 23, 42, 0.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: ${COLOR_ACCENT}; background: ${COLOR_ACCENT_SOFT}; 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}; font-variant-numeric: tabular-nums; } .tc-vehicle-model { font-size: 12px; color: ${COLOR_MUTED}; margin-top: 2px; } .tc-vehicle-idx { font-size: 11px; font-weight: 700; color: ${COLOR_ACCENT}; background: ${COLOR_ACCENT_SOFT}; 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: ${COLOR_ACCENT}; 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: ${COLOR_PRIMARY_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-row:last-child { border-bottom: none; } .tc-service-name { color: ${COLOR_TEXT}; font-weight: 500; } .tc-service-amt { color: ${COLOR_ACCENT}; 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:last-child { padding-bottom: 0; } .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: ${COLOR_ACCENT_SOFT}; color: ${COLOR_ACCENT}; border: 2px solid ${COLOR_ACCENT}; box-sizing: border-box; } .tc-step-body { flex: 1; min-width: 0; } .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; touch-action: manipulation; border: none; } .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, 0.3); } .tc-btn-reject { background: ${COLOR_PAGE}; color: ${COLOR_DANGER}; border: 1px solid rgba(245, 63, 63, 0.25); } .tc-btn-approve { background: linear-gradient(135deg, ${COLOR_PRIMARY} 0%, ${COLOR_PRIMARY_DEEP} 100%); color: #fff; box-shadow: 0 4px 14px rgba(0, 191, 165, 0.3); } .tc-btn:active { opacity: 0.92; transform: scale(0.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: ${COLOR_PRIMARY}; } .tc-notify-item.disabled { opacity: 0.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: ${COLOR_PRIMARY_SOFT}; color: ${COLOR_PRIMARY_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(22, 209, 161, 0.55); box-shadow: 0 0 0 3px rgba(22, 209, 161, 0.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: ${COLOR_PRIMARY}; background: ${COLOR_PRIMARY_SOFT}; color: ${COLOR_PRIMARY_DEEP}; font-weight: 600; } .tc-drawer-rows { display: flex; flex-direction: column; gap: 0; } .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:last-child { border-bottom: none; } .tc-drawer-row-label { color: ${COLOR_TEXT_SEC}; } .tc-drawer-row-val { font-weight: 700; color: ${COLOR_TEXT}; font-variant-numeric: tabular-nums; } .tc-drawer-row-val.highlight { color: ${COLOR_ACCENT}; font-size: 16px; } .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: ${COLOR_ACCENT}; font-size: 18px; font-variant-numeric: tabular-nums; } `; const IconBack = () => ( ); const InfoRow = ({ label, value, full }) => (