diff --git a/web端/业务管理/租赁账单.jsx b/web端/业务管理/租赁账单.jsx index 40aeb83..5192a9c 100644 --- a/web端/业务管理/租赁账单.jsx +++ b/web端/业务管理/租赁账单.jsx @@ -1,933 +1,362 @@ // 【重要】必须使用 const Component 作为组件变量名 -// 账单管理 - 车辆资产管理后台(按 antd 规范) +// 租赁账单(2026年3月10日版本) -const Component = function() { +const Component = function () { var useState = React.useState; - var useCallback = React.useCallback; var useMemo = React.useMemo; - var useEffect = React.useEffect; var antd = window.antd; var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; - var Select = antd.Select; - var Button = antd.Button; var Table = antd.Table; - var Tag = antd.Tag; - var Modal = antd.Modal; + var Button = antd.Button; + var Select = antd.Select; var Input = antd.Input; - var message = antd.message; var Space = antd.Space; - var Row = antd.Row; - var Col = antd.Col; + var Popover = antd.Popover; + var message = antd.message; - // 当前视图:list | detail - var viewState = useState('list'); - var currentView = viewState[0]; - var setCurrentView = viewState[1]; + var filterContractCode = useState(undefined); + var filterProjectName = useState(undefined); + var filterCustomerName = useState(undefined); + var filterBusinessDept = useState(undefined); + var filterBusinessPerson = useState(undefined); + var filterDeliveryTaskCode = useState(''); + var filterExpanded = useState(false); + var expandedRowKeysState = useState([]); + var deliveryPopoverOpen = useState(null); + var expandedRowKeys = expandedRowKeysState[0]; + var setExpandedRowKeys = expandedRowKeysState[1]; - // 分页 - var pageState = useState(1); - var currentPage = pageState[0]; - var setCurrentPage = pageState[1]; - var pageSizeState = useState(10); - var pageSize = pageSizeState[0]; - var setPageSize = pageSizeState[1]; - - // 选中项(Table rowSelection 用) - var selectedRowKeysState = useState([]); - var selectedRowKeys = selectedRowKeysState[0]; - var setSelectedRowKeys = selectedRowKeysState[1]; - - // 筛选器(表单值) - var contractFilterState = useState(undefined); - var contractFilter = contractFilterState[0]; - var setContractFilter = contractFilterState[1]; - var projectFilterState = useState(undefined); - var projectFilter = projectFilterState[0]; - var setProjectFilter = projectFilterState[1]; - var customerFilterState = useState(undefined); - var customerFilter = customerFilterState[0]; - var setCustomerFilter = customerFilterState[1]; - var statusFilterState = useState([]); - var statusFilter = statusFilterState[0]; - var setStatusFilter = statusFilterState[1]; - - // 已应用的筛选条件(点击查询后生效) - var appliedFilterState = useState({ contract: '', project: '', customer: '', status: [] }); - var appliedFilter = appliedFilterState[0]; - var setAppliedFilter = appliedFilterState[1]; - - // 弹窗:应付金额明细 | 服务费明细 - var popoverState = useState({ type: null, data: null }); - var popover = popoverState[0]; - var setPopover = popoverState[1]; - - // 照片查看器 - var photoViewerState = useState({ visible: false, photos: [], currentIndex: 0 }); - var photoViewer = photoViewerState[0]; - var setPhotoViewer = photoViewerState[1]; - - var viewingBillIdState = useState(null); - var viewingBillId = viewingBillIdState[0]; - var setViewingBillId = viewingBillIdState[1]; - - // 详情模式:view | payment - var detailModeState = useState('view'); - var detailMode = detailModeState[0]; - var setDetailMode = detailModeState[1]; - - // 付款表单数据 - var paymentFormState = useState({ - discountAmount: '', - discountReason: '', - vehicleList: [], - hydrogenList: [], - violationList: [], - returnFeeList: [] - }); - var paymentForm = paymentFormState[0]; - var setPaymentForm = paymentFormState[1]; - - // 列表内成本编辑 - var editingListCellState = useState(null); - var editingListCell = editingListCellState[0]; - var setEditingListCell = editingListCellState[1]; - var editingInputValueState = useState(''); - var editingInputValue = editingInputValueState[0]; - var setEditingInputValue = editingInputValueState[1]; - var listEditableCostsState = useState({}); - var listEditableCosts = listEditableCostsState[0]; - var setListEditableCosts = listEditableCostsState[1]; - - // 模拟选项与数据(与原文一致) - var contractOptions = useMemo(function() { - return [ - { value: 'HT001', label: 'HT001 - 租赁合同A' }, - { value: 'HT002', label: 'HT002 - 租赁合同B' }, - { value: 'HT003', label: 'HT003 - 租赁合同C' } - ]; - }, []); - var projectOptions = useMemo(function() { - return [ - { value: 'XM001', label: 'XM001 - 北京项目' }, - { value: 'XM002', label: 'XM002 - 上海项目' }, - { value: 'XM003', label: 'XM003 - 广州项目' } - ]; - }, []); - var customerOptions = useMemo(function() { - return [ - { value: 'KH001', label: 'KH001 - 科技公司A' }, - { value: 'KH002', label: 'KH002 - 制造企业B' }, - { value: 'KH003', label: 'KH003 - 物流公司C' } - ]; - }, []); - var statusOptions = [ - { value: 'paid', label: '已付款' }, - { value: 'partial', label: '部分付款' }, - { value: 'unpaid', label: '未付款' } + // 主表数据:按合同聚合,每条主表下有多条账单(子表) + var mainListData = [ + { + contractCode: 'HT-ZL-2025-001', + deliveryTaskCode: 'JT-2025-001-A', + contractType: '正式合同', + projectName: '嘉兴氢能示范项目', + customerName: '嘉兴某某物流有限公司', + contractEffectiveDate: '2025-01-15', + businessDept: '业务1部', + businessPerson: '张经理', + children: [ + { period: 1, billStartDate: '2025-01-01', billEndDate: '2025-01-31', deliveryCount: 3, deliveryVehicles: [{ brand: '东风', model: 'DFH1180', plateNo: '浙A12345' }, { brand: '福田', model: 'BJ1180', plateNo: '浙A23456' }, { brand: '重汽', model: 'ZZ1187', plateNo: '浙A34567' }], receivableTotal: 45800.00, actualTotal: 45500.00, discountTotal: 300.00, arrivalAmount: 45500.00, isInvoiced: '已开票', invoiceAmount: 45500.00 }, + { period: 2, billStartDate: '2025-02-01', billEndDate: '2025-02-28', deliveryCount: 2, deliveryVehicles: [{ brand: '陕汽', model: 'SX1313', plateNo: '浙A45678' }, { brand: '解放', model: 'J6P', plateNo: '浙A56789' }], receivableTotal: 45800.00, actualTotal: 45800.00, discountTotal: 0.00, arrivalAmount: 45800.00, isInvoiced: '已开票', invoiceAmount: 45800.00 }, + { period: 3, billStartDate: '2025-03-01', billEndDate: '2025-03-31', deliveryCount: 2, deliveryVehicles: [{ brand: '江淮', model: '格尔发K5', plateNo: '浙A67890' }, { brand: '东风', model: 'DFH1250', plateNo: '浙A11111' }], receivableTotal: 45800.00, actualTotal: 45000.00, discountTotal: 800.00, arrivalAmount: 42000.00, isInvoiced: '部分开票', invoiceAmount: 42000.00 } + ] + }, + { + contractCode: 'HT-ZL-2025-002', + deliveryTaskCode: 'JT-2025-002-B', + contractType: '试用合同', + projectName: '上海物流租赁项目', + customerName: '上海某某运输公司', + contractEffectiveDate: '2025-02-01', + businessDept: '业务2部', + businessPerson: '李专员', + children: [ + { period: 1, billStartDate: '2025-02-01', billEndDate: '2025-02-28', deliveryCount: 2, deliveryVehicles: [{ brand: '江淮', model: '格尔发K5', plateNo: '沪B11111' }, { brand: '东风', model: 'DFH1250', plateNo: '沪B22222' }], receivableTotal: 33400.00, actualTotal: 33400.00, discountTotal: 0.00, arrivalAmount: 33400.00, isInvoiced: '已开票', invoiceAmount: 33400.00 }, + { period: 2, billStartDate: '2025-03-01', billEndDate: '2025-03-31', deliveryCount: 1, deliveryVehicles: [{ brand: '解放', model: 'JH6', plateNo: '沪B33333' }], receivableTotal: 33400.00, actualTotal: 33400.00, discountTotal: 0.00, arrivalAmount: 0.00, isInvoiced: '未开票', invoiceAmount: 0.00 } + ] + }, + { + contractCode: 'HT-ZL-2025-003', + deliveryTaskCode: 'JT-2025-003-C', + contractType: '正式合同', + projectName: '杭州城配租赁项目', + customerName: '杭州某某租赁有限公司', + contractEffectiveDate: '2025-02-10', + businessDept: '业务3部', + businessPerson: '王专员', + children: [ + { period: 1, billStartDate: '2025-02-10', billEndDate: '2025-03-09', deliveryCount: 1, deliveryVehicles: [{ brand: '福田', model: '欧曼EST', plateNo: '浙C33333' }], receivableTotal: 41200.00, actualTotal: 41200.00, discountTotal: 0.00, arrivalAmount: 41200.00, isInvoiced: '已开票', invoiceAmount: 41200.00 } + ] + } ]; - var mockBillList = useMemo(function() { - var list = []; - for (var i = 1; i <= 25; i++) { - var statuses = ['paid', 'partial', 'unpaid']; - var status = statuses[i % 3]; - list.push({ - id: 'BILL' + i, - contractCode: 'HT00' + (i % 3 + 1), - projectName: 'XM00' + (i % 3 + 1) + ' - 项目' + i, - customerName: 'KH00' + (i % 3 + 1) + ' - 客户' + i, - period: i, - payableAmount: (15000 + i * 500).toFixed(2), - paidAmount: status === 'paid' ? (15000 + i * 500).toFixed(2) : status === 'partial' ? (8000).toFixed(2) : '0.00', - discountAmount: (i % 5 === 0 ? 200 : 0).toFixed(2), - remark: status !== 'unpaid' ? '已付款备注' + i : '-', - vehicleCost: (5000 + i * 100).toFixed(2), - hydrogenCost: (300 + i * 20).toFixed(2), - otherCost: (200 + i * 10).toFixed(2), - status: status - }); - } + var mainList = mainListData; + var filterOptions = useMemo(function () { + var codes = [], projects = [], customers = [], depts = [], persons = []; + mainList.forEach(function (r) { + if (r.contractCode && codes.indexOf(r.contractCode) === -1) codes.push(r.contractCode); + if (r.projectName && projects.indexOf(r.projectName) === -1) projects.push(r.projectName); + if (r.customerName && customers.indexOf(r.customerName) === -1) customers.push(r.customerName); + if (r.businessDept && depts.indexOf(r.businessDept) === -1) depts.push(r.businessDept); + if (r.businessPerson && persons.indexOf(r.businessPerson) === -1) persons.push(r.businessPerson); + }); + return { + contractCode: codes.map(function (v) { return { value: v, label: v }; }), + projectName: projects.map(function (v) { return { value: v, label: v }; }), + customerName: customers.map(function (v) { return { value: v, label: v }; }), + businessDept: depts.map(function (v) { return { value: v, label: v }; }), + businessPerson: persons.map(function (v) { return { value: v, label: v }; }) + }; + }, [mainList]); + + var filteredMainList = useMemo(function () { + var list = mainList; + var code = filterContractCode[0]; + var project = filterProjectName[0]; + var customer = filterCustomerName[0]; + var dept = filterBusinessDept[0]; + var person = filterBusinessPerson[0]; + var taskCode = (filterDeliveryTaskCode[0] || '').trim().toLowerCase(); + if (code) list = list.filter(function (r) { return r.contractCode === code; }); + if (project) list = list.filter(function (r) { return r.projectName === project; }); + if (customer) list = list.filter(function (r) { return r.customerName === customer; }); + if (dept) list = list.filter(function (r) { return r.businessDept === dept; }); + if (person) list = list.filter(function (r) { return r.businessPerson === person; }); + if (taskCode) list = list.filter(function (r) { return (r.deliveryTaskCode || '').toLowerCase().indexOf(taskCode) !== -1; }); return list; - }, []); + }, [mainList, filterContractCode[0], filterProjectName[0], filterCustomerName[0], filterBusinessDept[0], filterBusinessPerson[0], filterDeliveryTaskCode[0]]); - var mockPayableDetail = useMemo(function() { - return [ - { startDate: '2025-01-01', endDate: '2025-01-31', plateNo: '京A12345', rent: '8000.00', serviceFee: '500.00', deposit: '2000.00' }, - { startDate: '2025-01-01', endDate: '2025-01-31', plateNo: '京B67890', rent: '7000.00', serviceFee: '400.00', deposit: '1500.00' } - ]; - }, []); - - var mockBillDetail = useMemo(function() { - return { - startDate: '2025-01-01', - endDate: '2025-01-31', - contractCode: 'HT001', - projectName: 'XM001 - 北京项目', - customerName: 'KH001 - 科技公司A', - department: '运营部', - responsible: '张三', - paymentCycle: '先付(付款周期:1个月)', - discountTotal: '200.00', - discountReason: '长期合作客户优惠,首期账单减免部分金额' - }; - }, []); - - var mockVehicleList = useMemo(function() { - return [ - { brand: '奔驰', model: 'E300L', plateNo: '京A12345', planDelivery: '2024-12-25', actualDelivery: '2024-12-28', billStart: '2025-01-01', billEnd: '2025-01-31', monthlyRent: '8000.00', paidMonthlyRent: '7600.00', serviceFee: '500.00', paidServiceFee: '480.00', deposit: '2000.00', paidDeposit: '2000.00', serviceItems: [{ name: '保养服务', price: '300.00', effectiveDate: '2025-01-01' }, { name: '保险', price: '200.00', effectiveDate: '2025-01-01' }] }, - { brand: '宝马', model: '530Li', plateNo: '京B67890', planDelivery: '2024-12-20', actualDelivery: '2024-12-22', billStart: '2025-01-01', billEnd: '2025-01-31', monthlyRent: '7000.00', paidMonthlyRent: '7000.00', serviceFee: '400.00', paidServiceFee: '380.00', deposit: '1500.00', paidDeposit: '1500.00', serviceItems: [{ name: '保养服务', price: '250.00', effectiveDate: '2025-01-01' }] } - ]; - }, []); - - var mockReturnFeeData = useMemo(function() { - return { - totalAmount: '2850.00', - paidTotalAmount: '2700.00', - list: [ - { feeName: '车辆外观损伤费', amount: '800.00', paidAmount: '760.00', photos: ['https://picsum.photos/80/80?random=1', 'https://picsum.photos/80/80?random=2'], attachments: [{ name: '外观损伤说明.pdf' }] }, - { feeName: '轮胎磨损费', amount: '1200.00', paidAmount: '1150.00', photos: ['https://picsum.photos/80/80?random=3'], attachments: [{ name: '轮胎检测报告.pdf' }, { name: '维修单据.pdf' }] }, - { feeName: '内饰清洁费', amount: '450.00', paidAmount: '430.00', photos: ['https://picsum.photos/80/80?random=4', 'https://picsum.photos/80/80?random=5', 'https://picsum.photos/80/80?random=6'], attachments: [] }, - { feeName: '油量补充费', amount: '400.00', paidAmount: '360.00', photos: [], attachments: [{ name: '加油凭证.jpg' }] } - ] - }; - }, []); - - var mockViolationData = useMemo(function() { - return { - violationCount: 3, - totalAmount: '650.00', - paidTotalAmount: '600.00', - list: [ - { violationTime: '2025-01-10 08:30:00', plateNo: '京A12345', violationType: '违停', location: '北京市朝阳区xxx路', fineAmount: '200.00', paidFineAmount: '200.00' }, - { violationTime: '2025-01-18 14:20:00', plateNo: '京B67890', violationType: '超速', location: '北京市海淀区xxx大道', fineAmount: '200.00', paidFineAmount: '180.00' }, - { violationTime: '2025-01-25 09:15:00', plateNo: '京A12345', violationType: '闯红灯', location: '北京市东城区xxx路口', fineAmount: '250.00', paidFineAmount: '220.00' } - ] - }; - }, []); - - var mockHydrogenData = useMemo(function() { - return { - refuelCount: 12, - balance: '3580.00', - list: [ - { refuelTime: '2025-01-15 09:30:00', stationName: '北京朝阳加氢站', plateNo: '京A12345', amount: '15.5', costPrice: '28.00', feePrice: '434.00', paidFeePrice: '420.00' }, - { refuelTime: '2025-01-18 14:20:00', stationName: '北京海淀加氢站', plateNo: '京B67890', amount: '12.0', costPrice: '28.00', feePrice: '336.00', paidFeePrice: '336.00' }, - { refuelTime: '2025-01-22 11:00:00', stationName: '北京朝阳加氢站', plateNo: '京A12345', amount: '18.2', costPrice: '28.00', feePrice: '509.60', paidFeePrice: '490.00' } - ] - }; - }, []); - - var vehicleBillTotals = useMemo(function() { - var list = mockVehicleList || []; - var monthlyRentTotal = 0, paidRentTotal = 0, serviceFeeTotal = 0, paidServiceFeeTotal = 0, depositTotal = 0, paidDepositTotal = 0; - list.forEach(function(v) { - monthlyRentTotal += parseFloat(v.monthlyRent || 0); - paidRentTotal += parseFloat(v.paidMonthlyRent || 0); - serviceFeeTotal += parseFloat(v.serviceFee || 0); - paidServiceFeeTotal += parseFloat(v.paidServiceFee || 0); - depositTotal += parseFloat(v.deposit || 0); - paidDepositTotal += parseFloat(v.paidDeposit || 0); + var mainTableDataSource = useMemo(function () { + return filteredMainList.map(function (r) { + var o = {}; + for (var k in r) if (k !== 'children') o[k] = r[k]; + o._detailList = r.children || []; + return o; }); - return { - monthlyRentTotal: monthlyRentTotal.toFixed(2), - paidRentTotal: paidRentTotal.toFixed(2), - serviceFeeTotal: serviceFeeTotal.toFixed(2), - paidServiceFeeTotal: paidServiceFeeTotal.toFixed(2), - depositTotal: depositTotal.toFixed(2), - paidDepositTotal: paidDepositTotal.toFixed(2) - }; - }, [mockVehicleList]); + }, [filteredMainList]); - var billInfoTotals = useMemo(function() { - var monthlyRentTotal = parseFloat(vehicleBillTotals.monthlyRentTotal || 0); - var serviceFeeTotal = parseFloat(vehicleBillTotals.serviceFeeTotal || 0); - var depositTotal = parseFloat(vehicleBillTotals.depositTotal || 0); - var hydrogenTotal = 0; - (mockHydrogenData.list || []).forEach(function(item) { hydrogenTotal += parseFloat(item.feePrice || 0); }); - var violationTotal = parseFloat(mockViolationData.totalAmount || 0); - var returnFeeTotal = parseFloat(mockReturnFeeData.totalAmount || 0); - var payableTotal = monthlyRentTotal + serviceFeeTotal + depositTotal + hydrogenTotal + violationTotal + returnFeeTotal; - var paidTotal = 0; - (mockVehicleList || []).forEach(function(v) { - paidTotal += parseFloat(v.paidMonthlyRent || 0) + parseFloat(v.paidServiceFee || 0) + parseFloat(v.paidDeposit || 0); - }); - var discountTotal = parseFloat(mockBillDetail.discountTotal || 0); - var unpaidTotal = Math.max(0, payableTotal - paidTotal - discountTotal); - return { - payableTotal: payableTotal.toFixed(2), - paidTotal: paidTotal.toFixed(2), - unpaidTotal: unpaidTotal.toFixed(2) - }; - }, [vehicleBillTotals, mockHydrogenData, mockViolationData, mockReturnFeeData, mockVehicleList, mockBillDetail]); - - var vehicleTotals = billInfoTotals; - - var filteredList = useMemo(function() { - var list = (mockBillList || []).slice(); - var c = (appliedFilter.contract || '').trim(); - var p = (appliedFilter.project || '').trim(); - var cust = (appliedFilter.customer || '').trim(); - var st = Array.isArray(appliedFilter.status) ? appliedFilter.status : []; - if (c) list = list.filter(function(item) { return String(item.contractCode || '').toLowerCase().indexOf(c.toLowerCase()) >= 0; }); - if (p) list = list.filter(function(item) { return String(item.projectName || '').toLowerCase().indexOf(p.toLowerCase()) >= 0; }); - if (cust) list = list.filter(function(item) { return String(item.customerName || '').toLowerCase().indexOf(cust.toLowerCase()) >= 0; }); - if (st.length > 0) { - var statusSet = {}; - st.forEach(function(s) { statusSet[s] = true; }); - list = list.filter(function(item) { return statusSet[item.status]; }); - } - return list; - }, [mockBillList, appliedFilter.contract, appliedFilter.project, appliedFilter.customer, appliedFilter.status]); - - var totalCount = filteredList.length; - var paginatedList = useMemo(function() { - var start = (currentPage - 1) * pageSize; - return filteredList.slice(start, start + pageSize); - }, [filteredList, currentPage, pageSize]); - - var handleSearch = useCallback(function() { - setAppliedFilter({ - contract: (contractFilter != null && contractFilter !== '') ? String(contractFilter) : '', - project: (projectFilter != null && projectFilter !== '') ? String(projectFilter) : '', - customer: (customerFilter != null && customerFilter !== '') ? String(customerFilter) : '', - status: Array.isArray(statusFilter) ? statusFilter.slice() : [] - }); - setCurrentPage(1); - }, [contractFilter, projectFilter, customerFilter, statusFilter]); - - var handleReset = useCallback(function() { - setContractFilter(undefined); - setProjectFilter(undefined); - setCustomerFilter(undefined); - setStatusFilter([]); - setAppliedFilter({ contract: '', project: '', customer: '', status: [] }); - setCurrentPage(1); - }, []); - - var handleExport = useCallback(function() { - if (selectedRowKeys.length === 0) { - message.warning('请先选择要导出的数据'); - return; - } - message.success('导出 ' + selectedRowKeys.length + ' 条数据'); - }, [selectedRowKeys.length]); - - var handleView = useCallback(function(id, isPayment) { - setViewingBillId(id); - setDetailMode(isPayment ? 'payment' : 'view'); - if (isPayment) { - var vl = mockVehicleList.map(function(v) { - return { paidMonthlyRent: v.paidMonthlyRent || '', paidServiceFee: v.paidServiceFee || '', paidDeposit: v.paidDeposit || '' }; - }); - var hl = mockHydrogenData.list.map(function(item) { return { paidFeePrice: item.paidFeePrice || '' }; }); - var viol = mockViolationData.list.map(function(item) { return { paidFineAmount: item.paidFineAmount || '' }; }); - var rfl = mockReturnFeeData.list.map(function(item) { return { paidAmount: item.paidAmount || '' }; }); - setPaymentForm({ - discountAmount: mockBillDetail.discountTotal || '', - discountReason: mockBillDetail.discountReason || '', - vehicleList: vl, - hydrogenList: hl, - violationList: viol, - returnFeeList: rfl - }); - } - setCurrentView('detail'); - }, []); - - var handleBackToList = useCallback(function() { - setCurrentView('list'); - setViewingBillId(null); - setDetailMode('view'); - }, []); - - var handlePaymentFormChange = useCallback(function(section, index, field, value) { - setPaymentForm(function(prev) { - var next = { - discountAmount: prev.discountAmount, - discountReason: prev.discountReason, - vehicleList: prev.vehicleList.slice(), - hydrogenList: prev.hydrogenList.slice(), - violationList: prev.violationList.slice(), - returnFeeList: prev.returnFeeList.slice() - }; - if (section === 'bill') { - if (field === 'discountAmount') next.discountAmount = value; - if (field === 'discountReason') next.discountReason = value; - } else if (section === 'vehicle' && index >= 0 && index < next.vehicleList.length) { - next.vehicleList[index] = Object.assign({}, next.vehicleList[index], { [field]: value }); - } else if (section === 'hydrogen' && index >= 0 && index < next.hydrogenList.length) { - next.hydrogenList[index] = Object.assign({}, next.hydrogenList[index], { [field]: value }); - } else if (section === 'violation' && index >= 0 && index < next.violationList.length) { - next.violationList[index] = Object.assign({}, next.violationList[index], { [field]: value }); - } else if (section === 'returnFee' && index >= 0 && index < next.returnFeeList.length) { - next.returnFeeList[index] = Object.assign({}, next.returnFeeList[index], { [field]: value }); - } - return next; - }); - }, []); - - var handleSubmit = useCallback(function() { - var errors = []; - if (!paymentForm.discountAmount || String(paymentForm.discountAmount).trim() === '') errors.push('减免金额'); - if (!paymentForm.discountReason || String(paymentForm.discountReason).trim() === '') errors.push('减免原因'); - paymentForm.vehicleList.forEach(function(v, i) { - if (!v.paidMonthlyRent || String(v.paidMonthlyRent).trim() === '') errors.push('车辆账单-实付月租金(第' + (i + 1) + '行)'); - if (!v.paidServiceFee || String(v.paidServiceFee).trim() === '') errors.push('车辆账单-实付服务费(第' + (i + 1) + '行)'); - if (!v.paidDeposit || String(v.paidDeposit).trim() === '') errors.push('车辆账单-实付保证金(第' + (i + 1) + '行)'); - }); - paymentForm.hydrogenList.forEach(function(h, i) { - if (!h.paidFeePrice || String(h.paidFeePrice).trim() === '') errors.push('氢费账单-实付氢费金额(第' + (i + 1) + '行)'); - }); - paymentForm.violationList.forEach(function(v, i) { - if (!v.paidFineAmount || String(v.paidFineAmount).trim() === '') errors.push('违章费用-实付罚款金额(第' + (i + 1) + '行)'); - }); - paymentForm.returnFeeList.forEach(function(r, i) { - if (!r.paidAmount || String(r.paidAmount).trim() === '') errors.push('还车费用-实付金额(第' + (i + 1) + '行)'); - }); - if (errors.length > 0) { - message.warning('请填写必填项:' + errors.join('、')); - return; - } - message.success('提交成功'); - handleBackToList(); - }, [paymentForm, handleBackToList]); - - var handleSave = useCallback(function() { - message.success('保存成功'); - }, []); - - function getStatusText(status) { - if (status === 'paid') return '已付款'; - if (status === 'partial') return '部分付款'; - return '未付款'; - } - function getStatusTagColor(status) { - if (status === 'paid') return 'success'; - if (status === 'partial') return 'warning'; - return 'error'; + function fmtMoney(v) { + if (v === null || v === undefined) return '0.00元'; + var n = typeof v === 'number' ? v : parseFloat(v); + return (isNaN(n) ? '0.00' : n.toFixed(2)) + '元'; } - // 列表表格列(含行内编辑氢费/其他成本) - function renderEditableCost(row, field, label) { - var rowId = row.id; - var val = (listEditableCosts[rowId] && listEditableCosts[rowId][field] !== undefined) - ? listEditableCosts[rowId][field] - : (row[field] != null ? row[field] : '0.00'); - var num = (parseFloat(val) || 0).toFixed(2); - var isEditing = editingListCell && editingListCell.rowId === rowId && editingListCell.field === field; - if (isEditing) { - return React.createElement(Space, { key: 'edit', size: 4 }, - React.createElement(Input, { - value: editingInputValue, - onChange: function(e) { setEditingInputValue(e.target.value); }, - onBlur: function() { - var n = parseFloat(editingInputValue); - var formatted = (isNaN(n) ? 0 : n).toFixed(2); - setListEditableCosts(function(prev) { - var next = Object.assign({}, prev); - next[rowId] = Object.assign({}, next[rowId], { [field]: formatted }); - return next; - }); - setEditingListCell(null); - setEditingInputValue(''); - }, - style: { width: 80 }, - autoFocus: true - }), - React.createElement('span', null, '元') - ); - } - return React.createElement('span', { - style: { cursor: 'pointer' }, - onClick: function() { - setEditingInputValue(num); - setEditingListCell({ rowId: rowId, field: field }); - } - }, num); + function goView(record, parent) { + message.info('查看账单详情(原型)'); + } + function goChargeDetail(record, parent) { + message.info('收费明细(原型)'); } - var listColumns = useMemo(function() { - return [ - { title: '付款状态', dataIndex: 'status', key: 'status', width: 100, render: function(s) { return React.createElement(Tag, { color: getStatusTagColor(s) }, getStatusText(s)); } }, - { title: '合同编码', dataIndex: 'contractCode', key: 'contractCode', width: 120 }, - { title: '项目名称', dataIndex: 'projectName', key: 'projectName', width: 160 }, - { title: '客户名称', dataIndex: 'customerName', key: 'customerName', width: 160 }, - { title: '当前期数', dataIndex: 'period', key: 'period', width: 90 }, - { title: '应付金额', dataIndex: 'payableAmount', key: 'payableAmount', width: 110, render: function(_, row) { - return React.createElement('a', { - onClick: function() { setPopover({ type: 'payable', data: mockPayableDetail }); }, - style: { color: '#1677ff', fontWeight: 600, textDecoration: 'underline', cursor: 'pointer' } - }, row.payableAmount); - }}, - { title: '实付金额', dataIndex: 'paidAmount', key: 'paidAmount', width: 110 }, - { title: '减免金额', dataIndex: 'discountAmount', key: 'discountAmount', width: 100 }, - { title: '未付金额', key: 'unpaid', width: 100, render: function(_, row) { - return (parseFloat(row.payableAmount || 0) - parseFloat(row.paidAmount || 0) - parseFloat(row.discountAmount || 0)).toFixed(2); - }}, - { title: '备注', dataIndex: 'remark', key: 'remark', width: 120 }, - { title: '车辆成本(元)', dataIndex: 'vehicleCost', key: 'vehicleCost', width: 120, render: function(val) { return (parseFloat(val) || 0).toFixed(2); } }, - { title: '氢费成本(元)', key: 'hydrogenCost', width: 120, render: function(_, row) { return renderEditableCost(row, 'hydrogenCost', '氢费'); } }, - { title: '其他成本(元)', key: 'otherCost', width: 120, render: function(_, row) { return renderEditableCost(row, 'otherCost', '其他'); } }, - { title: '操作', key: 'action', width: 140, fixed: 'right', render: function(_, row) { - return React.createElement(Space, null, - React.createElement(Button, { type: 'link', size: 'small', onClick: function() { handleView(row.id, false); } }, '查看'), - React.createElement(Button, { type: 'link', size: 'small', style: { color: '#52c41a' }, onClick: function() { handleView(row.id, true); } }, '收费') - ); - }} - ]; - }, [listEditableCosts, editingListCell, editingInputValue, handleView]); - - var rowSelection = useMemo(function() { - return { - selectedRowKeys: selectedRowKeys, - onChange: function(keys) { setSelectedRowKeys(keys || []); } - }; - }, [selectedRowKeys]); - - var tablePagination = useMemo(function() { - return { - current: currentPage, - pageSize: pageSize, - total: totalCount, - showSizeChanger: true, - showTotal: function(t) { return '共 ' + t + ' 条'; }, - pageSizeOptions: ['10', '20', '50'], - onChange: function(page, size) { - setCurrentPage(page); - if (size !== pageSize) setPageSize(size); - } - }; - }, [currentPage, pageSize, totalCount]); - - // —————— 详情视图 —————— - if (currentView === 'detail') { - var detailBreadcrumbItems = [ - { title: '运维管理' }, - { title: '业务管理' }, - { title: '租赁账单' }, - { title: detailMode === 'payment' ? '收费' : '查看' } - ]; - - var billInfoCols = [ - { label: '账单开始日期', value: mockBillDetail.startDate }, - { label: '账单结束日期', value: mockBillDetail.endDate }, - { label: '合同编码', value: mockBillDetail.contractCode }, - { label: '项目名称', value: mockBillDetail.projectName }, - { label: '客户名称', value: mockBillDetail.customerName }, - { label: '业务部门', value: mockBillDetail.department }, - { label: '业务负责人', value: mockBillDetail.responsible }, - { label: '付款周期', value: mockBillDetail.paymentCycle } - ]; - - return React.createElement('div', { style: { padding: 24, background: '#f5f5f5', minHeight: '100vh' } }, - React.createElement(Breadcrumb, { items: detailBreadcrumbItems, style: { marginBottom: 16 } }), - React.createElement(Card, { style: { marginBottom: 16 } }, - React.createElement('div', { style: { fontSize: 16, fontWeight: 600, marginBottom: 16, paddingBottom: 12, borderBottom: '1px solid #f0f0f0' } }, '账单信息'), - React.createElement(Row, { gutter: [24, 24] }, - billInfoCols.map(function(item, i) { - return React.createElement(Col, { key: i, span: 8 }, - React.createElement('div', { style: { marginBottom: 8 } }, - React.createElement('span', { style: { color: '#666', marginRight: 8 } }, item.label + ':'), - React.createElement('span', null, item.value) - ) - ); - }), - React.createElement(Col, { span: 8 }, - React.createElement('div', { style: { marginBottom: 8 } }, - React.createElement('span', { style: { color: '#666', marginRight: 8 } }, '应付款总额:'), - React.createElement('span', { style: { color: '#1890ff', fontWeight: 600 } }, vehicleTotals.payableTotal + ' 元') - ) - ), - React.createElement(Col, { span: 8 }, - React.createElement('div', { style: { marginBottom: 8 } }, - React.createElement('span', { style: { color: '#666', marginRight: 8 } }, '实付款总额:'), - React.createElement('span', { style: { color: '#52c41a', fontWeight: 600 } }, vehicleTotals.paidTotal + ' 元') - ) - ), - React.createElement(Col, { span: 8 }, - React.createElement('div', { style: { marginBottom: 8 } }, - React.createElement('span', { style: { color: '#666', marginRight: 8 } }, '减免金额:'), - detailMode === 'payment' - ? React.createElement(Input, { - value: paymentForm.discountAmount, - onChange: function(e) { handlePaymentFormChange('bill', -1, 'discountAmount', e.target.value); }, - style: { width: 120 }, - placeholder: '0.00', - addonAfter: '元' - }) - : React.createElement('span', { style: { fontWeight: 600 } }, mockBillDetail.discountTotal + ' 元') - ) - ), - React.createElement(Col, { span: 8 }, - React.createElement('div', { style: { marginBottom: 8 } }, - React.createElement('span', { style: { color: '#666', marginRight: 8 } }, '未付款总额:'), - React.createElement('span', { style: { color: '#ff4d4f', fontWeight: 600 } }, vehicleTotals.unpaidTotal + ' 元') - ) - ), - React.createElement(Col, { span: 24 }, - React.createElement('div', { style: { marginBottom: 8 } }, - React.createElement('span', { style: { color: '#666', marginRight: 8 } }, '减免原因:'), - detailMode === 'payment' - ? React.createElement(Input.TextArea, { - value: paymentForm.discountReason, - onChange: function(e) { handlePaymentFormChange('bill', -1, 'discountReason', e.target.value); }, - placeholder: '请输入减免原因', - rows: 3, - style: { maxWidth: 400 } - }) - : React.createElement('span', null, mockBillDetail.discountReason || '-') - ) + // 子表提车数量气泡:列表显示 品牌、型号、车牌号 + function renderDeliveryPopover(record) { + var vehicles = record.deliveryVehicles || []; + var parentCode = (record._parentRecord && record._parentRecord.contractCode) || ''; + var popoverKey = parentCode + '-' + (record.period != null ? record.period : ''); + var listStyle = { width: '100%', borderCollapse: 'collapse', fontSize: 13 }; + var thStyle = { padding: '6px 10px', textAlign: 'left', borderBottom: '1px solid #f0f0f0', backgroundColor: '#fafafa', fontWeight: 600 }; + var tdStyle = { padding: '6px 10px', borderBottom: '1px solid #f0f0f0' }; + var content = vehicles.length === 0 ? React.createElement('div', { style: { padding: 8 } }, '—') : React.createElement('div', { style: { padding: 0, minWidth: 200 } }, + React.createElement('table', { style: listStyle }, + React.createElement('thead', null, + React.createElement('tr', null, + React.createElement('th', { style: thStyle }, '品牌'), + React.createElement('th', { style: thStyle }, '型号'), + React.createElement('th', { style: thStyle }, '车牌号') ) - ) - ), - // 车辆账单 - React.createElement(Card, { title: '车辆账单', style: { marginBottom: 16 } }, - React.createElement(Table, { - dataSource: mockVehicleList, - rowKey: 'plateNo', - pagination: false, - scroll: { x: 1200 }, - columns: [ - { title: '品牌', dataIndex: 'brand', width: 80 }, - { title: '型号', dataIndex: 'model', width: 90 }, - { title: '车牌号', dataIndex: 'plateNo', width: 100 }, - { title: '计划交车日期', dataIndex: 'planDelivery', width: 120 }, - { title: '实际交车日期', dataIndex: 'actualDelivery', width: 120 }, - { title: '账单开始日期', dataIndex: 'billStart', width: 120 }, - { title: '计费结束日期', dataIndex: 'billEnd', width: 120 }, - { title: '车辆月租金', dataIndex: 'monthlyRent', width: 110 }, - { title: '实付月租金', dataIndex: 'paidMonthlyRent', width: 110, render: function(_, record, idx) { - if (detailMode !== 'payment') return (parseFloat(record.paidMonthlyRent || 0)).toFixed(2); - var pf = paymentForm.vehicleList[idx] || {}; - return React.createElement(Input, { value: pf.paidMonthlyRent, onChange: function(e) { handlePaymentFormChange('vehicle', idx, 'paidMonthlyRent', e.target.value); }, style: { width: 100 }, addonAfter: '元' }); - }}, - { title: '服务费', dataIndex: 'serviceFee', width: 90, render: function(val, record) { - if (detailMode === 'payment') return val; - return React.createElement('a', { onClick: function() { setPopover({ type: 'service', data: record.serviceItems }); } }, val); - }}, - { title: '实付服务费', dataIndex: 'paidServiceFee', width: 110, render: function(_, record, idx) { - if (detailMode !== 'payment') return (parseFloat(record.paidServiceFee || 0)).toFixed(2); - var pf = paymentForm.vehicleList[idx] || {}; - return React.createElement(Input, { value: pf.paidServiceFee, onChange: function(e) { handlePaymentFormChange('vehicle', idx, 'paidServiceFee', e.target.value); }, style: { width: 100 }, addonAfter: '元' }); - }}, - { title: '保证金', dataIndex: 'deposit', width: 90 }, - { title: '实付保证金', dataIndex: 'paidDeposit', width: 110, render: function(_, record, idx) { - if (detailMode !== 'payment') return (parseFloat(record.paidDeposit || 0)).toFixed(2); - var pf = paymentForm.vehicleList[idx] || {}; - return React.createElement(Input, { value: pf.paidDeposit, onChange: function(e) { handlePaymentFormChange('vehicle', idx, 'paidDeposit', e.target.value); }, style: { width: 100 }, addonAfter: '元' }); - }} - ], - summary: function() { - return React.createElement(Table.Summary, null, - React.createElement(Table.Summary.Row, null, - React.createElement(Table.Summary.Cell, { index: 0, colSpan: 7 }, '总计'), - React.createElement(Table.Summary.Cell, { index: 7 }, vehicleBillTotals.monthlyRentTotal), - React.createElement(Table.Summary.Cell, { index: 8 }, vehicleBillTotals.paidRentTotal), - React.createElement(Table.Summary.Cell, { index: 9 }, vehicleBillTotals.serviceFeeTotal), - React.createElement(Table.Summary.Cell, { index: 10 }, vehicleBillTotals.paidServiceFeeTotal), - React.createElement(Table.Summary.Cell, { index: 11 }, vehicleBillTotals.depositTotal), - React.createElement(Table.Summary.Cell, { index: 12 }, vehicleBillTotals.paidDepositTotal) - ) - ); - } - }) - ), - // 氢费账单 - React.createElement(Card, { title: '氢费账单', style: { marginBottom: 16 } }, - React.createElement('div', { style: { marginBottom: 16 } }, - React.createElement('span', { style: { marginRight: 24 } }, '加氢次数:', React.createElement('span', { style: { fontWeight: 600 } }, mockHydrogenData.refuelCount + ' 次')), - React.createElement('span', null, '当前氢费余额:', React.createElement('span', { style: { fontWeight: 600, color: '#1890ff' } }, mockHydrogenData.balance + ' 元')) ), - React.createElement(Table, { - dataSource: mockHydrogenData.list, - rowKey: function(item, i) { return i; }, - pagination: false, - columns: [ - { title: '加氢时间', dataIndex: 'refuelTime', width: 180 }, - { title: '加氢站名称', dataIndex: 'stationName', width: 140 }, - { title: '车牌号', dataIndex: 'plateNo', width: 100 }, - { title: '加氢量', dataIndex: 'amount', width: 90 }, - { title: '成本单价(元/KG)', dataIndex: 'costPrice', width: 120 }, - { title: '氢费价格(元)', dataIndex: 'feePrice', width: 120 }, - { title: '实付氢费金额(元)', dataIndex: 'paidFeePrice', width: 140, render: function(_, record, i) { - if (detailMode !== 'payment') return (parseFloat(record.paidFeePrice || 0)).toFixed(2); - var pf = paymentForm.hydrogenList[i] || {}; - return React.createElement(Input, { value: pf.paidFeePrice, onChange: function(e) { handlePaymentFormChange('hydrogen', i, 'paidFeePrice', e.target.value); }, style: { width: 100 }, addonAfter: '元' }); - }} - ], - summary: function() { - var totalFee = mockHydrogenData.list.reduce(function(s, item) { return s + parseFloat(item.feePrice || 0); }, 0).toFixed(2); - var totalPaid = mockHydrogenData.list.reduce(function(s, item) { return s + parseFloat(item.paidFeePrice || 0); }, 0).toFixed(2); - return React.createElement(Table.Summary, null, - React.createElement(Table.Summary.Row, null, - React.createElement(Table.Summary.Cell, { index: 0, colSpan: 5 }, '总计'), - React.createElement(Table.Summary.Cell, { index: 5 }, totalFee), - React.createElement(Table.Summary.Cell, { index: 6 }, totalPaid) - ) + React.createElement('tbody', null, + vehicles.map(function (v, i) { + return React.createElement('tr', { key: i }, + React.createElement('td', { style: tdStyle }, v.brand || '—'), + React.createElement('td', { style: tdStyle }, v.model || '—'), + React.createElement('td', { style: tdStyle }, v.plateNo || '—') ); - } - }) - ), - // 违章费用 - React.createElement(Card, { title: '违章费用', style: { marginBottom: 16 } }, - React.createElement('div', { style: { marginBottom: 16 } }, - React.createElement('span', null, '违章次数:', React.createElement('span', { style: { fontWeight: 600 } }, mockViolationData.violationCount + ' 次')) - ), - React.createElement(Table, { - dataSource: mockViolationData.list, - rowKey: function(item, i) { return i; }, - pagination: false, - columns: [ - { title: '违章时间', dataIndex: 'violationTime', width: 180 }, - { title: '车牌号', dataIndex: 'plateNo', width: 100 }, - { title: '违章类型', dataIndex: 'violationType', width: 100 }, - { title: '违章地点', dataIndex: 'location', width: 200 }, - { title: '罚款金额', dataIndex: 'fineAmount', width: 100 }, - { title: '实付罚款金额(元)', dataIndex: 'paidFineAmount', width: 140, render: function(_, record, i) { - if (detailMode !== 'payment') return (parseFloat(record.paidFineAmount || 0)).toFixed(2); - var pf = paymentForm.violationList[i] || {}; - return React.createElement(Input, { value: pf.paidFineAmount, onChange: function(e) { handlePaymentFormChange('violation', i, 'paidFineAmount', e.target.value); }, style: { width: 100 }, addonAfter: '元' }); - }} - ], - summary: function() { - var totalPaid = mockViolationData.list.reduce(function(s, item) { return s + parseFloat(item.paidFineAmount || 0); }, 0).toFixed(2); - return React.createElement(Table.Summary, null, - React.createElement(Table.Summary.Row, null, - React.createElement(Table.Summary.Cell, { index: 0, colSpan: 4 }, '总计'), - React.createElement(Table.Summary.Cell, { index: 4 }, mockViolationData.totalAmount), - React.createElement(Table.Summary.Cell, { index: 5 }, totalPaid) - ) - ); - } - }) - ), - // 还车费用 - React.createElement(Card, { title: '还车费用', style: { marginBottom: 16 } }, - React.createElement(Table, { - dataSource: mockReturnFeeData.list, - rowKey: function(item, i) { return i; }, - pagination: false, - columns: [ - { title: '费用名称', dataIndex: 'feeName', width: 140 }, - { title: '金额', dataIndex: 'amount', width: 100 }, - { title: '实付金额(元)', dataIndex: 'paidAmount', width: 120, render: function(_, record, i) { - if (detailMode !== 'payment') return (parseFloat(record.paidAmount || 0)).toFixed(2); - var pf = paymentForm.returnFeeList[i] || {}; - return React.createElement(Input, { value: pf.paidAmount, onChange: function(e) { handlePaymentFormChange('returnFee', i, 'paidAmount', e.target.value); }, style: { width: 100 }, addonAfter: '元' }); - }}, - { title: '照片', dataIndex: 'photos', width: 200, render: function(photos) { - if (!photos || photos.length === 0) return '-'; - return React.createElement(Space, null, photos.slice(0, 5).map(function(url, idx) { - return React.createElement('img', { - key: idx, - src: url, - alt: '', - style: { width: 40, height: 40, objectFit: 'cover', borderRadius: 4, cursor: 'pointer' }, - onClick: function() { setPhotoViewer({ visible: true, photos: photos, currentIndex: idx }); } - }); - })); - }}, - { title: '附件', dataIndex: 'attachments', render: function(attachments) { - if (!attachments || attachments.length === 0) return '-'; - return React.createElement(Space, { wrap: true }, attachments.map(function(att, idx) { - return React.createElement('a', { key: idx, onClick: function() { message.info('下载附件:' + att.name); } }, att.name); - })); - }} - ], - summary: function() { - var totalPaid = mockReturnFeeData.list.reduce(function(s, item) { return s + parseFloat(item.paidAmount || 0); }, 0).toFixed(2); - return React.createElement(Table.Summary, null, - React.createElement(Table.Summary.Row, null, - React.createElement(Table.Summary.Cell, { index: 0 }, '总计'), - React.createElement(Table.Summary.Cell, { index: 1 }, mockReturnFeeData.totalAmount), - React.createElement(Table.Summary.Cell, { index: 2 }, totalPaid), - React.createElement(Table.Summary.Cell, { index: 3 }, ''), - React.createElement(Table.Summary.Cell, { index: 4 }, '') - ) - ); - } - }) - ), - React.createElement('div', { style: { marginTop: 24, textAlign: 'center' } }, - React.createElement(Space, null, - detailMode === 'payment' && React.createElement(Button, { type: 'primary', onClick: handleSubmit }, '提交审核'), - detailMode === 'payment' && React.createElement(Button, { onClick: handleSave }, '保存'), - detailMode === 'payment' && React.createElement(Button, { onClick: handleBackToList }, '取消'), - detailMode === 'view' && React.createElement(Button, { onClick: handleBackToList }, '返回') + }) ) - ), - // 照片查看器 Modal - React.createElement(Modal, { - title: '照片查看', - open: photoViewer.visible && photoViewer.photos.length > 0, - onCancel: function() { setPhotoViewer({ visible: false, photos: [], currentIndex: 0 }); }, - footer: null, - width: '90vw', - centered: true - }, photoViewer.photos.length > 0 ? React.createElement('div', { style: { textAlign: 'center' } }, - React.createElement('img', { - src: photoViewer.photos[photoViewer.currentIndex].replace('80/80', '600/600'), - alt: '', - style: { maxWidth: '100%', maxHeight: '70vh', objectFit: 'contain' } - }), - React.createElement('div', { style: { marginTop: 16 } }, - React.createElement(Button, { - disabled: photoViewer.currentIndex <= 0, - onClick: function() { setPhotoViewer({ visible: true, photos: photoViewer.photos, currentIndex: photoViewer.currentIndex - 1 }); } - }, '上一张'), - React.createElement('span', { style: { margin: '0 16px' } }, (photoViewer.currentIndex + 1) + ' / ' + photoViewer.photos.length), - React.createElement(Button, { - disabled: photoViewer.currentIndex >= photoViewer.photos.length - 1, - onClick: function() { setPhotoViewer({ visible: true, photos: photoViewer.photos, currentIndex: photoViewer.currentIndex + 1 }); } - }, '下一张') - ) - ) : null), - // 服务费明细 Modal - popover.type === 'service' && React.createElement(Modal, { - title: '服务费明细', - open: true, - onCancel: function() { setPopover({ type: null, data: null }); }, - footer: React.createElement(Button, { onClick: function() { setPopover({ type: null, data: null }); } }, '关闭') - }, React.createElement(Table, { - dataSource: popover.data, - rowKey: function(item, i) { return i; }, - pagination: false, - columns: [ - { title: '服务项', dataIndex: 'name' }, - { title: '价格', dataIndex: 'price' }, - { title: '服务生效日期', dataIndex: 'effectiveDate' } - ] - })) + ) ); + var count = record.deliveryCount != null && record.deliveryCount !== '' ? Number(record.deliveryCount) : 0; + return React.createElement(Popover, { + content: content, + title: '车辆详情', + trigger: 'click', + open: deliveryPopoverOpen[0] === popoverKey, + onOpenChange: function (open) { deliveryPopoverOpen[1](open ? popoverKey : null); } + }, React.createElement(Button, { type: 'link', size: 'small', style: { padding: 0 } }, (isNaN(count) ? 0 : count) + '辆')); } - // —————— 列表视图 —————— - var listBreadcrumbItems = [ - { title: '运维管理' }, - { title: '业务管理' }, - { title: '租赁账单' } + var layoutStyle = { padding: '16px 24px', background: '#f5f5f5', minHeight: '100vh' }; + var cardStyle = { marginBottom: 16 }; + var filterLabelStyle = { marginBottom: 6, fontSize: 14, color: 'rgba(0,0,0,0.65)' }; + var filterItemStyle = { marginBottom: 12 }; + var filterControlStyle = { width: '100%' }; + var expandColumnWidth = 48; + + var mainColumns = [ + { title: '合同编码', dataIndex: 'contractCode', key: 'contractCode', width: 130, ellipsis: true, render: function (v) { return v || '—'; } }, + { title: '合同类型', dataIndex: 'contractType', key: 'contractType', width: 100, ellipsis: true, render: function (v) { return v || '—'; } }, + { title: '项目名称', dataIndex: 'projectName', key: 'projectName', width: 140, ellipsis: true, render: function (v) { return v || '—'; } }, + { title: '客户名称', dataIndex: 'customerName', key: 'customerName', width: 140, ellipsis: true, render: function (v) { return v || '—'; } }, + { title: '合同生效日期', dataIndex: 'contractEffectiveDate', key: 'contractEffectiveDate', width: 118, render: function (v) { return v || '—'; } }, + { title: '交车任务编码', dataIndex: 'deliveryTaskCode', key: 'deliveryTaskCode', width: 130, ellipsis: true, render: function (v) { return v || '—'; } }, + { title: '业务部门', dataIndex: 'businessDept', key: 'businessDept', width: 100, render: function (v) { return v || '—'; } }, + { title: '业务负责人', dataIndex: 'businessPerson', key: 'businessPerson', width: 100, render: function (v) { return v || '—'; } } ]; - return React.createElement('div', { style: { padding: 24, background: '#f5f5f5', minHeight: '100vh' } }, - React.createElement(Breadcrumb, { items: listBreadcrumbItems, style: { marginBottom: 16 } }), - React.createElement(Card, null, - React.createElement(Row, { gutter: [16, 16], style: { marginBottom: 16 }, align: 'middle' }, - React.createElement(Col, null, - React.createElement('span', { style: { marginRight: 8 } }, '合同编码:'), - React.createElement(Select, { - placeholder: '请选择合同编码', - allowClear: true, - showSearch: true, - optionFilterProp: 'label', - value: contractFilter, - onChange: setContractFilter, - style: { width: 200 }, - options: contractOptions - }) - ), - React.createElement(Col, null, - React.createElement('span', { style: { marginRight: 8 } }, '项目名称:'), - React.createElement(Select, { - placeholder: '请选择项目名称', - allowClear: true, - showSearch: true, - optionFilterProp: 'label', - value: projectFilter, - onChange: setProjectFilter, - style: { width: 200 }, - options: projectOptions - }) - ), - React.createElement(Col, null, - React.createElement('span', { style: { marginRight: 8 } }, '客户名称:'), - React.createElement(Select, { - placeholder: '请选择客户名称', - allowClear: true, - showSearch: true, - optionFilterProp: 'label', - value: customerFilter, - onChange: setCustomerFilter, - style: { width: 200 }, - options: customerOptions - }) - ), - React.createElement(Col, null, - React.createElement('span', { style: { marginRight: 8 } }, '付款状态:'), - React.createElement(Select, { - mode: 'multiple', - placeholder: '全部', - allowClear: true, - value: statusFilter, - onChange: setStatusFilter, - style: { width: 200 }, - options: statusOptions, - maxTagCount: 'responsive' - }) - ), - React.createElement(Col, null, - React.createElement(Space, null, - React.createElement(Button, { type: 'primary', onClick: handleSearch }, '查询'), - React.createElement(Button, { onClick: handleReset }, '重置') - ) - ) - ), - React.createElement('div', { style: { marginBottom: 16, display: 'flex', justifyContent: 'flex-end' } }, - React.createElement(Button, { type: 'primary', onClick: handleExport }, '导出') - ), + var subColumns = [ + { title: '账单编码', dataIndex: 'billNo', key: 'billNo', width: 200, ellipsis: true, render: function (v, record) { var p = record._parentRecord; var code = (p && p.contractCode) || ''; var period = record.period != null ? record.period : ''; var suffix = period !== '' ? ('0000' + period).slice(-4) : ''; return code + suffix || '—'; } }, + { title: '账单期数', dataIndex: 'period', key: 'period', width: 90, align: 'center', render: function (v) { return v != null ? v : '—'; } }, + { title: '账单开始日期', dataIndex: 'billStartDate', key: 'billStartDate', width: 120, render: function (v) { return v || '—'; } }, + { title: '账单结束日期', dataIndex: 'billEndDate', key: 'billEndDate', width: 120, render: function (v) { return v || '—'; } }, + { title: '提车数量', key: 'deliveryCount', width: 88, align: 'center', render: function (_, record) { return renderDeliveryPopover(record); } }, + { title: '应收款总额', dataIndex: 'receivableTotal', key: 'receivableTotal', width: 110, align: 'right', render: function (v) { return fmtMoney(v); } }, + { title: '实收款总额', dataIndex: 'actualTotal', key: 'actualTotal', width: 110, align: 'right', render: function (v) { return fmtMoney(v); } }, + { title: '减免总金额', dataIndex: 'discountTotal', key: 'discountTotal', width: 100, align: 'right', render: function (v) { return fmtMoney(v); } }, + { title: '实际到账金额', dataIndex: 'arrivalAmount', key: 'arrivalAmount', width: 118, align: 'right', render: function (v) { return fmtMoney(v); } }, + { title: '是否已开票', dataIndex: 'isInvoiced', key: 'isInvoiced', width: 96, render: function (v) { return v === '已开票' ? '已开票' : v === '部分开票' ? '部分开票' : (v === '未开票' ? '未开票' : (v || '—')); } }, + { title: '开票金额', dataIndex: 'invoiceAmount', key: 'invoiceAmount', width: 100, align: 'right', render: function (v) { return fmtMoney(v); } }, + { + title: '操作', + key: 'action', + width: 160, + fixed: 'right', + render: function (_, record, rowIndex, extra) { + var parentRecord = (extra && extra._parentRecord) || record._parentRecord; + return React.createElement(Space, { size: 'small' }, + React.createElement(Button, { type: 'link', size: 'small', onClick: function () { goView(record, parentRecord); } }, '查看'), + React.createElement(Button, { type: 'link', size: 'small', onClick: function () { goChargeDetail(record, parentRecord); } }, '收费明细') + ); + } + } + ]; + + // 子表 rowKey 唯一:contractCode + period + var _expandRowRender = function (record) { + var rows = (record._detailList || []).map(function (r, idx) { + var o = {}; for (var k in r) o[k] = r[k]; o._parentRecord = record; o._rowIndex = idx; return o; + }); + return React.createElement('div', { + style: { marginBottom: 0, paddingLeft: expandColumnWidth, boxSizing: 'border-box' }, + draggable: false, + onDragStart: function (e) { e.preventDefault(); } + }, React.createElement(Table, { - rowSelection: rowSelection, - columns: listColumns, - dataSource: paginatedList, - rowKey: 'id', - pagination: tablePagination, - scroll: { x: 1600 }, - size: 'middle' + rowKey: function (r) { return (record.contractCode || '') + '-' + (r.period != null ? r.period : r._rowIndex); }, + columns: subColumns.map(function (col) { + if (col.key !== 'action') return col; + return { + title: col.title, + key: col.key, + width: col.width, + fixed: col.fixed, + render: function (val, row) { + return React.createElement(Space, { size: 'small' }, + React.createElement(Button, { type: 'link', size: 'small', onClick: function () { goView(row, record); } }, '查看'), + React.createElement(Button, { type: 'link', size: 'small', onClick: function () { goChargeDetail(row, record); } }, '收费明细') + ); + } + }; + }), + dataSource: rows, + pagination: false, + size: 'small', + bordered: true, + scroll: { x: 1300 } + }) + ); + }; + + return React.createElement('div', { style: layoutStyle }, + React.createElement('div', { style: { marginBottom: 16 } }, + React.createElement(Breadcrumb, { + items: [ + { title: '业务管理' }, + { title: '租赁账单' } + ] }) ), - // 应付金额明细 Modal(列表按内容一行显示、宽度随内容调整) - React.createElement(Modal, { - title: '应付金额明细', - open: popover.type === 'payable', - onCancel: function() { setPopover({ type: null, data: null }); }, - footer: React.createElement(Button, { onClick: function() { setPopover({ type: null, data: null }); } }, '关闭'), - width: 'fit-content', - style: { maxWidth: '90vw' }, - styles: { body: { paddingBottom: 24 } } - }, popover.type === 'payable' && popover.data ? React.createElement(Table, { - dataSource: popover.data, - rowKey: function(item, i) { return i; }, - pagination: false, - tableLayout: 'auto', - style: { minWidth: 0 }, - columns: [ - { title: '账单开始日期', dataIndex: 'startDate', onCell: function() { return { style: { whiteSpace: 'nowrap' } }; } }, - { title: '账单结束日期', dataIndex: 'endDate', onCell: function() { return { style: { whiteSpace: 'nowrap' } }; } }, - { title: '车牌号', dataIndex: 'plateNo', onCell: function() { return { style: { whiteSpace: 'nowrap' } }; } }, - { title: '车辆租金', dataIndex: 'rent', onCell: function() { return { style: { whiteSpace: 'nowrap' } }; } }, - { title: '服务费', dataIndex: 'serviceFee', onCell: function() { return { style: { whiteSpace: 'nowrap' } }; } }, - { title: '保证金', dataIndex: 'deposit', onCell: function() { return { style: { whiteSpace: 'nowrap' } }; } } - ] - }) : null) + React.createElement(Card, { title: '筛选', style: cardStyle }, + React.createElement('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '16px 24px', alignItems: 'start' } }, + React.createElement('div', { style: filterItemStyle }, + React.createElement('div', { style: filterLabelStyle }, '合同编码'), + React.createElement(Select, { + placeholder: '请选择或输入合同编码', + allowClear: true, + showSearch: true, + style: filterControlStyle, + value: filterContractCode[0], + onChange: function (v) { filterContractCode[1](v); }, + options: filterOptions.contractCode, + filterOption: function (input, opt) { return (opt.label || '').toString().toLowerCase().indexOf((input || '').toLowerCase()) !== -1; } + }) + ), + React.createElement('div', { style: filterItemStyle }, + React.createElement('div', { style: filterLabelStyle }, '项目名称'), + React.createElement(Select, { + placeholder: '请选择或输入项目名称', + allowClear: true, + showSearch: true, + style: filterControlStyle, + value: filterProjectName[0], + onChange: function (v) { filterProjectName[1](v); }, + options: filterOptions.projectName, + filterOption: function (input, opt) { return (opt.label || '').toString().toLowerCase().indexOf((input || '').toLowerCase()) !== -1; } + }) + ), + React.createElement('div', { style: filterItemStyle }, + React.createElement('div', { style: filterLabelStyle }, '客户名称'), + React.createElement(Select, { + placeholder: '请选择或输入客户名称', + allowClear: true, + showSearch: true, + style: filterControlStyle, + value: filterCustomerName[0], + onChange: function (v) { filterCustomerName[1](v); }, + options: filterOptions.customerName, + filterOption: function (input, opt) { return (opt.label || '').toString().toLowerCase().indexOf((input || '').toLowerCase()) !== -1; } + }) + ), + filterExpanded[0] ? React.createElement('div', { style: filterItemStyle }, + React.createElement('div', { style: filterLabelStyle }, '业务部门'), + React.createElement(Select, { + placeholder: '请选择业务部门', + allowClear: true, + style: filterControlStyle, + value: filterBusinessDept[0], + onChange: function (v) { filterBusinessDept[1](v); }, + options: filterOptions.businessDept + }) + ) : null, + filterExpanded[0] ? React.createElement('div', { style: filterItemStyle }, + React.createElement('div', { style: filterLabelStyle }, '业务负责人'), + React.createElement(Select, { + placeholder: '请选择业务负责人', + allowClear: true, + style: filterControlStyle, + value: filterBusinessPerson[0], + onChange: function (v) { filterBusinessPerson[1](v); }, + options: filterOptions.businessPerson + }) + ) : null, + filterExpanded[0] ? React.createElement('div', { style: filterItemStyle }, + React.createElement('div', { style: filterLabelStyle }, '交车任务编码'), + React.createElement(Input, { + placeholder: '请输入交车任务编码,支持模糊搜索', + allowClear: true, + style: filterControlStyle, + value: filterDeliveryTaskCode[0], + onChange: function (e) { filterDeliveryTaskCode[1](e.target.value); } + }) + ) : null + ), + React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 16 } }, + React.createElement(Button, { type: 'link', size: 'small', onClick: function () { filterExpanded[1](!filterExpanded[0]); } }, filterExpanded[0] ? '收起' : '展开'), + React.createElement(Button, { onClick: function () { filterContractCode[1](undefined); filterProjectName[1](undefined); filterCustomerName[1](undefined); filterBusinessDept[1](undefined); filterBusinessPerson[1](undefined); filterDeliveryTaskCode[1](''); } }, '重置'), + React.createElement(Button, { type: 'primary' }, '查询') + ) + ), + React.createElement(Card, { title: '租赁账单列表', style: cardStyle }, + React.createElement(Table, { + rowKey: 'contractCode', + columns: mainColumns, + dataSource: mainTableDataSource, + expandable: { + expandedRowKeys: expandedRowKeys, + onExpandedRowsChange: function (keys) { setExpandedRowKeys(keys || []); }, + expandedRowRender: _expandRowRender, + rowExpandable: function (record) { return (record._detailList && record._detailList.length > 0); }, + columnWidth: expandColumnWidth + }, + pagination: { pageSize: 10, showSizeChanger: true, showTotal: function (t) { return '共 ' + t + ' 条'; } }, + size: 'middle', + bordered: true, + scroll: { x: 958 } + }) + ) ); }; - -if (typeof window !== 'undefined') { - window.Component = Component; - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', function() { - var rootEl = document.getElementById('root'); - if (rootEl && window.ReactDOM && window.React) { - var root = ReactDOM.createRoot(rootEl); - root.render(React.createElement(Component)); - } - }); - } else { - var rootEl = document.getElementById('root'); - if (rootEl && window.ReactDOM && window.React) { - var root = ReactDOM.createRoot(rootEl); - root.render(React.createElement(Component)); - } - } -} diff --git a/web端/加氢站管理/加氢订单.jsx b/web端/加氢站管理/加氢订单.jsx new file mode 100644 index 0000000..c80d150 --- /dev/null +++ b/web端/加氢站管理/加氢订单.jsx @@ -0,0 +1,163 @@ +// 【重要】必须使用 const Component 作为组件变量名 +// 加氢记录(列表 + 筛选)(2026年3月版) + +const Component = function () { + var useState = React.useState; + var useMemo = React.useMemo; + + var antd = window.antd; + var Card = antd.Card; + var Table = antd.Table; + var Button = antd.Button; + var Select = antd.Select; + var Input = antd.Input; + var DatePicker = antd.DatePicker; + var Space = antd.Space; + var message = antd.message; + + var Option = Select.Option; + var RangePicker = DatePicker.RangePicker || DatePicker.RangePicker; + + // 筛选状态 + var stationState = useState(undefined); + var timeRangeState = useState([]); + var plateNoState = useState(''); + var station = stationState[0]; + var setStation = stationState[1]; + var timeRange = timeRangeState[0]; + var setTimeRange = timeRangeState[1]; + var plateNo = plateNoState[0]; + var setPlateNo = plateNoState[1]; + + // 模拟:加氢站与加氢记录数据 + var stationList = useMemo(function () { + return [ + { value: 'JX-H2-001', label: '嘉兴加氢站(一期)' }, + { value: 'HZ-H2-002', label: '杭州临平加氢站' }, + { value: 'SH-H2-003', label: '上海宝山加氢站' } + ]; + }, []); + + var rawData = useMemo(function () { + return [ + { id: 1, stationCode: 'JX-H2-001', stationName: '嘉兴加氢站(一期)', time: '2026-03-01 10:21', plateNo: '浙A12345', amountKg: 12.5, amountYuan: 625.00 }, + { id: 2, stationCode: 'JX-H2-001', stationName: '嘉兴加氢站(一期)', time: '2026-03-01 14:08', plateNo: '浙A67890', amountKg: 10.0, amountYuan: 500.00 }, + { id: 3, stationCode: 'HZ-H2-002', stationName: '杭州临平加氢站', time: '2026-03-02 09:30', plateNo: '浙B23456', amountKg: 15.3, amountYuan: 765.00 }, + { id: 4, stationCode: 'SH-H2-003', stationName: '上海宝山加氢站', time: '2026-03-03 16:45', plateNo: '沪A88888', amountKg: 8.0, amountYuan: 400.00 }, + { id: 5, stationCode: 'HZ-H2-002', stationName: '杭州临平加氢站', time: '2026-03-03 18:10', plateNo: '浙B99999', amountKg: 18.2, amountYuan: 910.00 } + ]; + }, []); + + // 简单字符串时间过滤(示例用,实际可改为 dayjs 比较) + function inRange(timeStr, range) { + if (!range || !range.length || !range[0] || !range[1]) return true; + // timeStr: 'YYYY-MM-DD HH:mm',只做字符串比较以示意 + var t = timeStr.replace(/[-:\\s]/g, ''); + var s = range[0].format ? range[0].format('YYYYMMDDHHmm') : ''; + var e = range[1].format ? range[1].format('YYYYMMDDHHmm') : ''; + if (!s || !e) return true; + return t >= s && t <= e; + } + + var filteredData = useMemo(function () { + var list = rawData.slice(); + if (station) { + list = list.filter(function (r) { return r.stationCode === station; }); + } + if (plateNo && plateNo.trim()) { + var kw = plateNo.trim().toLowerCase(); + list = list.filter(function (r) { return (r.plateNo || '').toLowerCase().indexOf(kw) !== -1; }); + } + if (timeRange && timeRange.length === 2) { + list = list.filter(function (r) { return inRange(r.time, timeRange); }); + } + return list.map(function (r, idx) { + return Object.assign({}, r, { seq: idx + 1 }); + }); + }, [rawData, station, plateNo, timeRange]); + + var columns = [ + { title: '序号', dataIndex: 'seq', key: 'seq', width: 70, align: 'center' }, + { title: '加氢站', dataIndex: 'stationName', key: 'stationName', width: 200 }, + { title: '加氢时间', dataIndex: 'time', key: 'time', width: 180 }, + { title: '车牌号', dataIndex: 'plateNo', key: 'plateNo', width: 120 }, + { title: '加氢量(kg)', dataIndex: 'amountKg', key: 'amountKg', width: 120, align: 'right', render: function (v) { return (v != null ? v.toFixed(2) : '0.00'); } }, + { title: '金额(元)', dataIndex: 'amountYuan', key: 'amountYuan', width: 120, align: 'right', render: function (v) { var n = typeof v === 'number' ? v : parseFloat(v); return (isNaN(n) ? '0.00' : n.toFixed(2)); } } + ]; + + var layoutStyle = { padding: '16px 24px', background: '#f5f5f5', minHeight: '100vh' }; + var cardStyle = { marginBottom: 16 }; + var filterLabelStyle = { marginBottom: 6, fontSize: 14, color: 'rgba(0,0,0,0.65)' }; + var filterItemStyle = { marginBottom: 12 }; + + function handleReset() { + setStation(undefined); + setTimeRange([]); + setPlateNo(''); + } + + function handleExport() { + // 实际项目中这里调用导出接口;此处仅做提示 + message.info('导出当前筛选条件下的加氢记录(示例)'); + } + + return React.createElement('div', { style: layoutStyle }, + React.createElement(Card, { title: '筛选', style: cardStyle }, + React.createElement('div', { + style: { display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '16px 24px', alignItems: 'flex-end' } + }, + React.createElement('div', { style: filterItemStyle }, + React.createElement('div', { style: filterLabelStyle }, '站点筛选'), + React.createElement(Select, { + placeholder: '请选择加氢站', + allowClear: true, + style: { width: '100%' }, + value: station, + onChange: function (v) { setStation(v); } + }, + stationList.map(function (s) { + return React.createElement(Option, { key: s.value, value: s.value }, s.label); + }) + ) + ), + React.createElement('div', { style: filterItemStyle }, + React.createElement('div', { style: filterLabelStyle }, '加氢时间'), + React.createElement(RangePicker, { + style: { width: '100%' }, + showTime: true, + placeholder: ['开始时间', '结束时间'], + value: timeRange, + onChange: function (v) { setTimeRange(v || []); } + }) + ), + React.createElement('div', { style: filterItemStyle }, + React.createElement('div', { style: filterLabelStyle }, '车牌号'), + React.createElement(Input, { + placeholder: '请输入车牌号', + value: plateNo, + onChange: function (e) { setPlateNo(e.target.value); } + }) + ) + ), + React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', marginTop: 16 } }, + React.createElement(Space, null, + React.createElement(Button, { type: 'primary' }, '查询'), + React.createElement(Button, { onClick: handleReset }, '重置') + ), + React.createElement(Button, { onClick: handleExport }, '导出') + ) + ), + React.createElement(Card, { title: '加氢记录', style: cardStyle }, + React.createElement(Table, { + rowKey: 'id', + columns: columns, + dataSource: filteredData, + pagination: { pageSize: 10, showSizeChanger: true, showTotal: function (t) { return '共 ' + t + ' 条'; } }, + bordered: true, + size: 'middle', + scroll: { x: 700 } + }) + ) + ); +}; + diff --git a/web端/安全培训扫码.jsx b/web端/安全培训扫码.jsx new file mode 100644 index 0000000..b1f1144 --- /dev/null +++ b/web端/安全培训扫码.jsx @@ -0,0 +1,268 @@ +// 【重要】必须使用 const Component 作为组件变量名 +// 安全培训扫码 - H5 分步表单(扫码链接进入) + +const Component = function () { + var useState = React.useState; + var useCallback = React.useCallback; + var useRef = React.useRef; + var useEffect = React.useEffect; + + var antd = window.antd; + var Steps = antd.Steps; + var Button = antd.Button; + var Input = antd.Input; + var message = antd.message; + var Progress = antd.Progress; + + // 当前步骤 1 | 2 | 3 | 4 + var stepState = useState(1); + var step = stepState[0]; + var setStep = stepState[1]; + + // 第一步:手机号、验证码 + var phoneState = useState(''); + var verifyCodeState = useState(''); + var codeCountdownState = useState(0); + var phone = phoneState[0]; + var setPhone = phoneState[1]; + var verifyCode = verifyCodeState[0]; + var setVerifyCode = verifyCodeState[1]; + var codeCountdown = codeCountdownState[0]; + var setCodeCountdown = codeCountdownState[1]; + + // 模拟:已完成培训的手机号,再次进入直接显示提车码 + var completedPhonesState = useState({ '13800138000': 'TC-2026-8888', '13900139000': 'TC-2026-9999' }); + var completedPhones = completedPhonesState[0]; + var setCompletedPhones = completedPhonesState[1]; + + // 第二步:证照上传(用文件名/已上传标记模拟) + var idFrontState = useState(null); + var idBackState = useState(null); + var licenseState = useState(null); + var qualificationState = useState(null); + var needQualification = false; // 18吨以上车辆必填,此处为可选 + + // 第三步:视频进度(0-100),是否播放中 + var videoProgressState = useState(0); + var videoPlayingState = useState(false); + var videoProgress = videoProgressState[0]; + var setVideoProgress = videoProgressState[1]; + var videoPlaying = videoPlayingState[0]; + var setVideoPlaying = videoPlayingState[1]; + var videoTimerRef = useRef(null); + + // 第四步:提车码(生成后保存,同一手机再次进入可直接显示) + var pickupCodeState = useState(''); + var pickupCode = pickupCodeState[0]; + var setPickupCode = pickupCodeState[1]; + + // 获取验证码倒计时 + useEffect(function () { + if (codeCountdown <= 0) return; + var t = setTimeout(function () { setCodeCountdown(function (c) { return c - 1; }); }, 1000); + return function () { clearTimeout(t); }; + }, [codeCountdown]); + + // 视频模拟:播放时每 500ms 增加进度,到 100% 停止 + useEffect(function () { + if (!videoPlaying || videoProgress >= 100) { + if (videoProgress >= 100) setVideoPlaying(false); + return; + } + var t = setInterval(function () { + setVideoProgress(function (p) { + var next = Math.min(100, p + 2); + return next; + }); + }, 500); + return function () { clearInterval(t); }; + }, [videoPlaying, videoProgress]); + + // 第一步:验证并进入 + var handleStep1Next = useCallback(function () { + var p = (phone || '').trim(); + var c = (verifyCode || '').trim(); + if (!p) { message.warning('请输入手机号'); return; } + if (!c) { message.warning('请输入验证码'); return; } + // 已完成培训的手机号直接进入第四步 + if (completedPhones[p]) { + setPickupCode(completedPhones[p]); + setStep(4); + return; + } + // 模拟验证成功(任意 4-6 位验证码) + if (c.length < 4) { message.warning('请输入正确的验证码'); return; } + setStep(2); + }, [phone, verifyCode, completedPhones]); + + var handleSendCode = useCallback(function () { + var p = (phone || '').trim(); + if (!p || p.length < 11) { message.warning('请输入正确手机号'); return; } + if (codeCountdown > 0) return; + setCodeCountdown(60); + message.success('验证码已发送'); + }, [phone, codeCountdown]); + + // 第二步:上传(模拟点击即视为已上传) + var uploadAreaStyle = { + border: '1px dashed #d9d9d9', + borderRadius: 8, + padding: '24px 16px', + textAlign: 'center', + background: '#fafafa', + color: 'rgba(0,0,0,0.65)', + fontSize: 14, + cursor: 'pointer' + }; + var uploadDoneStyle = { borderColor: '#52c41a', background: '#f6ffed', color: '#52c41a' }; + + function renderUpload(label, value, setValue) { + var done = value != null && value !== ''; + return React.createElement('div', { + key: label, + style: Object.assign({}, uploadAreaStyle, done ? uploadDoneStyle : {}), + onClick: function () { + // 模拟选择文件/拍照:点击即视为已上传 + setValue(done ? null : label + '-已上传.jpg'); + } + }, done ? (value + ' ✓') : ('点击上传 ' + label + '(支持现场拍照/本地文件)')); + } + + var allRequiredUploaded = (idFrontState[0] != null && idFrontState[0] !== '') && + (idBackState[0] != null && idBackState[0] !== '') && + (licenseState[0] != null && licenseState[0] !== '') && + (!needQualification || (qualificationState[0] != null && qualificationState[0] !== '')); + + var handleStep2WatchVideo = useCallback(function () { + if (!allRequiredUploaded) { message.warning('请完成全部必填证照上传'); return; } + setStep(3); + setVideoProgress(0); + setVideoPlaying(false); + }, [allRequiredUploaded]); + + // 第三步:播放/暂停,进度到 100% 后可生成提车码 + var handleVideoPlayPause = useCallback(function () { + if (videoProgress >= 100) return; + setVideoPlaying(function (v) { return !v; }); + }, [videoProgress]); + + var handleStep3Generate = useCallback(function () { + if (videoProgress < 100) { message.warning('请完整观看安全培训视频'); return; } + var code = 'TC-' + new Date().getFullYear() + '-' + Math.floor(1000 + Math.random() * 9000); + setPickupCode(code); + setCompletedPhones(function (prev) { + var next = {}; for (var k in prev) next[k] = prev[k]; next[phone.trim()] = code; return next; + }); + setStep(4); + message.success('提车码已生成'); + }, [videoProgress, phone]); + + // H5 移动端布局 + var pageStyle = { + maxWidth: 414, + margin: '0 auto', + minHeight: '100vh', + background: '#f5f5f5', + padding: '16px', + boxSizing: 'border-box' + }; + var stepIndicatorStyle = { marginBottom: 24 }; + var cardStyle = { marginBottom: 16, borderRadius: 12 }; + var labelStyle = { display: 'block', marginBottom: 8, fontSize: 15, color: 'rgba(0,0,0,0.85)' }; + var inputStyle = { width: '100%', height: 48, fontSize: 16, boxSizing: 'border-box' }; + var btnBlockStyle = { width: '100%', height: 48, fontSize: 16, marginTop: 8 }; + var stepTitleStyle = { fontSize: 17, fontWeight: 600, marginBottom: 16, color: 'rgba(0,0,0,0.85)' }; + + var stepsItems = [ + { title: '验证' }, + { title: '证照' }, + { title: '视频' }, + { title: '提车码' } + ]; + + // 第一步视图 + var step1Content = React.createElement('div', null, + React.createElement('div', { style: stepTitleStyle }, '手机号验证'), + React.createElement('div', { style: { marginBottom: 16 } }, + React.createElement('label', { style: labelStyle }, '手机号'), + React.createElement(Input, { + placeholder: '请输入手机号', + value: phone, + onChange: function (e) { setPhone(e.target.value); }, + style: inputStyle, + maxLength: 11, + type: 'tel' + }) + ), + React.createElement('div', { style: { marginBottom: 16 } }, + React.createElement('label', { style: labelStyle }, '验证码'), + React.createElement('div', { style: { display: 'flex', gap: 8 } }, + React.createElement(Input, { + placeholder: '请输入验证码', + value: verifyCode, + onChange: function (e) { setVerifyCode(e.target.value); }, + style: Object.assign({}, inputStyle, { flex: 1 }), + maxLength: 6 + }), + React.createElement(Button, { + disabled: codeCountdown > 0, + onClick: handleSendCode, + style: { height: 48, minWidth: 100 } + }, codeCountdown > 0 ? codeCountdown + 's' : '获取验证码') + ) + ), + React.createElement(Button, { type: 'primary', size: 'large', style: btnBlockStyle, onClick: handleStep1Next }, '下一步') + ); + + // 第二步视图 + var step2Content = React.createElement('div', null, + React.createElement('div', { style: stepTitleStyle }, '证照上传'), + React.createElement('div', { style: { marginBottom: 12 } }, '请上传以下证照,支持现场拍照或手机本地文件。'), + React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 12 } }, + renderUpload('身份证正面', idFrontState[0], idFrontState[1]), + renderUpload('身份证反面', idBackState[0], idBackState[1]), + renderUpload('驾驶证', licenseState[0], licenseState[1]), + renderUpload('从业资格证(18吨以上车辆请上传,可选)', qualificationState[0], qualificationState[1]) + ), + React.createElement(Button, { type: 'primary', size: 'large', style: Object.assign({}, btnBlockStyle, { marginTop: 24 }), onClick: handleStep2WatchVideo, disabled: !allRequiredUploaded }, '观看安全培训视频') + ); + + // 第三步视图:视频区域(不可快进快退,仅暂停/播放) + var step3Content = React.createElement('div', null, + React.createElement('div', { style: stepTitleStyle }, '安全培训视频'), + React.createElement('div', { style: { marginBottom: 8, fontSize: 14, color: 'rgba(0,0,0,0.65)' } }, '请完整观看视频,不支持快进快退。'), + React.createElement('div', { + style: { background: '#000', borderRadius: 8, overflow: 'hidden', marginBottom: 16, position: 'relative', paddingBottom: '56.25%', height: 0 }, + onClick: handleVideoPlayPause + }, + React.createElement('div', { style: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff', fontSize: 14 } }, + videoProgress >= 100 ? '播放完成' : (videoPlaying ? '播放中... 点击暂停' : '点击播放') + ) + ), + React.createElement('div', { style: { marginBottom: 8 } }, + React.createElement(Progress, { percent: videoProgress, showInfo: true }) + ), + React.createElement('div', { style: { display: 'flex', gap: 8 } }, + React.createElement(Button, { onClick: handleVideoPlayPause, disabled: videoProgress >= 100 }, videoPlaying ? '暂停' : '播放'), + React.createElement(Button, { type: 'primary', disabled: videoProgress < 100, onClick: handleStep3Generate }, '生成提车码') + ) + ); + + // 第四步视图 + var step4Content = React.createElement('div', { style: { textAlign: 'center', padding: '24px 0' } }, + React.createElement('div', { style: stepTitleStyle }, '提车码'), + React.createElement('div', { style: { fontSize: 15, color: 'rgba(0,0,0,0.65)', marginBottom: 24 } }, '小程序扫描提车码后自动拉取司机证件信息。提车码在运维完成扫提车码并交车成功后失效。'), + React.createElement('div', { + style: { fontSize: 28, fontWeight: 700, letterSpacing: 4, padding: '20px', background: '#f0f0f0', borderRadius: 12, marginBottom: 16, userSelect: 'all' } + }, pickupCode || '—'), + React.createElement('div', { style: { width: 160, height: 160, margin: '0 auto 16px', background: '#f0f0f0', border: '2px dashed #d9d9d9', borderRadius: 8, display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#999', fontSize: 12 } }, '提车码二维码') + ); + + var stepContents = [step1Content, step2Content, step3Content, step4Content]; + + return React.createElement('div', { style: pageStyle }, + React.createElement('div', { style: { padding: '12px 0', marginBottom: 8, fontSize: 18, fontWeight: 600 } }, '安全培训扫码'), + React.createElement(Steps, { current: step - 1, style: stepIndicatorStyle, size: 'small', items: stepsItems }), + React.createElement('div', { style: { background: '#fff', borderRadius: 12, padding: 20, boxShadow: '0 1px 2px rgba(0,0,0,0.05)' } }, stepContents[step - 1]) + ); +}; diff --git a/web端/财务管理/提车应收款-审核.jsx b/web端/财务管理/提车应收款-审核.jsx new file mode 100644 index 0000000..ee53f85 --- /dev/null +++ b/web端/财务管理/提车应收款-审核.jsx @@ -0,0 +1,397 @@ +// 【重要】必须使用 const Component 作为组件变量名 +// 财务管理 - 提车应收款 - 审批(2026年3月4日版本) + +const Component = function () { + var useState = React.useState; + var useMemo = React.useMemo; + + var antd = window.antd; + var Breadcrumb = antd.Breadcrumb; + var Card = antd.Card; + var Button = antd.Button; + var Modal = antd.Modal; + var Input = antd.Input; + var Popover = antd.Popover; + var Tooltip = antd.Tooltip; + var Steps = antd.Steps; + var message = antd.message; + + var projectCardExpanded = useState(true); + var receivableCardExpanded = useState(true); + var approveCardExpanded = useState(true); + var rejectModalVisible = useState(false); + var setRejectModalVisible = rejectModalVisible[1]; + var rejectRemark = useState(''); + var setRejectRemark = rejectRemark[1]; + var approveRemark = useState(''); + var setApproveRemark = approveRemark[1]; + var requirementModalVisible = useState(false); + var setRequirementModalVisible = requirementModalVisible[1]; + var proofPreviewVisible = useState(false); + var setProofPreviewVisible = proofPreviewVisible[1]; + var proofPreviewFile = useState(null); + var setProofPreviewFile = proofPreviewFile[1]; + + var requirementContent = '提车应收款-审批(2026年3月4日版本)\n一个「数字化资产ONEOS运管平台」中的「财务管理」「提车应收款」「审批」模块\n#面包屑:财务管理-提车应收款-审批;\n\n页面分为4个卡片,业务流程请参考流程图;\n1.项目信息:\n#显示项目详细信息,包括:\n1.1.合同编码:显示该租赁合同对应合同编码;\n1.2.合同类型:显示该租赁合同对应合同编码;\n1.3.项目名称:显示该租赁合同对应项目名称;\n1.4.客户名称:显示该租赁合同对应客户名称;\n1.5.付款方式:显示该租赁合同对应付款方式,分为:预付、后付两种,根据实际合同反写;\n1.6.付款周期:显示该租赁合同对应付款周期,格式为:x个月;\n1.7.合同生效时间:显示该租赁合同对应合同生效时间;\n1.8.合同结束时间:显示该租赁合同对应合同结束时间;\n1.9.业务部门:显示该租赁合同对应业务部门名称;\n1.10.业务负责人:显示该租赁合同对应业务负责人;\n\n2.提车应收款信息:\n#上方显示应收款总额、实收款总额;\n2.1.应收款总额:显示提交审核时的应收款总额,格式为:xx.xx元,点击金额弹出气泡卡片,卡片中显示列表,字段为:\n 2.1.1.项目:包括总计应收车辆月租金、总计应收车辆保证金、总计应收服务费三项;\n 2.1.2.金额:显示该项目对应应收金额;\n2.2.实收款总额:显示提交审核时的实收款总额,格式为:xx.xx元,点击金额弹出气泡卡片,卡片中显示列表,字段为:\n 2.2.1.项目:包括总计实收车辆月租金、总计应收车辆保证金、总计实收服务费、总计减免金额四项;\n 2.2.2.金额:显示该项目对应实收金额;\n#中间为车辆应收款明细,以列表展示提车应收款已选中所有车辆明细,包括以下字段:\n2.1.序号:根据租赁合同中车辆对应序号顺序展示;\n2.2.品牌:显示租赁合同中车辆对应品牌;\n2.3.型号:显示租赁合同中车辆对应型号;\n2.4.车牌号:显示租赁合同中车辆对应车牌号,如果车牌号为空则显示为-(后续如果交车成功,则车牌号会反显在该处);\n2.5.应收车辆月租金:根据租赁合同付款周期自动计算,例如:付款周期为6个月,则这里显示金额为:车辆月租金*6;\n2.6.实收车辆月租金:显示提交审核时的实收车辆月租金,格式为:xx.xx元;\n2.7.车辆租金备注:显示提交审核时的车辆租金备注,超长则显示...,悬浮时显示全部详细内容;\n2.8.减免金额:显示提交审核时的减免金额,格式为:xx.xx元;\n2.9.减免金额备注:显示提交审核时的减免金额备注,超长则显示...,悬浮时显示全部详细内容;\n2.10.减免证明:显示所有附件,可点击预览;\n2.11.应收车辆保证金:显示租赁合同中当前车辆保证金金额,格式为:xx.xx元;\n2.12.服务费项目:点击管理,弹出气泡卡片,气泡卡片标题为服务费项目,下方为列表,显示服务项目、应收费用、实收费用、备注;\n 2.12.1.服务项目:显示租赁合同中所有服务项目名称;\n 2.12.2.应收费用:显示租赁合同中所有服务项目对应费用;\n 2.12.3.实收费用:显示提交审核时的实收费用金额,格式为:xx.xx元;\n 2.12.4.减免费用:显示提交审核时的减免费用金额,格式为:xx.xx元;\n 2.12.4.备注:显示提交审核时的服务费减免备注,超长则显示...,悬浮时显示全部详细内容;\n2.13.应收服务费:显示当前车辆所有服务费应收费用总和,格式为:xx.xx元;\n2.14.实收服务费:显示当前车辆所有服务费实收费用总和,格式为:xx.xx元;\n2.15.列表下方对应字段下方显示总计数据,包括:总计应收车辆月租金、总计实收车辆月租金、总计应收车辆保证金、总计应收服务费、总计实收服务费、总计减免金额;\n列表不支持分页功能;\n#下方为氢费预付款情况,以列表展示租赁合同中所有氢费预付款明细,包括以下字段,如租赁合同氢费非预付款模式,则不显示该部分内容:\n2.19.氢费预付款应收金额:显示租赁合同中氢费预付款金额,格式为:xx.xx元,支持2位小数;\n2.20.氢费预付款实收金额:显示提交审核时的氢费预付款实收金额,格式为:xx.xx元;\n2.21.减免金额:显示提交审核时的减免金额,格式为:xx.xx元;\n2.22.减免金额备注:显示提交审核时的氢费预付款减免备注,超长则显示...,悬浮时显示全部详细内容;\n#最底部为开票方式、开票备注;\n2.23.开票方式:显示提交审核时的开票方式;\n2.24.开票备注:显示提交审核时的开票备注;\n\n3.审核情况:\n#上方为纵向步骤条,显示所有审批步骤\n3.1.步骤中显示部门名称,部门名称后方显示审批状态、审批人、审批时间;\n 3.1.1.部门名称:当前节点部门名称;\n 3.1.2.审批状态:分为待审批、审批通过、审批驳回;\n 3.1.3.审批人:步骤节点对应审批人姓名;\n 3.1.4.审批时间:显示审批通过/驳回时间,格式为:YYYY-MM-DD HH:MM;\n3.2.当前步骤的审批人下方为审批说明:非必填,文本域,支持自定义输入;\n3.3.底部为审批通过、驳回、取消;\n 3.3.1.审批通过:点击提示:审批完成,同时下个流程节点审批人工作台待办任务中收到审批任务和消息提示;如当前已是最后节点,则完成审批,同时进入财务开票环节;\n 3.3.2.驳回:点击进行二次确认,二次确认后提示:驳回成功,同时该任务在列表中显示为审核驳回,发起人可重新编辑;\n 3.3.3.取消:点击返回提车应收款列表页;'; + + var projectInfo = useMemo(function () { + return { + contractCode: 'HT-ZL-2025-001', + contractType: '正式合同', + projectName: '嘉兴氢能示范项目', + customerName: '嘉兴某某物流有限公司', + paymentMethod: '预付', + paymentCycle: '6个月', + contractStart: '2025-01-15', + contractEnd: '2026-01-14', + businessDept: '业务1部', + businessPerson: '张经理' + }; + }, []); + + var vehicles = useMemo(function () { + return [ + { key: 'v1', index: 1, brand: '东风', model: 'DFH1180', plateNo: '浙A12345', receivableRent: 30000, actualRent: '30000.00', rentRemark: '首月全额', receivableDeposit: 10000, receivableService: 700, actualService: '700.00', discountAmount: '0.00', discountRemark: '', discountProof: [{ name: '证明1.pdf' }], serviceItems: [{ name: '代处理费用', receivable: 200, actual: '200.00', discount: '0.00', remark: '' }, { name: '保险上浮', receivable: 500, actual: '500.00', discount: '0.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: '', discountProof: [], serviceItems: [{ name: '保养费用', receivable: 300, actual: '300.00', discount: '0.00', remark: '' }] }, + { key: 'v3', index: 3, brand: '重汽', model: 'ZZ1187', plateNo: '浙A34567', receivableRent: 31200, actualRent: '31200.00', rentRemark: '客户协商延期支付部分', receivableDeposit: 10000, receivableService: 580, actualService: '580.00', discountAmount: '0.00', discountRemark: '无', discountProof: [{ name: '减免说明.jpg' }], serviceItems: [{ name: '代处理费用', receivable: 180, actual: '180.00', discount: '0.00', remark: '' }, { name: '上牌服务', receivable: 400, actual: '400.00', discount: '0.00', remark: '' }] } + ]; + }, []); + + var hasHydrogenPrepay = true; + var hydrogen = useMemo(function () { return { receivable: '3580.00', actual: '3580.00', discount: '0.00', discountRemark: '氢费预付款减免说明,与客户签订补充协议' }; }, []); + var invoiceMethod = '先开票后付款'; + var invoiceRemark = '开票项目:租赁费,税率 13%。请开具增值税专用发票。'; + + var totals = useMemo(function () { + var receivableRent = 0, actualRent = 0, receivableDeposit = 0, receivableService = 0, actualService = 0, discountTotal = 0; + vehicles.forEach(function (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; + }); + 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) + }; + }, [vehicles]); + + var receivableTotal = (parseFloat(totals.receivableRent) + parseFloat(totals.receivableDeposit) + parseFloat(totals.receivableService)).toFixed(2); + var actualTotal = (parseFloat(totals.actualRent) + parseFloat(totals.receivableDeposit) + parseFloat(totals.actualService) - parseFloat(totals.discountTotal)).toFixed(2); + + var receivablePopoverContent = useMemo(function () { + return React.createElement('div', { style: { padding: 8, minWidth: 220 } }, + React.createElement('table', { style: { width: '100%', borderCollapse: 'collapse', fontSize: 13 } }, + React.createElement('thead', null, + React.createElement('tr', null, + React.createElement('th', { style: { textAlign: 'left', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '项目'), + React.createElement('th', { style: { textAlign: 'right', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '金额') + ) + ), + React.createElement('tbody', null, + React.createElement('tr', null, React.createElement('td', { style: { padding: '6px 8px' } }, '总计应收车辆月租金'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.receivableRent + ' 元')), + React.createElement('tr', null, React.createElement('td', { style: { padding: '6px 8px' } }, '总计应收车辆保证金'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.receivableDeposit + ' 元')), + React.createElement('tr', null, React.createElement('td', { style: { padding: '6px 8px' } }, '总计应收服务费'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.receivableService + ' 元')) + ) + ) + ); + }, [totals]); + + var actualPopoverContent = useMemo(function () { + return React.createElement('div', { style: { padding: 8, minWidth: 220 } }, + React.createElement('table', { style: { width: '100%', borderCollapse: 'collapse', fontSize: 13 } }, + React.createElement('thead', null, + React.createElement('tr', null, + React.createElement('th', { style: { textAlign: 'left', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '项目'), + React.createElement('th', { style: { textAlign: 'right', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '金额') + ) + ), + React.createElement('tbody', null, + React.createElement('tr', null, React.createElement('td', { style: { padding: '6px 8px' } }, '总计实收车辆月租金'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.actualRent + ' 元')), + React.createElement('tr', null, React.createElement('td', { style: { padding: '6px 8px' } }, '总计应收车辆保证金'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.receivableDeposit + ' 元')), + React.createElement('tr', null, React.createElement('td', { style: { padding: '6px 8px' } }, '总计实收服务费'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.actualService + ' 元')), + React.createElement('tr', null, React.createElement('td', { style: { padding: '6px 8px' } }, '总计减免金额'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.discountTotal + ' 元')) + ) + ) + ); + }, [totals]); + + var approvalSteps = useMemo(function () { + return [ + { title: '业务部门', status: 'finish', person: '张经理', time: '2026-03-01 10:00' }, + { title: '财务部门', status: 'process', person: '李四', time: '' }, + { title: '财务负责人', status: 'wait', person: '王五', time: '' } + ]; + }, []); + + var layoutStyle = { padding: '16px 24px', background: '#f5f5f5', minHeight: '100vh' }; + var cardStyle = { marginBottom: 16 }; + var labelStyle = { marginBottom: 6, fontSize: 14, color: 'rgba(0,0,0,0.65)' }; + var formRowStyle = { display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '16px 24px', marginBottom: 16 }; + var formItemStyle = { marginBottom: 12 }; + var valueStyle = { color: 'rgba(0,0,0,0.85)', fontSize: 14, lineHeight: '22px', minHeight: 22 }; + var thBase = { padding: '10px 12px', border: '1px solid #f0f0f0', whiteSpace: 'nowrap' }; + var highlightStyle = { color: '#1890ff', fontWeight: 600, cursor: 'pointer' }; + + function ellipsisCell(text, maxLen) { + var str = (text == null || text === '') ? '-' : String(text); + var show = str.length <= (maxLen || 8) ? str : str.slice(0, maxLen || 8) + '...'; + return React.createElement(Tooltip, { title: str === '-' ? '' : str }, React.createElement('span', { style: { display: 'inline-block', maxWidth: 120, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, show)); + } + + var handlePass = function () { + message.success('审批完成,同时下个流程节点审批人工作台待办任务中收到审批任务和消息提示'); + if (window.__receivableBack) window.__receivableBack(); else message.info('返回提车应收款列表(原型)'); + }; + + var handleRejectClick = function () { + setRejectModalVisible(true); + }; + + var handleRejectConfirm = function () { + setRejectModalVisible(false); + setRejectRemark(''); + message.success('驳回成功,同时该任务在列表中显示为审核驳回,发起人可重新编辑'); + if (window.__receivableBack) window.__receivableBack(); else message.info('返回提车应收款列表(原型)'); + }; + + var handleCancel = function () { + if (window.__receivableBack) window.__receivableBack(); else message.info('返回提车应收款列表(原型)'); + }; + + return React.createElement('div', { style: layoutStyle }, + React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 } }, + React.createElement(Breadcrumb, { + items: [ + { title: '财务管理' }, + { title: '提车应收款' }, + { title: '审批' } + ] + }), + React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalVisible(true); } }, '查看需求说明') + ), + React.createElement(Card, { + title: '项目信息', + style: cardStyle, + extra: React.createElement(Button, { type: 'link', size: 'small', onClick: function () { projectCardExpanded[1](!projectCardExpanded[0]); } }, projectCardExpanded[0] ? '收起' : '展开') + }, + projectCardExpanded[0] ? React.createElement('div', { style: formRowStyle }, + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '合同编码'), React.createElement('div', { style: valueStyle }, projectInfo.contractCode || '-')), + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '合同类型'), React.createElement('div', { style: valueStyle }, projectInfo.contractType || '-')), + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '项目名称'), React.createElement('div', { style: valueStyle }, projectInfo.projectName || '-')), + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '客户名称'), React.createElement('div', { style: valueStyle }, projectInfo.customerName || '-')), + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '付款方式'), React.createElement('div', { style: valueStyle }, projectInfo.paymentMethod || '-')), + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '付款周期'), React.createElement('div', { style: valueStyle }, projectInfo.paymentCycle || '-')), + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '合同生效时间'), React.createElement('div', { style: valueStyle }, projectInfo.contractStart || '-')), + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '合同结束时间'), React.createElement('div', { style: valueStyle }, projectInfo.contractEnd || '-')), + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '业务部门'), React.createElement('div', { style: valueStyle }, projectInfo.businessDept || '-')), + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '业务负责人'), React.createElement('div', { style: valueStyle }, projectInfo.businessPerson || '-')) + ) : null + ), + React.createElement(Card, { + title: '提车应收款信息', + style: cardStyle, + extra: React.createElement(Button, { type: 'link', size: 'small', onClick: function () { receivableCardExpanded[1](!receivableCardExpanded[0]); } }, receivableCardExpanded[0] ? '收起' : '展开') + }, + receivableCardExpanded[0] ? React.createElement(React.Fragment, null, + React.createElement('div', { style: { marginBottom: 16, display: 'flex', gap: 24, alignItems: 'center' } }, + React.createElement(Popover, { content: receivablePopoverContent, title: '应收款明细', trigger: 'click' }, + React.createElement('span', { style: { cursor: 'pointer' } }, '应收款总额:', React.createElement('span', { style: highlightStyle }, receivableTotal, ' 元')) + ), + React.createElement(Popover, { content: actualPopoverContent, title: '实收款明细', trigger: 'click' }, + React.createElement('span', { style: { cursor: 'pointer' } }, '实收款总额:', React.createElement('span', { style: highlightStyle }, actualTotal, ' 元')) + ) + ), + React.createElement('div', { style: { overflowX: 'auto', marginBottom: 16 } }, + React.createElement('table', { style: { width: '100%', minWidth: 1500, borderCollapse: 'collapse', fontSize: 13 } }, + React.createElement('thead', null, + React.createElement('tr', { style: { backgroundColor: '#fafafa' } }, + React.createElement('th', { style: Object.assign({}, thBase, { width: 50 }) }, '序号'), + React.createElement('th', { style: Object.assign({}, thBase, { width: 80 }) }, '品牌'), + React.createElement('th', { style: Object.assign({}, thBase, { width: 90 }) }, '型号'), + React.createElement('th', { style: Object.assign({}, thBase, { width: 100 }) }, '车牌号'), + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'right', width: 120 }) }, '应收车辆月租金'), + React.createElement('th', { style: Object.assign({}, thBase, { width: 130 }) }, '实收车辆月租金'), + React.createElement('th', { style: Object.assign({}, thBase, { width: 130 }) }, '车辆租金备注'), + React.createElement('th', { style: Object.assign({}, thBase, { width: 100 }) }, '减免金额'), + React.createElement('th', { style: Object.assign({}, thBase, { width: 130 }) }, '减免金额备注'), + React.createElement('th', { style: Object.assign({}, thBase, { width: 130 }) }, '减免证明'), + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'right', width: 110 }) }, '应收车辆保证金'), + React.createElement('th', { style: Object.assign({}, thBase, { width: 90 }) }, '服务费项目'), + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'right', width: 90 }) }, '应收服务费'), + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'right', width: 90 }) }, '实收服务费') + ) + ), + React.createElement('tbody', null, + vehicles.map(function (row) { + var servicePopover = React.createElement('div', { style: { padding: 8, minWidth: 360 } }, + React.createElement('div', { style: { fontWeight: 600, marginBottom: 8 } }, '服务费项目'), + React.createElement('table', { style: { width: '100%', borderCollapse: 'collapse', fontSize: 12 } }, + React.createElement('thead', null, + React.createElement('tr', null, + React.createElement('th', { style: { textAlign: 'left', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '服务项目'), + React.createElement('th', { style: { textAlign: 'right', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '应收费用'), + React.createElement('th', { style: { textAlign: 'right', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '实收费用'), + React.createElement('th', { style: { textAlign: 'right', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '减免费用'), + React.createElement('th', { style: { textAlign: 'left', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '备注') + ) + ), + React.createElement('tbody', null, + (row.serviceItems || []).map(function (s, si) { + return React.createElement('tr', { key: si }, + React.createElement('td', { style: { padding: '6px 8px' } }, s.name), + React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, (s.receivable != null ? s.receivable : '') + ' 元'), + React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, (s.actual != null ? s.actual : '') + ' 元'), + React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, (s.discount != null ? s.discount : '') + ' 元'), + React.createElement('td', { style: { padding: '6px 8px' } }, s.remark || '-') + ); + }) + ) + ) + ); + return React.createElement('tr', { key: row.key }, + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0' } }, row.index), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0' } }, row.brand), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0' } }, row.model), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0' } }, row.plateNo || '-'), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0', textAlign: 'right' } }, (row.receivableRent || 0) + ' 元'), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0' } }, (row.actualRent || '') + ' 元'), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0', maxWidth: 130 } }, ellipsisCell(row.rentRemark, 10)), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0' } }, (row.discountAmount || '0.00') + ' 元'), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0', maxWidth: 130 } }, ellipsisCell(row.discountRemark, 10)), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0' } }, (row.discountProof && row.discountProof.length) ? row.discountProof.map(function (p, i) { + return React.createElement('div', { key: i, style: { fontSize: 12, marginBottom: 2 } }, + React.createElement(Button, { type: 'link', size: 'small', style: { padding: 0, height: 'auto', fontSize: 12 }, onClick: function () { setProofPreviewFile(p); setProofPreviewVisible(true); } }, p.name || '附件') + ); + }) : '-'), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0', textAlign: 'right' } }, (row.receivableDeposit || 0) + ' 元'), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0' } }, + React.createElement(Popover, { content: servicePopover, title: null, trigger: 'click' }, + React.createElement(Button, { type: 'link', size: 'small' }, '管理') + ) + ), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0', textAlign: 'right' } }, (row.receivableService || 0) + ' 元'), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0', textAlign: 'right' } }, (row.actualService || '0.00') + ' 元') + ); + }) + ), + React.createElement('tfoot', null, + React.createElement('tr', { style: { backgroundColor: '#fafafa', fontWeight: 500 } }, + React.createElement('td', { style: { padding: '10px 12px', border: '1px solid #f0f0f0' }, colSpan: 4 }, '总计'), + React.createElement('td', { style: { padding: '10px 12px', border: '1px solid #f0f0f0', textAlign: 'right' } }, totals.receivableRent + ' 元'), + React.createElement('td', { style: { padding: '10px 12px', border: '1px solid #f0f0f0', textAlign: 'right' } }, totals.actualRent + ' 元'), + React.createElement('td', { style: { padding: '10px 12px', border: '1px solid #f0f0f0' } }), + React.createElement('td', { style: { padding: '10px 12px', border: '1px solid #f0f0f0', textAlign: 'right' } }, totals.discountTotal + ' 元'), + React.createElement('td', { style: { padding: '10px 12px', border: '1px solid #f0f0f0' } }), + React.createElement('td', { style: { padding: '10px 12px', border: '1px solid #f0f0f0' } }), + React.createElement('td', { style: { padding: '10px 12px', border: '1px solid #f0f0f0', textAlign: 'right' } }, totals.receivableDeposit + ' 元'), + React.createElement('td', { style: { padding: '10px 12px', border: '1px solid #f0f0f0' } }), + React.createElement('td', { style: { padding: '10px 12px', border: '1px solid #f0f0f0', textAlign: 'right' } }, totals.receivableService + ' 元'), + React.createElement('td', { style: { padding: '10px 12px', border: '1px solid #f0f0f0', textAlign: 'right' } }, totals.actualService + ' 元') + ) + ) + ) + ), + hasHydrogenPrepay ? React.createElement('div', { style: { marginBottom: 16 } }, + React.createElement('div', { style: { fontSize: 14, fontWeight: 500, marginBottom: 8 } }, '氢费预付款情况'), + React.createElement('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', gap: 16, maxWidth: 800 } }, + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '氢费预付款应收金额'), React.createElement('div', { style: valueStyle }, hydrogen.receivable + ' 元')), + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '氢费预付款实收金额'), React.createElement('div', { style: valueStyle }, hydrogen.actual + ' 元')), + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '减免金额'), React.createElement('div', { style: valueStyle }, hydrogen.discount + ' 元')), + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '减免金额备注'), React.createElement('div', { style: valueStyle }, ellipsisCell(hydrogen.discountRemark, 12))) + ) + ) : null, + React.createElement('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px 24px', marginTop: 16 } }, + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '开票方式'), React.createElement('div', { style: valueStyle }, invoiceMethod)), + React.createElement('div', { style: Object.assign({}, formItemStyle, { gridColumn: '1 / -1' }) }, React.createElement('div', { style: labelStyle }, '开票备注'), React.createElement('div', { style: valueStyle }, invoiceRemark || '-')) + ) + ) : null + ), + React.createElement(Card, { + title: '审核情况', + style: cardStyle, + extra: React.createElement(Button, { type: 'link', size: 'small', onClick: function () { approveCardExpanded[1](!approveCardExpanded[0]); } }, approveCardExpanded[0] ? '收起' : '展开') + }, + approveCardExpanded[0] ? React.createElement(React.Fragment, null, + React.createElement(Steps, { + direction: 'vertical', + current: 1, + items: approvalSteps.map(function (s, i) { + var statusText = s.status === 'finish' ? '审批通过' : s.status === 'process' ? '待审批' : '待审批'; + var desc = React.createElement('div', { style: { fontSize: 13, color: 'rgba(0,0,0,0.65)', marginTop: 4 } }, + React.createElement('div', null, '审批状态:', statusText), + React.createElement('div', null, '审批人:', s.person || '-'), + s.time ? React.createElement('div', null, '审批时间:', s.time) : null + ); + return { + title: s.title, + description: desc, + status: s.status === 'finish' ? 'finish' : s.status === 'process' ? 'process' : 'wait' + }; + }) + }), + React.createElement('div', { style: { marginTop: 24, marginBottom: 16 } }, + React.createElement('div', { style: labelStyle }, '审批说明'), + React.createElement(Input.TextArea, { + value: approveRemark[0], + onChange: function (e) { setApproveRemark(e.target.value); }, + placeholder: '非必填,可输入审批说明', + rows: 3, + style: { width: '100%', maxWidth: 560 } }) + ), + React.createElement('div', { style: { display: 'flex', gap: 8 } }, + React.createElement(Button, { type: 'primary', onClick: handlePass }, '审批通过'), + React.createElement(Button, { danger: true, onClick: handleRejectClick }, '驳回'), + React.createElement(Button, { onClick: handleCancel }, '取消') + ) + ) : null + ), + React.createElement(Modal, { + title: '需求说明', + open: requirementModalVisible[0], + onCancel: function () { setRequirementModalVisible(false); }, + footer: React.createElement(Button, { onClick: function () { setRequirementModalVisible(false); } }, '关闭'), + width: 720 + }, + React.createElement('div', { style: { padding: '8px 0', whiteSpace: 'pre-wrap', fontSize: 13, lineHeight: 1.6, maxHeight: '70vh', overflow: 'auto' } }, requirementContent) + ), + React.createElement(Modal, { + title: '附件预览', + open: proofPreviewVisible[0], + onCancel: function () { setProofPreviewVisible(false); setProofPreviewFile(null); }, + footer: React.createElement(Button, { onClick: function () { setProofPreviewVisible(false); setProofPreviewFile(null); } }, '关闭'), + width: 640 + }, + React.createElement('div', { style: { padding: '16px 0' } }, + React.createElement('div', { style: { marginBottom: 12, fontSize: 14, color: 'rgba(0,0,0,0.65)' } }, '文件:', proofPreviewFile[0] ? (proofPreviewFile[0].name || '附件') : ''), + React.createElement('div', { style: { minHeight: 360, background: '#fafafa', borderRadius: 4, display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'rgba(0,0,0,0.45)', fontSize: 14 } }, '此处可预览附件内容,支持 jpg、png、pdf 等格式') + ) + ), + React.createElement(Modal, { + title: '确认驳回', + open: rejectModalVisible[0], + onCancel: function () { setRejectModalVisible(false); setRejectRemark(''); }, + onOk: handleRejectConfirm, + okText: '确认驳回' + }, + React.createElement(React.Fragment, null, + React.createElement('p', { style: { marginBottom: 12 } }, '驳回后进行二次确认,驳回成功后该任务在列表中显示为审核驳回,发起人可重新编辑。'), + React.createElement('div', { style: { marginBottom: 8 } }, '驳回原因:'), + React.createElement(Input.TextArea, { + value: rejectRemark[0], + onChange: function (e) { setRejectRemark(e.target.value); }, + placeholder: '请填写驳回原因', + rows: 4, + style: { width: '100%' } }) + ) + ) + ); +}; + +if (typeof module !== 'undefined' && module.exports) module.exports = Component; diff --git a/web端/财务管理/提车应收款-开票信息.jsx b/web端/财务管理/提车应收款-开票信息.jsx new file mode 100644 index 0000000..c6ce7a0 --- /dev/null +++ b/web端/财务管理/提车应收款-开票信息.jsx @@ -0,0 +1,590 @@ +// 【重要】必须使用 const Component 作为组件变量名 +// 财务管理 - 提车应收款 - 开票信息(2026年3月5日版本) + +const Component = function () { + var useState = React.useState; + var useCallback = React.useCallback; + var useMemo = React.useMemo; + var useRef = React.useRef; + var useEffect = React.useEffect; + + var antd = window.antd; + var Breadcrumb = antd.Breadcrumb; + var Card = antd.Card; + var Input = antd.Input; + var Button = antd.Button; + var Popover = antd.Popover; + var Tooltip = antd.Tooltip; + var Modal = antd.Modal; + var message = antd.message; + var Steps = antd.Steps; + var DatePicker = antd.DatePicker; + + var dayjs = window.dayjs || null; + function nowStr() { + return dayjs && dayjs().format ? dayjs().format('YYYY-MM-DD HH:mm') : (new Date().getFullYear() + '-' + String(new Date().getMonth() + 1).padStart(2, '0') + '-' + String(new Date().getDate()).padStart(2, '0') + ' ' + String(new Date().getHours()).padStart(2, '0') + ':' + String(new Date().getMinutes()).padStart(2, '0')); + } + + var projectCardExpanded = useState(true); + var receivableCardExpanded = useState(true); + var invoiceCardExpanded = useState(true); + var approveCardExpanded = useState(true); + var submitConfirmVisible = useState(false); + var cancelConfirmVisible = useState(false); + var requirementModalVisible = useState(false); + var proofPreviewVisible = useState(false); + var proofPreviewFile = useState(null); + var invoicePreviewVisible = useState(false); + var invoicePreviewFile = useState(null); + + // 开票记录列表:历史记录 submitted=true 不可删,新增行 submitted=false 可删 + var invoiceListState = useState([ + { id: 'hist1', arrivalTime: '2026-03-01 10:00', arrivalAmount: '50000.00', invoiceTime: '2026-03-02 14:30', invoiceFiles: [{ name: '发票1.pdf' }], remark: '首笔到账', invoicePerson: '李财务', submitted: true }, + { id: 'new1', arrivalTime: '', arrivalAmount: '', invoiceTime: nowStr(), invoiceFiles: [], remark: '', invoicePerson: '-', submitted: false } + ]); + var invoiceList = invoiceListState[0]; + var setInvoiceList = invoiceListState[1]; + var nextIdRef = useRef(2); + + var projectInfo = useMemo(function () { + return { + contractCode: 'HT-ZL-2025-001', + contractType: '正式合同', + projectName: '嘉兴氢能示范项目', + customerName: '嘉兴某某物流有限公司', + paymentMethod: '预付', + paymentCycle: '6个月', + contractStart: '2025-01-15', + contractEnd: '2026-01-14', + businessDept: '业务1部', + businessPerson: '张经理' + }; + }, []); + + // 车辆应收款明细:全选默认为取消勾选;已完成提车应收款的不可勾选 + var vehicleRows = useState([ + { key: 'v1', index: 1, brand: '东风', model: 'DFH1180', plateNo: '浙A12345', receivableRent: 30000, actualRent: '30000.00', rentRemark: '', receivableDeposit: 10000, serviceItems: [{ name: '代处理费用', receivable: 200, actual: '200.00', discount: '0.00', remark: '' }, { name: '保险上浮', receivable: 500, actual: '500.00', discount: '0.00', remark: '' }], receivableService: 700, actualService: '700.00', discountAmount: '0.00', discountRemark: '', discountProof: [], selected: false, receivableCompleted: true }, + { key: 'v2', index: 2, brand: '福田', model: 'BJ1180', plateNo: '浙A23456', receivableRent: 27000, actualRent: '27000.00', rentRemark: '', receivableDeposit: 8000, serviceItems: [{ name: '保养费用', receivable: 300, actual: '300.00', discount: '0.00', remark: '' }], receivableService: 300, actualService: '300.00', discountAmount: '0.00', discountRemark: '', discountProof: [], selected: false, receivableCompleted: true }, + { key: 'v3', index: 3, brand: '重汽', model: 'ZZ1187', plateNo: '浙A34567', receivableRent: 31200, actualRent: '31200.00', rentRemark: '', receivableDeposit: 10000, serviceItems: [{ name: '代处理费用', receivable: 180, actual: '180.00', discount: '0.00', remark: '' }, { name: '上牌服务', receivable: 400, actual: '400.00', discount: '0.00', remark: '' }], receivableService: 580, actualService: '580.00', discountAmount: '0.00', discountRemark: '', discountProof: [{ name: '减免说明.pdf' }], selected: true, receivableCompleted: false }, + { key: 'v4', index: 4, brand: '陕汽', model: 'SX1313', plateNo: '', receivableRent: 28800, actualRent: '28800.00', rentRemark: '', receivableDeposit: 9000, serviceItems: [{ name: '保险上浮', receivable: 350, actual: '350.00', discount: '0.00', remark: '' }], receivableService: 350, actualService: '350.00', discountAmount: '0.00', discountRemark: '', discountProof: [], selected: true, receivableCompleted: false }, + { key: 'v5', index: 5, brand: '解放', model: 'J6P', plateNo: '浙A45678', receivableRent: 33000, actualRent: '33000.00', rentRemark: '', receivableDeposit: 11000, serviceItems: [{ name: '代处理费用', receivable: 220, actual: '220.00', discount: '0.00', remark: '' }, { name: '保养费用', receivable: 280, actual: '280.00', discount: '0.00', remark: '' }], receivableService: 500, actualService: '500.00', discountAmount: '0.00', discountRemark: '', discountProof: [], selected: true, receivableCompleted: false } + ]); + var vehicles = vehicleRows[0]; + var setVehicles = vehicleRows[1]; + + var hasHydrogenPrepay = true; + // 氢费预付款(仅一条,提交审核时的数据,只读展示) + var hydrogen = useMemo(function () { + return { receivable: '3580.00', actual: '3580.00', discount: '0.00', discountRemark: '氢费预付款减免说明,与客户签订补充协议' }; + }, []); + var invoiceMethod = '先开票后付款'; + var invoiceRemark = '开票项目:租赁费,税率 13%。请开具增值税专用发票。'; + + var updateVehicle = useCallback(function (key, field, value) { + setVehicles(function (prev) { + return prev.map(function (r) { + if (r.key !== key) return r; + var next = Object.assign({}, r); + next[field] = value; + if (field === 'serviceItems') { + var total = 0; + (value || []).forEach(function (s) { total += parseFloat(s.actual) || 0; }); + next.actualService = total.toFixed(2); + } + return next; + }); + }); + }, []); + + var updateServiceItem = useCallback(function (vehicleKey, itemIndex, field, value) { + setVehicles(function (prev) { + return prev.map(function (r) { + if (r.key !== vehicleKey) return r; + var items = (r.serviceItems || []).slice(); + if (!items[itemIndex]) return r; + items[itemIndex] = Object.assign({}, items[itemIndex], { [field]: value }); + var total = 0; + items.forEach(function (s) { total += parseFloat(s.actual) || 0; }); + return Object.assign({}, r, { serviceItems: items, actualService: total.toFixed(2) }); + }); + }); + }, []); + + var totals = useMemo(function () { + var selected = vehicles.filter(function (v) { return v.selected; }); + var receivableRent = 0, actualRent = 0, receivableDeposit = 0, receivableService = 0, actualService = 0, discountTotal = 0; + selected.forEach(function (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; + }); + 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) + }; + }, [vehicles]); + + var selectedVehicles = vehicles.filter(function (v) { return v.selected; }); + + var receivableTotal = (parseFloat(totals.receivableRent) + parseFloat(totals.receivableDeposit) + parseFloat(totals.receivableService)).toFixed(2); + var actualTotal = (parseFloat(totals.actualRent) + parseFloat(totals.receivableDeposit) + parseFloat(totals.actualService) - parseFloat(totals.discountTotal)).toFixed(2); + + var receivablePopoverContent = useMemo(function () { + return React.createElement('div', { style: { padding: 8, minWidth: 220 } }, + React.createElement('table', { style: { width: '100%', borderCollapse: 'collapse', fontSize: 13 } }, + React.createElement('thead', null, + React.createElement('tr', null, + React.createElement('th', { style: { textAlign: 'left', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '项目'), + React.createElement('th', { style: { textAlign: 'right', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '金额') + ) + ), + React.createElement('tbody', null, + React.createElement('tr', null, React.createElement('td', { style: { padding: '6px 8px' } }, '总计应收车辆月租金'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.receivableRent + ' 元')), + React.createElement('tr', null, React.createElement('td', { style: { padding: '6px 8px' } }, '总计应收车辆保证金'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.receivableDeposit + ' 元')), + React.createElement('tr', null, React.createElement('td', { style: { padding: '6px 8px' } }, '总计应收服务费'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.receivableService + ' 元')) + ) + ) + ); + }, [totals]); + + var actualPopoverContent = useMemo(function () { + return React.createElement('div', { style: { padding: 8, minWidth: 220 } }, + React.createElement('table', { style: { width: '100%', borderCollapse: 'collapse', fontSize: 13 } }, + React.createElement('thead', null, + React.createElement('tr', null, + React.createElement('th', { style: { textAlign: 'left', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '项目'), + React.createElement('th', { style: { textAlign: 'right', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '金额') + ) + ), + React.createElement('tbody', null, + React.createElement('tr', null, React.createElement('td', { style: { padding: '6px 8px' } }, '总计实收车辆月租金'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.actualRent + ' 元')), + React.createElement('tr', null, React.createElement('td', { style: { padding: '6px 8px' } }, '总计应收车辆保证金'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.receivableDeposit + ' 元')), + React.createElement('tr', null, React.createElement('td', { style: { padding: '6px 8px' } }, '总计实收服务费'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.actualService + ' 元')), + React.createElement('tr', null, React.createElement('td', { style: { padding: '6px 8px' } }, '总计减免金额'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.discountTotal + ' 元')) + ) + ) + ); + }, [totals]); + + var invoiceInfo = useMemo(function () { + return { + customerName: '嘉兴某某物流有限公司', + taxId: '91330400MA2XXXXX1', + address: '浙江省嘉兴市南湖区科技大道1号', + phone: '0571-88888888', + account: '6222021234567890123', + bank: '中国工商银行嘉兴分行', + mailingAddress: '浙江省嘉兴市南湖区科技大道1号' + }; + }, []); + + var approvalSteps = useMemo(function () { + return [ + { title: '业务部门', status: 'finish', person: '张经理', time: '2026-03-01 10:00' }, + { title: '财务部门', status: 'finish', person: '李四', time: '2026-03-03 09:00' }, + { title: '财务负责人', status: 'finish', person: '王五', time: '2026-03-03 11:00' } + ]; + }, []); + + var unpaidAmount = useMemo(function () { + var sumArrival = invoiceList.reduce(function (s, r) { return s + (parseFloat(r.arrivalAmount) || 0); }, 0); + return (Math.max(0, parseFloat(actualTotal) - sumArrival)).toFixed(2); + }, [invoiceList, actualTotal]); + + function updateInvoiceRow(id, field, value) { + setInvoiceList(function (prev) { + return prev.map(function (r) { + if (r.id !== id) return r; + var next = Object.assign({}, r); + next[field] = value; + if (field === 'invoiceFiles') next.invoiceFiles = value || []; + return next; + }); + }); + } + + function addInvoiceRow() { + nextIdRef.current += 1; + setInvoiceList(function (prev) { + return prev.concat([{ id: 'new' + nextIdRef.current, arrivalTime: '', arrivalAmount: '', invoiceTime: nowStr(), invoiceFiles: [], remark: '', invoicePerson: '-', submitted: false }]); + }); + } + + function removeInvoiceRow(id) { + var row = invoiceList.find(function (r) { return r.id === id; }); + if (row && row.submitted) return; + setInvoiceList(function (prev) { return prev.filter(function (r) { return r.id !== id; }); }); + } + + var handleSubmit = useCallback(function () { + submitConfirmVisible[1](true); + }, []); + + var handleSubmitConfirm = useCallback(function () { + message.success('提交成功'); + submitConfirmVisible[1](false); + if (window.__receivableBack) window.__receivableBack(); else message.info('跳转至提车应收款列表页(原型)'); + }, []); + + var handleCancel = useCallback(function () { + cancelConfirmVisible[1](true); + }, []); + + var handleCancelConfirm = useCallback(function () { + cancelConfirmVisible[1](false); + if (window.__receivableBack) window.__receivableBack(); else message.info('返回提车应收款列表页(原型)'); + }, []); + + var requirementContent = '提车应收款-开票信息(2026年3月5日版本)\n一个「数字化资产ONEOS运管平台」中的「财务管理」「提车应收款」「开票信息」模块\n#面包屑:财务管理-提车应收款-开票信息;\n\n页面分为3个卡片,业务流程请参考流程图;\n1.项目信息:\n#显示项目详细信息,包括:\n1.1.合同编码:显示该租赁合同对应合同编码;\n1.2.合同类型:显示该租赁合同对应合同编码;\n1.3.项目名称:显示该租赁合同对应项目名称;\n1.4.客户名称:显示该租赁合同对应客户名称;\n1.5.付款方式:显示该租赁合同对应付款方式,分为:预付、后付两种,根据实际合同反写;\n1.6.付款周期:显示该租赁合同对应付款周期,格式为:x个月;\n1.7.合同生效时间:显示该租赁合同对应合同生效时间;\n1.8.合同结束时间:显示该租赁合同对应合同结束时间;\n1.9.业务部门:显示该租赁合同对应业务部门名称;\n1.10.业务负责人:显示该租赁合同对应业务负责人;\n\n2.提车应收款信息:\n#上方显示应收款总额、实收款总额;\n2.1.应收款总额:显示提交审核时的应收款总额,格式为:xx.xx元,点击金额弹出气泡卡片,卡片中显示列表,字段为:\n 2.1.1.项目:包括总计应收车辆月租金、总计应收车辆保证金、总计应收服务费三项;\n 2.1.2.金额:显示该项目对应应收金额;\n2.2.实收款总额:显示提交审核时的实收款总额,格式为:xx.xx元,点击金额弹出气泡卡片,卡片中显示列表,字段为:\n 2.2.1.项目:包括总计实收车辆月租金、总计应收车辆保证金、总计实收服务费、总计减免金额四项;\n 2.2.2.金额:显示该项目对应实收金额;\n#中间为车辆应收款明细,以列表展示提车应收款已选中所有车辆明细,包括以下字段:\n2.1.序号:根据租赁合同中车辆对应序号顺序展示;\n2.2.品牌:显示租赁合同中车辆对应品牌;\n2.3.型号:显示租赁合同中车辆对应型号;\n2.4.车牌号:显示租赁合同中车辆对应车牌号,如果车牌号为空则显示为-(后续如果交车成功,则车牌号会反显在该处);\n2.5.应收车辆月租金:根据租赁合同付款周期自动计算,例如:付款周期为6个月,则这里显示金额为:车辆月租金*6;\n2.6.实收车辆月租金:显示提交审核时的实收车辆月租金,格式为:xx.xx元;\n2.7.车辆租金备注:显示提交审核时的车辆租金备注,超长则显示...,悬浮时显示全部详细内容;\n2.8.减免金额:显示提交审核时的减免金额,格式为:xx.xx元;\n2.9.减免金额备注:显示提交审核时的减免金额备注,超长则显示...,悬浮时显示全部详细内容;\n2.10.减免证明:显示所有附件,可点击预览;\n2.11.应收车辆保证金:显示租赁合同中当前车辆保证金金额,格式为:xx.xx元;\n2.12.服务费项目:点击管理,弹出气泡卡片,气泡卡片标题为服务费项目,下方为列表,显示服务项目、应收费用、实收费用、备注;\n 2.12.1.服务项目:显示租赁合同中所有服务项目名称;\n 2.12.2.应收费用:显示租赁合同中所有服务项目对应费用;\n 2.12.3.实收费用:显示提交审核时的实收费用金额,格式为:xx.xx元;\n 2.12.4.减免费用:显示提交审核时的减免费用金额,格式为:xx.xx元;\n 2.12.4.备注:显示提交审核时的服务费减免备注,超长则显示...,悬浮时显示全部详细内容;\n2.13.应收服务费:显示当前车辆所有服务费应收费用总和,格式为:xx.xx元;\n2.14.实收服务费:显示当前车辆所有服务费实收费用总和,格式为:xx.xx元;\n2.15.列表下方对应字段下方显示总计数据,包括:总计应收车辆月租金、总计实收车辆月租金、总计应收车辆保证金、总计应收服务费、总计实收服务费、总计减免金额;\n列表不支持分页功能;\n#下方为氢费预付款情况,以列表展示租赁合同中所有氢费预付款明细,包括以下字段,如租赁合同氢费非预付款模式,则不显示该部分内容:\n2.19.氢费预付款应收金额:显示租赁合同中氢费预付款金额,格式为:xx.xx元,支持2位小数;\n2.20.氢费预付款实收金额:显示提交审核时的氢费预付款实收金额,格式为:xx.xx元;\n2.21.减免金额:显示提交审核时的减免金额,格式为:xx.xx元;\n2.22.减免金额备注:显示提交审核时的氢费预付款减免备注,超长则显示...,悬浮时显示全部详细内容;\n#最底部为开票方式、开票备注;\n2.23.开票方式:显示提交审核时的开票方式;\n2.24.开票备注:显示提交审核时的开票备注;\n\n3.开票信息:\n#上方显示该租赁合同客户信息(如果该合同转为三方合同,则未开票部分开票信息修改为丙方开票信息),包括:\n3.1.客户名称:显示租赁合同客户名称;\n3.2.纳税人识别号:显示该客户对应纳税人识别号;\n3.3.地址:显示该客户对应地址;\n3.4.电话:显示该客户对应电话;\n3.5.账户:显示该客户对应账户;\n3.6.开户行:显示该客户对应开户行;\n3.7.邮寄地址:显示该客户对应邮寄地址;\n#下方显示列表,包括以下字段,列表上方显示未付金额,格式为xx.xx元(计算方式为:实收款总额-(所有到账金额记录总和)),列表显示过往提交的历史记录,同时默认一行可编辑,支持继续新增一行和删除行操作(过往提交的历史记录无法删除):\n3.8.到账时间:时间选择器,格式为:YYYY-MM-DD HH:MM,新增未提交的记录支持删除,但之前已完成提交的记录无法删除;\n3.9.到账金额:输入框,支持2位小数,格式为:xx.xx元,新增未提交的记录支持删除,但之前已完成提交的记录无法删除;;\n3.10.开票时间:时间选择器,格式为:YYYY-MM-DD HH:MM,默认为打开页面的时间,新增未提交的记录支持删除,但之前已完成提交的记录无法删除;\n3.11.发票附件:附件上传按钮,支持多附件上传,上传后显示附件名称,新增未提交的记录支持删除,但之前已完成提交的记录无法删除;\n3.12.备注:输入框,新增未提交的记录支持删除,但之前已完成提交的记录无法删除;;\n3.13.开票人:显示上传发票的操作用户姓名;\n3.14.支持新增一行、删除一行(新增未提交的记录可以删除,已完成提交的历史记录不能删除);\n\n4.审批情况:\n#上方为纵向步骤条,显示所有审批步骤\n4.1.步骤中显示部门名称,部门名称后方显示审批状态、审批人、审批时间,所有审批记录已完成;\n\n5.底部为提交,取消;\n5.1.提交:点击提交,进行二次确认,文案为:请仔细核对提车首付款到账和开票信息,提交后无法修改;\n5.2.取消:点击取消,进行二次确认,文案为:取消将会丢失所有已填写数据,是否确认,点击确认则跳转至提车应收款列表页;'; + + var layoutStyle = { padding: '16px 24px', background: '#f5f5f5', minHeight: '100vh' }; + var cardStyle = { marginBottom: 16 }; + var labelStyle = { marginBottom: 6, fontSize: 14, color: 'rgba(0,0,0,0.65)' }; + var requiredStar = { color: '#ff4d4f', marginRight: 2 }; + var formRowStyle = { display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '16px 24px', marginBottom: 16 }; + var formItemStyle = { marginBottom: 12 }; + var highlightStyle = { color: '#1890ff', fontWeight: 600, cursor: 'pointer' }; + var valueStyle = { color: 'rgba(0,0,0,0.85)', fontSize: 14, lineHeight: '22px', minHeight: 22 }; + var thBase = { padding: '10px 12px', border: '1px solid #f0f0f0', whiteSpace: 'nowrap' }; + + return React.createElement('div', { style: layoutStyle }, + React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 } }, + React.createElement(Breadcrumb, { + items: [ + { title: '财务管理' }, + { title: '提车应收款' }, + { title: '开票信息' } + ] + }), + React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { requirementModalVisible[1](true); } }, '查看需求说明') + ), + React.createElement(Card, { + title: '项目信息', + style: cardStyle, + extra: React.createElement(Button, { type: 'link', size: 'small', onClick: function () { projectCardExpanded[1](!projectCardExpanded[0]); } }, projectCardExpanded[0] ? '收起' : '展开') + }, + projectCardExpanded[0] ? React.createElement('div', { style: formRowStyle }, + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '合同编码'), React.createElement('div', { style: valueStyle }, projectInfo.contractCode || '-')), + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '合同类型'), React.createElement('div', { style: valueStyle }, projectInfo.contractType || '-')), + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '项目名称'), React.createElement('div', { style: valueStyle }, projectInfo.projectName || '-')), + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '客户名称'), React.createElement('div', { style: valueStyle }, projectInfo.customerName || '-')), + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '付款方式'), React.createElement('div', { style: valueStyle }, projectInfo.paymentMethod || '-')), + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '付款周期'), React.createElement('div', { style: valueStyle }, projectInfo.paymentCycle || '-')), + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '合同生效时间'), React.createElement('div', { style: valueStyle }, projectInfo.contractStart || '-')), + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '合同结束时间'), React.createElement('div', { style: valueStyle }, projectInfo.contractEnd || '-')), + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '业务部门'), React.createElement('div', { style: valueStyle }, projectInfo.businessDept || '-')), + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '业务负责人'), React.createElement('div', { style: valueStyle }, projectInfo.businessPerson || '-')) + ) : null + ), + React.createElement(Card, { + title: '提车应收款信息', + style: cardStyle, + extra: React.createElement(Button, { type: 'link', size: 'small', onClick: function () { receivableCardExpanded[1](!receivableCardExpanded[0]); } }, receivableCardExpanded[0] ? '收起' : '展开') + }, + receivableCardExpanded[0] ? React.createElement(React.Fragment, null, + React.createElement('div', { style: { marginBottom: 16, display: 'flex', gap: 24, alignItems: 'center' } }, + React.createElement(Popover, { content: receivablePopoverContent, title: '应收款明细', trigger: 'click' }, + React.createElement('span', { style: { cursor: 'pointer' } }, '应收款总额:', React.createElement('span', { style: highlightStyle }, receivableTotal, ' 元')) + ), + React.createElement(Popover, { content: actualPopoverContent, title: '实收款明细', trigger: 'click' }, + React.createElement('span', { style: { cursor: 'pointer' } }, '实收款总额:', React.createElement('span', { style: highlightStyle }, actualTotal, ' 元')) + ) + ), + React.createElement('div', { style: { overflowX: 'auto', marginBottom: 16, overflowY: 'visible' } }, + React.createElement('table', { style: { width: '100%', minWidth: 1600, borderCollapse: 'collapse', fontSize: 13, tableLayout: 'fixed' } }, + React.createElement('thead', null, + React.createElement('tr', { style: { backgroundColor: '#fafafa' } }, + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'left', width: 50 }) }, '序号'), + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'left', width: 80 }) }, '品牌'), + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'left', width: 90 }) }, '型号'), + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'left', width: 100 }) }, '车牌号'), + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'right', width: 120 }) }, '应收车辆月租金'), + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'left', width: 130 }) }, React.createElement('span', null, React.createElement('span', { style: requiredStar }, '*'), '实收车辆月租金')), + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'left', width: 130 }) }, '车辆租金备注'), + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'left', width: 130 }) }, '减免金额'), + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'left', width: 130 }) }, '减免金额备注'), + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'left', width: 130 }) }, '减免证明'), + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'right', width: 110 }) }, '应收车辆保证金'), + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'left', width: 90 }) }, '服务费项目'), + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'right', width: 90 }) }, '应收服务费'), + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'right', width: 130 }) }, '实收服务费') + ) + ), + React.createElement('tbody', null, + selectedVehicles.map(function (row) { + var disabled = row.receivableCompleted; + var servicePopover = React.createElement('div', { style: { padding: 8, minWidth: 360 } }, + React.createElement('div', { style: { fontWeight: 600, marginBottom: 8 } }, '服务费项目'), + React.createElement('table', { style: { width: '100%', borderCollapse: 'collapse', fontSize: 12 } }, + React.createElement('thead', null, + React.createElement('tr', null, + React.createElement('th', { style: { textAlign: 'left', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '服务项目'), + React.createElement('th', { style: { textAlign: 'right', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '应收费用'), + React.createElement('th', { style: { textAlign: 'right', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '实收费用'), + React.createElement('th', { style: { textAlign: 'right', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '减免费用'), + React.createElement('th', { style: { textAlign: 'left', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '备注') + ) + ), + React.createElement('tbody', null, + (row.serviceItems || []).map(function (s, si) { + return React.createElement('tr', { key: si }, + React.createElement('td', { style: { padding: '6px 8px' } }, s.name), + React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, (s.receivable != null ? s.receivable : '') + ' 元'), + React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, (s.actual || '') + ' 元'), + React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, (s.discount || '0.00') + ' 元'), + React.createElement('td', { style: { padding: '6px 8px' } }, s.remark || '-') + ); + }) + ) + ) + ); + return React.createElement('tr', { key: row.key }, + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0' } }, row.index), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0' } }, row.brand), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0' } }, row.model), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0' } }, row.plateNo || '-'), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0', textAlign: 'right' } }, (row.receivableRent || 0) + ' 元'), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0', width: 130, textAlign: 'right' } }, (row.actualRent || '0.00') + ' 元'), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0', width: 130 } }, row.rentRemark || '-'), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0', width: 130, textAlign: 'right' } }, (row.discountAmount || '0.00') + ' 元'), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0', width: 130 } }, row.discountRemark || '-'), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0', width: 130 } }, + (row.discountProof && row.discountProof.length) + ? React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 2, fontSize: 12 } }, + row.discountProof.map(function (p, pidx) { + return React.createElement(Button, { key: pidx, type: 'link', size: 'small', style: { padding: 0, height: 'auto', fontSize: 12, textAlign: 'left' }, onClick: function () { proofPreviewFile[1](p); proofPreviewVisible[1](true); } }, p.name || '附件'); + }) + ) + : '-' + ), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0', textAlign: 'right' } }, (row.receivableDeposit || 0) + ' 元'), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0' } }, + React.createElement(Popover, { content: servicePopover, title: null, trigger: 'click' }, + React.createElement(Button, { type: 'link', size: 'small' }, '管理') + ) + ), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0', textAlign: 'right' } }, (row.receivableService || 0) + ' 元'), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0', textAlign: 'right', width: 130 } }, (row.actualService || '0.00') + ' 元') + ); + }) + ), + React.createElement('tfoot', null, + React.createElement('tr', { style: { backgroundColor: '#fafafa', fontWeight: 500 } }, + React.createElement('td', { style: { padding: '10px 12px', border: '1px solid #f0f0f0', fontWeight: 600 } }, '总计'), + React.createElement('td', { style: { padding: '10px 12px', border: '1px solid #f0f0f0' } }), + React.createElement('td', { style: { padding: '10px 12px', border: '1px solid #f0f0f0' } }), + React.createElement('td', { style: { padding: '10px 12px', border: '1px solid #f0f0f0' } }), + React.createElement('td', { style: { padding: '10px 12px', border: '1px solid #f0f0f0', textAlign: 'right' } }, totals.receivableRent + ' 元'), + React.createElement('td', { style: { padding: '10px 12px', border: '1px solid #f0f0f0', textAlign: 'right' } }, totals.actualRent + ' 元'), + React.createElement('td', { style: { padding: '10px 12px', border: '1px solid #f0f0f0' } }), + React.createElement('td', { style: { padding: '10px 12px', border: '1px solid #f0f0f0', textAlign: 'right' } }, totals.discountTotal + ' 元'), + React.createElement('td', { style: { padding: '10px 12px', border: '1px solid #f0f0f0' } }), + React.createElement('td', { style: { padding: '10px 12px', border: '1px solid #f0f0f0' } }), + React.createElement('td', { style: { padding: '10px 12px', border: '1px solid #f0f0f0', textAlign: 'right' } }, totals.receivableDeposit + ' 元'), + React.createElement('td', { style: { padding: '10px 12px', border: '1px solid #f0f0f0' } }), + React.createElement('td', { style: { padding: '10px 12px', border: '1px solid #f0f0f0', textAlign: 'right' } }, totals.receivableService + ' 元'), + React.createElement('td', { style: { padding: '10px 12px', border: '1px solid #f0f0f0', textAlign: 'right' } }, totals.actualService + ' 元') + ) + ) + ) + ), + hasHydrogenPrepay ? React.createElement('div', { style: { marginBottom: 16 } }, + React.createElement('div', { style: { fontSize: 14, fontWeight: 500, marginBottom: 8 } }, '氢费预付款情况'), + React.createElement('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', gap: 16, maxWidth: 800 } }, + React.createElement('div', { style: formItemStyle }, + React.createElement('div', { style: labelStyle }, '氢费预付款应收金额'), + React.createElement('div', { style: valueStyle }, hydrogen.receivable + ' 元') + ), + React.createElement('div', { style: formItemStyle }, + React.createElement('div', { style: labelStyle }, '氢费预付款实收金额'), + React.createElement('div', { style: valueStyle }, hydrogen.actual + ' 元') + ), + React.createElement('div', { style: formItemStyle }, + React.createElement('div', { style: labelStyle }, '减免金额'), + React.createElement('div', { style: valueStyle }, hydrogen.discount + ' 元') + ), + React.createElement('div', { style: formItemStyle }, + React.createElement('div', { style: labelStyle }, '减免金额备注'), + React.createElement('div', { style: valueStyle }, hydrogen.discountRemark || '-') + ) + ) + ) : null, + React.createElement('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px 24px', marginTop: 16 } }, + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '开票方式'), React.createElement('div', { style: valueStyle }, invoiceMethod)), + React.createElement('div', { style: Object.assign({}, formItemStyle, { gridColumn: '1 / -1' }) }, React.createElement('div', { style: labelStyle }, '开票备注'), React.createElement('div', { style: valueStyle }, invoiceRemark || '-')) + ) + ) : null + ), + React.createElement(Card, { + title: '开票信息', + style: cardStyle, + extra: React.createElement(Button, { type: 'link', size: 'small', onClick: function () { invoiceCardExpanded[1](!invoiceCardExpanded[0]); } }, invoiceCardExpanded[0] ? '收起' : '展开') + }, + invoiceCardExpanded[0] ? React.createElement(React.Fragment, null, + React.createElement('div', { style: { fontSize: 14, fontWeight: 500, marginBottom: 12 } }, '客户信息'), + React.createElement('div', { style: formRowStyle }, + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '客户名称'), React.createElement('div', { style: valueStyle }, invoiceInfo.customerName || '-')), + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '纳税人识别号'), React.createElement('div', { style: valueStyle }, invoiceInfo.taxId || '-')), + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '地址'), React.createElement('div', { style: valueStyle }, invoiceInfo.address || '-')), + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '电话'), React.createElement('div', { style: valueStyle }, invoiceInfo.phone || '-')), + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '账户'), React.createElement('div', { style: valueStyle }, invoiceInfo.account || '-')), + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '开户行'), React.createElement('div', { style: valueStyle }, invoiceInfo.bank || '-')), + React.createElement('div', { style: Object.assign({}, formItemStyle, { gridColumn: '1 / -1' }) }, React.createElement('div', { style: labelStyle }, '邮寄地址'), React.createElement('div', { style: valueStyle }, invoiceInfo.mailingAddress || '-')) + ), + React.createElement('div', { style: { fontSize: 14, fontWeight: 500, marginBottom: 4, marginTop: 16 } }, '到账与开票记录'), + React.createElement('div', { style: { marginBottom: 8, fontSize: 14 } }, '未付金额:', React.createElement('span', { style: highlightStyle }, unpaidAmount, ' 元')), + React.createElement('table', { style: { width: '100%', borderCollapse: 'collapse', fontSize: 13 } }, + React.createElement('thead', null, + React.createElement('tr', { style: { backgroundColor: '#fafafa' } }, + React.createElement('th', { style: Object.assign({}, thBase, { width: 160 }) }, '到账时间'), + React.createElement('th', { style: Object.assign({}, thBase, { width: 120 }) }, '到账金额'), + React.createElement('th', { style: Object.assign({}, thBase, { width: 160 }) }, '开票时间'), + React.createElement('th', { style: Object.assign({}, thBase) }, '发票附件'), + React.createElement('th', { style: Object.assign({}, thBase) }, '备注'), + React.createElement('th', { style: Object.assign({}, thBase, { width: 80 }) }, '开票人'), + React.createElement('th', { style: Object.assign({}, thBase, { width: 70 }) }, '操作') + ) + ), + React.createElement('tbody', null, + invoiceList.map(function (r) { + var dateValArrival = r.arrivalTime && dayjs ? dayjs(r.arrivalTime, 'YYYY-MM-DD HH:mm') : null; + var dateValInvoice = r.invoiceTime && dayjs ? dayjs(r.invoiceTime, 'YYYY-MM-DD HH:mm') : null; + if (dateValInvoice && dateValInvoice.isValid && !dateValInvoice.isValid()) dateValInvoice = null; + if (dateValArrival && dateValArrival.isValid && !dateValArrival.isValid()) dateValArrival = null; + return React.createElement('tr', { key: r.id }, + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0' } }, + r.submitted ? (r.arrivalTime || '-') : React.createElement(DatePicker, { + showTime: true, + style: { width: '100%' }, + format: 'YYYY-MM-DD HH:mm', + placeholder: '请选择到账时间', + value: dateValArrival, + onChange: function (d, dateStr) { updateInvoiceRow(r.id, 'arrivalTime', dateStr || ''); } + }) + ), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0' } }, + r.submitted ? (r.arrivalAmount ? r.arrivalAmount + ' 元' : '-') : React.createElement(Input, { size: 'small', value: r.arrivalAmount, suffix: '元', placeholder: '0.00', onChange: function (e) { updateInvoiceRow(r.id, 'arrivalAmount', e.target.value); } }) + ), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0' } }, + r.submitted ? (r.invoiceTime || '-') : React.createElement(DatePicker, { + showTime: true, + style: { width: '100%' }, + format: 'YYYY-MM-DD HH:mm', + placeholder: '请选择开票时间', + value: dateValInvoice, + onChange: function (d, dateStr) { updateInvoiceRow(r.id, 'invoiceTime', dateStr || ''); } + }) + ), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0' } }, + r.submitted + ? ((r.invoiceFiles && r.invoiceFiles.length) ? r.invoiceFiles.map(function (f, i) { return React.createElement(Button, { key: i, type: 'link', size: 'small', style: { padding: 0, height: 'auto', fontSize: 12, marginRight: 4 }, onClick: function () { invoicePreviewFile[1](f); invoicePreviewVisible[1](true); } }, f.name || '附件'); }) : '-') + : React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 4 } }, + (r.invoiceFiles || []).map(function (f, i) { + return React.createElement('div', { key: i, style: { display: 'flex', alignItems: 'center', gap: 4, fontSize: 12 } }, + React.createElement(Button, { type: 'link', size: 'small', style: { padding: 0, flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', textAlign: 'left', height: 'auto' }, onClick: function () { invoicePreviewFile[1](f); invoicePreviewVisible[1](true); } }, f.name || '附件'), + React.createElement(Button, { type: 'link', size: 'small', danger: true, style: { padding: 0 }, onClick: function () { var next = (r.invoiceFiles || []).slice(); next.splice(i, 1); updateInvoiceRow(r.id, 'invoiceFiles', next); } }, '删除') + ); + }), + React.createElement('span', null, + React.createElement('input', { type: 'file', multiple: true, accept: '.jpg,.jpeg,.png,.pdf', style: { display: 'none' }, id: 'inv-file-' + r.id, onChange: function (e) { + var files = e.target.files; + if (!files || !files.length) return; + var next = (r.invoiceFiles || []).slice(); + for (var i = 0; i < files.length; i++) next.push({ name: files[i].name }); + updateInvoiceRow(r.id, 'invoiceFiles', next); + e.target.value = ''; + } }), + React.createElement(Button, { type: 'default', size: 'small', onClick: function () { var el = document.getElementById('inv-file-' + r.id); if (el) el.click(); } }, '附件上传') + ) + ) + ), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0' } }, + r.submitted ? (r.remark || '-') : React.createElement(Input, { size: 'small', value: r.remark, placeholder: '选填', onChange: function (e) { updateInvoiceRow(r.id, 'remark', e.target.value); } }) + ), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0' } }, r.invoicePerson || '-'), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0' } }, + r.submitted ? '-' : React.createElement(Button, { type: 'link', size: 'small', danger: true, onClick: function () { removeInvoiceRow(r.id); } }, '删除') + ) + ); + }) + ) + ), + React.createElement('div', { style: { marginTop: 8 } }, React.createElement(Button, { type: 'dashed', size: 'small', onClick: addInvoiceRow }, '新增一行')) + ) : null + ), + React.createElement(Card, { + title: '审批情况', + style: cardStyle, + extra: React.createElement(Button, { type: 'link', size: 'small', onClick: function () { approveCardExpanded[1](!approveCardExpanded[0]); } }, approveCardExpanded[0] ? '收起' : '展开') + }, + approveCardExpanded[0] ? React.createElement(Steps, { + direction: 'vertical', + current: approvalSteps.length, + items: approvalSteps.map(function (s) { + var statusText = '审批通过'; + var desc = React.createElement('div', { style: { fontSize: 13, color: 'rgba(0,0,0,0.65)', marginTop: 4 } }, + React.createElement('div', null, '审批状态:', statusText), + React.createElement('div', null, '审批人:', s.person || '-'), + s.time ? React.createElement('div', null, '审批时间:', s.time) : null + ); + return { title: s.title, description: desc, status: 'finish' }; + }) + }) : null + ), + React.createElement('div', { style: { display: 'flex', gap: 8, marginTop: 24 } }, + React.createElement(Button, { type: 'primary', onClick: handleSubmit }, '提交'), + React.createElement(Button, { onClick: handleCancel }, '取消') + ), + React.createElement(Modal, { + title: '确认提交', + open: submitConfirmVisible[0], + onCancel: function () { submitConfirmVisible[1](false); }, + onOk: handleSubmitConfirm, + okText: '确认', + cancelText: '取消' + }, React.createElement('div', null, '请仔细核对提车首付款到账和开票信息,提交后无法修改。')), + React.createElement(Modal, { + title: '确认取消', + open: cancelConfirmVisible[0], + onCancel: function () { cancelConfirmVisible[1](false); }, + onOk: handleCancelConfirm, + okText: '确认', + cancelText: '取消' + }, React.createElement('div', null, '取消将会丢失所有已填写数据,是否确认?')), + React.createElement(Modal, { + title: '附件预览', + open: proofPreviewVisible[0], + onCancel: function () { proofPreviewVisible[1](false); proofPreviewFile[1](null); }, + footer: React.createElement(Button, { onClick: function () { proofPreviewVisible[1](false); proofPreviewFile[1](null); } }, '关闭'), + width: 640 + }, React.createElement('div', { style: { padding: '16px 0' } }, + React.createElement('div', { style: { marginBottom: 12, fontSize: 14, color: 'rgba(0,0,0,0.65)' } }, '文件:', proofPreviewFile[0] ? (proofPreviewFile[0].name || '附件') : ''), + React.createElement('div', { style: { minHeight: 360, background: '#fafafa', borderRadius: 4, display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'rgba(0,0,0,0.45)', fontSize: 14 } }, '此处可预览附件内容,支持 jpg、png、pdf 等格式') + )), + React.createElement(Modal, { + title: '发票附件预览', + open: invoicePreviewVisible[0], + onCancel: function () { invoicePreviewVisible[1](false); invoicePreviewFile[1](null); }, + footer: React.createElement(Button, { onClick: function () { invoicePreviewVisible[1](false); invoicePreviewFile[1](null); } }, '关闭'), + width: 640 + }, React.createElement('div', { style: { padding: '16px 0' } }, + React.createElement('div', { style: { marginBottom: 12, fontSize: 14, color: 'rgba(0,0,0,0.65)' } }, '文件:', invoicePreviewFile[0] ? (invoicePreviewFile[0].name || '附件') : ''), + React.createElement('div', { style: { minHeight: 360, background: '#fafafa', borderRadius: 4, display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'rgba(0,0,0,0.45)', fontSize: 14 } }, '此处可预览附件内容,支持 jpg、png、pdf 等格式') + )), + React.createElement(Modal, { + title: '需求说明', + open: requirementModalVisible[0], + onCancel: function () { requirementModalVisible[1](false); }, + width: 720, + footer: React.createElement(Button, { onClick: function () { requirementModalVisible[1](false); } }, '关闭'), + bodyStyle: { maxHeight: '70vh', overflow: 'auto' } + }, React.createElement('div', { style: { padding: '8px 0', whiteSpace: 'pre-wrap', fontSize: 13, lineHeight: 1.6 } }, requirementContent)) + ); +}; diff --git a/web端/财务管理/提车应收款-收款.jsx b/web端/财务管理/提车应收款-提车收款单.jsx similarity index 56% rename from web端/财务管理/提车应收款-收款.jsx rename to web端/财务管理/提车应收款-提车收款单.jsx index 8633fc0..35d2f0f 100644 --- a/web端/财务管理/提车应收款-收款.jsx +++ b/web端/财务管理/提车应收款-提车收款单.jsx @@ -1,5 +1,5 @@ // 【重要】必须使用 const Component 作为组件变量名 -// 财务管理 - 提车应收款 - 收款(2026年3月4日版本) +// 财务管理 - 提车应收款 - 提车收款单(2026年3月10日版本) const Component = function () { var useState = React.useState; @@ -16,6 +16,7 @@ const Component = function () { var Select = antd.Select; var Table = antd.Table; var Popover = antd.Popover; + var Tooltip = antd.Tooltip; var Modal = antd.Modal; var message = antd.message; var Option = Select.Option; @@ -28,6 +29,10 @@ const Component = function () { var setSubmitConfirmVisible = submitConfirmVisible[1]; var cancelConfirmVisible = useState(false); var setCancelConfirmVisible = cancelConfirmVisible[1]; + var projectCardExpanded = useState(true); + var setProjectCardExpanded = projectCardExpanded[1]; + var receivableCardExpanded = useState(true); + var setReceivableCardExpanded = receivableCardExpanded[1]; var servicePopoverRowIndex = useState(null); var setServicePopoverRowIndex = servicePopoverRowIndex[1]; @@ -50,47 +55,26 @@ const Component = function () { // 模拟:车辆应收款明细(租赁合同中所有车辆),付款周期 6 个月 => 应收月租金 = 月租金*6 var paymentCycleMonths = 6; var vehicleRows = useState([ - { - key: 'v1', - index: 1, - brand: '东风', - model: 'DFH1180', - plateNo: '浙A12345', - monthRent: 5000, - receivableRent: 30000, - actualRent: '30000.00', - rentRemark: '', - receivableDeposit: 10000, - serviceItems: [ - { name: '代处理费用', receivable: 200, actual: '200.00', discount: '0.00', remark: '' }, - { name: '保险上浮', receivable: 500, actual: '500.00', discount: '0.00', remark: '' } - ], - receivableService: 700, - actualService: '700.00', - discountAmount: '0.00', - discountRemark: '', - discountProof: [], - selected: true - }, - { - key: 'v2', - index: 2, - brand: '福田', - model: 'BJ1180', - plateNo: '', - monthRent: 4500, - receivableRent: 27000, - actualRent: '27000.00', - rentRemark: '', - receivableDeposit: 8000, - serviceItems: [{ name: '保养费用', receivable: 300, actual: '300.00', discount: '0.00', remark: '' }], - receivableService: 300, - actualService: '300.00', - discountAmount: '0.00', - discountRemark: '', - discountProof: [], - selected: true - } + { key: 'v1', index: 1, brand: '东风', model: 'DFH1180', plateNo: '浙A12345', monthRent: 5000, receivableRent: 30000, actualRent: '30000.00', rentRemark: '', receivableDeposit: 10000, serviceItems: [{ name: '代处理费用', receivable: 200, actual: '200.00', discount: '0.00', remark: '' }, { name: '保险上浮', receivable: 500, actual: '500.00', discount: '0.00', remark: '' }], receivableService: 700, actualService: '700.00', discountAmount: '0.00', discountRemark: '', discountProof: [], selected: true, receivableCompleted: true }, + { key: 'v2', index: 2, brand: '福田', model: 'BJ1180', plateNo: '浙A23456', monthRent: 4500, receivableRent: 27000, actualRent: '27000.00', rentRemark: '', receivableDeposit: 8000, serviceItems: [{ name: '保养费用', receivable: 300, actual: '300.00', discount: '0.00', remark: '' }], receivableService: 300, actualService: '300.00', discountAmount: '0.00', discountRemark: '', discountProof: [], selected: true, receivableCompleted: true }, + { key: 'v3', index: 3, brand: '重汽', model: 'ZZ1187', plateNo: '浙A34567', monthRent: 5200, receivableRent: 31200, actualRent: '31200.00', rentRemark: '', receivableDeposit: 10000, serviceItems: [{ name: '代处理费用', receivable: 180, actual: '180.00', discount: '0.00', remark: '' }, { name: '上牌服务', receivable: 400, actual: '400.00', discount: '0.00', remark: '' }], receivableService: 580, actualService: '580.00', discountAmount: '0.00', discountRemark: '', discountProof: [], selected: true, receivableCompleted: true }, + { key: 'v4', index: 4, brand: '陕汽', model: 'SX1313', plateNo: '', monthRent: 4800, receivableRent: 28800, actualRent: '28800.00', rentRemark: '', receivableDeposit: 9000, serviceItems: [{ name: '保险上浮', receivable: 350, actual: '350.00', discount: '0.00', remark: '' }], receivableService: 350, actualService: '350.00', discountAmount: '0.00', discountRemark: '', discountProof: [], selected: false }, + { key: 'v5', index: 5, brand: '解放', model: 'J6P', plateNo: '浙A45678', monthRent: 5500, receivableRent: 33000, actualRent: '33000.00', rentRemark: '', receivableDeposit: 11000, serviceItems: [{ name: '代处理费用', receivable: 220, actual: '220.00', discount: '0.00', remark: '' }, { name: '保养费用', receivable: 280, actual: '280.00', discount: '0.00', remark: '' }], receivableService: 500, actualService: '500.00', discountAmount: '0.00', discountRemark: '', discountProof: [], selected: false }, + { key: 'v6', index: 6, brand: '江淮', model: '格尔发K5', plateNo: '浙A56789', monthRent: 4300, receivableRent: 25800, actualRent: '25800.00', rentRemark: '', receivableDeposit: 8500, serviceItems: [{ name: '保养费用', receivable: 260, actual: '260.00', discount: '0.00', remark: '' }], receivableService: 260, actualService: '260.00', discountAmount: '0.00', discountRemark: '', discountProof: [], selected: false }, + { key: 'v7', index: 7, brand: '东风', model: 'DFH1250', plateNo: '浙A67890', monthRent: 6000, receivableRent: 36000, actualRent: '36000.00', rentRemark: '', receivableDeposit: 12000, serviceItems: [{ name: '代处理费用', receivable: 250, actual: '250.00', discount: '0.00', remark: '' }, { name: '保险上浮', receivable: 600, actual: '600.00', discount: '0.00', remark: '' }], receivableService: 850, actualService: '850.00', discountAmount: '0.00', discountRemark: '', discountProof: [], selected: false }, + { key: 'v8', index: 8, brand: '福田', model: '欧曼EST', plateNo: '', monthRent: 4700, receivableRent: 28200, actualRent: '28200.00', rentRemark: '', receivableDeposit: 9000, serviceItems: [{ name: '上牌服务', receivable: 380, actual: '380.00', discount: '0.00', remark: '' }], receivableService: 380, actualService: '380.00', discountAmount: '0.00', discountRemark: '', discountProof: [], selected: false }, + { key: 'v9', index: 9, brand: '重汽', model: '豪沃T7H', plateNo: '浙A78901', monthRent: 5300, receivableRent: 31800, actualRent: '31800.00', rentRemark: '', receivableDeposit: 10500, serviceItems: [{ name: '代处理费用', receivable: 200, actual: '200.00', discount: '0.00', remark: '' }, { name: '保养费用', receivable: 320, actual: '320.00', discount: '0.00', remark: '' }], receivableService: 520, actualService: '520.00', discountAmount: '0.00', discountRemark: '', discountProof: [], selected: false }, + { key: 'v10', index: 10, brand: '陕汽', model: '德龙X3000', plateNo: '浙A89012', monthRent: 5100, receivableRent: 30600, actualRent: '30600.00', rentRemark: '', receivableDeposit: 10000, serviceItems: [{ name: '保险上浮', receivable: 450, actual: '450.00', discount: '0.00', remark: '' }], receivableService: 450, actualService: '450.00', discountAmount: '0.00', discountRemark: '', discountProof: [], selected: false }, + { key: 'v11', index: 11, brand: '解放', model: 'JH6', plateNo: '浙A90123', monthRent: 5400, receivableRent: 32400, actualRent: '32400.00', rentRemark: '', receivableDeposit: 10800, serviceItems: [{ name: '代处理费用', receivable: 210, actual: '210.00', discount: '0.00', remark: '' }, { name: '上牌服务', receivable: 420, actual: '420.00', discount: '0.00', remark: '' }], receivableService: 630, actualService: '630.00', discountAmount: '0.00', discountRemark: '', discountProof: [], selected: false }, + { key: 'v12', index: 12, brand: '江淮', model: '帅铃', plateNo: '', monthRent: 4000, receivableRent: 24000, actualRent: '24000.00', rentRemark: '', receivableDeposit: 8000, serviceItems: [{ name: '保养费用', receivable: 240, actual: '240.00', discount: '0.00', remark: '' }], receivableService: 240, actualService: '240.00', discountAmount: '0.00', discountRemark: '', discountProof: [], selected: false }, + { key: 'v13', index: 13, brand: '东风', model: 'DFH1180', plateNo: '浙B12345', monthRent: 5000, receivableRent: 30000, actualRent: '30000.00', rentRemark: '', receivableDeposit: 10000, serviceItems: [{ name: '代处理费用', receivable: 200, actual: '200.00', discount: '0.00', remark: '' }], receivableService: 200, actualService: '200.00', discountAmount: '500.00', discountRemark: '首月优惠', discountProof: [], selected: false }, + { key: 'v14', index: 14, brand: '福田', model: 'BJ1180', plateNo: '浙B23456', monthRent: 4600, receivableRent: 27600, actualRent: '27600.00', rentRemark: '', receivableDeposit: 9200, serviceItems: [{ name: '保险上浮', receivable: 380, actual: '380.00', discount: '0.00', remark: '' }], receivableService: 380, actualService: '380.00', discountAmount: '0.00', discountRemark: '', discountProof: [], selected: false }, + { key: 'v15', index: 15, brand: '重汽', model: 'ZZ1187', plateNo: '浙B34567', monthRent: 4900, receivableRent: 29400, actualRent: '29400.00', rentRemark: '', receivableDeposit: 9800, serviceItems: [{ name: '代处理费用', receivable: 190, actual: '190.00', discount: '0.00', remark: '' }, { name: '保养费用', receivable: 310, actual: '310.00', discount: '0.00', remark: '' }], receivableService: 500, actualService: '500.00', discountAmount: '0.00', discountRemark: '', discountProof: [], selected: false }, + { key: 'v16', index: 16, brand: '陕汽', model: 'SX1313', plateNo: '', monthRent: 4750, receivableRent: 28500, actualRent: '28500.00', rentRemark: '', receivableDeposit: 9500, serviceItems: [{ name: '上牌服务', receivable: 360, actual: '360.00', discount: '0.00', remark: '' }], receivableService: 360, actualService: '360.00', discountAmount: '0.00', discountRemark: '', discountProof: [], selected: false }, + { key: 'v17', index: 17, brand: '解放', model: 'J6P', plateNo: '浙B45678', monthRent: 5600, receivableRent: 33600, actualRent: '33600.00', rentRemark: '', receivableDeposit: 11200, serviceItems: [{ name: '代处理费用', receivable: 230, actual: '230.00', discount: '0.00', remark: '' }, { name: '保险上浮', receivable: 550, actual: '550.00', discount: '0.00', remark: '' }], receivableService: 780, actualService: '780.00', discountAmount: '0.00', discountRemark: '', discountProof: [], selected: false }, + { key: 'v18', index: 18, brand: '江淮', model: '格尔发K5', plateNo: '浙B56789', monthRent: 4400, receivableRent: 26400, actualRent: '26400.00', rentRemark: '', receivableDeposit: 8800, serviceItems: [{ name: '保养费用', receivable: 270, actual: '270.00', discount: '0.00', remark: '' }], receivableService: 270, actualService: '270.00', discountAmount: '0.00', discountRemark: '', discountProof: [], selected: false }, + { key: 'v19', index: 19, brand: '东风', model: 'DFH1250', plateNo: '浙B67890', monthRent: 5800, receivableRent: 34800, actualRent: '34800.00', rentRemark: '', receivableDeposit: 11600, serviceItems: [{ name: '代处理费用', receivable: 240, actual: '240.00', discount: '0.00', remark: '' }], receivableService: 240, actualService: '240.00', discountAmount: '200.00', discountRemark: '客户协商', discountProof: [], selected: false }, + { key: 'v20', index: 20, brand: '福田', model: '欧曼EST', plateNo: '浙B78901', monthRent: 4650, receivableRent: 27900, actualRent: '27900.00', rentRemark: '', receivableDeposit: 9300, serviceItems: [{ name: '上牌服务', receivable: 390, actual: '390.00', discount: '0.00', remark: '' }, { name: '保养费用', receivable: 290, actual: '290.00', discount: '0.00', remark: '' }], receivableService: 680, actualService: '680.00', discountAmount: '0.00', discountRemark: '', discountProof: [], selected: false } ]); var vehicles = vehicleRows[0]; var setVehicles = vehicleRows[1]; @@ -111,28 +95,12 @@ const Component = function () { var invoiceRemark = useState(''); var setInvoiceRemark = invoiceRemark[1]; - // 开票信息(客户信息 + 开票时间/附件/开票人) - var invoiceInfo = useMemo(function () { - return { - customerName: '嘉兴某某物流有限公司', - taxId: '91330400MA2XXXXX1', - address: '浙江省嘉兴市南湖区科技大道1号', - phone: '0571-88888888', - account: '6222021234567890123', - bank: '中国工商银行嘉兴分行', - mailingAddress: '浙江省嘉兴市南湖区科技大道1号', - invoiceTime: '', - invoiceFile: '', - invoicePerson: '-' - }; - }, []); - var toggleVehicleSelected = useCallback(function (key) { setEdited(true); setVehicles(function (prev) { return prev.map(function (r) { - if (r.key === key) return Object.assign({}, r, { selected: !r.selected }); - return r; + if (r.key !== key || r.receivableCompleted) return r; + return Object.assign({}, r, { selected: !r.selected }); }); }); }, []); @@ -140,7 +108,10 @@ const Component = function () { var toggleAllSelected = useCallback(function (checked) { setEdited(true); setVehicles(function (prev) { - return prev.map(function (r) { return Object.assign({}, r, { selected: !!checked }); }); + return prev.map(function (r) { + if (r.receivableCompleted) return r; + return Object.assign({}, r, { selected: !!checked }); + }); }); }, []); @@ -177,7 +148,8 @@ const Component = function () { }, []); var totals = useMemo(function () { - var selected = vehicles.filter(function (v) { return v.selected; }); + // 只计算已选中且未完成提车应收款的车辆 + var selected = vehicles.filter(function (v) { return v.selected && !v.receivableCompleted; }); var receivableRent = 0, actualRent = 0, receivableDeposit = 0, receivableService = 0, actualService = 0, discountTotal = 0; selected.forEach(function (v) { receivableRent += Number(v.receivableRent) || 0; @@ -273,16 +245,18 @@ const Component = function () { var layoutStyle = { padding: '16px 24px', background: '#f5f5f5', minHeight: '100vh' }; var cardStyle = { marginBottom: 16 }; var labelStyle = { marginBottom: 6, fontSize: 14, color: 'rgba(0,0,0,0.65)' }; + var requiredStar = { color: '#ff4d4f', marginRight: 2 }; var formRowStyle = { display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '16px 24px', marginBottom: 16 }; var formItemStyle = { marginBottom: 12 }; var highlightStyle = { color: '#1890ff', fontWeight: 600, cursor: 'pointer' }; var valueStyle = { color: 'rgba(0,0,0,0.85)', fontSize: 14, lineHeight: '22px', minHeight: 22 }; var thBase = { padding: '10px 12px', border: '1px solid #f0f0f0', whiteSpace: 'nowrap' }; - var requirementContent = '提车应收款(2026年3月4日版本)\n一个「数字化资产ONEOS运管平台」中的「财务管理」「提车应收款」「收款」模块\n#面包屑:财务管理-提车应收款-收款;\n\n页面分为3个卡片。\n1.项目信息:合同编码、合同类型、项目名称、客户名称、付款方式、付款周期、合同生效时间、合同结束时间、业务部门、业务负责人。\n2.提车应收款信息:应收款总额/实收款总额(点击弹出气泡卡片);车辆应收款明细表(全选/多选、序号、品牌、型号、车牌号、应收/实收车辆月租金、车辆租金备注、应收车辆保证金、服务费项目、应收/实收服务费、减免金额、减免金额备注、减免证明);总计行;氢费预付款(可选);开票方式、开票备注。\n3.开票信息:客户名称、纳税人识别号、地址、电话、账户、开户行、邮寄地址、开票时间、发票附件、开票人。\n4.底部:提交审核、保存、取消及二次确认。'; + var requirementContent = '提车应收款-提车收款单(2026年3月10日版本)\n一个「数字化资产ONEOS运管平台」中的「财务管理」「提车应收款」「提车收款单」模块\n#面包屑:财务管理-提车应收款-提车收款单;\n\n页面分为3个卡片,业务流程请参考流程图;\n1.项目信息:\n#显示项目详细信息,包括:\n1.1.合同编码:显示该租赁合同对应合同编码;\n1.2.合同类型:显示该租赁合同对应合同类型;\n1.3.项目名称:显示该租赁合同对应项目名称;\n1.4.客户名称:显示该租赁合同对应客户名称;\n1.5.付款方式:显示该租赁合同对应付款方式,分为:预付、后付两种,根据实际合同反写;\n1.6.付款周期:显示该租赁合同对应付款周期,格式为:x个月;\n1.7.合同生效时间:显示该租赁合同对应合同生效时间;\n1.8.合同结束时间:显示该租赁合同对应合同结束时间;\n1.9.业务部门:显示该租赁合同对应业务部门名称;\n1.10.业务负责人:显示该租赁合同对应业务负责人;\n\n2.提车应收款信息:\n#上方显示应收款总额、实收款总额;\n2.1.应收款总额:格式为:xx.xx元,重点色,计算方式为:「选中车辆应收车辆月租金总和」+「选中车辆应收车辆保证金总和」+「选中车辆应收服务费总和」;\n 点击应收款总额金额,弹出气泡卡片,气泡卡片列表展示:项目、金额;\n 2.1.1.项目:包括总计应收车辆月租金、总计应收车辆保证金、总计应收服务费三项;\n 2.1.2.金额:显示该项目对应应收金额;\n2.2.实收款总额:格式为:xx.xx元,重点色,计算方式为:「选中车辆实收车辆月租金总和」+「选中车辆应收车辆保证金总和」+「选中车辆实收服务费总和」-「选中车辆减免金额总和」;\n 点击实收款总额金额,弹出气泡卡片,气泡卡片列表展示:项目、金额;\n 2.2.1.项目:包括总计实收车辆月租金、总计应收车辆保证金、总计实收服务费、总计减免金额四项;\n 2.2.2.金额:显示该项目对应实收金额;\n\n#中间为车辆应收款明细,以列表展示租赁合同中所有车辆明细,包括以下字段:\n2.1.全选/多选:多选按钮组,支持表头点击全选,默认为取消勾选(整个车辆清单);\n 选择代表本次提车应收款只收取该部分车辆费用,可以编辑以下相应字段,不选择则代表本次提车应收款不收取该部分车辆费用,不能编辑车辆相应字段;\n 提车应收款支持分批次进行收款,如果该车辆之前已提交提车应收款流程,则多选框不能勾选,悬浮多选框时提示:该车辆已完成提车应收款;\n2.2.序号:根据租赁合同中车辆对应序号顺序展示;\n2.3.品牌:显示租赁合同中车辆对应品牌;\n2.4.型号:显示租赁合同中车辆对应型号;\n2.5.车牌号:显示租赁合同中车辆对应车牌号,如果车牌号为空则显示为-(后续如果交车成功,则车牌号会反显在该处);\n2.6.应收车辆月租金:根据租赁合同付款周期自动计算,例如:付款周期为6个月,则这里显示金额为:车辆月租金*6;\n2.7.实收车辆月租金:必填项,输入框,默认反写实收车辆月租金,支持修改,由业务员自行输入车辆月租金金额,精确至2位小数,后缀为元;\n2.8.车辆租金备注:选填项,输入框,由业务员自行输入;\n2.9.减免金额:选填项,输入框,由业务员自行输入减免金额,默认为0.00,支持2位小数,后缀为元;\n2.10.减免金额备注:选填项,输入框,由业务员自行输入备注信息;\n2.11.应收车辆保证金:显示租赁合同中当前车辆保证金金额;\n2.12.服务费项目:点击管理,弹出气泡卡片,气泡卡片标题为服务费项目,下方为列表,显示服务项目、应收费用、实收费用、备注;\n 2.12.1.服务项目:显示租赁合同中所有服务项目名称;\n 2.12.2.应收费用:显示租赁合同中所有服务项目对应费用;\n 2.12.3.实收费用:必填项,输入框,默认反写应收费用,支持修改,由业务员自行输入实收费用金额,精确至2位小数,后缀为元;\n 2.12.4.减免费用:选填项,输入框,默认为:0.00,精确至2位小数,后缀为元;\n 2.12.4.备注:选填项,输入框,由业务员自行输入备注信息;\n2.13.应收服务费:显示当前车辆所有服务费应收费用总和,格式为:xx.xx元;\n2.14.实收服务费:显示当前车辆所有服务费实收费用总和,格式为:xx.xx元;\n2.15.减免金额:选填项,输入框,由业务员自行输入减免金额,默认为0.00,支持2位小数,后缀为元;\n2.16.减免金额备注:选填项,输入框,由业务员自行输入备注信息;\n2.17.减免证明:选填项,附件上传按钮,点击后上传本地文件,支持:jpg、png、pdf等格式,支持多附件上传;\n\n列表不支持分页功能;\n#下方为氢费预付款情况,以列表展示租赁合同中所有氢费预付款明细,包括以下字段,如租赁合同氢费非预付款模式,则不显示该部分内容:\n2.19.氢费预付款应收金额:显示租赁合同中氢费预付款金额,格式为:xx.xx元,支持2位小数;\n2.20.氢费预付款实收金额:必填项,输入框,默认反写氢费预付款应收金额,支持修改,由业务员自行输入金额,格式为:xx.xx元,支持2位小数;\n2.21.减免金额:选填项,输入框,由业务员自行输入减免金额,默认为:0.00元,格式为:xx.xx元,支持2位小数;\n2.22.减免金额备注:选填项,输入框,由业务员自行输入减免金额备注信息;\n#最底部为开票方式、开票备注;\n2.23.开票方式:选择器,选项包括:先开票后付款、先付款后开票,默认为先开票后付款;\n2.24.开票备注:必填项,文本域,默认提示文本为:请输入开票项目、税率以及其他备注信息,财务将以此进行开票;\n\n3.底部为提交审核,保存,取消;\n3.1.提交审核:点击提交审核,进行二次确认,文案为:请仔细核对提车首付款实收金额,点击确认则进入工作流;\n3.2.保存:点击保存,提示保存成功,跳转至提车应收款列表页,同时该条数据审核状态为:待提交:\n3.3.取消:点击取消,进行二次确认,文案为:取消将会丢失所有已填写数据,是否确认,点击确认则跳转至提车应收款列表页;'; - var allSelected = vehicles.length > 0 && vehicles.every(function (v) { return v.selected; }); - var indeterminate = vehicles.some(function (v) { return v.selected; }) && !allSelected; + var selectableVehicles = vehicles.filter(function (v) { return !v.receivableCompleted; }); + var allSelected = selectableVehicles.length > 0 && selectableVehicles.every(function (v) { return v.selected; }); + var indeterminate = selectableVehicles.some(function (v) { return v.selected; }) && !allSelected; var headerCheckRef = useRef(null); useEffect(function () { if (headerCheckRef.current) headerCheckRef.current.indeterminate = indeterminate; @@ -294,13 +268,17 @@ const Component = function () { items: [ { title: '财务管理' }, { title: '提车应收款' }, - { title: '收款' } + { title: '提车收款单' } ] }), React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalVisible(true); } }, '查看需求说明') ), - React.createElement(Card, { title: '项目信息', style: cardStyle }, - React.createElement('div', { style: formRowStyle }, + React.createElement(Card, { + title: '项目信息', + style: cardStyle, + extra: React.createElement(Button, { type: 'link', size: 'small', onClick: function () { setProjectCardExpanded(!projectCardExpanded[0]); } }, projectCardExpanded[0] ? '收起' : '展开') + }, + projectCardExpanded[0] ? React.createElement('div', { style: formRowStyle }, React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '合同编码'), React.createElement('div', { style: valueStyle }, projectInfo.contractCode || '-') @@ -341,15 +319,20 @@ const Component = function () { React.createElement('div', { style: labelStyle }, '业务负责人'), React.createElement('div', { style: valueStyle }, projectInfo.businessPerson || '-') ) - ) + ) : null ), - React.createElement(Card, { title: '提车应收款信息', style: cardStyle }, + React.createElement(Card, { + title: '提车应收款信息', + style: cardStyle, + extra: React.createElement(Button, { type: 'link', size: 'small', onClick: function () { setReceivableCardExpanded(!receivableCardExpanded[0]); } }, receivableCardExpanded[0] ? '收起' : '展开') + }, + receivableCardExpanded[0] ? React.createElement(React.Fragment, null, React.createElement('div', { style: { marginBottom: 16, display: 'flex', gap: 24, alignItems: 'center' } }, React.createElement(Popover, { content: receivablePopoverContent, title: '应收款明细', trigger: 'click' }, - React.createElement('span', { style: highlightStyle }, '应收款总额:', receivableTotal, ' 元') + React.createElement('span', { style: { cursor: 'pointer' } }, '应收款总额:', React.createElement('span', { style: highlightStyle }, receivableTotal, ' 元')) ), React.createElement(Popover, { content: actualPopoverContent, title: '实收款明细', trigger: 'click' }, - React.createElement('span', { style: highlightStyle }, '实收款总额:', actualTotal, ' 元') + React.createElement('span', { style: { cursor: 'pointer' } }, '实收款总额:', React.createElement('span', { style: highlightStyle }, actualTotal, ' 元')) ) ), React.createElement('div', { style: { overflowX: 'auto', marginBottom: 16, overflowY: 'visible' } }, @@ -364,20 +347,20 @@ const Component = function () { React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'left', width: 90 }) }, '型号'), React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'left', width: 100 }) }, '车牌号'), React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'right', width: 120 }) }, '应收车辆月租金'), - React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'left', width: 130 }) }, '实收车辆月租金'), + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'left', width: 130 }) }, React.createElement('span', null, React.createElement('span', { style: requiredStar }, '*'), '实收车辆月租金')), React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'left', width: 130 }) }, '车辆租金备注'), + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'left', width: 130 }) }, '减免金额'), + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'left', width: 130 }) }, '减免金额备注'), + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'left', width: 130 }) }, '减免证明'), React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'right', width: 110 }) }, '应收车辆保证金'), React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'left', width: 90 }) }, '服务费项目'), React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'right', width: 90 }) }, '应收服务费'), - React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'right', width: 130 }) }, '实收服务费'), - React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'left', width: 130 }) }, '减免金额'), - React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'left', width: 130 }) }, '减免金额备注'), - React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'left', width: 130 }) }, '减免证明') + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'right', width: 130 }) }, '实收服务费') ) ), React.createElement('tbody', null, vehicles.map(function (row) { - var disabled = !row.selected; + var disabled = !row.selected || row.receivableCompleted; var servicePopover = React.createElement('div', { style: { padding: 8, minWidth: 360 } }, React.createElement('div', { style: { fontWeight: 600, marginBottom: 8 } }, '服务费项目'), React.createElement('table', { style: { width: '100%', borderCollapse: 'collapse', fontSize: 12 } }, @@ -385,7 +368,7 @@ const Component = function () { React.createElement('tr', null, React.createElement('th', { style: { textAlign: 'left', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '服务项目'), React.createElement('th', { style: { textAlign: 'right', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '应收费用'), - React.createElement('th', { style: { textAlign: 'right', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '实收费用'), + React.createElement('th', { style: { textAlign: 'right', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, React.createElement('span', null, React.createElement('span', { style: requiredStar }, '*'), '实收费用')), React.createElement('th', { style: { textAlign: 'right', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '减免费用'), React.createElement('th', { style: { textAlign: 'left', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '备注') ) @@ -425,10 +408,15 @@ const Component = function () { next.splice(idx, 1); updateVehicle(row.key, 'discountProof', next); }; + var checkboxCell = row.receivableCompleted + ? React.createElement(Tooltip, { title: '该车辆已完成提车应收款' }, + React.createElement('span', { style: { display: 'inline-block', cursor: 'not-allowed' } }, + React.createElement('input', { type: 'checkbox', checked: true, disabled: true }) + ) + ) + : React.createElement('input', { type: 'checkbox', checked: row.selected, onChange: function () { toggleVehicleSelected(row.key); } }); return React.createElement('tr', { key: row.key }, - React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0' } }, - React.createElement('input', { type: 'checkbox', checked: row.selected, onChange: function () { toggleVehicleSelected(row.key); } }) - ), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0' } }, checkboxCell), React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0' } }, row.index), React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0' } }, row.brand), React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0' } }, row.model), @@ -440,14 +428,6 @@ const Component = function () { React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0', width: 130 } }, React.createElement(Input, { size: 'small', value: row.rentRemark, disabled: disabled, onChange: function (e) { updateVehicle(row.key, 'rentRemark', e.target.value); } }) ), - React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0', textAlign: 'right' } }, (row.receivableDeposit || 0) + ' 元'), - React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0' } }, - React.createElement(Popover, { content: servicePopover, title: null, trigger: 'click' }, - React.createElement(Button, { type: 'link', size: 'small', disabled: disabled }, '管理') - ) - ), - React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0', textAlign: 'right' } }, (row.receivableService || 0) + ' 元'), - React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0', textAlign: 'right', width: 130 } }, (row.actualService || '0.00') + ' 元'), React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0', width: 130 } }, React.createElement(Input, { size: 'small', value: row.discountAmount, disabled: disabled, suffix: '元', onChange: function (e) { updateVehicle(row.key, 'discountAmount', e.target.value); } }) ), @@ -467,24 +447,17 @@ const Component = function () { React.createElement(Button, { type: 'default', size: 'small', onClick: function () { var el = document.getElementById('proof-' + row.key); if (el) el.click(); } }, '附件上传') ) ) - ) + ), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0', textAlign: 'right' } }, (row.receivableDeposit || 0) + ' 元'), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0' } }, + React.createElement(Popover, { content: servicePopover, title: null, trigger: 'click' }, + React.createElement(Button, { type: 'link', size: 'small', disabled: disabled }, '管理') + ) + ), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0', textAlign: 'right' } }, (row.receivableService || 0) + ' 元'), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0', textAlign: 'right', width: 130 } }, (row.actualService || '0.00') + ' 元') ); }) - ), - React.createElement('tfoot', null, - React.createElement('tr', { style: { backgroundColor: '#fafafa', fontWeight: 500 } }, - React.createElement('td', { style: { padding: '10px 12px', border: '1px solid #f0f0f0' }, colSpan: 5 }, '总计'), - React.createElement('td', { style: { padding: '10px 12px', border: '1px solid #f0f0f0', textAlign: 'right' } }, totals.receivableRent + ' 元'), - React.createElement('td', { style: { padding: '10px 12px', border: '1px solid #f0f0f0', textAlign: 'right' } }, totals.actualRent + ' 元'), - React.createElement('td', { style: { padding: '10px 12px', border: '1px solid #f0f0f0' } }), - React.createElement('td', { style: { padding: '10px 12px', border: '1px solid #f0f0f0', textAlign: 'right' } }, totals.receivableDeposit + ' 元'), - React.createElement('td', { style: { padding: '10px 12px', border: '1px solid #f0f0f0' } }), - React.createElement('td', { style: { padding: '10px 12px', border: '1px solid #f0f0f0', textAlign: 'right' } }, totals.receivableService + ' 元'), - React.createElement('td', { style: { padding: '10px 12px', border: '1px solid #f0f0f0', textAlign: 'right' } }, totals.actualService + ' 元'), - React.createElement('td', { style: { padding: '10px 12px', border: '1px solid #f0f0f0', textAlign: 'right' } }, totals.discountTotal + ' 元'), - React.createElement('td', { style: { padding: '10px 12px', border: '1px solid #f0f0f0' } }), - React.createElement('td', { style: { padding: '10px 12px', border: '1px solid #f0f0f0' } }) - ) ) ) ), @@ -496,7 +469,7 @@ const Component = function () { React.createElement(Input, { value: hydrogen.receivable + ' 元', disabled: true }) ), React.createElement('div', { style: formItemStyle }, - React.createElement('div', { style: labelStyle }, '氢费预付款实收金额'), + React.createElement('div', { style: labelStyle }, React.createElement('span', { style: requiredStar }, '*'), '氢费预付款实收金额'), React.createElement(Input, { value: hydrogen.actual, suffix: '元', onChange: function (e) { setHydrogen(function (p) { return Object.assign({}, p, { actual: e.target.value }); }); } }) ), React.createElement('div', { style: formItemStyle }, @@ -518,54 +491,11 @@ const Component = function () { ) ), React.createElement('div', { style: Object.assign({}, formItemStyle, { gridColumn: '1 / -1' }) }, - React.createElement('div', { style: labelStyle }, '开票备注'), + React.createElement('div', { style: labelStyle }, React.createElement('span', { style: requiredStar }, '*'), '开票备注'), React.createElement(Input.TextArea, { value: invoiceRemark[0], onChange: function (e) { setInvoiceRemark(e.target.value); setEdited(true); }, rows: 3, placeholder: '请输入开票项目、税率以及其他备注信息,财务将以此进行开票', style: { width: '100%' } }) ) ) - ), - React.createElement(Card, { title: '开票信息', style: cardStyle }, - React.createElement('div', { style: formRowStyle }, - React.createElement('div', { style: formItemStyle }, - React.createElement('div', { style: labelStyle }, '客户名称'), - React.createElement('div', { style: valueStyle }, invoiceInfo.customerName || '-') - ), - React.createElement('div', { style: formItemStyle }, - React.createElement('div', { style: labelStyle }, '纳税人识别号'), - React.createElement('div', { style: valueStyle }, invoiceInfo.taxId || '-') - ), - React.createElement('div', { style: formItemStyle }, - React.createElement('div', { style: labelStyle }, '地址'), - React.createElement('div', { style: valueStyle }, invoiceInfo.address || '-') - ), - React.createElement('div', { style: formItemStyle }, - React.createElement('div', { style: labelStyle }, '电话'), - React.createElement('div', { style: valueStyle }, invoiceInfo.phone || '-') - ), - React.createElement('div', { style: formItemStyle }, - React.createElement('div', { style: labelStyle }, '账户'), - React.createElement('div', { style: valueStyle }, invoiceInfo.account || '-') - ), - React.createElement('div', { style: formItemStyle }, - React.createElement('div', { style: labelStyle }, '开户行'), - React.createElement('div', { style: valueStyle }, invoiceInfo.bank || '-') - ), - React.createElement('div', { style: Object.assign({}, formItemStyle, { gridColumn: '1 / -1' }) }, - React.createElement('div', { style: labelStyle }, '邮寄地址'), - React.createElement('div', { style: valueStyle }, invoiceInfo.mailingAddress || '-') - ), - React.createElement('div', { style: formItemStyle }, - React.createElement('div', { style: labelStyle }, '开票时间'), - React.createElement('div', { style: valueStyle }, invoiceInfo.invoiceTime || '未上传') - ), - React.createElement('div', { style: formItemStyle }, - React.createElement('div', { style: labelStyle }, '发票附件'), - React.createElement('div', { style: valueStyle }, invoiceInfo.invoiceFile || '未上传') - ), - React.createElement('div', { style: formItemStyle }, - React.createElement('div', { style: labelStyle }, '开票人'), - React.createElement('div', { style: valueStyle }, invoiceInfo.invoicePerson || '-') - ) - ) + ) : null ), React.createElement('div', { style: { display: 'flex', gap: 8, marginTop: 24 } }, React.createElement(Button, { type: 'primary', onClick: handleSubmit }, '提交审核'), diff --git a/web端/财务管理/提车应收款-查看.jsx b/web端/财务管理/提车应收款-查看.jsx new file mode 100644 index 0000000..2eb940d --- /dev/null +++ b/web端/财务管理/提车应收款-查看.jsx @@ -0,0 +1,305 @@ +// 【重要】必须使用 const Component 作为组件变量名 +// 财务管理 - 提车应收款-查看(2026年3月10日版本) + +const Component = function () { + var useState = React.useState; + var useMemo = React.useMemo; + + var antd = window.antd; + var Breadcrumb = antd.Breadcrumb; + var Card = antd.Card; + var Button = antd.Button; + var Popover = antd.Popover; + var Steps = antd.Steps; + var Tooltip = antd.Tooltip; + var message = antd.message; + + var servicePopoverOpen = useState(null); + + // 模拟:项目信息 + var projectInfo = useMemo(function () { + return { + contractCode: 'HT-ZL-2025-001', + contractType: '正式合同', + projectName: '嘉兴氢能示范项目', + customerName: '嘉兴某某物流有限公司', + paymentMethod: '预付', + paymentCycle: '6个月', + contractStart: '2025-01-15', + contractEnd: '2026-01-14', + businessDept: '业务1部', + businessPerson: '张经理' + }; + }, []); + + // 模拟:本单提车收款单已选车辆及填写内容(只读展示) + var vehicles = useMemo(function () { + return [ + { key: 'v1', index: 1, brand: '东风', model: 'DFH1180', plateNo: '浙A12345', receivableRent: 30000, actualRent: '29800.00', rentRemark: '首期六期一次性付清', receivableDeposit: 10000, serviceItems: [{ name: '代处理费用', receivable: 200, actual: '200.00', discount: '0.00', remark: '' }, { name: '保险上浮', receivable: 500, actual: '480.00', discount: '20.00', remark: '客户协商' }], receivableService: 700, actualService: '680.00', discountAmount: '200.00', discountRemark: '首月优惠', discountProof: [{ name: '优惠审批单.pdf' }] }, + { key: 'v2', index: 2, brand: '福田', model: 'BJ1180', plateNo: '浙A23456', receivableRent: 27000, actualRent: '27000.00', rentRemark: '', receivableDeposit: 8000, serviceItems: [{ name: '保养费用', receivable: 300, actual: '300.00', discount: '0.00', remark: '含首保' }], receivableService: 300, actualService: '300.00', discountAmount: '0.00', discountRemark: '', discountProof: [] }, + { key: 'v3', index: 3, brand: '重汽', model: 'ZZ1187', plateNo: '浙A34567', receivableRent: 31200, actualRent: '31200.00', rentRemark: '按合同约定', receivableDeposit: 10000, serviceItems: [{ name: '代处理费用', receivable: 180, actual: '180.00', discount: '0.00', remark: '' }, { name: '上牌服务', receivable: 400, actual: '400.00', discount: '0.00', remark: '已含上牌' }], receivableService: 580, actualService: '580.00', discountAmount: '0.00', discountRemark: '', discountProof: [] }, + { key: 'v4', index: 4, brand: '陕汽', model: 'SX1313', plateNo: '', receivableRent: 28800, actualRent: '28500.00', rentRemark: '待交车后补全', receivableDeposit: 9000, serviceItems: [{ name: '保险上浮', receivable: 350, actual: '350.00', discount: '0.00', remark: '' }], receivableService: 350, actualService: '350.00', discountAmount: '300.00', discountRemark: '客户协商减免', discountProof: [] } + ]; + }, []); + + var totals = useMemo(function () { + var receivableRent = 0, actualRent = 0, receivableDeposit = 0, receivableService = 0, actualService = 0, discountTotal = 0; + vehicles.forEach(function (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; + }); + 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) + }; + }, [vehicles]); + + var receivableTotal = (parseFloat(totals.receivableRent) + parseFloat(totals.receivableDeposit) + parseFloat(totals.receivableService)).toFixed(2); + var actualTotal = (parseFloat(totals.actualRent) + parseFloat(totals.receivableDeposit) + parseFloat(totals.actualService) - parseFloat(totals.discountTotal)).toFixed(2); + + var receivablePopoverContent = React.createElement('div', { style: { padding: 8, minWidth: 220 } }, + React.createElement('table', { style: { width: '100%', borderCollapse: 'collapse', fontSize: 13 } }, + React.createElement('thead', null, + React.createElement('tr', null, + React.createElement('th', { style: { textAlign: 'left', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '项目'), + React.createElement('th', { style: { textAlign: 'right', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '金额') + ) + ), + React.createElement('tbody', null, + React.createElement('tr', null, React.createElement('td', { style: { padding: '6px 8px' } }, '总计应收车辆月租金'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.receivableRent + ' 元')), + React.createElement('tr', null, React.createElement('td', { style: { padding: '6px 8px' } }, '总计应收车辆保证金'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.receivableDeposit + ' 元')), + React.createElement('tr', null, React.createElement('td', { style: { padding: '6px 8px' } }, '总计应收服务费'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.receivableService + ' 元')) + ) + ) + ); + + var actualPopoverContent = React.createElement('div', { style: { padding: 8, minWidth: 220 } }, + React.createElement('table', { style: { width: '100%', borderCollapse: 'collapse', fontSize: 13 } }, + React.createElement('thead', null, + React.createElement('tr', null, + React.createElement('th', { style: { textAlign: 'left', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '项目'), + React.createElement('th', { style: { textAlign: 'right', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '金额') + ) + ), + React.createElement('tbody', null, + React.createElement('tr', null, React.createElement('td', { style: { padding: '6px 8px' } }, '总计实收车辆月租金'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.actualRent + ' 元')), + React.createElement('tr', null, React.createElement('td', { style: { padding: '6px 8px' } }, '总计应收车辆保证金'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.receivableDeposit + ' 元')), + React.createElement('tr', null, React.createElement('td', { style: { padding: '6px 8px' } }, '总计实收服务费'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.actualService + ' 元')), + React.createElement('tr', null, React.createElement('td', { style: { padding: '6px 8px' } }, '总计减免金额'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.discountTotal + ' 元')) + ) + ) + ); + + // 客户付款信息:到账/开票历史 + var paymentList = useMemo(function () { + return [ + { id: '1', arrivalTime: '2026-03-01 10:00', arrivalAmount: '50000.00', invoiceTime: '2026-03-02 14:30', invoiceFiles: [{ name: '发票-HT-ZL-2025-001-001.pdf' }], remark: '首笔到账' }, + { id: '2', arrivalTime: '2026-03-10 09:00', arrivalAmount: '35280.00', invoiceTime: '2026-03-11 11:00', invoiceFiles: [{ name: '发票-HT-ZL-2025-001-002.pdf' }], remark: '第二笔' } + ]; + }, []); + var sumArrival = paymentList.reduce(function (s, r) { return s + (parseFloat(r.arrivalAmount) || 0); }, 0); + var unpaidAmount = (parseFloat(actualTotal) - sumArrival).toFixed(2); + if (parseFloat(unpaidAmount) < 0) unpaidAmount = '0.00'; + + // 审批情况:竖向步骤条 + var approvalSteps = useMemo(function () { + return [ + { 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: '-' } + ]; + }, []); + + var layoutStyle = { padding: '16px 24px', background: '#f5f5f5', minHeight: '100vh' }; + var cardStyle = { marginBottom: 16 }; + var labelStyle = { marginBottom: 6, fontSize: 14, color: 'rgba(0,0,0,0.65)' }; + var formRowStyle = { display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '16px 24px', marginBottom: 16 }; + var formItemStyle = { marginBottom: 12 }; + var highlightStyle = { color: '#1890ff', fontWeight: 600, cursor: 'pointer' }; + var valueStyle = { color: 'rgba(0,0,0,0.85)', fontSize: 14, lineHeight: '22px', minHeight: 22 }; + var thBase = { padding: '10px 12px', border: '1px solid #f0f0f0', whiteSpace: 'nowrap', backgroundColor: '#fafafa' }; + var tdBase = { padding: '8px 12px', border: '1px solid #f0f0f0', fontSize: 13 }; + + function handleBack() { + if (window.__receivableBack) window.__receivableBack(); + else message.info('返回提车应收款列表(原型)'); + } + + return React.createElement('div', { style: layoutStyle }, + React.createElement('div', { style: { marginBottom: 16 } }, + React.createElement(Breadcrumb, { + items: [ + { title: '财务管理' }, + { title: '提车应收款' }, + { title: '提车收款单' } + ] + }) + ), + React.createElement(Card, { title: '项目信息', style: cardStyle }, + React.createElement('div', { style: formRowStyle }, + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '合同编码'), React.createElement('div', { style: valueStyle }, projectInfo.contractCode || '—')), + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '合同类型'), React.createElement('div', { style: valueStyle }, projectInfo.contractType || '—')), + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '项目名称'), React.createElement('div', { style: valueStyle }, projectInfo.projectName || '—')), + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '客户名称'), React.createElement('div', { style: valueStyle }, projectInfo.customerName || '—')), + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '付款方式'), React.createElement('div', { style: valueStyle }, projectInfo.paymentMethod || '—')), + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '付款周期'), React.createElement('div', { style: valueStyle }, projectInfo.paymentCycle || '—')), + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '合同生效时间'), React.createElement('div', { style: valueStyle }, projectInfo.contractStart || '—')), + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '合同结束时间'), React.createElement('div', { style: valueStyle }, projectInfo.contractEnd || '—')), + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '业务部门'), React.createElement('div', { style: valueStyle }, projectInfo.businessDept || '—')), + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '业务负责人'), React.createElement('div', { style: valueStyle }, projectInfo.businessPerson || '—')) + ) + ), + React.createElement(Card, { title: '提车应收款信息', style: cardStyle }, + React.createElement(React.Fragment, null, + React.createElement('div', { style: { marginBottom: 16, display: 'flex', gap: 24, alignItems: 'center' } }, + React.createElement(Popover, { content: receivablePopoverContent, title: '应收款明细', trigger: 'click' }, + React.createElement('span', { style: { cursor: 'pointer' } }, '应收款总额:', React.createElement('span', { style: highlightStyle }, receivableTotal, ' 元')) + ), + React.createElement(Popover, { content: actualPopoverContent, title: '实收款明细', trigger: 'click' }, + React.createElement('span', { style: { cursor: 'pointer' } }, '实收款总额:', React.createElement('span', { style: highlightStyle }, actualTotal, ' 元')) + ) + ), + React.createElement('div', { style: { overflowX: 'auto', marginBottom: 0 } }, + React.createElement('table', { style: { width: '100%', minWidth: 1500, borderCollapse: 'collapse', fontSize: 13, tableLayout: 'fixed' } }, + React.createElement('thead', null, + React.createElement('tr', null, + React.createElement('th', { style: Object.assign({}, thBase, { width: 50 }) }, '序号'), + React.createElement('th', { style: Object.assign({}, thBase, { width: 80 }) }, '品牌'), + React.createElement('th', { style: Object.assign({}, thBase, { width: 90 }) }, '型号'), + React.createElement('th', { style: Object.assign({}, thBase, { width: 100 }) }, '车牌号'), + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'right', width: 120 }) }, '应收车辆月租金'), + React.createElement('th', { style: Object.assign({}, thBase, { width: 120 }) }, '实收车辆月租金'), + React.createElement('th', { style: Object.assign({}, thBase, { width: 130 }) }, '车辆租金备注'), + React.createElement('th', { style: Object.assign({}, thBase, { width: 100 }) }, '减免金额'), + React.createElement('th', { style: Object.assign({}, thBase, { width: 120 }) }, '减免金额备注'), + React.createElement('th', { style: Object.assign({}, thBase, { width: 100 }) }, '减免证明'), + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'right', width: 110 }) }, '应收车辆保证金'), + React.createElement('th', { style: Object.assign({}, thBase, { width: 90 }) }, '服务费项目'), + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'right', width: 90 }) }, '应收服务费'), + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'right', width: 100 }) }, '实收服务费') + ) + ), + React.createElement('tbody', null, + vehicles.map(function (row) { + var servicePopover = React.createElement('div', { style: { padding: 8, minWidth: 320 } }, + React.createElement('div', { style: { fontWeight: 600, marginBottom: 8 } }, '服务费项目'), + React.createElement('table', { style: { width: '100%', borderCollapse: 'collapse', fontSize: 12 } }, + React.createElement('thead', null, + React.createElement('tr', null, + React.createElement('th', { style: { textAlign: 'left', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '服务项目'), + React.createElement('th', { style: { textAlign: 'right', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '应收费用'), + React.createElement('th', { style: { textAlign: 'right', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '实收费用'), + React.createElement('th', { style: { textAlign: 'right', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '减免费用'), + React.createElement('th', { style: { textAlign: 'left', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '备注') + ) + ), + React.createElement('tbody', null, + (row.serviceItems || []).map(function (s, si) { + return React.createElement('tr', { key: si }, + React.createElement('td', { style: { padding: '6px 8px' } }, s.name), + React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, (s.receivable != null ? s.receivable : '') + ' 元'), + React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, (s.actual != null ? s.actual : '') + ' 元'), + React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, (s.discount != null ? s.discount : '0.00') + ' 元'), + React.createElement('td', { style: { padding: '6px 8px' } }, s.remark || '—') + ); + }) + ) + ) + ); + var proofNames = (row.discountProof || []).map(function (p) { return p.name; }).join('、') || '—'; + return React.createElement('tr', { key: row.key }, + React.createElement('td', { style: tdBase }, row.index), + React.createElement('td', { style: tdBase }, row.brand), + React.createElement('td', { style: tdBase }, row.model), + React.createElement('td', { style: tdBase }, row.plateNo || '—'), + React.createElement('td', { style: Object.assign({}, tdBase, { textAlign: 'right' }) }, (row.receivableRent || 0) + ' 元'), + React.createElement('td', { style: tdBase }, (row.actualRent || '0.00') + ' 元'), + React.createElement('td', { style: tdBase }, row.rentRemark || '—'), + React.createElement('td', { style: tdBase }, (row.discountAmount || '0.00') + ' 元'), + React.createElement('td', { style: tdBase }, row.discountRemark || '—'), + React.createElement('td', { style: tdBase }, proofNames), + React.createElement('td', { style: Object.assign({}, tdBase, { textAlign: 'right' }) }, (row.receivableDeposit || 0) + ' 元'), + React.createElement('td', { style: tdBase }, + React.createElement(Popover, { content: servicePopover, title: null, trigger: 'click', open: servicePopoverOpen[0] === row.key, onOpenChange: function (open) { servicePopoverOpen[1](open ? row.key : null); } }, + React.createElement(Button, { type: 'link', size: 'small', style: { padding: 0 } }, '管理') + ) + ), + React.createElement('td', { style: Object.assign({}, tdBase, { textAlign: 'right' }) }, (row.receivableService || 0) + ' 元'), + React.createElement('td', { style: Object.assign({}, tdBase, { textAlign: 'right' }) }, (row.actualService || '0.00') + ' 元') + ); + }) + ) + ) + ), + React.createElement('div', { style: { marginTop: 16, display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', gap: 16, maxWidth: 800 } }, + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '氢费预付款应收金额'), React.createElement('div', { style: valueStyle }, '3580.00 元')), + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '氢费预付款实收金额'), React.createElement('div', { style: valueStyle }, '3500.00 元')), + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '减免金额'), React.createElement('div', { style: valueStyle }, '80.00 元')), + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '减免金额备注'), React.createElement('div', { style: valueStyle }, '预付款批量减免')) + ), + React.createElement('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px 24px', marginTop: 16 } }, + React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '开票方式'), React.createElement('div', { style: valueStyle }, '先开票后付款')), + React.createElement('div', { style: Object.assign({}, formItemStyle, { gridColumn: '1 / -1' }) }, React.createElement('div', { style: labelStyle }, '开票备注'), React.createElement('div', { style: valueStyle }, '增值税专用发票,税率13%,开票项目:*现代服务*车辆租赁费;备注:嘉兴氢能示范项目-提车首付款')) + ) + ) + ), + React.createElement(Card, { title: '客户付款信息', style: cardStyle }, + React.createElement('div', { style: { marginBottom: 12, fontSize: 14 } }, '未付金额:', React.createElement('span', { style: highlightStyle }, unpaidAmount, ' 元')), + React.createElement('table', { style: { width: '100%', borderCollapse: 'collapse', fontSize: 13 } }, + React.createElement('thead', null, + React.createElement('tr', null, + React.createElement('th', { style: Object.assign({}, thBase, { width: 160 }) }, '到账时间'), + React.createElement('th', { style: Object.assign({}, thBase, { width: 120 }) }, '到账金额'), + React.createElement('th', { style: Object.assign({}, thBase, { width: 160 }) }, '开票时间'), + React.createElement('th', { style: thBase }, '发票附件'), + React.createElement('th', { style: thBase }, '备注') + ) + ), + React.createElement('tbody', null, + paymentList.map(function (r) { + var files = (r.invoiceFiles || []).map(function (f, i) { return React.createElement(Button, { key: i, type: 'link', size: 'small', style: { padding: 0, height: 'auto' }, onClick: function () { message.info('预览:' + (f.name || '附件')); } }, f.name || '附件'); }); + var remarkEl = r.remark && r.remark.length > 20 + ? React.createElement(Tooltip, { title: r.remark }, React.createElement('span', { style: { display: 'inline-block', maxWidth: 160, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, r.remark)) + : (r.remark || '—'); + return React.createElement('tr', { key: r.id }, + React.createElement('td', { style: tdBase }, r.arrivalTime || '—'), + React.createElement('td', { style: tdBase }, r.arrivalAmount ? r.arrivalAmount + ' 元' : '—'), + React.createElement('td', { style: tdBase }, r.invoiceTime || '—'), + React.createElement('td', { style: tdBase }, files.length ? React.createElement('span', null, files) : '—'), + React.createElement('td', { style: tdBase }, remarkEl) + ); + }) + ) + ) + ), + React.createElement(Card, { title: '审批情况', style: cardStyle }, + React.createElement(Steps, { + direction: 'vertical', + current: approvalSteps.findIndex(function (s) { return s.status === '待审批'; }) >= 0 ? approvalSteps.findIndex(function (s) { return s.status === '待审批'; }) : approvalSteps.length, + style: { paddingLeft: 8 }, + items: approvalSteps.map(function (step, idx) { + return { + title: step.department || step.title, + description: React.createElement('div', { style: { marginTop: 4, fontSize: 13, color: 'rgba(0,0,0,0.65)' } }, + React.createElement('div', null, '审批状态:', step.status), + React.createElement('div', null, '审批人:', step.person), + React.createElement('div', null, '审批时间:', step.approveTime) + ), + status: step.status === '已通过' ? 'finish' : step.status === '待审批' ? 'wait' : 'process' + }; + }) + }) + ), + React.createElement('div', { style: { marginTop: 24 } }, + React.createElement(Button, { onClick: handleBack }, '返回') + ) + ); +}; diff --git a/web端/财务管理/提车应收款.jsx b/web端/财务管理/提车应收款.jsx new file mode 100644 index 0000000..705afc5 --- /dev/null +++ b/web端/财务管理/提车应收款.jsx @@ -0,0 +1,465 @@ +// 【重要】必须使用 const Component 作为组件变量名 +// 财务管理 - 提车应收款(2026年3月5日版本) + +const Component = function () { + var useState = React.useState; + var useMemo = React.useMemo; + + var antd = window.antd; + var Breadcrumb = antd.Breadcrumb; + var Card = antd.Card; + var Table = antd.Table; + var Button = antd.Button; + var Select = antd.Select; + var Input = antd.Input; + var Space = antd.Space; + var Popover = antd.Popover; + var Tag = antd.Tag; + var Modal = antd.Modal; + var message = antd.message; + + // 筛选条件 + var filterContractCode = useState(undefined); + var filterProjectName = useState(undefined); + var filterCustomerName = useState(undefined); + var filterBusinessDept = useState(undefined); + var filterBusinessPerson = useState(undefined); + // 主表展开行:只允许同时展开一行,展开新的则收起其他 + var expandedRowKeysState = useState([]); + var expandedRowKeys = expandedRowKeysState[0]; + var setExpandedRowKeys = expandedRowKeysState[1]; + var deleteConfirmVisible = useState(false); + var deleteConfirmRecord = useState(null); + var deleteConfirmParentRecord = useState(null); + var requirementModalVisible = useState(false); + var filterExpanded = useState(false); + + // 主表数据:按合同编码聚合,每条主表下有多条提车应收款明细(子表) + var mainListData = [ + { + contractCode: 'HT-ZL-2025-001', + contractType: '正式合同', + projectName: '嘉兴氢能示范项目', + customerName: '嘉兴某某物流有限公司', + businessDept: '业务1部', + businessPerson: '张经理', + contractEffectiveDate: '2025-01-15', + totalReceivable: 256800.00, + totalActual: 255700.00, + totalDiscount: 1100.00, + totalFinanceReceived: 209900.00, + children: [ + { seq: 1, auditStatus: '审批通过', creator: '张三', chargeTime: '2026-02-20 10:00', deliveryCount: 3, deliveryVehicles: [{ brand: '东风', model: 'DFH1180', plateNo: '浙A12345' }, { brand: '福田', model: 'BJ1180', plateNo: '浙A23456' }, { brand: '重汽', model: 'ZZ1187', plateNo: '浙A34567' }], receivableTotal: 98700.00, actualTotal: 98200.00, discountTotal: 500.00, discountProof: '首月优惠审批单', arrivalAmount: 98200.00, arrivalTime: '2026-02-21 14:30', financeReceived: 98200.00, isInvoiced: '已开票', invoiceMethod: '先开票后付款', invoiceTime: '2026-02-22 11:00', invoicedAmount: 98200.00, invoiceAttachment: '发票-HT-ZL-2025-001-001.pdf' }, + { seq: 2, auditStatus: '待审批', creator: '李四', chargeTime: '2026-03-01 09:30', deliveryCount: 2, deliveryVehicles: [{ brand: '陕汽', model: 'SX1313', plateNo: '浙A45678' }, { brand: '解放', model: 'J6P', plateNo: '浙A56789' }], receivableTotal: 57300.00, actualTotal: 57300.00, discountTotal: 0.00, discountProof: '-', arrivalAmount: 57300.00, arrivalTime: '2026-03-02 10:00', financeReceived: 57300.00, isInvoiced: '部分开票', invoiceMethod: '先付款后开票', invoiceTime: '-', invoicedAmount: '0.00', invoiceAttachment: '-' }, + { seq: 3, auditStatus: '审批中', creator: '王五', chargeTime: '2026-03-10 11:00', deliveryCount: 1, deliveryVehicles: [{ brand: '江淮', model: '格尔发K5', plateNo: '浙A67890' }], receivableTotal: 32800.00, actualTotal: 32800.00, discountTotal: 0.00, discountProof: '-', arrivalAmount: 32800.00, arrivalTime: '2026-03-11 09:00', financeReceived: 32800.00, isInvoiced: '未开票', invoiceMethod: '先付款后开票', invoiceTime: '-', invoicedAmount: '0.00', invoiceAttachment: '-' }, + { seq: 4, auditStatus: '已驳回', creator: '赵六', chargeTime: '2026-03-15 14:20', deliveryCount: 2, deliveryVehicles: [{ brand: '东风', model: 'DFH1250', plateNo: '浙A11111' }, { brand: '福田', model: '欧曼EST', plateNo: '浙A22222' }], receivableTotal: 45800.00, actualTotal: 45800.00, discountTotal: 0.00, discountProof: '-', arrivalAmount: 0.00, arrivalTime: '-', financeReceived: 0.00, isInvoiced: '未开票', invoiceMethod: '先付款后开票', invoiceTime: '-', invoicedAmount: '0.00', invoiceAttachment: '-' }, + { seq: 5, auditStatus: '审批通过', creator: '张三', chargeTime: '2026-03-20 09:00', deliveryCount: 2, deliveryVehicles: [{ brand: '重汽', model: '豪沃T7H', plateNo: '浙B33333' }, { brand: '陕汽', model: '德龙X3000', plateNo: '浙B44444' }], receivableTotal: 22200.00, actualTotal: 21600.00, discountTotal: 600.00, discountProof: '客户协商减免', arrivalAmount: 21600.00, arrivalTime: '2026-03-21 10:30', financeReceived: 21600.00, isInvoiced: '已开票', invoiceMethod: '先开票后付款', invoiceTime: '2026-03-22 14:00', invoicedAmount: 21600.00, invoiceAttachment: '发票-HT-ZL-2025-001-005.pdf' } + ] + }, + { + contractCode: 'HT-ZL-2025-002', + contractType: '正式合同', + projectName: '上海物流租赁项目', + customerName: '上海某某运输公司', + businessDept: '业务2部', + businessPerson: '李专员', + contractEffectiveDate: '2025-02-01', + totalReceivable: 132600.00, + totalActual: 132600.00, + totalDiscount: 0.00, + totalFinanceReceived: 132600.00, + children: [ + { seq: 1, auditStatus: '审批通过', creator: '李专员', chargeTime: '2026-02-28 14:00', deliveryCount: 2, deliveryVehicles: [{ brand: '江淮', model: '格尔发K5', plateNo: '沪B11111' }, { brand: '东风', model: 'DFH1250', plateNo: '沪B22222' }], receivableTotal: 65800.00, actualTotal: 65800.00, discountTotal: 0.00, discountProof: '-', arrivalAmount: 65800.00, arrivalTime: '2026-03-01 09:00', financeReceived: 65800.00, isInvoiced: '已开票', invoiceMethod: '先开票后付款', invoiceTime: '2026-03-02 15:20', invoicedAmount: 65800.00, invoiceAttachment: '发票-HT-ZL-2025-002.pdf' }, + { seq: 2, auditStatus: '审批通过', creator: '李专员', chargeTime: '2026-03-08 10:30', deliveryCount: 1, deliveryVehicles: [{ brand: '解放', model: 'JH6', plateNo: '沪B33333' }], receivableTotal: 33400.00, actualTotal: 33400.00, discountTotal: 0.00, discountProof: '-', arrivalAmount: 33400.00, arrivalTime: '2026-03-09 11:00', financeReceived: 33400.00, isInvoiced: '部分开票', invoiceMethod: '先付款后开票', invoiceTime: '-', invoicedAmount: '0.00', invoiceAttachment: '-' }, + { seq: 3, auditStatus: '待审批', creator: '李专员', chargeTime: '2026-03-18 16:00', deliveryCount: 2, deliveryVehicles: [{ brand: '福田', model: 'BJ1180', plateNo: '沪B44444' }, { brand: '重汽', model: 'ZZ1187', plateNo: '沪B55555' }], receivableTotal: 33400.00, actualTotal: 33400.00, discountTotal: 0.00, discountProof: '-', arrivalAmount: 33400.00, arrivalTime: '2026-03-19 09:00', financeReceived: 33400.00, isInvoiced: '未开票', invoiceMethod: '先开票后付款', invoiceTime: '-', invoicedAmount: '0.00', invoiceAttachment: '-' } + ] + }, + { + contractCode: 'HT-ZL-2025-003', + contractType: '正式合同', + projectName: '杭州城配租赁项目', + customerName: '杭州某某租赁有限公司', + businessDept: '业务3部', + businessPerson: '王专员', + contractEffectiveDate: '2025-02-10', + totalReceivable: 82400.00, + totalActual: 82400.00, + totalDiscount: 0.00, + totalFinanceReceived: 41200.00, + children: [ + { seq: 1, auditStatus: '已驳回', creator: '王专员', chargeTime: '2026-03-05 11:00', deliveryCount: 1, deliveryVehicles: [{ brand: '福田', model: '欧曼EST', plateNo: '浙C33333' }], receivableTotal: 41200.00, actualTotal: 41200.00, discountTotal: 0.00, discountProof: '-', arrivalAmount: 0.00, arrivalTime: '-', financeReceived: 0.00, isInvoiced: '未开票', invoiceMethod: '先付款后开票', invoiceTime: '-', invoicedAmount: '0.00', invoiceAttachment: '-' }, + { seq: 2, auditStatus: '审批通过', creator: '王专员', chargeTime: '2026-03-12 09:15', deliveryCount: 1, deliveryVehicles: [{ brand: '东风', model: 'DFH1180', plateNo: '浙C44444' }], receivableTotal: 41200.00, actualTotal: 41200.00, discountTotal: 0.00, discountProof: '-', arrivalAmount: 41200.00, arrivalTime: '2026-03-13 14:00', financeReceived: 41200.00, isInvoiced: '已开票', invoiceMethod: '先开票后付款', invoiceTime: '2026-03-14 10:00', invoicedAmount: 41200.00, invoiceAttachment: '发票-HT-ZL-2025-003.pdf' } + ] + } + ]; + var mainListState = useState(mainListData); + var mainList = mainListState[0]; + var setMainList = mainListState[1]; + + var filterOptions = useMemo(function () { + var list = mainList; + var codes = [], projects = [], customers = [], depts = [], persons = []; + list.forEach(function (r) { + if (r.contractCode && codes.indexOf(r.contractCode) === -1) codes.push(r.contractCode); + if (r.projectName && projects.indexOf(r.projectName) === -1) projects.push(r.projectName); + if (r.customerName && customers.indexOf(r.customerName) === -1) customers.push(r.customerName); + if (r.businessDept && depts.indexOf(r.businessDept) === -1) depts.push(r.businessDept); + if (r.businessPerson && persons.indexOf(r.businessPerson) === -1) persons.push(r.businessPerson); + }); + return { + contractCode: codes.map(function (v) { return { value: v, label: v }; }), + projectName: projects.map(function (v) { return { value: v, label: v }; }), + customerName: customers.map(function (v) { return { value: v, label: v }; }), + businessDept: depts.map(function (v) { return { value: v, label: v }; }), + businessPerson: persons.map(function (v) { return { value: v, label: v }; }) + }; + }, [mainList]); + var filteredMainList = useMemo(function () { + var list = mainList; + var code = filterContractCode[0]; + var project = filterProjectName[0]; + var customer = filterCustomerName[0]; + var dept = filterBusinessDept[0]; + var person = filterBusinessPerson[0]; + if (code) list = list.filter(function (r) { return r.contractCode === code; }); + if (project) list = list.filter(function (r) { return r.projectName === project; }); + if (customer) list = list.filter(function (r) { return r.customerName === customer; }); + if (dept) list = list.filter(function (r) { return r.businessDept === dept; }); + if (person) list = list.filter(function (r) { return r.businessPerson === person; }); + return list; + }, [mainList, filterContractCode[0], filterProjectName[0], filterCustomerName[0], filterBusinessDept[0], filterBusinessPerson[0]]); + + // 主表 dataSource 去掉 children,避免 Table 把子节点当树形数据渲染出多余空白行;子表数据通过 _detailList 在展开行内使用 + var mainTableDataSource = useMemo(function () { + return filteredMainList.map(function (r) { + var o = {}; + for (var k in r) if (k !== 'children') o[k] = r[k]; + o._detailList = r.children || []; + return o; + }); + }, [filteredMainList]); + + function fmtMoney(v) { + if (v === null || v === undefined) return '0.00元'; + var n = typeof v === 'number' ? v : parseFloat(v); + return (isNaN(n) ? '0.00' : n.toFixed(2)) + '元'; + } + + function goCollect(record) { + if (window.__receivableToCollect) window.__receivableToCollect(record); + else message.info('跳转至提车应收款-收款(原型)'); + } + function goView(record) { + message.info('查看提车收款单详情(原型)'); + } + function goEdit(record) { + if (window.__receivableToEdit) window.__receivableToEdit(record); + else message.info('跳转至提车应收款-编辑(原型)'); + } + function goInvoice(record) { + if (window.__receivableToInvoice) window.__receivableToInvoice(record); + else message.info('跳转至开票(原型)'); + } + + var layoutStyle = { padding: '16px 24px', background: '#f5f5f5', minHeight: '100vh' }; + var cardStyle = { marginBottom: 16 }; + var filterLabelStyle = { marginBottom: 6, fontSize: 14, color: 'rgba(0,0,0,0.65)' }; + var filterItemStyle = { marginBottom: 12 }; + var filterControlStyle = { width: '100%' }; + + // 主表列(宽度压缩在一屏内显示) + var mainColumns = [ + { title: '合同编码', dataIndex: 'contractCode', key: 'contractCode', width: 120, ellipsis: true, render: function (v) { return v || '—'; } }, + { title: '合同类型', dataIndex: 'contractType', key: 'contractType', width: 88, ellipsis: true, render: function (v) { return v || '—'; } }, + { title: '项目名称', dataIndex: 'projectName', key: 'projectName', width: 120, ellipsis: true, render: function (v) { return v || '—'; } }, + { title: '客户名称', dataIndex: 'customerName', key: 'customerName', width: 120, ellipsis: true, render: function (v) { return v || '—'; } }, + { title: '合同生效日期', dataIndex: 'contractEffectiveDate', key: 'contractEffectiveDate', width: 110, render: function (v) { return v || '—'; } }, + { title: '业务部门', dataIndex: 'businessDept', key: 'businessDept', width: 100, render: function (v) { return v || '—'; } }, + { title: '业务负责人', dataIndex: 'businessPerson', key: 'businessPerson', width: 100, render: function (v) { return v || '—'; } }, + { + title: '操作', + key: 'action', + width: 100, + fixed: 'right', + render: function (_, record) { + return React.createElement(Button, { type: 'link', size: 'small', onClick: function () { goCollect(record); } }, '提车收款单'); + } + } + ]; + + // 子表:提车数量气泡用 state 控制当前展开的行 + var deliveryPopoverOpen = useState(null); + + function renderDeliveryPopover(record) { + var vehicles = record.deliveryVehicles || []; + var listStyle = { width: '100%', borderCollapse: 'collapse', fontSize: 13 }; + var thStyle = { padding: '6px 10px', textAlign: 'left', borderBottom: '1px solid #f0f0f0', backgroundColor: '#fafafa', fontWeight: 600 }; + var tdStyle = { padding: '6px 10px', borderBottom: '1px solid #f0f0f0' }; + var content = vehicles.length === 0 ? React.createElement('div', { style: { padding: 8 } }, '—') : React.createElement('div', { style: { padding: 0, minWidth: 200 } }, + React.createElement('table', { style: listStyle }, + React.createElement('thead', null, + React.createElement('tr', null, + React.createElement('th', { style: thStyle }, '品牌'), + React.createElement('th', { style: thStyle }, '型号'), + React.createElement('th', { style: thStyle }, '车牌号') + ) + ), + React.createElement('tbody', null, + vehicles.map(function (v, i) { + return React.createElement('tr', { key: i }, + React.createElement('td', { style: tdStyle }, v.brand || '—'), + React.createElement('td', { style: tdStyle }, v.model || '—'), + React.createElement('td', { style: tdStyle }, v.plateNo || '—') + ); + }) + ) + ) + ); + var popoverKey = (record._parentCode || '') + '-' + record.seq + '-' + (record.chargeTime || ''); + return React.createElement(Popover, { + content: content, + title: '车辆详情', + trigger: 'click', + open: deliveryPopoverOpen[0] === popoverKey, + onOpenChange: function (open) { deliveryPopoverOpen[1](open ? popoverKey : null); } + }, React.createElement(Button, { type: 'link', size: 'small', style: { padding: 0 } }, String(record.deliveryCount))); + } + + // 子表左缩进与主表展开列宽一致,使序号左侧边框与合同编码左侧边框对齐 + var expandColumnWidth = 48; + var subColumns = [ + { title: '序号', dataIndex: 'seq', key: 'seq', width: 52, align: 'center' }, + { + title: '审批状态', + dataIndex: 'auditStatus', + key: 'auditStatus', + width: 90, + render: function (v, record) { + var status = (record && record.auditStatus != null) ? String(record.auditStatus).trim() : (v != null ? String(v).trim() : ''); + var isRejected = status === '已驳回' || status === '审批驳回'; + var color = status === '审批通过' ? 'success' : status === '待审批' ? 'processing' : status === '审批中' ? 'warning' : isRejected ? 'error' : 'default'; + var text = isRejected ? '审批驳回' : (status || '—'); + return React.createElement(Tag, { color: color }, text); + } + }, + { title: '创建时间', dataIndex: 'chargeTime', key: 'chargeTime', width: 128, render: function (v) { return v || '—'; } }, + { title: '创建人', dataIndex: 'creator', key: 'creator', width: 88, render: function (v) { return v || '—'; } }, + { title: '提车数量', key: 'deliveryCount', width: 76, render: function (_, record) { return renderDeliveryPopover(record); } }, + { title: '应收款总额', dataIndex: 'receivableTotal', key: 'receivableTotal', width: 98, align: 'right', render: function (v) { return fmtMoney(v); } }, + { title: '实收款总额', dataIndex: 'actualTotal', key: 'actualTotal', width: 98, align: 'right', render: function (v) { return fmtMoney(v); } }, + { title: '减免总金额', dataIndex: 'discountTotal', key: 'discountTotal', width: 88, align: 'right', render: function (v) { return fmtMoney(v); } }, + { title: '实际到账金额', dataIndex: 'arrivalAmount', key: 'arrivalAmount', width: 100, align: 'right', render: function (v, record) { if (record && record.auditStatus === '待审批') return '—'; return fmtMoney(v); } }, + { title: '是否已开票', dataIndex: 'isInvoiced', key: 'isInvoiced', width: 88, render: function (v, record) { if (record && record.auditStatus === '待审批') return '—'; if (v === true || v === '已开票') return '已开票'; if (v === '部分开票') return '部分开票'; return '未开票'; } }, + { title: '已开票金额', dataIndex: 'invoicedAmount', key: 'invoicedAmount', width: 100, align: 'right', render: function (v, record) { if (record && record.auditStatus === '待审批') return '—'; var n = typeof v === 'number' ? v : parseFloat(v); return fmtMoney(isNaN(n) ? 0 : n); } }, + { + title: '操作', + key: 'action', + width: 180, + fixed: 'right', + render: function (_, record) { + var auditStatus = record && record.auditStatus; + var isPending = auditStatus === '待审批'; + var isRejected = auditStatus === '已驳回' || auditStatus === '审批驳回'; + var isApproved = auditStatus === '审批通过'; + var canEdit = isPending || isRejected; + var parentRecord = record && record._parentRecord; + function handleDelete() { + deleteConfirmRecord[1](record); + deleteConfirmParentRecord[1](parentRecord); + deleteConfirmVisible[1](true); + } + return React.createElement(Space, { size: 'small' }, + React.createElement(Button, { type: 'link', size: 'small', onClick: function () { goView(record); } }, '查看'), + canEdit ? React.createElement(Button, { type: 'link', size: 'small', onClick: function () { goEdit(record); } }, '编辑') : null, + (isPending || isRejected) ? React.createElement(Button, { type: 'link', size: 'small', danger: true, onClick: handleDelete }, '删除') : null, + isApproved ? React.createElement(Button, { type: 'link', size: 'small', onClick: function () { goInvoice(record); } }, '开票') : null + ); + } + } + ]; + function confirmDeleteSub() { + var parent = deleteConfirmParentRecord[0]; + var row = deleteConfirmRecord[0]; + var list = (parent && (parent._detailList || parent.children)) || []; + if (parent && row && list.length >= 0) { + var newChildren = list.filter(function (r) { return !(r.seq === row.seq && r.chargeTime === row.chargeTime); }); + // 删除后序号重新生成 1、2、3... + newChildren = newChildren.map(function (r, index) { var o = {}; for (var k in r) o[k] = r[k]; o.seq = index + 1; return o; }); + setMainList(function (prev) { + return prev.map(function (p) { + if (p.contractCode !== parent.contractCode) return p; + var next = {}; for (var k in p) next[k] = p[k]; next.children = newChildren; return next; + }); + }); + } + deleteConfirmVisible[1](false); + deleteConfirmRecord[1](null); + deleteConfirmParentRecord[1](null); + message.success('已删除'); + } + function expandRowRender(record) { + var rows = (record._detailList || []).map(function (r) { var o = {}; for (var k in r) o[k] = r[k]; o._parentCode = record.contractCode; o._parentRecord = record; return o; }); + var sumReceivable = rows.reduce(function (acc, r) { return acc + (Number(r.receivableTotal) || 0); }, 0); + var sumActual = rows.reduce(function (acc, r) { return acc + (Number(r.actualTotal) || 0); }, 0); + var sumDiscount = rows.reduce(function (acc, r) { return acc + (Number(r.discountTotal) || 0); }, 0); + var sumArrival = rows.reduce(function (acc, r) { return acc + (Number(r.arrivalAmount) || 0); }, 0); + var sumInvoiced = rows.reduce(function (acc, r) { return acc + (Number(r.invoicedAmount) || 0); }, 0); + var summaryStyle = { backgroundColor: '#fafafa', fontWeight: 600 }; + return React.createElement('div', { + style: { marginBottom: 0, paddingLeft: expandColumnWidth, boxSizing: 'border-box' }, + draggable: false, + onDragStart: function (e) { e.preventDefault(); } + }, + React.createElement(Table, { + rowKey: function (r) { return (record.contractCode || '') + '-' + (r.seq || r.chargeTime || Math.random()); }, + columns: subColumns, + dataSource: rows, + pagination: false, + size: 'small', + bordered: true, + summary: rows.length > 0 ? function () { + return React.createElement(Table.Summary, null, + React.createElement(Table.Summary.Row, { style: summaryStyle }, + React.createElement(Table.Summary.Cell, { index: 0, align: 'center' }, '总计'), + React.createElement(Table.Summary.Cell, { index: 1 }, ''), + React.createElement(Table.Summary.Cell, { index: 2 }, ''), + React.createElement(Table.Summary.Cell, { index: 3 }, ''), + React.createElement(Table.Summary.Cell, { index: 4 }, ''), + React.createElement(Table.Summary.Cell, { index: 5, align: 'right' }, fmtMoney(sumReceivable)), + React.createElement(Table.Summary.Cell, { index: 6, align: 'right' }, fmtMoney(sumActual)), + React.createElement(Table.Summary.Cell, { index: 7, align: 'right' }, fmtMoney(sumDiscount)), + React.createElement(Table.Summary.Cell, { index: 8, align: 'right' }, fmtMoney(sumArrival)), + React.createElement(Table.Summary.Cell, { index: 9 }, ''), + React.createElement(Table.Summary.Cell, { index: 10, align: 'right' }, fmtMoney(sumInvoiced)), + React.createElement(Table.Summary.Cell, { index: 11 }, '') + ) + ); + } : null + }) + ); + } + + var requirementContent = '提车应收款(2026年3月5日版本)\n一个「数字化资产ONEOS运管平台」中的「财务管理」「提车应收款」模块\n#面包屑:财务管理-提车应收款;\n\n页面分为2个卡片;\n1.筛选:\n#支持合同编码/项目名称/客户名称/业务部门/业务负责人等筛选方式;\n1.1.合同编码:选择器,支持输入框内手动输入下拉模糊匹配对应项;\n1.2.项目名称:选择器,支持输入框内手动输入下拉模糊匹配对应项;\n1.3.客户名称:选择器,支持输入框内手动输入下拉模糊匹配对应项;\n1.4.业务部门:选择器,支持选择所有业务部门;\n1.5.业务负责人:选择器,支持选择所有业务负责人;\n\n2.列表:\n#列表展示方式为:嵌套子表格,分为主表和子表,可点击主表展开子表,一个合同编码可以展开多个提车应收款明细;\n2.1.主表显示字段为:合同编码、合同类型、项目名称、客户名称、合同生效日期、业务部门、业务负责人、操作;\n2.1.1.合同编码:显示该提车应收款租赁合同对应合同编码;\n2.1.2.合同类型:显示该租赁合同对应合同类型,显示该合同为试用合同还是正式合同;\n2.1.3.项目名称:显示该租赁合同对应项目名称;\n2.1.4.客户名称:显示该租赁合同对应客户名称;\n2.1.5.合同生效日期:显示该租赁合同生效日期,格式为:YYYY-MM-DD;\n2.1.6.业务部门:显示该租赁合同对应业务部门名称;\n2.1.7.业务负责人:显示该租赁合同对应业务负责人;\n2.1.8.操作:提车收款单;\n 2.1.8.1.提车收款单:点击提车收款单,跳转提车收款单页进行子表创建;\n主表右下角为分页符,支持选择单页显示多少条数据;\n\n2.2.子表显示字段为:序号、审批状态、创建时间、提车数量、应收款总额、实收款总额、减免总金额、实际到账金额、是否已开票、已开票金额、操作;\n2.2.1.序号:按照提车首付款收费单据提交时间从最早开始显示第1条,按照顺序1.2.3....;\n2.2.2.审批状态:审批通过、待审批、审批中、审批驳回;\n 2.2.2.1.审批通过:该审批通过最终节点审批,显示为审批通过;\n 2.2.2.2.待审批:审批提车收款单仅保存,但未提交审批;\n 2.2.2.3.审批中:已发起审批流程,但未完成最终节点审批;\n 2.2.2.4.审批驳回:发起审批流程,但任意节点被驳回。被驳回的提车收款单支持重新修改后提交审批;\n2.2.3.创建时间:显示提车收款单创建时间,格式为:YYYY-MM-DD HH:MM;\n2.2.4.创建人:显示已提车收款单创建人姓名;\n2.2.5.提车数量:显示该提车收费单对应提车数量,点击提车数量弹出气泡卡片,卡片中列表显示品牌、型号、车牌号;\n2.2.6.应收款金额:按照该提车收费单据应收款金额,格式为:xx.xx元;\n2.2.7.实收款金额:按照该提车收费单据实收款金额,格式为:xx.xx元;\n2.2.8.减免金额:按照该提车收费单减免总金额,显示当前子表对应减免总额,格式为:xx.xx元;\n2.2.9.实际到账金额:显示该提车收费单财务填写的实际到账金额,格式为:xx.xx元;\n2.2.10.是否已开票:显示财务是否完成开票,已开票显示为:已开票,未开票显示为:未开票,部分开票显示为:部分开票;\n2.2.11.已开票金额:显示财务已开票金额,格式为:xx.xx元;\n2.2.12.总计:显示应收款总额、实收款总额、减免总金额、实际到账金额、已开票金额等所有子表求和数据;\n2.2.13.操作:查看、编辑、删除、开票;\n 2.2.12.1.查看:点击查看后,跳转提车应收款-查看页面,审批通过、待审批、审批中、审批驳回均可点击查看来查看提车收款单详情;\n 2.2.12.2.编辑:点击编辑后,跳转提车应收款-编辑页面,待审批、审批驳回均可进行编辑操作;\n 2.2.12.2.删除:待审批、审批驳回状态子表支持删除功能,点击弹出二次确认,点击确认后删除该记录;\n 2.2.12.3.开票:点击开票后,进入提车应收款-开票页面,审批通过状态子表才显示,开票由财务人员操作,需要做权限控制,其他用户没有开票入口;'; + + return React.createElement('div', { style: layoutStyle }, + React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 } }, + React.createElement(Breadcrumb, { + items: [ + { title: '财务管理' }, + { title: '提车应收款' } + ] + }), + React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { requirementModalVisible[1](true); } }, '查看需求说明') + ), + React.createElement(Card, { title: '筛选', style: cardStyle }, + React.createElement('div', { + style: { display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '16px 24px', alignItems: 'start' } + }, + React.createElement('div', { style: filterItemStyle }, + React.createElement('div', { style: filterLabelStyle }, '合同编码'), + React.createElement(Select, { + placeholder: '请选择或输入合同编码', + allowClear: true, + showSearch: true, + style: filterControlStyle, + value: filterContractCode[0], + onChange: function (v) { filterContractCode[1](v); }, + options: filterOptions.contractCode, + filterOption: function (input, opt) { return (opt.label || '').toString().toLowerCase().indexOf((input || '').toLowerCase()) !== -1; } + }) + ), + React.createElement('div', { style: filterItemStyle }, + React.createElement('div', { style: filterLabelStyle }, '项目名称'), + React.createElement(Select, { + placeholder: '请选择或输入项目名称', + allowClear: true, + showSearch: true, + style: filterControlStyle, + value: filterProjectName[0], + onChange: function (v) { filterProjectName[1](v); }, + options: filterOptions.projectName, + filterOption: function (input, opt) { return (opt.label || '').toString().toLowerCase().indexOf((input || '').toLowerCase()) !== -1; } + }) + ), + React.createElement('div', { style: filterItemStyle }, + React.createElement('div', { style: filterLabelStyle }, '客户名称'), + React.createElement(Select, { + placeholder: '请选择或输入客户名称', + allowClear: true, + showSearch: true, + style: filterControlStyle, + value: filterCustomerName[0], + onChange: function (v) { filterCustomerName[1](v); }, + options: filterOptions.customerName, + filterOption: function (input, opt) { return (opt.label || '').toString().toLowerCase().indexOf((input || '').toLowerCase()) !== -1; } + }) + ), + filterExpanded[0] ? React.createElement('div', { style: filterItemStyle }, + React.createElement('div', { style: filterLabelStyle }, '业务部门'), + React.createElement(Select, { + placeholder: '请选择业务部门', + allowClear: true, + style: filterControlStyle, + value: filterBusinessDept[0], + onChange: function (v) { filterBusinessDept[1](v); }, + options: filterOptions.businessDept + }) + ) : null, + filterExpanded[0] ? React.createElement('div', { style: filterItemStyle }, + React.createElement('div', { style: filterLabelStyle }, '业务负责人'), + React.createElement(Select, { + placeholder: '请选择业务负责人', + allowClear: true, + style: filterControlStyle, + value: filterBusinessPerson[0], + onChange: function (v) { filterBusinessPerson[1](v); }, + options: filterOptions.businessPerson + }) + ) : null + ), + React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 16 } }, + React.createElement(Button, { onClick: function () { filterContractCode[1](undefined); filterProjectName[1](undefined); filterCustomerName[1](undefined); filterBusinessDept[1](undefined); filterBusinessPerson[1](undefined); } }, '重置'), + React.createElement(Button, { type: 'primary' }, '查询'), + React.createElement(Button, { type: 'link', size: 'small', onClick: function () { filterExpanded[1](!filterExpanded[0]); } }, filterExpanded[0] ? '收起' : '展开') + ) + ), + React.createElement(Card, { title: '列表', style: cardStyle }, + React.createElement(Table, { + rowKey: 'contractCode', + columns: mainColumns, + dataSource: mainTableDataSource, + expandable: { + expandedRowKeys: expandedRowKeys, + onExpandedRowsChange: function (keys) { + setExpandedRowKeys(keys.length ? [keys[keys.length - 1]] : []); + }, + expandedRowRender: expandRowRender, + rowExpandable: function (record) { return (record._detailList && record._detailList.length > 0); }, + columnWidth: expandColumnWidth + }, + pagination: { pageSize: 10, showSizeChanger: true, showTotal: function (t) { return '共 ' + t + ' 条'; } }, + size: 'middle', + bordered: true, + scroll: { x: 758 } + }) + ), + React.createElement(Modal, { + title: '确认删除', + open: deleteConfirmVisible[0], + onCancel: function () { deleteConfirmVisible[1](false); deleteConfirmRecord[1](null); deleteConfirmParentRecord[1](null); }, + onOk: confirmDeleteSub, + okText: '确认', + cancelText: '取消' + }, '确定要删除该提车收款单记录吗?'), + React.createElement(Modal, { + title: '需求说明', + open: requirementModalVisible[0], + onCancel: function () { requirementModalVisible[1](false); }, + width: 720, + footer: React.createElement(Button, { onClick: function () { requirementModalVisible[1](false); } }, '关闭'), + bodyStyle: { maxHeight: '70vh', overflow: 'auto' } + }, React.createElement('div', { style: { padding: '8px 0' } }, + React.createElement('div', { style: { whiteSpace: 'pre-wrap', fontSize: 13, lineHeight: 1.6 } }, requirementContent)) + ) + ); +}; diff --git a/web端/财务管理/提车收款-收费明细-财务提交.jsx b/web端/财务管理/提车收款-收费明细-财务提交.jsx deleted file mode 100644 index 016e297..0000000 --- a/web端/财务管理/提车收款-收费明细-财务提交.jsx +++ /dev/null @@ -1,292 +0,0 @@ -// 【重要】必须使用 const Component 作为组件变量名 -// 提车应收款 - 收费明细(租赁费用管理-提车应收款,第二步由财务填写) - -const Component = function() { - var useState = React.useState; - var useCallback = React.useCallback; - var useMemo = React.useMemo; - var antd = window.antd; - var DatePicker = antd ? antd.DatePicker : null; - var Input = antd ? antd.Input : null; - var Upload = antd ? antd.Upload : null; - var Button = antd ? antd.Button : null; - - // 模拟:项目信息、车辆(业务提交后数据)、氢费(业务提交后)、开票基础信息 - var mockProject = useMemo(function() { - return { contractCode: 'HT-ZL-2025-001', contractType: '正式合同', projectName: '北京朝阳区租赁项目', customerName: '某某科技有限公司', paymentMethod: '预付', paymentCycle: '1个月', department: '运营部', responsible: '张三' }; - }, []); - var mockVehicleList = useMemo(function() { - return [ - { brand: '奔驰', model: 'E300L', plateNo: '京A12345', payableRent: '8000.00', rentRemark: '首月', deposit: '2000.00', serviceItems: [{ name: '保养服务', fee: '300.00', remark: '' }, { name: '保险', fee: '200.00', remark: '' }], serviceTotal: '500.00', discount: '0', discountRemark: '' }, - { brand: '宝马', model: '530Li', plateNo: '京B67890', payableRent: '7000.00', rentRemark: '', deposit: '1500.00', serviceItems: [{ name: '保养服务', fee: '250.00', remark: '' }], serviceTotal: '250.00', discount: '0', discountRemark: '' } - ]; - }, []); - var mockHydrogen = useMemo(function() { - return { paidTotal: '3580.00', discount: '0.00', discountRemark: '' }; - }, []); - var mockInvoice = useMemo(function() { - return { customerName: '某某科技有限公司', taxId: '91330400MA2XXXXX1', address: '浙江省嘉兴市南湖区科技大道1号', phone: '0571-88888888', account: '6222021234567890123', bank: '中国工商银行嘉兴分行', mailingAddress: '浙江省嘉兴市南湖区科技大道1号' }; - }, []); - - var totalPayable = useMemo(function() { - var sum = 0; - for (var i = 0; i < mockVehicleList.length; i++) { - var r = mockVehicleList[i]; - sum += parseFloat(r.payableRent) || 0; - sum += parseFloat(r.deposit) || 0; - sum += parseFloat(r.serviceTotal) || 0; - sum -= parseFloat(r.discount) || 0; - } - return sum.toFixed(2); - }, [mockVehicleList]); - var hydrogenPayableTotal = useMemo(function() { - var p = parseFloat(mockHydrogen.paidTotal) || 0; - var d = parseFloat(mockHydrogen.discount) || 0; - return (p - d >= 0 ? p - d : 0).toFixed(2); - }, [mockHydrogen]); - var invoiceAmount = useMemo(function() { - return (parseFloat(totalPayable) + parseFloat(hydrogenPayableTotal)).toFixed(2); - }, [totalPayable, hydrogenPayableTotal]); - - var serviceModalRowState = useState(null); - var serviceModalRow = serviceModalRowState[0]; - var setServiceModalRow = serviceModalRowState[1]; - var cancelConfirmVisibleState = useState(false); - var cancelConfirmVisible = cancelConfirmVisibleState[0]; - var setCancelConfirmVisible = cancelConfirmVisibleState[1]; - var requirementVisibleState = useState(false); - var requirementVisible = requirementVisibleState[0]; - var setRequirementVisible = requirementVisibleState[1]; - - var invoiceFormState = useState({ invoiceTime: '', remark: '', invoiceFiles: [] }); - var invoiceForm = invoiceFormState[0]; - var setInvoiceForm = invoiceFormState[1]; - - var handleSubmit = useCallback(function() { - alert('提交成功,跳转至提车应收款列表页'); - }, []); - var handleCancelClick = useCallback(function() { setCancelConfirmVisible(true); }, []); - var handleCancelConfirm = useCallback(function(confirmed) { - setCancelConfirmVisible(false); - if (confirmed) alert('已返回提车应收款列表'); - }, []); - - var requirementContent = '「车辆管理系统」中的「租赁费用管理」模块下「提车应收款」中「收费明细」模块,第二步由财务填写;\n\n#提车应收款-收款明细\n\n整个页面从上至下为项目信息、车辆首付款、氢费预付款、开票信息4个卡片模块组成;\n\n1.项目信息:\n1.1.上方为表单,显示合同编码、合同类型、项目名称、客户名称、付款方式、付款周期、业务部门、业务负责人;\n1.1.1.合同编码:显示租赁合同编号;\n1.1.2.合同类型:显示租赁合同类型,类型有正式合同/试用合同;\n1.1.3.项目名称:显示租赁合同项目名称;\n1.1.4.客户名称:显示租赁合同客户名称;\n1.1.5.付款方式:显示租赁合同付款方式,类型有预付/后付;\n1.1.6.付款周期:显示付款周期,类型有1个月-12个月;\n1.1.7.业务部门:显示租赁合同对应业务部门;\n1.1.8.业务负责人:显示租赁合同对应业务负责人;\n\n2.提车应收款:\n2.1.上方为提车应收款应收总额;\n2.1.1.提车应收款总额:显示总金额,格式为xx.xx元,计算方式为:所有应付车辆租金+所有应付保证金+所有应付服务费-所有减免金额;\n2.2.车辆账单:品牌/型号/车牌号/应收车辆租金/车辆租金备注/应收保证金/服务费项目/应收服务费/减免金额/减免金额备注\n2.2.1.品牌:显示租赁合同中对应品牌;\n2.2.2.型号:显示租赁合同中对应型号;\n2.2.3.车牌号:显示租赁合同中对应车牌号,如无则显示为-;\n2.2.4.应收车辆租金:显示业务提交的应收车辆租金;\n2.2.5.车辆租金备注:显示业务提交的车辆租金备注;\n2.2.6.应收保证金:显示业务提交的应收车辆保证金;\n2.2.7.服务费项目:点击查看,弹出气泡卡片,卡片中为列表,列表显示租赁合同所有已填写服务项目,列表字段为:服务项目、应收费用、服务费用备注;\n 2.2.8.1.服务项目:显示所有租赁合同中已添加的服务项目名称;\n 2.2.8.2.应收费用:显示所有业务提交的费用金额;\n 2.2.8.3.服务费用备注:显示所有业务提交的备注信息;\n2.1.8.应收服务费:根据服务费项目所有服务项目应付费用计算总和得出,不可修改;\n2.1.9.减免金额:显示业务提交的减免金额,格式为xx.xx元;\n2.1.10.减免金额备注:显示业务提交的减免金额备注信息;\n\n3.氢费预付款:\n3.1.上方为氢费预付款应收总额/氢费预付款应收金额/减免金额/减免金额备注;\n3.1.1.氢费应收总额:显示总金额,格式为xx.xx元,计算方式为:氢费预付款实付总额-减免金额;\n3.1.2.氢费预付款应收金额:显示业务填写的氢费预付款应收金额,格式为xx.xx元;\n3.1.2.减免金额:显示业务填写的减免金额,格式为xx.xx元;\n3.1.3.减免金额备注:显示业务填写的减免金额备注;\n\n\n4.开票信息:\n4.1.开票金额:显示总金额,格式为xx.xx元,计算方式为:提车应收款总额+氢费应收总额;\n4.2.之后为表单,显示客户名称、纳税人识别号、地址、电话、账户、开户行、邮寄地址、开票时间、发票附件、备注:\n4.2.1.客户名称:显示租赁合同客户名称;\n4.2.2.纳税人识别号:显示租赁合同对应客户纳税人识别号;\n4.2.3.地址:显示租赁合同对应客户地址;\n4.2.4.电话:显示租赁合同对应客户电话;\n4.2.5.账户:显示租赁合同对应客户账户;\n4.2.6.开户行:显示租赁合同对应客户开户行;\n4.2.7.邮寄地址:显示租赁合同对应客户邮寄地址;\n4.2.8.开票金额:显示总金额,格式为xx.xx元,计算方式为:提车应收款总额+氢费应收总额;\n4.2.9.开票时间:选填项,日期选择器,精确至天;;\n4.2.10.发票附件:选填项,附件上传按钮,点击后上传发票附件,支持多附件(满足可能会有多张发票组合的情况);\n4.2.11.备注:选填项,文本域,用于记录财务相关备注;\n\n5.页面底部为提交、取消;\n5.1.提交:点击提交,提示:提交成功,跳转至:提车应收款列表页;\n5.2.取消:点击取消,进行二次确认气泡提示,提示为:取消将会丢失所有已添加数据,是否确认取消,点击是则返回提车应收款列表页;点击否则关闭二次确认继续操作;'; - - var styles = useMemo(function() { - return { - page: { padding: '16px 24px', background: '#f5f5f5', minHeight: '100vh', fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif' }, - breadcrumb: { marginBottom: 16, fontSize: 14, color: '#666' }, - card: { background: '#fff', borderRadius: 8, padding: 24, marginBottom: 16, boxShadow: '0 1px 2px rgba(0,0,0,0.03)' }, - cardTitle: { fontSize: 16, fontWeight: 600, marginBottom: 16, paddingBottom: 12, borderBottom: '1px solid #f0f0f0' }, - detailRow: { display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '24px', marginBottom: 12, fontSize: 14 }, - formCol: { display: 'flex', flexDirection: 'column', gap: 4 }, - formLabel: { color: '#666', fontSize: 14 }, - formValue: { color: '#333', fontSize: 14 }, - table: { width: '100%', borderCollapse: 'collapse', fontSize: 14 }, - th: { textAlign: 'left', padding: '12px 16px', backgroundColor: '#fafafa', borderBottom: '1px solid #f0f0f0', fontWeight: 600, color: '#333', whiteSpace: 'nowrap' }, - td: { padding: '12px 16px', borderBottom: '1px solid #f0f0f0', color: '#333' }, - linkBtn: { color: '#1890ff', cursor: 'pointer', background: 'none', border: 'none', padding: 0, fontSize: 14 }, - totalLine: { marginBottom: 16, fontSize: 14 }, - totalAmount: { color: '#1890ff', fontWeight: 600, fontSize: 16 }, - footer: { marginTop: 24, display: 'flex', justifyContent: 'center', gap: 12 }, - primaryBtn: { padding: '8px 24px', backgroundColor: '#1890ff', color: '#fff', border: 'none', borderRadius: 4, cursor: 'pointer', fontSize: 14 }, - defaultBtn: { padding: '8px 24px', backgroundColor: '#fff', color: '#666', border: '1px solid #d9d9d9', borderRadius: 4, cursor: 'pointer', fontSize: 14 }, - formInput: { padding: '8px 12px', border: '1px solid #d9d9d9', borderRadius: 4, fontSize: 14, width: '100%', maxWidth: 200 }, - modalOverlay: { position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, backgroundColor: 'rgba(0,0,0,0.45)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1000 }, - modalCard: { backgroundColor: '#fff', borderRadius: 8, padding: 24, maxWidth: '90%', maxHeight: '80vh', overflow: 'auto' }, - modalHeader: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }, - modalTitle: { fontSize: 16, fontWeight: 600 }, - modalClose: { padding: '4px 12px', backgroundColor: '#f5f5f5', border: 'none', borderRadius: 4, cursor: 'pointer' }, - modalBody: { maxHeight: '60vh', overflowY: 'auto', whiteSpace: 'pre-wrap', fontSize: 14, lineHeight: 1.6, color: '#333' }, - pageHeader: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16, flexWrap: 'wrap', gap: 8 }, - requirementLink: { color: '#1890ff', cursor: 'pointer', fontSize: 14, background: 'none', border: 'none', padding: 0 } - }; - }, []); - - var projectCard = React.createElement('div', { style: styles.card }, - React.createElement('div', { style: styles.cardTitle }, '项目信息'), - React.createElement('div', { style: styles.detailRow }, - React.createElement('div', { style: styles.formCol }, React.createElement('div', { style: styles.formLabel }, '合同编码'), React.createElement('div', { style: styles.formValue }, mockProject.contractCode)), - React.createElement('div', { style: styles.formCol }, React.createElement('div', { style: styles.formLabel }, '合同类型'), React.createElement('div', { style: styles.formValue }, mockProject.contractType)), - React.createElement('div', { style: styles.formCol }, React.createElement('div', { style: styles.formLabel }, '项目名称'), React.createElement('div', { style: styles.formValue }, mockProject.projectName)) - ), - React.createElement('div', { style: styles.detailRow }, - React.createElement('div', { style: styles.formCol }, React.createElement('div', { style: styles.formLabel }, '客户名称'), React.createElement('div', { style: styles.formValue }, mockProject.customerName)), - React.createElement('div', { style: styles.formCol }, React.createElement('div', { style: styles.formLabel }, '付款方式'), React.createElement('div', { style: styles.formValue }, mockProject.paymentMethod)), - React.createElement('div', { style: styles.formCol }, React.createElement('div', { style: styles.formLabel }, '付款周期'), React.createElement('div', { style: styles.formValue }, mockProject.paymentCycle)) - ), - React.createElement('div', { style: styles.detailRow }, - React.createElement('div', { style: styles.formCol }, React.createElement('div', { style: styles.formLabel }, '业务部门'), React.createElement('div', { style: styles.formValue }, mockProject.department)), - React.createElement('div', { style: styles.formCol }, React.createElement('div', { style: styles.formLabel }, '业务负责人'), React.createElement('div', { style: styles.formValue }, mockProject.responsible)), - React.createElement('div', { style: styles.formCol }, null) - ) - ); - - var vehicleTableHeader = React.createElement('thead', null, - React.createElement('tr', null, - React.createElement('th', { style: styles.th }, '品牌'), - React.createElement('th', { style: styles.th }, '型号'), - React.createElement('th', { style: styles.th }, '车牌号'), - React.createElement('th', { style: styles.th }, '应收车辆租金'), - React.createElement('th', { style: styles.th }, '车辆租金备注'), - React.createElement('th', { style: styles.th }, '应收保证金'), - React.createElement('th', { style: styles.th }, '服务费项目'), - React.createElement('th', { style: styles.th }, '应收服务费'), - React.createElement('th', { style: styles.th }, '减免金额'), - React.createElement('th', { style: styles.th }, '减免金额备注') - ) - ); - var vehicleTableBody = React.createElement('tbody', null, - mockVehicleList.map(function(row, rowIndex) { - return React.createElement('tr', { key: rowIndex }, - React.createElement('td', { style: styles.td }, row.brand), - React.createElement('td', { style: styles.td }, row.model), - React.createElement('td', { style: styles.td }, row.plateNo || '-'), - React.createElement('td', { style: styles.td }, row.payableRent), - React.createElement('td', { style: styles.td }, row.rentRemark || '-'), - React.createElement('td', { style: styles.td }, row.deposit), - React.createElement('td', { style: styles.td }, - React.createElement('button', { type: 'button', style: styles.linkBtn, onClick: function() { setServiceModalRow(rowIndex); } }, '查看') - ), - React.createElement('td', { style: styles.td }, row.serviceTotal || '0.00'), - React.createElement('td', { style: styles.td }, (function() { var v = parseFloat(row.discount); return (isNaN(v) ? '0.00' : v.toFixed(2)) + ' 元'; })()), - React.createElement('td', { style: styles.td }, row.discountRemark || '-') - ); - }) - ); - var vehicleBillCard = React.createElement('div', { style: styles.card }, - React.createElement('div', { style: styles.cardTitle }, '提车应收款'), - React.createElement('div', { style: styles.totalLine }, - React.createElement('span', { style: { marginRight: 8 } }, '提车应收款总额:'), - React.createElement('span', { style: styles.totalAmount }, totalPayable + ' 元') - ), - React.createElement('table', { style: styles.table }, vehicleTableHeader, vehicleTableBody) - ); - - var hydrogenCard = React.createElement('div', { style: styles.card }, - React.createElement('div', { style: styles.cardTitle }, '氢费预付款'), - React.createElement('div', { style: styles.totalLine }, - React.createElement('span', { style: { marginRight: 8 } }, '氢费应收总额:'), - React.createElement('span', { style: styles.totalAmount }, hydrogenPayableTotal + ' 元') - ), - React.createElement('div', { style: styles.detailRow }, - React.createElement('div', { style: styles.formCol }, React.createElement('div', { style: styles.formLabel }, '氢费预付款应收金额'), React.createElement('div', { style: styles.formValue }, mockHydrogen.paidTotal + ' 元')), - React.createElement('div', { style: styles.formCol }, React.createElement('div', { style: styles.formLabel }, '减免金额'), React.createElement('div', { style: styles.formValue }, mockHydrogen.discount + ' 元')), - React.createElement('div', { style: styles.formCol }, React.createElement('div', { style: styles.formLabel }, '减免金额备注'), React.createElement('div', { style: styles.formValue }, mockHydrogen.discountRemark || '-')) - ) - ); - - var dayjs = window.dayjs || null; - var invoiceTimeValue = null; - if (invoiceForm.invoiceTime && dayjs) { - var d = dayjs(invoiceForm.invoiceTime, 'YYYY-MM-DD'); - invoiceTimeValue = d && d.isValid && d.isValid() ? d : null; - } - var datePickerEl = DatePicker ? React.createElement(DatePicker, { - style: { width: '100%' }, - format: 'YYYY-MM-DD', - placeholder: '请选择开票日期', - value: invoiceTimeValue, - onChange: function(d, dateStr) { setInvoiceForm(function(prev) { return Object.assign({}, prev, { invoiceTime: dateStr || '' }); }); } - }) : React.createElement('input', { type: 'date', style: styles.formInput, value: invoiceForm.invoiceTime, onChange: function(e) { setInvoiceForm(function(prev) { return Object.assign({}, prev, { invoiceTime: e.target.value }); }); } }); - var uploadButton = Button ? React.createElement(Button, null, '上传附件') : React.createElement('button', { type: 'button', style: styles.defaultBtn }, '上传附件'); - var uploadEl = Upload ? React.createElement(Upload, { - accept: '.pdf', - multiple: true, - fileList: invoiceForm.invoiceFiles || [], - onChange: function(info) { setInvoiceForm(function(prev) { return Object.assign({}, prev, { invoiceFiles: info.fileList }); }); }, - beforeUpload: function() { return false; }, - showUploadList: true - }, uploadButton) : React.createElement('div', { style: { fontSize: 14, color: '#999' } }, '(需加载 antd Upload)'); - var remarkEl = Input && Input.TextArea ? React.createElement(Input.TextArea, { - placeholder: '选填,用于记录财务相关备注', - value: invoiceForm.remark || '', - onChange: function(e) { setInvoiceForm(function(prev) { return Object.assign({}, prev, { remark: e.target.value }); }); }, - rows: 3, - style: { width: '100%' } - }) : React.createElement('textarea', { style: Object.assign({}, styles.formInput, { minHeight: 60, resize: 'vertical' }), value: invoiceForm.remark || '', onChange: function(e) { setInvoiceForm(function(prev) { return Object.assign({}, prev, { remark: e.target.value }); }); }, placeholder: '选填,用于记录财务相关备注' }); - var invoiceCard = React.createElement('div', { style: styles.card }, - React.createElement('div', { style: styles.cardTitle }, '开票信息'), - React.createElement('div', { style: styles.totalLine }, - React.createElement('span', { style: { marginRight: 8 } }, '开票金额:'), - React.createElement('span', { style: styles.totalAmount }, invoiceAmount + ' 元') - ), - React.createElement('div', { style: styles.detailRow }, - React.createElement('div', { style: styles.formCol }, React.createElement('div', { style: styles.formLabel }, '客户名称'), React.createElement('div', { style: styles.formValue }, mockInvoice.customerName)), - React.createElement('div', { style: styles.formCol }, React.createElement('div', { style: styles.formLabel }, '纳税人识别号'), React.createElement('div', { style: styles.formValue }, mockInvoice.taxId)), - React.createElement('div', { style: styles.formCol }, React.createElement('div', { style: styles.formLabel }, '地址'), React.createElement('div', { style: styles.formValue }, mockInvoice.address)) - ), - React.createElement('div', { style: styles.detailRow }, - React.createElement('div', { style: styles.formCol }, React.createElement('div', { style: styles.formLabel }, '电话'), React.createElement('div', { style: styles.formValue }, mockInvoice.phone)), - React.createElement('div', { style: styles.formCol }, React.createElement('div', { style: styles.formLabel }, '账户'), React.createElement('div', { style: styles.formValue }, mockInvoice.account)), - React.createElement('div', { style: styles.formCol }, React.createElement('div', { style: styles.formLabel }, '开户行'), React.createElement('div', { style: styles.formValue }, mockInvoice.bank)) - ), - React.createElement('div', { style: styles.detailRow }, - React.createElement('div', { style: styles.formCol }, React.createElement('div', { style: styles.formLabel }, '邮寄地址'), React.createElement('div', { style: styles.formValue }, mockInvoice.mailingAddress)), - React.createElement('div', { style: styles.formCol }, null), - React.createElement('div', { style: styles.formCol }, null) - ), - React.createElement('div', { style: styles.detailRow }, - React.createElement('div', { style: styles.formCol }, React.createElement('div', { style: styles.formLabel }, '开票时间'), React.createElement('div', { style: { width: '100%' } }, datePickerEl)), - React.createElement('div', { style: styles.formCol }, React.createElement('div', { style: styles.formLabel }, '发票附件'), React.createElement('div', { style: { width: '100%' } }, uploadEl)), - React.createElement('div', { style: styles.formCol }, React.createElement('div', { style: styles.formLabel }, '备注'), React.createElement('div', { style: { width: '100%' } }, remarkEl)) - ) - ); - - var serviceModal = null; - if (serviceModalRow !== null && mockVehicleList[serviceModalRow]) { - var r = mockVehicleList[serviceModalRow]; - var serviceList = r.serviceItems || []; - serviceModal = React.createElement('div', { style: styles.modalOverlay, onClick: function() { setServiceModalRow(null); } }, - React.createElement('div', { style: styles.modalCard, onClick: function(e) { e.stopPropagation(); } }, - React.createElement('div', { style: styles.modalHeader }, - React.createElement('span', { style: styles.modalTitle }, '服务费项目'), - React.createElement('button', { type: 'button', style: styles.modalClose, onClick: function() { setServiceModalRow(null); } }, '关闭') - ), - React.createElement('table', { style: styles.table }, - React.createElement('thead', null, React.createElement('tr', null, React.createElement('th', { style: styles.th }, '服务项目'), React.createElement('th', { style: styles.th }, '应收费用'), React.createElement('th', { style: styles.th }, '服务费用备注'))), - React.createElement('tbody', null, serviceList.map(function(item, i) { return React.createElement('tr', { key: i }, React.createElement('td', { style: styles.td }, item.name), React.createElement('td', { style: styles.td }, item.fee), React.createElement('td', { style: styles.td }, item.remark || '-')); })) - ) - ) - ); - } - - var footer = React.createElement('div', { style: styles.footer }, - React.createElement('button', { type: 'button', style: styles.primaryBtn, onClick: handleSubmit }, '提交'), - React.createElement('button', { type: 'button', style: styles.defaultBtn, onClick: handleCancelClick }, '取消') - ); - - var requirementModal = requirementVisible ? React.createElement('div', { style: styles.modalOverlay, onClick: function() { setRequirementVisible(false); } }, - React.createElement('div', { style: styles.modalCard, onClick: function(e) { e.stopPropagation(); } }, - React.createElement('div', { style: styles.modalHeader }, React.createElement('span', { style: styles.modalTitle }, '需求说明'), React.createElement('button', { type: 'button', style: styles.modalClose, onClick: function() { setRequirementVisible(false); } }, '关闭')), - React.createElement('div', { style: styles.modalBody }, requirementContent) - ) - ) : null; - - var cancelConfirmModal = cancelConfirmVisible ? React.createElement('div', { style: styles.modalOverlay, onClick: function() { setCancelConfirmVisible(false); } }, - React.createElement('div', { style: styles.modalCard, onClick: function(e) { e.stopPropagation(); } }, - React.createElement('div', { style: styles.modalHeader }, React.createElement('span', { style: styles.modalTitle }, '提示'), null), - React.createElement('div', { style: { marginBottom: 20, fontSize: 14, color: '#333' } }, '取消将会丢失所有已添加数据,是否确认取消?'), - React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: 12 } }, - React.createElement('button', { type: 'button', style: styles.defaultBtn, onClick: function() { handleCancelConfirm(false); } }, '否'), - React.createElement('button', { type: 'button', style: styles.primaryBtn, onClick: function() { handleCancelConfirm(true); } }, '是') - ) - ) - ) : null; - - return React.createElement('div', { style: styles.page }, - React.createElement('div', { style: styles.pageHeader }, - React.createElement('div', { style: styles.breadcrumb }, '租赁费用管理 > 提车应收款 > 收费明细 > 财务提交'), - React.createElement('button', { type: 'button', style: styles.requirementLink, onClick: function() { setRequirementVisible(true); } }, '查看需求说明') - ), - projectCard, - vehicleBillCard, - hydrogenCard, - invoiceCard, - footer, - serviceModal, - requirementModal, - cancelConfirmModal - ); -}; diff --git a/web端/财务管理/提车收款单-编辑.jsx b/web端/财务管理/提车收款单-编辑.jsx new file mode 100644 index 0000000..266a57e --- /dev/null +++ b/web端/财务管理/提车收款单-编辑.jsx @@ -0,0 +1,532 @@ +// 【重要】必须使用 const Component 作为组件变量名 +// 财务管理 - 提车应收款 - 提车收款单-编辑(2026年3月10日版本) + +const Component = function () { + var useState = React.useState; + var useCallback = React.useCallback; + var useMemo = React.useMemo; + var useEffect = React.useEffect; + var useRef = React.useRef; + + var antd = window.antd; + var Breadcrumb = antd.Breadcrumb; + var Card = antd.Card; + var Input = antd.Input; + var Button = antd.Button; + var Select = antd.Select; + var Table = antd.Table; + var Popover = antd.Popover; + var Tooltip = antd.Tooltip; + var Modal = antd.Modal; + var message = antd.message; + var Option = Select.Option; + + var requirementModalVisible = useState(false); + var setRequirementModalVisible = requirementModalVisible[1]; + var edited = useState(false); + var setEdited = edited[1]; + var submitConfirmVisible = useState(false); + var setSubmitConfirmVisible = submitConfirmVisible[1]; + var cancelConfirmVisible = useState(false); + var setCancelConfirmVisible = cancelConfirmVisible[1]; + var projectCardExpanded = useState(true); + var setProjectCardExpanded = projectCardExpanded[1]; + var receivableCardExpanded = useState(true); + var setReceivableCardExpanded = receivableCardExpanded[1]; + var servicePopoverRowIndex = useState(null); + var setServicePopoverRowIndex = servicePopoverRowIndex[1]; + + // 模拟:项目信息(实际由路由/接口拉取) + var projectInfo = useMemo(function () { + return { + contractCode: 'HT-ZL-2025-001', + contractType: '正式合同', + projectName: '嘉兴氢能示范项目', + customerName: '嘉兴某某物流有限公司', + paymentMethod: '预付', + paymentCycle: '6个月', + contractStart: '2025-01-15', + contractEnd: '2026-01-14', + businessDept: '业务1部', + businessPerson: '张经理' + }; + }, []); + + // 模拟:车辆应收款明细(租赁合同中所有车辆),付款周期 6 个月 => 应收月租金 = 月租金*6 + // 编辑页:默认勾选为“此前保存时已选中的车辆” + var paymentCycleMonths = 6; + var vehicleRows = useState([ + { key: 'v1', index: 1, brand: '东风', model: 'DFH1180', plateNo: '浙A12345', monthRent: 5000, receivableRent: 30000, actualRent: '29800.00', rentRemark: '首期六期一次性付清', receivableDeposit: 10000, serviceItems: [{ name: '代处理费用', receivable: 200, actual: '200.00', discount: '0.00', remark: '' }, { name: '保险上浮', receivable: 500, actual: '480.00', discount: '20.00', remark: '客户协商' }], receivableService: 700, actualService: '680.00', discountAmount: '200.00', discountRemark: '首月优惠', discountProof: [{ name: '优惠审批单.pdf' }], selected: true, receivableCompleted: true }, + { key: 'v2', index: 2, brand: '福田', model: 'BJ1180', plateNo: '浙A23456', monthRent: 4500, receivableRent: 27000, actualRent: '27000.00', rentRemark: '', receivableDeposit: 8000, serviceItems: [{ name: '保养费用', receivable: 300, actual: '300.00', discount: '0.00', remark: '含首保' }], receivableService: 300, actualService: '300.00', discountAmount: '0.00', discountRemark: '', discountProof: [], selected: true, receivableCompleted: true }, + { key: 'v3', index: 3, brand: '重汽', model: 'ZZ1187', plateNo: '浙A34567', monthRent: 5200, receivableRent: 31200, actualRent: '31200.00', rentRemark: '按合同约定', receivableDeposit: 10000, serviceItems: [{ name: '代处理费用', receivable: 180, actual: '180.00', discount: '0.00', remark: '' }, { name: '上牌服务', receivable: 400, actual: '400.00', discount: '0.00', remark: '已含上牌' }], receivableService: 580, actualService: '580.00', discountAmount: '0.00', discountRemark: '', discountProof: [], selected: true, receivableCompleted: true }, + { key: 'v4', index: 4, brand: '陕汽', model: 'SX1313', plateNo: '', monthRent: 4800, receivableRent: 28800, actualRent: '28500.00', rentRemark: '待交车后补全', receivableDeposit: 9000, serviceItems: [{ name: '保险上浮', receivable: 350, actual: '350.00', discount: '0.00', remark: '' }], receivableService: 350, actualService: '350.00', discountAmount: '300.00', discountRemark: '客户协商减免', discountProof: [], selected: true }, + { key: 'v5', index: 5, brand: '解放', model: 'J6P', plateNo: '浙A45678', monthRent: 5500, receivableRent: 33000, actualRent: '33000.00', rentRemark: '', receivableDeposit: 11000, serviceItems: [{ name: '代处理费用', receivable: 220, actual: '220.00', discount: '0.00', remark: '' }, { name: '保养费用', receivable: 280, actual: '280.00', discount: '0.00', remark: '' }], receivableService: 500, actualService: '500.00', discountAmount: '0.00', discountRemark: '', discountProof: [], selected: false }, + { key: 'v6', index: 6, brand: '江淮', model: '格尔发K5', plateNo: '浙A56789', monthRent: 4300, receivableRent: 25800, actualRent: '25800.00', rentRemark: '', receivableDeposit: 8500, serviceItems: [{ name: '保养费用', receivable: 260, actual: '260.00', discount: '0.00', remark: '' }], receivableService: 260, actualService: '260.00', discountAmount: '0.00', discountRemark: '', discountProof: [], selected: false } + ]); + var vehicles = vehicleRows[0]; + var setVehicles = vehicleRows[1]; + + // 氢费预付款(非预付款模式时不显示) + var hasHydrogenPrepay = true; + var hydrogenRow = useState({ + receivable: '3580.00', + actual: '3500.00', + discount: '80.00', + discountRemark: '预付款批量减免' + }); + var hydrogen = hydrogenRow[0]; + var setHydrogen = hydrogenRow[1]; + + var invoiceMethod = useState('先开票后付款'); + var setInvoiceMethod = invoiceMethod[1]; + var invoiceRemark = useState('增值税专用发票,税率13%,开票项目:*现代服务*车辆租赁费;备注:嘉兴氢能示范项目-提车首付款'); + var setInvoiceRemark = invoiceRemark[1]; + + var toggleVehicleSelected = useCallback(function (key) { + setEdited(true); + setVehicles(function (prev) { + return prev.map(function (r) { + if (r.key !== key || r.receivableCompleted) return r; + return Object.assign({}, r, { selected: !r.selected }); + }); + }); + }, []); + + var toggleAllSelected = useCallback(function (checked) { + setEdited(true); + setVehicles(function (prev) { + return prev.map(function (r) { + if (r.receivableCompleted) return r; + return Object.assign({}, r, { selected: !!checked }); + }); + }); + }, []); + + var updateVehicle = useCallback(function (key, field, value) { + setEdited(true); + setVehicles(function (prev) { + return prev.map(function (r) { + if (r.key !== key) return r; + var next = Object.assign({}, r); + next[field] = value; + if (field === 'serviceItems') { + var total = 0; + (value || []).forEach(function (s) { total += parseFloat(s.actual) || 0; }); + next.actualService = total.toFixed(2); + } + return next; + }); + }); + }, []); + + var updateServiceItem = useCallback(function (vehicleKey, itemIndex, field, value) { + setEdited(true); + setVehicles(function (prev) { + return prev.map(function (r) { + if (r.key !== vehicleKey) return r; + var items = (r.serviceItems || []).slice(); + if (!items[itemIndex]) return r; + items[itemIndex] = Object.assign({}, items[itemIndex], { [field]: value }); + var total = 0; + items.forEach(function (s) { total += parseFloat(s.actual) || 0; }); + return Object.assign({}, r, { serviceItems: items, actualService: total.toFixed(2) }); + }); + }); + }, []); + + var totals = useMemo(function () { + // 只计算已选中且未完成提车应收款的车辆 + var selected = vehicles.filter(function (v) { return v.selected && !v.receivableCompleted; }); + var receivableRent = 0, actualRent = 0, receivableDeposit = 0, receivableService = 0, actualService = 0, discountTotal = 0; + selected.forEach(function (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; + }); + 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) + }; + }, [vehicles]); + + var receivableTotal = useMemo(function () { + return (parseFloat(totals.receivableRent) + parseFloat(totals.receivableDeposit) + parseFloat(totals.receivableService)).toFixed(2); + }, [totals]); + + var actualTotal = useMemo(function () { + return (parseFloat(totals.actualRent) + parseFloat(totals.receivableDeposit) + parseFloat(totals.actualService) - parseFloat(totals.discountTotal)).toFixed(2); + }, [totals]); + + var receivablePopoverContent = useMemo(function () { + return React.createElement('div', { style: { padding: 8, minWidth: 220 } }, + React.createElement('table', { style: { width: '100%', borderCollapse: 'collapse', fontSize: 13 } }, + React.createElement('thead', null, + React.createElement('tr', null, + React.createElement('th', { style: { textAlign: 'left', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '项目'), + React.createElement('th', { style: { textAlign: 'right', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '金额') + ) + ), + React.createElement('tbody', null, + React.createElement('tr', null, React.createElement('td', { style: { padding: '6px 8px' } }, '总计应收车辆月租金'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.receivableRent + ' 元')), + React.createElement('tr', null, React.createElement('td', { style: { padding: '6px 8px' } }, '总计应收车辆保证金'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.receivableDeposit + ' 元')), + React.createElement('tr', null, React.createElement('td', { style: { padding: '6px 8px' } }, '总计应收服务费'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.receivableService + ' 元')) + ) + ) + ); + }, [totals]); + + var actualPopoverContent = useMemo(function () { + return React.createElement('div', { style: { padding: 8, minWidth: 220 } }, + React.createElement('table', { style: { width: '100%', borderCollapse: 'collapse', fontSize: 13 } }, + React.createElement('thead', null, + React.createElement('tr', null, + React.createElement('th', { style: { textAlign: 'left', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '项目'), + React.createElement('th', { style: { textAlign: 'right', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '金额') + ) + ), + React.createElement('tbody', null, + React.createElement('tr', null, React.createElement('td', { style: { padding: '6px 8px' } }, '总计实收车辆月租金'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.actualRent + ' 元')), + React.createElement('tr', null, React.createElement('td', { style: { padding: '6px 8px' } }, '总计应收车辆保证金'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.receivableDeposit + ' 元')), + React.createElement('tr', null, React.createElement('td', { style: { padding: '6px 8px' } }, '总计实收服务费'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.actualService + ' 元')), + React.createElement('tr', null, React.createElement('td', { style: { padding: '6px 8px' } }, '总计减免金额'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.discountTotal + ' 元')) + ) + ) + ); + }, [totals]); + + var selectableVehicles = vehicles.filter(function (v) { return !v.receivableCompleted; }); + var allSelected = selectableVehicles.length > 0 && selectableVehicles.every(function (v) { return v.selected; }); + var indeterminate = selectableVehicles.some(function (v) { return v.selected; }) && !allSelected; + var headerCheckRef = useRef(null); + useEffect(function () { + if (headerCheckRef.current) headerCheckRef.current.indeterminate = indeterminate; + }, [indeterminate]); + + var handleSubmit = useCallback(function () { + setSubmitConfirmVisible(true); + }, []); + + var handleSubmitConfirm = useCallback(function () { + setSubmitConfirmVisible(false); + message.success('提车收款单已提交审核'); + setEdited(false); + if (window.__receivableBack) window.__receivableBack(); + }, []); + + var handleSave = useCallback(function () { + message.success('保存成功'); + setEdited(false); + if (window.__receivableBack) window.__receivableBack(); + }, []); + + var handleCancel = useCallback(function () { + if (edited[0]) setCancelConfirmVisible(true); + else if (window.__receivableBack) window.__receivableBack(); else message.info('返回提车应收款列表(原型)'); + }, [edited[0]]); + + var handleCancelConfirm = useCallback(function () { + setCancelConfirmVisible(false); + if (window.__receivableBack) window.__receivableBack(); else message.info('返回提车应收款列表(原型)'); + }, []); + + var layoutStyle = { padding: '16px 24px', background: '#f5f5f5', minHeight: '100vh' }; + var cardStyle = { marginBottom: 16 }; + var labelStyle = { marginBottom: 6, fontSize: 14, color: 'rgba(0,0,0,0.65)' }; + var requiredStar = { color: '#ff4d4f', marginRight: 2 }; + var formRowStyle = { display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '16px 24px', marginBottom: 16 }; + var formItemStyle = { marginBottom: 12 }; + var highlightStyle = { color: '#1890ff', fontWeight: 600, cursor: 'pointer' }; + var valueStyle = { color: 'rgba(0,0,0,0.85)', fontSize: 14, lineHeight: '22px', minHeight: 22 }; + var thBase = { padding: '10px 12px', border: '1px solid #f0f0f0', whiteSpace: 'nowrap' }; + + var requirementContent = '提车应收款-提车收款单-编辑(2026年3月10日版本)\n一个「数字化资产ONEOS运管平台」中的「财务管理」「提车应收款」「提车收款单」「编辑」模块\n#面包屑:财务管理-提车应收款-提车收款单-编辑;\n\n页面分为3个卡片,业务流程请参考流程图;\n1.项目信息:\n#显示项目详细信息,包括:\n1.1.合同编码:显示该租赁合同对应合同编码;\n1.2.合同类型:显示该租赁合同对应合同类型;\n1.3.项目名称:显示该租赁合同对应项目名称;\n1.4.客户名称:显示该租赁合同对应客户名称;\n1.5.付款方式:显示该租赁合同对应付款方式,分为:预付、后付两种,根据实际合同反写;\n1.6.付款周期:显示该租赁合同对应付款周期,格式为:x个月;\n1.7.合同生效时间:显示该租赁合同对应合同生效时间;\n1.8.合同结束时间:显示该租赁合同对应合同结束时间;\n1.9.业务部门:显示该租赁合同对应业务部门名称;\n1.10.业务负责人:显示该租赁合同对应业务负责人;\n\n2.提车应收款信息:\n#上方显示应收款总额、实收款总额;\n2.1.应收款总额:格式为:xx.xx元,重点色,计算方式为:「选中车辆应收车辆月租金总和」+「选中车辆应收车辆保证金总和」+「选中车辆应收服务费总和」;\n 点击应收款总额金额,弹出气泡卡片,气泡卡片列表展示:项目、金额;\n 2.1.1.项目:包括总计应收车辆月租金、总计应收车辆保证金、总计应收服务费三项;\n 2.1.2.金额:显示该项目对应应收金额;\n2.2.实收款总额:格式为:xx.xx元,重点色,计算方式为:「选中车辆实收车辆月租金总和」+「选中车辆应收车辆保证金总和」+「选中车辆实收服务费总和」-「选中车辆减免金额总和」;\n 点击实收款总额金额,弹出气泡卡片,气泡卡片列表展示:项目、金额;\n 2.2.1.项目:包括总计实收车辆月租金、总计应收车辆保证金、总计实收服务费、总计减免金额四项;\n 2.2.2.金额:显示该项目对应实收金额;\n\n#中间为车辆应收款明细,以列表展示租赁合同中所有车辆明细,包括以下字段:\n2.1.全选/多选:多选按钮组,支持表头点击全选,编辑页面显示此前保存时已选中相关车辆,支持重新选择车辆;\n 提车应收款支持分批次进行收款,如果该车辆之前已提交提车应收款流程,则多选框不能勾选,悬浮多选框时提示:该车辆已完成提车应收款;\n2.2.序号:根据租赁合同中车辆对应序号顺序展示;\n2.3.品牌:显示租赁合同中车辆对应品牌;\n2.4.型号:显示租赁合同中车辆对应型号;\n2.5.车牌号:显示租赁合同中车辆对应车牌号,如果车牌号为空则显示为-(后续如果交车成功,则车牌号会反显在该处);\n2.6.应收车辆月租金:根据租赁合同付款周期自动计算,例如:付款周期为6个月,则这里显示金额为:车辆月租金*6;\n2.7.实收车辆月租金:必填项,输入框,默认反写实收车辆月租金,支持修改,由业务员自行输入车辆月租金金额,精确至2位小数,后缀为元;\n2.8.车辆租金备注:选填项,输入框,由业务员自行输入;\n2.9.减免金额:选填项,输入框,由业务员自行输入减免金额,默认为0.00,支持2位小数,后缀为元;\n2.10.减免金额备注:选填项,输入框,由业务员自行输入备注信息;\n2.11.应收车辆保证金:显示租赁合同中当前车辆保证金金额;\n2.12.服务费项目:点击管理,弹出气泡卡片,气泡卡片标题为服务费项目,下方为列表,显示服务项目、应收费用、实收费用、备注;\n 2.12.1.服务项目:显示租赁合同中所有服务项目名称;\n 2.12.2.应收费用:显示租赁合同中所有服务项目对应费用;\n 2.12.3.实收费用:必填项,输入框,默认反写应收费用,支持修改,由业务员自行输入实收费用金额,精确至2位小数,后缀为元;\n 2.12.4.减免费用:选填项,输入框,默认为:0.00,精确至2位小数,后缀为元;\n 2.12.4.备注:选填项,输入框,由业务员自行输入备注信息;\n2.13.应收服务费:显示当前车辆所有服务费应收费用总和,格式为:xx.xx元;\n2.14.实收服务费:显示当前车辆所有服务费实收费用总和,格式为:xx.xx元;\n2.15.减免金额:选填项,输入框,由业务员自行输入减免金额,默认为0.00,支持2位小数,后缀为元;\n2.16.减免金额备注:选填项,输入框,由业务员自行输入备注信息;\n2.17.减免证明:选填项,附件上传按钮,点击后上传本地文件,支持:jpg、png、pdf等格式,支持多附件上传;\n\n列表不支持分页功能;\n#下方为氢费预付款情况,以列表展示租赁合同中所有氢费预付款明细,包括以下字段,如租赁合同氢费非预付款模式,则不显示该部分内容:\n2.19.氢费预付款应收金额:显示租赁合同中氢费预付款金额,格式为:xx.xx元,支持2位小数;\n2.20.氢费预付款实收金额:必填项,输入框,默认反写氢费预付款应收金额,支持修改,由业务员自行输入金额,格式为:xx.xx元,支持2位小数;\n2.21.减免金额:选填项,输入框,由业务员自行输入减免金额,默认为:0.00元,格式为:xx.xx元,支持2位小数;\n2.22.减免金额备注:选填项,输入框,由业务员自行输入减免金额备注信息;\n#最底部为开票方式、开票备注;\n2.23.开票方式:选择器,选项包括:先开票后付款、先付款后开票,默认为先开票后付款;\n2.24.开票备注:必填项,文本域,默认提示文本为:请输入开票项目、税率以及其他备注信息,财务将以此进行开票;\n\n3.底部为提交审核,保存,取消;\n3.1.提交审核:点击提交审核,进行二次确认,文案为:请仔细核对提车首付款实收金额,点击确认则进入工作流;\n3.2.保存:点击保存,提示保存成功,跳转至提车应收款列表页,同时该条数据审核状态为:待提交:\n3.3.取消:点击取消,进行二次确认,文案为:取消将会丢失所有已填写数据,是否确认,点击确认则跳转至提车应收款列表页;'; + + // 兼容:用于 “服务费项目” 气泡卡片定位 + var activeServiceIndex = servicePopoverRowIndex[0]; + + return React.createElement('div', { style: layoutStyle }, + React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 } }, + React.createElement(Breadcrumb, { + items: [ + { title: '财务管理' }, + { title: '提车应收款' }, + { title: '提车收款单-编辑' } + ] + }), + React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalVisible(true); } }, '查看需求说明') + ), + React.createElement(Card, { + title: '项目信息', + style: cardStyle, + extra: React.createElement(Button, { type: 'link', size: 'small', onClick: function () { setProjectCardExpanded(!projectCardExpanded[0]); } }, projectCardExpanded[0] ? '收起' : '展开') + }, + projectCardExpanded[0] ? React.createElement('div', { style: formRowStyle }, + React.createElement('div', { style: formItemStyle }, + React.createElement('div', { style: labelStyle }, '合同编码'), + React.createElement('div', { style: valueStyle }, projectInfo.contractCode || '-') + ), + React.createElement('div', { style: formItemStyle }, + React.createElement('div', { style: labelStyle }, '合同类型'), + React.createElement('div', { style: valueStyle }, projectInfo.contractType || '-') + ), + React.createElement('div', { style: formItemStyle }, + React.createElement('div', { style: labelStyle }, '项目名称'), + React.createElement('div', { style: valueStyle }, projectInfo.projectName || '-') + ), + React.createElement('div', { style: formItemStyle }, + React.createElement('div', { style: labelStyle }, '客户名称'), + React.createElement('div', { style: valueStyle }, projectInfo.customerName || '-') + ), + React.createElement('div', { style: formItemStyle }, + React.createElement('div', { style: labelStyle }, '付款方式'), + React.createElement('div', { style: valueStyle }, projectInfo.paymentMethod || '-') + ), + React.createElement('div', { style: formItemStyle }, + React.createElement('div', { style: labelStyle }, '付款周期'), + React.createElement('div', { style: valueStyle }, projectInfo.paymentCycle || '-') + ), + React.createElement('div', { style: formItemStyle }, + React.createElement('div', { style: labelStyle }, '合同生效时间'), + React.createElement('div', { style: valueStyle }, projectInfo.contractStart || '-') + ), + React.createElement('div', { style: formItemStyle }, + React.createElement('div', { style: labelStyle }, '合同结束时间'), + React.createElement('div', { style: valueStyle }, projectInfo.contractEnd || '-') + ), + React.createElement('div', { style: formItemStyle }, + React.createElement('div', { style: labelStyle }, '业务部门'), + React.createElement('div', { style: valueStyle }, projectInfo.businessDept || '-') + ), + React.createElement('div', { style: formItemStyle }, + React.createElement('div', { style: labelStyle }, '业务负责人'), + React.createElement('div', { style: valueStyle }, projectInfo.businessPerson || '-') + ) + ) : null + ), + React.createElement(Card, { + title: '提车应收款信息', + style: cardStyle, + extra: React.createElement(Button, { type: 'link', size: 'small', onClick: function () { setReceivableCardExpanded(!receivableCardExpanded[0]); } }, receivableCardExpanded[0] ? '收起' : '展开') + }, + receivableCardExpanded[0] ? React.createElement(React.Fragment, null, + React.createElement('div', { style: { marginBottom: 16, display: 'flex', gap: 24, alignItems: 'center' } }, + React.createElement(Popover, { content: receivablePopoverContent, title: '应收款明细', trigger: 'click' }, + React.createElement('span', { style: { cursor: 'pointer' } }, '应收款总额:', React.createElement('span', { style: highlightStyle }, receivableTotal, ' 元')) + ), + React.createElement(Popover, { content: actualPopoverContent, title: '实收款明细', trigger: 'click' }, + React.createElement('span', { style: { cursor: 'pointer' } }, '实收款总额:', React.createElement('span', { style: highlightStyle }, actualTotal, ' 元')) + ) + ), + React.createElement('div', { style: { overflowX: 'auto', marginBottom: 16, overflowY: 'visible' } }, + React.createElement('table', { style: { width: '100%', minWidth: 1600, borderCollapse: 'collapse', fontSize: 13, tableLayout: 'fixed' } }, + React.createElement('thead', null, + React.createElement('tr', { style: { backgroundColor: '#fafafa' } }, + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'left', width: 48 }) }, + React.createElement('input', { type: 'checkbox', checked: allSelected, ref: headerCheckRef, onChange: function (e) { toggleAllSelected(e.target.checked); } }) + ), + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'left', width: 50 }) }, '序号'), + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'left', width: 80 }) }, '品牌'), + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'left', width: 90 }) }, '型号'), + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'left', width: 100 }) }, '车牌号'), + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'right', width: 120 }) }, '应收车辆月租金'), + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'left', width: 130 }) }, React.createElement('span', null, React.createElement('span', { style: requiredStar }, '*'), '实收车辆月租金')), + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'left', width: 130 }) }, '车辆租金备注'), + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'left', width: 130 }) }, '减免金额'), + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'left', width: 130 }) }, '减免金额备注'), + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'left', width: 130 }) }, '减免证明'), + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'right', width: 110 }) }, '应收车辆保证金'), + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'left', width: 90 }) }, '服务费项目'), + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'right', width: 90 }) }, '应收服务费'), + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'right', width: 130 }) }, '实收服务费') + ) + ), + React.createElement('tbody', null, + vehicles.map(function (row) { + var disabled = !row.selected || row.receivableCompleted; + + var servicePopover = React.createElement('div', { style: { padding: 8, minWidth: 360 } }, + React.createElement('div', { style: { fontWeight: 600, marginBottom: 8 } }, '服务费项目'), + React.createElement('table', { style: { width: '100%', borderCollapse: 'collapse', fontSize: 12 } }, + React.createElement('thead', null, + React.createElement('tr', null, + React.createElement('th', { style: { textAlign: 'left', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '服务项目'), + React.createElement('th', { style: { textAlign: 'right', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '应收费用'), + React.createElement('th', { style: { textAlign: 'right', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, React.createElement('span', null, React.createElement('span', { style: requiredStar }, '*'), '实收费用')), + React.createElement('th', { style: { textAlign: 'right', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '减免费用'), + React.createElement('th', { style: { textAlign: 'left', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '备注') + ) + ), + React.createElement('tbody', null, + (row.serviceItems || []).map(function (s, si) { + return React.createElement('tr', { key: si }, + React.createElement('td', { style: { padding: '6px 8px' } }, s.name), + React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, (s.receivable != null ? s.receivable : '') + ' 元'), + React.createElement('td', { style: { padding: '6px 8px' } }, + React.createElement(Input, { size: 'small', value: s.actual, disabled: disabled, suffix: '元', onChange: function (e) { updateServiceItem(row.key, si, 'actual', e.target.value); } }) + ), + React.createElement('td', { style: { padding: '6px 8px' } }, + React.createElement(Input, { size: 'small', value: s.discount, disabled: disabled, suffix: '元', onChange: function (e) { updateServiceItem(row.key, si, 'discount', e.target.value); } }) + ), + React.createElement('td', { style: { padding: '6px 8px' } }, + React.createElement(Input, { size: 'small', value: s.remark, disabled: disabled, placeholder: '备注', onChange: function (e) { updateServiceItem(row.key, si, 'remark', e.target.value); } }) + ) + ); + }) + ) + ) + ); + + var proofList = row.discountProof || []; + var onProofUpload = function (e) { + var files = e.target.files; + if (!files || !files.length) return; + setEdited(true); + var next = proofList.slice(); + for (var i = 0; i < files.length; i++) { next.push({ name: files[i].name }); } + updateVehicle(row.key, 'discountProof', next); + e.target.value = ''; + }; + var removeProof = function (idx) { + setEdited(true); + var next = (row.discountProof || []).slice(); + next.splice(idx, 1); + updateVehicle(row.key, 'discountProof', next); + }; + + var checkboxCell = row.receivableCompleted + ? React.createElement(Tooltip, { title: '该车辆已完成提车应收款' }, + React.createElement('span', { style: { display: 'inline-block', cursor: 'not-allowed' } }, + React.createElement('input', { type: 'checkbox', checked: true, disabled: true }) + ) + ) + : React.createElement('input', { type: 'checkbox', checked: row.selected, onChange: function () { toggleVehicleSelected(row.key); } }); + + return React.createElement('tr', { key: row.key }, + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0' } }, checkboxCell), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0' } }, row.index), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0' } }, row.brand), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0' } }, row.model), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0' } }, row.plateNo || '-'), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0', textAlign: 'right' } }, (row.receivableRent || 0) + ' 元'), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0', width: 130 } }, + React.createElement(Input, { size: 'small', value: row.actualRent, disabled: disabled, suffix: '元', onChange: function (e) { updateVehicle(row.key, 'actualRent', e.target.value); } }) + ), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0', width: 130 } }, + React.createElement(Input, { size: 'small', value: row.rentRemark, disabled: disabled, onChange: function (e) { updateVehicle(row.key, 'rentRemark', e.target.value); } }) + ), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0', width: 130 } }, + React.createElement(Input, { size: 'small', value: row.discountAmount, disabled: disabled, suffix: '元', onChange: function (e) { updateVehicle(row.key, 'discountAmount', e.target.value); } }) + ), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0', width: 130 } }, + React.createElement(Input, { size: 'small', value: row.discountRemark, disabled: disabled, onChange: function (e) { updateVehicle(row.key, 'discountRemark', e.target.value); } }) + ), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0', width: 130 } }, + disabled ? '-' : React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 4 } }, + proofList.map(function (p, pidx) { + return React.createElement('div', { key: pidx, style: { display: 'flex', alignItems: 'center', gap: 4, fontSize: 12 } }, + React.createElement('span', { style: { flex: 1, overflow: 'hidden', textOverflow: 'ellipsis' } }, p.name || '附件'), + React.createElement(Button, { type: 'link', size: 'small', danger: true, style: { padding: 0, minWidth: 'auto' }, onClick: function () { removeProof(pidx); } }, '删除') + ); + }), + React.createElement('span', null, + React.createElement('input', { type: 'file', multiple: true, accept: '.jpg,.jpeg,.png,.pdf', style: { display: 'none' }, id: 'proof-' + row.key, onChange: onProofUpload }), + React.createElement(Button, { type: 'default', size: 'small', onClick: function () { var el = document.getElementById('proof-' + row.key); if (el) el.click(); } }, '附件上传') + ) + ) + ), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0', textAlign: 'right' } }, (row.receivableDeposit || 0) + ' 元'), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0' } }, + React.createElement(Popover, { + content: servicePopover, + title: null, + trigger: 'click', + open: activeServiceIndex === row.key, + onOpenChange: function (open) { setServicePopoverRowIndex(open ? row.key : null); } + }, + React.createElement(Button, { type: 'link', size: 'small', disabled: disabled }, '管理') + ) + ), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0', textAlign: 'right' } }, (row.receivableService || 0) + ' 元'), + React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0', textAlign: 'right', width: 130 } }, (row.actualService || '0.00') + ' 元') + ); + }) + ) + ) + ), + hasHydrogenPrepay ? React.createElement('div', { style: { marginBottom: 16 } }, + React.createElement('div', { style: { fontSize: 14, fontWeight: 500, marginBottom: 8 } }, '氢费预付款情况'), + React.createElement('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', gap: 16, maxWidth: 800 } }, + React.createElement('div', { style: formItemStyle }, + React.createElement('div', { style: labelStyle }, '氢费预付款应收金额'), + React.createElement(Input, { value: hydrogen.receivable + ' 元', disabled: true }) + ), + React.createElement('div', { style: formItemStyle }, + React.createElement('div', { style: labelStyle }, React.createElement('span', { style: requiredStar }, '*'), '氢费预付款实收金额'), + React.createElement(Input, { value: hydrogen.actual, suffix: '元', onChange: function (e) { setEdited(true); setHydrogen(function (p) { return Object.assign({}, p, { actual: e.target.value }); }); } }) + ), + React.createElement('div', { style: formItemStyle }, + React.createElement('div', { style: labelStyle }, '减免金额'), + React.createElement(Input, { value: hydrogen.discount, suffix: '元', onChange: function (e) { setEdited(true); setHydrogen(function (p) { return Object.assign({}, p, { discount: e.target.value }); }); } }) + ), + React.createElement('div', { style: formItemStyle }, + React.createElement('div', { style: labelStyle }, '减免金额备注'), + React.createElement(Input, { value: hydrogen.discountRemark, onChange: function (e) { setEdited(true); setHydrogen(function (p) { return Object.assign({}, p, { discountRemark: e.target.value }); }); } }) + ) + ) + ) : null, + React.createElement('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px 24px', marginTop: 16 } }, + React.createElement('div', { style: formItemStyle }, + React.createElement('div', { style: labelStyle }, '开票方式'), + React.createElement(Select, { style: { width: '100%' }, value: invoiceMethod[0], onChange: function (v) { setInvoiceMethod(v); setEdited(true); }, placeholder: '请选择' }, + React.createElement(Option, { value: '先开票后付款' }, '先开票后付款'), + React.createElement(Option, { value: '先付款后开票' }, '先付款后开票') + ) + ), + React.createElement('div', { style: Object.assign({}, formItemStyle, { gridColumn: '1 / -1' }) }, + React.createElement('div', { style: labelStyle }, React.createElement('span', { style: requiredStar }, '*'), '开票备注'), + React.createElement(Input.TextArea, { value: invoiceRemark[0], onChange: function (e) { setInvoiceRemark(e.target.value); setEdited(true); }, rows: 3, placeholder: '请输入开票项目、税率以及其他备注信息,财务将以此进行开票', style: { width: '100%' } }) + ) + ) + ) : null + ), + React.createElement('div', { style: { display: 'flex', gap: 8, marginTop: 24 } }, + React.createElement(Button, { type: 'primary', onClick: handleSubmit }, '提交审核'), + React.createElement(Button, { onClick: handleSave }, '保存'), + React.createElement(Button, { onClick: handleCancel }, '取消') + ), + React.createElement(Modal, { + title: '确认', + open: submitConfirmVisible[0], + onCancel: function () { setSubmitConfirmVisible(false); }, + onOk: handleSubmitConfirm, + okText: '确认', + cancelText: '取消' + }, React.createElement('div', null, '请仔细核对提车首付款实收金额,点击确认则进入工作流')), + React.createElement(Modal, { + title: '确认取消', + open: cancelConfirmVisible[0], + onCancel: function () { setCancelConfirmVisible(false); }, + onOk: handleCancelConfirm, + okText: '确认', + cancelText: '取消' + }, React.createElement('div', null, '取消将会丢失所有已填写数据,是否确认?')), + React.createElement(Modal, { + title: '需求说明', + open: requirementModalVisible[0], + onCancel: function () { setRequirementModalVisible(false); }, + width: 720, + footer: React.createElement(Button, { onClick: function () { setRequirementModalVisible(false); } }, '关闭'), + bodyStyle: { maxHeight: '70vh', overflow: 'auto' } + }, React.createElement('div', { style: { padding: '8px 0' } }, + React.createElement('div', { style: { whiteSpace: 'pre-wrap', fontSize: 13, lineHeight: 1.6 } }, requirementContent) + )) + ); +}; + diff --git a/web端/需求说明/Antd设计规范(v5).md b/web端/需求说明/Antd设计规范(v5).md new file mode 100644 index 0000000..3daf42c --- /dev/null +++ b/web端/需求说明/Antd设计规范(v5).md @@ -0,0 +1,200 @@ +# Ant Design(AntD)设计规范导出(v5) + +> 说明:本文档基于 Ant Design v5 官方文档与设计令牌(Design Token)整理,用于项目内统一 UI 口径。若与线上文档不一致,以官方文档与实际 `token` 输出为准。 +> +> 参考: +> - Ant Design 文档:`https://ant.design/docs/react/introduce` +> - 主题定制(Design Token):`https://ant.design/docs/react/customize-theme` +> - CSS Variables:`https://ant.design/docs/react/css-variables` + +## 1. 设计原则(适用于业务后台) + +- **清晰**:信息层级明确(标题/正文/辅助信息/弱提示),减少不必要装饰。 +- **一致**:同类型组件在同场景下外观与交互一致(按钮语义、表单校验、弹窗页脚等)。 +- **高效**:高频任务优先(表格筛选、快捷操作、批量处理),减少路径长度。 +- **可控**:可预期的反馈(加载/成功/失败/空态),避免“无响应”与不可逆操作。 + +## 2. 视觉基础(Design Token 口径) + +### 2.1 色彩(Color) + +#### 语义色(常用) + +- **主色(Primary)**:品牌/主要操作按钮/高亮状态 + - AntD v5 默认:`colorPrimary = #1677ff` +- **成功(Success)**:成功态、完成态、正向结果 +- **警告(Warning)**:需要注意但不阻断流程 +- **错误(Error)**:失败、阻断、危险操作 +- **信息(Info)**:中性提示(一般与 Primary 同系或更弱) + +#### 文本与背景(建议层级) + +- **正文主文本**:用于标题、正文关键值 +- **次级文本**:用于说明、辅助信息 +- **弱文本**:用于占位、不可用、次要信息 +- **容器背景**:页面背景、卡片/表格容器背景 +- **分割/边框**:分割线、边框、表格网格线 + +> 落地建议:**不要直接手写颜色值**,优先使用 AntD token(如 `colorText` / `colorBgContainer` / `colorBorder` / `colorPrimary`),避免暗色模式或主题切换时失控。 + +### 2.2 字体(Typography) + +- **基础字号**:AntD v5 默认 `fontSize = 14px` +- **字号体系**:在正文 14 的基础上,常用 `12/14/16/20/24`(具体以 token 为准) +- **字重**:正文 400,标题/强调 500/600(按设计与字体实际支持) +- **行高**:正文建议 1.4–1.6 区间;表格/表单密集场景可略收紧但要保证可读性 + +落地建议: +- 表单 label、辅助说明、错误提示使用 **次级/弱** 文本层级,避免与正文抢占注意力。 +- 数字与金额在表格中建议 **右对齐**,并用等宽数字特性(如字体支持 `font-variant-numeric: tabular-nums`)提升可扫读性。 + +### 2.3 间距(Spacing) + +推荐用 8px 网格作为基础节奏(token 会覆盖实际值): +- **4/8/12/16/24/32**:常用间距档位 +- 表单项上下间距、卡片内边距、区块间距尽量统一 + +落地建议: +- 页面区块:24(区块间) +- 卡片/容器内边距:16 或 24 +- 表单项间距:16(常规)/ 12(密集) + +### 2.4 圆角(Radius) + +- AntD v5 默认 `borderRadius = 6px` + 常见分层:`2/4/6/8`(不同组件会使用不同层级) + +落地建议: +- 不要在同一页面混用多个“视觉上相近但不一致”的圆角(例如 4 与 6 随意混用)。 + +### 2.5 阴影与层级(Shadow & Elevation) + +典型层级: +- **容器浮层**:Popover/Dropdown/Tooltip +- **对话框**:Modal +- **抽屉**:Drawer(靠近边缘) + +落地建议: +- 阴影仅用于表达“悬浮/层级”,不要把阴影当分割线使用。 + +### 2.6 动效(Motion) + +- **有意义**:动效用于表达状态变化(展开/收起/加载/反馈),避免纯装饰。 +- **短而稳**:后台系统建议偏短时长,降低等待感;重要确认可以稍长但不要拖沓。 +- **可中断**:用户快速操作时动效不应叠加导致卡顿或错位。 + +## 3. 布局与栅格(Layout & Grid) + +- 使用 `Row/Col` 或 `Space/Flex` 组织布局,避免大量手写 margin 拼接。 +- 表单与筛选区建议固定结构: + - **筛选区**(可折叠) + - **操作区**(主按钮 + 次按钮 + 批量操作) + - **表格区** + - **分页/汇总** +- 页面建议采用“标题 + 关键指标/说明 + 主体内容”层级,减少首屏信息噪声。 + +## 4. 组件使用规范(后台高频) + +### 4.1 Button(按钮) + +- **主按钮(Primary)**:每个视图/弹窗**尽量只有一个**主按钮(最主要动作)。 +- **次按钮(Default)**:辅助动作、返回、取消。 +- **危险(Danger)**:删除/作废/清空等不可逆操作,必须配确认(Popconfirm/Modal.confirm)。 +- **禁用态**:禁用必须有原因(提示或辅助文案),避免“为什么点不了”。 + +弹窗页脚建议顺序(从右到左):**主操作** → 次操作(取消/关闭)。 + +### 4.2 Form(表单) + +- 校验提示就近展示,错误文案可读且可执行(告诉用户怎么改)。 +- 必填项统一标识;不要同页混用不同必填标记策略。 +- 长表单建议分组(Card/Collapse/Steps),并提供保存草稿/分步提交(视业务复杂度)。 + +### 4.3 Table(表格) + +- 列宽:关键列固定宽度,描述列可自适应;避免全自适应导致抖动。 +- 对齐:金额/数量 **右对齐**;状态/标签可居中;文本默认左对齐。 +- 操作列:避免超过 3 个直出操作;其余收纳到 Dropdown/More。 +- 空态:提供原因与下一步(调整筛选/新建/刷新)。 + +### 4.4 Modal / Drawer(弹窗/抽屉) + +- **Modal**:用于强打断、需要确认的任务;内容不宜过长。 +- **Drawer**:用于不中断上下文的编辑/详情;适合较长内容或分区信息。 +- 关闭方式:提供右上角关闭;重要内容关闭需二次确认(有未保存变更时)。 + +### 4.5 Message / Notification / Alert(反馈) + +- **Message**:短反馈(保存成功/复制成功),不承载复杂信息。 +- **Notification**:较长信息、异步结果、可操作提示(带按钮/链接)。 +- **Alert**:页面内静态提示/规则说明/风险提示。 + +落地建议:错误信息尽量包含 **定位信息 + 原因 + 建议动作**(例如“金额格式不合法,请输入最多两位小数”)。 + +## 5. 主题与 Token “导出”(工程落地) + +### 5.1 通过 `ConfigProvider` 统一主题 + +在应用入口用 `ConfigProvider` 设置全局 token(示意): + +```tsx +import { ConfigProvider, theme } from 'antd'; + +export function App() { + return ( + + {/* routes */} + + ); +} +``` + +### 5.2 在页面/组件中打印当前 token(用于“导出当前主题规范”) + +你可以在任意页面临时输出(开发态): + +```tsx +import { theme } from 'antd'; + +export function DebugTokens() { + const { token } = theme.useToken(); + // 控制台复制即可作为“当前工程实际 token 导出” + console.log('antd token =>', token); + return null; +} +``` + +如果需要生成 JSON 文件,可以把 `token` 序列化后下载(浏览器端): + +```ts +export function downloadJson(filename: string, data: unknown) { + const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json;charset=utf-8' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = filename; + a.click(); + URL.revokeObjectURL(url); +} +``` + +## 6. 默认关键值速查(AntD v5) + +- **主色**:`#1677ff` +- **基础字号**:`14px` +- **默认圆角**:`6px` + +> 注:完整 token 以 `theme.useToken()` 输出为准(因为你的工程可能有覆盖)。 + diff --git a/web端/需求说明/财务管理/提车应收款 b/web端/需求说明/财务管理/提车应收款 new file mode 100644 index 0000000..40582a8 --- /dev/null +++ b/web端/需求说明/财务管理/提车应收款 @@ -0,0 +1,49 @@ +提车应收款(2026年3月10日版本) +一个「数字化资产ONEOS运管平台」中的「财务管理」「提车应收款」模块 +#面包屑:财务管理-提车应收款; + +页面分为2个卡片; +1.筛选: +#支持合同编码/项目名称/客户名称/业务部门/业务负责人等筛选方式; +1.1.合同编码:选择器,支持输入框内手动输入下拉模糊匹配对应项; +1.2.项目名称:选择器,支持输入框内手动输入下拉模糊匹配对应项; +1.3.客户名称:选择器,支持输入框内手动输入下拉模糊匹配对应项; +1.4.业务部门:选择器,支持选择所有业务部门; +1.5.业务负责人:选择器,支持选择所有业务负责人; + +2.列表: +#列表展示方式为:嵌套子表格,分为主表和子表,可点击主表展开子表,一个合同编码可以展开多个提车应收款明细; +2.1.主表显示字段为:合同编码、合同类型、项目名称、客户名称、合同生效日期、业务部门、业务负责人、操作; +2.1.1.合同编码:显示该提车应收款租赁合同对应合同编码; +2.1.2.合同类型:显示该租赁合同对应合同类型,显示该合同为试用合同还是正式合同; +2.1.3.项目名称:显示该租赁合同对应项目名称; +2.1.4.客户名称:显示该租赁合同对应客户名称; +2.1.5.合同生效日期:显示该租赁合同生效日期,格式为:YYYY-MM-DD; +2.1.6.业务部门:显示该租赁合同对应业务部门名称; +2.1.7.业务负责人:显示该租赁合同对应业务负责人; +2.1.8.操作:提车收款单; + 2.1.8.1.提车收款单:点击提车收款单,跳转提车收款单页进行子表创建; +主表右下角为分页符,支持选择单页显示多少条数据; + +2.2.子表显示字段为:序号、审批状态、创建时间、提车数量、应收款总额、实收款总额、减免总金额、实际到账金额、是否已开票、已开票金额、操作; +2.2.1.序号:按照提车首付款收费单据提交时间从最早开始显示第1条,按照顺序1.2.3....; +2.2.2.审批状态:审批通过、待审批、审批中、已驳回; + 2.2.2.1.审批通过:该审批通过最终节点审批,显示为审批通过; + 2.2.2.2.待审批:审批提车收款单仅保存,但未提交审批; + 2.2.2.3.审批中:已发起审批流程,但未完成最终节点审批; + 2.2.2.4.审批驳回:发起审批流程,但任意节点被驳回。被驳回的提车收款单支持重新修改后提交审批; +2.2.3.创建时间:显示提车收款单创建时间,格式为:YYYY-MM-DD HH:MM; +2.2.4.创建人:显示已提车收款单创建人姓名; +2.2.5.提车数量:显示该提车收费单对应提车数量,点击提车数量弹出气泡卡片,卡片中列表显示品牌、型号、车牌号; +2.2.6.应收款金额:按照该提车收费单据应收款金额,格式为:xx.xx元; +2.2.7.实收款金额:按照该提车收费单据实收款金额,格式为:xx.xx元; +2.2.8.减免金额:按照该提车收费单减免总金额,显示当前子表对应减免总额,格式为:xx.xx元; +2.2.9.实际到账金额:显示该提车收费单财务填写的实际到账金额,格式为:xx.xx元; +2.2.10.是否已开票:显示财务是否完成开票,已开票显示为:已开票,未开票显示为:未开票,部分开票显示为:部分开票; +2.2.11.已开票金额:显示财务已开票金额,格式为:xx.xx元; +2.2.12.总计:显示应收款总额、实收款总额、减免总金额、实际到账金额、已开票金额等所有子表求和数据; +2.2.13.操作:查看、编辑、删除、开票; + 2.2.12.1.查看:点击查看后,跳转提车应收款-查看页面,审批通过、待审批、审批中、审批驳回均可点击查看来查看提车收款单详情; + 2.2.12.2.编辑:点击编辑后,跳转提车应收款-编辑页面,待审批、审批驳回均可进行编辑操作; + 2.2.12.2.删除:待审批、审批驳回状态子表支持删除功能,点击弹出二次确认,点击确认后删除该记录; + 2.2.12.3.开票:点击开票后,进入提车应收款-开票页面,审批通过状态子表才显示,开票由财务人员操作,需要做权限控制,其他用户没有开票入口; \ No newline at end of file diff --git a/web端/需求说明/财务管理/提车应收款-审批 b/web端/需求说明/财务管理/提车应收款-审批 new file mode 100644 index 0000000..49c35a8 --- /dev/null +++ b/web端/需求说明/财务管理/提车应收款-审批 @@ -0,0 +1,69 @@ +提车应收款-审批(2026年3月4日版本) +一个「数字化资产ONEOS运管平台」中的「财务管理」「提车应收款」「审批」模块 +#面包屑:财务管理-提车应收款-审批; + +页面分为4个卡片,业务流程请参考流程图; +1.项目信息: +#显示项目详细信息,包括: +1.1.合同编码:显示该租赁合同对应合同编码; +1.2.合同类型:显示该租赁合同对应合同编码; +1.3.项目名称:显示该租赁合同对应项目名称; +1.4.客户名称:显示该租赁合同对应客户名称; +1.5.付款方式:显示该租赁合同对应付款方式,分为:预付、后付两种,根据实际合同反写; +1.6.付款周期:显示该租赁合同对应付款周期,格式为:x个月; +1.7.合同生效时间:显示该租赁合同对应合同生效时间; +1.8.合同结束时间:显示该租赁合同对应合同结束时间; +1.9.业务部门:显示该租赁合同对应业务部门名称; +1.10.业务负责人:显示该租赁合同对应业务负责人; + +2.提车应收款信息: +#上方显示应收款总额、实收款总额; +2.1.应收款总额:显示提交审核时的应收款总额,格式为:xx.xx元,点击金额弹出气泡卡片,卡片中显示列表,字段为: + 2.1.1.项目:包括总计应收车辆月租金、总计应收车辆保证金、总计应收服务费三项; + 2.1.2.金额:显示该项目对应应收金额; +2.2.实收款总额:显示提交审核时的实收款总额,格式为:xx.xx元,点击金额弹出气泡卡片,卡片中显示列表,字段为: + 2.2.1.项目:包括总计实收车辆月租金、总计应收车辆保证金、总计实收服务费、总计减免金额四项; + 2.2.2.金额:显示该项目对应实收金额; +#中间为车辆应收款明细,以列表展示提车应收款已选中所有车辆明细,包括以下字段: +2.1.序号:根据租赁合同中车辆对应序号顺序展示; +2.2.品牌:显示租赁合同中车辆对应品牌; +2.3.型号:显示租赁合同中车辆对应型号; +2.4.车牌号:显示租赁合同中车辆对应车牌号,如果车牌号为空则显示为-(后续如果交车成功,则车牌号会反显在该处); +2.5.应收车辆月租金:根据租赁合同付款周期自动计算,例如:付款周期为6个月,则这里显示金额为:车辆月租金*6; +2.6.实收车辆月租金:显示提交审核时的实收车辆月租金,格式为:xx.xx元; +2.7.车辆租金备注:显示提交审核时的车辆租金备注,超长则显示...,悬浮时显示全部详细内容; +2.8.减免金额:显示提交审核时的减免金额,格式为:xx.xx元; +2.9.减免金额备注:显示提交审核时的减免金额备注,超长则显示...,悬浮时显示全部详细内容; +2.10.减免证明:显示所有附件,可点击预览; +2.11.应收车辆保证金:显示租赁合同中当前车辆保证金金额,格式为:xx.xx元; +2.12.服务费项目:点击管理,弹出气泡卡片,气泡卡片标题为服务费项目,下方为列表,显示服务项目、应收费用、实收费用、备注; + 2.12.1.服务项目:显示租赁合同中所有服务项目名称; + 2.12.2.应收费用:显示租赁合同中所有服务项目对应费用; + 2.12.3.实收费用:显示提交审核时的实收费用金额,格式为:xx.xx元; + 2.12.4.减免费用:显示提交审核时的减免费用金额,格式为:xx.xx元; + 2.12.4.备注:显示提交审核时的服务费减免备注,超长则显示...,悬浮时显示全部详细内容; +2.13.应收服务费:显示当前车辆所有服务费应收费用总和,格式为:xx.xx元; +2.14.实收服务费:显示当前车辆所有服务费实收费用总和,格式为:xx.xx元; +2.15.列表下方对应字段下方显示总计数据,包括:总计应收车辆月租金、总计实收车辆月租金、总计应收车辆保证金、总计应收服务费、总计实收服务费、总计减免金额; +列表不支持分页功能; +#下方为氢费预付款情况,以列表展示租赁合同中所有氢费预付款明细,包括以下字段,如租赁合同氢费非预付款模式,则不显示该部分内容: +2.19.氢费预付款应收金额:显示租赁合同中氢费预付款金额,格式为:xx.xx元,支持2位小数; +2.20.氢费预付款实收金额:显示提交审核时的氢费预付款实收金额,格式为:xx.xx元; +2.21.减免金额:显示提交审核时的减免金额,格式为:xx.xx元; +2.22.减免金额备注:显示提交审核时的氢费预付款减免备注,超长则显示...,悬浮时显示全部详细内容; +#最底部为开票方式、开票备注; +2.23.开票方式:显示提交审核时的开票方式; +2.24.开票备注:显示提交审核时的开票备注; + +3.审核情况: +#上方为纵向步骤条,显示所有审批步骤 +3.1.步骤中显示部门名称,部门名称后方显示审批状态、审批人、审批时间; + 3.1.1.部门名称:当前节点部门名称; + 3.1.2.审批状态:分为待审批、审批通过、审批驳回; + 3.1.3.审批人:步骤节点对应审批人姓名; + 3.1.4.审批时间:显示审批通过/驳回时间,格式为:YYYY-MM-DD HH:MM; +3.2.当前步骤的审批人下方为审批说明:非必填,文本域,支持自定义输入; +3.3.底部为审批通过、驳回、取消; + 3.3.1.审批通过:点击提示:审批完成,同时下个流程节点审批人工作台待办任务中收到审批任务和消息提示;如当前已是最后节点,则完成审批,同时进入财务开票环节; + 4.3.2.驳回:点击进行二次确认,二次确认后提示:驳回成功,同时该任务在列表中显示为审核驳回,发起人可重新编辑; + 4.3.3.取消:点击返回提车应收款列表页; \ No newline at end of file diff --git a/web端/需求说明/财务管理/提车应收款-开票信息 b/web端/需求说明/财务管理/提车应收款-开票信息 new file mode 100644 index 0000000..5474a48 --- /dev/null +++ b/web端/需求说明/财务管理/提车应收款-开票信息 @@ -0,0 +1,82 @@ +提车应收款-开票信息(2026年3月5日版本) +一个「数字化资产ONEOS运管平台」中的「财务管理」「提车应收款」「开票信息」模块 +#面包屑:财务管理-提车应收款-开票信息; + +页面分为3个卡片,业务流程请参考流程图; +1.项目信息: +#显示项目详细信息,包括: +1.1.合同编码:显示该租赁合同对应合同编码; +1.2.合同类型:显示该租赁合同对应合同编码; +1.3.项目名称:显示该租赁合同对应项目名称; +1.4.客户名称:显示该租赁合同对应客户名称; +1.5.付款方式:显示该租赁合同对应付款方式,分为:预付、后付两种,根据实际合同反写; +1.6.付款周期:显示该租赁合同对应付款周期,格式为:x个月; +1.7.合同生效时间:显示该租赁合同对应合同生效时间; +1.8.合同结束时间:显示该租赁合同对应合同结束时间; +1.9.业务部门:显示该租赁合同对应业务部门名称; +1.10.业务负责人:显示该租赁合同对应业务负责人; + +22.提车应收款信息: +#上方显示应收款总额、实收款总额; +2.1.应收款总额:显示提交审核时的应收款总额,格式为:xx.xx元,点击金额弹出气泡卡片,卡片中显示列表,字段为: + 2.1.1.项目:包括总计应收车辆月租金、总计应收车辆保证金、总计应收服务费三项; + 2.1.2.金额:显示该项目对应应收金额; +2.2.实收款总额:显示提交审核时的实收款总额,格式为:xx.xx元,点击金额弹出气泡卡片,卡片中显示列表,字段为: + 2.2.1.项目:包括总计实收车辆月租金、总计应收车辆保证金、总计实收服务费、总计减免金额四项; + 2.2.2.金额:显示该项目对应实收金额; +#中间为车辆应收款明细,以列表展示提车应收款已选中所有车辆明细,包括以下字段: +2.1.序号:根据租赁合同中车辆对应序号顺序展示; +2.2.品牌:显示租赁合同中车辆对应品牌; +2.3.型号:显示租赁合同中车辆对应型号; +2.4.车牌号:显示租赁合同中车辆对应车牌号,如果车牌号为空则显示为-(后续如果交车成功,则车牌号会反显在该处); +2.5.应收车辆月租金:根据租赁合同付款周期自动计算,例如:付款周期为6个月,则这里显示金额为:车辆月租金*6; +2.6.实收车辆月租金:显示提交审核时的实收车辆月租金,格式为:xx.xx元; +2.7.车辆租金备注:显示提交审核时的车辆租金备注,超长则显示...,悬浮时显示全部详细内容; +2.8.减免金额:显示提交审核时的减免金额,格式为:xx.xx元; +2.9.减免金额备注:显示提交审核时的减免金额备注,超长则显示...,悬浮时显示全部详细内容; +2.10.减免证明:显示所有附件,可点击预览; +2.11.应收车辆保证金:显示租赁合同中当前车辆保证金金额,格式为:xx.xx元; +2.12.服务费项目:点击管理,弹出气泡卡片,气泡卡片标题为服务费项目,下方为列表,显示服务项目、应收费用、实收费用、备注; + 2.12.1.服务项目:显示租赁合同中所有服务项目名称; + 2.12.2.应收费用:显示租赁合同中所有服务项目对应费用; + 2.12.3.实收费用:显示提交审核时的实收费用金额,格式为:xx.xx元; + 2.12.4.减免费用:显示提交审核时的减免费用金额,格式为:xx.xx元; + 2.12.4.备注:显示提交审核时的服务费减免备注,超长则显示...,悬浮时显示全部详细内容; +2.13.应收服务费:显示当前车辆所有服务费应收费用总和,格式为:xx.xx元; +2.14.实收服务费:显示当前车辆所有服务费实收费用总和,格式为:xx.xx元; +2.15.列表下方对应字段下方显示总计数据,包括:总计应收车辆月租金、总计实收车辆月租金、总计应收车辆保证金、总计应收服务费、总计实收服务费、总计减免金额; +列表不支持分页功能; +#下方为氢费预付款情况,以列表展示租赁合同中所有氢费预付款明细,包括以下字段,如租赁合同氢费非预付款模式,则不显示该部分内容: +2.19.氢费预付款应收金额:显示租赁合同中氢费预付款金额,格式为:xx.xx元,支持2位小数; +2.20.氢费预付款实收金额:显示提交审核时的氢费预付款实收金额,格式为:xx.xx元; +2.21.减免金额:显示提交审核时的减免金额,格式为:xx.xx元; +2.22.减免金额备注:显示提交审核时的氢费预付款减免备注,超长则显示...,悬浮时显示全部详细内容; +#最底部为开票方式、开票备注; +2.23.开票方式:显示提交审核时的开票方式; +2.24.开票备注:显示提交审核时的开票备注; + +3.开票信息: +#上方显示该租赁合同客户信息(如果该合同转为三方合同,则未开票部分开票信息修改为丙方开票信息),包括: +3.1.客户名称:显示租赁合同客户名称; +3.2.纳税人识别号:显示该客户对应纳税人识别号; +3.3.地址:显示该客户对应地址; +3.4.电话:显示该客户对应电话; +3.5.账户:显示该客户对应账户; +3.6.开户行:显示该客户对应开户行; +3.7.邮寄地址:显示该客户对应邮寄地址; +#下方显示列表,包括以下字段,列表上方显示未付金额,格式为xx.xx元(计算方式为:实收款总额-(所有到账金额记录总和)),列表显示过往提交的历史记录,同时默认一行可编辑,支持继续新增一行和删除行操作(过往提交的历史记录无法删除): +3.8.到账时间:时间选择器,格式为:YYYY-MM-DD HH:MM,新增未提交的记录支持删除,但之前已完成提交的记录无法删除; +3.9.到账金额:输入框,支持2位小数,格式为:xx.xx元,新增未提交的记录支持删除,但之前已完成提交的记录无法删除;; +3.10.开票时间:时间选择器,格式为:YYYY-MM-DD HH:MM,默认为打开页面的时间,新增未提交的记录支持删除,但之前已完成提交的记录无法删除; +3.11.发票附件:附件上传按钮,支持多附件上传,上传后显示附件名称,新增未提交的记录支持删除,但之前已完成提交的记录无法删除; +3.12.备注:输入框,新增未提交的记录支持删除,但之前已完成提交的记录无法删除;; +3.13.开票人:显示上传发票的操作用户姓名; +3.14.支持新增一行、删除一行(新增未提交的记录可以删除,已完成提交的历史记录不能删除); + +4.审批情况: +#上方为纵向步骤条,显示所有审批步骤 +4.1.步骤中显示部门名称,部门名称后方显示审批状态、审批人、审批时间,所有审批记录已完成; + +5.底部为提交,取消; +5.1.提交:点击提交,进行二次确认,文案为:请仔细核对提车首付款到账和开票信息,提交后无法修改; +5.2.取消:点击取消,进行二次确认,文案为:取消将会丢失所有已填写数据,是否确认,点击确认则跳转至提车应收款列表页; \ No newline at end of file diff --git a/web端/需求说明/财务管理/提车应收款-提车收款单-编辑 b/web端/需求说明/财务管理/提车应收款-提车收款单-编辑 new file mode 100644 index 0000000..58e8aa6 --- /dev/null +++ b/web端/需求说明/财务管理/提车应收款-提车收款单-编辑 @@ -0,0 +1,68 @@ +提车应收款-提车收款单-编辑(2026年3月10日版本) +一个「数字化资产ONEOS运管平台」中的「财务管理」「提车应收款」「提车收款单」「编辑」模块 +#面包屑:财务管理-提车应收款-提车收款单-编辑; + +页面分为3个卡片,业务流程请参考流程图; +1.项目信息: +#显示项目详细信息,包括: +1.1.合同编码:显示该租赁合同对应合同编码; +1.2.合同类型:显示该租赁合同对应合同类型; +1.3.项目名称:显示该租赁合同对应项目名称; +1.4.客户名称:显示该租赁合同对应客户名称; +1.5.付款方式:显示该租赁合同对应付款方式,分为:预付、后付两种,根据实际合同反写; +1.6.付款周期:显示该租赁合同对应付款周期,格式为:x个月; +1.7.合同生效时间:显示该租赁合同对应合同生效时间; +1.8.合同结束时间:显示该租赁合同对应合同结束时间; +1.9.业务部门:显示该租赁合同对应业务部门名称; +1.10.业务负责人:显示该租赁合同对应业务负责人; + +2.提车应收款信息: +#上方显示应收款总额、实收款总额; +2.1.应收款总额:格式为:xx.xx元,重点色,计算方式为:「选中车辆应收车辆月租金总和」+「选中车辆应收车辆保证金总和」+「选中车辆应收服务费总和」; + 点击应收款总额金额,弹出气泡卡片,气泡卡片列表展示:项目、金额; + 2.1.1.项目:包括总计应收车辆月租金、总计应收车辆保证金、总计应收服务费三项; + 2.1.2.金额:显示该项目对应应收金额; +2.2.实收款总额:格式为:xx.xx元,重点色,计算方式为:「选中车辆实收车辆月租金总和」+「选中车辆应收车辆保证金总和」+「选中车辆实收服务费总和」-「选中车辆减免金额总和」; + 点击实收款总额金额,弹出气泡卡片,气泡卡片列表展示:项目、金额; + 2.2.1.项目:包括总计实收车辆月租金、总计应收车辆保证金、总计实收服务费、总计减免金额四项; + 2.2.2.金额:显示该项目对应实收金额; + +#中间为车辆应收款明细,以列表展示租赁合同中所有车辆明细,包括以下字段: +2.1.全选/多选:多选按钮组,支持表头点击全选,编辑页面显示此前保存时已选中相关车辆,支持重新选择车辆; + 提车应收款支持分批次进行收款,如果该车辆之前已提交提车应收款流程,则多选框不能勾选,悬浮多选框时提示:该车辆已完成提车应收款; +2.2.序号:根据租赁合同中车辆对应序号顺序展示; +2.3.品牌:显示租赁合同中车辆对应品牌; +2.4.型号:显示租赁合同中车辆对应型号; +2.5.车牌号:显示租赁合同中车辆对应车牌号,如果车牌号为空则显示为-(后续如果交车成功,则车牌号会反显在该处); +2.6.应收车辆月租金:根据租赁合同付款周期自动计算,例如:付款周期为6个月,则这里显示金额为:车辆月租金*6; +2.7.实收车辆月租金:必填项,输入框,默认反写实收车辆月租金,支持修改,由业务员自行输入车辆月租金金额,精确至2位小数,后缀为元; +2.8.车辆租金备注:选填项,输入框,由业务员自行输入; +2.9.减免金额:选填项,输入框,由业务员自行输入减免金额,默认为0.00,支持2位小数,后缀为元; +2.10.减免金额备注:选填项,输入框,由业务员自行输入备注信息; +2.11.应收车辆保证金:显示租赁合同中当前车辆保证金金额; +2.12.服务费项目:点击管理,弹出气泡卡片,气泡卡片标题为服务费项目,下方为列表,显示服务项目、应收费用、实收费用、备注; + 2.12.1.服务项目:显示租赁合同中所有服务项目名称; + 2.12.2.应收费用:显示租赁合同中所有服务项目对应费用; + 2.12.3.实收费用:必填项,输入框,默认反写应收费用,支持修改,由业务员自行输入实收费用金额,精确至2位小数,后缀为元; + 2.12.4.减免费用:选填项,输入框,默认为:0.00,精确至2位小数,后缀为元; + 2.12.4.备注:选填项,输入框,由业务员自行输入备注信息; +2.13.应收服务费:显示当前车辆所有服务费应收费用总和,格式为:xx.xx元; +2.14.实收服务费:显示当前车辆所有服务费实收费用总和,格式为:xx.xx元; +2.15.减免金额:选填项,输入框,由业务员自行输入减免金额,默认为0.00,支持2位小数,后缀为元; +2.16.减免金额备注:选填项,输入框,由业务员自行输入备注信息; +2.17.减免证明:选填项,附件上传按钮,点击后上传本地文件,支持:jpg、png、pdf等格式,支持多附件上传; + +列表不支持分页功能; +#下方为氢费预付款情况,以列表展示租赁合同中所有氢费预付款明细,包括以下字段,如租赁合同氢费非预付款模式,则不显示该部分内容: +2.19.氢费预付款应收金额:显示租赁合同中氢费预付款金额,格式为:xx.xx元,支持2位小数; +2.20.氢费预付款实收金额:必填项,输入框,默认反写氢费预付款应收金额,支持修改,由业务员自行输入金额,格式为:xx.xx元,支持2位小数; +2.21.减免金额:选填项,输入框,由业务员自行输入减免金额,默认为:0.00元,格式为:xx.xx元,支持2位小数; +2.22.减免金额备注:选填项,输入框,由业务员自行输入减免金额备注信息; +#最底部为开票方式、开票备注; +2.23.开票方式:选择器,选项包括:先开票后付款、先付款后开票,默认为先开票后付款; +2.24.开票备注:必填项,文本域,默认提示文本为:请输入开票项目、税率以及其他备注信息,财务将以此进行开票; + +3.底部为提交审核,保存,取消; +3.1.提交审核:点击提交审核,进行二次确认,文案为:请仔细核对提车首付款实收金额,点击确认则进入工作流; +3.2.保存:点击保存,提示保存成功,跳转至提车应收款列表页,同时该条数据审核状态为:待提交: +3.3.取消:点击取消,进行二次确认,文案为:取消将会丢失所有已填写数据,是否确认,点击确认则跳转至提车应收款列表页; \ No newline at end of file diff --git a/web端/需求说明/财务管理/提车应收款-收款 b/web端/需求说明/财务管理/提车应收款-收款 index 80fb694..2311305 100644 --- a/web端/需求说明/财务管理/提车应收款-收款 +++ b/web端/需求说明/财务管理/提车应收款-收款 @@ -1,12 +1,12 @@ -提车应收款-收款(2026年3月4日版本) -一个「数字化资产ONEOS运管平台」中的「财务管理」「提车应收款」「收款」模块 -#面包屑:财务管理-提车应收款-收款; +提车应收款-提车收款单(2026年3月10日版本) +一个「数字化资产ONEOS运管平台」中的「财务管理」「提车应收款」「提车收款单」模块 +#面包屑:财务管理-提车应收款-提车收款单; 页面分为3个卡片,业务流程请参考流程图; 1.项目信息: #显示项目详细信息,包括: 1.1.合同编码:显示该租赁合同对应合同编码; -1.2.合同类型:显示该租赁合同对应合同编码; +1.2.合同类型:显示该租赁合同对应合同类型; 1.3.项目名称:显示该租赁合同对应项目名称; 1.4.客户名称:显示该租赁合同对应客户名称; 1.5.付款方式:显示该租赁合同对应付款方式,分为:预付、后付两种,根据实际合同反写; @@ -18,17 +18,19 @@ 2.提车应收款信息: #上方显示应收款总额、实收款总额; -2.1.应收款总额:格式为:xx.xx元,重点色,计算方式为:「总计应收车辆月租金」+「总计应收车辆保证金」+「总计应收服务费」; +2.1.应收款总额:格式为:xx.xx元,重点色,计算方式为:「选中车辆应收车辆月租金总和」+「选中车辆应收车辆保证金总和」+「选中车辆应收服务费总和」; 点击应收款总额金额,弹出气泡卡片,气泡卡片列表展示:项目、金额; 2.1.1.项目:包括总计应收车辆月租金、总计应收车辆保证金、总计应收服务费三项; 2.1.2.金额:显示该项目对应应收金额; -2.2.实收款总额:格式为:xx.xx元,重点色,计算方式为:「总计实收车辆月租金」+「总计应收车辆保证金」+「总计实收服务费」-「总计减免金额」; +2.2.实收款总额:格式为:xx.xx元,重点色,计算方式为:「选中车辆实收车辆月租金总和」+「选中车辆应收车辆保证金总和」+「选中车辆实收服务费总和」-「选中车辆减免金额总和」; 点击实收款总额金额,弹出气泡卡片,气泡卡片列表展示:项目、金额; 2.2.1.项目:包括总计实收车辆月租金、总计应收车辆保证金、总计实收服务费、总计减免金额四项; 2.2.2.金额:显示该项目对应实收金额; + #中间为车辆应收款明细,以列表展示租赁合同中所有车辆明细,包括以下字段: -2.1.全选/多选:多选按钮组,支持表头点击全选; +2.1.全选/多选:多选按钮组,支持表头点击全选,默认为取消勾选(整个车辆清单); 选择代表本次提车应收款只收取该部分车辆费用,可以编辑以下相应字段,不选择则代表本次提车应收款不收取该部分车辆费用,不能编辑车辆相应字段; + 提车应收款支持分批次进行收款,如果该车辆之前已提交提车应收款流程,则多选框不能勾选,悬浮多选框时提示:该车辆已完成提车应收款; 2.2.序号:根据租赁合同中车辆对应序号顺序展示; 2.3.品牌:显示租赁合同中车辆对应品牌; 2.4.型号:显示租赁合同中车辆对应型号; @@ -36,44 +38,32 @@ 2.6.应收车辆月租金:根据租赁合同付款周期自动计算,例如:付款周期为6个月,则这里显示金额为:车辆月租金*6; 2.7.实收车辆月租金:必填项,输入框,默认反写实收车辆月租金,支持修改,由业务员自行输入车辆月租金金额,精确至2位小数,后缀为元; 2.8.车辆租金备注:选填项,输入框,由业务员自行输入; -2.9.应收车辆保证金:显示租赁合同中当前车辆保证金金额; -2.10.服务费项目:点击管理,弹出气泡卡片,气泡卡片标题为服务费项目,下方为列表,显示服务项目、应收费用、实收费用、备注; - 2.10.1.服务项目:显示租赁合同中所有服务项目名称; - 2.10.2.应收费用:显示租赁合同中所有服务项目对应费用; - 2.10.3.实收费用:必填项,输入框,默认反写应收费用,支持修改,由业务员自行输入实收费用金额,精确至2位小数,后缀为元; - 2.10.4.减免费用:选填项,输入框,默认为:0.00,精确至2位小数,后缀为元; - 2.10.4.备注:选填项,输入框,由业务员自行输入备注信息; -2.11.应收服务费:显示当前车辆所有服务费应收费用总和,格式为:xx.xx元; -2.12.实收服务费:显示当前车辆所有服务费实收费用总和,格式为:xx.xx元; -2.13.减免金额:选填项,输入框,由业务员自行输入减免金额,默认为0.00,支持2位小数,后缀为元; -2.14.减免金额备注:选填项,输入框,由业务员自行输入备注信息; -2.15.减免证明:选填项,附件上传按钮,点击后上传本地文件,支持:jpg、png、pdf等格式; -2.16.列表下方对应字段下方显示总计数据,包括:总计应收车辆月租金、总计实收车辆月租金、总计应收车辆保证金、总计应收服务费、总计实收服务费、总计减免金额; +2.9.减免金额:选填项,输入框,由业务员自行输入减免金额,默认为0.00,支持2位小数,后缀为元; +2.10.减免金额备注:选填项,输入框,由业务员自行输入备注信息; +2.11.应收车辆保证金:显示租赁合同中当前车辆保证金金额; +2.12.服务费项目:点击管理,弹出气泡卡片,气泡卡片标题为服务费项目,下方为列表,显示服务项目、应收费用、实收费用、备注; + 2.12.1.服务项目:显示租赁合同中所有服务项目名称; + 2.12.2.应收费用:显示租赁合同中所有服务项目对应费用; + 2.12.3.实收费用:必填项,输入框,默认反写应收费用,支持修改,由业务员自行输入实收费用金额,精确至2位小数,后缀为元; + 2.12.4.减免费用:选填项,输入框,默认为:0.00,精确至2位小数,后缀为元; + 2.12.4.备注:选填项,输入框,由业务员自行输入备注信息; +2.13.应收服务费:显示当前车辆所有服务费应收费用总和,格式为:xx.xx元; +2.14.实收服务费:显示当前车辆所有服务费实收费用总和,格式为:xx.xx元; +2.15.减免金额:选填项,输入框,由业务员自行输入减免金额,默认为0.00,支持2位小数,后缀为元; +2.16.减免金额备注:选填项,输入框,由业务员自行输入备注信息; +2.17.减免证明:选填项,附件上传按钮,点击后上传本地文件,支持:jpg、png、pdf等格式,支持多附件上传; + 列表不支持分页功能; #下方为氢费预付款情况,以列表展示租赁合同中所有氢费预付款明细,包括以下字段,如租赁合同氢费非预付款模式,则不显示该部分内容: -2.17.氢费预付款应收金额:显示租赁合同中氢费预付款金额,格式为:xx.xx元,支持2位小数; -2.18.氢费预付款实收金额:必填项,输入框,默认反写氢费预付款应收金额,支持修改,由业务员自行输入金额,格式为:xx.xx元,支持2位小数; -2.19.减免金额:选填项,输入框,由业务员自行输入减免金额,默认为:0.00元,格式为:xx.xx元,支持2位小数; -2.20.减免金额备注:选填项,输入框,由业务员自行输入减免金额备注信息; +2.19.氢费预付款应收金额:显示租赁合同中氢费预付款金额,格式为:xx.xx元,支持2位小数; +2.20.氢费预付款实收金额:必填项,输入框,默认反写氢费预付款应收金额,支持修改,由业务员自行输入金额,格式为:xx.xx元,支持2位小数; +2.21.减免金额:选填项,输入框,由业务员自行输入减免金额,默认为:0.00元,格式为:xx.xx元,支持2位小数; +2.22.减免金额备注:选填项,输入框,由业务员自行输入减免金额备注信息; #最底部为开票方式、开票备注; -2.21.开票方式:选择器,选项包括:先开票后付款、先付款后开票,默认为先开票后付款; -2.22.开票备注:必填项,文本域,默认提示文本为:请输入开票项目、税率以及其他备注信息,财务将以此进行开票; +2.23.开票方式:选择器,选项包括:先开票后付款、先付款后开票,默认为先开票后付款; +2.24.开票备注:必填项,文本域,默认提示文本为:请输入开票项目、税率以及其他备注信息,财务将以此进行开票; -3.开票信息: -#上方显示该租赁合同客户信息(如果该合同转为三方合同,则未开票部分开票信息修改为丙方开票信息),包括: -3.1.客户名称:显示租赁合同客户名称; -3.2.纳税人识别号:显示该客户对应纳税人识别号; -3.3.地址:显示该客户对应地址; -3.4.电话:显示该客户对应电话; -3.5.账户:显示该客户对应账户; -3.6.开户行:显示该客户对应开户行; -3.7.邮寄地址:显示该客户对应邮寄地址; -#下方显示 -3.8.开票时间:格式为:YYYY-MM-DD HH:MM ,如未上传发票则显示未上传; -3.9.发票附件:显示已上传发票附件,格式为:发票文件名称.文件类型,如未上传发票附件,则显示未上传; -3.10.开票人:显示上传发票的操作用户姓名,如未上传则显示-; - -4.底部为提交审核,保存,取消; -4.1.提交审核:点击提交审核,进行二次确认,文案为:请仔细核对提车首付款实收金额,点击确认则进入工作流; -4.2.保存:点击保存,提示保存成功,跳转至提车应收款列表页,同时该条数据审核状态为:待提交: -4.3.取消:点击取消,进行二次确认,文案为:取消将会丢失所有已填写数据,是否确认,点击确认则跳转至提车应收款列表页; \ No newline at end of file +3.底部为提交审核,保存,取消; +3.1.提交审核:点击提交审核,进行二次确认,文案为:请仔细核对提车首付款实收金额,点击确认则进入工作流; +3.2.保存:点击保存,提示保存成功,跳转至提车应收款列表页,同时该条数据审核状态为:待提交: +3.3.取消:点击取消,进行二次确认,文案为:取消将会丢失所有已填写数据,是否确认,点击确认则跳转至提车应收款列表页; \ No newline at end of file diff --git a/web端/需求说明/财务管理/提车应收款-查看 b/web端/需求说明/财务管理/提车应收款-查看 new file mode 100644 index 0000000..0de24ce --- /dev/null +++ b/web端/需求说明/财务管理/提车应收款-查看 @@ -0,0 +1,83 @@ +提车应收款-查看(2026年3月10日版本) +一个「数字化资产ONEOS运管平台」中的「财务管理」「提车应收款」「提车收款单」模块 +#面包屑:财务管理-提车应收款-提车收款单; + +页面分为3个卡片,业务流程请参考流程图; +1.项目信息: +#显示项目详细信息,包括: +1.1.合同编码:显示该租赁合同对应合同编码; +1.2.合同类型:显示该租赁合同对应合同类型; +1.3.项目名称:显示该租赁合同对应项目名称; +1.4.客户名称:显示该租赁合同对应客户名称; +1.5.付款方式:显示该租赁合同对应付款方式,分为:预付、后付两种,根据实际合同反写; +1.6.付款周期:显示该租赁合同对应付款周期,格式为:x个月; +1.7.合同生效时间:显示该租赁合同对应合同生效时间; +1.8.合同结束时间:显示该租赁合同对应合同结束时间; +1.9.业务部门:显示该租赁合同对应业务部门名称; +1.10.业务负责人:显示该租赁合同对应业务负责人; + +2.提车应收款信息: +#上方显示应收款总额、实收款总额; +2.1.应收款总额:格式为:xx.xx元,重点色,计算方式为:「选中车辆应收车辆月租金总和」+「选中车辆应收车辆保证金总和」+「选中车辆应收服务费总和」; + 点击应收款总额金额,弹出气泡卡片,气泡卡片列表展示:项目、金额; + 2.1.1.项目:包括总计应收车辆月租金、总计应收车辆保证金、总计应收服务费三项; + 2.1.2.金额:显示该项目对应应收金额; +2.2.实收款总额:格式为:xx.xx元,重点色,计算方式为:「选中车辆实收车辆月租金总和」+「选中车辆应收车辆保证金总和」+「选中车辆实收服务费总和」-「选中车辆减免金额总和」; + 点击实收款总额金额,弹出气泡卡片,气泡卡片列表展示:项目、金额; + 2.2.1.项目:包括总计实收车辆月租金、总计应收车辆保证金、总计实收服务费、总计减免金额四项; + 2.2.2.金额:显示该项目对应实收金额; + +#中间为车辆应收款明细,以列表展示租赁合同中所有车辆明细,包括以下字段: +2.1.全选/多选:多选按钮组,支持表头点击全选,默认为取消勾选(整个车辆清单); + 选择代表本次提车应收款只收取该部分车辆费用,可以编辑以下相应字段,不选择则代表本次提车应收款不收取该部分车辆费用,不能编辑车辆相应字段; + 提车应收款支持分批次进行收款,如果该车辆之前已提交提车应收款流程,则多选框不能勾选,悬浮多选框时提示:该车辆已完成提车应收款; +2.2.序号:根据租赁合同中车辆对应序号顺序展示; +2.3.品牌:显示租赁合同中车辆对应品牌; +2.4.型号:显示租赁合同中车辆对应型号; +2.5.车牌号:显示租赁合同中车辆对应车牌号,如果车牌号为空则显示为-(后续如果交车成功,则车牌号会反显在该处); +2.6.应收车辆月租金:根据租赁合同付款周期自动计算,例如:付款周期为6个月,则这里显示金额为:车辆月租金*6; +2.7.实收车辆月租金:必填项,输入框,默认反写实收车辆月租金,支持修改,由业务员自行输入车辆月租金金额,精确至2位小数,后缀为元; +2.8.车辆租金备注:选填项,输入框,由业务员自行输入; +2.9.减免金额:选填项,输入框,由业务员自行输入减免金额,默认为0.00,支持2位小数,后缀为元; +2.10.减免金额备注:选填项,输入框,由业务员自行输入备注信息; +2.11.应收车辆保证金:显示租赁合同中当前车辆保证金金额; +2.12.服务费项目:点击管理,弹出气泡卡片,气泡卡片标题为服务费项目,下方为列表,显示服务项目、应收费用、实收费用、备注; + 2.12.1.服务项目:显示租赁合同中所有服务项目名称; + 2.12.2.应收费用:显示租赁合同中所有服务项目对应费用; + 2.12.3.实收费用:必填项,输入框,默认反写应收费用,支持修改,由业务员自行输入实收费用金额,精确至2位小数,后缀为元; + 2.12.4.减免费用:选填项,输入框,默认为:0.00,精确至2位小数,后缀为元; + 2.12.4.备注:选填项,输入框,由业务员自行输入备注信息; +2.13.应收服务费:显示当前车辆所有服务费应收费用总和,格式为:xx.xx元; +2.14.实收服务费:显示当前车辆所有服务费实收费用总和,格式为:xx.xx元; +2.15.减免金额:选填项,输入框,由业务员自行输入减免金额,默认为0.00,支持2位小数,后缀为元; +2.16.减免金额备注:选填项,输入框,由业务员自行输入备注信息; +2.17.减免证明:选填项,附件上传按钮,点击后上传本地文件,支持:jpg、png、pdf等格式,支持多附件上传; +2.18.列表下方对应字段下方显示总计数据,包括:总计应收车辆月租金、总计实收车辆月租金、总计应收车辆保证金、总计应收服务费、总计实收服务费、总计减免金额; +列表不支持分页功能; +#下方为氢费预付款情况,以列表展示租赁合同中所有氢费预付款明细,包括以下字段,如租赁合同氢费非预付款模式,则不显示该部分内容: +2.19.氢费预付款应收金额:显示租赁合同中氢费预付款金额,格式为:xx.xx元,支持2位小数; +2.20.氢费预付款实收金额:必填项,输入框,默认反写氢费预付款应收金额,支持修改,由业务员自行输入金额,格式为:xx.xx元,支持2位小数; +2.21.减免金额:选填项,输入框,由业务员自行输入减免金额,默认为:0.00元,格式为:xx.xx元,支持2位小数; +2.22.减免金额备注:选填项,输入框,由业务员自行输入减免金额备注信息; +#最底部为开票方式、开票备注; +2.23.开票方式:选择器,选项包括:先开票后付款、先付款后开票,默认为先开票后付款; +2.24.开票备注:必填项,文本域,默认提示文本为:请输入开票项目、税率以及其他备注信息,财务将以此进行开票; + +3.客户付款信息: +#显示列表,包括以下字段,列表上方显示未付金额,格式为xx.xx元(计算方式为:实收款总额-(所有到账金额记录总和)),列表显示过往提交的历史记录; + 该部分数据由V1.2版本从YS系统对接获取; +3.1.到账时间:显示实际到账时间,格式为:YYYY-MM-DD HH:MM; +3.2.到账金额:显示实际到账金额,支持2位小数,格式为:xx.xx元; +3.3.开票时间:显示实际开票时间,格式为:YYYY-MM-DD HH:MM; +3.4.发票附件:显示附件名称,支持点击预览,支持多附件; +3.5.备注:显示实际开票备注信息,超长则显示...,悬浮时显示全部详细内容; + +4.审批情况: +#显示竖向步骤条,显示各流程节点部门、审批状态、审批人、审批时间; +4.1.部门:显示部门名称; +4.2.审批状态:显示当前节点审批状态; +4.3.审批人:显示当前节点审批人; +4.4.审批时间:显示当前节点审批时间,格式为:YYYY-MM-DD HH:MM; + +5.底部为返回; +5.1.返回:点击跳转提车应收款列表页; \ No newline at end of file