commit 09cc45db36bdf93c720585841d887dd131fe2059 Author: 王冕 Date: Fri Feb 27 18:11:40 2026 +0800 Initial commit: ONE-OS project Made-with: Cursor diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..ca937d0 Binary files /dev/null and b/.DS_Store differ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..7a73a41 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/blueway/数据集管理.jsx b/blueway/数据集管理.jsx new file mode 100644 index 0000000..1a3e8fa --- /dev/null +++ b/blueway/数据集管理.jsx @@ -0,0 +1,506 @@ +// 【重要】必须使用 const Component 作为组件变量名 +// 数据集管理 - 模仿 GitLab 项目列表风格(纯 React,不依赖 antd,兼容 Axhub) + +const Component = function () { + var useState = React.useState; + var useCallback = React.useCallback; + var useMemo = React.useMemo; + + // GitLab 风格色值(Pajamas 近似) + var colors = { + pageBg: '#fafafa', + cardBg: '#ffffff', + border: '#e5e5e5', + borderLight: '#ebebeb', + text: '#333333', + textSecondary: '#666666', + textMuted: '#999999', + primary: '#1f75cb', + primaryHover: '#1068bf', + link: '#1f75cb', + success: '#108548', + warning: '#c17d10', + danger: '#dd2b0e', + hoverBg: '#f5f5f5', + tabActive: '#333333', + tabInactive: '#666666' + }; + + var filterState = useState({ + keyword: '', + datasetType: '', + status: '' + }); + var filters = filterState[0]; + var setFilters = filterState[1]; + + var activeTabState = useState('all'); + var activeTab = activeTabState[0]; + var setActiveTab = activeTabState[1]; + + var sortState = useState('updated_desc'); + var sortBy = sortState[0]; + var setSortBy = sortState[1]; + + var listState = useState([ + { id: '1', name: '销售数据集', type: '表格', recordCount: 12000, createTime: '2025-02-20 10:00', updateTime: '2025-02-24 14:00', status: '已发布', remark: '销售业务数据', isStarred: true, owner: '当前用户' }, + { id: '2', name: '客户画像数据集', type: '表格', recordCount: 8500, createTime: '2025-02-19 14:30', updateTime: '2025-02-23 09:00', status: '草稿', remark: '客户标签与画像', isStarred: false, owner: '当前用户' }, + { id: '3', name: '订单明细数据集', type: '表格', recordCount: 25600, createTime: '2025-02-18 09:15', updateTime: '2025-02-22 16:30', status: '已发布', remark: '订单明细与物流', isStarred: true, owner: '当前用户' }, + { id: '4', name: '库存数据集', type: '文件', recordCount: 5200, createTime: '2025-02-17 11:00', updateTime: '2025-02-21 10:00', status: '已下线', remark: '仓库库存快照', isStarred: false, owner: '其他用户' } + ]); + var dataList = listState[0]; + var setDataList = listState[1]; + + var pageState = useState(1); + var page = pageState[0]; + var setPage = pageState[1]; + var pageSizeState = useState(20); + var pageSize = pageSizeState[0]; + + var modalState = useState({ open: false, record: null, mode: 'view' }); + var modalOpen = modalState[0].open; + var modalRecord = modalState[0].record; + var modalMode = modalState[0].mode; + var setModalState = modalState[1]; + + var dropdownOpenIdState = useState(null); + var dropdownOpenId = dropdownOpenIdState[0]; + var setDropdownOpenId = dropdownOpenIdState[1]; + + var confirmState = useState({ open: false, record: null }); + var confirmOpen = confirmState[0].open; + var confirmRecord = confirmState[0].record; + var setConfirmState = confirmState[1]; + + var toastState = useState({ show: false, text: '' }); + var toastShow = toastState[0].show; + var toastText = toastState[0].text; + var setToastState = toastState[1]; + + var typeOptions = useMemo(function () { + return [ + { value: '表格', label: '表格' }, + { value: '文件', label: '文件' }, + { value: 'API', label: 'API' } + ]; + }, []); + + var statusOptions = useMemo(function () { + return [ + { value: '草稿', label: '草稿' }, + { value: '已发布', label: '已发布' }, + { value: '已下线', label: '已下线' } + ]; + }, []); + + var sortOptions = useMemo(function () { + return [ + { value: 'updated_desc', label: '最近更新' }, + { value: 'created_desc', label: '最近创建' }, + { value: 'name_asc', label: '名称 A-Z' }, + { value: 'name_desc', label: '名称 Z-A' } + ]; + }, []); + + var filteredList = useMemo(function () { + var list = dataList.slice(); + if (activeTab === 'mine') list = list.filter(function (r) { return r.owner === '当前用户'; }); + if (activeTab === 'published') list = list.filter(function (r) { return r.status === '已发布'; }); + if (activeTab === 'draft') list = list.filter(function (r) { return r.status === '草稿'; }); + if (activeTab === 'starred') list = list.filter(function (r) { return r.isStarred; }); + if (filters.keyword) { + var kw = filters.keyword.trim().toLowerCase(); + list = list.filter(function (r) { + return (r.name && r.name.toLowerCase().indexOf(kw) !== -1) || + (r.remark && r.remark.toLowerCase().indexOf(kw) !== -1); + }); + } + if (filters.datasetType) list = list.filter(function (r) { return r.type === filters.datasetType; }); + if (filters.status) list = list.filter(function (r) { return r.status === filters.status; }); + if (sortBy === 'updated_desc') list.sort(function (a, b) { return (b.updateTime || b.createTime || '').localeCompare(a.updateTime || a.createTime || ''); }); + else if (sortBy === 'created_desc') list.sort(function (a, b) { return (b.createTime || '').localeCompare(a.createTime || ''); }); + else if (sortBy === 'name_asc') list.sort(function (a, b) { return (a.name || '').localeCompare(b.name || ''); }); + else if (sortBy === 'name_desc') list.sort(function (a, b) { return (b.name || '').localeCompare(a.name || ''); }); + return list; + }, [dataList, activeTab, filters, sortBy]); + + var displayList = useMemo(function () { + var start = (page - 1) * pageSize; + return filteredList.slice(start, start + pageSize); + }, [filteredList, page, pageSize]); + + var totalCount = filteredList.length; + + var handleQuery = useCallback(function () { setPage(1); }, []); + var handleReset = useCallback(function () { + setFilters({ keyword: '', datasetType: '', status: '' }); + setPage(1); + }, []); + + var openModal = useCallback(function (record, mode) { + setModalState({ open: true, record: record || null, mode: mode || 'view' }); + setDropdownOpenId(null); + }, []); + var closeModal = useCallback(function () { + setModalState({ open: false, record: null, mode: 'view' }); + }, []); + + var showToast = useCallback(function (text) { + setToastState({ show: true, text: text }); + setTimeout(function () { setToastState({ show: false, text: '' }); }, 2000); + }, []); + + var toggleStar = useCallback(function (record, e) { + if (e) e.stopPropagation(); + setDataList(dataList.map(function (r) { + if (r.id === record.id) return Object.assign({}, r, { isStarred: !r.isStarred }); + return r; + })); + }, [dataList]); + + var openConfirm = useCallback(function (record) { + setConfirmState({ open: true, record: record }); + setDropdownOpenId(null); + }, []); + var closeConfirm = useCallback(function () { + setConfirmState({ open: false, record: null }); + }, []); + var handleDelete = useCallback(function (record) { + setDataList(dataList.filter(function (r) { return r.id !== record.id; })); + closeConfirm(); + showToast('已删除'); + }, [dataList]); + + function statusTag(status) { + var bg = status === '已发布' ? '#e7f5ec' : (status === '草稿' ? '#f0f0f0' : '#fdf3e6'); + var color = status === '已发布' ? colors.success : (status === '草稿' ? colors.textSecondary : colors.warning); + return React.createElement('span', { + style: { + display: 'inline-block', + padding: '2px 8px', + borderRadius: 4, + fontSize: 12, + background: bg, + color: color + } + }, status); + } + + var tabs = [ + { key: 'all', label: '全部' }, + { key: 'mine', label: '我创建的' }, + { key: 'starred', label: '已加星' }, + { key: 'published', label: '已发布' }, + { key: 'draft', label: '草稿' } + ]; + + return React.createElement('div', { + style: { minHeight: '100vh', background: colors.pageBg, padding: '24px 24px 48px', position: 'relative' } + }, + // 标题栏 + React.createElement('div', { + style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16, flexWrap: 'wrap', gap: 12 } + }, + React.createElement('h1', { style: { margin: 0, fontSize: 24, fontWeight: 600, color: colors.text } }, '数据集'), + React.createElement('button', { + type: 'button', + onClick: function () { openModal(null, 'add'); }, + style: { + padding: '6px 16px', + border: 'none', + borderRadius: 6, + background: colors.primary, + color: '#fff', + fontSize: 14, + cursor: 'pointer', + fontWeight: 500 + } + }, '新建数据集') + ), + + // 搜索与筛选 + React.createElement('div', { + style: { background: colors.cardBg, border: '1px solid ' + colors.border, borderRadius: 6, padding: '12px 16px', marginBottom: 0 } + }, + React.createElement('div', { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 12 } }, + React.createElement('div', { style: { display: 'flex', width: 280 } }, + React.createElement('input', { + type: 'text', + placeholder: '搜索数据集…', + value: filters.keyword, + onChange: function (e) { setFilters(function (f) { var g = {}; for (var k in f) g[k] = f[k]; g.keyword = e.target.value; return g; }); }, + onKeyDown: function (e) { if (e.key === 'Enter') handleQuery(); }, + style: { + flex: 1, + padding: '6px 12px', + border: '1px solid ' + colors.border, + borderRight: 'none', + borderRadius: '6px 0 0 6px', + fontSize: 14, + outline: 'none' + } + }), + React.createElement('button', { + type: 'button', + onClick: handleQuery, + style: { + padding: '6px 12px', + border: '1px solid ' + colors.border, + borderRadius: '0 6px 6px 0', + background: colors.pageBg, + cursor: 'pointer', + fontSize: 14 + } + }, '搜索') + ), + React.createElement('select', { + value: filters.datasetType, + onChange: function (e) { setFilters(function (f) { var g = {}; for (var k in f) g[k] = f[k]; g.datasetType = e.target.value; return g; }); }, + style: { width: 100, padding: '6px 8px', border: '1px solid ' + colors.border, borderRadius: 6, fontSize: 14 } + }, + React.createElement('option', { value: '' }, '类型'), + typeOptions.map(function (o) { return React.createElement('option', { key: o.value, value: o.value }, o.label); }) + ), + React.createElement('select', { + value: filters.status, + onChange: function (e) { setFilters(function (f) { var g = {}; for (var k in f) g[k] = f[k]; g.status = e.target.value; return g; }); }, + style: { width: 100, padding: '6px 8px', border: '1px solid ' + colors.border, borderRadius: 6, fontSize: 14 } + }, + React.createElement('option', { value: '' }, '状态'), + statusOptions.map(function (o) { return React.createElement('option', { key: o.value, value: o.value }, o.label); }) + ), + React.createElement('button', { type: 'button', onClick: handleQuery, style: { padding: '6px 14px', border: '1px solid ' + colors.border, borderRadius: 6, background: colors.cardBg, cursor: 'pointer', fontSize: 14 } }, '搜索'), + React.createElement('button', { type: 'button', onClick: handleReset, style: { padding: '6px 14px', border: '1px solid ' + colors.border, borderRadius: 6, background: colors.cardBg, cursor: 'pointer', fontSize: 14 } }, '重置'), + React.createElement('span', { style: { marginLeft: 'auto', color: colors.textMuted, fontSize: 12 } }, + '排序:', + React.createElement('select', { + value: sortBy, + onChange: function (e) { setSortBy(e.target.value); }, + style: { width: 120, marginLeft: 4, padding: '4px 8px', border: 'none', background: 'transparent', fontSize: 12, color: colors.textSecondary, cursor: 'pointer' } + }, + sortOptions.map(function (o) { return React.createElement('option', { key: o.value, value: o.value }, o.label); }) + ) + ) + ) + ), + + // Tab + React.createElement('div', { + style: { background: colors.cardBg, border: '1px solid ' + colors.border, borderTop: 'none', borderRadius: '0 0 6px 6px', padding: '0 16px', marginBottom: 16, display: 'flex', gap: 8 } + }, + tabs.map(function (t) { + var isActive = activeTab === t.key; + return React.createElement('button', { + key: t.key, + type: 'button', + onClick: function () { setActiveTab(t.key); setPage(1); }, + style: { + padding: '12px 4px', + margin: 0, + border: 'none', + background: 'none', + cursor: 'pointer', + fontSize: 14, + color: isActive ? colors.tabActive : colors.tabInactive, + fontWeight: isActive ? 600 : 400, + borderBottom: '2px solid ' + (isActive ? colors.primary : 'transparent'), + marginBottom: -1 + } + }, t.label); + }) + ), + + // 列表 + React.createElement('div', { + style: { background: colors.cardBg, border: '1px solid ' + colors.border, borderRadius: 6, overflow: 'hidden' } + }, + displayList.length === 0 + ? React.createElement('div', { style: { padding: 48, textAlign: 'center', color: colors.textMuted } }, activeTab === 'all' && !filters.keyword ? '暂无数据集,点击「新建数据集」创建' : '没有匹配的数据集') + : displayList.map(function (row) { + var menuOpen = dropdownOpenId === row.id; + return React.createElement('div', { + key: row.id, + style: { + display: 'flex', + alignItems: 'flex-start', + padding: '12px 16px', + borderBottom: '1px solid ' + colors.borderLight, + cursor: 'pointer', + transition: 'background 0.15s' + }, + onMouseEnter: function (e) { e.currentTarget.style.background = colors.hoverBg; }, + onMouseLeave: function (e) { e.currentTarget.style.background = 'transparent'; }, + onClick: function (e) { + if (e.target.closest('button') || e.target.closest('[data-dropdown]')) return; + openModal(row, 'view'); + } + }, + React.createElement('div', { + style: { + width: 40, height: 40, borderRadius: 6, background: colors.pageBg, border: '1px solid ' + colors.border, + marginRight: 12, flexShrink: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 18, color: colors.primary + } + }, '\uD83D\uDCCA'), + React.createElement('div', { style: { flex: 1, minWidth: 0 } }, + React.createElement('div', { style: { display: 'flex', alignItems: 'center', flexWrap: 'wrap', gap: 8, marginBottom: 4 } }, + React.createElement('span', { style: { fontSize: 14, fontWeight: 600, color: colors.link } }, row.name), + statusTag(row.status), + React.createElement('span', { style: { fontSize: 12, color: colors.textMuted } }, row.type + ' · ' + (row.recordCount != null ? row.recordCount.toLocaleString() + ' 条' : '-')) + ), + row.remark ? React.createElement('div', { style: { fontSize: 13, color: colors.textSecondary, marginBottom: 4, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, row.remark) : null, + React.createElement('div', { style: { fontSize: 12, color: colors.textMuted } }, '更新于 ' + (row.updateTime || row.createTime || '-')) + ), + React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 4, flexShrink: 0 }, onClick: function (e) { e.stopPropagation(); } }, + React.createElement('button', { + type: 'button', + title: row.isStarred ? '取消加星' : '加星', + onClick: function (e) { toggleStar(row, e); }, + style: { border: 'none', background: 'none', cursor: 'pointer', padding: 4, fontSize: 16, color: row.isStarred ? '#c17d10' : colors.textMuted } + }, '\u2605'), + React.createElement('div', { style: { position: 'relative' }, 'data-dropdown': true }, + React.createElement('button', { + type: 'button', + onClick: function (e) { e.stopPropagation(); setDropdownOpenId(menuOpen ? null : row.id); }, + style: { border: 'none', background: 'none', cursor: 'pointer', padding: '2px 8px', fontSize: 16, color: colors.textSecondary } + }, '\u22EE'), + menuOpen ? React.createElement('div', { + style: { + position: 'absolute', + right: 0, + top: '100%', + marginTop: 4, + background: colors.cardBg, + border: '1px solid ' + colors.border, + borderRadius: 6, + boxShadow: '0 4px 12px rgba(0,0,0,0.1)', + minWidth: 100, + zIndex: 10 + } + }, + React.createElement('div', { style: { padding: '6px 12px', cursor: 'pointer', fontSize: 13 }, onClick: function () { openModal(row, 'view'); setDropdownOpenId(null); } }, '查看'), + React.createElement('div', { style: { padding: '6px 12px', cursor: 'pointer', fontSize: 13 }, onClick: function () { openModal(row, 'edit'); setDropdownOpenId(null); } }, '编辑'), + React.createElement('div', { style: { borderTop: '1px solid ' + colors.borderLight } }), + React.createElement('div', { style: { padding: '6px 12px', cursor: 'pointer', fontSize: 13, color: colors.danger }, onClick: function () { openConfirm(row); } }, '删除') + ) : null + ) + ) + ); + }) + ), + + // 分页 + totalCount > 0 ? React.createElement('div', { + style: { display: 'flex', justifyContent: 'flex-end', alignItems: 'center', padding: '16px 0', gap: 16, fontSize: 13, color: colors.textSecondary } + }, + React.createElement('span', null, '共 ' + totalCount + ' 项'), + React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8 } }, + React.createElement('button', { + type: 'button', + disabled: page <= 1, + onClick: function () { setPage(page - 1); }, + style: { padding: '4px 10px', border: '1px solid ' + colors.border, borderRadius: 4, background: colors.cardBg, cursor: page <= 1 ? 'not-allowed' : 'pointer', opacity: page <= 1 ? 0.6 : 1 } + }, '上一页'), + React.createElement('span', null, page + ' / ' + Math.max(1, Math.ceil(totalCount / pageSize))), + React.createElement('button', { + type: 'button', + disabled: page >= Math.ceil(totalCount / pageSize), + onClick: function () { setPage(page + 1); }, + style: { padding: '4px 10px', border: '1px solid ' + colors.border, borderRadius: 4, background: colors.cardBg, cursor: page >= Math.ceil(totalCount / pageSize) ? 'not-allowed' : 'pointer', opacity: page >= Math.ceil(totalCount / pageSize) ? 0.6 : 1 } + }, '下一页') + ) + ) : null, + + // 弹窗(查看/编辑/新增) + modalOpen && React.createElement('div', { + style: { position: 'fixed', left: 0, top: 0, right: 0, bottom: 0, background: 'rgba(0,0,0,0.45)', zIndex: 1000, display: 'flex', alignItems: 'center', justifyContent: 'center' }, + onClick: function (e) { if (e.target === e.currentTarget) closeModal(); } + }, + React.createElement('div', { + style: { background: colors.cardBg, borderRadius: 8, width: 520, maxWidth: '90vw', maxHeight: '90vh', overflow: 'auto', boxShadow: '0 6px 16px rgba(0,0,0,0.2)' }, + onClick: function (e) { e.stopPropagation(); } + }, + React.createElement('div', { style: { padding: '16px 24px', borderBottom: '1px solid ' + colors.border, fontSize: 16, fontWeight: 600 } }, + modalMode === 'view' ? '查看数据集' : (modalMode === 'edit' ? '编辑数据集' : '新建数据集') + ), + (modalRecord || modalMode === 'add') && React.createElement('div', { style: { padding: 24 } }, + React.createElement('div', { style: { marginBottom: 16 } }, + React.createElement('div', { style: { marginBottom: 4, color: colors.textSecondary, fontSize: 13 } }, '数据集名称'), + modalMode === 'view' ? (modalRecord && modalRecord.name) : React.createElement('input', { + type: 'text', + placeholder: '请输入名称', + defaultValue: modalRecord && modalRecord.name, + style: { width: '100%', padding: '8px 12px', border: '1px solid ' + colors.border, borderRadius: 6, fontSize: 14, boxSizing: 'border-box' } + }) + ), + React.createElement('div', { style: { marginBottom: 16 } }, + React.createElement('div', { style: { marginBottom: 4, color: colors.textSecondary, fontSize: 13 } }, '类型'), + modalMode === 'view' ? (modalRecord && modalRecord.type) : React.createElement('select', { + defaultValue: modalRecord && modalRecord.type, + style: { width: '100%', padding: '8px 12px', border: '1px solid ' + colors.border, borderRadius: 6, fontSize: 14, boxSizing: 'border-box' } + }, typeOptions.map(function (o) { return React.createElement('option', { key: o.value, value: o.value }, o.label); })) + ), + React.createElement('div', { style: { marginBottom: 16 } }, + React.createElement('div', { style: { marginBottom: 4, color: colors.textSecondary, fontSize: 13 } }, '备注'), + modalMode === 'view' ? (modalRecord && modalRecord.remark) || '-' : React.createElement('textarea', { + placeholder: '选填', + defaultValue: modalRecord && modalRecord.remark, + rows: 3, + style: { width: '100%', padding: '8px 12px', border: '1px solid ' + colors.border, borderRadius: 6, fontSize: 14, boxSizing: 'border-box', resize: 'vertical' } + }) + ) + ), + React.createElement('div', { style: { padding: '12px 24px', borderTop: '1px solid ' + colors.border, display: 'flex', justifyContent: 'flex-end', gap: 8 } }, + modalMode === 'view' + ? React.createElement('button', { type: 'button', onClick: closeModal, style: { padding: '6px 16px', border: '1px solid ' + colors.border, borderRadius: 6, background: colors.cardBg, cursor: 'pointer', fontSize: 14 } }, '关闭') + : [ + React.createElement('button', { key: 'cancel', type: 'button', onClick: closeModal, style: { padding: '6px 16px', border: '1px solid ' + colors.border, borderRadius: 6, background: colors.cardBg, cursor: 'pointer', fontSize: 14 } }, '取消'), + React.createElement('button', { + key: 'save', + type: 'button', + onClick: function () { showToast(modalMode === 'add' ? '创建成功' : '已保存'); closeModal(); }, + style: { padding: '6px 16px', border: 'none', borderRadius: 6, background: colors.primary, color: '#fff', cursor: 'pointer', fontSize: 14 } + }, modalMode === 'add' ? '创建' : '保存') + ] + ) + ) + ), + + // 删除确认弹窗 + confirmOpen && confirmRecord && React.createElement('div', { + style: { position: 'fixed', left: 0, top: 0, right: 0, bottom: 0, background: 'rgba(0,0,0,0.45)', zIndex: 1001, display: 'flex', alignItems: 'center', justifyContent: 'center' }, + onClick: function (e) { if (e.target === e.currentTarget) closeConfirm(); } + }, + React.createElement('div', { + style: { background: colors.cardBg, borderRadius: 8, padding: 24, width: 400, maxWidth: '90vw', boxShadow: '0 6px 16px rgba(0,0,0,0.2)' }, + onClick: function (e) { e.stopPropagation(); } + }, + React.createElement('div', { style: { fontSize: 16, fontWeight: 600, marginBottom: 8 } }, '删除数据集'), + React.createElement('div', { style: { color: colors.textSecondary, marginBottom: 24 } }, '确定要删除「' + (confirmRecord.name || '') + '」吗?此操作不可恢复。'), + React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: 8 } }, + React.createElement('button', { type: 'button', onClick: closeConfirm, style: { padding: '6px 16px', border: '1px solid ' + colors.border, borderRadius: 6, background: colors.cardBg, cursor: 'pointer', fontSize: 14 } }, '取消'), + React.createElement('button', { + type: 'button', + onClick: function () { handleDelete(confirmRecord); }, + style: { padding: '6px 16px', border: 'none', borderRadius: 6, background: colors.danger, color: '#fff', cursor: 'pointer', fontSize: 14 } + }, '删除') + ) + ) + ), + + // 轻提示 + toastShow && React.createElement('div', { + style: { + position: 'fixed', + left: '50%', + bottom: 48, + transform: 'translateX(-50%)', + padding: '8px 16px', + background: 'rgba(0,0,0,0.75)', + color: '#fff', + borderRadius: 6, + fontSize: 14, + zIndex: 1002 + } + }, toastText) + ); +}; diff --git a/docs/ARCO-DESIGN-SPEC.md b/docs/ARCO-DESIGN-SPEC.md new file mode 100644 index 0000000..acd9445 --- /dev/null +++ b/docs/ARCO-DESIGN-SPEC.md @@ -0,0 +1,125 @@ +# Arco Design 设计规范学习笔记 + +> 参考:[Arco Design 设计原则](https://arco.design/docs/spec/philosophy) + +## 一、设计语言定位:务实的浪漫主义 + +Arco Design 用「**务实的浪漫主义**」形容自己的设计语言,也代表其工作模式。 + +- **务实**:清晰与一致 → 清晰的效率提升是基础,品牌一致性是目的 +- **浪漫**:韵律与开放 → 推敲设计的韵律,用包容开放的体系解决问题 + +设计原则相当于语言体系中的「修辞手法」,在实际设计中提供**模式化的思考结构**,让整个设计系统更完整、可复用。 + +--- + +## 二、八大设计原则 + +### 1. 及时反馈 + +**系统应让用户清楚当前状态,并及时给出对应反馈。** + +- 操作后要有明确、即时的状态变化(如加载、成功、失败) +- 避免用户不确定「有没有生效」「进行到哪一步」 + +--- + +### 2. 贴近现实 + +**用用户的语言、用词和熟悉的概念,而不是系统术语。** + +- 遵循现实世界的惯例 +- 让信息符合自然思考逻辑 +- 避免技术黑话、内部用语 + +--- + +### 3. 系统一致性 + +**同一用语、功能、操作在系统内保持一致。** + +- 相同功能用相同名称、相同交互方式 +- 相同类型的反馈用相同形式(如成功/错误提示) +- 有利于降低学习成本、建立心智模型 + +--- + +### 4. 防止错误发生 + +**比「出错后再提示」更好的是:用设计提前避免错误。** + +- 在用户做出选择/动作之前,就减少容易混淆或错误的选择 +- 通过禁用、约束、确认、默认值等方式预防问题 +- 错误提示是兜底,不是主要手段 + +--- + +### 5. 遵从习惯 + +**减少用户对操作目标的记忆负荷,动作和选项应尽量可见。** + +- 例如:填完表单后,下一步应是「生成/提交表单」,而不是笼统的「完成」 +- 步骤、选项要符合用户对流程的预期 +- 减少「需要记住才能操作」的情况 + +--- + +### 6. 突出重点 + +**用户的行为多是「扫」而不是「读」或「看」。** + +- 设计中应**突出重点**,弱化或剔除无关信息 +- 重要对话/流程中不堆砌无关内容 +- 通过层级、对比、留白引导注意力 + +--- + +### 7. 错误帮助 + +**错误信息要用自然语言表达,而不是仅显示错误码(如 404)。** + +- 准确说明问题所在 +- 给出建设性的解决建议 +- 帮助用户从错误中恢复,将损失降到最低 +- 提供清晰的说明和下一步指引 + +--- + +### 8. 人性化帮助 + +**理想情况是系统本身无需帮助文档;若需要帮助,应分层、适度。** + +帮助的优先级建议: + +1. **无需提示**:设计自解释,用户自然能懂 +2. **一次性提示**:如首次使用的引导、Tooltip +3. **常驻提示**:如表单旁的说明、占位符 +4. **帮助文档**:作为最后兜底,便于深度查阅 + +--- + +## 三、设计规范结构速览(Arco 官网) + +设计相关文档大致包括: + +| 类型 | 说明 | +|------------|------| +| 设计价值观 | 务实与浪漫的价值观定义 | +| 设计原则 | 上述八大原则(本文档重点) | +| 样式指南 | 视觉与交互的样式规范 | +| 组件用法 | 各组件(Button、Form、Table 等)的设计与使用规范 | + +开发侧还有:快速上手、暗黑模式、颜色、设计变量(Token)、国际化、定制主题等,与设计规范配合使用。 + +--- + +## 四、在项目中的使用建议 + +1. **做界面时**:先对照「及时反馈、贴近现实、一致性、防错、遵从习惯、突出重点」检查一版交互与文案。 +2. **写错误与帮助**:遵循「错误帮助」和「人性化帮助」,避免只扔错误码,并控制提示的侵入性。 +3. **统一用语与交互**:建立一份产品用词表与组件使用规范,对齐「系统一致性」。 +4. **用 Arco 组件时**:优先按官方组件用法与样式指南使用,便于保持「务实的浪漫主义」下的整体体验。 + +--- + +*文档根据 [Arco Design 设计原则](https://arco.design/docs/spec/philosophy) 整理,便于团队统一理解与落地。* diff --git a/package.json b/package.json new file mode 100644 index 0000000..a73bfbf --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "name": "one-os", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@arco-design/web-react": "^2.67.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.28.0" + }, + "devDependencies": { + "@types/node": "^22.9.0", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.3", + "typescript": "~5.6.2", + "vite": "^5.4.10" + } +} diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..3548eba --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,15 @@ +import { BrowserRouter, Routes, Route } from 'react-router-dom'; +import VehicleManage from '@/pages/VehicleManage'; + +function App() { + return ( + + + } /> + } /> + + + ); +} + +export default App; diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..2d40348 --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import '@arco-design/web-react/dist/css/arco.css'; +import App from './App'; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + +); diff --git a/src/pages/VehicleManage/VehiclePlateModal.tsx b/src/pages/VehicleManage/VehiclePlateModal.tsx new file mode 100644 index 0000000..6ce3ff5 --- /dev/null +++ b/src/pages/VehicleManage/VehiclePlateModal.tsx @@ -0,0 +1,116 @@ +import React, { useState } from 'react'; +import { Modal, Form, Input, Upload, Image, Message } from '@arco-design/web-react'; +import type { VehicleRecord } from '@/types/vehicle'; + +type Props = { + visible: boolean; + vehicle: VehicleRecord | null; + onCancel: () => void; + onOk: (vin: string, plateNo: string) => void; +}; + +export default function VehiclePlateModal({ visible, vehicle, onCancel, onOk }: Props) { + const [ocrLoading, setOcrLoading] = useState(false); + const [licenseImageUrl, setLicenseImageUrl] = useState(''); + const [form] = Form.useForm(); + + const handleUploadChange = (fileList: { file?: File; url?: string }[]) => { + const file = fileList[0]?.file; + if (!file) return; + const url = URL.createObjectURL(file); + setLicenseImageUrl(url); + setOcrLoading(true); + // 模拟 OCR 识别,实际对接 OCR 接口 + setTimeout(() => { + form.setFieldsValue({ vin: vehicle?.vin ?? '', plateNo: vehicle?.plateNo ?? '' }); + setOcrLoading(false); + }, 1500); + }; + + const handleOk = () => { + form.validate().then((values) => { + const v = (values.vin ?? '').trim(); + const p = (values.plateNo ?? '').trim(); + if (!v || !p) { + Message.warning('请填写车辆识别代号与车牌号'); + return; + } + if (vehicle && v !== vehicle.vin) { + Message.error('车辆识别代号与该车辆不匹配'); + return; + } + onOk(v, p); + reset(); + }).catch(() => {}); + }; + + const reset = () => { + setLicenseImageUrl(''); + setOcrLoading(false); + form.resetFields(); + }; + + const handleCancel = () => { + reset(); + onCancel(); + }; + + return ( + <> + +
+
+
行驶证照片
+ {licenseImageUrl ? ( + + ) : ( + { + const { file } = option; + if (file) handleUploadChange([{ file: file as File }]); + }} + > +
上传行驶证照片
+
+ )} +
+
+ {ocrLoading ? ( +
+ 识别中,请勿关闭页面 +
+ ) : ( +
+ + + + + + +
+ )} +
+
+
+ + ); +} diff --git a/src/pages/VehicleManage/index.tsx b/src/pages/VehicleManage/index.tsx new file mode 100644 index 0000000..7ba00e6 --- /dev/null +++ b/src/pages/VehicleManage/index.tsx @@ -0,0 +1,384 @@ +import React, { useState, useCallback } from 'react'; +import { + Breadcrumb, + Form, + Grid, + Select, + Cascader, + Input, + Button, + Space, + Table, + Card, + Message, + Dropdown, + Menu, + Checkbox, + Modal, + Upload, +} from '@arco-design/web-react'; +import type { TableColumnProps } from '@arco-design/web-react/es/Table'; +import { + IconExport, + IconImport, + IconMore, + IconSearch, + IconRefresh, +} from '@arco-design/web-react/icon'; +import { + regionOptions, + vehicleTypeOptions, + brandOptions, + modelOptions, + customerOptions, + businessDeptOptions, + contractNoOptions, + registeredOwnerOptions, + mockVehicleList, +} from '@/services/vehicle'; +import type { VehicleRecord, VehicleFilterForm } from '@/types/vehicle'; +import VehiclePlateModal from './VehiclePlateModal'; + +const { Row, Col } = Grid; + +const defaultFilter: VehicleFilterForm = {}; + +export default function VehicleManage() { + const [filter, setFilter] = useState(defaultFilter); + const [plateNoQuick, setPlateNoQuick] = useState(''); + const [selectedRowKeys, setSelectedRowKeys] = useState([]); + const [loading, setLoading] = useState(false); + const [data, setData] = useState([]); + const [total, setTotal] = useState(0); + const [page, setPage] = useState(1); + const [pageSize, setPageSize] = useState(20); + const [plateModalVisible, setPlateModalVisible] = useState(false); + const [plateModalVehicle, setPlateModalVehicle] = useState(null); + const [form] = Form.useForm(); + + const loadList = useCallback( + (p = page, ps = pageSize) => { + setLoading(true); + setTimeout(() => { + const res = mockVehicleList({ + ...filter, + plateNo: plateNoQuick || undefined, + page: p, + pageSize: ps, + }); + setData(res.list); + setTotal(res.total); + setLoading(false); + }, 300); + }, + [filter, plateNoQuick, page, pageSize] + ); + + React.useEffect(() => { + loadList(); + }, [loadList]); + + const onFilterSubmit = (values: VehicleFilterForm) => { + setFilter(values); + setPage(1); + loadList(1, pageSize); + }; + + const onFilterReset = () => { + form.resetFields(); + setFilter(defaultFilter); + setPlateNoQuick(''); + setPage(1); + loadList(1, pageSize); + }; + + const onExport = () => { + if (selectedRowKeys.length === 0) { + Message.warning('请先勾选要导出的车辆'); + return; + } + Message.info(`导出 ${selectedRowKeys.length} 条记录(联调时对接导出接口)`); + }; + + const onBatchImport = () => { + Message.info('批量导入(联调时对接导入接口)'); + }; + + const onView = (record: VehicleRecord) => { + Message.info(`查看车辆详情:${record.plateNo || record.vin}`); + // 实际可: navigate(`/vehicle/detail/${record.id}`); + }; + + const onPlate = (record: VehicleRecord) => { + setPlateModalVehicle(record); + setPlateModalVisible(true); + }; + + const onPlateModalOk = (vin: string, plateNo: string) => { + if (plateModalVehicle && (vin !== plateModalVehicle.vin || !plateNo)) { + Message.error('车辆识别代号与该车辆不匹配'); + return; + } + Message.success('上牌信息已更新'); + setPlateModalVisible(false); + setPlateModalVehicle(null); + loadList(); + }; + + const moreMenu = (record: VehicleRecord) => ( + + onPlate(record)}>车辆上牌 + 车辆过户 + 车辆异动 + 车辆调拨 + 车辆报废 + 车辆年审 + 车辆出售 + 故障提报 + + ); + + const columns: TableColumnProps[] = [ + { title: '运营城市', dataIndex: 'operationCity', width: 120, fixed: 'left' }, + { title: '车辆识别代号', dataIndex: 'vin', width: 180, fixed: 'left' }, + { title: '车牌号', dataIndex: 'plateNo', width: 100, fixed: 'left' }, + { title: '车辆编号', dataIndex: 'vehicleNo', width: 100, render: (v) => v || '-' }, + { title: '车辆类型', dataIndex: 'vehicleType', width: 100 }, + { title: '品牌', dataIndex: 'brand', width: 90 }, + { title: '型号', dataIndex: 'model', width: 90 }, + { title: '车身颜色', dataIndex: 'bodyColor', width: 90 }, + { title: '归属停车场', dataIndex: 'parkingLot', width: 110 }, + { title: '客户名称', dataIndex: 'customerName', width: 100 }, + { title: '业务部门', dataIndex: 'businessDept', width: 100 }, + { title: '业务负责人', dataIndex: 'businessOwner', width: 100 }, + { title: '运营状态', dataIndex: 'operationStatus', width: 90 }, + { title: '库位状态', dataIndex: 'libStatus', width: 180 }, + { title: '出库状态', dataIndex: 'outboundStatus', width: 100 }, + { title: '整备状态', dataIndex: 'prepStatus', width: 90 }, + { title: '过户状态', dataIndex: 'transferStatus', width: 120 }, + { title: '维修状态', dataIndex: 'repairStatus', width: 110 }, + { title: '证照状态', dataIndex: 'licenseStatus', width: 90 }, + { title: '报废状态', dataIndex: 'scrapStatus', width: 90 }, + { title: '登记所有权', dataIndex: 'registeredOwner', width: 160 }, + { title: '在线状态', dataIndex: 'onlineStatus', width: 90 }, + { title: '出厂年份', dataIndex: 'manufactureYear', width: 90 }, + { title: '行驶公里数(KM)', dataIndex: 'mileage', width: 120, render: (v) => v?.toFixed(2) ?? '-' }, + { title: '采购入库时间', dataIndex: 'purchaseTime', width: 120 }, + { title: '行驶证注册日期', dataIndex: 'licenseRegisterDate', width: 130 }, + { title: '行驶证检验有效期', dataIndex: 'licenseExpiry', width: 130 }, + { title: '上次交车时间', dataIndex: 'lastDeliveryTime', width: 120 }, + { title: '上次交车里程(KM)', dataIndex: 'lastDeliveryMileage', width: 130, render: (v) => (v ? Number(v).toFixed(2) : '-') }, + { title: '上次还车时间', dataIndex: 'lastReturnTime', width: 120 }, + { title: '上次还车里程(KM)', dataIndex: 'lastReturnMileage', width: 130, render: (v) => (v ? Number(v).toFixed(2) : '-') }, + { title: '强制报废日期', dataIndex: 'forceScrapDate', width: 120 }, + { title: '合同编号', dataIndex: 'contractNo', width: 110 }, + { title: '当前位置', dataIndex: 'currentLocation', width: 300 }, + { title: 'GPS最后上传时间', dataIndex: 'gpsLastTime', width: 160 }, + { + title: '操作', + dataIndex: 'op', + width: 140, + fixed: 'right', + render: (_, record) => ( + + + + + + + ), + }, + ]; + + const watchedBrand = Form.useWatch('brand', form); + const modelOptionsByBrand = watchedBrand ? (modelOptions as Record)[watchedBrand] ?? [] : []; + + return ( +
+ + 运维管理 + 车辆管理 + + + +
+ + + + + + + + + + + + + + + + + + + + (option?.label ?? '').toLowerCase().includes(input.toLowerCase()) + } + /> + + + + + loadList(1, pageSize)} + /> + + + + + + +
+ + 0} + indeterminate={selectedRowKeys.length > 0 && selectedRowKeys.length < data.length} + onChange={(checked) => setSelectedRowKeys(checked ? data.map((r) => r.id) : [])} + /> + ), + width: 48, + fixed: 'left', + render: (_, record) => ( + + setSelectedRowKeys((prev) => + checked ? [...prev, record.id] : prev.filter((k) => k !== record.id) + ) + } + /> + ), + }, + ...columns, + ]} + scroll={{ x: 3800 }} + pagination={{ + current: page, + pageSize, + total, + showTotal: true, + showJumper: true, + sizeCanChange: true, + pageSizeChange: (ps) => { + setPageSize(ps); + setPage(1); + loadList(1, ps); + }, + onChange: (p) => { + setPage(p); + loadList(p, pageSize); + }, + }} + /> + + + { setPlateModalVisible(false); setPlateModalVehicle(null); }} + onOk={onPlateModalOk} + /> + + ); +} diff --git a/src/services/vehicle.ts b/src/services/vehicle.ts new file mode 100644 index 0000000..9004ded --- /dev/null +++ b/src/services/vehicle.ts @@ -0,0 +1,145 @@ +import type { VehicleRecord, VehicleFilterForm } from '@/types/vehicle'; + +/** 模拟省-市二级地区 */ +export const regionOptions = [ + { + value: 'guangdong', + label: '广东省', + children: [ + { value: 'guangzhou', label: '广州市' }, + { value: 'shenzhen', label: '深圳市' }, + { value: 'dongguan', label: '东莞市' }, + ], + }, + { + value: 'zhejiang', + label: '浙江省', + children: [ + { value: 'hangzhou', label: '杭州市' }, + { value: 'ningbo', label: '宁波市' }, + ], + }, + { + value: 'jiangsu', + label: '江苏省', + children: [ + { value: 'nanjing', label: '南京市' }, + { value: 'suzhou', label: '苏州市' }, + ], + }, +]; + +/** 车辆类型(从车辆类型表拉取) */ +export const vehicleTypeOptions = [ + { value: 'light_truck', label: '轻卡' }, + { value: 'van', label: '厢式车' }, + { value: 'suv', label: 'SUV' }, + { value: 'sedan', label: '轿车' }, +]; + +/** 品牌(从型号参数表拉取) */ +export const brandOptions = [ + { value: 'brand_a', label: '品牌A' }, + { value: 'brand_b', label: '品牌B' }, + { value: 'brand_c', label: '品牌C' }, +]; + +/** 型号(从型号参数表拉取,可按品牌联动) */ +export const modelOptions: Record = { + brand_a: [ + { value: 'model_a1', label: '型号A1' }, + { value: 'model_a2', label: '型号A2' }, + ], + brand_b: [ + { value: 'model_b1', label: '型号B1' }, + { value: 'model_b2', label: '型号B2' }, + ], + brand_c: [ + { value: 'model_c1', label: '型号C1' }, + ], +}; + +/** 客户名称(租赁/自营合同客户,含「无」) */ +export const customerOptions = [ + { value: '__none__', label: '无' }, + { value: 'customer_1', label: '客户甲' }, + { value: 'customer_2', label: '客户乙' }, + { value: 'customer_3', label: '客户丙' }, +]; + +/** 归属业务部门(含「无」) */ +export const businessDeptOptions = [ + { value: '__none__', label: '无' }, + { value: 'dept_1', label: '业务一部' }, + { value: 'dept_2', label: '业务二部' }, + { value: 'dept_3', label: '业务三部' }, +]; + +/** 合同编号列表(支持模糊匹配,此处为示例) */ +export const contractNoOptions = [ + { value: 'HT-2024-001', label: 'HT-2024-001' }, + { value: 'HT-2024-002', label: 'HT-2024-002' }, + { value: 'HT-2024-003', label: 'HT-2024-003' }, + { value: 'ZY-2024-001', label: 'ZY-2024-001' }, +]; + +/** 登记所有权列表(支持模糊匹配) */ +export const registeredOwnerOptions = [ + { value: 'owner_1', label: '某某物流有限公司' }, + { value: 'owner_2', label: '某某租赁有限公司' }, +]; + +function randomItem(arr: T[]): T { + return arr[Math.floor(Math.random() * arr.length)]; +} + +/** 生成模拟列表数据 */ +export function mockVehicleList(params: VehicleFilterForm & { plateNo?: string; page?: number; pageSize?: number }): { list: VehicleRecord[]; total: number } { + const total = 48; + const list: VehicleRecord[] = []; + const cities = ['广东省广州市', '广东省深圳市', '浙江省杭州市', '江苏省南京市']; + for (let i = 1; i <= (params.pageSize ?? 20); i++) { + const idx = ((params.page ?? 1) - 1) * (params.pageSize ?? 20) + i; + if (idx > total) break; + list.push({ + id: `v-${idx}`, + operationCity: randomItem(cities), + vin: `L${String(idx).padStart(6, '0')}${Math.random().toString(36).slice(2, 11).toUpperCase()}`, + plateNo: `粤A${String(10000 + idx).slice(-5)}`, + vehicleNo: idx <= 10 ? `VN-${idx}` : '', + vehicleType: randomItem(vehicleTypeOptions).label, + brand: randomItem(brandOptions).label, + model: randomItem(modelOptions.brand_a).label, + bodyColor: ['白', '黑', '银', '蓝'][idx % 4], + parkingLot: idx % 3 === 0 ? '-' : `停车场${(idx % 3) + 1}`, + customerName: idx % 4 === 0 ? '-' : `客户${(idx % 4)}`, + businessDept: idx % 4 === 0 ? '-' : `业务${(idx % 4)}部`, + businessOwner: idx % 4 === 0 ? '-' : `负责人${idx % 5}`, + operationStatus: randomItem(['待运营', '库存', '租赁', '自营', '退出运营']), + libStatus: randomItem(['库存车-可交付车', '已交付车-租赁交车', '新车入库-待验车']), + outboundStatus: randomItem(['无', '租赁交车', '异动出库']), + preemptStatus: '-', + prepStatus: randomItem(['待整备', '整备中', '正常', '无']), + transferStatus: randomItem(['无', '过户中', '内部过户完成']), + repairStatus: randomItem(['待服务站接单', '维修中', '正常']), + licenseStatus: randomItem(['正常', '异常']), + scrapStatus: randomItem(['无', '报废中', '已报废']), + registeredOwner: '某某物流有限公司', + onlineStatus: randomItem(['在线', '离线']), + manufactureYear: `${2020 + (idx % 5)}`, + mileage: Number((10000 + idx * 500 + Math.random() * 200).toFixed(2)), + purchaseTime: `2022-0${(idx % 9) + 1}-15`, + licenseRegisterDate: `2022-0${(idx % 9) + 1}-01`, + licenseExpiry: `2025-0${(idx % 9) + 1}-01`, + lastDeliveryTime: idx % 2 === 0 ? `2024-01-${String(10 + (idx % 20)).padStart(2, '0')}` : '-', + lastDeliveryMileage: idx % 2 === 0 ? Number((12000 + idx * 100).toFixed(2)) : 0, + lastReturnTime: idx % 2 === 1 ? `2024-02-${String(5 + (idx % 20)).padStart(2, '0')}` : '-', + lastReturnMileage: idx % 2 === 1 ? Number((12500 + idx * 100).toFixed(2)) : 0, + forceScrapDate: `2030-12-31`, + contractNo: idx % 3 === 0 ? '-' : `HT-2024-00${(idx % 3) + 1}`, + currentLocation: `广东省广州市天河区某某路${idx}号`, + gpsLastTime: `2024-02-13 ${String(10 + (idx % 12)).padStart(2, '0')}:${String(idx % 60).padStart(2, '0')}`, + }); + } + return { list, total }; +} diff --git a/src/types/vehicle.ts b/src/types/vehicle.ts new file mode 100644 index 0000000..34b9cd7 --- /dev/null +++ b/src/types/vehicle.ts @@ -0,0 +1,129 @@ +/** 车辆管理相关类型 */ + +export type OperationCity = { province: string; city: string }; +export type VehicleTypeItem = { value: string; label: string }; +export type ModelParamItem = { brand: string; model: string }; +export type CustomerItem = { value: string; label: string }; +export type DeptItem = { value: string; label: string }; + +export const OPERATION_STATUS_OPTIONS = [ + { value: 'pending', label: '待运营' }, + { value: 'in_stock', label: '库存' }, + { value: 'lease', label: '租赁' }, + { value: 'self', label: '自营' }, + { value: 'exit', label: '退出运营' }, +] as const; + +export const LIB_STATUS_OPTIONS = [ + { value: 'new_pending', label: '新车入库-待验车' }, + { value: 'new_license', label: '新车入库-证照办理' }, + { value: 'stock_ok', label: '库存车-可交付车' }, + { value: 'stock_no', label: '库存车-不可交付车' }, + { value: 'stock_slow', label: '库存车-呆滞车' }, + { value: 'out_lease', label: '已交付车-租赁交车' }, + { value: 'out_self', label: '已交付车-自营交车' }, + { value: 'out_replace', label: '已交付车-替换交车' }, + { value: 'exit_scrap', label: '退出运营-报废车' }, + { value: 'exit_third', label: '退出运营-三方退租车' }, + { value: 'exit_sale', label: '退出运营-过户售车' }, +] as const; + +export const OUTBOUND_STATUS_OPTIONS = [ + { value: 'move', label: '异动出库' }, + { value: 'transfer', label: '调拨出库' }, + { value: 'show', label: '展示出库' }, + { value: 'lease_deliver', label: '租赁交车' }, + { value: 'self_deliver', label: '自营交车' }, + { value: 'replace_deliver', label: '替换交车' }, + { value: 'sale', label: '过户售车' }, + { value: 'lease_return', label: '外租退车' }, + { value: 'scrap', label: '报废出库' }, + { value: 'none', label: '无' }, +] as const; + +export const PREP_STATUS_OPTIONS = [ + { value: 'pending', label: '待整备' }, + { value: 'doing', label: '整备中' }, + { value: 'normal', label: '正常' }, + { value: 'none', label: '无' }, +] as const; + +export const TRANSFER_STATUS_OPTIONS = [ + { value: 'doing', label: '过户中' }, + { value: 'internal_done', label: '内部过户完成' }, + { value: 'sale_done', label: '销售过户完成' }, + { value: 'none', label: '无' }, +] as const; + +export const REPAIR_STATUS_OPTIONS = [ + { value: 'pending', label: '待服务站接单' }, + { value: 'doing', label: '维修中' }, + { value: 'normal', label: '正常' }, +] as const; + +export const LICENSE_STATUS_OPTIONS = [ + { value: 'normal', label: '正常' }, + { value: 'abnormal', label: '异常' }, +] as const; + +export const SCRAP_STATUS_OPTIONS = [ + { value: 'doing', label: '报废中' }, + { value: 'done', label: '已报废' }, + { value: 'none', label: '无' }, +] as const; + +export const ONLINE_STATUS_OPTIONS = [ + { value: 'online', label: '在线' }, + { value: 'offline', label: '离线' }, +] as const; + +export interface VehicleRecord { + id: string; + operationCity: string; + vin: string; + plateNo: string; + vehicleNo: string; + vehicleType: string; + brand: string; + model: string; + bodyColor: string; + parkingLot: string; + customerName: string; + businessDept: string; + businessOwner: string; + operationStatus: string; + libStatus: string; + outboundStatus: string; + preemptStatus: string; + prepStatus: string; + transferStatus: string; + repairStatus: string; + licenseStatus: string; + scrapStatus: string; + registeredOwner: string; + onlineStatus: string; + manufactureYear: string; + mileage: number; + purchaseTime: string; + licenseRegisterDate: string; + licenseExpiry: string; + lastDeliveryTime: string; + lastDeliveryMileage: number; + lastReturnTime: string; + lastReturnMileage: number; + forceScrapDate: string; + contractNo: string; + currentLocation: string; + gpsLastTime: string; +} + +export interface VehicleFilterForm { + operationCity?: string[]; + vehicleType?: string; + brand?: string; + model?: string; + customerName?: string; + businessDept?: string; + contractNo?: string; + registeredOwner?: string; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..4ffafe1 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "baseUrl": ".", + "paths": { "@/*": ["src/*"] } + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..97ede7e --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true + }, + "include": ["vite.config.ts"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..b70c402 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import path from 'path'; + +export default defineConfig({ + plugins: [react()], + resolve: { + alias: { '@': path.resolve(__dirname, 'src') }, + }, +}); diff --git a/web端.zip b/web端.zip new file mode 100644 index 0000000..2ee407a Binary files /dev/null and b/web端.zip differ diff --git a/web端/.DS_Store b/web端/.DS_Store new file mode 100644 index 0000000..20ff8d6 Binary files /dev/null and b/web端/.DS_Store differ diff --git a/web端/业务管理/交车任务.jsx b/web端/业务管理/交车任务.jsx new file mode 100644 index 0000000..a9517da --- /dev/null +++ b/web端/业务管理/交车任务.jsx @@ -0,0 +1,577 @@ +// 【重要】必须使用 const Component 作为组件变量名 +// 数字化资产ONEOS运管平台 - 业务管理 - 交车任务 + +const Component = function() { + var useState = React.useState; + var useMemo = React.useMemo; + var antd = window.antd; + var Breadcrumb = antd.Breadcrumb; + var Select = antd.Select; + var Button = antd.Button; + var Table = antd.Table; + var Card = antd.Card; + var Tabs = antd.Tabs; + var DatePicker = antd.DatePicker; + var Popover = antd.Popover; + var Modal = antd.Modal; + var message = antd.message; + var App = antd.App; + var RangePicker = DatePicker.RangePicker; + + // 筛选条件(表单输入) + var _contractCode = useState(undefined); + var _projectName = useState(undefined); + var _customerName = useState(undefined); + var _planDateRange = useState(null); + var _creator = useState(undefined); + // 已应用的筛选条件(点击查询后生效) + var _applied = useState({ + contractCode: undefined, + projectName: undefined, + customerName: undefined, + planDateRange: null, + creator: undefined + }); + + // Tab:进行中 / 已完成 + var _activeTab = useState('ongoing'); + + // 筛选展开(默认显示前 3 项,与车辆租赁合同一致) + var _filterExpanded = useState(false); + + // 需求说明弹窗 + var _reqSpecOpen = useState(false); + + // 停用/启用:重新选择预计交车日期弹窗 + var _rescheduleModalVisible = useState(false); + var _rescheduleTask = useState(null); + var _rescheduleDateRange = useState(null); + + // 模拟选项(可模糊搜索) + var contractOptions = [ + { value: 'JXZL20260216YW101235A', label: 'JXZL20260216YW101235A' }, + { value: 'JXZL20260216YW101236A', label: 'JXZL20260216YW101236A' }, + { value: 'JXZL20260215YW101234A', label: 'JXZL20260215YW101234A' }, + { value: 'SHZL20260210YW101200A', label: 'SHZL20260210YW101200A' } + ]; + var projectOptions = [ + { value: 'p1', label: '嘉兴氢能运输项目' }, + { value: 'p2', label: '上海物流租赁项目' }, + { value: 'p3', label: '杭州城配租赁项目' } + ]; + var customerOptions = [ + { value: 'c1', label: '嘉兴某某物流有限公司' }, + { value: 'c2', label: '上海某某运输公司' }, + { value: 'c3', label: '杭州某某租赁有限公司' } + ]; + var creatorOptions = [ + { value: 'u1', label: '张经理' }, + { value: 'u2', label: '李专员' }, + { value: 'u3', label: '王专员' } + ]; + + // 进行中列表数据(未完成交车单提交),用 state 以便停用/启用后刷新 + var _ongoingList = useState([ + { id: 'o1', contractCode: 'JXZL20260216YW101235A', projectName: '嘉兴氢能运输项目', customerName: '嘉兴某某物流有限公司', deliveryCount: 2, deliveryRegion: '浙江省-嘉兴市', deliveryLocation: '嘉兴市南湖区科技大道1号', planDeliveryDisplay: '2026-03-01至2026-03-05', planDeliveryEnd: '2026-03-05', billingStartDate: '2026-03-01', creator: '张经理', createdAt: '2026-02-20', lastUpdater: '张经理', lastUpdatedAt: '2026-02-24', enabled: true }, + { id: 'o2', contractCode: 'JXZL20260216YW101236A', projectName: '上海物流租赁项目', customerName: '上海某某运输公司', deliveryCount: 1, deliveryRegion: '上海市-上海市', deliveryLocation: '上海市浦东新区张江高科技园区', planDeliveryDisplay: '2026-03-10', planDeliveryEnd: '2026-03-10', billingStartDate: '-', creator: '李专员', createdAt: '2026-02-21', lastUpdater: '李专员', lastUpdatedAt: '2026-02-22', enabled: false }, + { id: 'o3', contractCode: 'JXZL20260215YW101234A', projectName: '杭州城配租赁项目', customerName: '杭州某某租赁有限公司', deliveryCount: 3, deliveryRegion: '浙江省-杭州市', deliveryLocation: '杭州市余杭区未来科技城', planDeliveryDisplay: '2026-02-28至2026-03-02', planDeliveryEnd: '2026-03-02', billingStartDate: '2026-02-28', creator: '王专员', createdAt: '2026-02-18', lastUpdater: '王专员', lastUpdatedAt: '2026-02-25', enabled: true }, + { id: 'o4', contractCode: 'JXZL20260101YW101200A', projectName: '嘉兴氢能运输项目', customerName: '嘉兴某某物流有限公司', deliveryCount: 1, deliveryRegion: '浙江省-嘉兴市', deliveryLocation: '嘉兴市秀洲区洪兴西路288号', planDeliveryDisplay: '2026-01-05至2026-01-10', planDeliveryEnd: '2026-01-10', billingStartDate: '-', creator: '张经理', createdAt: '2026-01-02', lastUpdater: '张经理', lastUpdatedAt: '2026-01-15', enabled: false }, + { id: 'o5', contractCode: 'JXZL20260218YW101237A', projectName: '嘉兴氢能运输项目', customerName: '嘉兴某某物流有限公司', deliveryCount: 2, deliveryRegion: '浙江省-嘉兴市', deliveryLocation: '嘉兴市南湖区广益路与亚中路交叉口', planDeliveryDisplay: '2026-03-08至2026-03-12', planDeliveryEnd: '2026-03-12', billingStartDate: '-', creator: '张经理', createdAt: '2026-02-22', lastUpdater: '张经理', lastUpdatedAt: '2026-02-26', enabled: true }, + { id: 'o6', contractCode: 'SHZL20260210YW101201A', projectName: '上海物流租赁项目', customerName: '上海某某运输公司', deliveryCount: 1, deliveryRegion: '上海市-上海市', deliveryLocation: '上海市闵行区申滨路1051号', planDeliveryDisplay: '2026-03-15', planDeliveryEnd: '2026-03-15', billingStartDate: '-', creator: '李专员', createdAt: '2026-02-19', lastUpdater: '李专员', lastUpdatedAt: '2026-02-23', enabled: true }, + { id: 'o7', contractCode: 'JXZL20260220YW101238A', projectName: '杭州城配租赁项目', customerName: '杭州某某租赁有限公司', deliveryCount: 4, deliveryRegion: '浙江省-杭州市', deliveryLocation: '杭州市萧山区市心北路与建设一路交叉口', planDeliveryDisplay: '2026-03-20至2026-03-25', planDeliveryEnd: '2026-03-25', billingStartDate: '-', creator: '王专员', createdAt: '2026-02-23', lastUpdater: '王专员', lastUpdatedAt: '2026-02-27', enabled: true }, + { id: 'o8', contractCode: 'JXZL20260214YW101233A', projectName: '嘉兴氢能运输项目', customerName: '嘉兴某某物流有限公司', deliveryCount: 1, deliveryRegion: '浙江省-嘉兴市', deliveryLocation: '嘉兴市经开区昌盛路与文昌路交叉口', planDeliveryDisplay: '2026-03-05', planDeliveryEnd: '2026-03-05', billingStartDate: '-', creator: '张经理', createdAt: '2026-02-17', lastUpdater: '李专员', lastUpdatedAt: '2026-02-24', enabled: true }, + { id: 'o9', contractCode: 'SHZL20260212YW101202A', projectName: '上海物流租赁项目', customerName: '上海某某运输公司', deliveryCount: 2, deliveryRegion: '上海市-上海市', deliveryLocation: '上海市嘉定区安亭镇墨玉南路与博园路交叉口', planDeliveryDisplay: '2026-03-18至2026-03-22', planDeliveryEnd: '2026-03-22', billingStartDate: '-', creator: '李专员', createdAt: '2026-02-24', lastUpdater: '李专员', lastUpdatedAt: '2026-02-28', enabled: true }, + { id: 'o10', contractCode: 'JXZL20260222YW101239A', projectName: '杭州城配租赁项目', customerName: '杭州某某租赁有限公司', deliveryCount: 3, deliveryRegion: '浙江省-杭州市', deliveryLocation: '杭州市西湖区文三路与古翠路交叉口', planDeliveryDisplay: '2026-03-25至2026-03-30', planDeliveryEnd: '2026-03-30', billingStartDate: '-', creator: '王专员', createdAt: '2026-02-25', lastUpdater: '王专员', lastUpdatedAt: '2026-02-28', enabled: true } + ]); + var ongoingList = _ongoingList[0]; + var setOngoingList = _ongoingList[1]; + + // 已完成列表数据(已提交交车单),交车数量可点开气泡 + var completedList = [ + { id: 'c1', contractCode: 'JXZL20260215YW101234A', projectName: '杭州城配租赁项目', customerName: '杭州某某租赁有限公司', deliveryCount: 2, deliveryRegion: '浙江省-杭州市', deliveryLocation: '杭州市余杭区未来科技城', vehicles: [{ vehicleType: '4.5吨冷链车-轻型厢式货车', brand: '品牌A', model: '型号A1', plateNo: '浙A10001', actualDeliveryDate: '2026-02-28', deliverer: '运维李' }, { vehicleType: '18吨厢式货车-重型厢式货车', brand: '品牌B', model: '型号B1', plateNo: '浙B20002', actualDeliveryDate: '2026-03-01', deliverer: '运维王' }], planDeliveryDisplay: '2026-02-28至2026-03-01', billingStartDate: '2026-02-28', creator: '王专员', createdAt: '2026-02-18', lastUpdater: '王专员', lastUpdatedAt: '2026-03-01' }, + { id: 'c2', contractCode: 'SHZL20260210YW101200A', projectName: '上海物流租赁项目', customerName: '上海某某运输公司', deliveryCount: 1, deliveryRegion: '上海市-上海市', deliveryLocation: '上海市浦东新区张江高科技园区', vehicles: [{ vehicleType: '18吨厢式货车-重型厢式货车', brand: '品牌B', model: '型号B1', plateNo: '沪A30003', actualDeliveryDate: '2026-02-25', deliverer: '运维赵' }], planDeliveryDisplay: '2026-02-25', billingStartDate: '2026-02-25', creator: '李专员', createdAt: '2026-02-15', lastUpdater: '李专员', lastUpdatedAt: '2026-02-25' }, + { id: 'c3', contractCode: 'JXZL20260208YW101231A', projectName: '嘉兴氢能运输项目', customerName: '嘉兴某某物流有限公司', deliveryCount: 2, deliveryRegion: '浙江省-嘉兴市', deliveryLocation: '嘉兴市南湖区科技大道1号', vehicles: [{ vehicleType: '4.5吨冷链车-轻型厢式货车', brand: '品牌A', model: '型号A1', plateNo: '浙A10002', actualDeliveryDate: '2026-02-20', deliverer: '运维李' }, { vehicleType: '18吨厢式货车-重型厢式货车', brand: '品牌B', model: '型号B1', plateNo: '浙A10003', actualDeliveryDate: '2026-02-21', deliverer: '运维王' }], planDeliveryDisplay: '2026-02-20至2026-02-21', billingStartDate: '2026-02-20', creator: '张经理', createdAt: '2026-02-10', lastUpdater: '张经理', lastUpdatedAt: '2026-02-21' }, + { id: 'c4', contractCode: 'SHZL20260205YW101198A', projectName: '上海物流租赁项目', customerName: '上海某某运输公司', deliveryCount: 1, deliveryRegion: '上海市-上海市', deliveryLocation: '上海市闵行区申滨路1051号', vehicles: [{ vehicleType: '18吨厢式货车-重型厢式货车', brand: '品牌B', model: '型号B2', plateNo: '沪B40004', actualDeliveryDate: '2026-02-18', deliverer: '运维赵' }], planDeliveryDisplay: '2026-02-18', billingStartDate: '2026-02-18', creator: '李专员', createdAt: '2026-02-08', lastUpdater: '李专员', lastUpdatedAt: '2026-02-18' }, + { id: 'c5', contractCode: 'JXZL20260212YW101232A', projectName: '杭州城配租赁项目', customerName: '杭州某某租赁有限公司', deliveryCount: 3, deliveryRegion: '浙江省-杭州市', deliveryLocation: '杭州市萧山区市心北路与建设一路交叉口', vehicles: [{ vehicleType: '4.5吨冷链车-轻型厢式货车', brand: '品牌A', model: '型号A2', plateNo: '浙C50001', actualDeliveryDate: '2026-02-22', deliverer: '运维李' }, { vehicleType: '18吨厢式货车-重型厢式货车', brand: '品牌B', model: '型号B1', plateNo: '浙C50002', actualDeliveryDate: '2026-02-23', deliverer: '运维王' }, { vehicleType: '4.5吨货车-轻型厢式货车', brand: '品牌C', model: '型号C1', plateNo: '浙C50003', actualDeliveryDate: '2026-02-24', deliverer: '运维赵' }], planDeliveryDisplay: '2026-02-22至2026-02-24', billingStartDate: '2026-02-22', creator: '王专员', createdAt: '2026-02-14', lastUpdater: '王专员', lastUpdatedAt: '2026-02-24' }, + { id: 'c6', contractCode: 'JXZL20260201YW101228A', projectName: '嘉兴氢能运输项目', customerName: '嘉兴某某物流有限公司', deliveryCount: 1, deliveryRegion: '浙江省-嘉兴市', deliveryLocation: '嘉兴市秀洲区洪兴西路288号', vehicles: [{ vehicleType: '4.5吨冷链车-轻型厢式货车', brand: '品牌A', model: '型号A1', plateNo: '浙A10004', actualDeliveryDate: '2026-02-15', deliverer: '运维李' }], planDeliveryDisplay: '2026-02-15', billingStartDate: '2026-02-15', creator: '张经理', createdAt: '2026-02-05', lastUpdater: '张经理', lastUpdatedAt: '2026-02-15' }, + { id: 'c7', contractCode: 'SHZL20260203YW101199A', projectName: '上海物流租赁项目', customerName: '上海某某运输公司', deliveryCount: 2, deliveryRegion: '上海市-上海市', deliveryLocation: '上海市嘉定区安亭镇墨玉南路与博园路交叉口', vehicles: [{ vehicleType: '18吨厢式货车-重型厢式货车', brand: '品牌B', model: '型号B1', plateNo: '沪A30005', actualDeliveryDate: '2026-02-16', deliverer: '运维赵' }, { vehicleType: '4.5吨冷链车-轻型厢式货车', brand: '品牌A', model: '型号A1', plateNo: '沪A30006', actualDeliveryDate: '2026-02-17', deliverer: '运维李' }], planDeliveryDisplay: '2026-02-16至2026-02-17', billingStartDate: '2026-02-16', creator: '李专员', createdAt: '2026-02-06', lastUpdater: '李专员', lastUpdatedAt: '2026-02-17' }, + { id: 'c8', contractCode: 'JXZL20260128YW101227A', projectName: '杭州城配租赁项目', customerName: '杭州某某租赁有限公司', deliveryCount: 2, deliveryRegion: '浙江省-杭州市', deliveryLocation: '杭州市西湖区文三路与古翠路交叉口', vehicles: [{ vehicleType: '18吨厢式货车-重型厢式货车', brand: '品牌B', model: '型号B2', plateNo: '浙B20004', actualDeliveryDate: '2026-02-10', deliverer: '运维王' }, { vehicleType: '4.5吨货车-轻型厢式货车', brand: '品牌C', model: '型号C1', plateNo: '浙B20005', actualDeliveryDate: '2026-02-11', deliverer: '运维赵' }], planDeliveryDisplay: '2026-02-10至2026-02-11', billingStartDate: '2026-02-10', creator: '王专员', createdAt: '2026-01-30', lastUpdater: '王专员', lastUpdatedAt: '2026-02-11' }, + { id: 'c9', contractCode: 'JXZL20260206YW101230A', projectName: '嘉兴氢能运输项目', customerName: '嘉兴某某物流有限公司', deliveryCount: 1, deliveryRegion: '浙江省-嘉兴市', deliveryLocation: '嘉兴市经开区昌盛路与文昌路交叉口', vehicles: [{ vehicleType: '4.5吨冷链车-轻型厢式货车', brand: '品牌A', model: '型号A2', plateNo: '浙A10005', actualDeliveryDate: '2026-02-19', deliverer: '运维李' }], planDeliveryDisplay: '2026-02-19', billingStartDate: '2026-02-19', creator: '张经理', createdAt: '2026-02-09', lastUpdater: '张经理', lastUpdatedAt: '2026-02-19' }, + { id: 'c10', contractCode: 'SHZL20260215YW101203A', projectName: '上海物流租赁项目', customerName: '上海某某运输公司', deliveryCount: 2, deliveryRegion: '上海市-上海市', deliveryLocation: '上海市浦东新区金桥镇金海路1000号', vehicles: [{ vehicleType: '18吨厢式货车-重型厢式货车', brand: '品牌B', model: '型号B1', plateNo: '沪C60001', actualDeliveryDate: '2026-02-26', deliverer: '运维赵' }, { vehicleType: '4.5吨冷链车-轻型厢式货车', brand: '品牌A', model: '型号A1', plateNo: '沪C60002', actualDeliveryDate: '2026-02-27', deliverer: '运维李' }], planDeliveryDisplay: '2026-02-26至2026-02-27', billingStartDate: '2026-02-26', creator: '李专员', createdAt: '2026-02-18', lastUpdater: '李专员', lastUpdatedAt: '2026-02-27' } + ]; + + var applied = _applied[0]; + var setApplied = _applied[1]; + + var getProjectLabel = function(v) { var o = projectOptions.find(function(x) { return x.value === v; }); return o ? o.label : v; }; + var getCustomerLabel = function(v) { var o = customerOptions.find(function(x) { return x.value === v; }); return o ? o.label : v; }; + var getCreatorLabel = function(v) { var o = creatorOptions.find(function(x) { return x.value === v; }); return o ? o.label : v; }; + + var filterOngoing = function(list) { + var a = applied; + if (a.contractCode) list = list.filter(function(r) { return r.contractCode === a.contractCode; }); + if (a.projectName) { var pl = getProjectLabel(a.projectName); if (pl) list = list.filter(function(r) { return r.projectName === pl; }); } + if (a.customerName) { var cl = getCustomerLabel(a.customerName); if (cl) list = list.filter(function(r) { return r.customerName === cl; }); } + if (a.creator) { var ul = getCreatorLabel(a.creator); if (ul) list = list.filter(function(r) { return r.creator === ul; }); } + if (a.planDateRange && a.planDateRange.length === 2 && window.dayjs) { + var start = window.dayjs(a.planDateRange[0]).startOf('day'); + var end = window.dayjs(a.planDateRange[1]).endOf('day'); + list = list.filter(function(r) { + var d = r.planDeliveryEnd ? window.dayjs(r.planDeliveryEnd) : null; + return d && !d.isBefore(start) && !d.isAfter(end); + }); + } + return list; + }; + + var filteredOngoing = useMemo(function() { + return filterOngoing(ongoingList.slice()); + }, [ongoingList, applied.contractCode, applied.projectName, applied.customerName, applied.creator, applied.planDateRange]); + + var filteredCompleted = useMemo(function() { + var list = completedList.slice(); + if (applied.contractCode) list = list.filter(function(r) { return r.contractCode === applied.contractCode; }); + if (applied.projectName) { var pl = getProjectLabel(applied.projectName); if (pl) list = list.filter(function(r) { return r.projectName === pl; }); } + if (applied.customerName) { var cl = getCustomerLabel(applied.customerName); if (cl) list = list.filter(function(r) { return r.customerName === cl; }); } + if (applied.creator) { var ul = getCreatorLabel(applied.creator); if (ul) list = list.filter(function(r) { return r.creator === ul; }); } + if (applied.planDateRange && applied.planDateRange.length === 2 && window.dayjs) { + var start = window.dayjs(applied.planDateRange[0]).startOf('day'); + var end = window.dayjs(applied.planDateRange[1]).endOf('day'); + list = list.filter(function(r) { + var planEnd = r.planDeliveryDisplay && r.planDeliveryDisplay.indexOf('至') >= 0 + ? r.planDeliveryDisplay.split('至')[1] + : r.planDeliveryDisplay; + var d = planEnd ? window.dayjs(planEnd) : null; + return d && !d.isBefore(start) && !d.isAfter(end); + }); + } + return list; + }, [applied.contractCode, applied.projectName, applied.customerName, applied.creator, applied.planDateRange]); + + var handleQuery = function() { + setApplied({ + contractCode: _contractCode[0], + projectName: _projectName[0], + customerName: _customerName[0], + planDateRange: _planDateRange[0] && _planDateRange[0].length === 2 ? _planDateRange[0] : null, + creator: _creator[0] + }); + message.info('已按筛选条件查询'); + }; + + var handleReset = function() { + _contractCode[1](undefined); + _projectName[1](undefined); + _customerName[1](undefined); + _planDateRange[1](null); + _creator[1](undefined); + setApplied({ + contractCode: undefined, + projectName: undefined, + customerName: undefined, + planDateRange: null, + creator: undefined + }); + }; + + var handleDisableEnable = function(record) { + if (record.enabled) { + Modal.confirm({ + title: '是否确认停用该交车任务?', + okText: '确定', + cancelText: '取消', + onOk: function() { + setOngoingList(function(prev) { + return prev.map(function(r) { return r.id === record.id ? Object.assign({}, r, { enabled: false }) : r; }); + }); + message.success('已停用'); + } + }); + return; + } + // 启用:判断预计交车结束日期是否已过期 + var dayjs = window.dayjs; + var endDate = record.planDeliveryEnd ? (dayjs ? dayjs(record.planDeliveryEnd) : null) : null; + var now = dayjs ? dayjs() : null; + if (endDate && now && endDate.isBefore(now, 'day')) { + _rescheduleTask[1](record); + _rescheduleDateRange[1](null); + _rescheduleModalVisible[1](true); + return; + } + Modal.confirm({ + title: '是否确认启用该交车任务?', + okText: '确定', + cancelText: '取消', + onOk: function() { + setOngoingList(function(prev) { + return prev.map(function(r) { return r.id === record.id ? Object.assign({}, r, { enabled: true }) : r; }); + }); + message.success('已启用'); + } + }); + }; + + var handleRescheduleConfirm = function() { + var task = _rescheduleTask[0]; + var range = _rescheduleDateRange[0]; + if (!task || !range || range.length !== 2) { + message.warning('请选择预计交车日期(单日请选择同一天为开始和结束)'); + return; + } + var dayjs = window.dayjs; + var startStr = dayjs ? dayjs(range[0]).format('YYYY-MM-DD') : String(range[0]); + var endStr = dayjs ? dayjs(range[1]).format('YYYY-MM-DD') : String(range[1]); + var planDisplay = startStr === endStr ? startStr : startStr + '至' + endStr; + setOngoingList(function(prev) { + return prev.map(function(r) { + if (r.id !== task.id) return r; + return Object.assign({}, r, { planDeliveryDisplay: planDisplay, planDeliveryEnd: endStr, enabled: true }); + }); + }); + message.success('已重新选择预计交车日期并启用'); + _rescheduleModalVisible[1](false); + _rescheduleTask[1](null); + _rescheduleDateRange[1](null); + }; + + // 进行中表格列 + var ongoingColumns = [ + { title: '合同编码', dataIndex: 'contractCode', key: 'contractCode', width: 180, ellipsis: true, fixed: 'left' }, + { title: '项目名称', dataIndex: 'projectName', key: 'projectName', width: 140, ellipsis: true, fixed: 'left' }, + { title: '客户名称', dataIndex: 'customerName', key: 'customerName', width: 160, ellipsis: true, fixed: 'left' }, + { title: '交车数量', dataIndex: 'deliveryCount', key: 'deliveryCount', width: 100 }, + { title: '交车区域', dataIndex: 'deliveryRegion', key: 'deliveryRegion', width: 140 }, + { title: '交车地点', dataIndex: 'deliveryLocation', key: 'deliveryLocation', width: 200, ellipsis: true }, + { title: '预计交车日期', dataIndex: 'planDeliveryDisplay', key: 'planDeliveryDisplay', width: 180 }, + { title: '开始计费日期', dataIndex: 'billingStartDate', key: 'billingStartDate', width: 120 }, + { title: '创建人', dataIndex: 'creator', key: 'creator', width: 100 }, + { title: '创建时间', dataIndex: 'createdAt', key: 'createdAt', width: 120 }, + { title: '最后更新人', dataIndex: 'lastUpdater', key: 'lastUpdater', width: 100 }, + { title: '最后更新时间', dataIndex: 'lastUpdatedAt', key: 'lastUpdatedAt', width: 120 }, + { + title: '操作', + key: 'action', + width: 180, + fixed: 'right', + render: function(_, record) { + return React.createElement('span', null, + React.createElement(Button, { type: 'link', size: 'small', onClick: function() { message.info('请参照原型业务管理-交车任务页面'); } }, '查看'), + React.createElement(Button, { type: 'link', size: 'small', onClick: function() { message.info('请参照原型业务管理-交车任务-编辑交车任务页面'); } }, '编辑'), + React.createElement(Button, { + type: 'link', + size: 'small', + onClick: function() { handleDisableEnable(record); } + }, record.enabled ? '停用' : '启用') + ); + } + } + ]; + + // 已完成表格列(交车数量重点色+气泡) + var vehiclePopoverContent = function(vehicles) { + if (!vehicles || !vehicles.length) return React.createElement('span', null, '-'); + var headers = ['车辆类型', '品牌', '型号', '车牌号', '实际交车日期', '交车人']; + return React.createElement('div', { style: { padding: 4, minWidth: 420 } }, + React.createElement('table', { style: { width: '100%', borderCollapse: 'collapse', fontSize: 12 } }, + React.createElement('thead', null, + React.createElement('tr', null, + headers.map(function(h) { return React.createElement('th', { key: h, style: { padding: '6px 8px', textAlign: 'left', borderBottom: '1px solid #eee', fontWeight: 600 } }, h); }) + ) + ), + React.createElement('tbody', null, + vehicles.map(function(v, i) { + return React.createElement('tr', { key: i }, + React.createElement('td', { style: { padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, v.vehicleType || '-'), + React.createElement('td', { style: { padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, v.brand || '-'), + React.createElement('td', { style: { padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, v.model || '-'), + React.createElement('td', { style: { padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, v.plateNo || '-'), + React.createElement('td', { style: { padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, v.actualDeliveryDate || '-'), + React.createElement('td', { style: { padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, v.deliverer || '-') + ); + }) + ) + ) + ); + }; + + var completedColumns = [ + { title: '合同编码', dataIndex: 'contractCode', key: 'contractCode', width: 180, ellipsis: true, fixed: 'left' }, + { title: '项目名称', dataIndex: 'projectName', key: 'projectName', width: 140, ellipsis: true, fixed: 'left' }, + { title: '客户名称', dataIndex: 'customerName', key: 'customerName', width: 160, ellipsis: true, fixed: 'left' }, + { + title: '交车数量', + dataIndex: 'deliveryCount', + key: 'deliveryCount', + width: 100, + render: function(count, record) { + var content = vehiclePopoverContent(record.vehicles); + return React.createElement(Popover, { content: content, title: '交车车辆明细', trigger: 'click' }, + React.createElement('span', { style: { color: '#1890ff', cursor: 'pointer', fontWeight: 500 } }, count) + ); + } + }, + { title: '交车区域', dataIndex: 'deliveryRegion', key: 'deliveryRegion', width: 140 }, + { title: '交车地点', dataIndex: 'deliveryLocation', key: 'deliveryLocation', width: 200, ellipsis: true }, + { title: '预计交车日期', dataIndex: 'planDeliveryDisplay', key: 'planDeliveryDisplay', width: 180 }, + { title: '开始计费日期', dataIndex: 'billingStartDate', key: 'billingStartDate', width: 120 }, + { title: '创建人', dataIndex: 'creator', key: 'creator', width: 100 }, + { title: '创建时间', dataIndex: 'createdAt', key: 'createdAt', width: 120 }, + { title: '最后更新人', dataIndex: 'lastUpdater', key: 'lastUpdater', width: 100 }, + { title: '最后更新时间', dataIndex: 'lastUpdatedAt', key: 'lastUpdatedAt', width: 120 }, + { + title: '操作', + key: 'action', + width: 80, + fixed: 'right', + render: function(_, record) { + return React.createElement(Button, { type: 'link', size: 'small', onClick: function() { message.info('请参照原型业务管理-交车任务页面'); } }, '查看'); + } + } + ]; + + var filterOption = function(input, option) { + var label = (option && option.label) ? String(option.label) : ''; + return label.toLowerCase().indexOf((input || '').toLowerCase()) >= 0; + }; + + var filterLabelStyle = { marginBottom: 6, fontSize: 14, color: 'rgba(0,0,0,0.65)' }; + var filterItemStyle = { marginBottom: 12 }; + var filterControlStyle = { width: '100%' }; + + var filterItems = [ + React.createElement('div', { key: 'contractCode', style: filterItemStyle }, + React.createElement('div', { style: filterLabelStyle }, '合同编码'), + React.createElement(Select, { + placeholder: '请输入或选择合同编码', + style: filterControlStyle, + value: _contractCode[0], + onChange: function(v) { _contractCode[1](v); }, + options: contractOptions, + showSearch: true, + allowClear: true, + filterOption: filterOption + })), + React.createElement('div', { key: 'projectName', style: filterItemStyle }, + React.createElement('div', { style: filterLabelStyle }, '项目名称'), + React.createElement(Select, { + placeholder: '请输入或选择项目名称', + style: filterControlStyle, + value: _projectName[0], + onChange: function(v) { _projectName[1](v); }, + options: projectOptions, + showSearch: true, + allowClear: true, + filterOption: filterOption + })), + React.createElement('div', { key: 'customerName', style: filterItemStyle }, + React.createElement('div', { style: filterLabelStyle }, '客户名称'), + React.createElement(Select, { + placeholder: '请输入或选择客户名称', + style: filterControlStyle, + value: _customerName[0], + onChange: function(v) { _customerName[1](v); }, + options: customerOptions, + showSearch: true, + allowClear: true, + filterOption: filterOption + })), + React.createElement('div', { key: 'planDate', style: filterItemStyle }, + React.createElement('div', { style: filterLabelStyle }, '预计交车日期'), + React.createElement(RangePicker, { + style: filterControlStyle, + placeholder: ['请选择预计交车开始时间', '请选择预计交车结束时间'], + value: _planDateRange[0] && _planDateRange[0].length === 2 && window.dayjs ? [_planDateRange[0][0], _planDateRange[0][1]] : null, + onChange: function(dates) { _planDateRange[1](dates && dates.length === 2 ? dates : null); } + })), + React.createElement('div', { key: 'creator', style: filterItemStyle }, + React.createElement('div', { style: filterLabelStyle }, '创建人'), + React.createElement(Select, { + placeholder: '请输入或选择创建人', + style: filterControlStyle, + value: _creator[0], + onChange: function(v) { _creator[1](v); }, + options: creatorOptions, + showSearch: true, + allowClear: true, + filterOption: filterOption + })) + ]; + + var filterCount = _filterExpanded[0] ? 5 : 3; + var filterNodes = []; + for (var i = 0; i < filterCount && i < filterItems.length; i++) { + filterNodes.push(filterItems[i]); + } + + // 表格单元格一行显示、根据内容适应宽度 + var cellNoWrap = function() { return { style: { whiteSpace: 'nowrap' } }; }; + var ongoingColumnsWithNowrap = ongoingColumns.map(function(col) { return Object.assign({}, col, { onCell: cellNoWrap }); }); + var completedColumnsWithNowrap = completedColumns.map(function(col) { return Object.assign({}, col, { onCell: cellNoWrap }); }); + + var layoutStyle = { padding: '16px 24px 48px', backgroundColor: '#f5f5f5', minHeight: '100vh' }; + + var reqTitleStyle = { fontSize: 18, fontWeight: 600, marginBottom: 16, color: 'rgba(0,0,0,0.85)' }; + var reqSectionStyle = { fontSize: 15, fontWeight: 600, marginTop: 16, marginBottom: 8, color: 'rgba(0,0,0,0.85)' }; + var reqSubStyle = { fontSize: 14, fontWeight: 500, marginLeft: 16, marginTop: 8, marginBottom: 4, color: 'rgba(0,0,0,0.85)' }; + var reqItemStyle = { fontSize: 13, marginLeft: 32, marginTop: 4, marginBottom: 2, lineHeight: 1.6, color: 'rgba(0,0,0,0.75)' }; + var reqSubItemStyle = { fontSize: 13, marginLeft: 48, marginTop: 2, marginBottom: 2, lineHeight: 1.6, color: 'rgba(0,0,0,0.7)' }; + var reqBlockStyle = { marginBottom: 8 }; + + var reqSpecContent = React.createElement('div', { style: { padding: '0 4px' } }, + React.createElement('div', { style: reqTitleStyle }, '交车任务'), + React.createElement('div', { style: reqBlockStyle }, + React.createElement('div', { style: reqSectionStyle }, '1.面包屑:'), + React.createElement('div', { style: reqSubStyle }, '1.1.业务管理-交车任务') + ), + React.createElement('div', { style: reqBlockStyle }, + React.createElement('div', { style: reqSectionStyle }, '2.筛选:'), + React.createElement('div', { style: reqItemStyle }, '支持通过合同编码、项目名称、客户名称、预计交车日期、创建人进行筛选,右侧为查询、重置按钮;'), + React.createElement('div', { style: reqItemStyle }, '2.1.合同编码:选择器,默认为所有合同;提示信息为:请输入或选择合同编码,支持从输入框输入内容进行模糊搜索,下拉显示结果;'), + React.createElement('div', { style: reqItemStyle }, '2.2.项目名称:选择器,默认为所有项目;提示信息为:请输入或选择项目名称,支持从输入框输入内容进行模糊搜索,下拉显示结果;'), + React.createElement('div', { style: reqItemStyle }, '2.3.客户名称:选择器,默认为所有客户;提示信息为:请输入或选择客户名称,支持从输入框输入内容进行模糊搜索,下拉显示结果;'), + React.createElement('div', { style: reqItemStyle }, '2.4.预计交车日期:日期选择器,默认提示信息为:请选择预计交车开始时间 请选择预计交车结束时间,单输入框,双日历,支持时间段选择,精确至天,格式为:YYYY-MM-DD - YYYY-MM-DD;'), + React.createElement('div', { style: reqItemStyle }, '2.5.创建人:选择器,默认为所有创建人;提示信息为:请输入或选择创建人,支持从输入框输入内容进行模糊搜索,下拉显示结果;'), + React.createElement('div', { style: reqItemStyle }, '2.6.查询:点击查询,根据单个或多个筛选条件(且)联动表格进行查询;'), + React.createElement('div', { style: reqItemStyle }, '2.7.重置:点击清空查询条件至默认') + ), + React.createElement('div', { style: reqBlockStyle }, + React.createElement('div', { style: reqSectionStyle }, '3.列表:分为2个tab:进行中、已完成,右侧为新增;'), + React.createElement('div', { style: reqSubStyle }, '3.1进行中:显示所有已新增成功,但未完成交车单提交的任务;列表展示以下内容:合同编码、项目名称、客户名称、交车数量、预计交车日期、开始计费日期、创建人、创建时间、最后更新人、最后更新时间、操作;'), + React.createElement('div', { style: reqItemStyle }, '3.1.1合同编码:显示对应合同编码;'), + React.createElement('div', { style: reqItemStyle }, '3.1.2.项目名称:显示对应项目名称;'), + React.createElement('div', { style: reqItemStyle }, '3.1.3.客户名称:显示对应客户名称;'), + React.createElement('div', { style: reqItemStyle }, '3.1.4.交车数量:显示交车数;'), + React.createElement('div', { style: reqItemStyle }, '3.1.5.交车区域:显示交车省-市;'), + React.createElement('div', { style: reqItemStyle }, '3.1.6.交车地点:显示交车详细地址;'), + React.createElement('div', { style: reqItemStyle }, '3.1.7.预计交车日期:显示预计交车日期,支持某天或某个时间段,格式为YYYY-MM-DD或YYYY-MM-DD至YYYY-MM-DD;'), + React.createElement('div', { style: reqItemStyle }, '3.1.8.开始计费日期:显示开始计费日期,格式为YYYY-MM-DD,在交车单完成电子签章后开始生效;'), + React.createElement('div', { style: reqItemStyle }, '3.1.9.创建人:显示交车任务的创建人用户姓名;'), + React.createElement('div', { style: reqItemStyle }, '3.1.10.创建时间:显示交车任务的创建时间,格式为YYYY-MM-DD;'), + React.createElement('div', { style: reqItemStyle }, '3.1.11.最后更新人:显示交车任务的最后一次更新人用户姓名;'), + React.createElement('div', { style: reqItemStyle }, '3.1.12.最后更新时间:显示交车任务的最后一次更新时间,格式为YYYY-MM-DD;'), + React.createElement('div', { style: reqItemStyle }, '3.1.13.操作:支持查看、编辑、停用/启用;'), + React.createElement('div', { style: reqSubItemStyle }, '3.1.13.1.查看:跳转查看交车任务;'), + React.createElement('div', { style: reqSubItemStyle }, '3.1.13.2.编辑:跳转编辑交车任务;'), + React.createElement('div', { style: reqSubItemStyle }, '3.1.13.3.停用/启用:任务创建后为启用状态,此处为停用,点击停用后,变为启用。停用状态的交车任务不会推送给对应区域运维人员,重新启用时判断预计交车结束日期是否已经过期,如已过期则启用时需要在弹出卡片中重新选择预计交车日期;'), + React.createElement('div', { style: reqSubStyle }, '3.2.已完成:显示所有已新增成功并完成交车单提交的任务,列表展示以下内容:合同编码、项目名称、客户名称、交车数量、预计交车日期、开始计费日期、创建人、创建时间、最后更新人、最后更新时间、操作;'), + React.createElement('div', { style: reqItemStyle }, '3.2.1.合同编码:显示对应合同编码;'), + React.createElement('div', { style: reqItemStyle }, '3.2.2.项目名称:显示对应项目名称;'), + React.createElement('div', { style: reqItemStyle }, '3.2.3.客户名称:显示对应客户名称;'), + React.createElement('div', { style: reqItemStyle }, '3.2.4.交车数量:显示交车数,重点色显示,点击弹出气泡卡片,列表显示:车辆类型、品牌、型号、车牌号、实际交车日期、交车人;'), + React.createElement('div', { style: reqSubItemStyle }, '3.2.4.1.车辆类型:显示车辆类型;'), + React.createElement('div', { style: reqSubItemStyle }, '3.2.4.2.品牌:显示车辆品牌;'), + React.createElement('div', { style: reqSubItemStyle }, '3.2.4.3.型号:显示车辆型号;'), + React.createElement('div', { style: reqSubItemStyle }, '3.2.4.4.车牌号:显示交车车辆车牌号,如果该车还未交车则显示为-;'), + React.createElement('div', { style: reqSubItemStyle }, '3.2.4.5.实际交车日期:显示实际交车日期,与列表中显示相同,格式为YYYY-MM-DD,如该车还未交车则显示为-;'), + React.createElement('div', { style: reqSubItemStyle }, '3.2.4.6.交车人:显示实际交车人用户姓名,如该车还未还车则显示为-;'), + React.createElement('div', { style: reqItemStyle }, '3.2.5.预计交车日期:显示预计交车日期,支持某天或某个时间段,格式为YYYY-MM-DD或YYYY-MM-DD至YYYY-MM-DD;'), + React.createElement('div', { style: reqItemStyle }, '3.2.6.开始计费日期:显示开始计费日期,格式为YYYY-MM-DD,在交车单完成电子签章后开始生效;'), + React.createElement('div', { style: reqItemStyle }, '3.2.7.创建人:显示交车任务的创建人用户姓名;'), + React.createElement('div', { style: reqItemStyle }, '3.2.8.创建时间:显示交车任务的创建时间,格式为YYYY-MM-DD;'), + React.createElement('div', { style: reqItemStyle }, '3.2.9.最后更新人:显示交车任务的最后一次更新人用户姓名;'), + React.createElement('div', { style: reqItemStyle }, '3.2.10.最后更新时间:显示交车任务的最后一次更新时间,格式为YYYY-MM-DD;'), + React.createElement('div', { style: reqItemStyle }, '3.2.11.操作:支持查看;') + ), + React.createElement('div', { style: reqBlockStyle }, + React.createElement('div', { style: reqSectionStyle }, '4.列表右下方为分页功能,支持单页显示条目选择;') + ) + ); + + return React.createElement(App, null, + 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() { _reqSpecOpen[1](true); } }, '查看需求说明') + ), + React.createElement(Card, { style: { marginBottom: 16 } }, + React.createElement('div', { + style: { + display: 'grid', + gridTemplateColumns: '1fr 1fr 1fr', + gap: '16px 24px', + alignItems: 'start', + flex: 1, + minWidth: 0 + } + }, filterNodes), + React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 16 } }, + React.createElement(Button, { onClick: handleReset }, '重置'), + React.createElement(Button, { type: 'primary', onClick: handleQuery }, '查询'), + React.createElement(Button, { type: 'link', size: 'small', onClick: function() { _filterExpanded[1](!_filterExpanded[0]); } }, _filterExpanded[0] ? '收起' : '展开') + ) + ), + React.createElement(Card, null, + React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 } }, + React.createElement(Tabs, { + activeKey: _activeTab[0], + onChange: function(k) { _activeTab[1](k); }, + items: [ + { key: 'ongoing', label: '进行中' }, + { key: 'completed', label: '已完成' } + ] + }), + React.createElement(Button, { type: 'primary', onClick: function() { message.info('请参照原型业务管理-交车任务-新增交车任务页面'); } }, '新增') + ), + _activeTab[0] === 'ongoing' + ? React.createElement(Table, { + rowKey: 'id', + columns: ongoingColumnsWithNowrap, + dataSource: filteredOngoing, + scroll: { x: 1860 }, + size: 'small', + pagination: { + showSizeChanger: true, + showTotal: function(t) { return '共 ' + t + ' 条'; }, + pageSizeOptions: ['10', '20', '50', '100'] + } + }) + : React.createElement(Table, { + rowKey: 'id', + columns: completedColumnsWithNowrap, + dataSource: filteredCompleted, + scroll: { x: 1860 }, + size: 'small', + pagination: { + showSizeChanger: true, + showTotal: function(t) { return '共 ' + t + ' 条'; }, + pageSizeOptions: ['10', '20', '50', '100'] + } + }) + ), + React.createElement(Modal, { + title: '需求说明', + open: _reqSpecOpen[0], + onCancel: function() { _reqSpecOpen[1](false); }, + width: 720, + footer: React.createElement(Button, { onClick: function() { _reqSpecOpen[1](false); } }, '关闭'), + bodyStyle: { maxHeight: '70vh', overflow: 'auto' } + }, reqSpecContent), + React.createElement(Modal, { + title: '预计交车日期已过期,请重新选择预计交车日期', + open: _rescheduleModalVisible[0], + onCancel: function() { + _rescheduleModalVisible[1](false); + _rescheduleTask[1](null); + _rescheduleDateRange[1](null); + }, + onOk: handleRescheduleConfirm, + okText: '确定', + cancelText: '取消' + }, React.createElement('div', { style: { padding: '8px 0' } }, + React.createElement('div', { style: { marginBottom: 8, color: '#666', fontSize: 13 } }, '支持选择单日或开始-结束时间段,单日时开始与结束选同一天即可。'), + React.createElement('span', { style: { marginRight: 8 } }, '预计交车日期:'), + React.createElement(RangePicker, { + placeholder: ['请选择开始日期', '请选择结束日期(单日请选同一天)'], + value: _rescheduleDateRange[0] && _rescheduleDateRange[0].length === 2 && window.dayjs ? [_rescheduleDateRange[0][0], _rescheduleDateRange[0][1]] : null, + onChange: function(dates) { _rescheduleDateRange[1](dates && dates.length === 2 ? dates : null); } + }) + )) + ) + ); +}; diff --git a/web端/业务管理/新增交车任务.jsx b/web端/业务管理/新增交车任务.jsx new file mode 100644 index 0000000..17cc43f --- /dev/null +++ b/web端/业务管理/新增交车任务.jsx @@ -0,0 +1,300 @@ +// 【重要】必须使用 const Component 作为组件变量名 - Axhub 产品原型 +// 数字化资产ONEOS运管平台 - 新增交车任务模块(参照新增租赁合同布局) + +const Component = function() { + var antd = window.antd; + var Input = antd.Input; + var Select = antd.Select; + var Button = antd.Button; + var DatePicker = antd.DatePicker; + var message = antd.message; + var Option = Select.Option; + var Checkbox = antd.Checkbox; + var Tooltip = antd.Tooltip; + var RangePicker = DatePicker.RangePicker; + + var projectId = React.useState(''); + var selectedProjectId = projectId[0]; + var setSelectedProjectId = projectId[1]; + + var expectedDelivery = React.useState(null); + var expectedDeliveryValue = expectedDelivery[0]; + var setExpectedDelivery = expectedDelivery[1]; + + var billingDate = React.useState(null); + var billingDateValue = billingDate[0]; + var setBillingDate = billingDate[1]; + + var selectedRowKeys = React.useState([]); + var checkedRowKeys = selectedRowKeys[0]; + var setCheckedRowKeys = selectedRowKeys[1]; + + var formErrors = React.useState({}); + var errors = formErrors[0]; + var setErrors = formErrors[1]; + + var reqSpecState = React.useState(false); + var reqSpecOpen = reqSpecState[0]; + var setReqSpecOpen = reqSpecState[1]; + + // Mock:项目列表及车辆样例。deliveryStatus: 'none' 可选,'submitted' 已提交交车任务不可选,'completed' 已完成交车不显示 + var projectList = [ + { id: 'p1', name: '嘉兴某某物流氢能运输项目', contractCode: 'JXZL20260216YW101235A', customerName: '嘉兴某某物流有限公司', deliveryRegion: '浙江省 / 嘉兴市', deliveryLocation: '浙江省嘉兴市南湖区科技大道1号', vehicles: [ + { key: 'v1', brand: '东风', model: '氢燃料电池重卡 H31', plateNo: '浙A10001', vin: 'LFV2BJCH8K3123456', monthRent: '12800', serviceFee: '800', deposit: '30000', remark: '首车', deliveryStatus: 'submitted' }, + { key: 'v2', brand: '东风', model: '氢燃料电池重卡 H31', plateNo: '浙A10002', vin: 'LFV2BJCH8K3123457', monthRent: '12800', serviceFee: '800', deposit: '30000', remark: '', deliveryStatus: 'submitted' }, + { key: 'v3', brand: '福田', model: '智蓝氢能轻卡', plateNo: '', vin: 'LZYTBACR2M1234567', monthRent: '8500', serviceFee: '500', deposit: '20000', remark: '待上牌', deliveryStatus: 'none' }, + { key: 'v4', brand: '重汽', model: '豪沃氢能牵引车', plateNo: '浙F20001', vin: 'ZZ4257N386FZ12345', monthRent: '15000', serviceFee: '1000', deposit: '35000', remark: '', deliveryStatus: 'none' }, + { key: 'v5', brand: '陕汽', model: '德龙氢能自卸', plateNo: '浙F20002', vin: 'SX1313GR456123456', monthRent: '13200', serviceFee: '880', deposit: '32000', remark: '固定线路', deliveryStatus: 'none' } + ]}, + { id: 'p2', name: '上海某某运输氢能租赁项目', contractCode: 'SHZL20260201YW200123A', customerName: '上海某某运输公司', deliveryRegion: '上海市 / 上海市', deliveryLocation: '上海市浦东新区张江高科技园区', vehicles: [ + { key: 'v6', brand: '上汽红岩', model: '杰狮氢能牵引', plateNo: '沪A30003', vin: 'SH1313HY789012345', monthRent: '14500', serviceFee: '950', deposit: '34000', remark: '', deliveryStatus: 'submitted' }, + { key: 'v7', brand: '宇通', model: '氢能公交 ZK6126', plateNo: '沪B40001', vin: 'LZYTAGCF8K4567890', monthRent: '22000', serviceFee: '1200', deposit: '50000', remark: '示范线路', deliveryStatus: 'none' }, + { key: 'v8', brand: '福田', model: '欧辉氢能大巴', plateNo: '', vin: 'LZYTBACR2M2345678', monthRent: '19800', serviceFee: '1100', deposit: '45000', remark: '', deliveryStatus: 'none' } + ]}, + { id: 'p3', name: '杭州某某租赁氢能项目', contractCode: 'HZZL20260115YW100089A', customerName: '杭州某某租赁有限公司', deliveryRegion: '浙江省 / 杭州市', deliveryLocation: '浙江省杭州市余杭区未来科技城', vehicles: [ + { key: 'v9', brand: '品牌C', model: '型号C1', plateNo: '浙A40004', vin: 'L4234567890ABCDEF', monthRent: '8200', serviceFee: '450', deposit: '20000', remark: '重点客户', deliveryStatus: 'completed' }, + { key: 'v10', brand: '品牌C', model: '型号C2', plateNo: '', vin: 'L5234567890ABCDEF', monthRent: '7800', serviceFee: '420', deposit: '19000', remark: '', deliveryStatus: 'submitted' }, + { key: 'v11', brand: '东风', model: '氢燃料电池厢货', plateNo: '浙A50001', vin: 'LFV2BJCH8K5678901', monthRent: '9200', serviceFee: '520', deposit: '22000', remark: '城配', deliveryStatus: 'none' }, + { key: 'v12', brand: '开沃', model: '创源氢能轻卡', plateNo: '浙A50002', vin: 'LJXTBACR9N6789012', monthRent: '8800', serviceFee: '480', deposit: '21000', remark: '', deliveryStatus: 'none' } + ]} + ]; + + var selectedProject = projectList.find(function(p) { return p.id === selectedProjectId; }); + var vehicleListRaw = selectedProject ? selectedProject.vehicles : []; + var vehicleList = vehicleListRaw.filter(function(v) { return v.deliveryStatus !== 'completed'; }); + var selectableVehicles = vehicleList.filter(function(v) { return v.deliveryStatus !== 'submitted'; }); + + var handleProjectChange = function(id) { + setSelectedProjectId(id || ''); + setCheckedRowKeys([]); + }; + + var todayStr = (function() { + var d = new Date(); + return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0'); + })(); + + var validateExpectedDelivery = function() { + if (!expectedDeliveryValue || !expectedDeliveryValue.length) return '请选择预计交车日期'; + var start = expectedDeliveryValue[0] && expectedDeliveryValue[0].format ? expectedDeliveryValue[0].format('YYYY-MM-DD') : null; + var end = expectedDeliveryValue[1] && expectedDeliveryValue[1].format ? expectedDeliveryValue[1].format('YYYY-MM-DD') : null; + if (!start) return '请选择预计交车日期'; + if (end && end < start) return '结束日期不能早于开始日期'; // 结束可与开始相同,即单日 + if (end && end < todayStr) return '结束日期不能早于当前日期'; + return null; + }; + + var expectedDeliveryError = validateExpectedDelivery(); + + var billingDateError = !billingDateValue ? '请选择开始计费日期' : null; + + var handleSubmit = function() { + var err = {}; + if (!selectedProjectId) err.projectName = '请选择项目名称'; + if (expectedDeliveryError) err.expectedDelivery = expectedDeliveryError; + if (billingDateError) err.billingDate = billingDateError; + if (checkedRowKeys.length === 0 && selectableVehicles.length > 0) err.vehicles = '请至少选择一辆车'; + setErrors(err); + if (Object.keys(err).length) return; + var count = checkedRowKeys.length; + message.success('已为 ' + count + ' 辆车生成交车任务,将按交车区域分配对应运维人员。'); + }; + + var handleCancel = function() { + message.info('取消'); + }; + + var onSelectAll = function(checked) { + if (checked) setCheckedRowKeys(selectableVehicles.map(function(v) { return v.key; })); + else setCheckedRowKeys([]); + }; + + var onSelectRow = function(record, checked) { + if (checked) setCheckedRowKeys(function(prev) { return prev.indexOf(record.key) === -1 ? prev.concat(record.key) : prev; }); + else setCheckedRowKeys(function(prev) { return prev.filter(function(k) { return k !== record.key; }); }); + }; + + var styles = { + page: { padding: '16px 24px 48px', backgroundColor: '#f5f5f5', minHeight: '100vh', fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif', fontSize: 14 }, + breadcrumb: { marginBottom: 16, color: '#666' }, + breadcrumbSep: { margin: '0 8px', color: '#999' }, + card: { backgroundColor: '#fff', borderRadius: 8, marginBottom: 16, boxShadow: '0 1px 2px rgba(0,0,0,0.05)', overflow: 'hidden' }, + cardHeader: { padding: '16px 20px', borderBottom: '1px solid #f0f0f0' }, + cardTitle: { fontSize: 16, fontWeight: 600, color: '#333' }, + cardBody: { padding: '20px 24px' }, + formRow: { display: 'flex', flexWrap: 'wrap', marginBottom: 16 }, + formCol: { flex: '0 0 33.33%', minWidth: 200, paddingRight: 16, marginBottom: 8 }, + label: { display: 'block', marginBottom: 6, color: '#333' }, + labelRequired: { color: '#ff4d4f', marginRight: 4 }, + errMsg: { color: '#ff4d4f', fontSize: 12, marginTop: 4 }, + footer: { position: 'fixed', bottom: 0, left: 0, right: 0, padding: '12px 24px', backgroundColor: '#fff', borderTop: '1px solid #e8e8e8', display: 'flex', gap: 12, justifyContent: 'flex-start', zIndex: 99 }, + tableWrap: { marginTop: 16, overflowX: 'auto' }, + table: { width: '100%', borderCollapse: 'collapse', fontSize: 13 }, + th: { padding: '10px 8px', textAlign: 'left', borderBottom: '1px solid #e8e8e8', backgroundColor: '#fafafa', fontWeight: 600 }, + td: { padding: '8px', borderBottom: '1px solid #f0f0f0', verticalAlign: 'middle' }, + inputDisabled: { width: '100%', padding: '6px 10px', border: '1px solid #d9d9d9', borderRadius: 4, backgroundColor: '#f5f5f5', color: '#666', fontSize: 13 }, + modalMask: { position: 'fixed', left: 0, top: 0, right: 0, bottom: 0, backgroundColor: 'rgba(0,0,0,0.45)', zIndex: 1000, display: 'flex', alignItems: 'center', justifyContent: 'center' }, + modalBox: { backgroundColor: '#fff', borderRadius: 8, width: '90%', maxWidth: 640, maxHeight: '85vh', overflow: 'hidden', display: 'flex', flexDirection: 'column', boxShadow: '0 4px 20px rgba(0,0,0,0.15)' }, + modalHeader: { padding: '16px 20px', borderBottom: '1px solid #f0f0f0', fontSize: 16, fontWeight: 600 }, + modalBody: { padding: 20, overflow: 'auto', flex: 1 } + }; + + var reqSpecBlock = { marginBottom: 16 }; + var reqSpecH2 = { fontSize: 14, fontWeight: 600, color: '#333', marginBottom: 6 }; + var reqSpecP = { fontSize: 13, color: '#666', marginBottom: 4 }; + var reqSpecLi = { fontSize: 13, color: '#666', marginBottom: 2, paddingLeft: 8 }; + + var reqSpecDoc = React.createElement('div', { style: { padding: '0 4px' } }, + React.createElement('div', { style: reqSpecBlock }, React.createElement('div', { style: reqSpecH2 }, '交车任务')), + React.createElement('div', { style: reqSpecBlock }, React.createElement('div', { style: reqSpecH2 }, '1.面包屑:'), React.createElement('div', { style: reqSpecLi }, '1.1.业务管理-交车任务-新增交车任务')), + React.createElement('div', { style: reqSpecBlock }, React.createElement('div', { style: reqSpecH2 }, '2.表单:'), + React.createElement('div', { style: reqSpecLi }, '2.1.选择项目名称:必选项,选择器,默认提示文本:请选择或输入项目名称,支持从输入框内输入内容进行模糊搜索,对应自营合同、租赁合同-「项目名称」字段;'), + React.createElement('div', { style: reqSpecLi }, '2.2.合同编码:输入框(禁用状态),根据所选项目名称自动反写合同编码;'), + React.createElement('div', { style: reqSpecLi }, '2.3.客户名称:输入框(禁用状态),根据所选项目名称自动反写客户名称;'), + React.createElement('div', { style: reqSpecLi }, '2.4.交车区域:输入框(禁用状态),根据所选项目名称自动反写交车区域。提交时根据交车区域,为对应区域运维人员分配对应交车任务;'), + React.createElement('div', { style: reqSpecLi }, '2.5.交车地点:输入框(禁用状态),根据所选项目名称自动反写交车地点;'), + React.createElement('div', { style: reqSpecLi }, '2.6.预计交车日期:必填项,日期选择器,支持某天或某个时间段两种模式,格式为YYYY-MM-DD或YYYY-MM-DD至YYYY-MM-DD,结束日期不能早于开始日期,并且结束日期不能早于当前日期;'), + React.createElement('div', { style: reqSpecLi }, '2.7.开始计费日期:必填项,日期选择器,支持单日选择,格式为YYYY-MM-DD;'), + React.createElement('div', { style: reqSpecLi }, '2.8.下方为列表,列表拉取该车辆租赁合同对应所有车辆信息(该合同下已提交过交车任务的车辆不可选,已完成交车的车辆不显示在列表中),列表字段为:全选/多选、品牌、型号、车牌号、车辆识别代码、车辆月租金、服务费、保证金、备注;'), + React.createElement('div', { style: reqSpecLi }, '2.8.1.全选/多选:支持全选、多选模式,选择对应车辆后,点击提交自动生成被选中车辆交车任务,需要至少选择1辆,才能进行提交,该合同下已提交过交车任务的车辆不可选,已完成交车的车辆不显示在列表中;'), + React.createElement('div', { style: reqSpecLi }, '2.8.2.品牌:输入框(禁用状态),根据所选项目名称自动反写品牌;'), + React.createElement('div', { style: reqSpecLi }, '2.8.3.型号:输入框(禁用状态),根据所选项目名称自动反写型号;'), + React.createElement('div', { style: reqSpecLi }, '2.8.4.车牌号:输入框(禁用状态),根据所选项目名称自动反写车牌号,车牌号可能为空,为空时显示为-;'), + React.createElement('div', { style: reqSpecLi }, '2.8.5.车辆识别代码:输入框(禁用状态),根据所选项目名称自动反写车辆识别代码;'), + React.createElement('div', { style: reqSpecLi }, '2.8.6.车辆月租金:输入框(禁用状态),根据所选项目名称自动反写车辆月租金,后缀为元;'), + React.createElement('div', { style: reqSpecLi }, '2.8.7.服务费:输入框(禁用状态),根据所选项目名称自动反写服务费,后缀为元;'), + React.createElement('div', { style: reqSpecLi }, '2.8.8.保证金:输入框(禁用状态),根据所选项目名称自动反写保证金,后缀为元;'), + React.createElement('div', { style: reqSpecLi }, '2.8.9.备注:输入框(禁用状态),根据所选项目名称自动反写备注信息,备注为空时显示为-;'), + React.createElement('div', { style: reqSpecLi }, '2.9.页面底部为提交、取消;') + )); + + var reqSpecModalContent = reqSpecOpen ? React.createElement('div', { style: styles.modalMask, onClick: function(e) { if (e.target === e.currentTarget) setReqSpecOpen(false); } }, React.createElement('div', { style: styles.modalBox, onClick: function(e) { e.stopPropagation(); } }, React.createElement('div', { style: styles.modalHeader }, '需求说明'), React.createElement('div', { style: Object.assign({}, styles.modalBody, { maxHeight: '70vh', padding: '20px 24px' }) }, reqSpecDoc), React.createElement('div', { style: { padding: '12px 20px', borderTop: '1px solid #f0f0f0', textAlign: 'right' } }, React.createElement(Button, { onClick: function() { setReqSpecOpen(false); } }, '关闭')))) : null; + + var FormItem = function(props) { + return React.createElement('div', { style: styles.formCol }, + React.createElement('label', { style: styles.label }, props.required ? React.createElement('span', { style: styles.labelRequired }, '*') : null, props.label), + props.children, + props.error ? React.createElement('div', { style: styles.errMsg }, props.error) : null + ); + }; + + var projectOptions = projectList.map(function(p) { return React.createElement(Option, { key: p.id, value: p.id }, p.name); }); + + var formRow1 = React.createElement('div', { style: styles.formRow }, + React.createElement(FormItem, { label: '选择项目名称', required: true, error: errors.projectName }, + React.createElement(Select, { + placeholder: '请选择或输入项目名称', + style: { width: '100%' }, + value: selectedProjectId || undefined, + onChange: handleProjectChange, + showSearch: true, + allowClear: true, + filterOption: function(input, opt) { return opt.children && String(opt.children).toLowerCase().indexOf((input || '').toLowerCase()) >= 0; }, + status: errors.projectName ? 'error' : undefined + }, projectOptions)), + React.createElement(FormItem, { label: '合同编码' }, React.createElement(Input, { value: selectedProject ? selectedProject.contractCode : '', disabled: true, style: { width: '100%' } })), + React.createElement(FormItem, { label: '客户名称' }, React.createElement(Input, { value: selectedProject ? selectedProject.customerName : '', disabled: true, style: { width: '100%' } })), + React.createElement(FormItem, { label: '交车区域' }, React.createElement(Input, { value: selectedProject ? selectedProject.deliveryRegion : '', disabled: true, style: { width: '100%' } })), + React.createElement(FormItem, { label: '交车地点' }, React.createElement(Input, { value: selectedProject ? selectedProject.deliveryLocation : '', disabled: true, style: { width: '100%' } })) + ); + + var formRow2 = React.createElement('div', { style: styles.formRow }, + React.createElement(FormItem, { label: '预计交车日期', required: true, error: errors.expectedDelivery }, + React.createElement(RangePicker, { + style: { width: '100%' }, + format: 'YYYY-MM-DD', + placeholder: ['请选择开始日期', '请选择结束日期(单日请选同一天)'], + value: expectedDeliveryValue, + onChange: function(dates) { setExpectedDelivery(dates && dates.length === 2 ? dates : null); }, + status: errors.expectedDelivery ? 'error' : undefined + })), + React.createElement(FormItem, { label: '开始计费日期', required: true, error: errors.billingDate }, + React.createElement(DatePicker, { + style: { width: '100%' }, + format: 'YYYY-MM-DD', + placeholder: '请选择日期', + value: billingDateValue, + onChange: function(d, dateStr) { setBillingDate(d); }, + status: errors.billingDate ? 'error' : undefined + })) + ); + + var allSelectableChecked = selectableVehicles.length > 0 && selectableVehicles.every(function(v) { return checkedRowKeys.indexOf(v.key) !== -1; }); + var someSelectableChecked = selectableVehicles.some(function(v) { return checkedRowKeys.indexOf(v.key) !== -1; }); + var tableHeader = React.createElement('thead', null, + React.createElement('tr', null, + React.createElement('th', { style: Object.assign({}, styles.th, { width: 48 }) }, + React.createElement(Checkbox, { + checked: allSelectableChecked, + indeterminate: someSelectableChecked && !allSelectableChecked, + onChange: function(e) { onSelectAll(e.target.checked); } + })), + 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 tableBody = React.createElement('tbody', null, + vehicleList.length === 0 + ? React.createElement('tr', null, React.createElement('td', { colSpan: 9, style: Object.assign({}, styles.td, { textAlign: 'center', color: '#999' }) }, '请先选择项目名称,将自动带出该合同下车辆信息')) + : vehicleList.map(function(row) { + var isSubmitted = row.deliveryStatus === 'submitted'; + return React.createElement('tr', { key: row.key }, + React.createElement('td', { style: styles.td }, + isSubmitted + ? React.createElement(Tooltip, { title: '该车辆已有交车任务' }, + React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', gap: 6, cursor: 'not-allowed' } }, + React.createElement(Checkbox, { disabled: true, checked: false }), + React.createElement('span', { style: { color: '#999', fontSize: 12 } }, '已提交'))) + : React.createElement(Checkbox, { checked: checkedRowKeys.indexOf(row.key) !== -1, onChange: function(e) { onSelectRow(row, e.target.checked); } })), + React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.brand, disabled: true, style: styles.inputDisabled })), + React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.model, disabled: true, style: styles.inputDisabled })), + React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.plateNo || '-', disabled: true, style: styles.inputDisabled })), + React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.vin, disabled: true, style: styles.inputDisabled })), + React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.monthRent + '元', disabled: true, style: styles.inputDisabled })), + React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.serviceFee + '元', disabled: true, style: styles.inputDisabled })), + React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.deposit + '元', disabled: true, style: styles.inputDisabled })), + React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.remark || '-', disabled: true, style: styles.inputDisabled })) + ); + }) + ); + + var tableEl = React.createElement('div', { style: styles.tableWrap }, + React.createElement('table', { style: styles.table }, tableHeader, tableBody) + ); + + if (errors.vehicles) { + tableEl = React.createElement('div', null, + React.createElement('div', { style: Object.assign({}, styles.errMsg, { marginBottom: 8 }) }, errors.vehicles), + tableEl + ); + } + + return React.createElement('div', { style: styles.page }, + React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 } }, + React.createElement('div', { style: styles.breadcrumb }, + React.createElement('span', null, '业务管理'), + React.createElement('span', { style: styles.breadcrumbSep }, ' / '), + React.createElement('span', null, '交车任务'), + React.createElement('span', { style: styles.breadcrumbSep }, ' / '), + React.createElement('span', { style: { color: '#1890ff' } }, '新增交车任务')), + React.createElement('span', { style: { color: '#1890ff', cursor: 'pointer', fontSize: 14 }, onClick: function() { setReqSpecOpen(true); } }, '查看需求说明')), + React.createElement('div', { style: styles.card }, + React.createElement('div', { style: styles.cardHeader }, React.createElement('span', { style: styles.cardTitle }, '交车任务')), + React.createElement('div', { style: styles.cardBody }, + formRow1, + formRow2, + tableEl)), + React.createElement('div', { style: { height: 60 } }), + reqSpecModalContent, + React.createElement('div', { style: styles.footer }, + React.createElement(Button, { type: 'primary', onClick: handleSubmit }, '提交'), + React.createElement(Button, { onClick: handleCancel }, '取消')) + ); +}; diff --git a/web端/业务管理/查看交车任务.jsx b/web端/业务管理/查看交车任务.jsx new file mode 100644 index 0000000..7dd98da --- /dev/null +++ b/web端/业务管理/查看交车任务.jsx @@ -0,0 +1,123 @@ +// 【重要】必须使用 const Component 作为组件变量名 - Axhub 产品原型 +// 数字化资产ONEOS运管平台 - 查看交车任务模块(只读,布局参照新增交车任务) + +const Component = function() { + var antd = window.antd; + var Input = antd.Input; + var Button = antd.Button; + + // Mock:当前查看的交车任务详情(从列表跳转带入或根据 id 拉取) + var task = { + projectName: '嘉兴某某物流氢能运输项目', + contractCode: 'JXZL20260216YW101235A', + customerName: '嘉兴某某物流有限公司', + deliveryRegion: '浙江省 / 嘉兴市', + deliveryLocation: '浙江省嘉兴市南湖区科技大道1号', + planDeliveryDisplay: '2026-03-01至2026-03-05', + billingStartDate: '2026-03-06', + vehicles: [ + { brand: '东风', model: '氢燃料电池重卡 H31', plateNo: '浙A10001', vin: 'LFV2BJCH8K3123456', monthRent: '12800', serviceFee: '800', deposit: '30000', remark: '首车' }, + { brand: '福田', model: '智蓝氢能轻卡', plateNo: '', vin: 'LZYTBACR2M1234567', monthRent: '8500', serviceFee: '500', deposit: '20000', remark: '待上牌' }, + { brand: '重汽', model: '豪沃氢能牵引车', plateNo: '浙F20001', vin: 'ZZ4257N386FZ12345', monthRent: '15000', serviceFee: '1000', deposit: '35000', remark: '' } + ] + }; + + var vehicleList = task.vehicles || []; + + var styles = { + page: { padding: '16px 24px 48px', backgroundColor: '#f5f5f5', minHeight: '100vh', fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif', fontSize: 14 }, + breadcrumb: { marginBottom: 16, color: '#666' }, + breadcrumbSep: { margin: '0 8px', color: '#999' }, + card: { backgroundColor: '#fff', borderRadius: 8, marginBottom: 16, boxShadow: '0 1px 2px rgba(0,0,0,0.05)', overflow: 'hidden' }, + cardHeader: { padding: '16px 20px', borderBottom: '1px solid #f0f0f0' }, + cardTitle: { fontSize: 16, fontWeight: 600, color: '#333' }, + cardBody: { padding: '20px 24px' }, + formRow: { display: 'flex', flexWrap: 'wrap', marginBottom: 16 }, + formCol: { flex: '0 0 33.33%', minWidth: 200, paddingRight: 16, marginBottom: 8 }, + label: { display: 'block', marginBottom: 6, color: '#333' }, + inputDisabled: { width: '100%', padding: '6px 10px', border: '1px solid #d9d9d9', borderRadius: 4, backgroundColor: '#f5f5f5', color: '#666', fontSize: 13 }, + footer: { position: 'fixed', bottom: 0, left: 0, right: 0, padding: '12px 24px', backgroundColor: '#fff', borderTop: '1px solid #e8e8e8', display: 'flex', gap: 12, justifyContent: 'flex-start', zIndex: 99 }, + tableWrap: { marginTop: 16, overflowX: 'auto' }, + table: { width: '100%', borderCollapse: 'collapse', fontSize: 13 }, + th: { padding: '10px 8px', textAlign: 'left', borderBottom: '1px solid #e8e8e8', backgroundColor: '#fafafa', fontWeight: 600 }, + td: { padding: '8px', borderBottom: '1px solid #f0f0f0', verticalAlign: 'middle' } + }; + + var FormItemReadOnly = function(props) { + return React.createElement('div', { style: styles.formCol }, + React.createElement('label', { style: styles.label }, props.label), + React.createElement(Input, { value: props.value != null ? props.value : '', disabled: true, style: styles.inputDisabled }) + ); + }; + + var formRow1 = React.createElement('div', { style: styles.formRow }, + React.createElement(FormItemReadOnly, { label: '选择项目名称', value: task.projectName }), + React.createElement(FormItemReadOnly, { label: '合同编码', value: task.contractCode }), + React.createElement(FormItemReadOnly, { label: '客户名称', value: task.customerName }), + React.createElement(FormItemReadOnly, { label: '交车区域', value: task.deliveryRegion }), + React.createElement(FormItemReadOnly, { label: '交车地点', value: task.deliveryLocation }) + ); + + var formRow2 = React.createElement('div', { style: styles.formRow }, + React.createElement(FormItemReadOnly, { label: '预计交车日期', value: task.planDeliveryDisplay }), + React.createElement(FormItemReadOnly, { label: '开始计费日期', value: task.billingStartDate }) + ); + + var tableHeader = 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 }, '备注') + ) + ); + + var tableBody = React.createElement('tbody', null, + vehicleList.length === 0 + ? React.createElement('tr', null, React.createElement('td', { colSpan: 8, style: Object.assign({}, styles.td, { textAlign: 'center', color: '#999' }) }, '暂无车辆')) + : vehicleList.map(function(row, i) { + return React.createElement('tr', { key: i }, + React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.brand, disabled: true, style: styles.inputDisabled })), + React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.model, disabled: true, style: styles.inputDisabled })), + React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.plateNo || '-', disabled: true, style: styles.inputDisabled })), + React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.vin, disabled: true, style: styles.inputDisabled })), + React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.monthRent + '元', disabled: true, style: styles.inputDisabled })), + React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.serviceFee + '元', disabled: true, style: styles.inputDisabled })), + React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.deposit + '元', disabled: true, style: styles.inputDisabled })), + React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.remark || '-', disabled: true, style: styles.inputDisabled })) + ); + }) + ); + + var tableEl = React.createElement('div', { style: styles.tableWrap }, + React.createElement('table', { style: styles.table }, tableHeader, tableBody) + ); + + var handleBack = function() { + if (window.history && window.history.back) window.history.back(); + else antd.message.info('返回'); + }; + + return React.createElement('div', { style: styles.page }, + React.createElement('div', { style: { marginBottom: 16 } }, + React.createElement('div', { style: styles.breadcrumb }, + React.createElement('span', null, '业务管理'), + React.createElement('span', { style: styles.breadcrumbSep }, ' / '), + React.createElement('span', null, '交车任务'), + React.createElement('span', { style: styles.breadcrumbSep }, ' / '), + React.createElement('span', { style: { color: '#1890ff' } }, '查看交车任务'))), + React.createElement('div', { style: styles.card }, + React.createElement('div', { style: styles.cardHeader }, React.createElement('span', { style: styles.cardTitle }, '交车任务')), + React.createElement('div', { style: styles.cardBody }, + formRow1, + formRow2, + tableEl)), + React.createElement('div', { style: { height: 60 } }), + React.createElement('div', { style: styles.footer }, + React.createElement(Button, { onClick: handleBack }, '返回')) + ); +}; diff --git a/web端/业务管理/租赁账单.jsx b/web端/业务管理/租赁账单.jsx new file mode 100644 index 0000000..40aeb83 --- /dev/null +++ b/web端/业务管理/租赁账单.jsx @@ -0,0 +1,933 @@ +// 【重要】必须使用 const Component 作为组件变量名 +// 账单管理 - 车辆资产管理后台(按 antd 规范) + +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 Input = antd.Input; + var message = antd.message; + var Space = antd.Space; + var Row = antd.Row; + var Col = antd.Col; + + // 当前视图:list | detail + var viewState = useState('list'); + var currentView = viewState[0]; + var setCurrentView = viewState[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 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 + }); + } + return list; + }, []); + + 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); + }); + 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]); + + 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 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); + } + + 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 || '-') + ) + ) + ) + ), + // 车辆账单 + 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(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 listBreadcrumbItems = [ + { title: '运维管理' }, + { title: '业务管理' }, + { title: '租赁账单' } + ]; + + 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 }, '导出') + ), + React.createElement(Table, { + rowSelection: rowSelection, + columns: listColumns, + dataSource: paginatedList, + rowKey: 'id', + pagination: tablePagination, + scroll: { x: 1600 }, + size: 'middle' + }) + ), + // 应付金额明细 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) + ); +}; + +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..6269bba --- /dev/null +++ b/web端/业务管理/编辑交车任务.jsx @@ -0,0 +1,283 @@ +// 【重要】必须使用 const Component 作为组件变量名 - Axhub 产品原型 +// 数字化资产ONEOS运管平台 - 编辑交车任务模块(布局同新增交车任务,项目名称禁用不可改) + +const Component = function() { + var antd = window.antd; + var Input = antd.Input; + var Select = antd.Select; + var Button = antd.Button; + var DatePicker = antd.DatePicker; + var message = antd.message; + var Option = Select.Option; + var Checkbox = antd.Checkbox; + var Tooltip = antd.Tooltip; + var RangePicker = DatePicker.RangePicker; + + var projectId = React.useState('p1'); + var selectedProjectId = projectId[0]; + var setSelectedProjectId = projectId[1]; + + var expectedDelivery = React.useState(null); + var expectedDeliveryValue = expectedDelivery[0]; + var setExpectedDelivery = expectedDelivery[1]; + + var billingDate = React.useState(null); + var billingDateValue = billingDate[0]; + var setBillingDate = billingDate[1]; + + var selectedRowKeys = React.useState(['v3', 'v4']); + var checkedRowKeys = selectedRowKeys[0]; + var setCheckedRowKeys = selectedRowKeys[1]; + + var formErrors = React.useState({}); + var errors = formErrors[0]; + var setErrors = formErrors[1]; + + var reqSpecState = React.useState(false); + var reqSpecOpen = reqSpecState[0]; + var setReqSpecOpen = reqSpecState[1]; + + // Mock:项目列表及车辆样例。deliveryStatus: 'none' 可选,'submitted' 已提交交车任务不可选,'completed' 已完成交车不显示 + var projectList = [ + { id: 'p1', name: '嘉兴某某物流氢能运输项目', contractCode: 'JXZL20260216YW101235A', customerName: '嘉兴某某物流有限公司', deliveryRegion: '浙江省 / 嘉兴市', deliveryLocation: '浙江省嘉兴市南湖区科技大道1号', vehicles: [ + { key: 'v1', brand: '东风', model: '氢燃料电池重卡 H31', plateNo: '浙A10001', vin: 'LFV2BJCH8K3123456', monthRent: '12800', serviceFee: '800', deposit: '30000', remark: '首车', deliveryStatus: 'submitted' }, + { key: 'v2', brand: '东风', model: '氢燃料电池重卡 H31', plateNo: '浙A10002', vin: 'LFV2BJCH8K3123457', monthRent: '12800', serviceFee: '800', deposit: '30000', remark: '', deliveryStatus: 'submitted' }, + { key: 'v3', brand: '福田', model: '智蓝氢能轻卡', plateNo: '', vin: 'LZYTBACR2M1234567', monthRent: '8500', serviceFee: '500', deposit: '20000', remark: '待上牌', deliveryStatus: 'none' }, + { key: 'v4', brand: '重汽', model: '豪沃氢能牵引车', plateNo: '浙F20001', vin: 'ZZ4257N386FZ12345', monthRent: '15000', serviceFee: '1000', deposit: '35000', remark: '', deliveryStatus: 'none' }, + { key: 'v5', brand: '陕汽', model: '德龙氢能自卸', plateNo: '浙F20002', vin: 'SX1313GR456123456', monthRent: '13200', serviceFee: '880', deposit: '32000', remark: '固定线路', deliveryStatus: 'none' } + ]}, + { id: 'p2', name: '上海某某运输氢能租赁项目', contractCode: 'SHZL20260201YW200123A', customerName: '上海某某运输公司', deliveryRegion: '上海市 / 上海市', deliveryLocation: '上海市浦东新区张江高科技园区', vehicles: [ + { key: 'v6', brand: '上汽红岩', model: '杰狮氢能牵引', plateNo: '沪A30003', vin: 'SH1313HY789012345', monthRent: '14500', serviceFee: '950', deposit: '34000', remark: '', deliveryStatus: 'submitted' }, + { key: 'v7', brand: '宇通', model: '氢能公交 ZK6126', plateNo: '沪B40001', vin: 'LZYTAGCF8K4567890', monthRent: '22000', serviceFee: '1200', deposit: '50000', remark: '示范线路', deliveryStatus: 'none' }, + { key: 'v8', brand: '福田', model: '欧辉氢能大巴', plateNo: '', vin: 'LZYTBACR2M2345678', monthRent: '19800', serviceFee: '1100', deposit: '45000', remark: '', deliveryStatus: 'none' } + ]}, + { id: 'p3', name: '杭州某某租赁氢能项目', contractCode: 'HZZL20260115YW100089A', customerName: '杭州某某租赁有限公司', deliveryRegion: '浙江省 / 杭州市', deliveryLocation: '浙江省杭州市余杭区未来科技城', vehicles: [ + { key: 'v9', brand: '品牌C', model: '型号C1', plateNo: '浙A40004', vin: 'L4234567890ABCDEF', monthRent: '8200', serviceFee: '450', deposit: '20000', remark: '重点客户', deliveryStatus: 'completed' }, + { key: 'v10', brand: '品牌C', model: '型号C2', plateNo: '', vin: 'L5234567890ABCDEF', monthRent: '7800', serviceFee: '420', deposit: '19000', remark: '', deliveryStatus: 'submitted' }, + { key: 'v11', brand: '东风', model: '氢燃料电池厢货', plateNo: '浙A50001', vin: 'LFV2BJCH8K5678901', monthRent: '9200', serviceFee: '520', deposit: '22000', remark: '城配', deliveryStatus: 'none' }, + { key: 'v12', brand: '开沃', model: '创源氢能轻卡', plateNo: '浙A50002', vin: 'LJXTBACR9N6789012', monthRent: '8800', serviceFee: '480', deposit: '21000', remark: '', deliveryStatus: 'none' } + ]} + ]; + + var selectedProject = projectList.find(function(p) { return p.id === selectedProjectId; }); + var vehicleListRaw = selectedProject ? selectedProject.vehicles : []; + var vehicleList = vehicleListRaw.filter(function(v) { return v.deliveryStatus !== 'completed'; }); + var selectableVehicles = vehicleList.filter(function(v) { return v.deliveryStatus !== 'submitted'; }); + + var handleProjectChange = function(id) { + setSelectedProjectId(id || ''); + setCheckedRowKeys([]); + }; + + var todayStr = (function() { + var d = new Date(); + return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0'); + })(); + + var validateExpectedDelivery = function() { + if (!expectedDeliveryValue || !expectedDeliveryValue.length) return '请选择预计交车日期'; + var start = expectedDeliveryValue[0] && expectedDeliveryValue[0].format ? expectedDeliveryValue[0].format('YYYY-MM-DD') : null; + var end = expectedDeliveryValue[1] && expectedDeliveryValue[1].format ? expectedDeliveryValue[1].format('YYYY-MM-DD') : null; + if (!start) return '请选择预计交车日期'; + if (end && end < start) return '结束日期不能早于开始日期'; + if (end && end < todayStr) return '结束日期不能早于当前日期'; + return null; + }; + + var expectedDeliveryError = validateExpectedDelivery(); + + var billingDateError = !billingDateValue ? '请选择开始计费日期' : null; + + var handleSubmit = function() { + var err = {}; + if (!selectedProjectId) err.projectName = '请选择项目名称'; + if (expectedDeliveryError) err.expectedDelivery = expectedDeliveryError; + if (billingDateError) err.billingDate = billingDateError; + if (checkedRowKeys.length === 0 && selectableVehicles.length > 0) err.vehicles = '请至少选择一辆车'; + setErrors(err); + if (Object.keys(err).length) return; + message.success('交车任务已保存。'); + }; + + var handleCancel = function() { + message.info('取消'); + }; + + var onSelectAll = function(checked) { + if (checked) setCheckedRowKeys(selectableVehicles.map(function(v) { return v.key; })); + else setCheckedRowKeys([]); + }; + + var onSelectRow = function(record, checked) { + if (checked) setCheckedRowKeys(function(prev) { return prev.indexOf(record.key) === -1 ? prev.concat(record.key) : prev; }); + else setCheckedRowKeys(function(prev) { return prev.filter(function(k) { return k !== record.key; }); }); + }; + + var styles = { + page: { padding: '16px 24px 48px', backgroundColor: '#f5f5f5', minHeight: '100vh', fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif', fontSize: 14 }, + breadcrumb: { marginBottom: 16, color: '#666' }, + breadcrumbSep: { margin: '0 8px', color: '#999' }, + card: { backgroundColor: '#fff', borderRadius: 8, marginBottom: 16, boxShadow: '0 1px 2px rgba(0,0,0,0.05)', overflow: 'hidden' }, + cardHeader: { padding: '16px 20px', borderBottom: '1px solid #f0f0f0' }, + cardTitle: { fontSize: 16, fontWeight: 600, color: '#333' }, + cardBody: { padding: '20px 24px' }, + formRow: { display: 'flex', flexWrap: 'wrap', marginBottom: 16 }, + formCol: { flex: '0 0 33.33%', minWidth: 200, paddingRight: 16, marginBottom: 8 }, + label: { display: 'block', marginBottom: 6, color: '#333' }, + labelRequired: { color: '#ff4d4f', marginRight: 4 }, + errMsg: { color: '#ff4d4f', fontSize: 12, marginTop: 4 }, + footer: { position: 'fixed', bottom: 0, left: 0, right: 0, padding: '12px 24px', backgroundColor: '#fff', borderTop: '1px solid #e8e8e8', display: 'flex', gap: 12, justifyContent: 'flex-start', zIndex: 99 }, + tableWrap: { marginTop: 16, overflowX: 'auto' }, + table: { width: '100%', borderCollapse: 'collapse', fontSize: 13 }, + th: { padding: '10px 8px', textAlign: 'left', borderBottom: '1px solid #e8e8e8', backgroundColor: '#fafafa', fontWeight: 600 }, + td: { padding: '8px', borderBottom: '1px solid #f0f0f0', verticalAlign: 'middle' }, + inputDisabled: { width: '100%', padding: '6px 10px', border: '1px solid #d9d9d9', borderRadius: 4, backgroundColor: '#f5f5f5', color: '#666', fontSize: 13 }, + modalMask: { position: 'fixed', left: 0, top: 0, right: 0, bottom: 0, backgroundColor: 'rgba(0,0,0,0.45)', zIndex: 1000, display: 'flex', alignItems: 'center', justifyContent: 'center' }, + modalBox: { backgroundColor: '#fff', borderRadius: 8, width: '90%', maxWidth: 640, maxHeight: '85vh', overflow: 'hidden', display: 'flex', flexDirection: 'column', boxShadow: '0 4px 20px rgba(0,0,0,0.15)' }, + modalHeader: { padding: '16px 20px', borderBottom: '1px solid #f0f0f0', fontSize: 16, fontWeight: 600 }, + modalBody: { padding: 20, overflow: 'auto', flex: 1 } + }; + + var reqSpecBlock = { marginBottom: 16 }; + var reqSpecH2 = { fontSize: 14, fontWeight: 600, color: '#333', marginBottom: 6 }; + var reqSpecLi = { fontSize: 13, color: '#666', marginBottom: 2, paddingLeft: 8 }; + + var reqSpecDoc = React.createElement('div', { style: { padding: '0 4px' } }, + React.createElement('div', { style: reqSpecBlock }, React.createElement('div', { style: reqSpecH2 }, '交车任务')), + React.createElement('div', { style: reqSpecBlock }, React.createElement('div', { style: reqSpecH2 }, '1.面包屑:'), React.createElement('div', { style: reqSpecLi }, '1.1.业务管理-交车任务-编辑交车任务')), + React.createElement('div', { style: reqSpecBlock }, React.createElement('div', { style: reqSpecH2 }, '2.表单:'), + React.createElement('div', { style: reqSpecLi }, '2.1.选择项目名称:已选择禁用,不可修改;'), + React.createElement('div', { style: reqSpecLi }, '2.2.合同编码、客户名称、交车区域、交车地点、预计交车日期、开始计费日期、车辆列表等可修改;'), + React.createElement('div', { style: reqSpecLi }, '预计交车日期:不能早于当前日期'), + React.createElement('div', { style: reqSpecLi }, '2.9.页面底部为提交、取消;') + )); + + var reqSpecModalContent = reqSpecOpen ? React.createElement('div', { style: styles.modalMask, onClick: function(e) { if (e.target === e.currentTarget) setReqSpecOpen(false); } }, React.createElement('div', { style: styles.modalBox, onClick: function(e) { e.stopPropagation(); } }, React.createElement('div', { style: styles.modalHeader }, '需求说明'), React.createElement('div', { style: Object.assign({}, styles.modalBody, { maxHeight: '70vh', padding: '20px 24px' }) }, reqSpecDoc), React.createElement('div', { style: { padding: '12px 20px', borderTop: '1px solid #f0f0f0', textAlign: 'right' } }, React.createElement(Button, { onClick: function() { setReqSpecOpen(false); } }, '关闭')))) : null; + + var FormItem = function(props) { + return React.createElement('div', { style: styles.formCol }, + React.createElement('label', { style: styles.label }, props.required ? React.createElement('span', { style: styles.labelRequired }, '*') : null, props.label), + props.children, + props.error ? React.createElement('div', { style: styles.errMsg }, props.error) : null + ); + }; + + var projectOptions = projectList.map(function(p) { return React.createElement(Option, { key: p.id, value: p.id }, p.name); }); + + var formRow1 = React.createElement('div', { style: styles.formRow }, + React.createElement(FormItem, { label: '选择项目名称', required: true, error: errors.projectName }, + React.createElement(Select, { + placeholder: '请选择或输入项目名称', + style: { width: '100%' }, + value: selectedProjectId || undefined, + disabled: true, + showSearch: true, + filterOption: function(input, opt) { return opt.children && String(opt.children).toLowerCase().indexOf((input || '').toLowerCase()) >= 0; }, + status: errors.projectName ? 'error' : undefined + }, projectOptions)), + React.createElement(FormItem, { label: '合同编码' }, React.createElement(Input, { value: selectedProject ? selectedProject.contractCode : '', disabled: true, style: { width: '100%' } })), + React.createElement(FormItem, { label: '客户名称' }, React.createElement(Input, { value: selectedProject ? selectedProject.customerName : '', disabled: true, style: { width: '100%' } })), + React.createElement(FormItem, { label: '交车区域' }, React.createElement(Input, { value: selectedProject ? selectedProject.deliveryRegion : '', disabled: true, style: { width: '100%' } })), + React.createElement(FormItem, { label: '交车地点' }, React.createElement(Input, { value: selectedProject ? selectedProject.deliveryLocation : '', disabled: true, style: { width: '100%' } })) + ); + + var formRow2 = React.createElement('div', { style: styles.formRow }, + React.createElement(FormItem, { label: '预计交车日期', required: true, error: errors.expectedDelivery }, + React.createElement(RangePicker, { + style: { width: '100%' }, + format: 'YYYY-MM-DD', + placeholder: ['请选择开始日期', '请选择结束日期(单日请选同一天)'], + value: expectedDeliveryValue, + onChange: function(dates) { setExpectedDelivery(dates && dates.length === 2 ? dates : null); }, + status: errors.expectedDelivery ? 'error' : undefined + })), + React.createElement(FormItem, { label: '开始计费日期', required: true, error: errors.billingDate }, + React.createElement(DatePicker, { + style: { width: '100%' }, + format: 'YYYY-MM-DD', + placeholder: '请选择日期', + value: billingDateValue, + onChange: function(d, dateStr) { setBillingDate(d); }, + status: errors.billingDate ? 'error' : undefined + })) + ); + + var allSelectableChecked = selectableVehicles.length > 0 && selectableVehicles.every(function(v) { return checkedRowKeys.indexOf(v.key) !== -1; }); + var someSelectableChecked = selectableVehicles.some(function(v) { return checkedRowKeys.indexOf(v.key) !== -1; }); + var tableHeader = React.createElement('thead', null, + React.createElement('tr', null, + React.createElement('th', { style: Object.assign({}, styles.th, { width: 48 }) }, + React.createElement(Checkbox, { + checked: allSelectableChecked, + indeterminate: someSelectableChecked && !allSelectableChecked, + onChange: function(e) { onSelectAll(e.target.checked); } + })), + 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 tableBody = React.createElement('tbody', null, + vehicleList.length === 0 + ? React.createElement('tr', null, React.createElement('td', { colSpan: 9, style: Object.assign({}, styles.td, { textAlign: 'center', color: '#999' }) }, '该合同下暂无车辆信息')) + : vehicleList.map(function(row) { + var isSubmitted = row.deliveryStatus === 'submitted'; + return React.createElement('tr', { key: row.key }, + React.createElement('td', { style: styles.td }, + isSubmitted + ? React.createElement(Tooltip, { title: '该车辆已有交车任务' }, + React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', gap: 6, cursor: 'not-allowed' } }, + React.createElement(Checkbox, { disabled: true, checked: false }), + React.createElement('span', { style: { color: '#999', fontSize: 12 } }, '已提交'))) + : React.createElement(Checkbox, { checked: checkedRowKeys.indexOf(row.key) !== -1, onChange: function(e) { onSelectRow(row, e.target.checked); } })), + React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.brand, disabled: true, style: styles.inputDisabled })), + React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.model, disabled: true, style: styles.inputDisabled })), + React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.plateNo || '-', disabled: true, style: styles.inputDisabled })), + React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.vin, disabled: true, style: styles.inputDisabled })), + React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.monthRent + '元', disabled: true, style: styles.inputDisabled })), + React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.serviceFee + '元', disabled: true, style: styles.inputDisabled })), + React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.deposit + '元', disabled: true, style: styles.inputDisabled })), + React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.remark || '-', disabled: true, style: styles.inputDisabled })) + ); + }) + ); + + var tableEl = React.createElement('div', { style: styles.tableWrap }, + React.createElement('table', { style: styles.table }, tableHeader, tableBody) + ); + + if (errors.vehicles) { + tableEl = React.createElement('div', null, + React.createElement('div', { style: Object.assign({}, styles.errMsg, { marginBottom: 8 }) }, errors.vehicles), + tableEl + ); + } + + return React.createElement('div', { style: styles.page }, + React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 } }, + React.createElement('div', { style: styles.breadcrumb }, + React.createElement('span', null, '业务管理'), + React.createElement('span', { style: styles.breadcrumbSep }, ' / '), + React.createElement('span', null, '交车任务'), + React.createElement('span', { style: styles.breadcrumbSep }, ' / '), + React.createElement('span', { style: { color: '#1890ff' } }, '编辑交车任务')), + React.createElement('span', { style: { color: '#1890ff', cursor: 'pointer', fontSize: 14 }, onClick: function() { setReqSpecOpen(true); } }, '查看需求说明')), + React.createElement('div', { style: styles.card }, + React.createElement('div', { style: styles.cardHeader }, React.createElement('span', { style: styles.cardTitle }, '交车任务')), + React.createElement('div', { style: styles.cardBody }, + formRow1, + formRow2, + tableEl)), + React.createElement('div', { style: { height: 60 } }), + reqSpecModalContent, + React.createElement('div', { style: styles.footer }, + React.createElement(Button, { type: 'primary', onClick: handleSubmit }, '提交'), + React.createElement(Button, { onClick: handleCancel }, '取消')) + ); +}; diff --git a/web端/登录.jsx b/web端/登录.jsx new file mode 100644 index 0000000..1da2bc7 --- /dev/null +++ b/web端/登录.jsx @@ -0,0 +1,446 @@ +// 【重要】必须使用 const Component 作为组件变量名 +// 参考 Arco Design Pro 登录页布局:左侧品牌区 + 右侧登录表单 +const Component = function () { + var antd = window.antd; + var useState = React.useState; + var useRef = React.useRef; + var useEffect = React.useEffect; + + var _state = useState({ username: '', password: '', remember: true, agreeTerms: false, phone: '', smsCode: '' }); + var formData = _state[0]; + var setFormData = _state[1]; + + var _mode = useState('account'); + var loginMode = _mode[0]; + var setLoginMode = _mode[1]; + + var _countdown = useState(0); + var countdown = _countdown[0]; + var setCountdown = _countdown[1]; + + var _hasRequestedCode = useState(false); + var hasRequestedCode = _hasRequestedCode[0]; + var setHasRequestedCode = _hasRequestedCode[1]; + + var timerRef = useRef(null); + + useEffect(function () { + return function () { + if (timerRef.current) { + clearInterval(timerRef.current); + } + }; + }, []); + + var handleChange = function (field, value) { + var next = {}; + next[field] = value; + setFormData(function (prev) { + return Object.assign({}, prev, next); + }); + }; + + var handleGetCode = function () { + if (countdown > 0) return; + setHasRequestedCode(true); + setCountdown(60); + timerRef.current = setInterval(function () { + setCountdown(function (prev) { + if (prev <= 1) { + clearInterval(timerRef.current); + timerRef.current = null; + return 0; + } + return prev - 1; + }); + }, 1000); + }; + + var handleSubmit = function () { + if (!formData.agreeTerms) { + antd.message.warning('请阅读并同意《用户服务协议》和《隐私政策》'); + return; + } + if (loginMode === 'phone') { + if (!formData.phone) { + antd.message.warning('请输入手机号'); + return; + } + if (!formData.smsCode) { + antd.message.warning('请输入验证码'); + return; + } + } else { + if (!formData.username) { + antd.message.warning('请输入用户名'); + return; + } + if (!formData.password) { + antd.message.warning('请输入密码'); + return; + } + } + antd.message.success('登录成功(原型演示)'); + if (window.$axure && typeof window.$axure.navigateToUrl === 'function') { + window.$axure.navigateToUrl('工作台'); + } else { + window.location.hash = '工作台'; + } + }; + + var switchToPhone = function (e) { + if (e && e.preventDefault) e.preventDefault(); + setLoginMode('phone'); + }; + var switchToAccount = function (e) { + if (e && e.preventDefault) e.preventDefault(); + setLoginMode('account'); + }; + + var styles = { + wrap: { + display: 'flex', + minHeight: '100vh', + width: '100%', + fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif', + backgroundColor: '#f5f5f5' + }, + left: { + flex: '1', + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + padding: '48px', + background: 'linear-gradient(135deg, #1d2129 0%, #2d3748 50%, #1d2129 100%)', + color: '#fff' + }, + leftContent: { + maxWidth: '400px', + textAlign: 'center' + }, + title: { + fontSize: '28px', + fontWeight: '600', + marginBottom: '16px', + letterSpacing: '0.5px' + }, + desc: { + fontSize: '14px', + opacity: 0.85, + lineHeight: 1.6 + }, + right: { + width: '480px', + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + padding: '48px', + backgroundColor: '#fff', + boxShadow: '-4px 0 24px rgba(0,0,0,0.06)' + }, + formCard: { + width: '100%', + maxWidth: '360px' + }, + formTitle: { + fontSize: '24px', + fontWeight: '600', + color: '#1d2129', + marginBottom: '8px', + textAlign: 'center' + }, + formSub: { + fontSize: '14px', + color: '#86909c', + marginBottom: '32px', + textAlign: 'center' + }, + formItem: { + marginBottom: '20px' + }, + label: { + display: 'block', + fontSize: '14px', + color: '#4e5969', + marginBottom: '8px' + }, + input: { + width: '100%', + height: '40px', + padding: '8px 12px', + fontSize: '14px', + border: '1px solid #e5e6eb', + borderRadius: '6px', + boxSizing: 'border-box', + outline: 'none' + }, + codeRow: { + display: 'flex', + gap: '8px', + alignItems: 'center' + }, + codeInput: { + flex: '1', + height: '40px', + padding: '8px 12px', + fontSize: '14px', + border: '1px solid #e5e6eb', + borderRadius: '6px', + boxSizing: 'border-box', + outline: 'none' + }, + getCodeBtn: { + flexShrink: 0, + height: '40px', + padding: '0 16px', + fontSize: '14px', + color: '#165dff', + backgroundColor: '#fff', + border: '1px solid #165dff', + borderRadius: '6px', + cursor: 'pointer' + }, + getCodeBtnDisabled: { + color: '#86909c', + borderColor: '#e5e6eb', + cursor: 'not-allowed' + }, + row: { + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: '24px' + }, + submitBtn: { + width: '100%', + height: '44px', + fontSize: '16px', + fontWeight: '500', + borderRadius: '6px' + }, + tabBar: { + display: 'flex', + marginBottom: '24px', + borderBottom: '1px solid #e5e6eb' + }, + tab: { + flex: 1, + textAlign: 'center', + padding: '12px 0', + fontSize: '15px', + color: '#86909c', + cursor: 'pointer', + borderBottom: '2px solid transparent', + marginBottom: '-1px' + }, + tabActive: { + color: '#165dff', + fontWeight: '500', + borderBottomColor: '#165dff' + } + }; + + return React.createElement( + React.Fragment, + null, + React.createElement('style', { + dangerouslySetInnerHTML: { + __html: '@media (max-width: 768px) { .login-right { width: 100% !important; min-width: 100% !important; } .login-left { min-height: 200px !important; } .login-wrap { flex-direction: column !important; } }' + } + }), + React.createElement( + 'div', + { style: styles.wrap, className: 'login-wrap' }, + React.createElement( + 'div', + { style: styles.left, className: 'login-left' }, + React.createElement( + 'div', + { style: styles.leftContent }, + React.createElement('h1', { style: styles.title }, '羚牛氢能'), + React.createElement('p', { style: styles.desc }, '数字化资产ONE-OS运管平台') + ) + ), + React.createElement( + 'div', + { style: styles.right, className: 'login-right' }, + React.createElement( + 'div', + { style: styles.formCard }, + React.createElement('h2', { style: styles.formTitle }, '欢迎登录'), + React.createElement( + 'div', + { style: styles.tabBar }, + React.createElement( + 'div', + { + style: loginMode === 'account' ? Object.assign({}, styles.tab, styles.tabActive) : styles.tab, + onClick: switchToAccount, + role: 'button', + tabIndex: 0 + }, + '账号密码登录' + ), + React.createElement( + 'div', + { + style: loginMode === 'phone' ? Object.assign({}, styles.tab, styles.tabActive) : styles.tab, + onClick: switchToPhone, + role: 'button', + tabIndex: 0 + }, + '手机号登录' + ) + ), + React.createElement('p', { style: styles.formSub }, loginMode === 'phone' ? '请输入手机号与验证码' : '请输入您的账号与密码'), + loginMode === 'phone' + ? React.createElement( + React.Fragment, + null, + React.createElement( + 'div', + { style: styles.formItem }, + React.createElement('label', { style: styles.label }, '手机号'), + React.createElement('input', { + style: styles.input, + placeholder: '请输入手机号', + value: formData.phone, + onChange: function (e) { + handleChange('phone', e.target.value); + } + }) + ), + React.createElement( + 'div', + { style: styles.formItem }, + React.createElement('label', { style: styles.label }, '验证码'), + React.createElement( + 'div', + { style: styles.codeRow }, + React.createElement('input', { + style: styles.codeInput, + placeholder: '请输入验证码', + value: formData.smsCode, + onChange: function (e) { + handleChange('smsCode', e.target.value); + } + }), + React.createElement( + 'button', + { + type: 'button', + style: countdown > 0 ? Object.assign({}, styles.getCodeBtn, styles.getCodeBtnDisabled) : styles.getCodeBtn, + disabled: countdown > 0, + onClick: handleGetCode + }, + countdown > 0 ? countdown + '秒后重新获取' : (hasRequestedCode ? '再次获取' : '获取验证码') + ) + ) + ) + ) + : React.createElement( + React.Fragment, + null, + React.createElement( + 'div', + { style: styles.formItem }, + React.createElement('label', { style: styles.label }, '用户名'), + React.createElement('input', { + style: styles.input, + placeholder: '请输入用户名', + value: formData.username, + onChange: function (e) { + handleChange('username', e.target.value); + } + }) + ), + React.createElement( + 'div', + { style: styles.formItem }, + React.createElement('label', { style: styles.label }, '密码'), + React.createElement('input', { + style: styles.input, + type: 'password', + placeholder: '请输入密码', + value: formData.password, + onChange: function (e) { + handleChange('password', e.target.value); + } + }) + ) + ), + React.createElement( + 'div', + { style: styles.row }, + React.createElement( + 'label', + { style: { display: 'flex', alignItems: 'center', cursor: 'pointer', fontSize: '14px', color: '#4e5969' } }, + React.createElement('input', { + type: 'checkbox', + checked: formData.remember, + onChange: function (e) { + handleChange('remember', e.target.checked); + }, + style: { marginRight: '8px' } + }), + '记住我' + ) + ), + React.createElement( + 'label', + { + style: { + display: 'flex', + alignItems: 'flex-start', + cursor: 'pointer', + fontSize: '14px', + color: '#4e5969', + marginBottom: '20px', + lineHeight: 1.5 + } + }, + React.createElement('input', { + type: 'checkbox', + checked: formData.agreeTerms, + onChange: function (e) { + handleChange('agreeTerms', e.target.checked); + }, + style: { marginRight: '8px', marginTop: '3px', flexShrink: 0 } + }), + React.createElement( + 'span', + null, + '阅读并同意', + React.createElement('a', { href: '#', style: { color: '#165dff', marginLeft: '2px' }, onClick: function (e) { e.preventDefault(); } }, '《用户服务协议》'), + '和', + React.createElement('a', { href: '#', style: { color: '#165dff' }, onClick: function (e) { e.preventDefault(); } }, '《隐私政策》') + ) + ), + antd && antd.Button + ? React.createElement(antd.Button, { + type: 'primary', + block: true, + size: 'large', + style: styles.submitBtn, + onClick: handleSubmit + }, '登 录') + : React.createElement( + 'button', + { + style: Object.assign({}, styles.submitBtn, { + backgroundColor: '#165dff', + color: '#fff', + border: 'none', + cursor: 'pointer' + }), + onClick: handleSubmit + }, + '登 录' + ) + ) + ) + ) + ); +}; diff --git a/web端/财务管理/提车首付款.jsx b/web端/财务管理/提车首付款.jsx new file mode 100644 index 0000000..0817308 --- /dev/null +++ b/web端/财务管理/提车首付款.jsx @@ -0,0 +1,1048 @@ +// 【重要】必须使用 const Component 作为组件变量名 +// 提车首付款 - 车辆资产管理后台(运维管理-财务管理-提车首付款) + +const Component = function() { + var useState = React.useState; + var useCallback = React.useCallback; + var useMemo = React.useMemo; + + // 分页 + var pageState = useState(1); + var currentPage = pageState[0]; + var setCurrentPage = pageState[1]; + var pageSizeState = useState(10); + var pageSize = pageSizeState[0]; + var setPageSize = pageSizeState[1]; + + // 筛选:表单输入值(用户填写) + var contractInputState = useState(''); + var contractInput = contractInputState[0]; + var setContractInput = contractInputState[1]; + var projectInputState = useState(''); + var projectInput = projectInputState[0]; + var setProjectInput = projectInputState[1]; + var customerInputState = useState(''); + var customerInput = customerInputState[0]; + var setCustomerInput = customerInputState[1]; + var statusInputState = useState([]); + var statusInput = statusInputState[0]; + var setStatusInput = statusInputState[1]; + // 可搜索选择器下拉展开:contract | project | customer | null + var filterDropdownOpenState = useState(null); + var filterDropdownOpen = filterDropdownOpenState[0]; + var setFilterDropdownOpen = filterDropdownOpenState[1]; + // 付款状态多选下拉展开 + var statusDropdownOpenState = useState(false); + var statusDropdownOpen = statusDropdownOpenState[0]; + var setStatusDropdownOpen = statusDropdownOpenState[1]; + // 筛选:已生效条件(点击查询后与列表联动) + var appliedFilterState = useState({ + contract: '', + project: '', + customer: '', + status: [] + }); + var appliedFilter = appliedFilterState[0]; + var setAppliedFilter = appliedFilterState[1]; + + // 查看页:当前视图 list | detail + var currentViewState = useState('list'); + var currentView = currentViewState[0]; + var setCurrentView = currentViewState[1]; + // 详情模式:view 查看 | payment 付款 + var detailModeState = useState('view'); + var detailMode = detailModeState[0]; + var setDetailMode = detailModeState[1]; + // 付款表单(付款模式可编辑) + var paymentFormState = useState({ + discountAmount: '', + discountReason: '', + hydrogenPaidAmount: '', + vehicleList: [], + hydrogenList: [], + violationList: [], + returnFeeList: [] + }); + var paymentForm = paymentFormState[0]; + var setPaymentForm = paymentFormState[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]; + // 当前查看/付款的账单 id + var viewingBillIdState = useState(null); + var viewingBillId = viewingBillIdState[0]; + var setViewingBillId = viewingBillIdState[1]; + // 审核状态覆盖(id -> draft|pending|auditing|completed|withdrawn|rejected)待提交|审核中|审核完成|撤回|审核驳回 + var approvalStatusMapState = useState({}); + var approvalStatusMap = approvalStatusMapState[0]; + var setApprovalStatusMap = approvalStatusMapState[1]; + // 撤回二次确认:待撤回的 row id + var withdrawConfirmIdState = useState(null); + var withdrawConfirmId = withdrawConfirmIdState[0]; + var setWithdrawConfirmId = withdrawConfirmIdState[1]; + // 查看需求明细弹窗 + var requirementDetailVisibleState = useState(false); + var requirementDetailVisible = requirementDetailVisibleState[0]; + var setRequirementDetailVisible = requirementDetailVisibleState[1]; + + // 需求明细文案(提车首付款) + var requirementDetailContent = '提车首付款\n\n1.面包屑:\n1.1.运维管理-财务管理-提车首付款\n\n2.筛选:\n2.1.合同编码:合同编码显示租赁合同编码;选择器,支持从输入框进行内容模糊搜索;\n2.2.项目名称:项目名称显示租赁合同对应项目名称;选择器,支持从输入框输入内容模糊搜索;\n2.3.客户名称:客户名称显示租赁合同对应客户名称,选择器,支持从输入框输入内容模糊搜索;\n2.4.付款状态:显示合同提车首付款付款状态,选择器,支持多选;选项为全部、部分付款、未付款、已付款;\n2.5.右侧为查询和重置按钮,筛选条件以且的模式进行筛选,点击重置清空条件;\n\n3.列表:\n3.1.列表下方为分页器,支持选择单页数据条数;\n3.2.列表字段按照以下排序:付款状态、合同编码、项目名称、客户名称、合同生效日期、首期应付金额、实付金额、减免金额、未付金额、备注、操作;\n3.3.审核状态:分为待提交、审核中、审核完成、审核驳回、撤回;\n 流程流转为:待提交(交车任务生效/完成收费补充保存) → 完成收费补充提交 → 审核中(可撤回,撤回后显示为撤回)→ 审核完成(最终节点审核完成) / 审核驳回(驳回显示为审核驳回);\n a.待提交:交车单交车成功时系统会自动生成提车首付款,状态为「待提交」;业务人员点击收费补充信息点击保存,状态也为「待提交」,同时操作可:查看、收费;\n b.审核中:提车首付款完成收费信息填写,点击提交审核,状态变更为「审核中」,同时操作可:查看、撤回;\n c.审核完成:提车首付款完成审核后,显示为审核完成,同时操作可:查看;\n d.审核驳回:提车首付款在任意审核节点被驳回时,显示为审核驳回,同时操作可:查看、收费;\n e.撤回:提车首付款在审核中时,发起人可进行撤回,撤回后可重新补充收费信息并再次提交;操作可:查看、收费;\n3.4.付款状态:显示付款状态,状态分为:已付款、部分付款、未付款;\n a.已付款:应付金额为0时,显示为已付款;\n b.部分付款:未付金额>0且<应付金额时,显示为部分付款;\n c.未付款:实付金额为0时,显示为未付款;\n3.5.合同编码:显示租赁合同-合同编码;\n3.6.项目名称:显示该项目租赁合同-项目名称;\n3.7.客户名称:显示该项目租赁合同-客户名称;\n3.8.合同生效日期:显示该项目租赁合同-生效日期,格式为YYYY-MM-DD;\n3.9.首期应付金额:显示该笔账单首期应付金额,精确至2位小数,计算方式为:应付款总额=车辆月租金总计+服务费总计+保证金总计+氢气预付款金额;\n3.10.实付金额:显示该笔账单实付金额,精确至2位小数,如未付则显示为0;如已上传付款信息,则显示金额,计算方式为:实付款金额=实付月租金总计+实付服务费总计+实付保证金总计+氢气实付款金额\n3.11.减免金额:显示该笔账单减免金额,精确至2位小数,如无则显示为0;\n3.12.未付金额:显示该笔账单未付金额,精确至2位小数,如无则显示为0;未付款金额=应付款总额-实付金额-减免金额\n3.13.减免原因:显示减免金额添加原因;\n3.14.操作:查看、收费、撤回;\n a.查看:点击查看进入查看页;\n b.收费:点击收费,录入收费信息;\n c.撤回:审核状态为审核中时,点击撤回,进行二次提示,确认后该条数据审核状态显示为撤回,可重新进行收费操作;'; + + // 模拟数据:合同编码、项目、客户选项(用于选择器展示,可选) + var contractOptions = useMemo(function() { + return [ + { value: 'HT-ZL-2025-001', label: 'HT-ZL-2025-001' }, + { value: 'HT-ZL-2025-002', label: 'HT-ZL-2025-002' }, + { value: 'HT-ZL-2025-003', label: 'HT-ZL-2025-003' }, + { value: 'HT-ZL-2024-088', label: 'HT-ZL-2024-088' }, + { value: 'HT-ZL-2024-099', label: 'HT-ZL-2024-099' } + ]; + }, []); + var projectOptions = useMemo(function() { + return [ + { value: '北京朝阳区租赁项目', label: '北京朝阳区租赁项目' }, + { value: '上海浦东车辆租赁', label: '上海浦东车辆租赁' }, + { value: '广州天河运营项目', label: '广州天河运营项目' }, + { value: '深圳南山首期项目', label: '深圳南山首期项目' } + ]; + }, []); + var customerOptions = useMemo(function() { + return [ + { value: '某某科技有限公司', label: '某某科技有限公司' }, + { value: '某某物流有限公司', label: '某某物流有限公司' }, + { value: '某某制造有限公司', label: '某某制造有限公司' }, + { value: '某某出行服务公司', label: '某某出行服务公司' } + ]; + }, []); + + // 模拟列表数据 + var mockList = useMemo(function() { + var list = []; + var statuses = ['paid', 'partial', 'unpaid']; + var approvalStatuses = ['draft', 'pending', 'auditing', 'completed', 'withdrawn', 'rejected']; + var contracts = ['HT-ZL-2025-001', 'HT-ZL-2025-002', 'HT-ZL-2025-003', 'HT-ZL-2024-088', 'HT-ZL-2024-099']; + var projects = ['北京朝阳区租赁项目', '上海浦东车辆租赁', '广州天河运营项目', '深圳南山首期项目']; + var customers = ['某某科技有限公司', '某某物流有限公司', '某某制造有限公司', '某某出行服务公司']; + for (var i = 1; i <= 28; i++) { + var status = statuses[i % 3]; + var approvalStatus = approvalStatuses[i % 6]; + var firstPayable = 15000 + i * 800; + var paid = 0; + var discount = 0; + if (status === 'paid') { + paid = firstPayable; + discount = i % 4 === 0 ? 200 : 0; + } else if (status === 'partial') { + paid = Math.floor(firstPayable * 0.5); + discount = i % 5 === 0 ? 100 : 0; + } + var unpaid = Math.max(0, firstPayable - paid - discount); + var remark = status !== 'unpaid' ? '已上传付款凭证,备注编号' + i : '-'; + var discountReason = discount > 0 ? (i % 4 === 0 ? '长期合作客户优惠' : '首期减免') : '-'; + list.push({ + id: 'FP' + i, + paymentStatus: status, + approvalStatus: approvalStatus, + contractCode: contracts[i % contracts.length], + projectName: projects[i % projects.length], + customerName: customers[i % customers.length], + contractEffectiveDate: '2025-' + (String((i % 12) + 1)).padStart(2, '0') + '-' + (String((i % 28) + 1)).padStart(2, '0'), + firstPayableAmount: firstPayable, + paidAmount: paid, + discountAmount: discount, + unpaidAmount: unpaid, + discountReason: discountReason, + remark: remark + }); + } + return list; + }, []); + + // 查看页 mock 数据(按 查看页面需求说明 结构) + var mockBillDetail = useMemo(function() { + return { + startDate: '2025-01-01', + endDate: '2025-01-31', + contractCode: 'HT-ZL-2025-001', + projectName: '北京朝阳区租赁项目', + customerName: '某某科技有限公司', + department: '运营部', + responsible: '张三', + paymentCycle: '先付(付款周期:1个月)', + discountTotal: '200.00', + discountReason: '长期合作客户优惠,首期账单减免部分金额' + }; + }, []); + var mockVehicleList = useMemo(function() { + return [ + { brand: '奔驰', model: 'E300L', plateNo: '京A12345', planDelivery: '2024-12-25', planDeliveryEnd: '2024-12-31', 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', planDeliveryEnd: '2024-12-26', 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 mockHydrogenData = useMemo(function() { + return { + paymentMethod: '预付', + prepaymentAmount: '3580.00', + paidAmount: '3200.00' + }; + }, []); + 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 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 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); + }); + 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]); + var vehicleTotals = useMemo(function() { + var monthlyRentTotal = parseFloat(vehicleBillTotals.monthlyRentTotal || 0); + var serviceFeeTotal = parseFloat(vehicleBillTotals.serviceFeeTotal || 0); + var depositTotal = parseFloat(vehicleBillTotals.depositTotal || 0); + var hydrogenTotal = parseFloat(mockHydrogenData.prepaymentAmount || 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 = payableTotal - paidTotal - discountTotal; + if (unpaidTotal < 0) unpaidTotal = 0; + return { + payableTotal: payableTotal.toFixed(2), + paidTotal: paidTotal.toFixed(2), + unpaidTotal: unpaidTotal.toFixed(2) + }; + }, [vehicleBillTotals, mockHydrogenData, mockViolationData, mockReturnFeeData, mockVehicleList, mockBillDetail]); + + // 筛选后的列表(使用已生效条件,点击查询后才与选择器联动) + var filteredList = useMemo(function() { + var list = mockList; + var af = appliedFilter; + // 合同编码选择器:与列表 contractCode 精确匹配 + if (af.contract && af.contract.trim()) { + var cf = af.contract.trim(); + list = list.filter(function(item) { + return (item.contractCode || '') === cf; + }); + } + // 项目名称选择器:与列表 projectName 精确匹配 + if (af.project && af.project.trim()) { + var pf = af.project.trim(); + list = list.filter(function(item) { + return (item.projectName || '') === pf; + }); + } + // 客户名称选择器:与列表 customerName 精确匹配 + if (af.customer && af.customer.trim()) { + var cuf = af.customer.trim(); + list = list.filter(function(item) { + return (item.customerName || '') === cuf; + }); + } + if (af.status && af.status.length > 0) { + list = list.filter(function(item) { + return af.status.indexOf(item.paymentStatus) >= 0; + }); + } + return list; + }, [mockList, appliedFilter]); + + var totalCount = filteredList.length; + var totalPages = Math.ceil(totalCount / pageSize) || 1; + var paginatedList = useMemo(function() { + var start = (currentPage - 1) * pageSize; + return filteredList.slice(start, start + pageSize); + }, [filteredList, currentPage, pageSize]); + + var getStatusText = function(status) { + if (status === 'paid') return '已付款'; + if (status === 'partial') return '部分付款'; + return '未付款'; + }; + var getStatusColor = function(status) { + if (status === 'paid') return '#52c41a'; + if (status === 'partial') return '#faad14'; + return '#ff4d4f'; + }; + + var formatAmount = function(num) { + var n = typeof num === 'number' ? num : parseFloat(num); + if (isNaN(n)) return '0.00'; + return n.toFixed(2); + }; + + var handleQuery = useCallback(function() { + setAppliedFilter({ + contract: contractInput, + project: projectInput, + customer: customerInput, + status: statusInput + }); + setCurrentPage(1); + }, [contractInput, projectInput, customerInput, statusInput]); + var handleReset = useCallback(function() { + setContractInput(''); + setProjectInput(''); + setCustomerInput(''); + setStatusInput([]); + setAppliedFilter({ contract: '', project: '', customer: '', status: [] }); + setCurrentPage(1); + }, []); + var getApprovalStatus = useCallback(function(row) { + return approvalStatusMap[row.id] != null ? approvalStatusMap[row.id] : row.approvalStatus; + }, [approvalStatusMap]); + var getApprovalStatusText = function(s) { + if (s === 'draft') return '待提交'; + if (s === 'pending' || s === 'auditing') return '审核中'; + if (s === 'completed') return '审核完成'; + if (s === 'withdrawn') return '撤回'; + if (s === 'rejected') return '审核驳回'; + return s || '-'; + }; + var handleView = useCallback(function(id) { + setViewingBillId(id); + setDetailMode('view'); + setCurrentView('detail'); + }, []); + var handlePayment = useCallback(function(id) { + setViewingBillId(id); + setDetailMode('payment'); + var vl = mockVehicleList.map(function(v) { + return { paidMonthlyRent: v.paidMonthlyRent || '', paidServiceFee: v.paidServiceFee || '', paidDeposit: v.paidDeposit || '' }; + }); + var hl = []; + 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 || '', + hydrogenPaidAmount: mockHydrogenData.paidAmount || '', + vehicleList: vl, + hydrogenList: hl, + violationList: viol, + returnFeeList: rfl + }); + setCurrentView('detail'); + }, []); + var handleBackToList = useCallback(function() { + setCurrentView('list'); + setDetailMode('view'); + setViewingBillId(null); + }, []); + var handleSave = useCallback(function() { + if (viewingBillId) { + setApprovalStatusMap(function(prev) { + var next = {}; + for (var k in prev) { if (prev.hasOwnProperty(k)) next[k] = prev[k]; } + next[viewingBillId] = 'draft'; + return next; + }); + } + alert('保存成功'); + handleBackToList(); + }, [viewingBillId, handleBackToList]); + var handleWithdraw = useCallback(function(rowId) { + setWithdrawConfirmId(rowId); + }, []); + var handleWithdrawConfirm = useCallback(function() { + if (withdrawConfirmId) { + setApprovalStatusMap(function(prev) { + var next = {}; + for (var k in prev) { if (prev.hasOwnProperty(k)) next[k] = prev[k]; } + next[withdrawConfirmId] = 'draft'; + return next; + }); + setWithdrawConfirmId(null); + alert('已撤回,可重新编辑后提交'); + } + }, [withdrawConfirmId]); + var handlePaymentFormChange = useCallback(function(section, index, field, value) { + setPaymentForm(function(prev) { + var next = { + discountAmount: prev.discountAmount, + discountReason: prev.discountReason, + hydrogenPaidAmount: prev.hydrogenPaidAmount, + 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; + if (field === 'hydrogenPaidAmount') next.hydrogenPaidAmount = value; + } else if (section === 'vehicle' && index >= 0 && index < next.vehicleList.length) { + next.vehicleList[index] = Object.assign({}, next.vehicleList[index]); + next.vehicleList[index][field] = value; + } else if (section === 'hydrogen' && index >= 0 && index < next.hydrogenList.length) { + next.hydrogenList[index] = Object.assign({}, next.hydrogenList[index]); + next.hydrogenList[index][field] = value; + } else if (section === 'violation' && index >= 0 && index < next.violationList.length) { + next.violationList[index] = Object.assign({}, next.violationList[index]); + next.violationList[index][field] = value; + } else if (section === 'returnFee' && index >= 0 && index < next.returnFeeList.length) { + next.returnFeeList[index] = Object.assign({}, next.returnFeeList[index]); + 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('减免原因'); + if (!paymentForm.hydrogenPaidAmount || String(paymentForm.hydrogenPaidAmount).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.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) { + alert('请填写必填项:' + errors.join('、')); + return; + } + if (viewingBillId) { + setApprovalStatusMap(function(prev) { + var next = {}; + for (var k in prev) { if (prev.hasOwnProperty(k)) next[k] = prev[k]; } + next[viewingBillId] = 'pending'; + return next; + }); + } + alert('提交成功'); + handleBackToList(); + }, [paymentForm, handleBackToList, viewingBillId]); + + // 样式 + var styles = { + page: { + padding: '24px', + backgroundColor: '#f5f5f5', + minHeight: '100vh', + fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif' + }, + breadcrumb: { + marginBottom: '16px', + fontSize: '14px', + color: '#666' + }, + breadcrumbSep: { color: '#bfbfbf', margin: '0 8px' }, + content: { + backgroundColor: '#fff', + borderRadius: '8px', + padding: '24px', + boxShadow: '0 1px 2px rgba(0,0,0,0.03)', + overflowX: 'auto' + }, + filterRow: { + display: 'flex', + flexWrap: 'nowrap', + gap: '16px', + marginBottom: '20px', + alignItems: 'center', + overflowX: 'auto' + }, + filterItem: { + display: 'flex', + alignItems: 'center', + gap: '8px' + }, + label: { fontSize: '14px', color: '#333', minWidth: '80px' }, + input: { + padding: '8px 12px', + border: '1px solid #d9d9d9', + borderRadius: '4px', + fontSize: '14px', + width: '200px' + }, + select: { + padding: '8px 12px', + border: '1px solid #d9d9d9', + borderRadius: '4px', + fontSize: '14px', + width: '200px' + }, + filterSelectWrap: { position: 'relative', width: '200px' }, + filterDropdown: { + position: 'absolute', + top: '100%', + left: 0, + right: 0, + marginTop: '2px', + maxHeight: '200px', + overflowY: 'auto', + backgroundColor: '#fff', + border: '1px solid #d9d9d9', + borderRadius: '4px', + boxShadow: '0 2px 8px rgba(0,0,0,0.15)', + zIndex: 10 + }, + filterDropdownItem: { + padding: '8px 12px', + fontSize: '14px', + cursor: 'pointer', + borderBottom: '1px solid #f0f0f0' + }, + selectMultiple: { + padding: '8px 12px', + border: '1px solid #d9d9d9', + borderRadius: '4px', + fontSize: '14px', + minWidth: '200px', + minHeight: '36px' + }, + statusMultiWrap: { position: 'relative', display: 'inline-block' }, + statusMultiDisplay: { + padding: '8px 12px', + border: '1px solid #d9d9d9', + borderRadius: '4px', + fontSize: '14px', + width: '200px', + backgroundColor: '#fff', + cursor: 'pointer', + minHeight: '36px', + boxSizing: 'border-box' + }, + statusDropdownPanel: { + position: 'absolute', + left: 0, + top: '100%', + marginTop: '2px', + width: '100%', + maxHeight: '220px', + overflowY: 'auto', + backgroundColor: '#fff', + border: '1px solid #d9d9d9', + borderRadius: '4px', + boxShadow: '0 2px 8px rgba(0,0,0,0.15)', + zIndex: 10 + }, + statusDropdownOption: { + padding: '8px 12px', + fontSize: '14px', + cursor: 'pointer', + borderBottom: '1px solid #f0f0f0' + }, + table: { width: '100%', borderCollapse: 'collapse', fontSize: '14px' }, + 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', + whiteSpace: 'nowrap' + }, + actionColTh: { + position: 'sticky', + right: 0, + backgroundColor: '#fafafa', + boxShadow: '-2px 0 4px rgba(0,0,0,0.06)', + zIndex: 1 + }, + actionColTd: { + position: 'sticky', + right: 0, + backgroundColor: '#fff', + boxShadow: '-2px 0 4px rgba(0,0,0,0.06)', + zIndex: 1 + }, + actionBtn: { + padding: '4px 12px', + marginRight: '8px', + borderRadius: '4px', + border: 'none', + cursor: 'pointer', + fontSize: '13px' + }, + viewBtn: { backgroundColor: '#e6f7ff', color: '#1890ff' }, + payBtn: { backgroundColor: '#f6ffed', color: '#52c41a' }, + withdrawBtn: { backgroundColor: '#fff7e6', color: '#fa8c16' }, + pagination: { + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + marginTop: '20px', + flexWrap: 'wrap', + gap: '12px' + }, + pageSizeSelect: { + padding: '6px 10px', + border: '1px solid #d9d9d9', + borderRadius: '4px', + fontSize: '13px' + }, + pageInfo: { fontSize: '14px', color: '#666' }, + pageHeader: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '16px', flexWrap: 'wrap', gap: '12px' }, + requirementLink: { color: '#1890ff', cursor: 'pointer', fontSize: '14px', textDecoration: 'none' }, + requirementModalBody: { maxHeight: '70vh', overflowY: 'auto', whiteSpace: 'pre-wrap', fontSize: '14px', lineHeight: 1.6, color: '#333', padding: '0 4px' }, + queryBtn: { + padding: '8px 20px', + backgroundColor: '#1890ff', + color: '#fff', + border: 'none', + borderRadius: '4px', + cursor: 'pointer', + fontSize: '14px' + }, + resetBtn: { + padding: '8px 20px', + backgroundColor: '#fff', + color: '#666', + border: '1px solid #d9d9d9', + borderRadius: '4px', + cursor: 'pointer', + fontSize: '14px' + }, + // 查看页样式(按 查看页面需求说明) + detailPage: { padding: '24px', backgroundColor: '#f5f5f5', minHeight: '100vh', width: '100%', boxSizing: 'border-box' }, + backBtn: { marginBottom: '16px', padding: '8px 16px', backgroundColor: '#fff', border: '1px solid #d9d9d9', borderRadius: '4px', cursor: 'pointer', fontSize: '14px' }, + detailContent: { display: 'flex', flexDirection: 'column', gap: '24px', width: '100%' }, + detailCard: { backgroundColor: '#fff', borderRadius: '8px', padding: '24px', boxShadow: '0 1px 2px rgba(0,0,0,0.03)', width: '100%', boxSizing: 'border-box' }, + detailCardTitle: { fontSize: '16px', fontWeight: 600, marginBottom: '16px', paddingBottom: '12px', borderBottom: '1px solid #f0f0f0' }, + detailRow: { display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '24px', marginBottom: '12px', fontSize: '14px' }, + detailItem: {}, + detailItemInline: { display: 'flex', alignItems: 'center' }, + detailItemInputRight: { display: 'flex', alignItems: 'center', justifyContent: 'space-between' }, + detailLabel: { color: '#666', marginRight: '8px' }, + amountInputWrap: { display: 'flex', alignItems: 'center', gap: '8px' }, + amountInput: { padding: '8px 12px', border: '1px solid #d9d9d9', borderRadius: '4px', fontSize: '14px', width: '120px' }, + amountSuffix: { color: '#666', fontSize: '14px' }, + detailTextarea: { padding: '8px 12px', border: '1px solid #d9d9d9', borderRadius: '4px', fontSize: '14px', width: '100%', minWidth: '200px', minHeight: '60px', resize: 'vertical' }, + vehicleTable: { width: '100%', borderCollapse: 'collapse', fontSize: '14px' }, + hydrogenSummary: { display: 'flex', gap: '48px', marginBottom: '16px', fontSize: '14px' }, + hydrogenSummaryItem: { color: '#333' }, + photoThumb: { width: '40px', height: '40px', objectFit: 'cover', borderRadius: '4px', cursor: 'pointer', marginRight: '4px', verticalAlign: 'middle' }, + attachmentLink: { color: '#1890ff', cursor: 'pointer', textDecoration: 'none', marginRight: '8px', fontSize: '14px' }, + amountLink: { color: '#1890ff', cursor: 'pointer', textDecoration: 'none' }, + photoViewerOverlay: { position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, backgroundColor: 'rgba(0,0,0,0.85)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1001 }, + photoViewerContent: { position: 'relative', textAlign: 'center' }, + photoViewerImg: { maxWidth: '90vw', maxHeight: '85vh', objectFit: 'contain' }, + photoViewerBtn: { position: 'absolute', top: '50%', marginTop: '-20px', padding: '10px 16px', backgroundColor: 'rgba(255,255,255,0.9)', border: 'none', borderRadius: '4px', cursor: 'pointer', fontSize: '14px' }, + photoViewerPrev: { left: '20px' }, + photoViewerNext: { right: '20px' }, + photoViewerClose: { position: 'absolute', top: '-40px', right: '0', padding: '8px 16px', backgroundColor: 'rgba(255,255,255,0.9)', border: 'none', borderRadius: '4px', cursor: 'pointer', fontSize: '14px' }, + photoViewerCounter: { color: '#fff', marginTop: '16px', fontSize: '14px' }, + popover: { 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 }, + popoverCard: { backgroundColor: '#fff', borderRadius: '8px', padding: '24px', maxWidth: '90%', maxHeight: '80vh', overflow: 'auto' }, + popoverHeader: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '16px', whiteSpace: 'nowrap' }, + popoverTitle: { fontSize: '16px', fontWeight: 600, whiteSpace: 'nowrap' }, + popoverClose: { padding: '4px 12px', backgroundColor: '#f5f5f5', border: 'none', borderRadius: '4px', cursor: 'pointer' }, + popoverTable: { width: '100%', borderCollapse: 'collapse', fontSize: '14px' }, + popoverTh: { textAlign: 'left', padding: '12px 16px', backgroundColor: '#fafafa', borderBottom: '1px solid #f0f0f0', fontWeight: 600, color: '#333', whiteSpace: 'nowrap' }, + popoverTd: { padding: '12px 16px', borderBottom: '1px solid #f0f0f0', color: '#333', whiteSpace: 'nowrap' }, + modalFooter: { marginTop: '24px', textAlign: 'right', display: 'flex', justifyContent: 'center', gap: '12px' } + }; + + // 查看页:按 查看页面需求说明 渲染(租赁账单详情) + if (currentView === 'detail') { + return ( + React.createElement('div', { style: styles.detailPage }, + React.createElement('div', { style: Object.assign({}, styles.breadcrumb, { marginBottom: '16px' }) }, + React.createElement('span', null, '财务管理'), + React.createElement('span', { style: styles.breadcrumbSep }, '>'), + React.createElement('span', null, '提车首付款'), + React.createElement('span', { style: styles.breadcrumbSep }, '>'), + React.createElement('span', null, detailMode === 'payment' ? '收费' : '查看') + ), + React.createElement('div', { style: styles.detailContent }, + React.createElement('div', { style: styles.detailCard }, + React.createElement('div', { style: styles.detailCardTitle }, '账单信息'), + React.createElement('div', { style: styles.detailRow }, + React.createElement('div', { style: styles.detailItem }, React.createElement('span', { style: styles.detailLabel }, '账单开始日期:'), React.createElement('span', null, mockBillDetail.startDate)), + React.createElement('div', { style: styles.detailItem }, React.createElement('span', { style: styles.detailLabel }, '账单结束日期:'), React.createElement('span', null, mockBillDetail.endDate)) + ), + React.createElement('div', { style: styles.detailRow }, + React.createElement('div', { style: styles.detailItem }, React.createElement('span', { style: styles.detailLabel }, '合同编码:'), React.createElement('span', null, mockBillDetail.contractCode)), + React.createElement('div', { style: styles.detailItem }, React.createElement('span', { style: styles.detailLabel }, '项目名称:'), React.createElement('span', null, mockBillDetail.projectName)), + React.createElement('div', { style: styles.detailItem }, React.createElement('span', { style: styles.detailLabel }, '客户名称:'), React.createElement('span', null, mockBillDetail.customerName)) + ), + React.createElement('div', { style: styles.detailRow }, + React.createElement('div', { style: styles.detailItem }, React.createElement('span', { style: styles.detailLabel }, '业务部门:'), React.createElement('span', null, mockBillDetail.department)), + React.createElement('div', { style: styles.detailItem }, React.createElement('span', { style: styles.detailLabel }, '业务负责人:'), React.createElement('span', null, mockBillDetail.responsible)), + React.createElement('div', { style: styles.detailItem }, React.createElement('span', { style: styles.detailLabel }, '付款周期:'), React.createElement('span', null, mockBillDetail.paymentCycle)) + ), + React.createElement('div', { style: styles.detailRow }, + React.createElement('div', { style: styles.detailItem }, React.createElement('span', { style: styles.detailLabel }, '首期应付金额:'), React.createElement('span', { style: { color: '#1890ff', fontWeight: 600 } }, vehicleTotals.payableTotal + ' 元')), + React.createElement('div', { style: styles.detailItem }, React.createElement('span', { style: styles.detailLabel }, '实付款总额:'), React.createElement('span', { style: { color: '#52c41a', fontWeight: 600 } }, vehicleTotals.paidTotal + ' 元')), + React.createElement('div', { style: Object.assign({}, styles.detailItem, styles.detailItemInline) }, React.createElement('span', { style: styles.detailLabel }, '减免金额:'), detailMode === 'payment' ? React.createElement('div', { style: styles.amountInputWrap }, React.createElement('input', { type: 'text', style: styles.amountInput, value: paymentForm.discountAmount, onChange: function(e) { handlePaymentFormChange('bill', -1, 'discountAmount', e.target.value); }, placeholder: '0.00' }), React.createElement('span', { style: styles.amountSuffix }, '元')) : React.createElement('span', { style: { fontWeight: 600 } }, mockBillDetail.discountTotal + ' 元')), + React.createElement('div', { style: styles.detailItem }, React.createElement('span', { style: styles.detailLabel }, '未付款总额:'), React.createElement('span', { style: { color: '#ff4d4f', fontWeight: 600 } }, vehicleTotals.unpaidTotal + ' 元')) + ), + React.createElement('div', { style: styles.detailRow }, + React.createElement('div', { style: Object.assign({}, styles.detailItem, { gridColumn: '1 / -1' }) }, React.createElement('span', { style: styles.detailLabel }, '减免原因:'), detailMode === 'payment' ? React.createElement('textarea', { style: styles.detailTextarea, value: paymentForm.discountReason, onChange: function(e) { handlePaymentFormChange('bill', -1, 'discountReason', e.target.value); }, placeholder: '请输入减免原因' }) : React.createElement('span', null, mockBillDetail.discountReason || '-')) + ) + ), + React.createElement('div', { style: styles.detailCard }, + React.createElement('div', { style: styles.detailCardTitle }, '车辆账单'), + React.createElement('table', { style: styles.vehicleTable }, + 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 }, '服务费'), React.createElement('th', { style: styles.th }, '实付服务费'), React.createElement('th', { style: styles.th }, '保证金'), React.createElement('th', { style: styles.th }, '实付保证金') + )), + React.createElement('tbody', null, mockVehicleList.map(function(v, idx) { + var pf = paymentForm.vehicleList[idx] || {}; + return React.createElement('tr', { key: v.plateNo }, + React.createElement('td', { style: styles.td }, v.brand), React.createElement('td', { style: styles.td }, v.model), React.createElement('td', { style: styles.td }, v.plateNo), + React.createElement('td', { style: styles.td }, (v.planDelivery && v.planDeliveryEnd) ? (v.planDelivery + '至' + v.planDeliveryEnd) : (v.planDelivery || '未设置')), React.createElement('td', { style: styles.td }, v.actualDelivery || '未交车'), React.createElement('td', { style: styles.td }, v.billStart || '未设置'), React.createElement('td', { style: styles.td }, v.billEnd || '未设置'), + React.createElement('td', { style: styles.td }, v.monthlyRent), + React.createElement('td', { style: styles.td }, detailMode === 'payment' ? React.createElement('div', { style: styles.amountInputWrap }, React.createElement('input', { type: 'text', style: styles.amountInput, value: pf.paidMonthlyRent || '', onChange: function(e) { handlePaymentFormChange('vehicle', idx, 'paidMonthlyRent', e.target.value); }, placeholder: '0.00' }), React.createElement('span', { style: styles.amountSuffix }, '元')) : (parseFloat(v.paidMonthlyRent || 0)).toFixed(2)), + React.createElement('td', { style: styles.td }, detailMode === 'payment' ? v.serviceFee : React.createElement('span', { style: styles.amountLink, onClick: function() { setPopover({ type: 'service', data: v.serviceItems }); } }, v.serviceFee)), + React.createElement('td', { style: styles.td }, detailMode === 'payment' ? React.createElement('div', { style: styles.amountInputWrap }, React.createElement('input', { type: 'text', style: styles.amountInput, value: pf.paidServiceFee || '', onChange: function(e) { handlePaymentFormChange('vehicle', idx, 'paidServiceFee', e.target.value); }, placeholder: '0.00' }), React.createElement('span', { style: styles.amountSuffix }, '元')) : (parseFloat(v.paidServiceFee || 0)).toFixed(2)), + React.createElement('td', { style: styles.td }, v.deposit), + React.createElement('td', { style: styles.td }, detailMode === 'payment' ? React.createElement('div', { style: styles.amountInputWrap }, React.createElement('input', { type: 'text', style: styles.amountInput, value: pf.paidDeposit || '', onChange: function(e) { handlePaymentFormChange('vehicle', idx, 'paidDeposit', e.target.value); }, placeholder: '0.00' }), React.createElement('span', { style: styles.amountSuffix }, '元')) : (parseFloat(v.paidDeposit || 0)).toFixed(2)) + ); + })), + React.createElement('tfoot', null, React.createElement('tr', { style: { backgroundColor: '#fafafa', fontWeight: 600 } }, React.createElement('td', { style: styles.td, colSpan: 7 }, '总计'), React.createElement('td', { style: styles.td }, vehicleBillTotals.monthlyRentTotal), React.createElement('td', { style: styles.td }, vehicleBillTotals.paidRentTotal), React.createElement('td', { style: styles.td }, vehicleBillTotals.serviceFeeTotal), React.createElement('td', { style: styles.td }, vehicleBillTotals.paidServiceFeeTotal), React.createElement('td', { style: styles.td }, vehicleBillTotals.depositTotal), React.createElement('td', { style: styles.td }, vehicleBillTotals.paidDepositTotal))) + ) + ), + React.createElement('div', { style: styles.detailCard }, + React.createElement('div', { style: styles.detailCardTitle }, '氢费账单'), + React.createElement('div', { style: styles.detailRow }, + React.createElement('div', { style: styles.detailItem }, React.createElement('span', { style: styles.detailLabel }, '氢费付款方式:'), React.createElement('span', null, mockHydrogenData.paymentMethod || '-')), + React.createElement('div', { style: styles.detailItem }, React.createElement('span', { style: styles.detailLabel }, '氢气预付款:'), React.createElement('span', null, (parseFloat(mockHydrogenData.prepaymentAmount || 0)).toFixed(2) + ' 元')), + React.createElement('div', { style: Object.assign({}, styles.detailItem, styles.detailItemInline) }, React.createElement('span', { style: styles.detailLabel }, '氢气实付款金额:'), detailMode === 'payment' ? React.createElement('div', { style: styles.amountInputWrap }, React.createElement('input', { type: 'text', style: styles.amountInput, value: paymentForm.hydrogenPaidAmount, onChange: function(e) { handlePaymentFormChange('bill', -1, 'hydrogenPaidAmount', e.target.value); }, placeholder: '0.00' }), React.createElement('span', { style: styles.amountSuffix }, '元')) : React.createElement('span', null, (parseFloat(mockHydrogenData.paidAmount || 0)).toFixed(2) + ' 元')) + ) + ) + ), + React.createElement('div', { style: { marginTop: '24px', textAlign: 'center', display: 'flex', justifyContent: 'center', gap: '16px' } }, + detailMode === 'payment' && React.createElement('button', { type: 'button', style: Object.assign({}, styles.backBtn, { backgroundColor: '#1890ff', color: '#fff', border: 'none' }), onClick: handleSubmit }, '提交审核'), + detailMode === 'payment' && React.createElement('button', { type: 'button', style: styles.backBtn, onClick: handleSave }, '保存'), + React.createElement('button', { type: 'button', style: styles.backBtn, onClick: handleBackToList }, '取消') + ), + photoViewer.visible && photoViewer.photos.length > 0 && React.createElement('div', { style: styles.photoViewerOverlay, onClick: function() { setPhotoViewer({ visible: false, photos: [], currentIndex: 0 }); } }, + React.createElement('div', { style: styles.photoViewerContent, onClick: function(e) { e.stopPropagation(); } }, + React.createElement('button', { style: Object.assign({}, styles.photoViewerBtn, styles.photoViewerClose), onClick: function() { setPhotoViewer({ visible: false, photos: [], currentIndex: 0 }); } }, '关闭'), + photoViewer.currentIndex > 0 ? React.createElement('button', { style: Object.assign({}, styles.photoViewerBtn, styles.photoViewerPrev), onClick: function() { setPhotoViewer({ visible: true, photos: photoViewer.photos, currentIndex: photoViewer.currentIndex - 1 }); } }, '上一张') : null, + React.createElement('img', { style: styles.photoViewerImg, src: photoViewer.photos[photoViewer.currentIndex].replace('80/80', '600/600'), alt: '' }), + photoViewer.currentIndex < photoViewer.photos.length - 1 ? React.createElement('button', { style: Object.assign({}, styles.photoViewerBtn, styles.photoViewerNext), onClick: function() { setPhotoViewer({ visible: true, photos: photoViewer.photos, currentIndex: photoViewer.currentIndex + 1 }); } }, '下一张') : null, + React.createElement('div', { style: styles.photoViewerCounter }, (photoViewer.currentIndex + 1) + ' / ' + photoViewer.photos.length) + ) + ), + (popover.type === 'service' && React.createElement('div', { style: styles.popover, onClick: function() { setPopover({ type: null, data: null }); } }, + React.createElement('div', { style: styles.popoverCard, onClick: function(e) { e.stopPropagation(); } }, + React.createElement('div', { style: styles.popoverHeader }, React.createElement('span', { style: styles.popoverTitle }, '服务费明细'), React.createElement('button', { style: styles.popoverClose, onClick: function() { setPopover({ type: null, data: 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, popover.data ? popover.data.map(function(s, i) { return React.createElement('tr', { key: i }, React.createElement('td', { style: styles.td }, s.name), React.createElement('td', { style: styles.td }, s.price), React.createElement('td', { style: styles.td }, s.effectiveDate)); }) : null)) + ) + )) + ) + ); + } + + + return ( + React.createElement('div', { style: styles.page }, + React.createElement('div', { style: styles.pageHeader }, + React.createElement('div', { style: styles.breadcrumb }, + React.createElement('span', null, '财务管理'), + React.createElement('span', { style: styles.breadcrumbSep }, '>'), + React.createElement('span', null, '提车首付款') + ), + React.createElement('a', { style: styles.requirementLink, onClick: function() { setRequirementDetailVisible(true); }, role: 'button', tabIndex: 0, onKeyDown: function(e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setRequirementDetailVisible(true); } } }, '查看需求明细') + ), + React.createElement('div', { style: styles.content }, + // 筛选:合同编码、项目名称、客户名称(选择器+输入搜索)、付款状态(多选);右侧 查询、重置 + React.createElement('div', { style: styles.filterRow }, + React.createElement('div', { style: styles.filterItem }, + React.createElement('span', { style: styles.label }, '合同编码:'), + React.createElement('div', { style: styles.filterSelectWrap }, + React.createElement('input', { + style: styles.input, + placeholder: '请输入合同编码', + value: contractInput, + onChange: function(e) { setContractInput(e.target.value); }, + onFocus: function() { setFilterDropdownOpen('contract'); }, + onBlur: function() { setTimeout(function() { setFilterDropdownOpen(null); }, 200); } + }), + filterDropdownOpen === 'contract' && React.createElement('div', { style: styles.filterDropdown }, + contractOptions.filter(function(opt) { + var kw = (contractInput || '').toLowerCase(); + if (!kw) return true; + return (opt.label || '').toLowerCase().indexOf(kw) >= 0 || (opt.value || '').toLowerCase().indexOf(kw) >= 0; + }).map(function(opt) { + return React.createElement('div', { + key: opt.value, + style: styles.filterDropdownItem, + onMouseDown: function(e) { e.preventDefault(); setContractInput(opt.value); setFilterDropdownOpen(null); } + }, opt.label); + }) + ) + ) + ), + React.createElement('div', { style: styles.filterItem }, + React.createElement('span', { style: styles.label }, '项目名称:'), + React.createElement('div', { style: styles.filterSelectWrap }, + React.createElement('input', { + style: styles.input, + placeholder: '请输入项目名称', + value: projectInput, + onChange: function(e) { setProjectInput(e.target.value); }, + onFocus: function() { setFilterDropdownOpen('project'); }, + onBlur: function() { setTimeout(function() { setFilterDropdownOpen(null); }, 200); } + }), + filterDropdownOpen === 'project' && React.createElement('div', { style: styles.filterDropdown }, + projectOptions.filter(function(opt) { + var kw = (projectInput || '').toLowerCase(); + if (!kw) return true; + return (opt.label || '').toLowerCase().indexOf(kw) >= 0 || (opt.value || '').toLowerCase().indexOf(kw) >= 0; + }).map(function(opt) { + return React.createElement('div', { + key: opt.value, + style: styles.filterDropdownItem, + onMouseDown: function(e) { e.preventDefault(); setProjectInput(opt.value); setFilterDropdownOpen(null); } + }, opt.label); + }) + ) + ) + ), + React.createElement('div', { style: styles.filterItem }, + React.createElement('span', { style: styles.label }, '客户名称:'), + React.createElement('div', { style: styles.filterSelectWrap }, + React.createElement('input', { + style: styles.input, + placeholder: '请输入客户名称', + value: customerInput, + onChange: function(e) { setCustomerInput(e.target.value); }, + onFocus: function() { setFilterDropdownOpen('customer'); }, + onBlur: function() { setTimeout(function() { setFilterDropdownOpen(null); }, 200); } + }), + filterDropdownOpen === 'customer' && React.createElement('div', { style: styles.filterDropdown }, + customerOptions.filter(function(opt) { + var kw = (customerInput || '').toLowerCase(); + if (!kw) return true; + return (opt.label || '').toLowerCase().indexOf(kw) >= 0 || (opt.value || '').toLowerCase().indexOf(kw) >= 0; + }).map(function(opt) { + return React.createElement('div', { + key: opt.value, + style: styles.filterDropdownItem, + onMouseDown: function(e) { e.preventDefault(); setCustomerInput(opt.value); setFilterDropdownOpen(null); } + }, opt.label); + }) + ) + ) + ), + React.createElement('div', { style: styles.filterItem }, + React.createElement('span', { style: styles.label }, '付款状态:'), + React.createElement('div', { + style: styles.statusMultiWrap, + tabIndex: 0, + onBlur: function() { setTimeout(function() { setStatusDropdownOpen(false); }, 200); } + }, + React.createElement('div', { + style: styles.statusMultiDisplay, + onClick: function() { setStatusDropdownOpen(!statusDropdownOpen); } + }, statusInput.length === 0 ? '全部' : statusInput.map(function(s) { return getStatusText(s); }).join('、')), + statusDropdownOpen ? React.createElement('div', { style: styles.statusDropdownPanel }, + ['partial', 'unpaid', 'paid'].map(function(s) { + var checked = statusInput.indexOf(s) >= 0; + return React.createElement('div', { + key: s, + style: styles.statusDropdownOption, + onMouseDown: function(e) { + e.preventDefault(); + var idx = statusInput.indexOf(s); + var next = statusInput.slice(); + if (idx >= 0) next.splice(idx, 1); + else next.push(s); + setStatusInput(next); + } + }, React.createElement('label', { style: { cursor: 'pointer', display: 'flex', alignItems: 'center', gap: '6px' } }, + React.createElement('input', { type: 'checkbox', checked: checked, readOnly: true }), + getStatusText(s) + )); + }) + ) : null + ) + ), + React.createElement('div', { style: { display: 'flex', gap: '8px', marginLeft: 'auto' } }, + React.createElement('button', { type: 'button', style: styles.queryBtn, onClick: handleQuery }, '查询'), + React.createElement('button', { type: 'button', style: styles.resetBtn, onClick: handleReset }, '重置') + ) + ), + // 列表:审核状态、付款状态、合同编码、项目名称、客户名称、合同生效日期、首期应付金额、实付款总额、减免金额、未付款总额、减免原因、操作(固定右侧) + 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('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: Object.assign({}, styles.th, styles.actionColTh) }, '操作') + ) + ), + React.createElement('tbody', null, + paginatedList.map(function(row) { + var approvalStatus = getApprovalStatus(row); + var showPay = approvalStatus === 'draft' || approvalStatus === 'withdrawn' || approvalStatus === 'rejected'; + var showWithdraw = approvalStatus === 'auditing' || approvalStatus === 'pending'; + return React.createElement('tr', { key: row.id }, + React.createElement('td', { style: styles.td }, getApprovalStatusText(approvalStatus)), + React.createElement('td', { style: styles.td }, + React.createElement('span', { style: { color: getStatusColor(row.paymentStatus) } }, getStatusText(row.paymentStatus)) + ), + React.createElement('td', { style: styles.td }, row.contractCode), + React.createElement('td', { style: styles.td }, row.projectName), + React.createElement('td', { style: styles.td }, row.customerName), + React.createElement('td', { style: styles.td }, row.contractEffectiveDate), + React.createElement('td', { style: styles.td }, formatAmount(row.firstPayableAmount)), + React.createElement('td', { style: styles.td }, formatAmount(row.paidAmount)), + React.createElement('td', { style: styles.td }, formatAmount(row.discountAmount)), + React.createElement('td', { style: styles.td }, formatAmount(row.unpaidAmount)), + React.createElement('td', { style: styles.td }, row.discountReason || '-'), + React.createElement('td', { style: Object.assign({}, styles.td, styles.actionColTd) }, + React.createElement('button', { + type: 'button', + style: Object.assign({}, styles.actionBtn, styles.viewBtn), + onClick: function() { handleView(row.id); } + }, '查看'), + showPay && React.createElement('button', { + type: 'button', + style: Object.assign({}, styles.actionBtn, styles.payBtn), + onClick: function() { handlePayment(row.id); } + }, '收费'), + showWithdraw && React.createElement('button', { + type: 'button', + style: Object.assign({}, styles.actionBtn, styles.withdrawBtn), + onClick: function() { handleWithdraw(row.id); } + }, '撤回') + ) + ); + }) + ) + ), + // 分页:支持选择单页数据条数 + React.createElement('div', { style: styles.pagination }, + React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: '8px' } }, + React.createElement('span', { style: styles.pageInfo }, '共 ' + totalCount + ' 条'), + React.createElement('select', { + style: styles.pageSizeSelect, + value: pageSize, + onChange: function(e) { + setPageSize(Number(e.target.value)); + setCurrentPage(1); + } + }, + React.createElement('option', { value: 10 }, '10 条/页'), + React.createElement('option', { value: 20 }, '20 条/页'), + React.createElement('option', { value: 50 }, '50 条/页') + ) + ), + React.createElement('div', { style: { display: 'flex', gap: '8px', alignItems: 'center' } }, + React.createElement('button', { + type: 'button', + style: Object.assign({}, styles.actionBtn, { padding: '6px 12px' }), + disabled: currentPage <= 1, + onClick: function() { setCurrentPage(currentPage - 1); } + }, '上一页'), + React.createElement('span', { style: styles.pageInfo }, currentPage + ' / ' + totalPages), + React.createElement('button', { + type: 'button', + style: Object.assign({}, styles.actionBtn, { padding: '6px 12px' }), + disabled: currentPage >= totalPages, + onClick: function() { setCurrentPage(currentPage + 1); } + }, '下一页') + ) + ), + withdrawConfirmId && React.createElement('div', { style: styles.popover, onClick: function() { setWithdrawConfirmId(null); } }, + React.createElement('div', { style: styles.popoverCard, onClick: function(e) { e.stopPropagation(); } }, + React.createElement('div', { style: styles.popoverTitle }, '确认撤回?'), + React.createElement('p', { style: { margin: '16px 0', fontSize: '14px', color: '#666' } }, '确认后将撤回该数据,审核状态变为撤回。'), + React.createElement('div', { style: styles.modalFooter }, + React.createElement('button', { type: 'button', style: styles.backBtn, onClick: function() { setWithdrawConfirmId(null); } }, '取消'), + React.createElement('button', { type: 'button', style: Object.assign({}, styles.backBtn, { backgroundColor: '#fa8c16', color: '#fff', border: 'none' }), onClick: handleWithdrawConfirm }, '确认') + ) + ) + ), + requirementDetailVisible && React.createElement('div', { style: styles.popover, onClick: function() { setRequirementDetailVisible(false); } }, + React.createElement('div', { style: Object.assign({}, styles.popoverCard, { maxWidth: '720px', width: '90%' }), onClick: function(e) { e.stopPropagation(); } }, + React.createElement('div', { style: styles.popoverHeader }, + React.createElement('span', { style: styles.popoverTitle }, '需求明细'), + React.createElement('button', { style: styles.popoverClose, onClick: function() { setRequirementDetailVisible(false); } }, '关闭') + ), + React.createElement('div', { style: styles.requirementModalBody }, requirementDetailContent) + ) + ) + ) + ) + ); +}; + +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端/车辆租赁合同/.DS_Store b/web端/车辆租赁合同/.DS_Store new file mode 100644 index 0000000..20c35fe Binary files /dev/null and b/web端/车辆租赁合同/.DS_Store differ diff --git a/web端/车辆租赁合同/2026-02-27租赁合同修改点.zip b/web端/车辆租赁合同/2026-02-27租赁合同修改点.zip new file mode 100644 index 0000000..5fe66bb Binary files /dev/null and b/web端/车辆租赁合同/2026-02-27租赁合同修改点.zip differ diff --git a/web端/车辆租赁合同/合同续签.jsx b/web端/车辆租赁合同/合同续签.jsx new file mode 100644 index 0000000..88e6c5a --- /dev/null +++ b/web端/车辆租赁合同/合同续签.jsx @@ -0,0 +1,690 @@ +// 【重要】必须使用 const Component 作为组件变量名 - Axhub 产品原型 +// 车辆管理系统 - 合同续签模块(布局与新增租赁合同一致,合同编码后增加续签自原合同编号,IE11+ 兼容,采用 antd) + +const Component = function() { + var antd = window.antd; + var Input = antd.Input; + var Select = antd.Select; + var Button = antd.Button; + var DatePicker = antd.DatePicker; + var Modal = antd.Modal; + var message = antd.message; + var Option = Select.Option; + + var customerList = [ + { id: '1', name: '嘉兴某某物流有限公司', creditCode: '91330400MA2XXXXX1', address: '浙江省嘉兴市南湖区科技大道1号', contact: '张三', phone: '13800138001', email: 'zhangsan@example.com', companyName: '嘉兴某某物流有限公司', companyPhone: '0571-88888888', mailingAddress: '浙江省嘉兴市南湖区科技大道1号', bank: '中国工商银行嘉兴分行', bankAccount: '6222021234567890123', taxId: '91330400MA2XXXXX1' }, + { id: '2', name: '上海某某运输公司', creditCode: '91310000MA2XXXXX2', address: '上海市浦东新区张江高科技园区', contact: '李四', phone: '13800138002', email: 'lisi@example.com', companyName: '上海某某运输公司', companyPhone: '021-66666666', mailingAddress: '上海市浦东新区张江高科技园区', bank: '中国建设银行上海分行', bankAccount: '6217001234567890123', taxId: '91310000MA2XXXXX2' }, + { id: '3', name: '杭州某某租赁有限公司', creditCode: '91330100MA2XXXXX3', address: '浙江省杭州市余杭区未来科技城', contact: '王五', phone: '13800138003', email: 'wangwu@example.com', companyName: '杭州某某租赁有限公司', companyPhone: '0571-99999999', mailingAddress: '浙江省杭州市余杭区未来科技城', bank: '中国农业银行杭州分行', bankAccount: '6228481234567890123', taxId: '91330100MA2XXXXX3' } + ]; + var deptList = [{ id: 'YW1', name: '业务1部', owners: ['张经理', '李专员', '王专员'] }, { id: 'YW2', name: '业务2部', owners: ['赵经理', '钱专员'] }, { id: 'YW3', name: '业务3部', owners: ['孙经理', '周专员'] }]; + var orgList = ['嘉兴羚牛', '上海羚牛', '广东羚牛']; + var regionList = [ + { province: '浙江省', cities: ['杭州市', '宁波市', '嘉兴市', '湖州市'] }, + { province: '上海市', cities: ['上海市'] }, + { province: '广东省', cities: ['广州市', '深圳市', '东莞市'] } + ]; + var feeTemplates = ['标准费用模板A', '标准费用模板B', '定制费用模板C']; + var feeTemplateCertFees = [{ project: '补办行驶证', standard: '50元/次', serviceFee: '20' }, { project: '补办驾驶证', standard: '30元/次', serviceFee: '10' }, { project: '补办牌照', standard: '100元/次', serviceFee: '50' }]; + var feeTemplatePenaltyFees = [{ project: '提前退车违约金', standard: '月租金×1', serviceFee: '0' }, { project: '违章处理违约金', standard: '按实际发生', serviceFee: '50' }]; + var feeTemplateConsumables = [{ category: '轮胎', part: '前轮', partName: '轮胎A型', qty: 1, feeDetail: '500.00' }, { category: '易损件', part: '雨刮', partName: '雨刮片', qty: 2, feeDetail: '80.00' }]; + var feeTemplateOtherFees = [{ project: '上门送车费', standard: '100元/次', serviceFee: '50' }, { project: '上门收车费', standard: '100元/次', serviceFee: '50' }, { project: '清洗费', standard: '80元/次', serviceFee: '30' }]; + var brandList = ['品牌A', '品牌B', '品牌C', '品牌D']; + var modelByBrand = { '品牌A': ['型号A1', '型号A2', '型号A3'], '品牌B': ['型号B1', '型号B2'], '品牌C': ['型号C1', '型号C2', '型号C3'], '品牌D': ['型号D1'] }; + var vehicleList = [{ plateNo: '浙A10001', vin: 'L1234567890ABCDEF' }, { plateNo: '浙B20002', vin: 'L2234567890ABCDEF' }, { plateNo: '沪A30003', vin: 'L3234567890ABCDEF' }, { plateNo: '粤A40004', vin: 'L4234567890ABCDEF' }]; + var serviceItemOptions = ['代处理费用', '罚款', '违章处理违约金', '未参加安全培训', '车辆出险', '年检年审违约', '停车费', '设备损坏金(包含易损件)', '清洗费', '上门收车人工费', '上门收车送车行驶费', '上门收车基础服务费', '保险上浮', '保养费用', '补办驾驶证', '补办牌照', '补办营运证', '补办加氢证', '借用备用钥匙', '补配钥匙', '租金', '氢气费-客', '退还车氢量差', '能源费补缴', '能源费退款', '送车上门人工费', '送车上门送车行驶费', '送车上门基础服务费', '保证金', '氢气预付费', '维修费用', 'ETC-客', 'ETC卡缺损费', 'ETC设备缺损费', '电费-客', '未结算保养费', '未结算维修费', '车损费', '工具损坏或丢失费', '证件费', '广告损坏费', '送车服务费', '接车服务费', '补办行驶证', '超赔险', '轮胎磨损费', '无忧包', '轮胎保', '养护保', '尾板']; + + var prevContractSample = { + customerId: '1', + businessDept: 'YW1', + businessOwner: '张经理', + contractOriginal: null, + projectName: '嘉兴氢能运输项目', + contractType: '正式合同', + effectiveDate: '2027-02-17', + paymentMethod: '预付', + endDate: '2028-02-17', + paymentPeriod: '1', + signingCompany: '嘉兴羚牛', + deliveryProvince: '浙江省', + deliveryCity: '嘉兴市', + deliveryLocation: '嘉兴市南湖区科技大道1号', + remarks: '续签自原合同 JXZL20260216YW101235A', + authorizedList: [{ name: '张三', phone: '13800138001', idCard: '330102199001011234' }], + rentalOrders: [ + { brand: '品牌A', model: '型号A1', plateNo: '浙A10001', vin: 'L1234567890ABCDEF', monthRent: '8000', serviceItems: [{ project: '保养费用', fee: '200', effectiveDate: '2026-03-01' }], deposit: '10000', remark: '' }, + { brand: '品牌A', model: '型号A2', plateNo: '浙B20002', vin: 'L2234567890ABCDEF', monthRent: '8000', serviceItems: [{ project: '清洗费', fee: '80', effectiveDate: '2026-03-01' }], deposit: '10000', remark: '' } + ], + hydrogenBearer: '客户', + hydrogenPaymentMethod: '预付', + hydrogenPrepay: '5000', + returnHydrogenPrice: '80', + feeTemplate: '标准费用模板A', + billingMethod: 'month' + }; + var prevCustomer = customerList.find(function(c) { return c.id === prevContractSample.customerId; }) || null; + + var cs1 = React.useState(prevCustomer ? prevCustomer.name : ''); + var customerSearch = cs1[0]; + var setCustomerSearch = cs1[1]; + var cs2 = React.useState(prevCustomer); + var selectedCustomer = cs2[0]; + var setSelectedCustomer = cs2[1]; + var cs3 = React.useState(false); + var customerDropdownOpen = cs3[0]; + var setCustomerDropdownOpen = cs3[1]; + var cs4 = React.useState(prevContractSample.businessDept); + var businessDept = cs4[0]; + var setBusinessDept = cs4[1]; + var cs5 = React.useState(prevContractSample.businessOwner); + var businessOwner = cs5[0]; + var setBusinessOwner = cs5[1]; + var cs6 = React.useState(''); + var ownerFocusError = cs6[0]; + var setOwnerFocusError = cs6[1]; + var contractOriginalRef = React.useRef(null); + var csContractOriginal = React.useState(null); + var contractOriginal = csContractOriginal[0]; + var setContractOriginal = csContractOriginal[1]; + + var bs1 = React.useState(prevContractSample.projectName); + var projectName = bs1[0]; + var setProjectName = bs1[1]; + var bs2 = React.useState(prevContractSample.contractType); + var contractType = bs2[0]; + var setContractType = bs2[1]; + var bs3 = React.useState(prevContractSample.effectiveDate); + var effectiveDate = bs3[0]; + var setEffectiveDate = bs3[1]; + var bs4 = React.useState(prevContractSample.paymentMethod); + var paymentMethod = bs4[0]; + var setPaymentMethod = bs4[1]; + var bs6 = React.useState(prevContractSample.endDate); + var endDate = bs6[0]; + var setEndDate = bs6[1]; + var bs7 = React.useState(prevContractSample.paymentPeriod); + var paymentPeriod = bs7[0]; + var setPaymentPeriod = bs7[1]; + var bs8 = React.useState(prevContractSample.signingCompany); + var signingCompany = bs8[0]; + var setSigningCompany = bs8[1]; + var bs9 = React.useState(prevContractSample.deliveryProvince); + var deliveryProvince = bs9[0]; + var setDeliveryProvince = bs9[1]; + var bs10 = React.useState(prevContractSample.deliveryCity); + var deliveryCity = bs10[0]; + var setDeliveryCity = bs10[1]; + var bs10b = React.useState(false); + var deliveryRegionOpen = bs10b[0]; + var setDeliveryRegionOpen = bs10b[1]; + var deliveryRegionClickInsideRef = React.useRef(false); + var bs11 = React.useState(prevContractSample.deliveryLocation); + var deliveryLocation = bs11[0]; + var setDeliveryLocation = bs11[1]; + var bs12 = React.useState(prevContractSample.remarks); + var remarks = bs12[0]; + var setRemarks = bs12[1]; + + var as1 = React.useState(prevContractSample.authorizedList.slice()); + var authorizedList = as1[0]; + var setAuthorizedList = as1[1]; + + var emptyRentalRow = { brand: '', model: '', plateNo: '', vin: '', monthRent: '', serviceItems: [{ project: '', fee: '', effectiveDate: '' }], deposit: '', remark: '' }; + var os1 = React.useState(prevContractSample.rentalOrders.map(function(r) { return { brand: r.brand, model: r.model, plateNo: r.plateNo, vin: r.vin, monthRent: r.monthRent, serviceItems: (r.serviceItems || []).map(function(s) { return { project: s.project, fee: s.fee, effectiveDate: s.effectiveDate }; }), deposit: r.deposit, remark: r.remark || '' }; })); + var rentalOrders = os1[0]; + var setRentalOrders = os1[1]; + var os1b = React.useState(null); + var serviceModalRowIndex = os1b[0]; + var setServiceModalRowIndex = os1b[1]; + var os1c = React.useState(prevContractSample.hydrogenBearer); + var hydrogenBearer = os1c[0]; + var setHydrogenBearer = os1c[1]; + var os1d = React.useState(prevContractSample.hydrogenPaymentMethod); + var hydrogenPaymentMethod = os1d[0]; + var setHydrogenPaymentMethod = os1d[1]; + var os1e = React.useState(prevContractSample.hydrogenPrepay); + var hydrogenPrepay = os1e[0]; + var setHydrogenPrepay = os1e[1]; + var os1f = React.useState(prevContractSample.returnHydrogenPrice); + var returnHydrogenPrice = os1f[0]; + var setReturnHydrogenPrice = os1f[1]; + var os1g = React.useState(null); + var plateNoFocusRow = os1g[0]; + var setPlateNoFocusRow = os1g[1]; + var os1h = React.useState(''); + var plateNoSearch = os1h[0]; + var setPlateNoSearch = os1h[1]; + var os1i = React.useState(null); + var serviceItemFocusRow = os1i[0]; + var setServiceItemFocusRow = os1i[1]; + var os1j = React.useState(''); + var serviceItemSearch = os1j[0]; + var setServiceItemSearch = os1j[1]; + var os1k = React.useState(null); + var plateNoDropdownRect = os1k[0]; + var setPlateNoDropdownRect = os1k[1]; + var os2 = React.useState(prevContractSample.feeTemplate); + var feeTemplate = os2[0]; + var setFeeTemplate = os2[1]; + var os3 = React.useState(prevContractSample.billingMethod); + var billingMethod = os3[0]; + var setBillingMethod = os3[1]; + + var cc1State = React.useState(false); + var cc1 = cc1State[0]; + var setCc1 = cc1State[1]; + var cc2State = React.useState(false); + var cc2 = cc2State[0]; + var setCc2 = cc2State[1]; + var cc3State = React.useState(false); + var cc3 = cc3State[0]; + var setCc3 = cc3State[1]; + var cc4State = React.useState(false); + var cc4 = cc4State[0]; + var setCc4 = cc4State[1]; + var cc5State = React.useState(false); + var cc5 = cc5State[0]; + var setCc5 = cc5State[1]; + var cc6State = React.useState(false); + var cc6 = cc6State[0]; + var setCc6 = cc6State[1]; + var reqSpecState = React.useState(false); + var reqSpecOpen = reqSpecState[0]; + var setReqSpecOpen = reqSpecState[1]; + var formErrorsState = React.useState({}); + var formErrors = formErrorsState[0]; + var setFormErrors = formErrorsState[1]; + + var filteredCustomers = customerList.filter(function(c) { return !customerSearch || c.name.indexOf(customerSearch) !== -1; }); + var currentDept = deptList.find(function(d) { return d.id === businessDept; }); + var ownerOptions = currentDept ? currentDept.owners : []; + var contractCode = (function() { + var cityCode = 'JX'; + var typeCode = 'ZL'; + var signCode = contractType === '正式合同' ? 'A' : 'B'; + var dateStr = effectiveDate ? effectiveDate.replace(/-/g, '') : '20260216'; + var deptCode = businessDept || 'YW1'; + return cityCode + typeCode + dateStr + deptCode + '01235' + signCode; + })(); + var originalContractCode = 'JXZL20260216YW101235A'; + var contractCodeDisplay = contractCode + '(续签自:合同编号' + originalContractCode + ')'; + var mainVehicleModelsDisplay = (function() { + var models = []; + var seen = {}; + for (var i = 0; i < rentalOrders.length; i++) { + var m = rentalOrders[i].model; + if (m && !seen[m]) { seen[m] = true; models.push(m); } + } + return models.join('、'); + })(); + var calcRowServiceFee = function(row) { + var sum = 0; + for (var i = 0; i < (row.serviceItems || []).length; i++) { + var fee = parseFloat(row.serviceItems[i].fee); + if (!isNaN(fee)) sum += fee; + } + return sum.toFixed(2); + }; + var rentalTotalVehicles = rentalOrders.length; + var rentalTotalRentService = rentalOrders.reduce(function(s, r) { return s + (parseFloat(r.monthRent) || 0) + (parseFloat(calcRowServiceFee(r)) || 0); }, 0); + var rentalTotalDeposit = rentalOrders.reduce(function(s, r) { return s + (parseFloat(r.deposit) || 0); }, 0); + var rentalTotalHydrogen = (hydrogenPaymentMethod === '预付' && hydrogenBearer === '客户') ? (parseFloat(hydrogenPrepay) || 0) : 0; + + var selectCustomer = function(c) { + setSelectedCustomer(c); + setCustomerSearch(c ? c.name : ''); + setCustomerDropdownOpen(false); + }; + var deliveryRegionDisplay = deliveryProvince && deliveryCity ? deliveryProvince + ' / ' + deliveryCity : ''; + var selectDeliveryRegion = function(province, city) { + setDeliveryProvince(province); + setDeliveryCity(city); + setDeliveryRegionOpen(false); + }; + var scrollToCard = function(id) { + var el = document.getElementById(id); + if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' }); + }; + var handleOwnerFocus = function() { + if (!businessDept) setOwnerFocusError('请先选择业务部门'); + else setOwnerFocusError(''); + }; + var handleOwnerBlur = function() { setOwnerFocusError(''); }; + var openContractOriginal = function() { + if (contractOriginal && contractOriginal.file) { + var url = URL.createObjectURL(contractOriginal.file); + window.open(url); + } + }; + + var validateSubmitAndReview = function() { + var errs = {}; + if (!selectedCustomer) errs.customer = '请选择客户'; + if (!businessDept) errs.businessDept = '请选择业务部门'; + if (!contractOriginal) errs.contractOriginal = '请上传合同原件'; + if (!businessOwner) errs.businessOwner = '请选择业务负责人'; + if (!projectName || !projectName.trim()) errs.projectName = '请输入项目名称'; + if (!contractType) errs.contractType = '请选择合同类型'; + if (!effectiveDate) errs.effectiveDate = '请选择生效日期'; + if (!paymentMethod) errs.paymentMethod = '请选择付款方式'; + if (!endDate) errs.endDate = '请选择结束日期'; + if (!paymentPeriod) errs.paymentPeriod = '请选择付款周期'; + if (!signingCompany) errs.signingCompany = '请选择签约公司'; + if (!deliveryProvince || !deliveryCity) errs.deliveryRegion = '请选择交车区域'; + var authInvalid = authorizedList.some(function(a) { return !a.name || !a.name.trim() || !a.phone || !a.phone.trim() || !a.idCard || !a.idCard.trim(); }); + if (authInvalid) errs.authorizedList = '请完整填写被授权人姓名、联系电话、身份证'; + var rentalInvalid = rentalOrders.some(function(r) { return !r.brand || !r.model || !(r.monthRent && String(r.monthRent).trim()) || !(r.deposit && String(r.deposit).trim()); }); + if (rentalInvalid) errs.rentalOrders = '请完整填写租赁订单的品牌、型号、车辆月租金、保证金'; + if (!hydrogenBearer) errs.hydrogenBearer = '请选择氢费承担方'; + if (hydrogenBearer === '客户' && !hydrogenPaymentMethod) errs.hydrogenPaymentMethod = '请选择付款方式'; + if (hydrogenBearer === '客户' && hydrogenPaymentMethod === '预付' && (!hydrogenPrepay || !String(hydrogenPrepay).trim())) errs.hydrogenPrepay = '请输入氢气预付款'; + if (!returnHydrogenPrice || !String(returnHydrogenPrice).trim()) errs.returnHydrogenPrice = '请输入退还车氢气单价'; + if (!feeTemplate) errs.feeTemplate = '请选择费用模板'; + if (!billingMethod) errs.billingMethod = '请选择账单计算方式'; + setFormErrors(errs); + if (Object.keys(errs).length > 0) { + var firstId = errs.customer || errs.businessDept || errs.businessOwner ? 'card-customer' : (errs.projectName || errs.contractType || errs.effectiveDate || errs.paymentMethod || errs.endDate || errs.paymentPeriod || errs.signingCompany || errs.deliveryRegion || errs.contractOriginal) ? 'card-contract' : errs.authorizedList ? 'card-authorized' : (errs.rentalOrders || errs.hydrogenBearer || errs.hydrogenPaymentMethod || errs.hydrogenPrepay || errs.returnHydrogenPrice) ? 'card-rental' : errs.feeTemplate ? 'card-fee' : 'card-billing'; + setCc1(false); setCc2(false); setCc3(false); setCc4(false); setCc5(false); setCc6(false); + setTimeout(function() { var el = document.getElementById(firstId); if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' }); }, 100); + return false; + } + return true; + }; + + var addAuthorized = function() { + setAuthorizedList(authorizedList.concat([{ name: '', phone: '', idCard: '' }])); + }; + var removeAuthorized = function(index) { + var next = authorizedList.slice(0); + next.splice(index, 1); + setAuthorizedList(next.length ? next : [{ name: '', phone: '', idCard: '' }]); + }; + var updateAuthorized = function(index, field, value) { + var next = authorizedList.slice(0); + var cur = next[index] || {}; + var patch = {}; + patch[field] = value; + next[index] = Object.assign({}, cur, patch); + setAuthorizedList(next); + }; + + var addRentalRow = function() { + setRentalOrders(rentalOrders.concat([{ brand: '', model: '', plateNo: '', vin: '', monthRent: '', serviceItems: [{ project: '', fee: '', effectiveDate: '' }], deposit: '', remark: '' }])); + }; + var removeRentalRow = function(index) { + var next = rentalOrders.slice(0); + next.splice(index, 1); + setRentalOrders(next.length ? next : [{ brand: '', model: '', plateNo: '', vin: '', monthRent: '', serviceItems: [{ project: '', fee: '', effectiveDate: '' }], deposit: '', remark: '' }]); + }; + var updateRentalOrder = function(index, field, value) { + var next = rentalOrders.slice(0); + var cur = next[index] || {}; + var newRow = Object.assign({}, cur); + newRow[field] = value; + if (field === 'plateNo') { + var v = vehicleList.find(function(x) { return x.plateNo === value; }); + newRow.vin = v ? v.vin : ''; + } + if (field === 'brand') newRow.model = ''; + next[index] = newRow; + setRentalOrders(next); + }; + var openServiceModal = function(index) { setServiceModalRowIndex(index); }; + var closeServiceModal = function() { setServiceModalRowIndex(null); }; + var addServiceItem = function() { + if (serviceModalRowIndex === null) return; + var next = rentalOrders.slice(0); + var row = next[serviceModalRowIndex]; + var items = (row.serviceItems || []).concat([{ project: '', fee: '', effectiveDate: '' }]); + var newRow = {}; + for (var k in row) { if (row.hasOwnProperty(k) && k !== 'serviceItems') newRow[k] = row[k]; } + newRow.serviceItems = items; + next[serviceModalRowIndex] = newRow; + setRentalOrders(next); + }; + var removeServiceItem = function(siIndex) { + if (serviceModalRowIndex === null) return; + var next = rentalOrders.slice(0); + var row = next[serviceModalRowIndex]; + var items = (row.serviceItems || []).slice(0); + items.splice(siIndex, 1); + if (items.length === 0) items = [{ project: '', fee: '', effectiveDate: '' }]; + var newRow = {}; + for (var k in row) { if (row.hasOwnProperty(k) && k !== 'serviceItems') newRow[k] = row[k]; } + newRow.serviceItems = items; + next[serviceModalRowIndex] = newRow; + setRentalOrders(next); + }; + var updateServiceItem = function(siIndex, field, value) { + if (serviceModalRowIndex === null) return; + var next = rentalOrders.slice(0); + var row = next[serviceModalRowIndex]; + var items = (row.serviceItems || []).slice(0); + var item = items[siIndex] || {}; + var newItem = {}; + for (var k in item) { if (item.hasOwnProperty(k)) newItem[k] = item[k]; } + newItem[field] = value; + items[siIndex] = newItem; + var newRow = {}; + for (var rk in row) { if (row.hasOwnProperty(rk) && rk !== 'serviceItems') newRow[rk] = row[rk]; } + newRow.serviceItems = items; + next[serviceModalRowIndex] = newRow; + setRentalOrders(next); + }; + + React.useEffect(function() { + if (!deliveryRegionOpen) return; + var handler = function(e) { + if (deliveryRegionClickInsideRef.current) { deliveryRegionClickInsideRef.current = false; return; } + var el = document.getElementById('delivery-region-wrap'); + if (el && !el.contains(e.target)) setDeliveryRegionOpen(false); + }; + document.addEventListener('mousedown', handler); + return function() { document.removeEventListener('mousedown', handler); }; + }, [deliveryRegionOpen]); + + React.useEffect(function() { + if (plateNoFocusRow === null) { setPlateNoDropdownRect(null); return; } + var timer = setTimeout(function() { + var el = document.getElementById('plate-no-input-' + plateNoFocusRow); + if (el && el.getBoundingClientRect) { + var rect = el.getBoundingClientRect(); + setPlateNoDropdownRect({ top: rect.bottom + 2, left: rect.left, width: rect.width }); + } else { setPlateNoDropdownRect(null); } + }, 0); + return function() { clearTimeout(timer); }; + }, [plateNoFocusRow]); + + var styles = { + page: { padding: '16px 24px 48px', backgroundColor: '#f5f5f5', minHeight: '100vh', fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif', fontSize: 14 }, + breadcrumb: { marginBottom: 16, color: '#666' }, + breadcrumbSep: { margin: '0 8px', color: '#999' }, + anchorWrap: { position: 'fixed', top: 80, right: 24, zIndex: 100, backgroundColor: '#fff', borderRadius: 8, boxShadow: '0 2px 8px rgba(0,0,0,0.12)', padding: '12px 16px', minWidth: 160 }, + anchorItem: { display: 'block', padding: '6px 0', color: '#1890ff', cursor: 'pointer', border: 'none', background: 'none', width: '100%', textAlign: 'left', fontSize: 13 }, + card: { backgroundColor: '#fff', borderRadius: 8, marginBottom: 16, boxShadow: '0 1px 2px rgba(0,0,0,0.05)', overflow: 'hidden' }, + cardHeader: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '16px 20px', borderBottom: '1px solid #f0f0f0', cursor: 'pointer' }, + cardTitle: { fontSize: 16, fontWeight: 600, color: '#333' }, + cardToggle: { color: '#999', fontSize: 14 }, + cardBody: { padding: '20px 24px' }, + formRow: { display: 'flex', flexWrap: 'wrap', marginBottom: 16 }, + formCol: { flex: '0 0 33.33%', minWidth: 200, paddingRight: 16, marginBottom: 8 }, + formColFull: { flex: '0 0 100%', marginBottom: 8 }, + label: { display: 'block', marginBottom: 6, color: '#333' }, + labelRequired: { color: '#ff4d4f', marginRight: 4 }, + input: { width: '100%', padding: '8px 12px', border: '1px solid #d9d9d9', borderRadius: 4, fontSize: 14 }, + inputDisabled: { backgroundColor: '#f5f5f5', color: '#999', cursor: 'not-allowed' }, + inputError: { borderColor: '#ff4d4f' }, + select: { width: '100%', padding: '8px 12px', border: '1px solid #d9d9d9', borderRadius: 4, fontSize: 14 }, + textarea: { width: '100%', padding: '8px 12px', border: '1px solid #d9d9d9', borderRadius: 4, fontSize: 14, minHeight: 80, resize: 'vertical' }, + errMsg: { color: '#ff4d4f', fontSize: 12, marginTop: 4 }, + summaryList: { marginBottom: 16, display: 'flex', flexWrap: 'wrap', gap: 16 }, + summaryListItem: { flex: '0 0 calc(50% - 8px)', display: 'flex', alignItems: 'center', padding: '12px 16px', border: '1px solid #e8e8e8', borderRadius: 4, backgroundColor: '#fafafa', fontSize: 14, boxSizing: 'border-box' }, + summaryListLabel: { flex: '0 0 140px', color: '#666' }, + summaryListValue: { flex: 1, fontWeight: 600, color: '#333' }, + authRow: { display: 'flex', gap: 12, alignItems: 'flex-start', marginBottom: 12 }, + authInput: { flex: 1, padding: '8px 12px', border: '1px solid #d9d9d9', borderRadius: 4 }, + btnDel: { padding: '8px 16px', color: '#ff4d4f', border: '1px solid #ff4d4f', borderRadius: 4, backgroundColor: '#fff', cursor: 'pointer' }, + btnAdd: { padding: '8px 16px', color: '#1890ff', border: '1px dashed #1890ff', borderRadius: 4, backgroundColor: '#fff', cursor: 'pointer', marginBottom: 16 }, + footer: { position: 'fixed', bottom: 0, left: 0, right: 0, padding: '12px 24px', backgroundColor: '#fff', borderTop: '1px solid #e8e8e8', display: 'flex', gap: 12, justifyContent: 'flex-start', zIndex: 99 }, + btn: { padding: '8px 24px', borderRadius: 4, border: '1px solid #d9d9d9', cursor: 'pointer', fontSize: 14 }, + btnPrimary: { backgroundColor: '#1890ff', color: '#fff', borderColor: '#1890ff' }, + btnDefault: { backgroundColor: '#fff', color: '#333' }, + tag: { display: 'inline-block', padding: '2px 8px', marginRight: 8, marginBottom: 4, backgroundColor: '#e6f7ff', color: '#1890ff', borderRadius: 4, fontSize: 12 }, + regionCascader: { position: 'absolute', top: '100%', left: 0, right: 0, marginTop: 4, backgroundColor: '#fff', border: '1px solid #d9d9d9', borderRadius: 4, boxShadow: '0 2px 8px rgba(0,0,0,0.12)', zIndex: 10, display: 'flex', minHeight: 200 }, + regionCascaderCol: { flex: 1, borderRight: '1px solid #f0f0f0', overflowY: 'auto' }, + regionCascaderColLast: { flex: 1 }, + regionCascaderItem: { padding: '10px 12px', cursor: 'pointer' }, + rentalTable: { width: '100%', borderCollapse: 'collapse', fontSize: 13 }, + rentalTh: { padding: '10px 8px', textAlign: 'left', borderBottom: '1px solid #e8e8e8', backgroundColor: '#fafafa', fontWeight: 600 }, + rentalTd: { padding: '8px', borderBottom: '1px solid #f0f0f0', verticalAlign: 'top' }, + rentalTdCenter: { padding: '8px', borderBottom: '1px solid #f0f0f0', verticalAlign: 'middle' }, + rentalInput: { width: '100%', padding: '6px 10px', border: '1px solid #d9d9d9', borderRadius: 4, fontSize: 13 }, + rentalInputDisabled: { backgroundColor: '#f5f5f5', color: '#999' }, + modalMask: { position: 'fixed', left: 0, top: 0, right: 0, bottom: 0, backgroundColor: 'rgba(0,0,0,0.45)', zIndex: 1000, display: 'flex', alignItems: 'center', justifyContent: 'center' }, + modalBox: { backgroundColor: '#fff', borderRadius: 8, width: '90%', maxWidth: 720, maxHeight: '85vh', overflow: 'hidden', display: 'flex', flexDirection: 'column', boxShadow: '0 4px 20px rgba(0,0,0,0.15)' }, + modalHeader: { padding: '16px 20px', borderBottom: '1px solid #f0f0f0', fontSize: 16, fontWeight: 600 }, + modalBody: { padding: 20, overflow: 'auto', flex: 1 }, + btnGroup: { display: 'inline-flex', border: '1px solid #d9d9d9', borderRadius: 4, overflow: 'hidden' }, + btnGroupItem: { padding: '8px 16px', border: 'none', borderRight: '1px solid #d9d9d9', backgroundColor: '#fff', color: '#333', cursor: 'pointer', fontSize: 14 }, + btnGroupItemLast: { borderRight: 'none' }, + btnGroupItemActive: { backgroundColor: '#1890ff', color: '#fff', borderColor: '#1890ff', borderRightColor: '#1890ff' }, + feeSectionTitle: { fontSize: 15, fontWeight: 600, color: '#333', marginTop: 20, marginBottom: 10 }, + feeSectionTitleFirst: { marginTop: 0 }, + modalFormInput: { width: '100%', padding: '8px 12px', border: '1px solid #d9d9d9', borderRadius: 4, fontSize: 14, height: 36, boxSizing: 'border-box' } + }; + + var CardBlock = function(props) { + return React.createElement('div', { id: props.id, style: styles.card }, + React.createElement('div', { style: styles.cardHeader, onClick: function() { props.setCollapsed(!props.collapsed); } }, + React.createElement('span', { style: styles.cardTitle }, props.title), + React.createElement('span', { style: styles.cardToggle }, props.collapsed ? '展开' : '收起') + ), + !props.collapsed ? React.createElement('div', { style: styles.cardBody }, props.children) : null + ); + }; + + var FormItem = function(props) { + var colStyle = props.fullWidth ? styles.formColFull : (props.colStyle ? Object.assign({}, styles.formCol, props.colStyle) : styles.formCol); + return React.createElement('div', { style: colStyle }, + React.createElement('label', { style: styles.label }, props.required ? React.createElement('span', { style: styles.labelRequired }, '*') : null, props.label), + props.children, + props.error ? React.createElement('div', { style: styles.errMsg }, props.error) : null + ); + }; + + var customerOptions = customerList.map(function(c) { return React.createElement(Option, { key: c.id, value: c.id }, c.name); }); + var customerFields = React.createElement('div', { style: styles.formRow }, + React.createElement(FormItem, { label: '客户名称', required: true, error: formErrors.customer }, React.createElement(Select, { placeholder: '请选择或输入搜索客户', style: { width: '100%' }, value: selectedCustomer ? selectedCustomer.id : undefined, onChange: function(id) { var c = customerList.find(function(x) { return x.id === id; }); selectCustomer(c || null); }, showSearch: true, allowClear: true, filterOption: function(input, opt) { return opt.children && String(opt.children).toLowerCase().indexOf((input || '').toLowerCase()) >= 0; }, status: formErrors.customer ? 'error' : undefined }, customerOptions)), + React.createElement(FormItem, { label: '客户统一信用代码' }, React.createElement(Input, { value: selectedCustomer ? selectedCustomer.creditCode : '', disabled: true, style: { width: '100%' } })), + React.createElement(FormItem, { label: '客户地址' }, React.createElement(Input, { value: selectedCustomer ? selectedCustomer.address : '', disabled: true, style: { width: '100%' } })), + React.createElement(FormItem, { label: '客户联系人' }, React.createElement(Input, { value: selectedCustomer ? selectedCustomer.contact : '', disabled: true, style: { width: '100%' } })), + React.createElement(FormItem, { label: '客户电话' }, React.createElement(Input, { value: selectedCustomer ? selectedCustomer.phone : '', disabled: true, style: { width: '100%' } })), + React.createElement(FormItem, { label: '客户电子邮箱' }, React.createElement(Input, { value: selectedCustomer ? selectedCustomer.email : '', disabled: true, style: { width: '100%' } })), + React.createElement(FormItem, { label: '企业名称' }, React.createElement(Input, { value: selectedCustomer ? selectedCustomer.companyName : '', disabled: true, style: { width: '100%' } })), + React.createElement(FormItem, { label: '企业电话' }, React.createElement(Input, { value: selectedCustomer ? selectedCustomer.companyPhone : '', disabled: true, style: { width: '100%' } })), + React.createElement(FormItem, { label: '邮寄地址' }, React.createElement(Input, { value: selectedCustomer ? selectedCustomer.mailingAddress : '', disabled: true, style: { width: '100%' } })), + React.createElement(FormItem, { label: '开户银行' }, React.createElement(Input, { value: selectedCustomer ? selectedCustomer.bank : '', disabled: true, style: { width: '100%' } })), + React.createElement(FormItem, { label: '银行账号' }, React.createElement(Input, { value: selectedCustomer ? selectedCustomer.bankAccount : '', disabled: true, style: { width: '100%' } })), + React.createElement(FormItem, { label: '纳税人识别号' }, React.createElement(Input, { value: selectedCustomer ? selectedCustomer.taxId : '', disabled: true, style: { width: '100%' } })), + React.createElement(FormItem, { label: '业务部门', required: true, error: formErrors.businessDept }, React.createElement(Select, { placeholder: '请选择业务部门', style: { width: '100%' }, value: businessDept || undefined, onChange: function(v) { setBusinessDept(v || ''); setBusinessOwner(''); }, status: formErrors.businessDept ? 'error' : undefined }, deptList.map(function(d, i) { return React.createElement(Option, { key: i, value: d.id }, d.name); }))), + React.createElement(FormItem, { label: '业务负责人', required: true, error: formErrors.businessOwner || ownerFocusError }, React.createElement(Select, { placeholder: businessDept ? '请选择' : '请先选择业务部门', style: { width: '100%' }, value: businessOwner || undefined, onChange: function(v) { setBusinessOwner(v || ''); setOwnerFocusError(''); }, onFocus: handleOwnerFocus, onBlur: handleOwnerBlur, disabled: !businessDept, status: (formErrors.businessOwner || ownerFocusError) ? 'error' : undefined }, ownerOptions.map(function(o, i) { return React.createElement(Option, { key: i, value: o }, o); }))) + ); + + var contractFormRow1 = React.createElement('div', { style: styles.formRow }, + React.createElement(FormItem, { label: '项目名称', required: true, error: formErrors.projectName }, React.createElement(Input, { placeholder: '请输入项目名称', value: projectName, onChange: function(e) { setProjectName(e.target.value); }, status: formErrors.projectName ? 'error' : undefined, style: { width: '100%' } })), + React.createElement(FormItem, { label: '合同编码' }, React.createElement(Input, { value: contractCodeDisplay, disabled: true, style: { width: '100%' } })), + React.createElement(FormItem, { label: '合同类型', required: true, error: formErrors.contractType }, React.createElement(Select, { placeholder: '请选择合同类型', style: { width: '100%' }, value: contractType || undefined, onChange: function(v) { setContractType(v || ''); }, status: formErrors.contractType ? 'error' : undefined }, React.createElement(Option, { value: '正式合同' }, '正式合同'), React.createElement(Option, { value: '试用合同' }, '试用合同'))), + React.createElement(FormItem, { label: '生效日期', required: true, error: formErrors.effectiveDate }, React.createElement(DatePicker, { style: { width: '100%' }, format: 'YYYY-MM-DD', placeholder: '请选择生效日期', value: effectiveDate && window.moment ? window.moment(effectiveDate, 'YYYY-MM-DD') : null, onChange: function(d, dateStr) { setEffectiveDate(dateStr || ''); }, status: formErrors.effectiveDate ? 'error' : undefined })), + React.createElement(FormItem, { label: '付款方式', required: true, error: formErrors.paymentMethod }, React.createElement(Select, { placeholder: '请选择付款方式', style: { width: '100%' }, value: paymentMethod || undefined, onChange: function(v) { setPaymentMethod(v || ''); }, status: formErrors.paymentMethod ? 'error' : undefined }, React.createElement(Option, { value: '预付' }, '预付'), React.createElement(Option, { value: '后付' }, '后付'))), + React.createElement(FormItem, { label: '主要车型' }, React.createElement('div', { style: { padding: '8px 12px', minHeight: 36, border: '1px solid #d9d9d9', borderRadius: 4, backgroundColor: '#f5f5f5', display: 'flex', flexWrap: 'wrap', gap: 8, alignItems: 'center' } }, mainVehicleModelsDisplay ? mainVehicleModelsDisplay.split('、').map(function(m, i) { return React.createElement('span', { key: i, style: styles.tag }, m); }) : React.createElement('span', { style: { color: '#999' } }, '根据租赁订单自动反写'))), + React.createElement(FormItem, { label: '结束日期', required: true, error: formErrors.endDate }, React.createElement(DatePicker, { style: { width: '100%' }, format: 'YYYY-MM-DD', placeholder: '请选择结束日期', value: endDate && window.moment ? window.moment(endDate, 'YYYY-MM-DD') : null, onChange: function(d, dateStr) { setEndDate(dateStr || ''); }, status: formErrors.endDate ? 'error' : undefined })), + React.createElement(FormItem, { label: '付款周期', required: true, error: formErrors.paymentPeriod }, React.createElement(Select, { placeholder: '请选择', style: { width: '100%' }, value: paymentPeriod || undefined, onChange: function(v) { setPaymentPeriod(v || ''); }, status: formErrors.paymentPeriod ? 'error' : undefined }, [1,2,3,4,5,6,7,8,9,10,11,12].map(function(n) { return React.createElement(Option, { key: n, value: String(n) }, n + '个月'); }))), + React.createElement(FormItem, { label: '签约公司', required: true, error: formErrors.signingCompany }, React.createElement(Select, { placeholder: '请选择', style: { width: '100%' }, value: signingCompany || undefined, onChange: function(v) { setSigningCompany(v || ''); }, status: formErrors.signingCompany ? 'error' : undefined }, orgList.map(function(o, i) { return React.createElement(Option, { key: i, value: o }, o); }))), + React.createElement('div', { style: styles.formCol }, + React.createElement(FormItem, { label: '交车区域', required: true, error: formErrors.deliveryRegion }, React.createElement('div', { id: 'delivery-region-wrap', style: { position: 'relative' } }, React.createElement(Input, { style: Object.assign({}, formErrors.deliveryRegion ? { borderColor: '#ff4d4f' } : {}, { cursor: 'pointer', caretColor: 'transparent', width: '100%' }), placeholder: '请选择省-市', value: deliveryRegionDisplay, readOnly: true, onClick: function() { setDeliveryRegionOpen(!deliveryRegionOpen); } }), deliveryRegionOpen ? React.createElement('div', { style: styles.regionCascader, onMouseDown: function() { deliveryRegionClickInsideRef.current = true; } }, React.createElement('div', { style: styles.regionCascaderCol }, regionList.map(function(r, i) { var isActive = r.province === deliveryProvince; return React.createElement('div', { key: i, style: Object.assign({}, styles.regionCascaderItem, isActive ? { backgroundColor: '#e6f7ff', color: '#1890ff' } : {}), onMouseEnter: function(e) { if (!isActive) e.currentTarget.style.backgroundColor = '#f5f5f5'; }, onMouseLeave: function(e) { if (!isActive) e.currentTarget.style.backgroundColor = 'transparent'; }, onMouseDown: function(e) { e.preventDefault(); setDeliveryProvince(r.province); setDeliveryCity(''); } }, r.province); })), React.createElement('div', { style: styles.regionCascaderColLast }, deliveryProvince ? (regionList.find(function(x) { return x.province === deliveryProvince; }) || { cities: [] }).cities.map(function(c, i) { var isActive = c === deliveryCity; return React.createElement('div', { key: i, style: Object.assign({}, styles.regionCascaderItem, isActive ? { backgroundColor: '#e6f7ff', color: '#1890ff' } : {}), onMouseEnter: function(e) { if (!isActive) e.currentTarget.style.backgroundColor = '#f5f5f5'; }, onMouseLeave: function(e) { if (!isActive) e.currentTarget.style.backgroundColor = 'transparent'; }, onMouseDown: function(e) { e.preventDefault(); selectDeliveryRegion(deliveryProvince, c); } }, c); }) : React.createElement('div', { style: { padding: 16, color: '#999', fontSize: 13 } }, '请先选择省'))) : null)), + React.createElement(FormItem, { label: '合同原件', required: true, error: formErrors.contractOriginal }, + React.createElement('div', null, + React.createElement('div', { style: { color: '#999', fontSize: 12, marginBottom: 8 } }, '续签时需重新上传合同原件附件'), + contractOriginal + ? React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap' } }, + React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' } }, + React.createElement('a', { href: '#', style: { color: '#1890ff' }, onClick: function(e) { e.preventDefault(); openContractOriginal(); } }, contractOriginal.name), + contractOriginal.size || contractOriginal.uploadTime ? React.createElement('span', { style: { color: '#999', fontSize: 12 } }, (contractOriginal.size ? contractOriginal.size : '') + (contractOriginal.size && contractOriginal.uploadTime ? ' · ' : '') + (contractOriginal.uploadTime || '')) : null + ), + React.createElement(Button, { type: 'button', size: 'small', danger: true, onClick: function() { setContractOriginal(null); } }, '删除') + ) + : React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8 } }, + React.createElement('input', { ref: contractOriginalRef, type: 'file', style: { display: 'none' }, onChange: function(e) { + var f = e.target.files && e.target.files[0]; + if (f) { + var sizeDisplay = f.size >= 1024 * 1024 ? (f.size / 1024 / 1024).toFixed(1) + ' MB' : (f.size / 1024).toFixed(1) + ' KB'; + var now = window.moment ? window.moment() : new Date(); + var uploadTimeStr = window.moment ? now.format('YYYY-MM-DD HH:mm') : now.getFullYear() + '-' + String(now.getMonth() + 1).padStart(2, '0') + '-' + String(now.getDate()).padStart(2, '0') + ' ' + String(now.getHours()).padStart(2, '0') + ':' + String(now.getMinutes()).padStart(2, '0'); + setContractOriginal({ name: f.name, file: f, size: sizeDisplay, uploadTime: uploadTimeStr }); + } + e.target.value = ''; + } }), + React.createElement(Button, { type: 'default', style: { padding: '8px 16px', border: '1px solid #d9d9d9', borderRadius: 4, backgroundColor: '#fff', color: '#333', cursor: 'pointer', fontSize: 14 }, onClick: function() { if (contractOriginalRef.current) contractOriginalRef.current.click(); } }, '上传附件') + ) + ) + ) + ), + React.createElement(FormItem, { label: '交车地点' }, React.createElement(Input, { placeholder: '请输入交车地点', value: deliveryLocation, onChange: function(e) { setDeliveryLocation(e.target.value); }, style: { width: '100%' } })) + ); + var contractFormRow2 = React.createElement('div', { style: styles.formRow }, React.createElement(FormItem, { label: '备注', fullWidth: true }, React.createElement(Input.TextArea, { placeholder: '请输入备注信息', value: remarks, onChange: function(e) { setRemarks(e.target.value); }, style: styles.textarea, rows: 4 }))); + + var authorizedContent = React.createElement('div', null, + React.createElement('div', { style: { display: 'flex', gap: 12, alignItems: 'center', marginBottom: 8 } }, React.createElement('span', { style: { flex: 1, fontWeight: 600, fontSize: 13 } }, React.createElement('span', { style: { color: '#ff4d4f', marginRight: 4 } }, '*'), '被授权人姓名'), React.createElement('span', { style: { flex: 1, fontWeight: 600, fontSize: 13 } }, React.createElement('span', { style: { color: '#ff4d4f', marginRight: 4 } }, '*'), '被授权人联系电话'), React.createElement('span', { style: { flex: 1, fontWeight: 600, fontSize: 13 } }, React.createElement('span', { style: { color: '#ff4d4f', marginRight: 4 } }, '*'), '被授权人身份证'), React.createElement('span', { style: { flex: '0 0 80px' } })), + authorizedList.map(function(item, index) { return React.createElement('div', { key: index, style: styles.authRow }, React.createElement(Input, { style: Object.assign({}, styles.authInput, formErrors.authorizedList ? { borderColor: '#ff4d4f' } : {}, { flex: 1 }), placeholder: '请输入被授权人姓名', value: item.name, onChange: function(e) { updateAuthorized(index, 'name', e.target.value); } }), React.createElement(Input, { style: Object.assign({}, styles.authInput, formErrors.authorizedList ? { borderColor: '#ff4d4f' } : {}, { flex: 1 }), placeholder: '请输入被授权人联系电话', value: item.phone, onChange: function(e) { updateAuthorized(index, 'phone', e.target.value); } }), React.createElement(Input, { style: Object.assign({}, styles.authInput, formErrors.authorizedList ? { borderColor: '#ff4d4f' } : {}, { flex: 1 }), placeholder: '请输入被授权人身份证号', value: item.idCard, onChange: function(e) { updateAuthorized(index, 'idCard', e.target.value); } }), React.createElement(Button, { type: 'button', danger: true, onClick: function() { removeAuthorized(index); } }, '删除')); }), + formErrors.authorizedList ? React.createElement('div', { style: styles.errMsg }, formErrors.authorizedList) : null, + React.createElement(Button, { type: 'dashed', style: { width: '100%', marginBottom: 16 }, onClick: addAuthorized }, '添加一行') + ); + + var hydrogenFormRow = (function() { + var hydrogenFields = []; + hydrogenFields.push(React.createElement(FormItem, { label: '氢费承担方', required: true, error: formErrors.hydrogenBearer }, React.createElement(Select, { style: { width: '100%' }, value: hydrogenBearer || undefined, onChange: function(v) { setHydrogenBearer(v || ''); setHydrogenPaymentMethod(v === '客户' ? '预付' : ''); }, status: formErrors.hydrogenBearer ? 'error' : undefined }, React.createElement(Option, { value: '我方' }, '我方'), React.createElement(Option, { value: '客户' }, '客户')))); + if (hydrogenBearer === '客户') { hydrogenFields.push(React.createElement(FormItem, { label: '付款方式', required: true, error: formErrors.hydrogenPaymentMethod }, React.createElement(Select, { style: { width: '100%' }, value: hydrogenPaymentMethod || undefined, onChange: function(v) { setHydrogenPaymentMethod(v || ''); }, status: formErrors.hydrogenPaymentMethod ? 'error' : undefined }, React.createElement(Option, { value: '预付' }, '预付'), React.createElement(Option, { value: '月付款' }, '月付款'), React.createElement(Option, { value: '自行结算' }, '自行结算')))); } + var hydrogenPrepayInput = React.createElement(Input, { placeholder: '0.00', value: hydrogenPrepay, onChange: function(e) { setHydrogenPrepay(e.target.value); }, addonAfter: '元', status: formErrors.hydrogenPrepay ? 'error' : undefined, style: { width: '100%' } }); + var returnHydrogenInput = React.createElement(Input, { placeholder: '0.00', value: returnHydrogenPrice, onChange: function(e) { setReturnHydrogenPrice(e.target.value); }, addonAfter: '元', status: formErrors.returnHydrogenPrice ? 'error' : undefined, style: { width: '100%' } }); + var returnHydrogenColStyle = hydrogenPaymentMethod === '预付' ? { flex: '1 1 0', minWidth: 180 } : { flex: '0 0 calc(50% - 8px)', minWidth: 180 }; + hydrogenFields.push(React.createElement('div', { key: 'hydrogen-amount-row', style: { display: 'flex', gap: 16, flex: '1 1 100%' } }, hydrogenPaymentMethod === '预付' ? React.createElement(FormItem, { label: '氢气预付款', required: true, error: formErrors.hydrogenPrepay, colStyle: { flex: '1 1 0', minWidth: 180 } }, hydrogenPrepayInput) : null, React.createElement(FormItem, { label: '退还车氢气单价', required: true, error: formErrors.returnHydrogenPrice, colStyle: returnHydrogenColStyle }, returnHydrogenInput))); + return React.createElement.apply(React, ['div', { style: Object.assign({}, styles.formRow, { marginTop: 20 }) }].concat(hydrogenFields)); + })(); + + var plateNoOptions = vehicleList.map(function(v) { return React.createElement(Option, { key: v.plateNo, value: v.plateNo }, v.plateNo); }); + var rentalTableBody = rentalOrders.map(function(row, idx) { + var modelOpts = row.brand ? (modelByBrand[row.brand] || []) : []; + return React.createElement('tr', { key: idx }, + React.createElement('td', { style: styles.rentalTdCenter }, idx + 1), + React.createElement('td', { style: styles.rentalTd }, React.createElement(Select, { style: { width: '100%', minWidth: 90 }, value: row.brand || undefined, onChange: function(v) { updateRentalOrder(idx, 'brand', v || ''); }, placeholder: '请选择' }, brandList.map(function(b, i) { return React.createElement(Option, { key: i, value: b }, b); }))), + React.createElement('td', { style: styles.rentalTd }, React.createElement(Select, { style: { width: '100%', minWidth: 90 }, value: row.model || undefined, onChange: function(v) { updateRentalOrder(idx, 'model', v || ''); }, disabled: !row.brand, placeholder: '请选择' }, modelOpts.map(function(m, i) { return React.createElement(Option, { key: i, value: m }, m); }))), + React.createElement('td', { style: styles.rentalTd }, React.createElement(Select, { style: { width: '100%' }, placeholder: '请选择或输入搜索', value: row.plateNo || undefined, onChange: function(v) { updateRentalOrder(idx, 'plateNo', v || ''); }, showSearch: true, allowClear: true, filterOption: function(input, opt) { return opt && opt.children && String(opt.children).toLowerCase().indexOf((input || '').toLowerCase()) >= 0; } }, plateNoOptions)), + React.createElement('td', { style: styles.rentalTd }, React.createElement(Input, { value: row.vin || '', disabled: true, style: { width: '100%' } })), + React.createElement('td', { style: styles.rentalTd }, React.createElement('div', { style: { display: 'flex', alignItems: 'center' } }, React.createElement(Input, { placeholder: '0.00', value: row.monthRent || '', onChange: function(e) { updateRentalOrder(idx, 'monthRent', e.target.value); }, style: styles.rentalInput }), React.createElement('span', { style: { marginLeft: 4, whiteSpace: 'nowrap' } }, '元'))), + React.createElement('td', { style: styles.rentalTdCenter }, React.createElement(Button, { type: 'link', size: 'small', onClick: function() { openServiceModal(idx); } }, '管理')), + React.createElement('td', { style: styles.rentalTdCenter }, calcRowServiceFee(row) + ' 元'), + React.createElement('td', { style: styles.rentalTd }, React.createElement('div', { style: { display: 'flex', alignItems: 'center' } }, React.createElement(Input, { placeholder: '0.00', value: row.deposit || '', onChange: function(e) { updateRentalOrder(idx, 'deposit', e.target.value); }, style: styles.rentalInput }), React.createElement('span', { style: { marginLeft: 4, whiteSpace: 'nowrap' } }, '元'))), + React.createElement('td', { style: styles.rentalTd }, React.createElement(Input, { placeholder: '备注', value: row.remark || '', onChange: function(e) { updateRentalOrder(idx, 'remark', e.target.value); }, style: Object.assign({}, styles.rentalInput, { width: '100%' }) })), + React.createElement('td', { style: styles.rentalTdCenter }, React.createElement(Button, { type: 'link', size: 'small', danger: true, onClick: function() { removeRentalRow(idx); } }, '删除')) + ); + }); + + var rentalSummary = React.createElement('div', { style: styles.summaryList }, + React.createElement('div', { style: styles.summaryListItem }, React.createElement('span', { style: styles.summaryListLabel }, '租赁车辆数'), React.createElement('span', { style: styles.summaryListValue }, rentalTotalVehicles + ' 辆')), + React.createElement('div', { style: styles.summaryListItem }, React.createElement('span', { style: styles.summaryListLabel }, '租金及服务费合计'), React.createElement('span', { style: styles.summaryListValue }, rentalTotalRentService.toFixed(2) + ' 元')), + React.createElement('div', { style: styles.summaryListItem }, React.createElement('span', { style: styles.summaryListLabel }, '保证金总额'), React.createElement('span', { style: styles.summaryListValue }, rentalTotalDeposit.toFixed(2) + ' 元')), + React.createElement('div', { style: styles.summaryListItem }, React.createElement('span', { style: styles.summaryListLabel }, '氢气预付款金额'), React.createElement('span', { style: styles.summaryListValue }, rentalTotalHydrogen.toFixed(2) + ' 元')) + ); + var reqStarStyle = { color: '#ff4d4f', marginRight: 4 }; + var reqStar = React.createElement('span', { style: reqStarStyle }, '*'); + var rentalTh1 = React.createElement('th', { style: Object.assign({}, styles.rentalTh, { width: 50, verticalAlign: 'middle' }) }, '序号'); + var rentalTh2 = React.createElement('th', { style: Object.assign({}, styles.rentalTh, { width: 100 }) }, reqStar, '品牌'); + var rentalTh3 = React.createElement('th', { style: Object.assign({}, styles.rentalTh, { width: 100 }) }, reqStar, '型号'); + var rentalTh4 = React.createElement('th', { style: Object.assign({}, styles.rentalTh, { width: 120 }) }, '车牌号'); + var rentalTh5 = React.createElement('th', { style: Object.assign({}, styles.rentalTh, { width: 160 }) }, '车辆识别代码'); + var rentalTh6 = React.createElement('th', { style: Object.assign({}, styles.rentalTh, { width: 120 }) }, reqStar, '车辆月租金'); + var rentalTh7 = React.createElement('th', { style: Object.assign({}, styles.rentalTh, { width: 80, verticalAlign: 'middle' }) }, '服务费项目'); + var rentalTh8 = React.createElement('th', { style: Object.assign({}, styles.rentalTh, { width: 90, verticalAlign: 'middle' }) }, '服务费'); + var rentalTh9 = React.createElement('th', { style: Object.assign({}, styles.rentalTh, { width: 100 }) }, reqStar, '保证金'); + var rentalTh10 = React.createElement('th', { style: Object.assign({}, styles.rentalTh, { width: 80 }) }, '备注'); + var rentalTh11 = React.createElement('th', { style: Object.assign({}, styles.rentalTh, { width: 60, verticalAlign: 'middle' }) }, '操作'); + var rentalTableThead = React.createElement('thead', null, React.createElement('tr', null, rentalTh1, rentalTh2, rentalTh3, rentalTh4, rentalTh5, rentalTh6, rentalTh7, rentalTh8, rentalTh9, rentalTh10, rentalTh11)); + var rentalTableTbody = React.createElement('tbody', null, rentalTableBody); + var rentalTableEl = React.createElement('table', { style: styles.rentalTable }, rentalTableThead, rentalTableTbody); + var rentalTableWrap = React.createElement('div', { style: { overflowX: 'auto', marginBottom: 16 } }, rentalTableEl); + var rentalContent = React.createElement('div', null, rentalSummary, formErrors.rentalOrders ? React.createElement('div', { style: styles.errMsg }, formErrors.rentalOrders) : null, rentalTableWrap, React.createElement(Button, { type: 'dashed', style: { marginTop: 12, width: '100%' }, onClick: addRentalRow }, '添加一行'), hydrogenFormRow); + + var feeTableHeader3 = React.createElement('tr', null, React.createElement('th', { style: styles.rentalTh }, '项目'), React.createElement('th', { style: styles.rentalTh }, '收费标准'), React.createElement('th', { style: styles.rentalTh }, '服务费')); + var feeTableHeader5 = React.createElement('tr', null, React.createElement('th', { style: styles.rentalTh }, '类别'), React.createElement('th', { style: styles.rentalTh }, '损坏部位'), React.createElement('th', { style: styles.rentalTh }, '配件'), React.createElement('th', { style: styles.rentalTh }, '数量'), React.createElement('th', { style: styles.rentalTh }, '费用明细')); + var makeFeeRow3 = function(r, i) { + var td1 = React.createElement('td', { style: styles.rentalTd }, r.project); + var td2 = React.createElement('td', { style: styles.rentalTd }, r.standard); + var td3 = React.createElement('td', { style: styles.rentalTd }, r.serviceFee); + return React.createElement('tr', { key: i }, td1, td2, td3); + }; + var makeFeeRow5 = function(r, i) { + var td1 = React.createElement('td', { style: styles.rentalTd }, r.category); + var td2 = React.createElement('td', { style: styles.rentalTd }, r.part); + var td3 = React.createElement('td', { style: styles.rentalTd }, r.partName); + var td4 = React.createElement('td', { style: styles.rentalTd }, r.qty); + var td5 = React.createElement('td', { style: styles.rentalTd }, r.feeDetail); + return React.createElement('tr', { key: i }, td1, td2, td3, td4, td5); + }; + var feeCertRows = feeTemplateCertFees.map(makeFeeRow3); + var feePenaltyRows = feeTemplatePenaltyFees.map(makeFeeRow3); + var feeConsumablesRows = feeTemplateConsumables.map(makeFeeRow5); + var feeOtherRows = feeTemplateOtherFees.map(makeFeeRow3); + var feeCertTable = React.createElement('table', { style: styles.rentalTable }, React.createElement('thead', null, feeTableHeader3), React.createElement('tbody', null, feeCertRows)); + var feePenaltyTable = React.createElement('table', { style: styles.rentalTable }, React.createElement('thead', null, feeTableHeader3), React.createElement('tbody', null, feePenaltyRows)); + var feeConsumablesTable = React.createElement('table', { style: styles.rentalTable }, React.createElement('thead', null, feeTableHeader5), React.createElement('tbody', null, feeConsumablesRows)); + var feeOtherTable = React.createElement('table', { style: styles.rentalTable }, React.createElement('thead', null, feeTableHeader3), React.createElement('tbody', null, feeOtherRows)); + var feeTemplateSelect = React.createElement('div', { style: styles.formRow }, React.createElement(FormItem, { label: '选择费用模板', required: true, error: formErrors.feeTemplate }, React.createElement(Select, { placeholder: '请选择费用模板', style: { width: '100%' }, value: feeTemplate || undefined, onChange: function(v) { setFeeTemplate(v || ''); }, status: formErrors.feeTemplate ? 'error' : undefined }, feeTemplates.map(function(f, i) { return React.createElement(Option, { key: i, value: f }, f); })))); + var feeTemplateBody = feeTemplate ? React.createElement('div', null, + React.createElement('div', { style: Object.assign({}, styles.feeSectionTitle, styles.feeSectionTitleFirst) }, '证照补办费用'), + feeCertTable, + React.createElement('div', { style: styles.feeSectionTitle }, '违约金费用'), + feePenaltyTable, + React.createElement('div', { style: styles.feeSectionTitle }, '易损件信息'), + feeConsumablesTable, + React.createElement('div', { style: styles.feeSectionTitle }, '其他费用信息'), + feeOtherTable + ) : null; + var feeContent = React.createElement('div', null, feeTemplateSelect, feeTemplateBody); + + var billingBtnBase = { padding: '12px 16px', border: '1px solid #d9d9d9', borderRadius: 0, backgroundColor: '#fff', color: '#333', cursor: 'pointer', fontSize: 14, textAlign: 'left', flex: 1, minWidth: 0, width: 0, height: 'auto', display: 'flex', flexDirection: 'column', alignItems: 'flex-start', justifyContent: 'flex-start', overflow: 'visible' }; + var billingContent = React.createElement('div', null, + React.createElement('div', { style: { display: 'flex', border: formErrors.billingMethod ? '1px solid #ff4d4f' : '1px solid #d9d9d9', borderRadius: 4, overflow: 'hidden', alignItems: 'stretch' } }, + React.createElement(Button, { type: 'button', style: Object.assign({}, billingBtnBase, { borderRight: '1px solid #d9d9d9' }, billingMethod === 'month' ? { backgroundColor: '#1890ff', color: '#fff', borderColor: '#1890ff' } : {}), onClick: function() { setBillingMethod('month'); } }, React.createElement('div', { style: { width: '100%' } }, React.createElement('div', { style: { fontWeight: 600, marginBottom: 6 } }, '按自然月结算'), React.createElement('div', { style: { fontSize: 12, opacity: 0.9, whiteSpace: 'normal', wordBreak: 'break-word', lineHeight: 1.5 } }, '账单按照第一个月计费日期开始-当月最后一天为第一期,之后按自然月方式形成每一期账单,例如付款周期为2个月,则第二期账单从2月1日-3月31日。'))), + React.createElement(Button, { type: 'button', style: Object.assign({}, billingBtnBase, billingMethod === 'period' ? { backgroundColor: '#1890ff', color: '#fff', borderColor: '#1890ff' } : {}), onClick: function() { setBillingMethod('period'); } }, React.createElement('div', { style: { width: '100%' } }, React.createElement('div', { style: { fontWeight: 600, marginBottom: 6 } }, '按付款周期天数结算'), React.createElement('div', { style: { fontSize: 12, opacity: 0.9, whiteSpace: 'normal', wordBreak: 'break-word', lineHeight: 1.5 } }, '账单按照合同基本信息中付款周期实际天数形成一期账单,例如付款周期为60天,则该账单账期为60天,每60天会自动生成新一期账单。'))) + ), + formErrors.billingMethod ? React.createElement('div', { style: styles.errMsg }, formErrors.billingMethod) : null + ); + + var serviceModalRows = serviceModalRowIndex !== null && rentalOrders[serviceModalRowIndex] ? rentalOrders[serviceModalRowIndex].serviceItems : []; + var serviceModalContent = serviceModalRowIndex !== null ? React.createElement('div', { style: styles.modalMask, onClick: function(e) { if (e.target === e.currentTarget) closeServiceModal(); } }, React.createElement('div', { style: styles.modalBox, onClick: function(e) { e.stopPropagation(); } }, React.createElement('div', { style: styles.modalHeader }, '服务项目'), React.createElement('div', { style: styles.modalBody }, React.createElement('table', { style: styles.rentalTable }, React.createElement('thead', null, React.createElement('tr', null, React.createElement('th', { style: styles.rentalTh }, '服务项目'), React.createElement('th', { style: styles.rentalTh }, '费用'), React.createElement('th', { style: styles.rentalTh }, '生效时间'), React.createElement('th', { style: Object.assign({}, styles.rentalTh, { width: 80 }) }, '操作'))), React.createElement('tbody', null, serviceModalRows.map(function(si, siIdx) { var filteredServiceOpts = serviceItemOptions.filter(function(o) { return !serviceItemSearch || o.indexOf(serviceItemSearch) !== -1; }); return React.createElement('tr', { key: siIdx }, React.createElement('td', { style: styles.rentalTd }, React.createElement(Select, { style: { width: '100%' }, placeholder: '请选择服务项目', value: si.project || undefined, onChange: function(v) { updateServiceItem(siIdx, 'project', v || ''); }, showSearch: true, filterOption: function(input, opt) { return opt && opt.children && String(opt.children).toLowerCase().indexOf((input || '').toLowerCase()) >= 0; }, allowClear: true }, serviceItemOptions.map(function(opt, oi) { return React.createElement(Option, { key: oi, value: opt }, opt); }))), React.createElement('td', { style: styles.rentalTd }, React.createElement(Input, { placeholder: '0.00', value: si.fee || '', onChange: function(e) { updateServiceItem(siIdx, 'fee', e.target.value); }, addonAfter: '元', style: { width: '100%' } })), React.createElement('td', { style: styles.rentalTd }, React.createElement(DatePicker, { style: { width: '100%' }, format: 'YYYY-MM-DD', placeholder: '请选择生效时间', value: si.effectiveDate && window.moment ? window.moment(si.effectiveDate, 'YYYY-MM-DD') : null, onChange: function(d, dateStr) { updateServiceItem(siIdx, 'effectiveDate', dateStr || ''); } })), React.createElement('td', { style: styles.rentalTd }, React.createElement(Button, { type: 'link', size: 'small', danger: true, onClick: function() { removeServiceItem(siIdx); } }, '删除'))); }))), React.createElement(Button, { type: 'dashed', style: { marginTop: 12, width: '100%' }, onClick: addServiceItem }, '添加一行')), React.createElement('div', { style: { padding: '12px 20px', borderTop: '1px solid #f0f0f0', textAlign: 'right', display: 'flex', gap: 12, justifyContent: 'flex-end' } }, React.createElement(Button, { type: 'primary', onClick: function() { closeServiceModal(); } }, '保存'), React.createElement(Button, { onClick: closeServiceModal }, '关闭')))) : null; + + var reqSpecH1 = { fontSize: 16, fontWeight: 600, marginBottom: 12, color: '#333' }; + var reqSpecH2 = { fontSize: 14, fontWeight: 600, marginTop: 16, marginBottom: 8, color: '#333' }; + var reqSpecH3 = { fontSize: 13, fontWeight: 600, marginTop: 10, marginBottom: 6, color: '#333' }; + var reqSpecP = { fontSize: 13, lineHeight: 1.6, marginBottom: 6, color: '#555' }; + var reqSpecLi = { fontSize: 13, lineHeight: 1.6, marginBottom: 4, marginLeft: 20, color: '#555' }; + var reqSpecBlock = { marginBottom: 8 }; + var reqSpecDoc = React.createElement('div', { style: { padding: '0 4px' } }, + React.createElement('div', { style: reqSpecBlock }, React.createElement('div', { style: reqSpecH2 }, '1.面包屑:'), React.createElement('div', { style: reqSpecP }, '1.1.业务管理-车辆租赁合同-新增租赁合同')), + React.createElement('div', { style: reqSpecBlock }, React.createElement('div', { style: reqSpecH2 }, '2.客户基本信息卡片:'), React.createElement('div', { style: reqSpecP }, '2.1.用于从客户列表中选择客户,并将该合同绑定业务部门及业务负责人;'), React.createElement('div', { style: reqSpecLi }, '2.1.1.客户名称:必选项,选择器,支持从输入框内输入内容进行模糊搜索,从客户信息列表中选择对应客户;'), React.createElement('div', { style: reqSpecLi }, '2.1.2.客户统一信用代码:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「信用代码」字段;'), React.createElement('div', { style: reqSpecLi }, '2.1.3.客户地址:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「客户地址」字段;'), React.createElement('div', { style: reqSpecLi }, '2.1.4.客户联系人:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「客户联系人」字段;'), React.createElement('div', { style: reqSpecLi }, '2.1.5.客户电话:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「客户电话」字段;'), React.createElement('div', { style: reqSpecLi }, '2.1.6.客户电子邮箱:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「客户电子邮箱」字段;'), React.createElement('div', { style: reqSpecLi }, '2.1.7.企业名称:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「企业名称」字段;'), React.createElement('div', { style: reqSpecLi }, '2.1.8.企业电话:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「企业电话」字段;'), React.createElement('div', { style: reqSpecLi }, '2.1.9.邮寄地址:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「邮寄地址」字段;'), React.createElement('div', { style: reqSpecLi }, '2.1.10.开户银行:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「开户银行」字段;'), React.createElement('div', { style: reqSpecLi }, '2.1.11.银行账号:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「银行账号」字段;'), React.createElement('div', { style: reqSpecLi }, '2.1.12.纳税人识别号:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「纳税人识别号」字段;'), React.createElement('div', { style: reqSpecLi }, '2.1.13.业务部门:必选项,选择器,从部门表中选择该租赁合同对应部门;'), React.createElement('div', { style: reqSpecLi }, '2.1.14.业务负责人:必选项,选择器,从已选业务部门下拉取对应业务负责人,未选择业务部门时,业务负责人输入框获取焦点时进行错误提示:请先选择业务部门;'), React.createElement('div', { style: reqSpecLi }, '2.1.15.合同原件:必填项,点击上传按钮,上传本地文件,支持doc、docx、pdf等格式。已上传则显示文件名,后方为删除,删除后可重新点击上传附件进行重新上传;')), + React.createElement('div', { style: reqSpecBlock }, React.createElement('div', { style: reqSpecH2 }, '3.合同基本信息卡片:'), React.createElement('div', { style: reqSpecP }, '3.1.用于定义租赁合同基本情况和付款方式;'), React.createElement('div', { style: reqSpecLi }, '3.1.1.项目名称:必填项,输入框,用于定义该合同对应项目名称,默认提示信息"请输入项目名称";'), React.createElement('div', { style: reqSpecLi }, '3.1.2.合同编码:按照合同编码规则自动生成;'), React.createElement('div', { style: reqSpecLi }, '合同编码规则:[城市简写][合同类型][签约时间][业务部门代码][顺序流水号][签署状态]'), React.createElement('div', { style: reqSpecLi }, '3.1.2.1.地区简写:如上海为SH,嘉兴为JX;'), React.createElement('div', { style: reqSpecLi }, '3.1.2.2.合同类型:采购合同为CG、租赁合同为ZL、自营合同为ZY;'), React.createElement('div', { style: reqSpecLi }, '3.1.2.3.签约时间:显示合同签约时间,如20260216'), React.createElement('div', { style: reqSpecLi }, '3.1.2.4.业务部门代码:显示合同签约业务部门信息,如YW1代表业务1部,YW2代表业务2部;'), React.createElement('div', { style: reqSpecLi }, '3.1.2.5.顺序流水号:实施5位编号补零规则,如01235,代表是集团第1235份合同;'), React.createElement('div', { style: reqSpecLi }, '3.1.2.6.签署状态:A为正式合同,B为试用合同;'), React.createElement('div', { style: reqSpecLi }, '如编号为:JXZL20260216YW101235A,则代表嘉兴业务1部在2026年2月16日签署的租赁正式合同,在集团中为第1235份;'), React.createElement('div', { style: reqSpecLi }, '3.1.3.合同类型:必选项,选择器,合同类型分为「正式合同」「试用合同」;'), React.createElement('div', { style: reqSpecLi }, '3.1.4.生效日期:必选项,日期选择器,格式为YYYY-MM-DD,精确至天;'), React.createElement('div', { style: reqSpecLi }, '3.1.5.付款方式:必选项,付款方式分为「预付」「后付」两种;'), React.createElement('div', { style: reqSpecLi }, '3.1.5.1.如果选择预付,则每笔账单以付款时间及账单计算方式,在账单日自动提前生成下月账单,同时生成时以消息/待办提醒对应业务人员;'), React.createElement('div', { style: reqSpecLi }, '3.1.5.2.如果选择后付,则每笔账单以付款时间及账单计算方式,在账单日自动提前生成下月账单,但只在退还车时,才以消息/待办提醒合同对应业务人员;'), React.createElement('div', { style: reqSpecLi }, '3.1.6.主要车型:输入框(禁用状态),根据租赁订单信息中所选车型,自动反写入输入框并以标签形式显示,支持多车型显示;'), React.createElement('div', { style: reqSpecLi }, '3.1.7.结束日期:必选项,日期选择器,格式为YYYY-MM-DD,精确至天;'), React.createElement('div', { style: reqSpecLi }, '3.1.7.1.合同结束日期前30天将以消息提醒方式提醒;'), React.createElement('div', { style: reqSpecLi }, '3.1.8.付款周期:必选项,选择器,支持1个月-12个月 12种付款周期,账单将以此周期进行定时生成;'), React.createElement('div', { style: reqSpecLi }, '3.1.9.签约公司:必选项,选择器,从组织机构表中获取所有根组织机构(如嘉兴羚牛、上海羚牛、广东羚牛等),默认显示录入合同人员所在机构;'), React.createElement('div', { style: reqSpecLi }, '3.1.10.交车区域:必选项,地区选择器,支持省-市2级,选择区域后,该任务生成交车任务时会自动推送至该区域负责运维人员;'), React.createElement('div', { style: reqSpecLi }, '3.1.11.交车地点:输入框,支持自定义输入交车地点;'), React.createElement('div', { style: reqSpecLi }, '3.1.12.备注:文本域,支持自定义输入备注信息;')) + ); + var reqSpecDocPart2 = React.createElement('div', { style: { padding: '0 4px' } }, + React.createElement('div', { style: reqSpecBlock }, React.createElement('div', { style: reqSpecH2 }, '4.被授权人信息卡片:'), React.createElement('div', { style: reqSpecP }, '4.1.用于定义租赁合同相关被授权人相关信息,被授权人交车单完成时需要通过手机短信调取E签宝进行签字确认;'), React.createElement('div', { style: reqSpecLi }, '4.1.1.被授权人:必填项,输入框,用于输入被授权人信息;'), React.createElement('div', { style: reqSpecLi }, '4.1.2.被授权人联系电话:必填项,输入框,用于输入被授权人联系电话,该电话后续需要接收E签宝签字链接;'), React.createElement('div', { style: reqSpecLi }, '4.1.3.被授权人身份证:必填项,输入框,用于输入被授权人身份证信息;'), React.createElement('div', { style: reqSpecLi }, '4.1.4.支持通过新增/删除一行的方式,创建或管理多个授权人,后续交车单完成时可选择多个授权人;')), + React.createElement('div', { style: reqSpecBlock }, React.createElement('div', { style: reqSpecH2 }, '5.租赁订单信息卡片:'), React.createElement('div', { style: reqSpecP }, '5.1.用于定义租赁订单租赁车辆品牌、型号、月租金、服务费、保证金等相关信息;'), React.createElement('div', { style: reqSpecLi }, '5.1.1.卡片上方为租赁车辆总计数据,包括租赁车辆数、租金及服务费合计、保证金总额、氢气预付款金额等相关信息;'), React.createElement('div', { style: reqSpecLi }, '5.1.1.1.租赁车辆数:显示下方租赁订单信息包含多少辆车;'), React.createElement('div', { style: reqSpecLi }, '5.1.1.2.租金及服务费合计:显示下方租赁订单信息中车辆租金总额及服务费总额;'), React.createElement('div', { style: reqSpecLi }, '5.1.1.3.保证金总额:显示下方租赁订单信息中车辆保证金总额;'), React.createElement('div', { style: reqSpecLi }, '5.1.1.4.氢气预付款金额:显示氢气预付款金额,如客户选择自行承担氢费或羚牛承担氢费,则该处为0不计入氢气预付款金额;'), React.createElement('div', { style: reqSpecLi }, '5.1.2.下方为列表,显示序号、品牌、型号、车牌号、车辆识别代码、车辆月租金(元)、服务费项目、服务费、保证金、备注;'), React.createElement('div', { style: reqSpecLi }, '默认显示一行空数据;'), React.createElement('div', { style: reqSpecLi }, '5.1.2.1.序号:自动按照条数生成,规则为1、2、3....以此类推;'), React.createElement('div', { style: reqSpecLi }, '5.1.2.2.品牌:必选项,选择器,从型号参数库中「品牌」字段拉取所有品牌;'), React.createElement('div', { style: reqSpecLi }, '5.1.2.3.型号:必选项,选择器,从型号参数库中「型号」字段拉取所有品牌;'), React.createElement('div', { style: reqSpecLi }, '5.1.2.4.车牌号:选填项,选择器(支持从输入框输入车牌号关键字下拉匹配);'), React.createElement('div', { style: reqSpecLi }, '5.1.2.5.车辆识别代码:输入框(禁用),显示该车辆对应车辆识别代码,从车辆表直接拉取;'), React.createElement('div', { style: reqSpecLi }, '5.1.2.6.车辆月租金(元):输入框,支持两位小数,用于输入该车辆月租金金额,输入框后缀为元;'), React.createElement('div', { style: reqSpecLi }, '5.1.2.7.服务费项目:显示管理按钮,点击弹出卡片,卡片标题为:服务项目,下方列表显示服务项目、费用、生效时间、操作;'), React.createElement('div', { style: reqSpecLi }, '5.1.2.7.1.服务项目:必选项,选择器(支持输入框输入服务项目关键字进行下拉匹配),选项包含:代处理费用、罚款、违章处理违约金、未参加安全培训、车辆出险、年检年审违约、停车费、设备损坏金(包含易损件)、清洗费、上门收车人工费、上门收车送车行驶费、上门收车基础服务费、保险上浮、保养费用、补办驾驶证、补办牌照、补办营运证、补办加氢证、借用备用钥匙、补配钥匙、租金、氢气费-客、退还车氢量差、能源费补缴、能源费退款、送车上门人工费、送车上门送车行驶费、送车上门基础服务费、保证金、氢气预付费、维修费用、ETC-客、ETC卡缺损费、ETC设备缺损费、电费-客、未结算保养费、未结算维修费、车损费、工具损坏或丢失费、证件费、广告损坏费、送车服务费、接车服务费、补办行驶证、超赔险、轮胎磨损费、无忧包、轮胎保、养护保、尾板;'), React.createElement('div', { style: reqSpecLi }, '5.1.2.7.2.费用:必填项,输入框,支持2位小数,输入框后缀为元;'), React.createElement('div', { style: reqSpecLi }, '5.1.2.7.3.生效时间:必选项,日期选择器,格式为YYYY-MM-DD,服务项目会以此时间提前3天进行消息通知;'), React.createElement('div', { style: reqSpecLi }, '5.1.2.7.4.操作:删除,点击删除直接删除该行数据;'), React.createElement('div', { style: reqSpecLi }, '5.1.2.7.5.新增一行数据:点击添加一行服务项目;'), React.createElement('div', { style: reqSpecLi }, '5.1.3.氢费承担方:必选项,填充按钮组,选项为我方、客户,默认为客户,选择承担方为我方时,无需选择付款方式、输入氢气预付款;'), React.createElement('div', { style: reqSpecLi }, '5.1.4.付款方式:必选项,填充按钮组,选项为预付、月付款、自行结算;'), React.createElement('div', { style: reqSpecLi }, '5.1.4.1.预付:指合同签署时客户就需预先付出的氢费款项;'), React.createElement('div', { style: reqSpecLi }, '5.1.4.2.月付款:指合同签署后,客户按照每月氢费实际账单,进行支付,设置为月付款时,每月账期时会提示对应业务管理中心-能源部完善氢费账单;'), React.createElement('div', { style: reqSpecLi }, '5.1.4.3.自行结算:指合同签署后,所有氢气费用由客户自行承担;'), React.createElement('div', { style: reqSpecLi }, '5.1.5.氢气预付款:必填项,输入框,支持2位小数,当付款方式为预付时,显示该输入框,氢气预付款金额会并入该合同交车前首付款中一并结算,并计入5.1.1.4.氢气预付款金额中;'), React.createElement('div', { style: reqSpecLi }, '5.1.6.退还车氢气单价:必填项,输入框,支持2位小数,后缀为元,该金额主要用于约定退还车时,与交车时氢气差值以此费用进行结算;')) + ); + var reqSpecDocPart3 = React.createElement('div', { style: { padding: '0 4px' } }, + React.createElement('div', { style: reqSpecBlock }, React.createElement('div', { style: reqSpecH2 }, '6.其他费用信息卡片:'), React.createElement('div', { style: reqSpecP }, '6.1.用于选择对应费用模板,展示证照补办费用、违约金费用、易损件费用、其他费用信息;'), React.createElement('div', { style: reqSpecLi }, '6.1.1.选择费用模板:必选项,通过选择通过费用模板预设好的费用金额明细,自动将该模板所有环节费用显示在合同中;'), React.createElement('div', { style: reqSpecLi }, '6.1.2.证照补办费用:单独标题显示,内容为列表,列表中包括项目、收费标准、服务费,选择费用模板后自动反显;'), React.createElement('div', { style: reqSpecLi }, '6.1.3.违约金费用:单独标题显示,内容为列表,列表中包含项目、收费标准、服务费,选择费用模板后自动反显;'), React.createElement('div', { style: reqSpecLi }, '6.1.4.易损件信息:单独标题显示,内容为列表,列表中包含类别、损坏部位、配件、数量、费用明细,选择费用模板后自动反显;'), React.createElement('div', { style: reqSpecLi }, '6.1.5.其他费用信息:单独标题显示,内容为列表,列表中包含项目、收费标准、服务费,选择费用模板后自动反显;')), + React.createElement('div', { style: reqSpecBlock }, React.createElement('div', { style: reqSpecH2 }, '7.账单计算方式卡片:'), React.createElement('div', { style: reqSpecP }, '7.1.必选项,填充按钮组,默认为按自然月结算,需要在两种账单计算方式二选一,可手动修改,用于定义租赁合同的账单计算方式,分为按付款周期天数结算、按自然月结算两种方式;'), React.createElement('div', { style: reqSpecLi }, '7.1.1.按付款周期天数结算:账单按照合同基本信息卡片中付款周期实际天数形成一期账单,例如付款周期为60天,则该账单账期为60天,每60天会自动生成新一期账单;'), React.createElement('div', { style: reqSpecLi }, '7.1.2.按自然月结算:账单按照第一个月计费日期开始-当月最后一天为第一期,之后按照付款周期设置,每个月第一天到对应月份最后一天的自然月方式,形成每一期账单,例如付款周期为2个月,则第二期账单从2月1日-3月31日,以此类推;')), + React.createElement('div', { style: reqSpecBlock }, React.createElement('div', { style: reqSpecH2 }, '8.最下方为提交并审核、保存、取消三个按钮;'), React.createElement('div', { style: reqSpecLi }, '8.1.点击提交并审核,toast提示:租赁合同已提交审核。同时该租赁合同进入租赁合同审核列表中;'), React.createElement('div', { style: reqSpecLi }, '8.2.点击保存,会存储租赁订单已填写内容,并加入租赁合同列表中,该条数据只能操作人自己查看并编辑,其他人无法操作;'), React.createElement('div', { style: reqSpecLi }, '8.3.点击取消,如当前页面有已编辑内容时,点击取消会进行二次提示,内容为:取消将会丢失所有已填写内容,是否确认?点击确认返回车辆租赁合同列表页;')), + React.createElement('div', { style: reqSpecBlock }, React.createElement('div', { style: reqSpecP }, '所有卡片支持收起/展开功能,通过点击卡片右侧收起/展开实现;并增加锚点功能,锚点固定于页面右上角,点击锚点对应卡片名称,页面自动跳转至该卡片所在区域;')) + ); + var reqSpecModalContent = reqSpecOpen ? React.createElement('div', { style: styles.modalMask, onClick: function(e) { if (e.target === e.currentTarget) setReqSpecOpen(false); } }, React.createElement('div', { style: Object.assign({}, styles.modalBox, { maxWidth: 640 }), onClick: function(e) { e.stopPropagation(); } }, React.createElement('div', { style: styles.modalHeader }, '需求说明'), React.createElement('div', { style: Object.assign({}, styles.modalBody, { maxHeight: '70vh', padding: '20px 24px' }) }, reqSpecDoc, reqSpecDocPart2, reqSpecDocPart3), React.createElement('div', { style: { padding: '12px 20px', borderTop: '1px solid #f0f0f0', textAlign: 'right' } }, React.createElement(Button, { onClick: function() { setReqSpecOpen(false); } }, '关闭')))) : null; + + return React.createElement('div', { style: styles.page }, + React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 } }, React.createElement('div', { style: styles.breadcrumb }, React.createElement('span', null, '业务管理'), React.createElement('span', { style: styles.breadcrumbSep }, ' / '), React.createElement('span', null, '车辆租赁合同'), React.createElement('span', { style: styles.breadcrumbSep }, ' / '), React.createElement('span', { style: { color: '#1890ff' } }, '合同续签')), React.createElement('span', { style: { color: '#1890ff', cursor: 'pointer', fontSize: 14 }, onClick: function() { setReqSpecOpen(true); } }, '查看需求说明')), + React.createElement('div', { style: styles.anchorWrap }, + React.createElement('div', { style: { marginBottom: 8, fontWeight: 600, fontSize: 14 } }, '锚点导航'), + React.createElement('button', { style: styles.anchorItem, onClick: function() { scrollToCard('card-customer'); } }, '客户基本信息'), + React.createElement('button', { style: styles.anchorItem, onClick: function() { scrollToCard('card-contract'); } }, '合同基本信息'), + React.createElement('button', { style: styles.anchorItem, onClick: function() { scrollToCard('card-authorized'); } }, '被授权人信息'), + React.createElement('button', { style: styles.anchorItem, onClick: function() { scrollToCard('card-rental'); } }, '租赁订单信息'), + React.createElement('button', { style: styles.anchorItem, onClick: function() { scrollToCard('card-fee'); } }, '其他费用信息'), + React.createElement('button', { style: styles.anchorItem, onClick: function() { scrollToCard('card-billing'); } }, '账单计算方式') + ), + React.createElement('div', { id: 'card-customer' }, React.createElement(CardBlock, { id: 'card-customer', title: '客户基本信息', collapsed: cc1, setCollapsed: setCc1 }, customerFields)), + React.createElement('div', { id: 'card-contract', style: { marginTop: 16 } }, React.createElement(CardBlock, { title: '合同基本信息', collapsed: cc2, setCollapsed: setCc2 }, React.createElement('div', null, contractFormRow1, contractFormRow2))), + React.createElement('div', { id: 'card-authorized', style: { marginTop: 16 } }, React.createElement(CardBlock, { title: '被授权人信息', collapsed: cc3, setCollapsed: setCc3 }, authorizedContent)), + React.createElement('div', { id: 'card-rental', style: { marginTop: 16 } }, React.createElement(CardBlock, { title: '租赁订单信息', collapsed: cc4, setCollapsed: setCc4 }, rentalContent)), + React.createElement('div', { id: 'card-fee', style: { marginTop: 16 } }, React.createElement(CardBlock, { title: '其他费用信息', collapsed: cc5, setCollapsed: setCc5 }, feeContent)), + React.createElement('div', { id: 'card-billing', style: { marginTop: 16 } }, React.createElement(CardBlock, { title: '账单计算方式', collapsed: cc6, setCollapsed: setCc6 }, billingContent)), + React.createElement('div', { style: { height: 60 } }), + serviceModalContent, + reqSpecModalContent, + React.createElement('div', { style: styles.footer }, React.createElement(Button, { type: 'primary', onClick: function() { if (validateSubmitAndReview()) { message.success('租赁合同已提交审核。'); } } }, '提交并审核'), React.createElement(Button, { onClick: function() { message.info('保存,加入租赁合同列表(仅操作人可查看编辑)'); } }, '保存'), React.createElement(Button, { onClick: function() { message.info('取消'); } }, '取消')) + ); +}; diff --git a/web端/车辆租赁合同/新增租赁合同.jsx b/web端/车辆租赁合同/新增租赁合同.jsx new file mode 100644 index 0000000..9dc4b85 --- /dev/null +++ b/web端/车辆租赁合同/新增租赁合同.jsx @@ -0,0 +1,655 @@ +// 【重要】必须使用 const Component 作为组件变量名 - Axhub 产品原型 +// 车辆管理系统 - 新增租赁合同模块(整体实现,IE11+ 兼容,采用 antd) + +const Component = function() { + var antd = window.antd; + var Input = antd.Input; + var Select = antd.Select; + var Button = antd.Button; + var DatePicker = antd.DatePicker; + var Modal = antd.Modal; + var message = antd.message; + var Option = Select.Option; + + var cs1 = React.useState(''); + var customerSearch = cs1[0]; + var setCustomerSearch = cs1[1]; + var cs2 = React.useState(null); + var selectedCustomer = cs2[0]; + var setSelectedCustomer = cs2[1]; + var cs3 = React.useState(false); + var customerDropdownOpen = cs3[0]; + var setCustomerDropdownOpen = cs3[1]; + var cs4 = React.useState(''); + var businessDept = cs4[0]; + var setBusinessDept = cs4[1]; + var cs5 = React.useState(''); + var businessOwner = cs5[0]; + var setBusinessOwner = cs5[1]; + var cs6 = React.useState(''); + var ownerFocusError = cs6[0]; + var setOwnerFocusError = cs6[1]; + var contractOriginalRef = React.useRef(null); + var csContractOriginal = React.useState(null); + var contractOriginal = csContractOriginal[0]; + var setContractOriginal = csContractOriginal[1]; + + var bs1 = React.useState(''); + var projectName = bs1[0]; + var setProjectName = bs1[1]; + var bs2 = React.useState(''); + var contractType = bs2[0]; + var setContractType = bs2[1]; + var bs3 = React.useState('2026-02-16'); + var effectiveDate = bs3[0]; + var setEffectiveDate = bs3[1]; + var bs4 = React.useState(''); + var paymentMethod = bs4[0]; + var setPaymentMethod = bs4[1]; + var bs6 = React.useState('2027-02-16'); + var endDate = bs6[0]; + var setEndDate = bs6[1]; + var bs7 = React.useState('1'); + var paymentPeriod = bs7[0]; + var setPaymentPeriod = bs7[1]; + var bs8 = React.useState('嘉兴羚牛'); + var signingCompany = bs8[0]; + var setSigningCompany = bs8[1]; + var bs9 = React.useState(''); + var deliveryProvince = bs9[0]; + var setDeliveryProvince = bs9[1]; + var bs10 = React.useState(''); + var deliveryCity = bs10[0]; + var setDeliveryCity = bs10[1]; + var bs10b = React.useState(false); + var deliveryRegionOpen = bs10b[0]; + var setDeliveryRegionOpen = bs10b[1]; + var deliveryRegionClickInsideRef = React.useRef(false); + var bs11 = React.useState(''); + var deliveryLocation = bs11[0]; + var setDeliveryLocation = bs11[1]; + var bs12 = React.useState(''); + var remarks = bs12[0]; + var setRemarks = bs12[1]; + + var as1 = React.useState([{ name: '', phone: '', idCard: '' }]); + var authorizedList = as1[0]; + var setAuthorizedList = as1[1]; + + var emptyRentalRow = { brand: '', model: '', plateNo: '', vin: '', monthRent: '', serviceItems: [{ project: '', fee: '', effectiveDate: '' }], deposit: '', remark: '' }; + var os1 = React.useState([emptyRentalRow]); + var rentalOrders = os1[0]; + var setRentalOrders = os1[1]; + var os1b = React.useState(null); + var serviceModalRowIndex = os1b[0]; + var setServiceModalRowIndex = os1b[1]; + var os1c = React.useState('客户'); + var hydrogenBearer = os1c[0]; + var setHydrogenBearer = os1c[1]; + var os1d = React.useState('预付'); + var hydrogenPaymentMethod = os1d[0]; + var setHydrogenPaymentMethod = os1d[1]; + var os1e = React.useState(''); + var hydrogenPrepay = os1e[0]; + var setHydrogenPrepay = os1e[1]; + var os1f = React.useState(''); + var returnHydrogenPrice = os1f[0]; + var setReturnHydrogenPrice = os1f[1]; + var os1g = React.useState(null); + var plateNoFocusRow = os1g[0]; + var setPlateNoFocusRow = os1g[1]; + var os1h = React.useState(''); + var plateNoSearch = os1h[0]; + var setPlateNoSearch = os1h[1]; + var os1i = React.useState(null); + var serviceItemFocusRow = os1i[0]; + var setServiceItemFocusRow = os1i[1]; + var os1j = React.useState(''); + var serviceItemSearch = os1j[0]; + var setServiceItemSearch = os1j[1]; + var os1k = React.useState(null); + var plateNoDropdownRect = os1k[0]; + var setPlateNoDropdownRect = os1k[1]; + var os2 = React.useState(''); + var feeTemplate = os2[0]; + var setFeeTemplate = os2[1]; + var os3 = React.useState('month'); + var billingMethod = os3[0]; + var setBillingMethod = os3[1]; + + var cc1State = React.useState(false); + var cc1 = cc1State[0]; + var setCc1 = cc1State[1]; + var cc2State = React.useState(false); + var cc2 = cc2State[0]; + var setCc2 = cc2State[1]; + var cc3State = React.useState(false); + var cc3 = cc3State[0]; + var setCc3 = cc3State[1]; + var cc4State = React.useState(false); + var cc4 = cc4State[0]; + var setCc4 = cc4State[1]; + var cc5State = React.useState(false); + var cc5 = cc5State[0]; + var setCc5 = cc5State[1]; + var cc6State = React.useState(false); + var cc6 = cc6State[0]; + var setCc6 = cc6State[1]; + var reqSpecState = React.useState(false); + var reqSpecOpen = reqSpecState[0]; + var setReqSpecOpen = reqSpecState[1]; + var formErrorsState = React.useState({}); + var formErrors = formErrorsState[0]; + var setFormErrors = formErrorsState[1]; + + var customerList = [ + { id: '1', name: '嘉兴某某物流有限公司', creditCode: '91330400MA2XXXXX1', address: '浙江省嘉兴市南湖区科技大道1号', contact: '张三', phone: '13800138001', email: 'zhangsan@example.com', companyName: '嘉兴某某物流有限公司', companyPhone: '0571-88888888', mailingAddress: '浙江省嘉兴市南湖区科技大道1号', bank: '中国工商银行嘉兴分行', bankAccount: '6222021234567890123', taxId: '91330400MA2XXXXX1' }, + { id: '2', name: '上海某某运输公司', creditCode: '91310000MA2XXXXX2', address: '上海市浦东新区张江高科技园区', contact: '李四', phone: '13800138002', email: 'lisi@example.com', companyName: '上海某某运输公司', companyPhone: '021-66666666', mailingAddress: '上海市浦东新区张江高科技园区', bank: '中国建设银行上海分行', bankAccount: '6217001234567890123', taxId: '91310000MA2XXXXX2' }, + { id: '3', name: '杭州某某租赁有限公司', creditCode: '91330100MA2XXXXX3', address: '浙江省杭州市余杭区未来科技城', contact: '王五', phone: '13800138003', email: 'wangwu@example.com', companyName: '杭州某某租赁有限公司', companyPhone: '0571-99999999', mailingAddress: '浙江省杭州市余杭区未来科技城', bank: '中国农业银行杭州分行', bankAccount: '6228481234567890123', taxId: '91330100MA2XXXXX3' } + ]; + var deptList = [{ id: 'YW1', name: '业务1部', owners: ['张经理', '李专员', '王专员'] }, { id: 'YW2', name: '业务2部', owners: ['赵经理', '钱专员'] }, { id: 'YW3', name: '业务3部', owners: ['孙经理', '周专员'] }]; + var orgList = ['嘉兴羚牛', '上海羚牛', '广东羚牛']; + var regionList = [ + { province: '浙江省', cities: ['杭州市', '宁波市', '嘉兴市', '湖州市'] }, + { province: '上海市', cities: ['上海市'] }, + { province: '广东省', cities: ['广州市', '深圳市', '东莞市'] } + ]; + var feeTemplates = ['标准费用模板A', '标准费用模板B', '定制费用模板C']; + var feeTemplateCertFees = [{ project: '补办行驶证', standard: '50元/次', serviceFee: '20' }, { project: '补办驾驶证', standard: '30元/次', serviceFee: '10' }, { project: '补办牌照', standard: '100元/次', serviceFee: '50' }]; + var feeTemplatePenaltyFees = [{ project: '提前退车违约金', standard: '月租金×1', serviceFee: '0' }, { project: '违章处理违约金', standard: '按实际发生', serviceFee: '50' }]; + var feeTemplateConsumables = [{ category: '轮胎', part: '前轮', partName: '轮胎A型', qty: 1, feeDetail: '500.00' }, { category: '易损件', part: '雨刮', partName: '雨刮片', qty: 2, feeDetail: '80.00' }]; + var feeTemplateOtherFees = [{ project: '上门送车费', standard: '100元/次', serviceFee: '50' }, { project: '上门收车费', standard: '100元/次', serviceFee: '50' }, { project: '清洗费', standard: '80元/次', serviceFee: '30' }]; + var brandList = ['品牌A', '品牌B', '品牌C', '品牌D']; + var modelByBrand = { '品牌A': ['型号A1', '型号A2', '型号A3'], '品牌B': ['型号B1', '型号B2'], '品牌C': ['型号C1', '型号C2', '型号C3'], '品牌D': ['型号D1'] }; + var vehicleList = [{ plateNo: '浙A10001', vin: 'L1234567890ABCDEF' }, { plateNo: '浙B20002', vin: 'L2234567890ABCDEF' }, { plateNo: '沪A30003', vin: 'L3234567890ABCDEF' }, { plateNo: '粤A40004', vin: 'L4234567890ABCDEF' }]; + var serviceItemOptions = ['代处理费用', '罚款', '违章处理违约金', '未参加安全培训', '车辆出险', '年检年审违约', '停车费', '设备损坏金(包含易损件)', '清洗费', '上门收车人工费', '上门收车送车行驶费', '上门收车基础服务费', '保险上浮', '保养费用', '补办驾驶证', '补办牌照', '补办营运证', '补办加氢证', '借用备用钥匙', '补配钥匙', '租金', '氢气费-客', '退还车氢量差', '能源费补缴', '能源费退款', '送车上门人工费', '送车上门送车行驶费', '送车上门基础服务费', '保证金', '氢气预付费', '维修费用', 'ETC-客', 'ETC卡缺损费', 'ETC设备缺损费', '电费-客', '未结算保养费', '未结算维修费', '车损费', '工具损坏或丢失费', '证件费', '广告损坏费', '送车服务费', '接车服务费', '补办行驶证', '超赔险', '轮胎磨损费', '无忧包', '轮胎保', '养护保', '尾板']; + + var filteredCustomers = customerList.filter(function(c) { return !customerSearch || c.name.indexOf(customerSearch) !== -1; }); + var currentDept = deptList.find(function(d) { return d.id === businessDept; }); + var ownerOptions = currentDept ? currentDept.owners : []; + var contractCode = (function() { + var cityCode = 'JX'; + var typeCode = 'ZL'; + var signCode = contractType === '正式合同' ? 'A' : 'B'; + var dateStr = effectiveDate ? effectiveDate.replace(/-/g, '') : '20260216'; + var deptCode = businessDept || 'YW1'; + return cityCode + typeCode + dateStr + deptCode + '01235' + signCode; + })(); + var mainVehicleModelsDisplay = (function() { + var models = []; + var seen = {}; + for (var i = 0; i < rentalOrders.length; i++) { + var m = rentalOrders[i].model; + if (m && !seen[m]) { seen[m] = true; models.push(m); } + } + return models.join('、'); + })(); + var calcRowServiceFee = function(row) { + var sum = 0; + for (var i = 0; i < (row.serviceItems || []).length; i++) { + var fee = parseFloat(row.serviceItems[i].fee); + if (!isNaN(fee)) sum += fee; + } + return sum.toFixed(2); + }; + var rentalTotalVehicles = rentalOrders.length; + var rentalTotalRentService = rentalOrders.reduce(function(s, r) { return s + (parseFloat(r.monthRent) || 0) + (parseFloat(calcRowServiceFee(r)) || 0); }, 0); + var rentalTotalDeposit = rentalOrders.reduce(function(s, r) { return s + (parseFloat(r.deposit) || 0); }, 0); + var rentalTotalHydrogen = (hydrogenPaymentMethod === '预付' && hydrogenBearer === '客户') ? (parseFloat(hydrogenPrepay) || 0) : 0; + + var selectCustomer = function(c) { + setSelectedCustomer(c); + setCustomerSearch(c ? c.name : ''); + setCustomerDropdownOpen(false); + }; + var deliveryRegionDisplay = deliveryProvince && deliveryCity ? deliveryProvince + ' / ' + deliveryCity : ''; + var selectDeliveryRegion = function(province, city) { + setDeliveryProvince(province); + setDeliveryCity(city); + setDeliveryRegionOpen(false); + }; + var scrollToCard = function(id) { + var el = document.getElementById(id); + if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' }); + }; + var handleOwnerFocus = function() { + if (!businessDept) setOwnerFocusError('请先选择业务部门'); + else setOwnerFocusError(''); + }; + var handleOwnerBlur = function() { setOwnerFocusError(''); }; + var openContractOriginal = function() { + if (contractOriginal && contractOriginal.file) { + var url = URL.createObjectURL(contractOriginal.file); + window.open(url); + } + }; + + var validateSubmitAndReview = function() { + var errs = {}; + if (!selectedCustomer) errs.customer = '请选择客户'; + if (!businessDept) errs.businessDept = '请选择业务部门'; + if (!contractOriginal) errs.contractOriginal = '请上传合同原件'; + if (!businessOwner) errs.businessOwner = '请选择业务负责人'; + if (!projectName || !projectName.trim()) errs.projectName = '请输入项目名称'; + if (!contractType) errs.contractType = '请选择合同类型'; + if (!effectiveDate) errs.effectiveDate = '请选择生效日期'; + if (!paymentMethod) errs.paymentMethod = '请选择付款方式'; + if (!endDate) errs.endDate = '请选择结束日期'; + if (!paymentPeriod) errs.paymentPeriod = '请选择付款周期'; + if (!signingCompany) errs.signingCompany = '请选择签约公司'; + if (!deliveryProvince || !deliveryCity) errs.deliveryRegion = '请选择交车区域'; + var authInvalid = authorizedList.some(function(a) { return !a.name || !a.name.trim() || !a.phone || !a.phone.trim() || !a.idCard || !a.idCard.trim(); }); + if (authInvalid) errs.authorizedList = '请完整填写被授权人姓名、联系电话、身份证'; + var rentalInvalid = rentalOrders.some(function(r) { return !r.brand || !r.model || !(r.monthRent && String(r.monthRent).trim()) || !(r.deposit && String(r.deposit).trim()); }); + if (rentalInvalid) errs.rentalOrders = '请完整填写租赁订单的品牌、型号、车辆月租金、保证金'; + if (!hydrogenBearer) errs.hydrogenBearer = '请选择氢费承担方'; + if (hydrogenBearer === '客户' && !hydrogenPaymentMethod) errs.hydrogenPaymentMethod = '请选择付款方式'; + if (hydrogenBearer === '客户' && hydrogenPaymentMethod === '预付' && (!hydrogenPrepay || !String(hydrogenPrepay).trim())) errs.hydrogenPrepay = '请输入氢气预付款'; + if (!returnHydrogenPrice || !String(returnHydrogenPrice).trim()) errs.returnHydrogenPrice = '请输入退还车氢气单价'; + if (!feeTemplate) errs.feeTemplate = '请选择费用模板'; + if (!billingMethod) errs.billingMethod = '请选择账单计算方式'; + setFormErrors(errs); + if (Object.keys(errs).length > 0) { + var firstId = errs.customer || errs.businessDept || errs.businessOwner ? 'card-customer' : (errs.projectName || errs.contractType || errs.effectiveDate || errs.paymentMethod || errs.endDate || errs.paymentPeriod || errs.signingCompany || errs.deliveryRegion || errs.contractOriginal) ? 'card-contract' : errs.authorizedList ? 'card-authorized' : (errs.rentalOrders || errs.hydrogenBearer || errs.hydrogenPaymentMethod || errs.hydrogenPrepay || errs.returnHydrogenPrice) ? 'card-rental' : errs.feeTemplate ? 'card-fee' : 'card-billing'; + setCc1(false); setCc2(false); setCc3(false); setCc4(false); setCc5(false); setCc6(false); + setTimeout(function() { var el = document.getElementById(firstId); if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' }); }, 100); + return false; + } + return true; + }; + + var addAuthorized = function() { + setAuthorizedList(authorizedList.concat([{ name: '', phone: '', idCard: '' }])); + }; + var removeAuthorized = function(index) { + var next = authorizedList.slice(0); + next.splice(index, 1); + setAuthorizedList(next.length ? next : [{ name: '', phone: '', idCard: '' }]); + }; + var updateAuthorized = function(index, field, value) { + var next = authorizedList.slice(0); + var cur = next[index] || {}; + var patch = {}; + patch[field] = value; + next[index] = Object.assign({}, cur, patch); + setAuthorizedList(next); + }; + + var addRentalRow = function() { + setRentalOrders(rentalOrders.concat([{ brand: '', model: '', plateNo: '', vin: '', monthRent: '', serviceItems: [{ project: '', fee: '', effectiveDate: '' }], deposit: '', remark: '' }])); + }; + var removeRentalRow = function(index) { + var next = rentalOrders.slice(0); + next.splice(index, 1); + setRentalOrders(next.length ? next : [{ brand: '', model: '', plateNo: '', vin: '', monthRent: '', serviceItems: [{ project: '', fee: '', effectiveDate: '' }], deposit: '', remark: '' }]); + }; + var updateRentalOrder = function(index, field, value) { + var next = rentalOrders.slice(0); + var cur = next[index] || {}; + var newRow = Object.assign({}, cur); + newRow[field] = value; + if (field === 'plateNo') { + var v = vehicleList.find(function(x) { return x.plateNo === value; }); + newRow.vin = v ? v.vin : ''; + } + if (field === 'brand') newRow.model = ''; + next[index] = newRow; + setRentalOrders(next); + }; + var openServiceModal = function(index) { setServiceModalRowIndex(index); }; + var closeServiceModal = function() { setServiceModalRowIndex(null); }; + var addServiceItem = function() { + if (serviceModalRowIndex === null) return; + var next = rentalOrders.slice(0); + var row = next[serviceModalRowIndex]; + var items = (row.serviceItems || []).concat([{ project: '', fee: '', effectiveDate: '' }]); + var newRow = {}; + for (var k in row) { if (row.hasOwnProperty(k) && k !== 'serviceItems') newRow[k] = row[k]; } + newRow.serviceItems = items; + next[serviceModalRowIndex] = newRow; + setRentalOrders(next); + }; + var removeServiceItem = function(siIndex) { + if (serviceModalRowIndex === null) return; + var next = rentalOrders.slice(0); + var row = next[serviceModalRowIndex]; + var items = (row.serviceItems || []).slice(0); + items.splice(siIndex, 1); + if (items.length === 0) items = [{ project: '', fee: '', effectiveDate: '' }]; + var newRow = {}; + for (var k in row) { if (row.hasOwnProperty(k) && k !== 'serviceItems') newRow[k] = row[k]; } + newRow.serviceItems = items; + next[serviceModalRowIndex] = newRow; + setRentalOrders(next); + }; + var updateServiceItem = function(siIndex, field, value) { + if (serviceModalRowIndex === null) return; + var next = rentalOrders.slice(0); + var row = next[serviceModalRowIndex]; + var items = (row.serviceItems || []).slice(0); + var item = items[siIndex] || {}; + var newItem = {}; + for (var k in item) { if (item.hasOwnProperty(k)) newItem[k] = item[k]; } + newItem[field] = value; + items[siIndex] = newItem; + var newRow = {}; + for (var rk in row) { if (row.hasOwnProperty(rk) && rk !== 'serviceItems') newRow[rk] = row[rk]; } + newRow.serviceItems = items; + next[serviceModalRowIndex] = newRow; + setRentalOrders(next); + }; + + React.useEffect(function() { + if (!deliveryRegionOpen) return; + var handler = function(e) { + if (deliveryRegionClickInsideRef.current) { deliveryRegionClickInsideRef.current = false; return; } + var el = document.getElementById('delivery-region-wrap'); + if (el && !el.contains(e.target)) setDeliveryRegionOpen(false); + }; + document.addEventListener('mousedown', handler); + return function() { document.removeEventListener('mousedown', handler); }; + }, [deliveryRegionOpen]); + + React.useEffect(function() { + if (plateNoFocusRow === null) { setPlateNoDropdownRect(null); return; } + var timer = setTimeout(function() { + var el = document.getElementById('plate-no-input-' + plateNoFocusRow); + if (el && el.getBoundingClientRect) { + var rect = el.getBoundingClientRect(); + setPlateNoDropdownRect({ top: rect.bottom + 2, left: rect.left, width: rect.width }); + } else { setPlateNoDropdownRect(null); } + }, 0); + return function() { clearTimeout(timer); }; + }, [plateNoFocusRow]); + + var styles = { + page: { padding: '16px 24px 48px', backgroundColor: '#f5f5f5', minHeight: '100vh', fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif', fontSize: 14 }, + breadcrumb: { marginBottom: 16, color: '#666' }, + breadcrumbSep: { margin: '0 8px', color: '#999' }, + anchorWrap: { position: 'fixed', top: 80, right: 24, zIndex: 100, backgroundColor: '#fff', borderRadius: 8, boxShadow: '0 2px 8px rgba(0,0,0,0.12)', padding: '12px 16px', minWidth: 160 }, + anchorItem: { display: 'block', padding: '6px 0', color: '#1890ff', cursor: 'pointer', border: 'none', background: 'none', width: '100%', textAlign: 'left', fontSize: 13 }, + card: { backgroundColor: '#fff', borderRadius: 8, marginBottom: 16, boxShadow: '0 1px 2px rgba(0,0,0,0.05)', overflow: 'hidden' }, + cardHeader: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '16px 20px', borderBottom: '1px solid #f0f0f0', cursor: 'pointer' }, + cardTitle: { fontSize: 16, fontWeight: 600, color: '#333' }, + cardToggle: { color: '#999', fontSize: 14 }, + cardBody: { padding: '20px 24px' }, + formRow: { display: 'flex', flexWrap: 'wrap', marginBottom: 16 }, + formCol: { flex: '0 0 33.33%', minWidth: 200, paddingRight: 16, marginBottom: 8 }, + formColFull: { flex: '0 0 100%', marginBottom: 8 }, + label: { display: 'block', marginBottom: 6, color: '#333' }, + labelRequired: { color: '#ff4d4f', marginRight: 4 }, + input: { width: '100%', padding: '8px 12px', border: '1px solid #d9d9d9', borderRadius: 4, fontSize: 14 }, + inputDisabled: { backgroundColor: '#f5f5f5', color: '#999', cursor: 'not-allowed' }, + inputError: { borderColor: '#ff4d4f' }, + select: { width: '100%', padding: '8px 12px', border: '1px solid #d9d9d9', borderRadius: 4, fontSize: 14 }, + textarea: { width: '100%', padding: '8px 12px', border: '1px solid #d9d9d9', borderRadius: 4, fontSize: 14, minHeight: 80, resize: 'vertical' }, + errMsg: { color: '#ff4d4f', fontSize: 12, marginTop: 4 }, + summaryList: { marginBottom: 16, display: 'flex', flexWrap: 'wrap', gap: 16 }, + summaryListItem: { flex: '0 0 calc(50% - 8px)', display: 'flex', alignItems: 'center', padding: '12px 16px', border: '1px solid #e8e8e8', borderRadius: 4, backgroundColor: '#fafafa', fontSize: 14, boxSizing: 'border-box' }, + summaryListLabel: { flex: '0 0 140px', color: '#666' }, + summaryListValue: { flex: 1, fontWeight: 600, color: '#333' }, + authRow: { display: 'flex', gap: 12, alignItems: 'flex-start', marginBottom: 12 }, + authInput: { flex: 1, padding: '8px 12px', border: '1px solid #d9d9d9', borderRadius: 4 }, + btnDel: { padding: '8px 16px', color: '#ff4d4f', border: '1px solid #ff4d4f', borderRadius: 4, backgroundColor: '#fff', cursor: 'pointer' }, + btnAdd: { padding: '8px 16px', color: '#1890ff', border: '1px dashed #1890ff', borderRadius: 4, backgroundColor: '#fff', cursor: 'pointer', marginBottom: 16 }, + footer: { position: 'fixed', bottom: 0, left: 0, right: 0, padding: '12px 24px', backgroundColor: '#fff', borderTop: '1px solid #e8e8e8', display: 'flex', gap: 12, justifyContent: 'flex-start', zIndex: 99 }, + btn: { padding: '8px 24px', borderRadius: 4, border: '1px solid #d9d9d9', cursor: 'pointer', fontSize: 14 }, + btnPrimary: { backgroundColor: '#1890ff', color: '#fff', borderColor: '#1890ff' }, + btnDefault: { backgroundColor: '#fff', color: '#333' }, + tag: { display: 'inline-block', padding: '2px 8px', marginRight: 8, marginBottom: 4, backgroundColor: '#e6f7ff', color: '#1890ff', borderRadius: 4, fontSize: 12 }, + regionCascader: { position: 'absolute', top: '100%', left: 0, right: 0, marginTop: 4, backgroundColor: '#fff', border: '1px solid #d9d9d9', borderRadius: 4, boxShadow: '0 2px 8px rgba(0,0,0,0.12)', zIndex: 10, display: 'flex', minHeight: 200 }, + regionCascaderCol: { flex: 1, borderRight: '1px solid #f0f0f0', overflowY: 'auto' }, + regionCascaderColLast: { flex: 1 }, + regionCascaderItem: { padding: '10px 12px', cursor: 'pointer' }, + rentalTable: { width: '100%', borderCollapse: 'collapse', fontSize: 13 }, + rentalTh: { padding: '10px 8px', textAlign: 'left', borderBottom: '1px solid #e8e8e8', backgroundColor: '#fafafa', fontWeight: 600 }, + rentalTd: { padding: '8px', borderBottom: '1px solid #f0f0f0', verticalAlign: 'top' }, + rentalTdCenter: { padding: '8px', borderBottom: '1px solid #f0f0f0', verticalAlign: 'middle' }, + rentalInput: { width: '100%', padding: '6px 10px', border: '1px solid #d9d9d9', borderRadius: 4, fontSize: 13 }, + rentalInputDisabled: { backgroundColor: '#f5f5f5', color: '#999' }, + modalMask: { position: 'fixed', left: 0, top: 0, right: 0, bottom: 0, backgroundColor: 'rgba(0,0,0,0.45)', zIndex: 1000, display: 'flex', alignItems: 'center', justifyContent: 'center' }, + modalBox: { backgroundColor: '#fff', borderRadius: 8, width: '90%', maxWidth: 720, maxHeight: '85vh', overflow: 'hidden', display: 'flex', flexDirection: 'column', boxShadow: '0 4px 20px rgba(0,0,0,0.15)' }, + modalHeader: { padding: '16px 20px', borderBottom: '1px solid #f0f0f0', fontSize: 16, fontWeight: 600 }, + modalBody: { padding: 20, overflow: 'auto', flex: 1 }, + btnGroup: { display: 'inline-flex', border: '1px solid #d9d9d9', borderRadius: 4, overflow: 'hidden' }, + btnGroupItem: { padding: '8px 16px', border: 'none', borderRight: '1px solid #d9d9d9', backgroundColor: '#fff', color: '#333', cursor: 'pointer', fontSize: 14 }, + btnGroupItemLast: { borderRight: 'none' }, + btnGroupItemActive: { backgroundColor: '#1890ff', color: '#fff', borderColor: '#1890ff', borderRightColor: '#1890ff' }, + feeSectionTitle: { fontSize: 15, fontWeight: 600, color: '#333', marginTop: 20, marginBottom: 10 }, + feeSectionTitleFirst: { marginTop: 0 }, + modalFormInput: { width: '100%', padding: '8px 12px', border: '1px solid #d9d9d9', borderRadius: 4, fontSize: 14, height: 36, boxSizing: 'border-box' } + }; + + var CardBlock = function(props) { + return React.createElement('div', { id: props.id, style: styles.card }, + React.createElement('div', { style: styles.cardHeader, onClick: function() { props.setCollapsed(!props.collapsed); } }, + React.createElement('span', { style: styles.cardTitle }, props.title), + React.createElement('span', { style: styles.cardToggle }, props.collapsed ? '展开' : '收起') + ), + !props.collapsed ? React.createElement('div', { style: styles.cardBody }, props.children) : null + ); + }; + + var FormItem = function(props) { + var colStyle = props.fullWidth ? styles.formColFull : (props.colStyle ? Object.assign({}, styles.formCol, props.colStyle) : styles.formCol); + return React.createElement('div', { style: colStyle }, + React.createElement('label', { style: styles.label }, props.required ? React.createElement('span', { style: styles.labelRequired }, '*') : null, props.label), + props.children, + props.error ? React.createElement('div', { style: styles.errMsg }, props.error) : null + ); + }; + + var customerOptions = customerList.map(function(c) { return React.createElement(Option, { key: c.id, value: c.id }, c.name); }); + var customerFields = React.createElement('div', { style: styles.formRow }, + React.createElement(FormItem, { label: '客户名称', required: true, error: formErrors.customer }, React.createElement(Select, { placeholder: '请选择或输入搜索客户', style: { width: '100%' }, value: selectedCustomer ? selectedCustomer.id : undefined, onChange: function(id) { var c = customerList.find(function(x) { return x.id === id; }); selectCustomer(c || null); }, showSearch: true, allowClear: true, filterOption: function(input, opt) { return opt.children && String(opt.children).toLowerCase().indexOf((input || '').toLowerCase()) >= 0; }, status: formErrors.customer ? 'error' : undefined }, customerOptions)), + React.createElement(FormItem, { label: '客户统一信用代码' }, React.createElement(Input, { value: selectedCustomer ? selectedCustomer.creditCode : '', disabled: true, style: { width: '100%' } })), + React.createElement(FormItem, { label: '客户地址' }, React.createElement(Input, { value: selectedCustomer ? selectedCustomer.address : '', disabled: true, style: { width: '100%' } })), + React.createElement(FormItem, { label: '客户联系人' }, React.createElement(Input, { value: selectedCustomer ? selectedCustomer.contact : '', disabled: true, style: { width: '100%' } })), + React.createElement(FormItem, { label: '客户电话' }, React.createElement(Input, { value: selectedCustomer ? selectedCustomer.phone : '', disabled: true, style: { width: '100%' } })), + React.createElement(FormItem, { label: '客户电子邮箱' }, React.createElement(Input, { value: selectedCustomer ? selectedCustomer.email : '', disabled: true, style: { width: '100%' } })), + React.createElement(FormItem, { label: '企业名称' }, React.createElement(Input, { value: selectedCustomer ? selectedCustomer.companyName : '', disabled: true, style: { width: '100%' } })), + React.createElement(FormItem, { label: '企业电话' }, React.createElement(Input, { value: selectedCustomer ? selectedCustomer.companyPhone : '', disabled: true, style: { width: '100%' } })), + React.createElement(FormItem, { label: '邮寄地址' }, React.createElement(Input, { value: selectedCustomer ? selectedCustomer.mailingAddress : '', disabled: true, style: { width: '100%' } })), + React.createElement(FormItem, { label: '开户银行' }, React.createElement(Input, { value: selectedCustomer ? selectedCustomer.bank : '', disabled: true, style: { width: '100%' } })), + React.createElement(FormItem, { label: '银行账号' }, React.createElement(Input, { value: selectedCustomer ? selectedCustomer.bankAccount : '', disabled: true, style: { width: '100%' } })), + React.createElement(FormItem, { label: '纳税人识别号' }, React.createElement(Input, { value: selectedCustomer ? selectedCustomer.taxId : '', disabled: true, style: { width: '100%' } })), + React.createElement(FormItem, { label: '业务部门', required: true, error: formErrors.businessDept }, React.createElement(Select, { placeholder: '请选择业务部门', style: { width: '100%' }, value: businessDept || undefined, onChange: function(v) { setBusinessDept(v || ''); setBusinessOwner(''); }, status: formErrors.businessDept ? 'error' : undefined }, deptList.map(function(d, i) { return React.createElement(Option, { key: i, value: d.id }, d.name); }))), + React.createElement(FormItem, { label: '业务负责人', required: true, error: formErrors.businessOwner || ownerFocusError }, React.createElement(Select, { placeholder: businessDept ? '请选择' : '请先选择业务部门', style: { width: '100%' }, value: businessOwner || undefined, onChange: function(v) { setBusinessOwner(v || ''); setOwnerFocusError(''); }, onFocus: handleOwnerFocus, onBlur: handleOwnerBlur, disabled: !businessDept, status: (formErrors.businessOwner || ownerFocusError) ? 'error' : undefined }, ownerOptions.map(function(o, i) { return React.createElement(Option, { key: i, value: o }, o); }))) + ); + + var contractFormRow1 = React.createElement('div', { style: styles.formRow }, + React.createElement(FormItem, { label: '项目名称', required: true, error: formErrors.projectName }, React.createElement(Input, { placeholder: '请输入项目名称', value: projectName, onChange: function(e) { setProjectName(e.target.value); }, status: formErrors.projectName ? 'error' : undefined, style: { width: '100%' } })), + React.createElement(FormItem, { label: '合同编码' }, React.createElement(Input, { value: contractCode, disabled: true, style: { width: '100%' } })), + React.createElement(FormItem, { label: '合同类型', required: true, error: formErrors.contractType }, React.createElement(Select, { placeholder: '请选择合同类型', style: { width: '100%' }, value: contractType || undefined, onChange: function(v) { setContractType(v || ''); }, status: formErrors.contractType ? 'error' : undefined }, React.createElement(Option, { value: '正式合同' }, '正式合同'), React.createElement(Option, { value: '试用合同' }, '试用合同'))), + React.createElement(FormItem, { label: '生效日期', required: true, error: formErrors.effectiveDate }, React.createElement(DatePicker, { style: { width: '100%' }, format: 'YYYY-MM-DD', placeholder: '请选择生效日期', value: effectiveDate && window.moment ? window.moment(effectiveDate, 'YYYY-MM-DD') : null, onChange: function(d, dateStr) { setEffectiveDate(dateStr || ''); }, status: formErrors.effectiveDate ? 'error' : undefined })), + React.createElement(FormItem, { label: '付款方式', required: true, error: formErrors.paymentMethod }, React.createElement(Select, { placeholder: '请选择付款方式', style: { width: '100%' }, value: paymentMethod || undefined, onChange: function(v) { setPaymentMethod(v || ''); }, status: formErrors.paymentMethod ? 'error' : undefined }, React.createElement(Option, { value: '预付' }, '预付'), React.createElement(Option, { value: '后付' }, '后付'))), + React.createElement(FormItem, { label: '主要车型' }, React.createElement('div', { style: { padding: '8px 12px', minHeight: 36, border: '1px solid #d9d9d9', borderRadius: 4, backgroundColor: '#f5f5f5', display: 'flex', flexWrap: 'wrap', gap: 8, alignItems: 'center' } }, mainVehicleModelsDisplay ? mainVehicleModelsDisplay.split('、').map(function(m, i) { return React.createElement('span', { key: i, style: styles.tag }, m); }) : React.createElement('span', { style: { color: '#999' } }, '根据租赁订单自动反写'))), + React.createElement(FormItem, { label: '结束日期', required: true, error: formErrors.endDate }, React.createElement(DatePicker, { style: { width: '100%' }, format: 'YYYY-MM-DD', placeholder: '请选择结束日期', value: endDate && window.moment ? window.moment(endDate, 'YYYY-MM-DD') : null, onChange: function(d, dateStr) { setEndDate(dateStr || ''); }, status: formErrors.endDate ? 'error' : undefined })), + React.createElement(FormItem, { label: '付款周期', required: true, error: formErrors.paymentPeriod }, React.createElement(Select, { placeholder: '请选择', style: { width: '100%' }, value: paymentPeriod || undefined, onChange: function(v) { setPaymentPeriod(v || ''); }, status: formErrors.paymentPeriod ? 'error' : undefined }, [1,2,3,4,5,6,7,8,9,10,11,12].map(function(n) { return React.createElement(Option, { key: n, value: String(n) }, n + '个月'); }))), + React.createElement(FormItem, { label: '签约公司', required: true, error: formErrors.signingCompany }, React.createElement(Select, { placeholder: '请选择', style: { width: '100%' }, value: signingCompany || undefined, onChange: function(v) { setSigningCompany(v || ''); }, status: formErrors.signingCompany ? 'error' : undefined }, orgList.map(function(o, i) { return React.createElement(Option, { key: i, value: o }, o); }))), + React.createElement('div', { style: styles.formCol }, + React.createElement(FormItem, { label: '交车区域', required: true, error: formErrors.deliveryRegion }, React.createElement('div', { id: 'delivery-region-wrap', style: { position: 'relative' } }, React.createElement(Input, { style: Object.assign({}, formErrors.deliveryRegion ? { borderColor: '#ff4d4f' } : {}, { cursor: 'pointer', caretColor: 'transparent', width: '100%' }), placeholder: '请选择省-市', value: deliveryRegionDisplay, readOnly: true, onClick: function() { setDeliveryRegionOpen(!deliveryRegionOpen); } }), deliveryRegionOpen ? React.createElement('div', { style: styles.regionCascader, onMouseDown: function() { deliveryRegionClickInsideRef.current = true; } }, React.createElement('div', { style: styles.regionCascaderCol }, regionList.map(function(r, i) { var isActive = r.province === deliveryProvince; return React.createElement('div', { key: i, style: Object.assign({}, styles.regionCascaderItem, isActive ? { backgroundColor: '#e6f7ff', color: '#1890ff' } : {}), onMouseEnter: function(e) { if (!isActive) e.currentTarget.style.backgroundColor = '#f5f5f5'; }, onMouseLeave: function(e) { if (!isActive) e.currentTarget.style.backgroundColor = 'transparent'; }, onMouseDown: function(e) { e.preventDefault(); setDeliveryProvince(r.province); setDeliveryCity(''); } }, r.province); })), React.createElement('div', { style: styles.regionCascaderColLast }, deliveryProvince ? (regionList.find(function(x) { return x.province === deliveryProvince; }) || { cities: [] }).cities.map(function(c, i) { var isActive = c === deliveryCity; return React.createElement('div', { key: i, style: Object.assign({}, styles.regionCascaderItem, isActive ? { backgroundColor: '#e6f7ff', color: '#1890ff' } : {}), onMouseEnter: function(e) { if (!isActive) e.currentTarget.style.backgroundColor = '#f5f5f5'; }, onMouseLeave: function(e) { if (!isActive) e.currentTarget.style.backgroundColor = 'transparent'; }, onMouseDown: function(e) { e.preventDefault(); selectDeliveryRegion(deliveryProvince, c); } }, c); }) : React.createElement('div', { style: { padding: 16, color: '#999', fontSize: 13 } }, '请先选择省'))) : null)), + React.createElement(FormItem, { label: '合同原件', required: true, error: formErrors.contractOriginal }, + contractOriginal + ? React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap' } }, + React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' } }, + React.createElement('a', { href: '#', style: { color: '#1890ff' }, onClick: function(e) { e.preventDefault(); openContractOriginal(); } }, contractOriginal.name), + contractOriginal.size || contractOriginal.uploadTime ? React.createElement('span', { style: { color: '#999', fontSize: 12 } }, (contractOriginal.size ? contractOriginal.size : '') + (contractOriginal.size && contractOriginal.uploadTime ? ' · ' : '') + (contractOriginal.uploadTime || '')) : null + ), + React.createElement(Button, { type: 'button', size: 'small', danger: true, onClick: function() { setContractOriginal(null); } }, '删除') + ) + : React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8 } }, + React.createElement('input', { ref: contractOriginalRef, type: 'file', style: { display: 'none' }, onChange: function(e) { + var f = e.target.files && e.target.files[0]; + if (f) { + var sizeDisplay = f.size >= 1024 * 1024 ? (f.size / 1024 / 1024).toFixed(1) + ' MB' : (f.size / 1024).toFixed(1) + ' KB'; + var now = window.moment ? window.moment() : new Date(); + var uploadTimeStr = window.moment ? now.format('YYYY-MM-DD HH:mm') : now.getFullYear() + '-' + String(now.getMonth() + 1).padStart(2, '0') + '-' + String(now.getDate()).padStart(2, '0') + ' ' + String(now.getHours()).padStart(2, '0') + ':' + String(now.getMinutes()).padStart(2, '0'); + setContractOriginal({ name: f.name, file: f, size: sizeDisplay, uploadTime: uploadTimeStr }); + } + e.target.value = ''; + } }), + React.createElement(Button, { type: 'default', style: { padding: '8px 16px', border: '1px solid #d9d9d9', borderRadius: 4, backgroundColor: '#fff', color: '#333', cursor: 'pointer', fontSize: 14 }, onClick: function() { if (contractOriginalRef.current) contractOriginalRef.current.click(); } }, '上传附件') + ) + ) + ), + React.createElement(FormItem, { label: '交车地点' }, React.createElement(Input, { placeholder: '请输入交车地点', value: deliveryLocation, onChange: function(e) { setDeliveryLocation(e.target.value); }, style: { width: '100%' } })) + ); + var contractFormRow2 = React.createElement('div', { style: styles.formRow }, React.createElement(FormItem, { label: '备注', fullWidth: true }, React.createElement(Input.TextArea, { placeholder: '请输入备注信息', value: remarks, onChange: function(e) { setRemarks(e.target.value); }, style: styles.textarea, rows: 4 }))); + + var authorizedContent = React.createElement('div', null, + React.createElement('div', { style: { display: 'flex', gap: 12, alignItems: 'center', marginBottom: 8 } }, React.createElement('span', { style: { flex: 1, fontWeight: 600, fontSize: 13 } }, React.createElement('span', { style: { color: '#ff4d4f', marginRight: 4 } }, '*'), '被授权人姓名'), React.createElement('span', { style: { flex: 1, fontWeight: 600, fontSize: 13 } }, React.createElement('span', { style: { color: '#ff4d4f', marginRight: 4 } }, '*'), '被授权人联系电话'), React.createElement('span', { style: { flex: 1, fontWeight: 600, fontSize: 13 } }, React.createElement('span', { style: { color: '#ff4d4f', marginRight: 4 } }, '*'), '被授权人身份证'), React.createElement('span', { style: { flex: '0 0 80px' } })), + authorizedList.map(function(item, index) { return React.createElement('div', { key: index, style: styles.authRow }, React.createElement(Input, { style: Object.assign({}, styles.authInput, formErrors.authorizedList ? { borderColor: '#ff4d4f' } : {}, { flex: 1 }), placeholder: '请输入被授权人姓名', value: item.name, onChange: function(e) { updateAuthorized(index, 'name', e.target.value); } }), React.createElement(Input, { style: Object.assign({}, styles.authInput, formErrors.authorizedList ? { borderColor: '#ff4d4f' } : {}, { flex: 1 }), placeholder: '请输入被授权人联系电话', value: item.phone, onChange: function(e) { updateAuthorized(index, 'phone', e.target.value); } }), React.createElement(Input, { style: Object.assign({}, styles.authInput, formErrors.authorizedList ? { borderColor: '#ff4d4f' } : {}, { flex: 1 }), placeholder: '请输入被授权人身份证号', value: item.idCard, onChange: function(e) { updateAuthorized(index, 'idCard', e.target.value); } }), React.createElement(Button, { type: 'button', danger: true, onClick: function() { removeAuthorized(index); } }, '删除')); }), + formErrors.authorizedList ? React.createElement('div', { style: styles.errMsg }, formErrors.authorizedList) : null, + React.createElement(Button, { type: 'dashed', style: { width: '100%', marginBottom: 16 }, onClick: addAuthorized }, '添加一行') + ); + + var hydrogenFormRow = (function() { + var hydrogenFields = []; + hydrogenFields.push(React.createElement(FormItem, { label: '氢费承担方', required: true, error: formErrors.hydrogenBearer }, React.createElement(Select, { style: { width: '100%' }, value: hydrogenBearer || undefined, onChange: function(v) { setHydrogenBearer(v || ''); setHydrogenPaymentMethod(v === '客户' ? '预付' : ''); }, status: formErrors.hydrogenBearer ? 'error' : undefined }, React.createElement(Option, { value: '我方' }, '我方'), React.createElement(Option, { value: '客户' }, '客户')))); + if (hydrogenBearer === '客户') { hydrogenFields.push(React.createElement(FormItem, { label: '付款方式', required: true, error: formErrors.hydrogenPaymentMethod }, React.createElement(Select, { style: { width: '100%' }, value: hydrogenPaymentMethod || undefined, onChange: function(v) { setHydrogenPaymentMethod(v || ''); }, status: formErrors.hydrogenPaymentMethod ? 'error' : undefined }, React.createElement(Option, { value: '预付' }, '预付'), React.createElement(Option, { value: '月付款' }, '月付款'), React.createElement(Option, { value: '自行结算' }, '自行结算')))); } + var hydrogenPrepayInput = React.createElement(Input, { placeholder: '0.00', value: hydrogenPrepay, onChange: function(e) { setHydrogenPrepay(e.target.value); }, addonAfter: '元', status: formErrors.hydrogenPrepay ? 'error' : undefined, style: { width: '100%' } }); + var returnHydrogenInput = React.createElement(Input, { placeholder: '0.00', value: returnHydrogenPrice, onChange: function(e) { setReturnHydrogenPrice(e.target.value); }, addonAfter: '元', status: formErrors.returnHydrogenPrice ? 'error' : undefined, style: { width: '100%' } }); + var returnHydrogenColStyle = hydrogenPaymentMethod === '预付' ? { flex: '1 1 0', minWidth: 180 } : { flex: '0 0 calc(50% - 8px)', minWidth: 180 }; + hydrogenFields.push(React.createElement('div', { key: 'hydrogen-amount-row', style: { display: 'flex', gap: 16, flex: '1 1 100%' } }, hydrogenPaymentMethod === '预付' ? React.createElement(FormItem, { label: '氢气预付款', required: true, error: formErrors.hydrogenPrepay, colStyle: { flex: '1 1 0', minWidth: 180 } }, hydrogenPrepayInput) : null, React.createElement(FormItem, { label: '退还车氢气单价', required: true, error: formErrors.returnHydrogenPrice, colStyle: returnHydrogenColStyle }, returnHydrogenInput))); + return React.createElement.apply(React, ['div', { style: Object.assign({}, styles.formRow, { marginTop: 20 }) }].concat(hydrogenFields)); + })(); + + var plateNoOptions = vehicleList.map(function(v) { return React.createElement(Option, { key: v.plateNo, value: v.plateNo }, v.plateNo); }); + var rentalTableBody = rentalOrders.map(function(row, idx) { + var modelOpts = row.brand ? (modelByBrand[row.brand] || []) : []; + return React.createElement('tr', { key: idx }, + React.createElement('td', { style: styles.rentalTdCenter }, idx + 1), + React.createElement('td', { style: styles.rentalTd }, React.createElement(Select, { style: { width: '100%', minWidth: 90 }, value: row.brand || undefined, onChange: function(v) { updateRentalOrder(idx, 'brand', v || ''); }, placeholder: '请选择' }, brandList.map(function(b, i) { return React.createElement(Option, { key: i, value: b }, b); }))), + React.createElement('td', { style: styles.rentalTd }, React.createElement(Select, { style: { width: '100%', minWidth: 90 }, value: row.model || undefined, onChange: function(v) { updateRentalOrder(idx, 'model', v || ''); }, disabled: !row.brand, placeholder: '请选择' }, modelOpts.map(function(m, i) { return React.createElement(Option, { key: i, value: m }, m); }))), + React.createElement('td', { style: styles.rentalTd }, React.createElement(Select, { style: { width: '100%' }, placeholder: '请选择或输入搜索', value: row.plateNo || undefined, onChange: function(v) { updateRentalOrder(idx, 'plateNo', v || ''); }, showSearch: true, allowClear: true, filterOption: function(input, opt) { return opt && opt.children && String(opt.children).toLowerCase().indexOf((input || '').toLowerCase()) >= 0; } }, plateNoOptions)), + React.createElement('td', { style: styles.rentalTd }, React.createElement(Input, { value: row.vin || '', disabled: true, style: { width: '100%' } })), + React.createElement('td', { style: styles.rentalTd }, React.createElement('div', { style: { display: 'flex', alignItems: 'center' } }, React.createElement(Input, { placeholder: '0.00', value: row.monthRent || '', onChange: function(e) { updateRentalOrder(idx, 'monthRent', e.target.value); }, style: styles.rentalInput }), React.createElement('span', { style: { marginLeft: 4, whiteSpace: 'nowrap' } }, '元'))), + React.createElement('td', { style: styles.rentalTdCenter }, React.createElement(Button, { type: 'link', size: 'small', onClick: function() { openServiceModal(idx); } }, '管理')), + React.createElement('td', { style: styles.rentalTdCenter }, calcRowServiceFee(row) + ' 元'), + React.createElement('td', { style: styles.rentalTd }, React.createElement('div', { style: { display: 'flex', alignItems: 'center' } }, React.createElement(Input, { placeholder: '0.00', value: row.deposit || '', onChange: function(e) { updateRentalOrder(idx, 'deposit', e.target.value); }, style: styles.rentalInput }), React.createElement('span', { style: { marginLeft: 4, whiteSpace: 'nowrap' } }, '元'))), + React.createElement('td', { style: styles.rentalTd }, React.createElement(Input, { placeholder: '备注', value: row.remark || '', onChange: function(e) { updateRentalOrder(idx, 'remark', e.target.value); }, style: Object.assign({}, styles.rentalInput, { width: '100%' }) })), + React.createElement('td', { style: styles.rentalTdCenter }, React.createElement(Button, { type: 'link', size: 'small', danger: true, onClick: function() { removeRentalRow(idx); } }, '删除')) + ); + }); + + var rentalSummary = React.createElement('div', { style: styles.summaryList }, + React.createElement('div', { style: styles.summaryListItem }, React.createElement('span', { style: styles.summaryListLabel }, '租赁车辆数'), React.createElement('span', { style: styles.summaryListValue }, rentalTotalVehicles + ' 辆')), + React.createElement('div', { style: styles.summaryListItem }, React.createElement('span', { style: styles.summaryListLabel }, '租金及服务费合计'), React.createElement('span', { style: styles.summaryListValue }, rentalTotalRentService.toFixed(2) + ' 元')), + React.createElement('div', { style: styles.summaryListItem }, React.createElement('span', { style: styles.summaryListLabel }, '保证金总额'), React.createElement('span', { style: styles.summaryListValue }, rentalTotalDeposit.toFixed(2) + ' 元')), + React.createElement('div', { style: styles.summaryListItem }, React.createElement('span', { style: styles.summaryListLabel }, '氢气预付款金额'), React.createElement('span', { style: styles.summaryListValue }, rentalTotalHydrogen.toFixed(2) + ' 元')) + ); + var reqStarStyle = { color: '#ff4d4f', marginRight: 4 }; + var reqStar = React.createElement('span', { style: reqStarStyle }, '*'); + var rentalTh1 = React.createElement('th', { style: Object.assign({}, styles.rentalTh, { width: 50, verticalAlign: 'middle' }) }, '序号'); + var rentalTh2 = React.createElement('th', { style: Object.assign({}, styles.rentalTh, { width: 100 }) }, reqStar, '品牌'); + var rentalTh3 = React.createElement('th', { style: Object.assign({}, styles.rentalTh, { width: 100 }) }, reqStar, '型号'); + var rentalTh4 = React.createElement('th', { style: Object.assign({}, styles.rentalTh, { width: 120 }) }, '车牌号'); + var rentalTh5 = React.createElement('th', { style: Object.assign({}, styles.rentalTh, { width: 160 }) }, '车辆识别代码'); + var rentalTh6 = React.createElement('th', { style: Object.assign({}, styles.rentalTh, { width: 120 }) }, reqStar, '车辆月租金'); + var rentalTh7 = React.createElement('th', { style: Object.assign({}, styles.rentalTh, { width: 80, verticalAlign: 'middle' }) }, '服务费项目'); + var rentalTh8 = React.createElement('th', { style: Object.assign({}, styles.rentalTh, { width: 90, verticalAlign: 'middle' }) }, '服务费'); + var rentalTh9 = React.createElement('th', { style: Object.assign({}, styles.rentalTh, { width: 100 }) }, reqStar, '保证金'); + var rentalTh10 = React.createElement('th', { style: Object.assign({}, styles.rentalTh, { width: 80 }) }, '备注'); + var rentalTh11 = React.createElement('th', { style: Object.assign({}, styles.rentalTh, { width: 60, verticalAlign: 'middle' }) }, '操作'); + var rentalTableThead = React.createElement('thead', null, React.createElement('tr', null, rentalTh1, rentalTh2, rentalTh3, rentalTh4, rentalTh5, rentalTh6, rentalTh7, rentalTh8, rentalTh9, rentalTh10, rentalTh11)); + var rentalTableTbody = React.createElement('tbody', null, rentalTableBody); + var rentalTableEl = React.createElement('table', { style: styles.rentalTable }, rentalTableThead, rentalTableTbody); + var rentalTableWrap = React.createElement('div', { style: { overflowX: 'auto', marginBottom: 16 } }, rentalTableEl); + var rentalContent = React.createElement('div', null, rentalSummary, formErrors.rentalOrders ? React.createElement('div', { style: styles.errMsg }, formErrors.rentalOrders) : null, rentalTableWrap, React.createElement(Button, { type: 'dashed', style: { marginTop: 12, width: '100%' }, onClick: addRentalRow }, '添加一行'), hydrogenFormRow); + + var feeTableHeader3 = React.createElement('tr', null, React.createElement('th', { style: styles.rentalTh }, '项目'), React.createElement('th', { style: styles.rentalTh }, '收费标准'), React.createElement('th', { style: styles.rentalTh }, '服务费')); + var feeTableHeader5 = React.createElement('tr', null, React.createElement('th', { style: styles.rentalTh }, '类别'), React.createElement('th', { style: styles.rentalTh }, '损坏部位'), React.createElement('th', { style: styles.rentalTh }, '配件'), React.createElement('th', { style: styles.rentalTh }, '数量'), React.createElement('th', { style: styles.rentalTh }, '费用明细')); + var makeFeeRow3 = function(r, i) { + var td1 = React.createElement('td', { style: styles.rentalTd }, r.project); + var td2 = React.createElement('td', { style: styles.rentalTd }, r.standard); + var td3 = React.createElement('td', { style: styles.rentalTd }, r.serviceFee); + return React.createElement('tr', { key: i }, td1, td2, td3); + }; + var makeFeeRow5 = function(r, i) { + var td1 = React.createElement('td', { style: styles.rentalTd }, r.category); + var td2 = React.createElement('td', { style: styles.rentalTd }, r.part); + var td3 = React.createElement('td', { style: styles.rentalTd }, r.partName); + var td4 = React.createElement('td', { style: styles.rentalTd }, r.qty); + var td5 = React.createElement('td', { style: styles.rentalTd }, r.feeDetail); + return React.createElement('tr', { key: i }, td1, td2, td3, td4, td5); + }; + var feeCertRows = feeTemplateCertFees.map(makeFeeRow3); + var feePenaltyRows = feeTemplatePenaltyFees.map(makeFeeRow3); + var feeConsumablesRows = feeTemplateConsumables.map(makeFeeRow5); + var feeOtherRows = feeTemplateOtherFees.map(makeFeeRow3); + var feeCertTable = React.createElement('table', { style: styles.rentalTable }, React.createElement('thead', null, feeTableHeader3), React.createElement('tbody', null, feeCertRows)); + var feePenaltyTable = React.createElement('table', { style: styles.rentalTable }, React.createElement('thead', null, feeTableHeader3), React.createElement('tbody', null, feePenaltyRows)); + var feeConsumablesTable = React.createElement('table', { style: styles.rentalTable }, React.createElement('thead', null, feeTableHeader5), React.createElement('tbody', null, feeConsumablesRows)); + var feeOtherTable = React.createElement('table', { style: styles.rentalTable }, React.createElement('thead', null, feeTableHeader3), React.createElement('tbody', null, feeOtherRows)); + var feeTemplateSelect = React.createElement('div', { style: styles.formRow }, React.createElement(FormItem, { label: '选择费用模板', required: true, error: formErrors.feeTemplate }, React.createElement(Select, { placeholder: '请选择费用模板', style: { width: '100%' }, value: feeTemplate || undefined, onChange: function(v) { setFeeTemplate(v || ''); }, status: formErrors.feeTemplate ? 'error' : undefined }, feeTemplates.map(function(f, i) { return React.createElement(Option, { key: i, value: f }, f); })))); + var feeTemplateBody = feeTemplate ? React.createElement('div', null, + React.createElement('div', { style: Object.assign({}, styles.feeSectionTitle, styles.feeSectionTitleFirst) }, '证照补办费用'), + feeCertTable, + React.createElement('div', { style: styles.feeSectionTitle }, '违约金费用'), + feePenaltyTable, + React.createElement('div', { style: styles.feeSectionTitle }, '易损件信息'), + feeConsumablesTable, + React.createElement('div', { style: styles.feeSectionTitle }, '其他费用信息'), + feeOtherTable + ) : null; + var feeContent = React.createElement('div', null, feeTemplateSelect, feeTemplateBody); + + var billingBtnBase = { padding: '12px 16px', border: '1px solid #d9d9d9', borderRadius: 0, backgroundColor: '#fff', color: '#333', cursor: 'pointer', fontSize: 14, textAlign: 'left', flex: 1, minWidth: 0, width: 0, height: 'auto', display: 'flex', flexDirection: 'column', alignItems: 'flex-start', justifyContent: 'flex-start', overflow: 'visible' }; + var billingContent = React.createElement('div', null, + React.createElement('div', { style: { display: 'flex', border: formErrors.billingMethod ? '1px solid #ff4d4f' : '1px solid #d9d9d9', borderRadius: 4, overflow: 'hidden', alignItems: 'stretch' } }, + React.createElement(Button, { type: 'button', style: Object.assign({}, billingBtnBase, { borderRight: '1px solid #d9d9d9' }, billingMethod === 'month' ? { backgroundColor: '#1890ff', color: '#fff', borderColor: '#1890ff' } : {}), onClick: function() { setBillingMethod('month'); } }, React.createElement('div', { style: { width: '100%' } }, React.createElement('div', { style: { fontWeight: 600, marginBottom: 6 } }, '按自然月结算'), React.createElement('div', { style: { fontSize: 12, opacity: 0.9, whiteSpace: 'normal', wordBreak: 'break-word', lineHeight: 1.5 } }, '账单按照第一个月计费日期开始-当月最后一天为第一期,之后按自然月方式形成每一期账单,例如付款周期为2个月,则第二期账单从2月1日-3月31日。'))), + React.createElement(Button, { type: 'button', style: Object.assign({}, billingBtnBase, billingMethod === 'period' ? { backgroundColor: '#1890ff', color: '#fff', borderColor: '#1890ff' } : {}), onClick: function() { setBillingMethod('period'); } }, React.createElement('div', { style: { width: '100%' } }, React.createElement('div', { style: { fontWeight: 600, marginBottom: 6 } }, '按付款周期天数结算'), React.createElement('div', { style: { fontSize: 12, opacity: 0.9, whiteSpace: 'normal', wordBreak: 'break-word', lineHeight: 1.5 } }, '账单按照合同基本信息中付款周期实际天数形成一期账单,例如付款周期为60天,则该账单账期为60天,每60天会自动生成新一期账单。'))) + ), + formErrors.billingMethod ? React.createElement('div', { style: styles.errMsg }, formErrors.billingMethod) : null + ); + + var serviceModalRows = serviceModalRowIndex !== null && rentalOrders[serviceModalRowIndex] ? rentalOrders[serviceModalRowIndex].serviceItems : []; + var serviceModalContent = serviceModalRowIndex !== null ? React.createElement('div', { style: styles.modalMask, onClick: function(e) { if (e.target === e.currentTarget) closeServiceModal(); } }, React.createElement('div', { style: styles.modalBox, onClick: function(e) { e.stopPropagation(); } }, React.createElement('div', { style: styles.modalHeader }, '服务项目'), React.createElement('div', { style: styles.modalBody }, React.createElement('table', { style: styles.rentalTable }, React.createElement('thead', null, React.createElement('tr', null, React.createElement('th', { style: styles.rentalTh }, '服务项目'), React.createElement('th', { style: styles.rentalTh }, '费用'), React.createElement('th', { style: styles.rentalTh }, '生效时间'), React.createElement('th', { style: Object.assign({}, styles.rentalTh, { width: 80 }) }, '操作'))), React.createElement('tbody', null, serviceModalRows.map(function(si, siIdx) { var filteredServiceOpts = serviceItemOptions.filter(function(o) { return !serviceItemSearch || o.indexOf(serviceItemSearch) !== -1; }); return React.createElement('tr', { key: siIdx }, React.createElement('td', { style: styles.rentalTd }, React.createElement(Select, { style: { width: '100%' }, placeholder: '请选择服务项目', value: si.project || undefined, onChange: function(v) { updateServiceItem(siIdx, 'project', v || ''); }, showSearch: true, filterOption: function(input, opt) { return opt && opt.children && String(opt.children).toLowerCase().indexOf((input || '').toLowerCase()) >= 0; }, allowClear: true }, serviceItemOptions.map(function(opt, oi) { return React.createElement(Option, { key: oi, value: opt }, opt); }))), React.createElement('td', { style: styles.rentalTd }, React.createElement(Input, { placeholder: '0.00', value: si.fee || '', onChange: function(e) { updateServiceItem(siIdx, 'fee', e.target.value); }, addonAfter: '元', style: { width: '100%' } })), React.createElement('td', { style: styles.rentalTd }, React.createElement(DatePicker, { style: { width: '100%' }, format: 'YYYY-MM-DD', placeholder: '请选择生效时间', value: si.effectiveDate && window.moment ? window.moment(si.effectiveDate, 'YYYY-MM-DD') : null, onChange: function(d, dateStr) { updateServiceItem(siIdx, 'effectiveDate', dateStr || ''); } })), React.createElement('td', { style: styles.rentalTd }, React.createElement(Button, { type: 'link', size: 'small', danger: true, onClick: function() { removeServiceItem(siIdx); } }, '删除'))); }))), React.createElement(Button, { type: 'dashed', style: { marginTop: 12, width: '100%' }, onClick: addServiceItem }, '添加一行')), React.createElement('div', { style: { padding: '12px 20px', borderTop: '1px solid #f0f0f0', textAlign: 'right', display: 'flex', gap: 12, justifyContent: 'flex-end' } }, React.createElement(Button, { type: 'primary', onClick: function() { closeServiceModal(); } }, '保存'), React.createElement(Button, { onClick: closeServiceModal }, '关闭')))) : null; + + var reqSpecH1 = { fontSize: 16, fontWeight: 600, marginBottom: 12, color: '#333' }; + var reqSpecH2 = { fontSize: 14, fontWeight: 600, marginTop: 16, marginBottom: 8, color: '#333' }; + var reqSpecH3 = { fontSize: 13, fontWeight: 600, marginTop: 10, marginBottom: 6, color: '#333' }; + var reqSpecP = { fontSize: 13, lineHeight: 1.6, marginBottom: 6, color: '#555' }; + var reqSpecLi = { fontSize: 13, lineHeight: 1.6, marginBottom: 4, marginLeft: 20, color: '#555' }; + var reqSpecBlock = { marginBottom: 8 }; + var reqSpecDoc = React.createElement('div', { style: { padding: '0 4px' } }, + React.createElement('div', { style: reqSpecBlock }, React.createElement('div', { style: reqSpecH2 }, '1.面包屑:'), React.createElement('div', { style: reqSpecP }, '1.1.业务管理-车辆租赁合同-新增租赁合同')), + React.createElement('div', { style: reqSpecBlock }, React.createElement('div', { style: reqSpecH2 }, '2.客户基本信息卡片:'), React.createElement('div', { style: reqSpecP }, '2.1.用于从客户列表中选择客户,并将该合同绑定业务部门及业务负责人;'), React.createElement('div', { style: reqSpecLi }, '2.1.1.客户名称:必选项,选择器,支持从输入框内输入内容进行模糊搜索,从客户信息列表中选择对应客户;'), React.createElement('div', { style: reqSpecLi }, '2.1.2.客户统一信用代码:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「信用代码」字段;'), React.createElement('div', { style: reqSpecLi }, '2.1.3.客户地址:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「客户地址」字段;'), React.createElement('div', { style: reqSpecLi }, '2.1.4.客户联系人:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「客户联系人」字段;'), React.createElement('div', { style: reqSpecLi }, '2.1.5.客户电话:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「客户电话」字段;'), React.createElement('div', { style: reqSpecLi }, '2.1.6.客户电子邮箱:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「客户电子邮箱」字段;'), React.createElement('div', { style: reqSpecLi }, '2.1.7.企业名称:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「企业名称」字段;'), React.createElement('div', { style: reqSpecLi }, '2.1.8.企业电话:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「企业电话」字段;'), React.createElement('div', { style: reqSpecLi }, '2.1.9.邮寄地址:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「邮寄地址」字段;'), React.createElement('div', { style: reqSpecLi }, '2.1.10.开户银行:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「开户银行」字段;'), React.createElement('div', { style: reqSpecLi }, '2.1.11.银行账号:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「银行账号」字段;'), React.createElement('div', { style: reqSpecLi }, '2.1.12.纳税人识别号:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「纳税人识别号」字段;'), React.createElement('div', { style: reqSpecLi }, '2.1.13.业务部门:必选项,选择器,从部门表中选择该租赁合同对应部门;'), React.createElement('div', { style: reqSpecLi }, '2.1.14.业务负责人:必选项,选择器,从已选业务部门下拉取对应业务负责人,未选择业务部门时,业务负责人输入框获取焦点时进行错误提示:请先选择业务部门;'), React.createElement('div', { style: reqSpecLi }, '2.1.15.合同原件:必填项,点击上传按钮,上传本地文件,支持doc、docx、pdf等格式。已上传则显示文件名,后方为删除,删除后可重新点击上传附件进行重新上传;')), + React.createElement('div', { style: reqSpecBlock }, React.createElement('div', { style: reqSpecH2 }, '3.合同基本信息卡片:'), React.createElement('div', { style: reqSpecP }, '3.1.用于定义租赁合同基本情况和付款方式;'), React.createElement('div', { style: reqSpecLi }, '3.1.1.项目名称:必填项,输入框,用于定义该合同对应项目名称,默认提示信息"请输入项目名称";'), React.createElement('div', { style: reqSpecLi }, '3.1.2.合同编码:按照合同编码规则自动生成;'), React.createElement('div', { style: reqSpecLi }, '合同编码规则:[城市简写][合同类型][签约时间][业务部门代码][顺序流水号][签署状态]'), React.createElement('div', { style: reqSpecLi }, '3.1.2.1.地区简写:如上海为SH,嘉兴为JX;'), React.createElement('div', { style: reqSpecLi }, '3.1.2.2.合同类型:采购合同为CG、租赁合同为ZL、自营合同为ZY;'), React.createElement('div', { style: reqSpecLi }, '3.1.2.3.签约时间:显示合同签约时间,如20260216'), React.createElement('div', { style: reqSpecLi }, '3.1.2.4.业务部门代码:显示合同签约业务部门信息,如YW1代表业务1部,YW2代表业务2部;'), React.createElement('div', { style: reqSpecLi }, '3.1.2.5.顺序流水号:实施5位编号补零规则,如01235,代表是集团第1235份合同;'), React.createElement('div', { style: reqSpecLi }, '3.1.2.6.签署状态:A为正式合同,B为试用合同;'), React.createElement('div', { style: reqSpecLi }, '如编号为:JXZL20260216YW101235A,则代表嘉兴业务1部在2026年2月16日签署的租赁正式合同,在集团中为第1235份;'), React.createElement('div', { style: reqSpecLi }, '3.1.3.合同类型:必选项,选择器,合同类型分为「正式合同」「试用合同」;'), React.createElement('div', { style: reqSpecLi }, '3.1.4.生效日期:必选项,日期选择器,格式为YYYY-MM-DD,精确至天;'), React.createElement('div', { style: reqSpecLi }, '3.1.5.付款方式:必选项,付款方式分为「预付」「后付」两种;'), React.createElement('div', { style: reqSpecLi }, '3.1.5.1.如果选择预付,则每笔账单以付款时间及账单计算方式,在账单日自动提前生成下月账单,同时生成时以消息/待办提醒对应业务人员;'), React.createElement('div', { style: reqSpecLi }, '3.1.5.2.如果选择后付,则每笔账单以付款时间及账单计算方式,在账单日自动提前生成下月账单,但只在退还车时,才以消息/待办提醒合同对应业务人员;'), React.createElement('div', { style: reqSpecLi }, '3.1.6.主要车型:输入框(禁用状态),根据租赁订单信息中所选车型,自动反写入输入框并以标签形式显示,支持多车型显示;'), React.createElement('div', { style: reqSpecLi }, '3.1.7.结束日期:必选项,日期选择器,格式为YYYY-MM-DD,精确至天;'), React.createElement('div', { style: reqSpecLi }, '3.1.7.1.合同结束日期前30天将以消息提醒方式提醒;'), React.createElement('div', { style: reqSpecLi }, '3.1.8.付款周期:必选项,选择器,支持1个月-12个月 12种付款周期,账单将以此周期进行定时生成;'), React.createElement('div', { style: reqSpecLi }, '3.1.9.签约公司:必选项,选择器,从组织机构表中获取所有根组织机构(如嘉兴羚牛、上海羚牛、广东羚牛等),默认显示录入合同人员所在机构;'), React.createElement('div', { style: reqSpecLi }, '3.1.10.交车区域:必选项,地区选择器,支持省-市2级,选择区域后,该任务生成交车任务时会自动推送至该区域负责运维人员;'), React.createElement('div', { style: reqSpecLi }, '3.1.11.交车地点:输入框,支持自定义输入交车地点;'), React.createElement('div', { style: reqSpecLi }, '3.1.12.备注:文本域,支持自定义输入备注信息;')) + ); + var reqSpecDocPart2 = React.createElement('div', { style: { padding: '0 4px' } }, + React.createElement('div', { style: reqSpecBlock }, React.createElement('div', { style: reqSpecH2 }, '4.被授权人信息卡片:'), React.createElement('div', { style: reqSpecP }, '4.1.用于定义租赁合同相关被授权人相关信息,被授权人交车单完成时需要通过手机短信调取E签宝进行签字确认;'), React.createElement('div', { style: reqSpecLi }, '4.1.1.被授权人:必填项,输入框,用于输入被授权人信息;'), React.createElement('div', { style: reqSpecLi }, '4.1.2.被授权人联系电话:必填项,输入框,用于输入被授权人联系电话,该电话后续需要接收E签宝签字链接;'), React.createElement('div', { style: reqSpecLi }, '4.1.3.被授权人身份证:必填项,输入框,用于输入被授权人身份证信息;'), React.createElement('div', { style: reqSpecLi }, '4.1.4.支持通过新增/删除一行的方式,创建或管理多个授权人,后续交车单完成时可选择多个授权人;')), + React.createElement('div', { style: reqSpecBlock }, React.createElement('div', { style: reqSpecH2 }, '5.租赁订单信息卡片:'), React.createElement('div', { style: reqSpecP }, '5.1.用于定义租赁订单租赁车辆品牌、型号、月租金、服务费、保证金等相关信息;'), React.createElement('div', { style: reqSpecLi }, '5.1.1.卡片上方为租赁车辆总计数据,包括租赁车辆数、租金及服务费合计、保证金总额、氢气预付款金额等相关信息;'), React.createElement('div', { style: reqSpecLi }, '5.1.1.1.租赁车辆数:显示下方租赁订单信息包含多少辆车;'), React.createElement('div', { style: reqSpecLi }, '5.1.1.2.租金及服务费合计:显示下方租赁订单信息中车辆租金总额及服务费总额;'), React.createElement('div', { style: reqSpecLi }, '5.1.1.3.保证金总额:显示下方租赁订单信息中车辆保证金总额;'), React.createElement('div', { style: reqSpecLi }, '5.1.1.4.氢气预付款金额:显示氢气预付款金额,如客户选择自行承担氢费或羚牛承担氢费,则该处为0不计入氢气预付款金额;'), React.createElement('div', { style: reqSpecLi }, '5.1.2.下方为列表,显示序号、品牌、型号、车牌号、车辆识别代码、车辆月租金(元)、服务费项目、服务费、保证金、备注;'), React.createElement('div', { style: reqSpecLi }, '默认显示一行空数据;'), React.createElement('div', { style: reqSpecLi }, '5.1.2.1.序号:自动按照条数生成,规则为1、2、3....以此类推;'), React.createElement('div', { style: reqSpecLi }, '5.1.2.2.品牌:必选项,选择器,从型号参数库中「品牌」字段拉取所有品牌;'), React.createElement('div', { style: reqSpecLi }, '5.1.2.3.型号:必选项,选择器,从型号参数库中「型号」字段拉取所有品牌;'), React.createElement('div', { style: reqSpecLi }, '5.1.2.4.车牌号:选填项,选择器(支持从输入框输入车牌号关键字下拉匹配);'), React.createElement('div', { style: reqSpecLi }, '5.1.2.5.车辆识别代码:输入框(禁用),显示该车辆对应车辆识别代码,从车辆表直接拉取;'), React.createElement('div', { style: reqSpecLi }, '5.1.2.6.车辆月租金(元):输入框,支持两位小数,用于输入该车辆月租金金额,输入框后缀为元;'), React.createElement('div', { style: reqSpecLi }, '5.1.2.7.服务费项目:显示管理按钮,点击弹出卡片,卡片标题为:服务项目,下方列表显示服务项目、费用、生效时间、操作;'), React.createElement('div', { style: reqSpecLi }, '5.1.2.7.1.服务项目:必选项,选择器(支持输入框输入服务项目关键字进行下拉匹配),选项包含:代处理费用、罚款、违章处理违约金、未参加安全培训、车辆出险、年检年审违约、停车费、设备损坏金(包含易损件)、清洗费、上门收车人工费、上门收车送车行驶费、上门收车基础服务费、保险上浮、保养费用、补办驾驶证、补办牌照、补办营运证、补办加氢证、借用备用钥匙、补配钥匙、租金、氢气费-客、退还车氢量差、能源费补缴、能源费退款、送车上门人工费、送车上门送车行驶费、送车上门基础服务费、保证金、氢气预付费、维修费用、ETC-客、ETC卡缺损费、ETC设备缺损费、电费-客、未结算保养费、未结算维修费、车损费、工具损坏或丢失费、证件费、广告损坏费、送车服务费、接车服务费、补办行驶证、超赔险、轮胎磨损费、无忧包、轮胎保、养护保、尾板;'), React.createElement('div', { style: reqSpecLi }, '5.1.2.7.2.费用:必填项,输入框,支持2位小数,输入框后缀为元;'), React.createElement('div', { style: reqSpecLi }, '5.1.2.7.3.生效时间:必选项,日期选择器,格式为YYYY-MM-DD,服务项目会以此时间提前3天进行消息通知;'), React.createElement('div', { style: reqSpecLi }, '5.1.2.7.4.操作:删除,点击删除直接删除该行数据;'), React.createElement('div', { style: reqSpecLi }, '5.1.2.7.5.新增一行数据:点击添加一行服务项目;'), React.createElement('div', { style: reqSpecLi }, '5.1.3.氢费承担方:必选项,填充按钮组,选项为我方、客户,默认为客户,选择承担方为我方时,无需选择付款方式、输入氢气预付款;'), React.createElement('div', { style: reqSpecLi }, '5.1.4.付款方式:必选项,填充按钮组,选项为预付、月付款、自行结算;'), React.createElement('div', { style: reqSpecLi }, '5.1.4.1.预付:指合同签署时客户就需预先付出的氢费款项;'), React.createElement('div', { style: reqSpecLi }, '5.1.4.2.月付款:指合同签署后,客户按照每月氢费实际账单,进行支付,设置为月付款时,每月账期时会提示对应业务管理中心-能源部完善氢费账单;'), React.createElement('div', { style: reqSpecLi }, '5.1.4.3.自行结算:指合同签署后,所有氢气费用由客户自行承担;'), React.createElement('div', { style: reqSpecLi }, '5.1.5.氢气预付款:必填项,输入框,支持2位小数,当付款方式为预付时,显示该输入框,氢气预付款金额会并入该合同交车前首付款中一并结算,并计入5.1.1.4.氢气预付款金额中;'), React.createElement('div', { style: reqSpecLi }, '5.1.6.退还车氢气单价:必填项,输入框,支持2位小数,后缀为元,该金额主要用于约定退还车时,与交车时氢气差值以此费用进行结算;')) + ); + var reqSpecDocPart3 = React.createElement('div', { style: { padding: '0 4px' } }, + React.createElement('div', { style: reqSpecBlock }, React.createElement('div', { style: reqSpecH2 }, '6.其他费用信息卡片:'), React.createElement('div', { style: reqSpecP }, '6.1.用于选择对应费用模板,展示证照补办费用、违约金费用、易损件费用、其他费用信息;'), React.createElement('div', { style: reqSpecLi }, '6.1.1.选择费用模板:必选项,通过选择通过费用模板预设好的费用金额明细,自动将该模板所有环节费用显示在合同中;'), React.createElement('div', { style: reqSpecLi }, '6.1.2.证照补办费用:单独标题显示,内容为列表,列表中包括项目、收费标准、服务费,选择费用模板后自动反显;'), React.createElement('div', { style: reqSpecLi }, '6.1.3.违约金费用:单独标题显示,内容为列表,列表中包含项目、收费标准、服务费,选择费用模板后自动反显;'), React.createElement('div', { style: reqSpecLi }, '6.1.4.易损件信息:单独标题显示,内容为列表,列表中包含类别、损坏部位、配件、数量、费用明细,选择费用模板后自动反显;'), React.createElement('div', { style: reqSpecLi }, '6.1.5.其他费用信息:单独标题显示,内容为列表,列表中包含项目、收费标准、服务费,选择费用模板后自动反显;')), + React.createElement('div', { style: reqSpecBlock }, React.createElement('div', { style: reqSpecH2 }, '7.账单计算方式卡片:'), React.createElement('div', { style: reqSpecP }, '7.1.必选项,填充按钮组,默认为按自然月结算,需要在两种账单计算方式二选一,可手动修改,用于定义租赁合同的账单计算方式,分为按付款周期天数结算、按自然月结算两种方式;'), React.createElement('div', { style: reqSpecLi }, '7.1.1.按付款周期天数结算:账单按照合同基本信息卡片中付款周期实际天数形成一期账单,例如付款周期为60天,则该账单账期为60天,每60天会自动生成新一期账单;'), React.createElement('div', { style: reqSpecLi }, '7.1.2.按自然月结算:账单按照第一个月计费日期开始-当月最后一天为第一期,之后按照付款周期设置,每个月第一天到对应月份最后一天的自然月方式,形成每一期账单,例如付款周期为2个月,则第二期账单从2月1日-3月31日,以此类推;')), + React.createElement('div', { style: reqSpecBlock }, React.createElement('div', { style: reqSpecH2 }, '8.最下方为提交并审核、保存、取消三个按钮;'), React.createElement('div', { style: reqSpecLi }, '8.1.点击提交并审核,toast提示:租赁合同已提交审核。同时该租赁合同进入租赁合同审核列表中;'), React.createElement('div', { style: reqSpecLi }, '8.2.点击保存,会存储租赁订单已填写内容,并加入租赁合同列表中,该条数据只能操作人自己查看并编辑,其他人无法操作;'), React.createElement('div', { style: reqSpecLi }, '8.3.点击取消,如当前页面有已编辑内容时,点击取消会进行二次提示,内容为:取消将会丢失所有已填写内容,是否确认?点击确认返回车辆租赁合同列表页;')), + React.createElement('div', { style: reqSpecBlock }, React.createElement('div', { style: reqSpecP }, '所有卡片支持收起/展开功能,通过点击卡片右侧收起/展开实现;并增加锚点功能,锚点固定于页面右上角,点击锚点对应卡片名称,页面自动跳转至该卡片所在区域;')) + ); + var reqSpecModalContent = reqSpecOpen ? React.createElement('div', { style: styles.modalMask, onClick: function(e) { if (e.target === e.currentTarget) setReqSpecOpen(false); } }, React.createElement('div', { style: Object.assign({}, styles.modalBox, { maxWidth: 640 }), onClick: function(e) { e.stopPropagation(); } }, React.createElement('div', { style: styles.modalHeader }, '需求说明'), React.createElement('div', { style: Object.assign({}, styles.modalBody, { maxHeight: '70vh', padding: '20px 24px' }) }, reqSpecDoc, reqSpecDocPart2, reqSpecDocPart3), React.createElement('div', { style: { padding: '12px 20px', borderTop: '1px solid #f0f0f0', textAlign: 'right' } }, React.createElement(Button, { onClick: function() { setReqSpecOpen(false); } }, '关闭')))) : null; + + return React.createElement('div', { style: styles.page }, + React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 } }, React.createElement('div', { style: styles.breadcrumb }, React.createElement('span', null, '业务管理'), React.createElement('span', { style: styles.breadcrumbSep }, ' / '), React.createElement('span', null, '车辆租赁合同'), React.createElement('span', { style: styles.breadcrumbSep }, ' / '), React.createElement('span', { style: { color: '#1890ff' } }, '新增租赁合同')), React.createElement('span', { style: { color: '#1890ff', cursor: 'pointer', fontSize: 14 }, onClick: function() { setReqSpecOpen(true); } }, '查看需求说明')), + React.createElement('div', { style: styles.anchorWrap }, + React.createElement('div', { style: { marginBottom: 8, fontWeight: 600, fontSize: 14 } }, '锚点导航'), + React.createElement('button', { style: styles.anchorItem, onClick: function() { scrollToCard('card-customer'); } }, '客户基本信息'), + React.createElement('button', { style: styles.anchorItem, onClick: function() { scrollToCard('card-contract'); } }, '合同基本信息'), + React.createElement('button', { style: styles.anchorItem, onClick: function() { scrollToCard('card-authorized'); } }, '被授权人信息'), + React.createElement('button', { style: styles.anchorItem, onClick: function() { scrollToCard('card-rental'); } }, '租赁订单信息'), + React.createElement('button', { style: styles.anchorItem, onClick: function() { scrollToCard('card-fee'); } }, '其他费用信息'), + React.createElement('button', { style: styles.anchorItem, onClick: function() { scrollToCard('card-billing'); } }, '账单计算方式') + ), + React.createElement('div', { id: 'card-customer' }, React.createElement(CardBlock, { id: 'card-customer', title: '客户基本信息', collapsed: cc1, setCollapsed: setCc1 }, customerFields)), + React.createElement('div', { id: 'card-contract', style: { marginTop: 16 } }, React.createElement(CardBlock, { title: '合同基本信息', collapsed: cc2, setCollapsed: setCc2 }, React.createElement('div', null, contractFormRow1, contractFormRow2))), + React.createElement('div', { id: 'card-authorized', style: { marginTop: 16 } }, React.createElement(CardBlock, { title: '被授权人信息', collapsed: cc3, setCollapsed: setCc3 }, authorizedContent)), + React.createElement('div', { id: 'card-rental', style: { marginTop: 16 } }, React.createElement(CardBlock, { title: '租赁订单信息', collapsed: cc4, setCollapsed: setCc4 }, rentalContent)), + React.createElement('div', { id: 'card-fee', style: { marginTop: 16 } }, React.createElement(CardBlock, { title: '其他费用信息', collapsed: cc5, setCollapsed: setCc5 }, feeContent)), + React.createElement('div', { id: 'card-billing', style: { marginTop: 16 } }, React.createElement(CardBlock, { title: '账单计算方式', collapsed: cc6, setCollapsed: setCc6 }, billingContent)), + React.createElement('div', { style: { height: 60 } }), + serviceModalContent, + reqSpecModalContent, + React.createElement('div', { style: styles.footer }, React.createElement(Button, { type: 'primary', onClick: function() { if (validateSubmitAndReview()) { message.success('租赁合同已提交审核。'); } } }, '提交并审核'), React.createElement(Button, { onClick: function() { message.info('保存,加入租赁合同列表(仅操作人可查看编辑)'); } }, '保存'), React.createElement(Button, { onClick: function() { message.info('取消'); } }, '取消')) + ); +}; diff --git a/web端/车辆租赁合同/新增车辆.jsx b/web端/车辆租赁合同/新增车辆.jsx new file mode 100644 index 0000000..1f65e5f --- /dev/null +++ b/web端/车辆租赁合同/新增车辆.jsx @@ -0,0 +1,448 @@ +// 【重要】必须使用 const Component 作为组件变量名 +// 业务管理 - 车辆租赁合同 - 新增车辆(与查看合同页布局一致,仅新增车辆信息卡片可编辑) + +const Component = function() { + var useState = React.useState; + var useCallback = React.useCallback; + var antd = window.antd; + var Select = antd.Select; + var Input = antd.Input; + var Button = antd.Button; + var Table = antd.Table; + var DatePicker = antd.DatePicker; + var message = antd.message; + var Option = Select.Option; + + var cc1State = React.useState(false); + var cc1 = cc1State[0]; + var setCc1 = cc1State[1]; + var cc2State = React.useState(false); + var cc2 = cc2State[0]; + var setCc2 = cc2State[1]; + var cc3State = React.useState(false); + var cc3 = cc3State[0]; + var setCc3 = cc3State[1]; + var cc4State = React.useState(false); + var cc4 = cc4State[0]; + var setCc4 = cc4State[1]; + var cc5State = React.useState(false); + var cc5 = cc5State[0]; + var setCc5 = cc5State[1]; + var cc6State = React.useState(false); + var cc6 = cc6State[0]; + var setCc6 = cc6State[1]; + var cc7State = React.useState(false); + var cc7 = cc7State[0]; + var setCc7 = cc7State[1]; + + var emptyNewRow = { brand: '', model: '', plateNo: '', vin: '', monthRent: '', serviceItems: [{ project: '', fee: '', effectiveDate: '' }], deposit: '', remark: '' }; + var _newVehicleRows = useState([Object.assign({}, emptyNewRow)]); + var newVehicleRows = _newVehicleRows[0]; + var setNewVehicleRows = _newVehicleRows[1]; + var _serviceModalRowIndex = useState(null); + var serviceModalRowIndex = _serviceModalRowIndex[0]; + var setServiceModalRowIndex = _serviceModalRowIndex[1]; + + var brandList = ['品牌A', '品牌B', '品牌C', '品牌D']; + var serviceItemOptions = ['代处理费用', '罚款', '违章处理违约金', '未参加安全培训', '车辆出险', '年检年审违约', '停车费', '设备损坏金(包含易损件)', '清洗费', '上门收车人工费', '上门收车送车行驶费', '上门收车基础服务费', '保险上浮', '保养费用', '补办驾驶证', '补办牌照', '补办营运证', '补办加氢证', '借用备用钥匙', '补配钥匙', '租金', '氢气费-客', '退还车氢量差', '能源费补缴', '能源费退款', '送车上门人工费', '送车上门送车行驶费', '送车上门基础服务费', '保证金', '氢气预付费', '维修费用', 'ETC-客', 'ETC卡缺损费', 'ETC设备缺损费', '电费-客', '未结算保养费', '未结算维修费', '车损费', '工具损坏或丢失费', '证件费', '广告损坏费', '送车服务费', '接车服务费', '补办行驶证', '超赔险', '轮胎磨损费', '无忧包', '轮胎保', '养护保', '尾板']; + var modelByBrand = { '品牌A': ['型号A1', '型号A2', '型号A3'], '品牌B': ['型号B1', '型号B2'], '品牌C': ['型号C1', '型号C2'], '品牌D': ['型号D1'] }; + var plateNoOptions = ['浙A10001', '浙A10002', '浙B20001', '浙B20002', '浙C30001', '浙C30002', '沪D40001', '沪D40002', '苏E50001', '苏E50002', '京F60001', '京F60002'].map(function(p) { return { value: p, label: p }; }); + + var addNewVehicleRow = useCallback(function() { + setNewVehicleRows(function(prev) { return prev.concat([{ brand: '', model: '', plateNo: '', vin: '', monthRent: '', serviceItems: [{ project: '', fee: '', effectiveDate: '' }], deposit: '', remark: '' }]); }); + }, []); + var removeNewVehicleRow = useCallback(function(index) { + setNewVehicleRows(function(prev) { + var next = prev.slice(); + next.splice(index, 1); + if (next.length === 0) next = [{ brand: '', model: '', plateNo: '', vin: '', monthRent: '', serviceItems: [{ project: '', fee: '', effectiveDate: '' }], deposit: '', remark: '' }]; + return next; + }); + }, []); + var updateNewVehicleRow = useCallback(function(index, field, value) { + setNewVehicleRows(function(prev) { + var next = prev.slice(); + var row = next[index] || emptyNewRow; + var o = {}; + o[field] = value; + if (field === 'brand') o.model = ''; + if (field === 'plateNo') o.vin = value; + next[index] = Object.assign({}, row, o); + return next; + }); + }, []); + + var calcRowServiceFee = function(row) { + var sum = 0; + for (var i = 0; i < (row.serviceItems || []).length; i++) { + var fee = parseFloat(row.serviceItems[i].fee); + if (!isNaN(fee)) sum += fee; + } + return sum.toFixed(2); + }; + var openServiceModal = function(index) { setServiceModalRowIndex(index); }; + var closeServiceModal = function() { setServiceModalRowIndex(null); }; + var addServiceItem = function() { + if (serviceModalRowIndex === null) return; + var next = newVehicleRows.slice(); + var row = next[serviceModalRowIndex]; + var items = (row.serviceItems || []).concat([{ project: '', fee: '', effectiveDate: '' }]); + var newRow = {}; + for (var k in row) { if (row.hasOwnProperty(k) && k !== 'serviceItems') newRow[k] = row[k]; } + newRow.serviceItems = items; + next[serviceModalRowIndex] = newRow; + setNewVehicleRows(next); + }; + var removeServiceItem = function(siIndex) { + if (serviceModalRowIndex === null) return; + var next = newVehicleRows.slice(); + var row = next[serviceModalRowIndex]; + var items = (row.serviceItems || []).slice(); + items.splice(siIndex, 1); + if (items.length === 0) items = [{ project: '', fee: '', effectiveDate: '' }]; + var newRow = {}; + for (var k in row) { if (row.hasOwnProperty(k) && k !== 'serviceItems') newRow[k] = row[k]; } + newRow.serviceItems = items; + next[serviceModalRowIndex] = newRow; + setNewVehicleRows(next); + }; + var updateServiceItem = function(siIndex, field, value) { + if (serviceModalRowIndex === null) return; + var next = newVehicleRows.slice(); + var row = next[serviceModalRowIndex]; + var items = (row.serviceItems || []).slice(); + var item = items[siIndex] || {}; + var newItem = {}; + for (var k in item) { if (item.hasOwnProperty(k)) newItem[k] = item[k]; } + newItem[field] = value; + items[siIndex] = newItem; + var newRow = {}; + for (var rk in row) { if (row.hasOwnProperty(rk) && rk !== 'serviceItems') newRow[rk] = row[rk]; } + newRow.serviceItems = items; + next[serviceModalRowIndex] = newRow; + setNewVehicleRows(next); + }; + var newOrderSummary = (function() { + var count = newVehicleRows.length; + var rentService = 0; + var depositTotal = 0; + for (var i = 0; i < newVehicleRows.length; i++) { + rentService += parseFloat(newVehicleRows[i].monthRent) || 0; + rentService += parseFloat(calcRowServiceFee(newVehicleRows[i])) || 0; + depositTotal += parseFloat(newVehicleRows[i].deposit) || 0; + } + return { vehicleCount: count, totalRentService: rentService.toFixed(2), totalDeposit: depositTotal.toFixed(2) }; + })(); + + var scrollToCard = function(id) { + var el = document.getElementById(id); + if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' }); + }; + + var styles = { + page: { padding: '16px 24px 48px', backgroundColor: '#f5f5f5', minHeight: '100vh', fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif', fontSize: 14 }, + breadcrumb: { marginBottom: 16, color: '#666' }, + breadcrumbSep: { margin: '0 8px', color: '#999' }, + anchorWrap: { position: 'fixed', top: 80, right: 24, zIndex: 100, backgroundColor: '#fff', borderRadius: 8, boxShadow: '0 2px 8px rgba(0,0,0,0.12)', padding: '12px 16px', minWidth: 160 }, + anchorItem: { display: 'block', padding: '6px 0', color: '#1890ff', cursor: 'pointer', border: 'none', background: 'none', width: '100%', textAlign: 'left', fontSize: 13 }, + card: { backgroundColor: '#fff', borderRadius: 8, marginBottom: 16, boxShadow: '0 1px 2px rgba(0,0,0,0.05)', overflow: 'hidden' }, + cardHeader: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '16px 20px', borderBottom: '1px solid #f0f0f0', cursor: 'pointer' }, + cardTitle: { fontSize: 16, fontWeight: 600, color: '#333' }, + cardToggle: { color: '#999', fontSize: 14 }, + cardBody: { padding: '20px 24px' }, + formRow: { display: 'flex', flexWrap: 'wrap', marginBottom: 16 }, + formCol: { flex: '0 0 33.33%', minWidth: 200, paddingRight: 16, marginBottom: 8 }, + formColFull: { flex: '0 0 100%', marginBottom: 8 }, + label: { display: 'block', marginBottom: 6, color: '#333' }, + input: { width: '100%', padding: '8px 12px', border: '1px solid #d9d9d9', borderRadius: 4, fontSize: 14 }, + inputDisabled: { backgroundColor: '#f5f5f5', color: '#666', cursor: 'default', borderColor: '#e8e8e8' }, + summaryList: { marginBottom: 16, display: 'flex', flexWrap: 'wrap', gap: 16 }, + summaryListItem: { flex: '0 0 calc(50% - 8px)', display: 'flex', alignItems: 'center', padding: '12px 16px', border: '1px solid #e8e8e8', borderRadius: 4, backgroundColor: '#fafafa', fontSize: 14, boxSizing: 'border-box' }, + summaryListLabel: { flex: '0 0 140px', color: '#666' }, + summaryListValue: { flex: 1, fontWeight: 600, color: '#333' }, + rentalTable: { width: '100%', borderCollapse: 'collapse', fontSize: 13 }, + rentalTh: { padding: '10px 8px', textAlign: 'left', borderBottom: '1px solid #e8e8e8', backgroundColor: '#fafafa', fontWeight: 600 }, + rentalTd: { padding: '8px', borderBottom: '1px solid #f0f0f0', verticalAlign: 'middle' }, + rentalInputDisabled: { backgroundColor: '#f5f5f5', color: '#666', border: '1px solid #e8e8e8', padding: '6px 10px', borderRadius: 4, width: '100%', fontSize: 13 }, + footer: { position: 'fixed', bottom: 0, left: 0, right: 0, padding: '12px 24px', backgroundColor: '#fff', borderTop: '1px solid #e8e8e8', display: 'flex', gap: 12, justifyContent: 'flex-start', zIndex: 99 }, + btn: { padding: '8px 24px', borderRadius: 4, border: '1px solid #d9d9d9', cursor: 'pointer', fontSize: 14 }, + btnDefault: { backgroundColor: '#fff', color: '#333' }, + btnPrimary: { backgroundColor: '#1890ff', color: '#fff', borderColor: '#1890ff' }, + feeSectionTitle: { fontSize: 15, fontWeight: 600, color: '#333', marginTop: 20, marginBottom: 10 }, + feeSectionTitleFirst: { marginTop: 0 }, + sectionTitle: { fontSize: 15, fontWeight: 600, color: '#333', marginBottom: 12 }, + modalMask: { position: 'fixed', left: 0, top: 0, right: 0, bottom: 0, backgroundColor: 'rgba(0,0,0,0.45)', zIndex: 1000, display: 'flex', alignItems: 'center', justifyContent: 'center' }, + modalBox: { backgroundColor: '#fff', borderRadius: 8, width: '90%', maxWidth: 720, maxHeight: '85vh', overflow: 'hidden', display: 'flex', flexDirection: 'column', boxShadow: '0 4px 20px rgba(0,0,0,0.15)' }, + modalHeader: { padding: '16px 20px', borderBottom: '1px solid #f0f0f0', fontSize: 16, fontWeight: 600 }, + modalBody: { padding: 20, overflow: 'auto', flex: 1 }, + rentalTdCenter: { padding: '8px', borderBottom: '1px solid #f0f0f0', verticalAlign: 'middle' } + }; + + var CardBlock = function(props) { + return React.createElement('div', { id: props.id, style: styles.card }, + React.createElement('div', { style: styles.cardHeader, onClick: function() { props.setCollapsed(!props.collapsed); } }, + React.createElement('span', { style: styles.cardTitle }, props.title), + React.createElement('span', { style: styles.cardToggle }, props.collapsed ? '展开' : '收起') + ), + !props.collapsed ? React.createElement('div', { style: styles.cardBody }, props.children) : null + ); + }; + + var FormItemReadOnly = function(props) { + var colStyle = props.fullWidth ? styles.formColFull : styles.formCol; + return React.createElement('div', { style: colStyle }, + React.createElement('label', { style: styles.label }, props.label), + React.createElement('div', { style: Object.assign({}, styles.input, styles.inputDisabled) }, props.value || '—') + ); + }; + + var mockCustomer = { name: '嘉兴某某物流有限公司', creditCode: '91330400MA2XXXXX1', address: '浙江省嘉兴市南湖区科技大道1号', contact: '张三', phone: '13800138001', email: 'zhangsan@example.com', companyName: '嘉兴某某物流有限公司', companyPhone: '0571-88888888', mailingAddress: '浙江省嘉兴市南湖区科技大道1号', bank: '中国工商银行嘉兴分行', bankAccount: '6222021234567890123', taxId: '91330400MA2XXXXX1' }; + var contractOriginal = { name: '租赁合同-原件.pdf', size: '1.2 MB', uploadTime: '2026-02-16 14:30' }; + var mockContract = { projectName: '嘉兴氢能运输项目', contractCode: 'JXZL20260216YW101235A', contractType: '正式合同', effectiveDate: '2026-02-16', paymentMethod: '预付', mainVehicleModels: '型号A1、型号A2', endDate: '2027-02-16', paymentPeriod: '1个月', signingCompany: '嘉兴羚牛', deliveryRegion: '浙江省 / 嘉兴市', deliveryLocation: '嘉兴市南湖区科技大道1号', remarks: '' }; + var mockAuthorized = [{ name: '张三', phone: '13800138001', idCard: '330102199001011234' }]; + var mockRentalOrders = [{ brand: '品牌A', model: '型号A1', plateNo: '浙A10001', vin: 'L1234567890ABCDEF', monthRent: '8000', serviceFee: '500', deposit: '10000', remark: '' }, { brand: '品牌A', model: '型号A2', plateNo: '浙B20002', vin: 'L2234567890ABCDEF', monthRent: '8000', serviceFee: '500', deposit: '10000', remark: '' }]; + var mockRentalSummary = { vehicleCount: 2, totalRentService: '17000.00', totalDeposit: '20000.00', hydrogenPrepay: '5000.00' }; + var mockHydrogen = { bearer: '客户', paymentMethod: '预付', hydrogenPrepay: '5000', returnPrice: '80' }; + var mockFeeTemplate = '标准费用模板A'; + var mockBillingMethod = '按自然月结算'; + var feeTemplateCertFees = [{ project: '补办行驶证', standard: '50元/次', serviceFee: '20' }, { project: '补办驾驶证', standard: '30元/次', serviceFee: '10' }, { project: '补办牌照', standard: '100元/次', serviceFee: '50' }]; + var feeTemplatePenaltyFees = [{ project: '提前退车违约金', standard: '月租金×1', serviceFee: '0' }, { project: '违章处理违约金', standard: '按实际发生', serviceFee: '50' }]; + var feeTemplateConsumables = [{ category: '轮胎', part: '前轮', partName: '轮胎A型', qty: 1, feeDetail: '500.00' }, { category: '易损件', part: '雨刮', partName: '雨刮片', qty: 2, feeDetail: '80.00' }]; + var feeTemplateOtherFees = [{ project: '上门送车费', standard: '100元/次', serviceFee: '50' }, { project: '上门收车费', standard: '100元/次', serviceFee: '50' }, { project: '清洗费', standard: '80元/次', serviceFee: '30' }]; + var feeTableHeader3 = React.createElement('tr', null, React.createElement('th', { style: styles.rentalTh }, '项目'), React.createElement('th', { style: styles.rentalTh }, '收费标准'), React.createElement('th', { style: styles.rentalTh }, '服务费')); + var feeTableHeader5 = React.createElement('tr', null, React.createElement('th', { style: styles.rentalTh }, '类别'), React.createElement('th', { style: styles.rentalTh }, '损坏部位'), React.createElement('th', { style: styles.rentalTh }, '配件'), React.createElement('th', { style: styles.rentalTh }, '数量'), React.createElement('th', { style: styles.rentalTh }, '费用明细')); + var makeFeeRow3 = function(r, i) { return React.createElement('tr', { key: i }, React.createElement('td', { style: styles.rentalTd }, r.project), React.createElement('td', { style: styles.rentalTd }, r.standard), React.createElement('td', { style: styles.rentalTd }, r.serviceFee)); }; + var makeFeeRow5 = function(r, i) { return React.createElement('tr', { key: i }, React.createElement('td', { style: styles.rentalTd }, r.category), React.createElement('td', { style: styles.rentalTd }, r.part), React.createElement('td', { style: styles.rentalTd }, r.partName), React.createElement('td', { style: styles.rentalTd }, r.qty), React.createElement('td', { style: styles.rentalTd }, r.feeDetail)); }; + var feeCertTable = React.createElement('table', { style: styles.rentalTable }, React.createElement('thead', null, feeTableHeader3), React.createElement('tbody', null, feeTemplateCertFees.map(makeFeeRow3))); + var feePenaltyTable = React.createElement('table', { style: styles.rentalTable }, React.createElement('thead', null, feeTableHeader3), React.createElement('tbody', null, feeTemplatePenaltyFees.map(makeFeeRow3))); + var feeConsumablesTable = React.createElement('table', { style: styles.rentalTable }, React.createElement('thead', null, feeTableHeader5), React.createElement('tbody', null, feeTemplateConsumables.map(makeFeeRow5))); + var feeOtherTable = React.createElement('table', { style: styles.rentalTable }, React.createElement('thead', null, feeTableHeader3), React.createElement('tbody', null, feeTemplateOtherFees.map(makeFeeRow3))); + var feeTemplateBody = React.createElement('div', null, + React.createElement('div', { style: Object.assign({}, styles.feeSectionTitle, styles.feeSectionTitleFirst) }, '证照补办费用'), feeCertTable, + React.createElement('div', { style: styles.feeSectionTitle }, '违约金费用'), feePenaltyTable, + React.createElement('div', { style: styles.feeSectionTitle }, '易损件信息'), feeConsumablesTable, + React.createElement('div', { style: styles.feeSectionTitle }, '其他费用信息'), feeOtherTable + ); + + var customerFields = React.createElement('div', { style: styles.formRow }, + React.createElement(FormItemReadOnly, { label: '客户名称', value: mockCustomer.name }), + React.createElement(FormItemReadOnly, { label: '客户统一信用代码', value: mockCustomer.creditCode }), + React.createElement(FormItemReadOnly, { label: '客户地址', value: mockCustomer.address }), + React.createElement(FormItemReadOnly, { label: '客户联系人', value: mockCustomer.contact }), + React.createElement(FormItemReadOnly, { label: '客户电话', value: mockCustomer.phone }), + React.createElement(FormItemReadOnly, { label: '客户电子邮箱', value: mockCustomer.email }), + React.createElement(FormItemReadOnly, { label: '企业名称', value: mockCustomer.companyName }), + React.createElement(FormItemReadOnly, { label: '企业电话', value: mockCustomer.companyPhone }), + React.createElement(FormItemReadOnly, { label: '邮寄地址', value: mockCustomer.mailingAddress }), + React.createElement(FormItemReadOnly, { label: '开户银行', value: mockCustomer.bank }), + React.createElement(FormItemReadOnly, { label: '银行账号', value: mockCustomer.bankAccount }), + React.createElement(FormItemReadOnly, { label: '纳税人识别号', value: mockCustomer.taxId }), + React.createElement(FormItemReadOnly, { label: '业务部门', value: '业务1部' }), + React.createElement(FormItemReadOnly, { label: '业务负责人', value: '张经理' }) + ); + + var contractFormRow1 = React.createElement('div', { style: styles.formRow }, + React.createElement(FormItemReadOnly, { label: '项目名称', value: mockContract.projectName }), + React.createElement(FormItemReadOnly, { label: '合同编码', value: mockContract.contractCode }), + React.createElement(FormItemReadOnly, { label: '合同类型', value: mockContract.contractType }), + React.createElement(FormItemReadOnly, { label: '生效日期', value: mockContract.effectiveDate }), + React.createElement(FormItemReadOnly, { label: '付款方式', value: mockContract.paymentMethod }), + React.createElement(FormItemReadOnly, { label: '主要车型', value: mockContract.mainVehicleModels }), + React.createElement(FormItemReadOnly, { label: '结束日期', value: mockContract.endDate }), + React.createElement(FormItemReadOnly, { label: '付款周期', value: mockContract.paymentPeriod }), + React.createElement(FormItemReadOnly, { label: '签约公司', value: mockContract.signingCompany }), + React.createElement('div', { style: styles.formCol }, + React.createElement(FormItemReadOnly, { label: '交车区域', value: mockContract.deliveryRegion }), + React.createElement('div', null, + React.createElement('label', { style: styles.label }, '合同原件'), + React.createElement('div', { style: Object.assign({}, styles.input, styles.inputDisabled, { padding: '8px 12px' }) }, + contractOriginal + ? React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' } }, + React.createElement('a', { href: '#', style: { color: '#1890ff', cursor: 'pointer', textDecoration: 'none' }, onClick: function(e) { e.preventDefault(); window.open('#', '_blank'); } }, contractOriginal.name), + (contractOriginal.size || contractOriginal.uploadTime) ? React.createElement('span', { style: { color: '#999', fontSize: 12 } }, (contractOriginal.size || '') + (contractOriginal.size && contractOriginal.uploadTime ? ' · ' : '') + (contractOriginal.uploadTime || '')) : null + ) + : '—' + ) + ) + ), + React.createElement(FormItemReadOnly, { label: '交车地点', value: mockContract.deliveryLocation }) + ); + var contractFormRow2 = React.createElement('div', { style: styles.formRow }, React.createElement(FormItemReadOnly, { label: '备注', fullWidth: true, value: mockContract.remarks || '—' })); + + var authorizedContent = React.createElement('div', null, + React.createElement('div', { style: { display: 'flex', gap: 12, alignItems: 'center', marginBottom: 8 } }, + React.createElement('span', { style: { flex: 1, fontWeight: 600, fontSize: 13 } }, '被授权人姓名'), + React.createElement('span', { style: { flex: 1, fontWeight: 600, fontSize: 13 } }, '被授权人联系电话'), + React.createElement('span', { style: { flex: 1, fontWeight: 600, fontSize: 13 } }, '被授权人身份证') + ), + mockAuthorized.map(function(item, index) { + return React.createElement('div', { key: index, style: { display: 'flex', gap: 12, alignItems: 'center', marginBottom: 12 } }, + React.createElement('div', { style: Object.assign({}, styles.input, styles.inputDisabled, { flex: 1 }) }, item.name), + React.createElement('div', { style: Object.assign({}, styles.input, styles.inputDisabled, { flex: 1 }) }, item.phone), + React.createElement('div', { style: Object.assign({}, styles.input, styles.inputDisabled, { flex: 1 }) }, item.idCard) + ); + }) + ); + + var rentalSummaryEl = React.createElement('div', { style: styles.summaryList }, + React.createElement('div', { style: styles.summaryListItem }, React.createElement('span', { style: styles.summaryListLabel }, '租赁车辆数'), React.createElement('span', { style: styles.summaryListValue }, mockRentalSummary.vehicleCount + ' 辆')), + React.createElement('div', { style: styles.summaryListItem }, React.createElement('span', { style: styles.summaryListLabel }, '租金及服务费合计'), React.createElement('span', { style: styles.summaryListValue }, mockRentalSummary.totalRentService + ' 元')), + React.createElement('div', { style: styles.summaryListItem }, React.createElement('span', { style: styles.summaryListLabel }, '保证金总额'), React.createElement('span', { style: styles.summaryListValue }, mockRentalSummary.totalDeposit + ' 元')), + React.createElement('div', { style: styles.summaryListItem }, React.createElement('span', { style: styles.summaryListLabel }, '氢气预付款金额'), React.createElement('span', { style: styles.summaryListValue }, mockRentalSummary.hydrogenPrepay + ' 元')) + ); + var rentalTableBody = mockRentalOrders.map(function(row, idx) { + return React.createElement('tr', { key: idx }, + React.createElement('td', { style: styles.rentalTd }, idx + 1), + React.createElement('td', { style: styles.rentalTd }, React.createElement('div', { style: styles.rentalInputDisabled }, row.brand)), + React.createElement('td', { style: styles.rentalTd }, React.createElement('div', { style: styles.rentalInputDisabled }, row.model)), + React.createElement('td', { style: styles.rentalTd }, React.createElement('div', { style: styles.rentalInputDisabled }, row.plateNo)), + React.createElement('td', { style: styles.rentalTd }, React.createElement('div', { style: styles.rentalInputDisabled }, row.vin)), + React.createElement('td', { style: styles.rentalTd }, React.createElement('div', { style: styles.rentalInputDisabled }, row.monthRent + ' 元')), + React.createElement('td', { style: styles.rentalTd }, React.createElement('div', { style: styles.rentalInputDisabled }, row.serviceFee + ' 元')), + React.createElement('td', { style: styles.rentalTd }, React.createElement('div', { style: styles.rentalInputDisabled }, row.deposit + ' 元')), + React.createElement('td', { style: styles.rentalTd }, React.createElement('div', { style: styles.rentalInputDisabled }, row.remark || '—')) + ); + }); + var rentalTableEl = React.createElement('table', { style: styles.rentalTable }, + React.createElement('thead', null, + React.createElement('tr', null, + React.createElement('th', { style: styles.rentalTh, width: 50 }, '序号'), + React.createElement('th', { style: styles.rentalTh, width: 100 }, '品牌'), + React.createElement('th', { style: styles.rentalTh, width: 100 }, '型号'), + React.createElement('th', { style: styles.rentalTh, width: 120 }, '车牌号'), + React.createElement('th', { style: styles.rentalTh, width: 160 }, '车辆识别代码'), + React.createElement('th', { style: styles.rentalTh, width: 120 }, '车辆月租金'), + React.createElement('th', { style: styles.rentalTh, width: 90 }, '服务费'), + React.createElement('th', { style: styles.rentalTh, width: 100 }, '保证金'), + React.createElement('th', { style: styles.rentalTh, width: 80 }, '备注') + ) + ), + React.createElement('tbody', null, rentalTableBody) + ); + var hydrogenReadOnly = React.createElement('div', { style: styles.formRow }, + React.createElement(FormItemReadOnly, { label: '氢费承担方', value: mockHydrogen.bearer }), + React.createElement(FormItemReadOnly, { label: '付款方式', value: mockHydrogen.paymentMethod }), + React.createElement(FormItemReadOnly, { label: '氢气预付款', value: mockHydrogen.hydrogenPrepay + ' 元' }), + React.createElement(FormItemReadOnly, { label: '退还车氢气单价', value: mockHydrogen.returnPrice + ' 元' }) + ); + var rentalContent = React.createElement('div', null, rentalSummaryEl, React.createElement('div', { style: { overflowX: 'auto', marginBottom: 16 } }, rentalTableEl), hydrogenReadOnly); + + var newVehicleColumns = [ + { title: '序号', key: 'no', width: 60, render: function(_, __, i) { return i + 1; } }, + { title: '品牌', key: 'brand', width: 90, render: function(_, row, i) { return React.createElement(Select, { placeholder: '请选择', style: { width: '100%' }, value: row.brand || undefined, onChange: function(v) { updateNewVehicleRow(i, 'brand', v || ''); }, options: brandList.map(function(b) { return { value: b, label: b }; }), allowClear: true }); } }, + { title: '型号', key: 'model', width: 100, render: function(_, row, i) { var opts = (row.brand && modelByBrand[row.brand]) ? modelByBrand[row.brand].map(function(m) { return { value: m, label: m }; }) : []; return React.createElement(Select, { placeholder: '请选择', style: { width: '100%' }, value: row.model || undefined, onChange: function(v) { updateNewVehicleRow(i, 'model', v || ''); }, options: opts, allowClear: true, disabled: !row.brand }); } }, + { title: '车牌号', key: 'plateNo', width: 120, render: function(_, row, i) { return React.createElement(Select, { placeholder: '请选择或输入搜索', style: { width: '100%' }, value: row.plateNo || undefined, onChange: function(v) { updateNewVehicleRow(i, 'plateNo', v || ''); }, options: plateNoOptions, allowClear: true, showSearch: true, filterOption: function(input, option) { var label = (option && option.label) ? option.label : ''; return label.toLowerCase().indexOf((input || '').toLowerCase()) >= 0; } }); } }, + { title: '车辆识别代码', key: 'vin', width: 160, render: function(_, row, i) { return React.createElement(Input, { placeholder: '由车牌号反写', value: row.plateNo || row.vin || '', disabled: true }); } }, + { title: '车辆月租金', key: 'monthRent', width: 120, render: function(_, row, i) { return React.createElement(Input, { placeholder: '0.00', value: row.monthRent || '', onChange: function(e) { updateNewVehicleRow(i, 'monthRent', e.target.value); }, addonAfter: '元' }); } }, + { title: '服务费项目', key: 'serviceItems', width: 80, render: function(_, row, i) { return React.createElement(Button, { type: 'link', size: 'small', onClick: function() { openServiceModal(i); } }, '管理'); } }, + { title: '服务费', key: 'serviceFee', width: 90, render: function(_, row, i) { return calcRowServiceFee(row) + ' 元'; } }, + { title: '保证金', key: 'deposit', width: 100, render: function(_, row, i) { return React.createElement(Input, { placeholder: '0.00', value: row.deposit || '', onChange: function(e) { updateNewVehicleRow(i, 'deposit', e.target.value); }, addonAfter: '元' }); } }, + { title: '备注', key: 'remark', width: 80, render: function(_, row, i) { return React.createElement(Input, { placeholder: '请输入', value: row.remark || '', onChange: function(e) { updateNewVehicleRow(i, 'remark', e.target.value); } }); } }, + { title: '操作', key: 'action', width: 80, render: function(_, row, i) { return React.createElement(Button, { type: 'link', size: 'small', danger: true, onClick: function() { removeNewVehicleRow(i); } }, '删除'); } } + ]; + + var newOrderSummaryEl = React.createElement('div', { style: styles.summaryList }, + React.createElement('div', { style: styles.summaryListItem }, React.createElement('span', { style: styles.summaryListLabel }, '租赁车辆数'), React.createElement('span', { style: styles.summaryListValue }, newOrderSummary.vehicleCount + ' 辆')), + React.createElement('div', { style: styles.summaryListItem }, React.createElement('span', { style: styles.summaryListLabel }, '租金及服务费合计'), React.createElement('span', { style: styles.summaryListValue }, newOrderSummary.totalRentService + ' 元')), + React.createElement('div', { style: styles.summaryListItem }, React.createElement('span', { style: styles.summaryListLabel }, '保证金总额'), React.createElement('span', { style: styles.summaryListValue }, newOrderSummary.totalDeposit + ' 元')) + ); + + var serviceModalRows = serviceModalRowIndex !== null && newVehicleRows[serviceModalRowIndex] ? newVehicleRows[serviceModalRowIndex].serviceItems : []; + var serviceModalContent = serviceModalRowIndex !== null ? React.createElement('div', { style: styles.modalMask, onClick: function(e) { if (e.target === e.currentTarget) closeServiceModal(); } }, + React.createElement('div', { style: styles.modalBox, onClick: function(e) { e.stopPropagation(); } }, + React.createElement('div', { style: styles.modalHeader }, '服务项目'), + React.createElement('div', { style: styles.modalBody }, + React.createElement('table', { style: styles.rentalTable }, + React.createElement('thead', null, React.createElement('tr', null, React.createElement('th', { style: styles.rentalTh }, '服务项目'), React.createElement('th', { style: styles.rentalTh }, '费用'), React.createElement('th', { style: styles.rentalTh }, '生效时间'), React.createElement('th', { style: Object.assign({}, styles.rentalTh, { width: 80 }) }, '操作'))), + React.createElement('tbody', null, serviceModalRows.map(function(si, siIdx) { + return React.createElement('tr', { key: siIdx }, + React.createElement('td', { style: styles.rentalTd }, React.createElement(Select, { style: { width: '100%' }, placeholder: '请选择服务项目', value: si.project || undefined, onChange: function(v) { updateServiceItem(siIdx, 'project', v || ''); }, showSearch: true, filterOption: function(input, opt) { return opt && opt.children && String(opt.children).toLowerCase().indexOf((input || '').toLowerCase()) >= 0; }, allowClear: true }, serviceItemOptions.map(function(opt, oi) { return React.createElement(Option, { key: oi, value: opt }, opt); }))), + React.createElement('td', { style: styles.rentalTd }, React.createElement(Input, { placeholder: '0.00', value: si.fee || '', onChange: function(e) { updateServiceItem(siIdx, 'fee', e.target.value); }, addonAfter: '元', style: { width: '100%' } })), + React.createElement('td', { style: styles.rentalTd }, React.createElement(DatePicker, { style: { width: '100%' }, format: 'YYYY-MM-DD', placeholder: '请选择生效时间', value: si.effectiveDate && window.moment ? window.moment(si.effectiveDate, 'YYYY-MM-DD') : null, onChange: function(d, dateStr) { updateServiceItem(siIdx, 'effectiveDate', dateStr || ''); } })), + React.createElement('td', { style: styles.rentalTd }, React.createElement(Button, { type: 'link', size: 'small', danger: true, onClick: function() { removeServiceItem(siIdx); } }, '删除')) + ); + })) + ), + React.createElement(Button, { type: 'dashed', style: { marginTop: 12, width: '100%' }, onClick: addServiceItem }, '添加一行') + ), + React.createElement('div', { style: { padding: '12px 20px', borderTop: '1px solid #f0f0f0', textAlign: 'right', display: 'flex', gap: 12, justifyContent: 'flex-end' } }, + React.createElement(Button, { type: 'primary', onClick: function() { closeServiceModal(); } }, '保存'), + React.createElement(Button, { onClick: closeServiceModal }, '关闭') + ) + ) + ) : null; + + var newVehicleCardContent = React.createElement('div', null, + React.createElement('div', { style: styles.sectionTitle }, '新增订单'), + newOrderSummaryEl, + React.createElement(Table, { rowKey: function(_, i) { return String(i); }, size: 'small', columns: newVehicleColumns, dataSource: newVehicleRows, pagination: false, scroll: { x: 1100 } }), + React.createElement(Button, { type: 'dashed', style: { marginTop: 12, width: '100%' }, onClick: addNewVehicleRow }, '添加一行') + ); + + var feeContent = React.createElement('div', null, React.createElement('div', { style: styles.formRow }, React.createElement(FormItemReadOnly, { label: '选择费用模板', value: mockFeeTemplate })), feeTemplateBody); + var billingContent = React.createElement('div', null, React.createElement('div', { style: { padding: '12px 16px', border: '1px solid #e8e8e8', borderRadius: 4, backgroundColor: '#fafafa', fontSize: 14, color: '#333' } }, mockBillingMethod)); + var attachmentContent = React.createElement('div', { style: styles.card }, + React.createElement('div', { style: styles.cardHeader }, React.createElement('span', { style: styles.cardTitle }, '盖章合同附件')), + React.createElement('div', { style: styles.cardBody }, React.createElement('div', { style: { color: '#666', fontSize: 14 } }, '租赁合同-盖章版.pdf · 1.2 MB · 2026-02-16 14:30')) + ); + + var changeHistorySorted = [{ changeTime: '2026-02-16 14:00', opType: '变更内容', operator: '张三', remark: '"结束日期"由"2027-01-16"修改为"2027-02-16"' }]; + var historyTableRows = changeHistorySorted.map(function(row, index) { + return React.createElement('tr', { key: index }, + React.createElement('td', { style: styles.rentalTd }, index + 1), + React.createElement('td', { style: styles.rentalTd }, row.changeTime), + React.createElement('td', { style: styles.rentalTd }, row.opType), + React.createElement('td', { style: styles.rentalTd }, row.operator), + React.createElement('td', { style: styles.rentalTd }, row.remark) + ); + }); + var historyTable = React.createElement('table', { style: styles.rentalTable }, + React.createElement('thead', null, + React.createElement('tr', null, + React.createElement('th', { style: styles.rentalTh, width: 60 }, '序号'), + React.createElement('th', { style: styles.rentalTh, width: 140 }, '变更时间'), + React.createElement('th', { style: styles.rentalTh, width: 100 }, '操作类型'), + React.createElement('th', { style: styles.rentalTh, width: 90 }, '操作人'), + React.createElement('th', { style: styles.rentalTh }, '备注') + ) + ), + React.createElement('tbody', null, historyTableRows) + ); + var changeHistoryContent = React.createElement('div', { style: styles.card }, + React.createElement('div', { style: styles.cardHeader }, React.createElement('span', { style: styles.cardTitle }, '合同变更历史记录')), + React.createElement('div', { style: styles.cardBody }, React.createElement('div', { style: { overflowX: 'auto' } }, historyTable)) + ); + + return React.createElement('div', { style: styles.page }, + React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 } }, + React.createElement('div', { style: styles.breadcrumb }, React.createElement('span', null, '业务管理'), React.createElement('span', { style: styles.breadcrumbSep }, ' / '), React.createElement('span', null, '车辆租赁合同'), React.createElement('span', { style: styles.breadcrumbSep }, ' / '), React.createElement('span', { style: { color: '#1890ff' } }, '新增车辆')) + ), + React.createElement('div', { style: styles.anchorWrap }, + React.createElement('div', { style: { marginBottom: 8, fontWeight: 600, fontSize: 14 } }, '锚点导航'), + React.createElement('button', { type: 'button', style: styles.anchorItem, onClick: function() { scrollToCard('card-customer'); } }, '客户基本信息'), + React.createElement('button', { type: 'button', style: styles.anchorItem, onClick: function() { scrollToCard('card-contract'); } }, '合同基本信息'), + React.createElement('button', { type: 'button', style: styles.anchorItem, onClick: function() { scrollToCard('card-authorized'); } }, '被授权人信息'), + React.createElement('button', { type: 'button', style: styles.anchorItem, onClick: function() { scrollToCard('card-rental'); } }, '租赁订单信息'), + React.createElement('button', { type: 'button', style: styles.anchorItem, onClick: function() { scrollToCard('card-new-vehicle'); } }, '新增车辆信息'), + React.createElement('button', { type: 'button', style: styles.anchorItem, onClick: function() { scrollToCard('card-fee'); } }, '其他费用信息'), + React.createElement('button', { type: 'button', style: styles.anchorItem, onClick: function() { scrollToCard('card-billing'); } }, '账单计算方式'), + React.createElement('button', { type: 'button', style: styles.anchorItem, onClick: function() { scrollToCard('card-attachment'); } }, '盖章合同附件'), + React.createElement('button', { type: 'button', style: styles.anchorItem, onClick: function() { scrollToCard('card-history'); } }, '合同变更历史记录') + ), + React.createElement('div', { id: 'card-customer' }, React.createElement(CardBlock, { title: '客户基本信息', collapsed: cc1, setCollapsed: setCc1 }, customerFields)), + React.createElement('div', { id: 'card-contract', style: { marginTop: 16 } }, React.createElement(CardBlock, { title: '合同基本信息', collapsed: cc2, setCollapsed: setCc2 }, React.createElement('div', null, contractFormRow1, contractFormRow2))), + React.createElement('div', { id: 'card-authorized', style: { marginTop: 16 } }, React.createElement(CardBlock, { title: '被授权人信息', collapsed: cc3, setCollapsed: setCc3 }, authorizedContent)), + React.createElement('div', { id: 'card-rental', style: { marginTop: 16 } }, React.createElement(CardBlock, { title: '租赁订单信息', collapsed: cc4, setCollapsed: setCc4 }, rentalContent)), + React.createElement('div', { id: 'card-new-vehicle', style: { marginTop: 16 } }, React.createElement(CardBlock, { title: '新增车辆信息', collapsed: cc7, setCollapsed: setCc7 }, newVehicleCardContent)), + React.createElement('div', { id: 'card-fee', style: { marginTop: 16 } }, React.createElement(CardBlock, { title: '其他费用信息', collapsed: cc5, setCollapsed: setCc5 }, feeContent)), + React.createElement('div', { id: 'card-billing', style: { marginTop: 16 } }, React.createElement(CardBlock, { title: '账单计算方式', collapsed: cc6, setCollapsed: setCc6 }, billingContent)), + React.createElement('div', { id: 'card-attachment', style: { marginTop: 16 } }, attachmentContent), + React.createElement('div', { id: 'card-history', style: { marginTop: 16 } }, changeHistoryContent), + React.createElement('div', { style: { height: 60 } }), + serviceModalContent, + React.createElement('div', { style: styles.footer }, + React.createElement('button', { type: 'button', style: Object.assign({}, styles.btn, styles.btnDefault), onClick: function() { message.info('取消(原型)'); } }, '取消'), + React.createElement('button', { type: 'button', style: Object.assign({}, styles.btn, styles.btnPrimary), onClick: function() { message.success('已提交审核,审核通过后生效(原型)'); } }, '提交审核') + ) + ); +}; diff --git a/web端/车辆租赁合同/查看租赁合同.jsx b/web端/车辆租赁合同/查看租赁合同.jsx new file mode 100644 index 0000000..b325197 --- /dev/null +++ b/web端/车辆租赁合同/查看租赁合同.jsx @@ -0,0 +1,448 @@ +// 【重要】必须使用 const Component 作为组件变量名 - Axhub 产品原型 +// 车辆资产管理系统 - 查看租赁合同模块(只读 + 审批状态 + 盖章附件,IE11+ 兼容) + +const Component = function() { + var cc1State = React.useState(false); + var cc1 = cc1State[0]; + var setCc1 = cc1State[1]; + var cc2State = React.useState(false); + var cc2 = cc2State[0]; + var setCc2 = cc2State[1]; + var cc3State = React.useState(false); + var cc3 = cc3State[0]; + var setCc3 = cc3State[1]; + var cc4State = React.useState(false); + var cc4 = cc4State[0]; + var setCc4 = cc4State[1]; + var cc5State = React.useState(false); + var cc5 = cc5State[0]; + var setCc5 = cc5State[1]; + var cc6State = React.useState(false); + var cc6 = cc6State[0]; + var setCc6 = cc6State[1]; + var attachmentHoverState = React.useState(false); + var attachmentHover = attachmentHoverState[0]; + var setAttachmentHover = attachmentHoverState[1]; + var reqSpecState = React.useState(false); + var reqSpecOpen = reqSpecState[0]; + var setReqSpecOpen = reqSpecState[1]; + var servicePopoverRowState = React.useState(null); + var servicePopoverRow = servicePopoverRowState[0]; + var setServicePopoverRow = servicePopoverRowState[1]; + + // 模拟已上传的盖章合同附件 + var uploadedFile = { name: '租赁合同-盖章版.pdf', size: '1.2 MB', uploadTime: '2026-02-16 14:30' }; + + // 合同变更历史记录(变更时间倒序,序号 1.2.3...) + var changeHistoryRaw = [ + { changeTime: '2026-02-16 14:00', opType: '变更内容', operator: '张三', remark: '"结束日期"由"2027-01-16"修改为"2027-02-16"' }, + { changeTime: '2026-02-16 10:30', opType: '附加费用', operator: '李四', remark: '添加服务项目"保养费用",费用为"200",生效时间为"2026-03-01"\n添加服务项目"清洗费",费用为"80",生效时间为"2026-03-01"' }, + { changeTime: '2026-02-15 16:20', opType: '添加授权人', operator: '王五', remark: '添加授权人"李四"' }, + { changeTime: '2026-02-14 09:15', opType: '合同续签', operator: '赵六', remark: '原合同编码"JXZL20250210YW101100A"' }, + { changeTime: '2026-02-13 10:00', opType: '终止合同', operator: '周九', remark: '终止原因"客户提前解约"' }, + { changeTime: '2026-02-12 11:00', opType: '撤回合同', operator: '钱七', remark: '-' }, + { changeTime: '2026-02-10 14:30', opType: '转正式合同', operator: '孙八', remark: '原合同编码"JXZL20260210YW101230B"' } + ]; + var changeHistorySorted = changeHistoryRaw.slice().sort(function(a, b) { return b.changeTime.localeCompare(a.changeTime); }); + + var scrollToCard = function(id) { + var el = document.getElementById(id); + if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' }); + }; + + var styles = { + page: { padding: '16px 24px 48px', backgroundColor: '#f5f5f5', minHeight: '100vh', fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif', fontSize: 14 }, + breadcrumb: { marginBottom: 16, color: '#666' }, + breadcrumbSep: { margin: '0 8px', color: '#999' }, + anchorWrap: { position: 'fixed', top: 80, right: 24, zIndex: 100, backgroundColor: '#fff', borderRadius: 8, boxShadow: '0 2px 8px rgba(0,0,0,0.12)', padding: '12px 16px', minWidth: 160 }, + anchorItem: { display: 'block', padding: '6px 0', color: '#1890ff', cursor: 'pointer', border: 'none', background: 'none', width: '100%', textAlign: 'left', fontSize: 13 }, + card: { backgroundColor: '#fff', borderRadius: 8, marginBottom: 16, boxShadow: '0 1px 2px rgba(0,0,0,0.05)', overflow: 'hidden' }, + cardHeader: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '16px 20px', borderBottom: '1px solid #f0f0f0', cursor: 'pointer' }, + cardTitle: { fontSize: 16, fontWeight: 600, color: '#333' }, + cardToggle: { color: '#999', fontSize: 14 }, + cardBody: { padding: '20px 24px' }, + formRow: { display: 'flex', flexWrap: 'wrap', marginBottom: 16 }, + formCol: { flex: '0 0 33.33%', minWidth: 200, paddingRight: 16, marginBottom: 8 }, + formColFull: { flex: '0 0 100%', marginBottom: 8 }, + label: { display: 'block', marginBottom: 6, color: '#333' }, + input: { width: '100%', padding: '8px 12px', border: '1px solid #d9d9d9', borderRadius: 4, fontSize: 14 }, + inputDisabled: { backgroundColor: '#f5f5f5', color: '#666', cursor: 'default', borderColor: '#e8e8e8' }, + summaryList: { marginBottom: 16, display: 'flex', flexWrap: 'wrap', gap: 16 }, + summaryListItem: { flex: '0 0 calc(50% - 8px)', display: 'flex', alignItems: 'center', padding: '12px 16px', border: '1px solid #e8e8e8', borderRadius: 4, backgroundColor: '#fafafa', fontSize: 14, boxSizing: 'border-box' }, + summaryListLabel: { flex: '0 0 140px', color: '#666' }, + summaryListValue: { flex: 1, fontWeight: 600, color: '#333' }, + rentalTable: { width: '100%', borderCollapse: 'collapse', fontSize: 13 }, + rentalTh: { padding: '10px 8px', textAlign: 'left', borderBottom: '1px solid #e8e8e8', backgroundColor: '#fafafa', fontWeight: 600 }, + rentalTd: { padding: '8px', borderBottom: '1px solid #f0f0f0', verticalAlign: 'middle' }, + rentalInputDisabled: { backgroundColor: '#f5f5f5', color: '#666', border: '1px solid #e8e8e8', padding: '6px 10px', borderRadius: 4, width: '100%', fontSize: 13 }, + tag: { display: 'inline-block', padding: '2px 8px', marginRight: 8, marginBottom: 4, backgroundColor: '#e6f7ff', color: '#1890ff', borderRadius: 4, fontSize: 12 }, + footer: { position: 'fixed', bottom: 0, left: 0, right: 0, padding: '12px 24px', backgroundColor: '#fff', borderTop: '1px solid #e8e8e8', display: 'flex', gap: 12, justifyContent: 'flex-start', zIndex: 99 }, + btn: { padding: '8px 24px', borderRadius: 4, border: '1px solid #d9d9d9', cursor: 'pointer', fontSize: 14 }, + btnDefault: { backgroundColor: '#fff', color: '#333' }, + approvalCard: { backgroundColor: '#fff', borderRadius: 8, marginBottom: 16, boxShadow: '0 1px 2px rgba(0,0,0,0.05)', overflow: 'hidden' }, + approvalCardHeader: { padding: '16px 20px', borderBottom: '1px solid #f0f0f0', fontSize: 16, fontWeight: 600, color: '#333', textAlign: 'center' }, + approvalCardBody: { padding: '24px 20px', display: 'flex', justifyContent: 'center' }, + stepWrap: { display: 'flex', alignItems: 'flex-start', flexWrap: 'wrap', justifyContent: 'center' }, + stepItem: { flex: '1 1 0', minWidth: 140, maxWidth: 220, textAlign: 'center', position: 'relative' }, + stepIcon: { width: 32, height: 32, borderRadius: '50%', margin: '0 auto 8px', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 16, fontWeight: 600 }, + stepIconDone: { backgroundColor: '#52c41a', color: '#fff' }, + stepLine: { position: 'absolute', top: 16, left: '50%', right: '-50%', height: 2, backgroundColor: '#e8e8e8', zIndex: 0 }, + stepLineDone: { backgroundColor: '#52c41a' }, + stepTitle: { fontSize: 13, color: '#333', fontWeight: 500, marginBottom: 4 }, + stepDesc: { fontSize: 12, color: '#666' }, + stepStatus: { fontSize: 12, color: '#52c41a', marginTop: 4 }, + stepTime: { fontSize: 12, color: '#999', marginTop: 2 }, + attachmentRow: { display: 'flex', alignItems: 'center', flexWrap: 'wrap', gap: 12 }, + attachmentFile: { display: 'inline-flex', alignItems: 'center', padding: '8px 12px', backgroundColor: '#f5f5f5', borderRadius: 4, border: '1px solid #d9d9d9', fontSize: 14, color: '#1890ff', cursor: 'pointer', textDecoration: 'none', fontFamily: 'inherit', margin: 0, outline: 'none' }, + attachmentFileHover: { color: '#40a9ff', borderColor: '#1890ff', backgroundColor: '#e6f7ff' }, + attachmentMeta: { fontSize: 12, color: '#999', marginLeft: 8 }, + feeSectionTitle: { fontSize: 15, fontWeight: 600, color: '#333', marginTop: 20, marginBottom: 10 }, + feeSectionTitleFirst: { marginTop: 0 }, + historyLink: { color: '#1890ff', cursor: 'pointer', background: 'none', border: 'none', padding: 0, fontSize: 14 }, + modalMask: { position: 'fixed', left: 0, top: 0, right: 0, bottom: 0, backgroundColor: 'rgba(0,0,0,0.45)', zIndex: 1000, display: 'flex', alignItems: 'center', justifyContent: 'center' }, + modalBox: { backgroundColor: '#fff', borderRadius: 8, width: '90%', maxWidth: 640, maxHeight: '85vh', overflow: 'hidden', display: 'flex', flexDirection: 'column', boxShadow: '0 4px 20px rgba(0,0,0,0.15)' }, + modalHeader: { padding: '16px 20px', borderBottom: '1px solid #f0f0f0', fontSize: 16, fontWeight: 600 }, + modalBody: { padding: 20, overflow: 'auto', flex: 1 } + }; + + var CardBlock = function(props) { + return React.createElement('div', { id: props.id, style: styles.card }, + React.createElement('div', { style: styles.cardHeader, onClick: function() { props.setCollapsed(!props.collapsed); } }, + React.createElement('span', { style: styles.cardTitle }, props.title), + React.createElement('span', { style: styles.cardToggle }, props.collapsed ? '展开' : '收起') + ), + !props.collapsed ? React.createElement('div', { style: styles.cardBody }, props.children) : null + ); + }; + + var FormItemReadOnly = function(props) { + var colStyle = props.fullWidth ? styles.formColFull : styles.formCol; + return React.createElement('div', { style: colStyle }, + React.createElement('label', { style: styles.label }, props.label), + React.createElement('div', { style: Object.assign({}, styles.input, styles.inputDisabled) }, props.value || '—') + ); + }; + + // 审批流程步骤数据(全部已通过,含审核时间 YYYY-MM-DD HH:MM) + var approvalSteps = [ + { title: '业务部主管', person: '姚守涛', status: '已通过', approveTime: '2026-02-16 09:30' }, + { title: '事业部主管', person: '尚建华', status: '已通过', approveTime: '2026-02-16 10:15' }, + { title: '财务部', person: '宋欣怡 / 吕红', status: '已通过', approveTime: '2026-02-16 11:20' }, + { title: '法务部', person: '高洁 / 彭青松', status: '已通过', approveTime: '2026-02-16 14:00' } + ]; + + var approvalStepEls = approvalSteps.map(function(step, index) { + var isLast = index === approvalSteps.length - 1; + var lineStyle = Object.assign({}, styles.stepLine, step.status === '已通过' ? styles.stepLineDone : {}); + if (isLast) lineStyle.display = 'none'; + return React.createElement('div', { key: index, style: Object.assign({}, styles.stepItem, { zIndex: approvalSteps.length - index }) }, + !isLast ? React.createElement('div', { style: lineStyle }) : null, + React.createElement('div', { style: Object.assign({}, styles.stepIcon, styles.stepIconDone) }, '✓'), + React.createElement('div', { style: styles.stepTitle }, step.title), + React.createElement('div', { style: styles.stepDesc }, step.person), + React.createElement('div', { style: styles.stepStatus }, step.status), + step.approveTime ? React.createElement('div', { style: styles.stepTime }, step.approveTime) : null + ); + }); + + var approvalCardEl = React.createElement('div', { style: styles.approvalCard }, + React.createElement('div', { style: styles.approvalCardHeader }, '审批状态'), + React.createElement('div', { style: styles.approvalCardBody }, + React.createElement('div', { style: styles.stepWrap }, approvalStepEls) + ) + ); + + // 模拟只读数据(与新增合同同结构) + var mockCustomer = { name: '嘉兴某某物流有限公司', creditCode: '91330400MA2XXXXX1', address: '浙江省嘉兴市南湖区科技大道1号', contact: '张三', phone: '13800138001', email: 'zhangsan@example.com', companyName: '嘉兴某某物流有限公司', companyPhone: '0571-88888888', mailingAddress: '浙江省嘉兴市南湖区科技大道1号', bank: '中国工商银行嘉兴分行', bankAccount: '6222021234567890123', taxId: '91330400MA2XXXXX1' }; + var contractOriginal = { name: '租赁合同-原件.pdf', size: '1.2 MB', uploadTime: '2026-02-16 14:30' }; + var mockContract = { projectName: '嘉兴氢能运输项目', contractCode: 'JXZL20260216YW101235A', contractType: '正式合同', effectiveDate: '2026-02-16', paymentMethod: '预付', mainVehicleModels: '型号A1、型号A2', endDate: '2027-02-16', paymentPeriod: '1个月', signingCompany: '嘉兴羚牛', deliveryRegion: '浙江省 / 嘉兴市', deliveryLocation: '嘉兴市南湖区科技大道1号', remarks: '' }; + var mockAuthorized = [{ name: '张三', phone: '13800138001', idCard: '330102199001011234' }]; + var mockRentalOrders = [ + { brand: '品牌A', model: '型号A1', plateNo: '浙A10001', vin: 'L1234567890ABCDEF', monthRent: '8000', serviceFee: '500', deposit: '10000', remark: '', serviceItems: [{ project: '保养费用', fee: '200', effectiveDate: '2026-03-01' }, { project: '清洗费', fee: '80', effectiveDate: '2026-03-01' }] }, + { brand: '品牌A', model: '型号A2', plateNo: '浙B20002', vin: 'L2234567890ABCDEF', monthRent: '8000', serviceFee: '500', deposit: '10000', remark: '', serviceItems: [{ project: '清洗费', fee: '80', effectiveDate: '2026-03-01' }] } + ]; + var mockRentalSummary = { vehicleCount: 2, totalRentService: '17000.00', totalDeposit: '20000.00', hydrogenPrepay: '5000.00' }; + var mockHydrogen = { bearer: '客户', paymentMethod: '预付', hydrogenPrepay: '5000', returnPrice: '80' }; + var mockFeeTemplate = '标准费用模板A'; + var mockBillingMethod = '按自然月结算'; + var feeTemplateCertFees = [{ project: '补办行驶证', standard: '50元/次', serviceFee: '20' }, { project: '补办驾驶证', standard: '30元/次', serviceFee: '10' }, { project: '补办牌照', standard: '100元/次', serviceFee: '50' }]; + var feeTemplatePenaltyFees = [{ project: '提前退车违约金', standard: '月租金×1', serviceFee: '0' }, { project: '违章处理违约金', standard: '按实际发生', serviceFee: '50' }]; + var feeTemplateConsumables = [{ category: '轮胎', part: '前轮', partName: '轮胎A型', qty: 1, feeDetail: '500.00' }, { category: '易损件', part: '雨刮', partName: '雨刮片', qty: 2, feeDetail: '80.00' }]; + var feeTemplateOtherFees = [{ project: '上门送车费', standard: '100元/次', serviceFee: '50' }, { project: '上门收车费', standard: '100元/次', serviceFee: '50' }, { project: '清洗费', standard: '80元/次', serviceFee: '30' }]; + var feeTableHeader3 = React.createElement('tr', null, React.createElement('th', { style: styles.rentalTh }, '项目'), React.createElement('th', { style: styles.rentalTh }, '收费标准'), React.createElement('th', { style: styles.rentalTh }, '服务费')); + var feeTableHeader5 = React.createElement('tr', null, React.createElement('th', { style: styles.rentalTh }, '类别'), React.createElement('th', { style: styles.rentalTh }, '损坏部位'), React.createElement('th', { style: styles.rentalTh }, '配件'), React.createElement('th', { style: styles.rentalTh }, '数量'), React.createElement('th', { style: styles.rentalTh }, '费用明细')); + var makeFeeRow3 = function(r, i) { + var td1 = React.createElement('td', { style: styles.rentalTd }, r.project); + var td2 = React.createElement('td', { style: styles.rentalTd }, r.standard); + var td3 = React.createElement('td', { style: styles.rentalTd }, r.serviceFee); + return React.createElement('tr', { key: i }, td1, td2, td3); + }; + var makeFeeRow5 = function(r, i) { + var td1 = React.createElement('td', { style: styles.rentalTd }, r.category); + var td2 = React.createElement('td', { style: styles.rentalTd }, r.part); + var td3 = React.createElement('td', { style: styles.rentalTd }, r.partName); + var td4 = React.createElement('td', { style: styles.rentalTd }, r.qty); + var td5 = React.createElement('td', { style: styles.rentalTd }, r.feeDetail); + return React.createElement('tr', { key: i }, td1, td2, td3, td4, td5); + }; + var feeCertRows = feeTemplateCertFees.map(makeFeeRow3); + var feePenaltyRows = feeTemplatePenaltyFees.map(makeFeeRow3); + var feeConsumablesRows = feeTemplateConsumables.map(makeFeeRow5); + var feeOtherRows = feeTemplateOtherFees.map(makeFeeRow3); + var feeCertTable = React.createElement('table', { style: styles.rentalTable }, React.createElement('thead', null, feeTableHeader3), React.createElement('tbody', null, feeCertRows)); + var feePenaltyTable = React.createElement('table', { style: styles.rentalTable }, React.createElement('thead', null, feeTableHeader3), React.createElement('tbody', null, feePenaltyRows)); + var feeConsumablesTable = React.createElement('table', { style: styles.rentalTable }, React.createElement('thead', null, feeTableHeader5), React.createElement('tbody', null, feeConsumablesRows)); + var feeOtherTable = React.createElement('table', { style: styles.rentalTable }, React.createElement('thead', null, feeTableHeader3), React.createElement('tbody', null, feeOtherRows)); + var feeTemplateBody = React.createElement('div', null, + React.createElement('div', { style: Object.assign({}, styles.feeSectionTitle, styles.feeSectionTitleFirst) }, '证照补办费用'), + feeCertTable, + React.createElement('div', { style: styles.feeSectionTitle }, '违约金费用'), + feePenaltyTable, + React.createElement('div', { style: styles.feeSectionTitle }, '易损件信息'), + feeConsumablesTable, + React.createElement('div', { style: styles.feeSectionTitle }, '其他费用信息'), + feeOtherTable + ); + + var customerFields = React.createElement('div', { style: styles.formRow }, + React.createElement(FormItemReadOnly, { label: '客户名称', value: mockCustomer.name }), + React.createElement(FormItemReadOnly, { label: '客户统一信用代码', value: mockCustomer.creditCode }), + React.createElement(FormItemReadOnly, { label: '客户地址', value: mockCustomer.address }), + React.createElement(FormItemReadOnly, { label: '客户联系人', value: mockCustomer.contact }), + React.createElement(FormItemReadOnly, { label: '客户电话', value: mockCustomer.phone }), + React.createElement(FormItemReadOnly, { label: '客户电子邮箱', value: mockCustomer.email }), + React.createElement(FormItemReadOnly, { label: '企业名称', value: mockCustomer.companyName }), + React.createElement(FormItemReadOnly, { label: '企业电话', value: mockCustomer.companyPhone }), + React.createElement(FormItemReadOnly, { label: '邮寄地址', value: mockCustomer.mailingAddress }), + React.createElement(FormItemReadOnly, { label: '开户银行', value: mockCustomer.bank }), + React.createElement(FormItemReadOnly, { label: '银行账号', value: mockCustomer.bankAccount }), + React.createElement(FormItemReadOnly, { label: '纳税人识别号', value: mockCustomer.taxId }), + React.createElement(FormItemReadOnly, { label: '业务部门', value: '业务1部' }), + React.createElement(FormItemReadOnly, { label: '业务负责人', value: '张经理' }) + ); + + var contractFormRow1 = React.createElement('div', { style: styles.formRow }, + React.createElement(FormItemReadOnly, { label: '项目名称', value: mockContract.projectName }), + React.createElement(FormItemReadOnly, { label: '合同编码', value: mockContract.contractCode }), + React.createElement(FormItemReadOnly, { label: '合同类型', value: mockContract.contractType }), + React.createElement(FormItemReadOnly, { label: '生效日期', value: mockContract.effectiveDate }), + React.createElement(FormItemReadOnly, { label: '付款方式', value: mockContract.paymentMethod }), + React.createElement(FormItemReadOnly, { label: '主要车型', value: mockContract.mainVehicleModels }), + React.createElement(FormItemReadOnly, { label: '结束日期', value: mockContract.endDate }), + React.createElement(FormItemReadOnly, { label: '付款周期', value: mockContract.paymentPeriod }), + React.createElement(FormItemReadOnly, { label: '签约公司', value: mockContract.signingCompany }), + React.createElement('div', { style: styles.formCol }, + React.createElement(FormItemReadOnly, { label: '交车区域', value: mockContract.deliveryRegion }), + React.createElement('div', null, + React.createElement('label', { style: styles.label }, '合同原件'), + React.createElement('div', { style: Object.assign({}, styles.input, styles.inputDisabled, { padding: '8px 12px' }) }, + contractOriginal + ? React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' } }, + React.createElement('a', { href: '#', style: { color: '#1890ff', cursor: 'pointer', textDecoration: 'none' }, onClick: function(e) { e.preventDefault(); window.open('#', '_blank'); } }, contractOriginal.name), + (contractOriginal.size || contractOriginal.uploadTime) ? React.createElement('span', { style: { color: '#999', fontSize: 12 } }, (contractOriginal.size || '') + (contractOriginal.size && contractOriginal.uploadTime ? ' · ' : '') + (contractOriginal.uploadTime || '')) : null + ) + : '—' + ) + ) + ), + React.createElement(FormItemReadOnly, { label: '交车地点', value: mockContract.deliveryLocation }) + ); + var contractFormRow2 = React.createElement('div', { style: styles.formRow }, React.createElement(FormItemReadOnly, { label: '备注', fullWidth: true, value: mockContract.remarks || '—' })); + + var authorizedContent = React.createElement('div', null, + React.createElement('div', { style: { display: 'flex', gap: 12, alignItems: 'center', marginBottom: 8 } }, + React.createElement('span', { style: { flex: 1, fontWeight: 600, fontSize: 13 } }, '被授权人姓名'), + React.createElement('span', { style: { flex: 1, fontWeight: 600, fontSize: 13 } }, '被授权人联系电话'), + React.createElement('span', { style: { flex: 1, fontWeight: 600, fontSize: 13 } }, '被授权人身份证') + ), + mockAuthorized.map(function(item, index) { + return React.createElement('div', { key: index, style: { display: 'flex', gap: 12, alignItems: 'center', marginBottom: 12 } }, + React.createElement('div', { style: Object.assign({}, styles.input, styles.inputDisabled, { flex: 1 }) }, item.name), + React.createElement('div', { style: Object.assign({}, styles.input, styles.inputDisabled, { flex: 1 }) }, item.phone), + React.createElement('div', { style: Object.assign({}, styles.input, styles.inputDisabled, { flex: 1 }) }, item.idCard) + ); + }) + ); + + var rentalSummaryEl = React.createElement('div', { style: styles.summaryList }, + React.createElement('div', { style: styles.summaryListItem }, React.createElement('span', { style: styles.summaryListLabel }, '租赁车辆数'), React.createElement('span', { style: styles.summaryListValue }, mockRentalSummary.vehicleCount + ' 辆')), + React.createElement('div', { style: styles.summaryListItem }, React.createElement('span', { style: styles.summaryListLabel }, '租金及服务费合计'), React.createElement('span', { style: styles.summaryListValue }, mockRentalSummary.totalRentService + ' 元')), + React.createElement('div', { style: styles.summaryListItem }, React.createElement('span', { style: styles.summaryListLabel }, '保证金总额'), React.createElement('span', { style: styles.summaryListValue }, mockRentalSummary.totalDeposit + ' 元')), + React.createElement('div', { style: styles.summaryListItem }, React.createElement('span', { style: styles.summaryListLabel }, '氢气预付款金额'), React.createElement('span', { style: styles.summaryListValue }, mockRentalSummary.hydrogenPrepay + ' 元')) + ); + var Popover = window.antd && window.antd.Popover; + var renderServiceItemsPopover = function(row) { + var items = row.serviceItems || []; + if (items.length === 0) { + return React.createElement('div', { style: { padding: 12, minWidth: 320, fontSize: 13 } }, '暂无服务项明细'); + } + var thead = React.createElement('thead', null, + React.createElement('tr', null, + React.createElement('th', { style: styles.rentalTh }, '服务项目'), + React.createElement('th', { style: styles.rentalTh }, '费用'), + React.createElement('th', { style: styles.rentalTh }, '生效时间') + ) + ); + var tbody = React.createElement('tbody', null, + items.map(function(si, i) { + return React.createElement('tr', { key: i }, + React.createElement('td', { style: styles.rentalTd }, si.project || '—'), + React.createElement('td', { style: styles.rentalTd }, si.fee != null && si.fee !== '' ? si.fee + ' 元' : '—'), + React.createElement('td', { style: styles.rentalTd }, si.effectiveDate || '—') + ); + }) + ); + return React.createElement('div', { style: { padding: 8 } }, + React.createElement('table', { style: styles.rentalTable }, thead, tbody) + ); + }; + var rentalTableBody = mockRentalOrders.map(function(row, idx) { + var servicePopoverContent = renderServiceItemsPopover(row); + var serviceCell = Popover + ? React.createElement(Popover, { content: servicePopoverContent, title: '服务项明细', trigger: 'click' }, + React.createElement('span', { style: { color: '#1890ff', cursor: 'pointer', fontSize: 13 } }, '查看')) + : React.createElement('span', { + style: { color: '#1890ff', cursor: 'pointer', fontSize: 13 }, + onClick: function() { setServicePopoverRow(servicePopoverRow === idx ? null : idx); } + }, '查看'); + return React.createElement('tr', { key: idx }, + React.createElement('td', { style: styles.rentalTd }, idx + 1), + React.createElement('td', { style: styles.rentalTd }, React.createElement('div', { style: styles.rentalInputDisabled }, row.brand)), + React.createElement('td', { style: styles.rentalTd }, React.createElement('div', { style: styles.rentalInputDisabled }, row.model)), + React.createElement('td', { style: styles.rentalTd }, React.createElement('div', { style: styles.rentalInputDisabled }, row.plateNo)), + React.createElement('td', { style: styles.rentalTd }, React.createElement('div', { style: styles.rentalInputDisabled }, row.vin)), + React.createElement('td', { style: styles.rentalTd }, React.createElement('div', { style: styles.rentalInputDisabled }, row.monthRent + ' 元')), + React.createElement('td', { style: styles.rentalTd }, serviceCell), + React.createElement('td', { style: styles.rentalTd }, React.createElement('div', { style: styles.rentalInputDisabled }, row.serviceFee + ' 元')), + React.createElement('td', { style: styles.rentalTd }, React.createElement('div', { style: styles.rentalInputDisabled }, row.deposit + ' 元')), + React.createElement('td', { style: styles.rentalTd }, React.createElement('div', { style: styles.rentalInputDisabled }, row.remark || '—')) + ); + }); + var rentalTableEl = React.createElement('table', { style: styles.rentalTable }, + React.createElement('thead', null, + React.createElement('tr', null, + React.createElement('th', { style: styles.rentalTh, width: 50 }, '序号'), + React.createElement('th', { style: styles.rentalTh, width: 100 }, '品牌'), + React.createElement('th', { style: styles.rentalTh, width: 100 }, '型号'), + React.createElement('th', { style: styles.rentalTh, width: 120 }, '车牌号'), + React.createElement('th', { style: styles.rentalTh, width: 160 }, '车辆识别代码'), + React.createElement('th', { style: styles.rentalTh, width: 120 }, '车辆月租金'), + React.createElement('th', { style: styles.rentalTh, width: 80 }, '服务费项目'), + React.createElement('th', { style: styles.rentalTh, width: 90 }, '服务费'), + React.createElement('th', { style: styles.rentalTh, width: 100 }, '保证金'), + React.createElement('th', { style: styles.rentalTh, width: 80 }, '备注') + ) + ), + React.createElement('tbody', null, rentalTableBody) + ); + var hydrogenReadOnly = React.createElement('div', { style: styles.formRow }, + React.createElement(FormItemReadOnly, { label: '氢费承担方', value: mockHydrogen.bearer }), + React.createElement(FormItemReadOnly, { label: '付款方式', value: mockHydrogen.paymentMethod }), + React.createElement(FormItemReadOnly, { label: '氢气预付款', value: mockHydrogen.hydrogenPrepay + ' 元' }), + React.createElement(FormItemReadOnly, { label: '退还车氢气单价', value: mockHydrogen.returnPrice + ' 元' }) + ); + var rentalContent = React.createElement('div', null, rentalSummaryEl, React.createElement('div', { style: { overflowX: 'auto', marginBottom: 16 } }, rentalTableEl), hydrogenReadOnly); + + var feeContent = React.createElement('div', null, React.createElement('div', { style: styles.formRow }, React.createElement(FormItemReadOnly, { label: '选择费用模板', value: mockFeeTemplate })), feeTemplateBody); + + var billingContent = React.createElement('div', null, React.createElement('div', { style: { padding: '12px 16px', border: '1px solid #e8e8e8', borderRadius: 4, backgroundColor: '#fafafa', fontSize: 14, color: '#333' } }, mockBillingMethod)); + + var attachmentContent = React.createElement('div', { style: styles.card }, + React.createElement('div', { style: styles.cardHeader }, + React.createElement('span', { style: styles.cardTitle }, '盖章合同附件') + ), + React.createElement('div', { style: styles.cardBody }, + React.createElement('div', { style: styles.attachmentRow }, + React.createElement('button', { + type: 'button', + style: Object.assign({}, styles.attachmentFile, attachmentHover ? styles.attachmentFileHover : {}), + onMouseEnter: function() { setAttachmentHover(true); }, + onMouseLeave: function() { setAttachmentHover(false); }, + onClick: function() { window.open('#', '_blank'); } + }, '📄 ' + uploadedFile.name, React.createElement('span', { style: styles.attachmentMeta }, uploadedFile.size + ' · ' + uploadedFile.uploadTime)) + ) + ) + ); + + var historyTableRows = changeHistorySorted.map(function(row, index) { + return React.createElement('tr', { key: index }, + React.createElement('td', { style: styles.rentalTd }, index + 1), + React.createElement('td', { style: styles.rentalTd }, row.changeTime), + React.createElement('td', { style: styles.rentalTd }, row.opType), + React.createElement('td', { style: styles.rentalTd }, row.operator), + React.createElement('td', { style: styles.rentalTd }, React.createElement('button', { type: 'button', style: styles.historyLink, onClick: function() { window.open('#', '_blank'); } }, '查看变更前记录')), + React.createElement('td', { style: Object.assign({}, styles.rentalTd, { verticalAlign: 'top', whiteSpace: 'pre-line' }) }, row.remark) + ); + }); + var historyTable = React.createElement('table', { style: styles.rentalTable }, + React.createElement('thead', null, + React.createElement('tr', null, + React.createElement('th', { style: styles.rentalTh, width: 60 }, '序号'), + React.createElement('th', { style: styles.rentalTh, width: 140 }, '变更时间'), + React.createElement('th', { style: styles.rentalTh, width: 100 }, '操作类型'), + React.createElement('th', { style: styles.rentalTh, width: 90 }, '操作人'), + React.createElement('th', { style: styles.rentalTh, width: 120 }, '原始记录'), + React.createElement('th', { style: styles.rentalTh }, '备注') + ) + ), + React.createElement('tbody', null, historyTableRows) + ); + var changeHistoryContent = React.createElement('div', { style: styles.card }, + React.createElement('div', { style: styles.cardHeader }, + React.createElement('span', { style: styles.cardTitle }, '合同变更历史记录') + ), + React.createElement('div', { style: styles.cardBody }, + React.createElement('div', { style: { overflowX: 'auto' } }, historyTable) + ) + ); + + var reqSpecBlock = { marginBottom: 8 }; + var reqSpecH2 = { fontSize: 14, fontWeight: 600, marginTop: 16, marginBottom: 8, color: '#333' }; + var reqSpecH2First = { marginTop: 0 }; + var reqSpecP = { fontSize: 13, lineHeight: 1.6, marginBottom: 6, color: '#555' }; + var reqSpecLi = { fontSize: 13, lineHeight: 1.6, marginBottom: 4, marginLeft: 20, color: '#555' }; + var reqSpecDoc = React.createElement('div', { style: { padding: '0 4px' } }, + React.createElement('div', { style: reqSpecBlock }, React.createElement('div', { style: Object.assign({}, reqSpecH2, reqSpecH2First) }, '1.其他部分与新增租赁合同相同,只是不可编辑,新增以下几块内容:')), + React.createElement('div', { style: reqSpecBlock }, React.createElement('div', { style: reqSpecH2 }, '2.审批状态卡片:'), React.createElement('div', { style: reqSpecP }, '显示所有审批步骤及当前节点、审批人姓名、已通过/待审批/驳回三种状态;最下方为对应审批节点审批操作时间,格式为 YYYY-MM-DD HH:MM。')), + React.createElement('div', { style: reqSpecBlock }, React.createElement('div', { style: reqSpecH2 }, '3.盖章合同附件:'), React.createElement('div', { style: reqSpecP }, '当法务未完成审核并上传盖章合同时,盖章合同附件显示为暂无附件;已上传盖章合同时,显示合同文件名称、文件大小、上传时间(YYYY-MM-DD HH:MM)。')), + React.createElement('div', { style: reqSpecBlock }, React.createElement('div', { style: reqSpecH2 }, '4.合同变更历史记录:'), React.createElement('div', { style: reqSpecP }, '列表字段为序号、变更时间、操作类型、操作人、原始记录、备注。'), React.createElement('div', { style: reqSpecLi }, '4.1.序号:1.2.3...以此类推;'), React.createElement('div', { style: reqSpecLi }, '4.2.变更时间:YYYY-MM-DD HH:MM 格式,倒序展示数据;'), React.createElement('div', { style: reqSpecLi }, '4.3.操作类型:包括变更内容、合同续签、撤回合同、添加授权人、附加费用、转正式合同、终止合同;'), React.createElement('div', { style: reqSpecLi }, '4.4.操作人:记录对应操作用户名称;'), React.createElement('div', { style: reqSpecLi }, '4.5.原始记录:显示查看变更前记录,点击会新开页查看未修改前合同原始记录;'), React.createElement('div', { style: reqSpecLi }, '4.6.备注:'), React.createElement('div', { style: Object.assign({}, reqSpecLi, { marginLeft: 36 }) }, '4.6.1.操作类型为变更内容时,显示:"字段名"由"xx"修改为"xxx";'), React.createElement('div', { style: Object.assign({}, reqSpecLi, { marginLeft: 36 }) }, '4.6.2.操作类型为合同续签时,显示:原合同编码"xxxxxxxxx";'), React.createElement('div', { style: Object.assign({}, reqSpecLi, { marginLeft: 36 }) }, '4.6.3.操作类型为撤回合同时,显示:-;'), React.createElement('div', { style: Object.assign({}, reqSpecLi, { marginLeft: 36 }) }, '4.6.4.操作类型为添加授权人时,显示:添加授权人"xxx";'), React.createElement('div', { style: Object.assign({}, reqSpecLi, { marginLeft: 36 }) }, '4.6.5.操作类型为附加费用时,显示:添加服务项目"xxxxxx",费用为"xxxxxxx",生效时间为"YYYY-MM-DD",如果有多条附加费用,支持多行显示;'), React.createElement('div', { style: Object.assign({}, reqSpecLi, { marginLeft: 36 }) }, '4.6.6.操作类型为转正式合同时,显示:原合同编码"xxxxxxxx";'), React.createElement('div', { style: Object.assign({}, reqSpecLi, { marginLeft: 36 }) }, '4.6.7.操作类型为终止合同时,显示:终止原因"xxxxxx"。')) + ); + var reqSpecModalContent = reqSpecOpen ? React.createElement('div', { style: styles.modalMask, onClick: function(e) { if (e.target === e.currentTarget) setReqSpecOpen(false); } }, React.createElement('div', { style: styles.modalBox, onClick: function(e) { e.stopPropagation(); } }, React.createElement('div', { style: styles.modalHeader }, '需求说明'), React.createElement('div', { style: Object.assign({}, styles.modalBody, { maxHeight: '70vh', padding: '20px 24px' }) }, reqSpecDoc), React.createElement('div', { style: { padding: '12px 20px', borderTop: '1px solid #f0f0f0', textAlign: 'right' } }, React.createElement('button', { style: Object.assign({}, styles.btn, styles.btnDefault), onClick: function() { setReqSpecOpen(false); } }, '关闭')))) : null; + + var servicePopoverContentCustom = !Popover && servicePopoverRow !== null && mockRentalOrders[servicePopoverRow] + ? React.createElement('div', { style: styles.modalMask, onClick: function(e) { if (e.target === e.currentTarget) setServicePopoverRow(null); } }, + React.createElement('div', { style: Object.assign({}, styles.modalBox, { maxWidth: 480 }), onClick: function(e) { e.stopPropagation(); } }, + React.createElement('div', { style: styles.modalHeader }, '服务项明细'), + React.createElement('div', { style: Object.assign({}, styles.modalBody, { padding: '16px 20px' }) }, renderServiceItemsPopover(mockRentalOrders[servicePopoverRow])), + React.createElement('div', { style: { padding: '12px 20px', borderTop: '1px solid #f0f0f0', textAlign: 'right' } }, React.createElement('button', { style: Object.assign({}, styles.btn, styles.btnDefault), onClick: function() { setServicePopoverRow(null); } }, '关闭')) + )) + : null; + + return React.createElement('div', { style: styles.page }, + servicePopoverContentCustom, + React.createElement('div', { style: { marginBottom: 16 } }, React.createElement('div', { style: styles.breadcrumb }, React.createElement('span', null, '业务管理'), React.createElement('span', { style: styles.breadcrumbSep }, ' / '), React.createElement('span', null, '车辆租赁合同'), React.createElement('span', { style: styles.breadcrumbSep }, ' / '), React.createElement('span', { style: { color: '#1890ff' } }, '查看租赁合同'))), + approvalCardEl, + React.createElement('div', { style: styles.anchorWrap }, + React.createElement('div', { style: { marginBottom: 8, fontWeight: 600, fontSize: 14 } }, '锚点导航'), + React.createElement('button', { style: styles.anchorItem, onClick: function() { scrollToCard('card-customer'); } }, '客户基本信息'), + React.createElement('button', { style: styles.anchorItem, onClick: function() { scrollToCard('card-contract'); } }, '合同基本信息'), + React.createElement('button', { style: styles.anchorItem, onClick: function() { scrollToCard('card-authorized'); } }, '被授权人信息'), + React.createElement('button', { style: styles.anchorItem, onClick: function() { scrollToCard('card-rental'); } }, '租赁订单信息'), + React.createElement('button', { style: styles.anchorItem, onClick: function() { scrollToCard('card-fee'); } }, '其他费用信息'), + React.createElement('button', { style: styles.anchorItem, onClick: function() { scrollToCard('card-billing'); } }, '账单计算方式'), + React.createElement('button', { style: styles.anchorItem, onClick: function() { scrollToCard('card-attachment'); } }, '盖章合同附件'), + React.createElement('button', { style: styles.anchorItem, onClick: function() { scrollToCard('card-history'); } }, '合同变更历史记录') + ), + React.createElement('div', { id: 'card-customer' }, React.createElement(CardBlock, { id: 'card-customer', title: '客户基本信息', collapsed: cc1, setCollapsed: setCc1 }, customerFields)), + React.createElement('div', { id: 'card-contract', style: { marginTop: 16 } }, React.createElement(CardBlock, { title: '合同基本信息', collapsed: cc2, setCollapsed: setCc2 }, React.createElement('div', null, contractFormRow1, contractFormRow2))), + React.createElement('div', { id: 'card-authorized', style: { marginTop: 16 } }, React.createElement(CardBlock, { title: '被授权人信息', collapsed: cc3, setCollapsed: setCc3 }, authorizedContent)), + React.createElement('div', { id: 'card-rental', style: { marginTop: 16 } }, React.createElement(CardBlock, { title: '租赁订单信息', collapsed: cc4, setCollapsed: setCc4 }, rentalContent)), + React.createElement('div', { id: 'card-fee', style: { marginTop: 16 } }, React.createElement(CardBlock, { title: '其他费用信息', collapsed: cc5, setCollapsed: setCc5 }, feeContent)), + React.createElement('div', { id: 'card-billing', style: { marginTop: 16 } }, React.createElement(CardBlock, { title: '账单计算方式', collapsed: cc6, setCollapsed: setCc6 }, billingContent)), + React.createElement('div', { id: 'card-attachment', style: { marginTop: 16 } }, attachmentContent), + React.createElement('div', { id: 'card-history', style: { marginTop: 16 } }, changeHistoryContent), + React.createElement('div', { style: { height: 60 } }), + reqSpecModalContent, + React.createElement('div', { style: styles.footer }, React.createElement('button', { style: Object.assign({}, styles.btn, styles.btnDefault) }, '返回')) + ); +}; diff --git a/web端/车辆租赁合同/车辆租赁合同.jsx b/web端/车辆租赁合同/车辆租赁合同.jsx new file mode 100644 index 0000000..1ef0f38 --- /dev/null +++ b/web端/车辆租赁合同/车辆租赁合同.jsx @@ -0,0 +1,1064 @@ +// 【重要】必须使用 const Component 作为组件变量名 +// 业务管理 - 车辆租赁合同(列表页,租赁合同管理) + +const Component = function() { + var useState = React.useState; + var useMemo = React.useMemo; + var useCallback = React.useCallback; + var antd = window.antd; + var Breadcrumb = antd.Breadcrumb; + var Select = antd.Select; + var Input = antd.Input; + var Button = antd.Button; + var Table = antd.Table; + var Space = antd.Space; + var Card = antd.Card; + var DatePicker = antd.DatePicker; + var Popover = antd.Popover; + var Dropdown = antd.Dropdown; + var Modal = antd.Modal; + var message = antd.message; + var App = antd.App; + + var RangePicker = DatePicker.RangePicker; + + // 筛选展开(默认收起,只显示第一行 3 列) + var _filterExpanded = useState(false); + var _contractCode = useState(undefined); + var _projectName = useState(undefined); + var _customerName = useState(undefined); + var _signingCompany = useState(undefined); + var _approvalStatus = useState(['全部']); + var _contractStatus = useState(['全部']); + var _businessDept = useState([]); + var _businessOwner = useState([]); + var _contractType = useState(['全部']); + var _creator = useState([]); + var _endDateRange = useState(null); + + var _appliedFilter = useState({ + contractCode: undefined, + projectName: undefined, + customerName: undefined, + signingCompany: undefined, + approvalStatus: ['全部'], + contractStatus: ['全部'], + businessDept: [], + businessOwner: [], + contractType: ['全部'], + creator: [], + endDateRange: null + }); + + var _vehiclePopoverRecord = useState(null); + var _authorizedModalVisible = useState(false); + var _authorizedModalRecord = useState(null); + var _authorizedList = useState([{ name: '', phone: '', idCard: '' }]); + var _extraFeeModalVisible = useState(false); + var _extraFeeModalRecord = useState(null); + var _extraFeeList = useState([]); + var _deleteModalVisible = useState(false); + var _deleteModalRecord = useState(null); + var _withdrawModalVisible = useState(false); + var _withdrawModalRecord = useState(null); + var _requirementModalVisible = useState(false); + + // 模拟选项(与新增租赁合同保持一致) + var contractCodeOptions = [ + { value: 'HT-ZL-2025-001', label: 'HT-ZL-2025-001' }, + { value: 'HT-ZL-2025-002', label: 'HT-ZL-2025-002' }, + { value: 'HT-ZL-2025-003', label: 'HT-ZL-2025-003' }, + { value: 'HT-ZL-2025-004', label: 'HT-ZL-2025-004' }, + { value: 'HT-ZL-2025-005', label: 'HT-ZL-2025-005' }, + { value: 'HT-ZL-2025-006', label: 'HT-ZL-2025-006' }, + { value: 'HT-ZL-2025-007', label: 'HT-ZL-2025-007' }, + { value: 'HT-ZL-2025-008', label: 'HT-ZL-2025-008' }, + { value: 'HT-ZL-2024-009', label: 'HT-ZL-2024-009' }, + { value: 'HT-ZL-2024-010', label: 'HT-ZL-2024-010' } + ]; + var projectNameOptions = [ + { value: 'p1', label: '嘉兴氢能示范项目' }, + { value: 'p2', label: '上海物流租赁项目' }, + { value: 'p3', label: '杭州城配租赁项目' } + ]; + var customerNameOptions = [ + { value: 'c1', label: '嘉兴某某物流有限公司' }, + { value: 'c2', label: '上海某某运输公司' }, + { value: 'c3', label: '杭州某某租赁有限公司' } + ]; + var signingCompanyOptions = [ + { value: '嘉兴羚牛', label: '嘉兴羚牛' }, + { value: '上海羚牛', label: '上海羚牛' }, + { value: '广东羚牛', label: '广东羚牛' } + ]; + var approvalStatusOptions = [ + { value: '全部', label: '全部' }, + { value: '待审批', label: '待审批' }, + { value: '审批中', label: '审批中' }, + { value: '审批通过', label: '审批通过' }, + { value: '审批驳回', label: '审批驳回' }, + { value: '未提交', label: '未提交' }, + { value: '已结束', label: '已结束' } + ]; + var contractStatusOptions = [ + { value: '全部', label: '全部' }, + { value: '草稿', label: '草稿' }, + { value: '变更', label: '变更' }, + { value: '合同进行中', label: '合同进行中' }, + { value: '到期合同', label: '到期合同' }, + { value: '已提交审批', label: '已提交审批' } + ]; + var contractTypeOptions = [ + { value: '全部', label: '全部' }, + { value: '正式合同', label: '正式合同' }, + { value: '试用合同', label: '试用合同' } + ]; + var deptOptions = [ + { value: '业务1部', label: '业务1部' }, + { value: '业务2部', label: '业务2部' }, + { value: '业务3部', label: '业务3部' } + ]; + var userOptions = [ + { value: '张经理', label: '张经理' }, + { value: '李专员', label: '李专员' }, + { value: '王专员', label: '王专员' }, + { value: '赵经理', label: '赵经理' }, + { value: '钱专员', label: '钱专员' } + ]; + + // 模拟列表数据:按审批状态 × 合同状态组合生成样例 + // 审批状态:待审批、审批中、审批通过、审批驳回、未提交、已结束 + // 合同状态:草稿、变更、合同进行中、到期合同、已提交审批 + var rawList = [ + // 1. 未提交 + 草稿(仅保存未提交) + { + id: '1', + contractCode: 'HT-ZL-2025-001', + projectName: '嘉兴氢能示范项目', + vehicleCount: 2, + vehicles: [ + { vehicleType: '4.5吨冷链车-轻型厢式货车', brand: '品牌A', model: '型号A1', plateNo: '浙A12345', actualDelivery: '2025-01-10 09:00' }, + { vehicleType: '18吨厢式货车-重型厢式货车', brand: '品牌B', model: '型号B1', plateNo: '-', actualDelivery: '2025-01-12 14:30' } + ], + approvalStatus: '未提交', + contractStatus: '草稿', + customerName: '嘉兴某某物流有限公司', + signingCompany: '嘉兴羚牛', + businessDept: '业务1部', + businessOwner: '张经理', + contractType: '正式合同', + contractEndDate: '2026-02-16', + contactName: '张三', + contactPhone: '13800138001', + creator: '张经理', + createTime: '2025-01-05 10:00', + updater: '-', + updateTime: '-', + remark: '草稿待完善' + }, + // 2. 未提交 + 草稿(试用合同) + { + id: '2', + contractCode: 'HT-ZL-2025-002', + projectName: '上海物流租赁项目', + vehicleCount: 1, + vehicles: [ + { vehicleType: '公务用车/小客车-小型普通客车', brand: '品牌C', model: '型号C1', plateNo: '沪D66666', actualDelivery: '2025-02-01 11:00' } + ], + approvalStatus: '未提交', + contractStatus: '草稿', + customerName: '上海某某运输公司', + signingCompany: '上海羚牛', + businessDept: '业务2部', + businessOwner: '李专员', + contractType: '试用合同', + contractEndDate: '2025-08-01', + contactName: '李四', + contactPhone: '13800138002', + creator: '李专员', + createTime: '2025-02-10 09:00', + updater: '-', + updateTime: '-', + remark: '试用期 3 个月' + }, + // 3. 待审批 + 已提交审批(初次提交,尚未有任何节点审批) + { + id: '3', + contractCode: 'HT-ZL-2025-003', + projectName: '杭州城配租赁项目', + vehicleCount: 1, + vehicles: [ + { vehicleType: '4.5吨货车-轻型厢式货车', brand: '品牌A', model: '型号A2', plateNo: '浙B20002', actualDelivery: '2025-02-15 08:30' } + ], + approvalStatus: '待审批', + contractStatus: '已提交审批', + customerName: '杭州某某租赁有限公司', + signingCompany: '嘉兴羚牛', + businessDept: '业务3部', + businessOwner: '王专员', + contractType: '正式合同', + contractEndDate: '2026-06-30', + contactName: '王五', + contactPhone: '13800138003', + creator: '王专员', + createTime: '2025-02-12 11:00', + updater: '-', + updateTime: '-', + remark: '-' + }, + // 4. 审批中 + 已提交审批(已有节点审批,未走完) + { + id: '4', + contractCode: 'HT-ZL-2025-004', + projectName: '宁波冷链运输项目', + vehicleCount: 2, + vehicles: [ + { vehicleType: '18吨双飞翼货车-重型厢式货车', brand: '品牌B', model: '型号B2', plateNo: '-', actualDelivery: '2025-02-16 10:00' }, + { vehicleType: '49吨牵引车头-重型半挂牵引车', brand: '品牌D', model: '型号D1', plateNo: '浙C30003', actualDelivery: '2025-02-18 14:00' } + ], + approvalStatus: '审批中', + contractStatus: '已提交审批', + customerName: '嘉兴某某物流有限公司', + signingCompany: '嘉兴羚牛', + businessDept: '业务1部', + businessOwner: '张经理', + contractType: '正式合同', + contractEndDate: '2026-03-01', + contactName: '赵六', + contactPhone: '13900139001', + creator: '张经理', + createTime: '2025-02-14 09:00', + updater: '李专员', + updateTime: '2025-02-15 16:00', + remark: '-' + }, + // 5. 审批中 + 变更(已通过审批后做了变更并重新提交) + { + id: '5', + contractCode: 'HT-ZL-2025-005', + projectName: '苏州城配试点项目', + vehicleCount: 1, + vehicles: [ + { vehicleType: '重型平板半挂车-重型平板半挂车', brand: '品牌D', model: '型号D2', plateNo: '苏E50005', actualDelivery: '2025-02-20 09:00' } + ], + approvalStatus: '审批中', + contractStatus: '变更', + customerName: '上海某某运输公司', + signingCompany: '上海羚牛', + businessDept: '业务2部', + businessOwner: '李专员', + contractType: '正式合同', + contractEndDate: '2026-05-31', + contactName: '孙七', + contactPhone: '13900139002', + creator: '李专员', + createTime: '2025-02-18 10:00', + updater: '李专员', + updateTime: '2025-02-22 14:00', + remark: '变更车辆数量' + }, + // 6. 审批通过 + 合同进行中(正式合同) + { + id: '6', + contractCode: 'HT-ZL-2025-006', + projectName: '南京氢能示范项目', + vehicleCount: 3, + vehicles: [ + { vehicleType: '4.5吨冷链车-轻型厢式货车', brand: '品牌A', model: '型号A1', plateNo: '苏A60006', actualDelivery: '2025-01-20 08:00' }, + { vehicleType: '18吨厢式货车-重型厢式货车', brand: '品牌B', model: '型号B1', plateNo: '苏A60007', actualDelivery: '2025-01-21 10:00' }, + { vehicleType: '35吨牵引车头-重型半挂牵引车', brand: '品牌D', model: '型号D1', plateNo: '苏A60008', actualDelivery: '2025-01-22 14:00' } + ], + approvalStatus: '审批通过', + contractStatus: '合同进行中', + customerName: '杭州某某租赁有限公司', + signingCompany: '广东羚牛', + businessDept: '业务3部', + businessOwner: '王专员', + contractType: '正式合同', + contractEndDate: '2026-12-31', + contactName: '周八', + contactPhone: '13900139003', + creator: '王专员', + createTime: '2025-01-15 09:00', + updater: '王专员', + updateTime: '2025-01-25 11:00', + remark: '-' + }, + // 7. 审批通过 + 合同进行中(试用合同,可转正式) + { + id: '7', + contractCode: 'HT-ZL-2025-007', + projectName: '无锡试用租赁项目', + vehicleCount: 1, + vehicles: [ + { vehicleType: '公务用车/小客车-小型普通客车', brand: '品牌C', model: '型号C2', plateNo: '苏B70007', actualDelivery: '2025-02-01 09:30' } + ], + approvalStatus: '审批通过', + contractStatus: '合同进行中', + customerName: '嘉兴某某物流有限公司', + signingCompany: '嘉兴羚牛', + businessDept: '业务1部', + businessOwner: '张经理', + contractType: '试用合同', + contractEndDate: '2025-05-31', + contactName: '吴九', + contactPhone: '13900139004', + creator: '张经理', + createTime: '2025-01-28 10:00', + updater: '-', + updateTime: '-', + remark: '试用 3 个月' + }, + // 8. 审批驳回 + 已提交审批(可编辑和重新提交) + { + id: '8', + contractCode: 'HT-ZL-2025-008', + projectName: '常州物流合作项目', + vehicleCount: 2, + vehicles: [ + { vehicleType: '重型集装箱半挂车-重型集装箱半挂车', brand: '品牌D', model: '型号D1', plateNo: '-', actualDelivery: '-' }, + { vehicleType: '4.5吨货车-轻型厢式货车', brand: '品牌A', model: '型号A2', plateNo: '-', actualDelivery: '-' } + ], + approvalStatus: '审批驳回', + contractStatus: '已提交审批', + customerName: '上海某某运输公司', + signingCompany: '上海羚牛', + businessDept: '业务2部', + businessOwner: '李专员', + contractType: '正式合同', + contractEndDate: '2026-08-15', + contactName: '郑十', + contactPhone: '13900139005', + creator: '李专员', + createTime: '2025-02-20 14:00', + updater: '李专员', + updateTime: '2025-02-23 09:00', + remark: '驳回原因:费用条款需调整' + }, + // 9. 已结束 + 到期合同(审批已通过但合同结束日期已过) + { + id: '9', + contractCode: 'HT-ZL-2024-009', + projectName: '南通去年到期项目', + vehicleCount: 2, + vehicles: [ + { vehicleType: '18吨双飞翼货车-重型厢式货车', brand: '品牌B', model: '型号B1', plateNo: '苏F90009', actualDelivery: '2024-03-01 09:00' }, + { vehicleType: '4.5吨冷链车-轻型厢式货车', brand: '品牌A', model: '型号A1', plateNo: '苏F90010', actualDelivery: '2024-03-02 10:00' } + ], + approvalStatus: '已结束', + contractStatus: '到期合同', + customerName: '杭州某某租赁有限公司', + signingCompany: '嘉兴羚牛', + businessDept: '业务3部', + businessOwner: '王专员', + contractType: '正式合同', + contractEndDate: '2024-12-31', + contactName: '王五', + contactPhone: '13800138003', + creator: '王专员', + createTime: '2024-02-20 10:00', + updater: '王专员', + updateTime: '2024-12-20 16:00', + remark: '已到期可续签' + }, + // 10. 已结束 + 到期合同(合同日期已过) + { + id: '10', + contractCode: 'HT-ZL-2024-010', + projectName: '镇江到期合同项目', + vehicleCount: 1, + vehicles: [ + { vehicleType: '公务用车/小客车-小型普通客车', brand: '品牌C', model: '型号C1', plateNo: '苏L00100', actualDelivery: '2024-06-01 11:00' } + ], + approvalStatus: '已结束', + contractStatus: '到期合同', + customerName: '嘉兴某某物流有限公司', + signingCompany: '嘉兴羚牛', + businessDept: '业务1部', + businessOwner: '张经理', + contractType: '正式合同', + contractEndDate: '2025-01-15', + contactName: '张三', + contactPhone: '13800138001', + creator: '张经理', + createTime: '2024-05-20 09:00', + updater: '张经理', + updateTime: '2025-01-10 14:00', + remark: '-' + } + ]; + + var appliedFilter = _appliedFilter[0]; + var filteredList = useMemo(function() { + var list = rawList.slice(); + var f = appliedFilter; + if (f.contractCode) { + list = list.filter(function(r) { + return (r.contractCode || '').indexOf(f.contractCode) !== -1; + }); + } + if (f.projectName) { + list = list.filter(function(r) { return r.projectName === f.projectName; }); + } + if (f.customerName) { + list = list.filter(function(r) { return r.customerName === f.customerName; }); + } + if (f.signingCompany) { + list = list.filter(function(r) { return r.signingCompany === f.signingCompany; }); + } + var approval = f.approvalStatus; + if (approval && approval.length > 0 && approval.indexOf('全部') === -1) { + list = list.filter(function(r) { return approval.indexOf(r.approvalStatus) !== -1; }); + } + var status = f.contractStatus; + if (status && status.length > 0 && status.indexOf('全部') === -1) { + list = list.filter(function(r) { return status.indexOf(r.contractStatus) !== -1; }); + } + if (f.businessDept && f.businessDept.length > 0) { + list = list.filter(function(r) { return f.businessDept.indexOf(r.businessDept) !== -1; }); + } + if (f.businessOwner && f.businessOwner.length > 0) { + list = list.filter(function(r) { return f.businessOwner.indexOf(r.businessOwner) !== -1; }); + } + var type = f.contractType; + if (type && type.length > 0 && type.indexOf('全部') === -1) { + list = list.filter(function(r) { return type.indexOf(r.contractType) !== -1; }); + } + if (f.creator && f.creator.length > 0) { + list = list.filter(function(r) { return f.creator.indexOf(r.creator) !== -1; }); + } + if (f.endDateRange && f.endDateRange.length === 2) { + var start = ''; + var end = ''; + if (f.endDateRange[0] && f.endDateRange[0].format) { + start = f.endDateRange[0].format('YYYY-MM-DD'); + } + if (f.endDateRange[1] && f.endDateRange[1].format) { + end = f.endDateRange[1].format('YYYY-MM-DD'); + } + if (start || end) { + list = list.filter(function(r) { + var d = r.contractEndDate || ''; + if (start && d < start) return false; + if (end && d > end) return false; + return true; + }); + } + } + return list; + }, [rawList, appliedFilter]); + + var handleQuery = useCallback(function() { + _appliedFilter[1]({ + contractCode: _contractCode[0], + projectName: _projectName[0], + customerName: _customerName[0], + signingCompany: _signingCompany[0], + approvalStatus: _approvalStatus[0] ? _approvalStatus[0].slice() : ['全部'], + contractStatus: _contractStatus[0] ? _contractStatus[0].slice() : ['全部'], + businessDept: _businessDept[0] ? _businessDept[0].slice() : [], + businessOwner: _businessOwner[0] ? _businessOwner[0].slice() : [], + contractType: _contractType[0] ? _contractType[0].slice() : ['全部'], + creator: _creator[0] ? _creator[0].slice() : [], + endDateRange: _endDateRange[0] + }); + }, []); + + var handleReset = useCallback(function() { + _contractCode[1](undefined); + _projectName[1](undefined); + _customerName[1](undefined); + _signingCompany[1](undefined); + _approvalStatus[1](['全部']); + _contractStatus[1](['全部']); + _businessDept[1]([]); + _businessOwner[1]([]); + _contractType[1](['全部']); + _creator[1]([]); + _endDateRange[1](null); + _appliedFilter[1]({ + contractCode: undefined, + projectName: undefined, + customerName: undefined, + signingCompany: undefined, + approvalStatus: ['全部'], + contractStatus: ['全部'], + businessDept: [], + businessOwner: [], + contractType: ['全部'], + creator: [], + endDateRange: null + }); + }, []); + + // 审批状态/合同状态/合同类型:「全部」与其它选项互斥,不能同时多选 + var handleApprovalStatusChange = useCallback(function(v) { + if (!v || v.length === 0) { _approvalStatus[1](['全部']); return; } + if (v.indexOf('全部') !== -1 && v.length > 1) { + var prev = _approvalStatus[0] || []; + var hadAllOnly = prev.length === 1 && prev.indexOf('全部') !== -1; + if (hadAllOnly) { + var next = []; + for (var i = 0; i < v.length; i++) { if (v[i] !== '全部') next.push(v[i]); } + _approvalStatus[1](next); + } else { + _approvalStatus[1](['全部']); + } + return; + } + _approvalStatus[1](v); + }, []); + var handleContractStatusChange = useCallback(function(v) { + if (!v || v.length === 0) { _contractStatus[1](['全部']); return; } + if (v.indexOf('全部') !== -1 && v.length > 1) { + var prev = _contractStatus[0] || []; + var hadAllOnly = prev.length === 1 && prev.indexOf('全部') !== -1; + if (hadAllOnly) { + var next = []; + for (var j = 0; j < v.length; j++) { if (v[j] !== '全部') next.push(v[j]); } + _contractStatus[1](next); + } else { + _contractStatus[1](['全部']); + } + return; + } + _contractStatus[1](v); + }, []); + var handleContractTypeChange = useCallback(function(v) { + if (!v || v.length === 0) { _contractType[1](['全部']); return; } + if (v.indexOf('全部') !== -1 && v.length > 1) { + var prev = _contractType[0] || []; + var hadAllOnly = prev.length === 1 && prev.indexOf('全部') !== -1; + if (hadAllOnly) { + var next = []; + for (var k = 0; k < v.length; k++) { if (v[k] !== '全部') next.push(v[k]); } + _contractType[1](next); + } else { + _contractType[1](['全部']); + } + return; + } + _contractType[1](v); + }, []); + + var addAuthorizedRow = useCallback(function() { + _authorizedList[1](function(prev) { return prev.concat([{ name: '', phone: '', idCard: '' }]); }); + }, []); + var removeAuthorizedRow = useCallback(function(index) { + _authorizedList[1](function(prev) { + var list = prev.slice(); + list.splice(index, 1); + if (list.length === 0) list = [{ name: '', phone: '', idCard: '' }]; + return list; + }); + }, []); + var updateAuthorizedRow = useCallback(function(index, field, value) { + _authorizedList[1](function(prev) { + var list = prev.slice(); + var row = list[index] || { name: '', phone: '', idCard: '' }; + var next = {}; + next[field] = value; + list[index] = Object.assign({}, row, next); + return list; + }); + }, []); + + // 附加费用:服务项目枚举(与需求文档一致) + var extraFeeServiceOptions = [ + { value: '代处理费用', label: '代处理费用' }, { value: '罚款', label: '罚款' }, { value: '违章处理违约金', label: '违章处理违约金' }, { value: '未参加安全培训', label: '未参加安全培训' }, { value: '车辆出险', label: '车辆出险' }, { value: '年检年审违约', label: '年检年审违约' }, { value: '停车费', label: '停车费' }, { value: '设备损坏金(包含易损件)', label: '设备损坏金(包含易损件)' }, { value: '清洗费', label: '清洗费' }, { value: '上门收车人工费', label: '上门收车人工费' }, { value: '上门收车送车行驶费', label: '上门收车送车行驶费' }, { value: '上门收车基础服务费', label: '上门收车基础服务费' }, { value: '保险上浮', label: '保险上浮' }, { value: '保养费用', label: '保养费用' }, { value: '补办驾驶证', label: '补办驾驶证' }, { value: '补办牌照', label: '补办牌照' }, { value: '补办营运证', label: '补办营运证' }, { value: '补办加氢证', label: '补办加氢证' }, { value: '借用备用钥匙', label: '借用备用钥匙' }, { value: '补配钥匙', label: '补配钥匙' }, { value: '租金', label: '租金' }, { value: '氢气费-客', label: '氢气费-客' }, { value: '退还车氢量差', label: '退还车氢量差' }, { value: '能源费补缴', label: '能源费补缴' }, { value: '能源费退款', label: '能源费退款' }, { value: '送车上门人工费', label: '送车上门人工费' }, { value: '送车上门送车行驶费', label: '送车上门送车行驶费' }, { value: '送车上门基础服务费', label: '送车上门基础服务费' }, { value: '保证金', label: '保证金' }, { value: '氢气预付费', label: '氢气预付费' }, { value: '维修费用', label: '维修费用' }, { value: 'ETC-客', label: 'ETC-客' }, { value: 'ETC卡缺损费', label: 'ETC卡缺损费' }, { value: 'ETC设备缺损费', label: 'ETC设备缺损费' }, { value: '电费-客', label: '电费-客' }, { value: '未结算保养费', label: '未结算保养费' }, { value: '未结算维修费', label: '未结算维修费' }, { value: '车损费', label: '车损费' }, { value: '工具损坏或丢失费', label: '工具损坏或丢失费' }, { value: '证件费', label: '证件费' }, { value: '广告损坏费', label: '广告损坏费' }, { value: '送车服务费', label: '送车服务费' }, { value: '接车服务费', label: '接车服务费' }, { value: '补办行驶证', label: '补办行驶证' }, { value: '超赔险', label: '超赔险' }, { value: '轮胎磨损费', label: '轮胎磨损费' }, { value: '无忧包', label: '无忧包' }, { value: '轮胎保', label: '轮胎保' }, { value: '养护保', label: '养护保' }, { value: '尾板', label: '尾板' } + ]; + var updateExtraFeeRow = useCallback(function(index, field, value) { + _extraFeeList[1](function(prev) { + var list = prev.slice(); + var row = list[index] || {}; + var next = {}; + next[field] = value; + list[index] = Object.assign({}, row, next); + return list; + }); + }, []); + + // 附加费用:服务项目枚举(与需求文档一致) + var extraFeeServiceOptions = [ + { value: '代处理费用', label: '代处理费用' }, { value: '罚款', label: '罚款' }, { value: '违章处理违约金', label: '违章处理违约金' }, { value: '未参加安全培训', label: '未参加安全培训' }, { value: '车辆出险', label: '车辆出险' }, { value: '年检年审违约', label: '年检年审违约' }, { value: '停车费', label: '停车费' }, { value: '设备损坏金(包含易损件)', label: '设备损坏金(包含易损件)' }, { value: '清洗费', label: '清洗费' }, { value: '上门收车人工费', label: '上门收车人工费' }, { value: '上门收车送车行驶费', label: '上门收车送车行驶费' }, { value: '上门收车基础服务费', label: '上门收车基础服务费' }, { value: '保险上浮', label: '保险上浮' }, { value: '保养费用', label: '保养费用' }, { value: '补办驾驶证', label: '补办驾驶证' }, { value: '补办牌照', label: '补办牌照' }, { value: '补办营运证', label: '补办营运证' }, { value: '补办加氢证', label: '补办加氢证' }, { value: '借用备用钥匙', label: '借用备用钥匙' }, { value: '补配钥匙', label: '补配钥匙' }, { value: '租金', label: '租金' }, { value: '氢气费-客', label: '氢气费-客' }, { value: '退还车氢量差', label: '退还车氢量差' }, { value: '能源费补缴', label: '能源费补缴' }, { value: '能源费退款', label: '能源费退款' }, { value: '送车上门人工费', label: '送车上门人工费' }, { value: '送车上门送车行驶费', label: '送车上门送车行驶费' }, { value: '送车上门基础服务费', label: '送车上门基础服务费' }, { value: '保证金', label: '保证金' }, { value: '氢气预付费', label: '氢气预付费' }, { value: '维修费用', label: '维修费用' }, { value: 'ETC-客', label: 'ETC-客' }, { value: 'ETC卡缺损费', label: 'ETC卡缺损费' }, { value: 'ETC设备缺损费', label: 'ETC设备缺损费' }, { value: '电费-客', label: '电费-客' }, { value: '未结算保养费', label: '未结算保养费' }, { value: '未结算维修费', label: '未结算维修费' }, { value: '车损费', label: '车损费' }, { value: '工具损坏或丢失费', label: '工具损坏或丢失费' }, { value: '证件费', label: '证件费' }, { value: '广告损坏费', label: '广告损坏费' }, { value: '送车服务费', label: '送车服务费' }, { value: '接车服务费', label: '接车服务费' }, { value: '补办行驶证', label: '补办行驶证' }, { value: '超赔险', label: '超赔险' }, { value: '轮胎磨损费', label: '轮胎磨损费' }, { value: '无忧包', label: '无忧包' }, { value: '轮胎保', label: '轮胎保' }, { value: '养护保', label: '养护保' }, { value: '尾板', label: '尾板' } + ]; + var updateExtraFeeRow = useCallback(function(index, field, value) { + _extraFeeList[1](function(prev) { + var list = prev.slice(); + var row = list[index] || {}; + var next = {}; + next[field] = value; + list[index] = Object.assign({}, row, next); + return list; + }); + }, []); + + var filterLabelStyle = { marginBottom: 6, fontSize: 14, color: 'rgba(0,0,0,0.65)' }; + var filterItemStyle = { marginBottom: 12 }; + var filterControlStyle = { width: '100%' }; + var layoutStyle = { padding: '16px 24px', background: '#f5f5f5', minHeight: '100vh' }; + var tableSingleLineStyle = '.contract-list-table .ant-table-thead th,.contract-list-table .ant-table-tbody td{white-space:nowrap;}'; + + var vehicleColumns = [ + { title: '车辆类型', dataIndex: 'vehicleType', key: 'vehicleType', width: 180 }, + { title: '品牌', dataIndex: 'brand', key: 'brand', width: 80 }, + { title: '型号', dataIndex: 'model', key: 'model', width: 100 }, + { title: '车牌号', dataIndex: 'plateNo', key: 'plateNo', width: 100 }, + { title: '实际交车日期', dataIndex: 'actualDelivery', key: 'actualDelivery', width: 140 } + ]; + + function getMoreMenuItems(record) { + var status = record.contractStatus; + var type = record.contractType; + var approval = record.approvalStatus; + var items = []; + if (status === '草稿') { + items.push({ key: 'edit', label: '编辑合同', onClick: function() { message.info('编辑合同(原型)'); } }); + items.push({ key: 'del', label: '删除合同', danger: true, onClick: function() { _deleteModalRecord[1](record); _deleteModalVisible[1](true); } }); + } + if (status === '合同进行中') { + items.push({ key: 'addVehicle', label: '新增车辆', onClick: function() { message.info('新增车辆(原型)'); } }); + items.push({ key: 'renew', label: '合同续签', onClick: function() { message.info('合同续签(原型)'); } }); + items.push({ key: 'authorized', label: '添加被授权人', onClick: function() { _authorizedModalRecord[1](record); _authorizedList[1]([{ name: '', phone: '', idCard: '' }]); _authorizedModalVisible[1](true); } }); + items.push({ key: 'extraFee', label: '附加费用', onClick: function() { + _extraFeeModalRecord[1](record); + var vehicles = record.vehicles || []; + _extraFeeList[1](vehicles.map(function(v) { + return { + vehicleType: v.vehicleType || '-', + brand: v.brand || '-', + model: v.model || '-', + plateNo: v.plateNo || '-', + serviceItem: undefined, + fee: '', + effectiveDate: null + }; + })); + _extraFeeModalVisible[1](true); + } }); + } + if (status === '到期合同') { + items.push({ key: 'renew2', label: '合同续签', onClick: function() { message.info('合同续签(原型)'); } }); + } + if (status === '已提交审批' && approval !== '审批驳回') { + items.push({ key: 'withdraw', label: '撤回合同', onClick: function() { _withdrawModalRecord[1](record); _withdrawModalVisible[1](true); } }); + } + if (approval === '审批驳回') { + items.push({ key: 'editReject', label: '编辑合同', onClick: function() { message.info('编辑合同(原型)'); } }); + } + if (type === '试用合同') { + items.push({ key: 'toFormal', label: '转正式合同', onClick: function() { message.info('转正式合同(原型)'); } }); + } + return items; + } + + function getOperationButtons(record) { + var menuItems = getMoreMenuItems(record); + var moreDropdown = menuItems.length > 0 + ? React.createElement( + Dropdown, + { + menu: { items: menuItems }, + trigger: ['hover'] + }, + React.createElement(Button, { type: 'link', size: 'small' }, '更多') + ) + : null; + return React.createElement( + Space, + { size: 4, wrap: true }, + React.createElement(Button, { type: 'link', size: 'small', onClick: function() { message.info('查看合同(原型)'); } }, '查看合同'), + moreDropdown + ); + } + + var columns = [ + { title: '合同编码', dataIndex: 'contractCode', key: 'contractCode', width: 140, fixed: 'left' }, + { title: '项目名称', dataIndex: 'projectName', key: 'projectName', width: 140, fixed: 'left' }, + { + title: '车辆数', + key: 'vehicleCount', + width: 90, + render: function(_, record) { + var open = _vehiclePopoverRecord[0] && _vehiclePopoverRecord[0].id === record.id; + var content = React.createElement('div', { style: { padding: 8 } }, + React.createElement(Table, { + size: 'small', + rowKey: function(r, i) { return String(i); }, + columns: vehicleColumns, + dataSource: record.vehicles || [], + pagination: false, + scroll: { x: 600 } + }) + ); + return React.createElement( + Popover, + { + content: content, + title: '车辆明细', + open: open, + onOpenChange: function(visible) { + if (!visible) _vehiclePopoverRecord[1](null); + else _vehiclePopoverRecord[1](record); + }, + trigger: 'click' + }, + React.createElement('a', { style: { cursor: 'pointer', color: '#1890ff', fontWeight: 500 } }, record.vehicleCount) + ); + } + }, + { title: '审批状态', dataIndex: 'approvalStatus', key: 'approvalStatus', width: 100 }, + { title: '合同状态', dataIndex: 'contractStatus', key: 'contractStatus', width: 110 }, + { title: '客户名称', dataIndex: 'customerName', key: 'customerName', width: 140 }, + { title: '签约公司', dataIndex: 'signingCompany', key: 'signingCompany', width: 100 }, + { title: '业务部门', dataIndex: 'businessDept', key: 'businessDept', width: 100 }, + { title: '业务负责人', dataIndex: 'businessOwner', key: 'businessOwner', width: 100 }, + { title: '合同类型', dataIndex: 'contractType', key: 'contractType', width: 100 }, + { title: '合同结束日期', dataIndex: 'contractEndDate', key: 'contractEndDate', width: 120 }, + { title: '客户联系人', dataIndex: 'contactName', key: 'contactName', width: 100 }, + { title: '联系电话', dataIndex: 'contactPhone', key: 'contactPhone', width: 120 }, + { title: '创建人', dataIndex: 'creator', key: 'creator', width: 90 }, + { title: '创建时间', dataIndex: 'createTime', key: 'createTime', width: 150 }, + { title: '更新人', dataIndex: 'updater', key: 'updater', width: 90 }, + { title: '最后更新时间', dataIndex: 'updateTime', key: 'updateTime', width: 150 }, + { title: '备注', dataIndex: 'remark', key: 'remark', width: 100, ellipsis: true }, + { title: '操作', key: 'action', width: 140, fixed: 'right', render: function(_, record) { return getOperationButtons(record); } } + ]; + + var filterItems = [ + React.createElement('div', { key: 'contractCode', style: filterItemStyle }, + React.createElement('div', { style: filterLabelStyle }, '合同编码'), + React.createElement(Select, { + placeholder: '请选择或输入合同编码', + style: filterControlStyle, + value: _contractCode[0], + onChange: function(v) { _contractCode[1](v); }, + allowClear: true, + showSearch: true, + options: contractCodeOptions, + filterOption: function(input, opt) { + return (opt.label || '').toString().toLowerCase().indexOf((input || '').toLowerCase()) !== -1; + } + })), + React.createElement('div', { key: 'projectName', style: filterItemStyle }, + React.createElement('div', { style: filterLabelStyle }, '项目名称'), + React.createElement(Select, { + placeholder: '请选择或输入项目名称', + style: filterControlStyle, + value: _projectName[0], + onChange: function(v) { _projectName[1](v); }, + allowClear: true, + showSearch: true, + options: projectNameOptions, + filterOption: function(input, opt) { + return (opt.label || '').toString().toLowerCase().indexOf((input || '').toLowerCase()) !== -1; + } + })), + React.createElement('div', { key: 'customerName', style: filterItemStyle }, + React.createElement('div', { style: filterLabelStyle }, '客户名称'), + React.createElement(Select, { + placeholder: '请选择或输入客户名称', + style: filterControlStyle, + value: _customerName[0], + onChange: function(v) { _customerName[1](v); }, + allowClear: true, + showSearch: true, + options: customerNameOptions, + filterOption: function(input, opt) { + return (opt.label || '').toString().toLowerCase().indexOf((input || '').toLowerCase()) !== -1; + } + })), + React.createElement('div', { key: 'signingCompany', style: filterItemStyle }, + React.createElement('div', { style: filterLabelStyle }, '签约公司'), + React.createElement(Select, { + placeholder: '请选择或输入签约公司名称', + style: filterControlStyle, + value: _signingCompany[0], + onChange: function(v) { _signingCompany[1](v); }, + allowClear: true, + options: signingCompanyOptions + })), + React.createElement('div', { key: 'approvalStatus', style: filterItemStyle }, + React.createElement('div', { style: filterLabelStyle }, '审批状态'), + React.createElement(Select, { + mode: 'multiple', + placeholder: '请选择', + style: filterControlStyle, + value: _approvalStatus[0], + onChange: handleApprovalStatusChange, + options: approvalStatusOptions + })), + React.createElement('div', { key: 'contractStatus', style: filterItemStyle }, + React.createElement('div', { style: filterLabelStyle }, '合同状态'), + React.createElement(Select, { + mode: 'multiple', + placeholder: '请选择', + style: filterControlStyle, + value: _contractStatus[0], + onChange: handleContractStatusChange, + options: contractStatusOptions + })), + React.createElement('div', { key: 'businessDept', style: filterItemStyle }, + React.createElement('div', { style: filterLabelStyle }, '业务部门'), + React.createElement(Select, { + mode: 'multiple', + placeholder: '请选择或输入业务部门名称', + style: filterControlStyle, + value: _businessDept[0], + onChange: function(v) { _businessDept[1](v); }, + options: deptOptions + })), + React.createElement('div', { key: 'businessOwner', style: filterItemStyle }, + React.createElement('div', { style: filterLabelStyle }, '业务负责人'), + React.createElement(Select, { + mode: 'multiple', + placeholder: '请选择或输入业务负责人姓名', + style: filterControlStyle, + value: _businessOwner[0], + onChange: function(v) { _businessOwner[1](v); }, + options: userOptions + })), + React.createElement('div', { key: 'contractType', style: filterItemStyle }, + React.createElement('div', { style: filterLabelStyle }, '合同类型'), + React.createElement(Select, { + mode: 'multiple', + placeholder: '请选择', + style: filterControlStyle, + value: _contractType[0], + onChange: handleContractTypeChange, + options: contractTypeOptions + })), + React.createElement('div', { key: 'creator', style: filterItemStyle }, + React.createElement('div', { style: filterLabelStyle }, '创建人'), + React.createElement(Select, { + mode: 'multiple', + placeholder: '请选择或输入创建人姓名', + style: filterControlStyle, + value: _creator[0], + onChange: function(v) { _creator[1](v); }, + options: userOptions + })), + React.createElement('div', { key: 'endDate', style: filterItemStyle }, + React.createElement('div', { style: filterLabelStyle }, '合同结束日期'), + React.createElement(RangePicker, { + style: filterControlStyle, + placeholder: ['开始日期', '结束日期'], + value: _endDateRange[0], + onChange: function(v) { _endDateRange[1](v); } + })) + ]; + + var filterCount = _filterExpanded[0] ? 11 : 3; + var filterNodes = []; + for (var i = 0; i < filterCount && i < filterItems.length; i++) { + filterNodes.push(filterItems[i]); + } + + var reqTitleStyle = { fontSize: 18, fontWeight: 600, marginBottom: 16, color: 'rgba(0,0,0,0.85)' }; + var reqSectionStyle = { fontSize: 15, fontWeight: 600, marginTop: 16, marginBottom: 8, color: 'rgba(0,0,0,0.85)' }; + var reqSubStyle = { fontSize: 14, fontWeight: 500, marginLeft: 16, marginTop: 8, marginBottom: 4, color: 'rgba(0,0,0,0.85)' }; + var reqItemStyle = { fontSize: 13, marginLeft: 32, marginTop: 4, marginBottom: 2, lineHeight: 1.6, color: 'rgba(0,0,0,0.75)' }; + var reqSubItemStyle = { fontSize: 13, marginLeft: 48, marginTop: 2, marginBottom: 2, lineHeight: 1.6, color: 'rgba(0,0,0,0.7)' }; + + return React.createElement(App, null, + 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, { style: { marginBottom: 16 } }, + React.createElement('div', { + style: { + display: 'grid', + gridTemplateColumns: '1fr 1fr 1fr', + gap: '16px 24px', + alignItems: 'start', + flex: 1, + minWidth: 0 + } + }, filterNodes), + React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 16 } }, + React.createElement(Button, { onClick: handleReset }, '重置'), + React.createElement(Button, { type: 'primary', onClick: handleQuery }, '查询'), + React.createElement(Button, { type: 'link', size: 'small', onClick: function() { _filterExpanded[1](!_filterExpanded[0]); } }, _filterExpanded[0] ? '收起' : '展开') + ) + ), + React.createElement('div', { style: { marginBottom: 16, display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 8 } }, + React.createElement(Button, { onClick: function() { message.info('租赁费用模板(原型)'); } }, '租赁费用模板'), + React.createElement('div', { style: { display: 'flex', gap: 8 } }, + React.createElement(Button, { type: 'primary', onClick: function() { message.info('请在原型菜单列表中点击:业务管理-车辆租赁合同-新建合同 查看具体原型'); } }, '新建'), + React.createElement(Button, { onClick: function() { message.info('根据筛选条件,导出相应记录'); } }, '导出') + ) + ), + React.createElement(Card, null, + React.createElement(React.Fragment, null, + React.createElement('style', null, tableSingleLineStyle), + React.createElement('div', { className: 'contract-list-table' }, + React.createElement(Table, { + rowKey: 'id', + columns: columns, + dataSource: filteredList, + scroll: { x: 2400 }, + size: 'small', + pagination: { showSizeChanger: true, showQuickJumper: true, showTotal: function(t) { return '共 ' + t + ' 条'; } } + }) + ) + ) + ), + React.createElement(Modal, { + title: '是否确认删除该合同草稿', + open: _deleteModalVisible[0], + onCancel: function() { _deleteModalVisible[1](false); _deleteModalRecord[1](null); }, + onOk: function() { + message.success('已删除(原型)'); + _deleteModalVisible[1](false); + _deleteModalRecord[1](null); + }, + okText: '确定', + cancelText: '取消' + }), + React.createElement(Modal, { + title: '是否确认撤回该合同', + open: _withdrawModalVisible[0], + onCancel: function() { _withdrawModalVisible[1](false); _withdrawModalRecord[1](null); }, + onOk: function() { + message.success('已撤回(原型)'); + _withdrawModalVisible[1](false); + _withdrawModalRecord[1](null); + }, + okText: '确定', + cancelText: '取消' + }), + React.createElement(Modal, { + title: '添加被授权人', + open: _authorizedModalVisible[0], + onCancel: function() { _authorizedModalVisible[1](false); _authorizedModalRecord[1](null); }, + width: 640, + footer: [ + React.createElement(Button, { key: 'cancel', onClick: function() { _authorizedModalVisible[1](false); _authorizedModalRecord[1](null); } }, '取消'), + React.createElement(Button, { key: 'ok', type: 'primary', onClick: function() { message.success('已提交审核(原型)'); _authorizedModalVisible[1](false); _authorizedModalRecord[1](null); } }, '提交审核') + ] + }, React.createElement('div', { style: { padding: '8px 0' } }, + React.createElement(Table, { + rowKey: function(_, i) { return String(i); }, + size: 'small', + columns: [ + { title: '被授权人', key: 'name', width: 140, render: function(_, row, index) { return React.createElement(Input, { value: row.name, onChange: function(e) { updateAuthorizedRow(index, 'name', e.target.value); }, placeholder: '请输入' }); } }, + { title: '被授权人联系电话', key: 'phone', width: 160, render: function(_, row, index) { return React.createElement(Input, { value: row.phone, onChange: function(e) { updateAuthorizedRow(index, 'phone', e.target.value); }, placeholder: '请输入' }); } }, + { title: '被授权人身份证', key: 'idCard', width: 200, render: function(_, row, index) { return React.createElement(Input, { value: row.idCard, onChange: function(e) { updateAuthorizedRow(index, 'idCard', e.target.value); }, placeholder: '请输入' }); } }, + { title: '操作', key: 'action', width: 80, render: function(_, row, index) { return React.createElement(Button, { type: 'link', size: 'small', danger: true, onClick: function() { removeAuthorizedRow(index); } }, '删除'); } } + ], + dataSource: _authorizedList[0], + pagination: false + }), + React.createElement(Button, { type: 'dashed', style: { marginTop: 12, width: '100%' }, onClick: addAuthorizedRow }, '添加一行') + )), + React.createElement(Modal, { + title: '附加费用', + open: _extraFeeModalVisible[0], + onCancel: function() { _extraFeeModalVisible[1](false); _extraFeeModalRecord[1](null); _extraFeeList[1]([]); }, + width: 900, + footer: [ + React.createElement(Button, { key: 'cancel', onClick: function() { _extraFeeModalVisible[1](false); _extraFeeModalRecord[1](null); _extraFeeList[1]([]); } }, '取消'), + React.createElement(Button, { key: 'ok', type: 'primary', onClick: function() { message.success('已提交审核(原型)'); _extraFeeModalVisible[1](false); _extraFeeModalRecord[1](null); _extraFeeList[1]([]); } }, '提交审核') + ] + }, React.createElement('div', { style: { padding: '8px 0' } }, + React.createElement(Table, { + rowKey: function(_, i) { return String(i); }, + size: 'small', + columns: [ + { title: '车辆类型', dataIndex: 'vehicleType', key: 'vehicleType', width: 160, render: function(v) { return v || '-'; } }, + { title: '品牌', dataIndex: 'brand', key: 'brand', width: 90, render: function(v) { return v || '-'; } }, + { title: '型号', dataIndex: 'model', key: 'model', width: 100, render: function(v) { return v || '-'; } }, + { title: '车牌号', dataIndex: 'plateNo', key: 'plateNo', width: 100, render: function(v) { return v || '-'; } }, + { title: '服务项目', key: 'serviceItem', width: 180, render: function(_, row, index) { return React.createElement(Select, { placeholder: '请选择', style: { width: '100%' }, value: row.serviceItem, onChange: function(v) { updateExtraFeeRow(index, 'serviceItem', v); }, options: extraFeeServiceOptions, allowClear: true }); } }, + { title: '费用(元)', key: 'fee', width: 140, render: function(_, row, index) { return React.createElement(Input, { value: row.fee, onChange: function(e) { updateExtraFeeRow(index, 'fee', e.target.value); }, placeholder: '请输入', addonAfter: '元' }); } }, + { title: '生效时间', key: 'effectiveDate', width: 140, render: function(_, row, index) { var dayjs = window.dayjs; var val = row.effectiveDate && dayjs ? dayjs(row.effectiveDate) : null; return React.createElement(DatePicker, { style: { width: '100%' }, placeholder: '请选择日期', value: val, onChange: function(date, dateString) { updateExtraFeeRow(index, 'effectiveDate', dateString || null); } }); } } + ], + dataSource: _extraFeeList[0], + pagination: false, + scroll: { x: 920 } + }) + )), + 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: reqTitleStyle }, '租赁合同管理'), + React.createElement('div', { style: reqSectionStyle }, '1.面包屑:'), + React.createElement('div', { style: reqSubStyle }, '1.1.业务管理-车辆租赁合同'), + React.createElement('div', { style: reqSectionStyle }, '2.筛选:'), + React.createElement('div', { style: reqSubStyle }, '2.1.支持通过合同编码、项目名称、客户名称、签约公司、审批状态、合同状态、业务部门、业务负责人、合同类型、创建人、合同结束日期等条件进行筛选,右侧为重置、查询、展开/收起(筛选条件以3列显示,默认显示一行,点击展开/收起对筛选栏卡片进行展开/收起所有筛选条件),点击查询后,筛选条件与列表内容联动。点击重置会回到默认筛选条件并在列表展示结果:'), + React.createElement('div', { style: reqItemStyle }, '2.1.1.合同编码:选择器,支持从输入框内输入内容进行模糊搜索,下拉显示匹配选项;'), + React.createElement('div', { style: reqItemStyle }, '2.1.2.项目名称:选择器,支持从输入框内输入内容进行模糊搜索,下拉显示匹配选项;'), + React.createElement('div', { style: reqItemStyle }, '2.1.3.客户名称:选择器,支持从输入框内输入内容进行模糊搜索,下拉显示匹配选项;'), + React.createElement('div', { style: reqItemStyle }, '2.1.4.签约公司:选择器,显示租赁合同所有签约公司;'), + React.createElement('div', { style: reqItemStyle }, '2.1.5.审批状态:选择器,支持全选或多选,选项为:全部、待审批、审批中、审批通过、审批驳回、未提交、已结束,默认显示全部;'), + React.createElement('div', { style: reqItemStyle }, '2.1.6.合同状态:选择器,支持全选或多选,选项为:全部、草稿、变更、合同进行中、到期合同、已提交审批;'), + React.createElement('div', { style: reqItemStyle }, '2.1.7.业务部门:选择器,支持全选或多选,拉取部门下所有业务相关部门;'), + React.createElement('div', { style: reqItemStyle }, '2.1.8.业务负责人:选择器,支持全选或多选,拉取所有业务相关部门下所有用户姓名;'), + React.createElement('div', { style: reqItemStyle }, '2.1.9.合同类型:选择器,支持全选或多选,选项为:全部、正式合同、试用合同;'), + React.createElement('div', { style: reqItemStyle }, '2.1.10.创建人:选择器,支持全选或多选,拉取所有业务相关部门下所有用户姓名;'), + React.createElement('div', { style: reqItemStyle }, '2.1.11.合同结束日期:日期选择器,支持单输入框内双日历选择开始-结束时间;'), + React.createElement('div', { style: reqSectionStyle }, '3.列表:'), + React.createElement('div', { style: reqSubStyle }, '3.1.列表展示所有租赁合同信息,字段依次为:合同编码、项目名称、车辆数、审批状态、合同状态、客户名称、签约公司、业务部门、业务负责人、合同类型、合同结束日期、客户联系人、联系电话、创建人、创建时间、更新人、最后更新时间、备注、操作;列表右上角为新建、导出;'), + React.createElement('div', { style: reqItemStyle }, '3.1.1.合同编码:显示租赁合同对应合同编码;'), + React.createElement('div', { style: reqItemStyle }, '3.1.2.项目名称:显示租赁合同对应项目名称;'), + React.createElement('div', { style: reqItemStyle }, '3.1.3.车辆数:显示车辆数,点击车辆数,显示气泡卡片,卡片中列表显示:车辆类型、品牌、型号、车牌号、实际交车日期;'), + React.createElement('div', { style: reqSubItemStyle }, '3.1.3.1.车辆类型:4.5吨冷链车-轻型厢式货车、18吨双飞翼货车-重型厢式货车、49吨牵引车头-重型半挂牵引车、4.5吨货车-轻型厢式货车、18吨厢式货车-重型厢式货车、重型集装箱半挂车-重型集装箱半挂车、公务用车/小客车-小型普通客车、35吨牵引车头-重型半挂牵引车、重型平板半挂车-重型平板半挂车;'), + React.createElement('div', { style: reqSubItemStyle }, '3.1.3.2.品牌:显示租赁合同中对应车辆品牌;'), + React.createElement('div', { style: reqSubItemStyle }, '3.1.3.3.型号:显示租赁合同中对应车辆型号;'), + React.createElement('div', { style: reqSubItemStyle }, '3.1.3.4.车牌号:显示租赁合同中对应车牌号,无则显示为-,有则显示具体车牌号;'), + React.createElement('div', { style: reqSubItemStyle }, '3.1.3.5.实际交车日期:显示租赁合同中对应车辆对应实际交车日期,精确至分钟,格式为YYYY-MM-DD HH:MM;'), + React.createElement('div', { style: reqItemStyle }, '3.1.4.审批状态:显示租赁合同实际审批状态,状态分为:待审批、审批中、审批通过、审批驳回、未提交、已结束;'), + React.createElement('div', { style: reqSubItemStyle }, '3.1.4.1.待审批:发起人已提交,但还没有任何流程节点完成审批;'), + React.createElement('div', { style: reqSubItemStyle }, '3.1.4.2.审批中:发起人已提交,已有1个以上节点完成审批,但未完成最终节点审批;'), + React.createElement('div', { style: reqSubItemStyle }, '3.1.4.3.审批通过:发起人已提交,最终节点完成审批;'), + React.createElement('div', { style: reqSubItemStyle }, '3.1.4.4.审批驳回:发起人已提交,任意流程节点驳回,该状态下操作列支持编辑和重新提交;'), + React.createElement('div', { style: reqSubItemStyle }, '3.1.4.5.未提交:发起人仅保存,但未提交审批;'), + React.createElement('div', { style: reqSubItemStyle }, '3.1.4.6.已结束:发起人已提交,最终节点完成审批,但当前日期超过合同结束日期;'), + React.createElement('div', { style: reqItemStyle }, '3.1.5.合同状态:显示租赁合同状态,状态分为:草稿、变更、合同进行中、到期合同、已提交审批;'), + React.createElement('div', { style: reqSubItemStyle }, '3.1.5.1.草稿:发起人仅保存,但未提交审批;'), + React.createElement('div', { style: reqSubItemStyle }, '3.1.5.2.变更:发起人提交后,合同已通过审批的基础上,进行了「变更内容」操作,且已提交审批,但未完成最终节点审批;'), + React.createElement('div', { style: reqSubItemStyle }, '3.1.5.3.合同进行中:发起人提交后,合同完成最终节点审批;'), + React.createElement('div', { style: reqSubItemStyle }, '3.1.5.4.到期合同:发起人已提交,最终节点完成审批,但当前日期超过合同结束日期;'), + React.createElement('div', { style: reqSubItemStyle }, '3.1.5.5.已提交审批:发起人初次提交,但未完成最终节点审批;'), + React.createElement('div', { style: reqItemStyle }, '3.1.6.客户名称:显示租赁合同创建时所选客户名称,客户名称来自「客户管理」-「客户名称」;'), + React.createElement('div', { style: reqItemStyle }, '3.1.7.签约公司:显示租赁合同创建时所选签约公司,签约公司来自组织机构表;'), + React.createElement('div', { style: reqItemStyle }, '3.1.8.业务部门:显示租赁合同创建时所选业务部门,业务部门来自部门表(业务组);'), + React.createElement('div', { style: reqItemStyle }, '3.1.9.业务负责人:显示租赁合同创建时所选业务负责人,与业务部门联动,查询该业务组下人员;'), + React.createElement('div', { style: reqItemStyle }, '3.1.10.合同类型:显示租赁合同类型,类型包括:正式合同、试用合同,于创建租赁合同时选取;'), + React.createElement('div', { style: reqItemStyle }, '3.1.11.合同结束日期:显示租赁合同结束日期,精确至日,格式为YYYY-MM-DD;'), + React.createElement('div', { style: reqItemStyle }, '3.1.12.客户联系人:显示租赁合同客户联系人姓名,客户联系人姓名来自「客户管理」-「联系人」;'), + React.createElement('div', { style: reqItemStyle }, '3.1.13.联系电话:显示租赁合同客户联系电话,客户联系电话来自「客户管理」-「联系人手机」;'), + React.createElement('div', { style: reqItemStyle }, '3.1.14.创建人:显示租赁合同创建人姓名,取自操作用户姓名;'), + React.createElement('div', { style: reqItemStyle }, '3.1.15.创建时间:显示租赁合同创建时间,精确至分钟,格式为YYYY-MM-DD HH:MM;'), + React.createElement('div', { style: reqItemStyle }, '3.1.16.更新人:显示租赁合同最后一次更新人姓名,取自操作用户姓名,如无则显示:-;'), + React.createElement('div', { style: reqItemStyle }, '3.1.17.最后更新时间:显示租赁合同最后一次更新时间,精确至分钟,格式为YYYY-MM-DD HH:MM,如无则显示:-;'), + React.createElement('div', { style: reqItemStyle }, '3.1.18.备注:显示租赁合同创建时输入的备注信息,如无则显示:-;'), + React.createElement('div', { style: reqItemStyle }, '3.1.19.操作:操作分为:查看合同、编辑合同、新增车辆、合同续签、删除合同、撤回合同、添加被授权人、附加费用、转正式合同;'), + React.createElement('div', { style: reqSubItemStyle }, '3.1.19.1.查看合同:查看租赁合同详情;'), + React.createElement('div', { style: reqSubItemStyle }, '3.1.19.2.编辑合同:当「合同状态」为「草稿」时显示,点击跳转编辑租赁合同页面,编辑租赁合同页面可输入项参考「新增租赁合同」页面,并支持对保存时已填内容进行修改;'), + React.createElement('div', { style: reqSubItemStyle }, '3.1.19.3.新增车辆:当「合同状态」为「合同进行中」时显示,仅能在租赁订单信息卡片下新订单中进行车辆新增;新增车辆提交后触发重新审核流程,审核通过后生效(不影响原有合同正常业务);'), + React.createElement('div', { style: reqSubItemStyle }, '3.1.19.4.合同续签:当「合同状态」为「合同进行中」、「合同到期」时显示,点击后跳转新增租赁合同页面,同时反写所有旧合同内信息,支持修改。此外,合同续签操作产生的合同,合同编码一栏中编码后方显示:(续签自:旧合同编码xxxx),同时查看新合同时下方变更记录中显示续签自哪个合同;'), + React.createElement('div', { style: reqSubItemStyle }, '3.1.19.5.删除合同:当「合同状态」为「草稿」时显示,点击删除合同时进行二次确认,提示语:是否确认删除该合同草稿;'), + React.createElement('div', { style: reqSubItemStyle }, '3.1.19.6.撤回合同:当「合同状态」为「已提交审核」时显示,点击撤回合同时进行二次确认,提示语:是否确认撤回该合同;'), + React.createElement('div', { style: reqSubItemStyle }, '3.1.19.7.添加被授权人:当「合同状态」为「合同进行中」时显示,点击弹出卡片,卡片中可编辑被授权人、被授权人联系电话、被授权人身份证,同时支持添加一行、删除已有行等操作;添加被授权人触发重新审核流程,审核通过后生效;'), + React.createElement('div', { style: reqSubItemStyle }, '3.1.19.8.附加费用:当「合同状态」为「合同进行中」时显示,点击弹出卡片,卡片中显示该租赁合同内:车辆类型、品牌、型号、车牌号,同时可对服务项目、费用、生效时间进行编辑;'), + React.createElement('div', { style: { fontSize: 13, marginLeft: 64, marginTop: 2, marginBottom: 2, lineHeight: 1.6, color: 'rgba(0,0,0,0.65)' } }, '3.1.19.8.1.服务项目:代处理费用、罚款、违章处理违约金、未参加安全培训、车辆出险、年检年审违约、停车费、设备损坏金(包含易损件)、清洗费、上门收车人工费、上门收车送车行驶费、上门收车基础服务费、保险上浮、保养费用、补办驾驶证、补办牌照、补办营运证、补办加氢证、借用备用钥匙、补配钥匙、租金、氢气费-客、退还车氢量差、能源费补缴、能源费退款、送车上门人工费、送车上门送车行驶费、送车上门基础服务费、保证金、氢气预付费、维修费用、ETC-客、ETC卡缺损费、ETC设备缺损费、电费-客、未结算保养费、未结算维修费、车损费、工具损坏或丢失费、证件费、广告损坏费、送车服务费、接车服务费、补办行驶证、超赔险、轮胎磨损费、无忧包、轮胎保、养护保、尾板;'), + React.createElement('div', { style: { fontSize: 13, marginLeft: 64, marginTop: 2, marginBottom: 2, lineHeight: 1.6, color: 'rgba(0,0,0,0.65)' } }, '3.1.19.8.2.费用:输入框,后缀为元,支持2位小数输入;'), + React.createElement('div', { style: { fontSize: 13, marginLeft: 64, marginTop: 2, marginBottom: 2, lineHeight: 1.6, color: 'rgba(0,0,0,0.65)' } }, '3.1.19.8.3.生效时间:日期选择器,精确至日;'), + React.createElement('div', { style: reqSubItemStyle }, '3.1.19.9.转正式合同:当「合同类型」为「试用合同」时显示,点击后跳转新增租赁合同页面,同时反写所有旧合同内信息,支持修改。此外,合同续签操作产生的合同,合同编码一栏中编码后方显示:(续签自:旧合同编码xxxx),同时查看新合同时下方变更记录中显示续签自哪个合同;') + )) + ) + ); +}; diff --git a/web端/车辆租赁合同/转正式合同.jsx b/web端/车辆租赁合同/转正式合同.jsx new file mode 100644 index 0000000..83a78af --- /dev/null +++ b/web端/车辆租赁合同/转正式合同.jsx @@ -0,0 +1,690 @@ +// 【重要】必须使用 const Component 作为组件变量名 - Axhub 产品原型 +// 车辆管理系统 - 转正式合同模块(布局与合同续签一致,合同编码后增加转正式合同自:合同编号xxxxxxx,IE11+ 兼容,采用 antd) + +const Component = function() { + var antd = window.antd; + var Input = antd.Input; + var Select = antd.Select; + var Button = antd.Button; + var DatePicker = antd.DatePicker; + var Modal = antd.Modal; + var message = antd.message; + var Option = Select.Option; + + var customerList = [ + { id: '1', name: '嘉兴某某物流有限公司', creditCode: '91330400MA2XXXXX1', address: '浙江省嘉兴市南湖区科技大道1号', contact: '张三', phone: '13800138001', email: 'zhangsan@example.com', companyName: '嘉兴某某物流有限公司', companyPhone: '0571-88888888', mailingAddress: '浙江省嘉兴市南湖区科技大道1号', bank: '中国工商银行嘉兴分行', bankAccount: '6222021234567890123', taxId: '91330400MA2XXXXX1' }, + { id: '2', name: '上海某某运输公司', creditCode: '91310000MA2XXXXX2', address: '上海市浦东新区张江高科技园区', contact: '李四', phone: '13800138002', email: 'lisi@example.com', companyName: '上海某某运输公司', companyPhone: '021-66666666', mailingAddress: '上海市浦东新区张江高科技园区', bank: '中国建设银行上海分行', bankAccount: '6217001234567890123', taxId: '91310000MA2XXXXX2' }, + { id: '3', name: '杭州某某租赁有限公司', creditCode: '91330100MA2XXXXX3', address: '浙江省杭州市余杭区未来科技城', contact: '王五', phone: '13800138003', email: 'wangwu@example.com', companyName: '杭州某某租赁有限公司', companyPhone: '0571-99999999', mailingAddress: '浙江省杭州市余杭区未来科技城', bank: '中国农业银行杭州分行', bankAccount: '6228481234567890123', taxId: '91330100MA2XXXXX3' } + ]; + var deptList = [{ id: 'YW1', name: '业务1部', owners: ['张经理', '李专员', '王专员'] }, { id: 'YW2', name: '业务2部', owners: ['赵经理', '钱专员'] }, { id: 'YW3', name: '业务3部', owners: ['孙经理', '周专员'] }]; + var orgList = ['嘉兴羚牛', '上海羚牛', '广东羚牛']; + var regionList = [ + { province: '浙江省', cities: ['杭州市', '宁波市', '嘉兴市', '湖州市'] }, + { province: '上海市', cities: ['上海市'] }, + { province: '广东省', cities: ['广州市', '深圳市', '东莞市'] } + ]; + var feeTemplates = ['标准费用模板A', '标准费用模板B', '定制费用模板C']; + var feeTemplateCertFees = [{ project: '补办行驶证', standard: '50元/次', serviceFee: '20' }, { project: '补办驾驶证', standard: '30元/次', serviceFee: '10' }, { project: '补办牌照', standard: '100元/次', serviceFee: '50' }]; + var feeTemplatePenaltyFees = [{ project: '提前退车违约金', standard: '月租金×1', serviceFee: '0' }, { project: '违章处理违约金', standard: '按实际发生', serviceFee: '50' }]; + var feeTemplateConsumables = [{ category: '轮胎', part: '前轮', partName: '轮胎A型', qty: 1, feeDetail: '500.00' }, { category: '易损件', part: '雨刮', partName: '雨刮片', qty: 2, feeDetail: '80.00' }]; + var feeTemplateOtherFees = [{ project: '上门送车费', standard: '100元/次', serviceFee: '50' }, { project: '上门收车费', standard: '100元/次', serviceFee: '50' }, { project: '清洗费', standard: '80元/次', serviceFee: '30' }]; + var brandList = ['品牌A', '品牌B', '品牌C', '品牌D']; + var modelByBrand = { '品牌A': ['型号A1', '型号A2', '型号A3'], '品牌B': ['型号B1', '型号B2'], '品牌C': ['型号C1', '型号C2', '型号C3'], '品牌D': ['型号D1'] }; + var vehicleList = [{ plateNo: '浙A10001', vin: 'L1234567890ABCDEF' }, { plateNo: '浙B20002', vin: 'L2234567890ABCDEF' }, { plateNo: '沪A30003', vin: 'L3234567890ABCDEF' }, { plateNo: '粤A40004', vin: 'L4234567890ABCDEF' }]; + var serviceItemOptions = ['代处理费用', '罚款', '违章处理违约金', '未参加安全培训', '车辆出险', '年检年审违约', '停车费', '设备损坏金(包含易损件)', '清洗费', '上门收车人工费', '上门收车送车行驶费', '上门收车基础服务费', '保险上浮', '保养费用', '补办驾驶证', '补办牌照', '补办营运证', '补办加氢证', '借用备用钥匙', '补配钥匙', '租金', '氢气费-客', '退还车氢量差', '能源费补缴', '能源费退款', '送车上门人工费', '送车上门送车行驶费', '送车上门基础服务费', '保证金', '氢气预付费', '维修费用', 'ETC-客', 'ETC卡缺损费', 'ETC设备缺损费', '电费-客', '未结算保养费', '未结算维修费', '车损费', '工具损坏或丢失费', '证件费', '广告损坏费', '送车服务费', '接车服务费', '补办行驶证', '超赔险', '轮胎磨损费', '无忧包', '轮胎保', '养护保', '尾板']; + + var prevContractSample = { + customerId: '1', + businessDept: 'YW1', + businessOwner: '张经理', + contractOriginal: null, + projectName: '嘉兴氢能运输项目', + contractType: '正式合同', + effectiveDate: '2027-02-17', + paymentMethod: '预付', + endDate: '2028-02-17', + paymentPeriod: '1', + signingCompany: '嘉兴羚牛', + deliveryProvince: '浙江省', + deliveryCity: '嘉兴市', + deliveryLocation: '嘉兴市南湖区科技大道1号', + remarks: '转正式合同自原合同 JXZL20260216YW101235A', + authorizedList: [{ name: '张三', phone: '13800138001', idCard: '330102199001011234' }], + rentalOrders: [ + { brand: '品牌A', model: '型号A1', plateNo: '浙A10001', vin: 'L1234567890ABCDEF', monthRent: '8000', serviceItems: [{ project: '保养费用', fee: '200', effectiveDate: '2026-03-01' }], deposit: '10000', remark: '' }, + { brand: '品牌A', model: '型号A2', plateNo: '浙B20002', vin: 'L2234567890ABCDEF', monthRent: '8000', serviceItems: [{ project: '清洗费', fee: '80', effectiveDate: '2026-03-01' }], deposit: '10000', remark: '' } + ], + hydrogenBearer: '客户', + hydrogenPaymentMethod: '预付', + hydrogenPrepay: '5000', + returnHydrogenPrice: '80', + feeTemplate: '标准费用模板A', + billingMethod: 'month' + }; + var prevCustomer = customerList.find(function(c) { return c.id === prevContractSample.customerId; }) || null; + + var cs1 = React.useState(prevCustomer ? prevCustomer.name : ''); + var customerSearch = cs1[0]; + var setCustomerSearch = cs1[1]; + var cs2 = React.useState(prevCustomer); + var selectedCustomer = cs2[0]; + var setSelectedCustomer = cs2[1]; + var cs3 = React.useState(false); + var customerDropdownOpen = cs3[0]; + var setCustomerDropdownOpen = cs3[1]; + var cs4 = React.useState(prevContractSample.businessDept); + var businessDept = cs4[0]; + var setBusinessDept = cs4[1]; + var cs5 = React.useState(prevContractSample.businessOwner); + var businessOwner = cs5[0]; + var setBusinessOwner = cs5[1]; + var cs6 = React.useState(''); + var ownerFocusError = cs6[0]; + var setOwnerFocusError = cs6[1]; + var contractOriginalRef = React.useRef(null); + var csContractOriginal = React.useState(null); + var contractOriginal = csContractOriginal[0]; + var setContractOriginal = csContractOriginal[1]; + + var bs1 = React.useState(prevContractSample.projectName); + var projectName = bs1[0]; + var setProjectName = bs1[1]; + var bs2 = React.useState(prevContractSample.contractType); + var contractType = bs2[0]; + var setContractType = bs2[1]; + var bs3 = React.useState(prevContractSample.effectiveDate); + var effectiveDate = bs3[0]; + var setEffectiveDate = bs3[1]; + var bs4 = React.useState(prevContractSample.paymentMethod); + var paymentMethod = bs4[0]; + var setPaymentMethod = bs4[1]; + var bs6 = React.useState(prevContractSample.endDate); + var endDate = bs6[0]; + var setEndDate = bs6[1]; + var bs7 = React.useState(prevContractSample.paymentPeriod); + var paymentPeriod = bs7[0]; + var setPaymentPeriod = bs7[1]; + var bs8 = React.useState(prevContractSample.signingCompany); + var signingCompany = bs8[0]; + var setSigningCompany = bs8[1]; + var bs9 = React.useState(prevContractSample.deliveryProvince); + var deliveryProvince = bs9[0]; + var setDeliveryProvince = bs9[1]; + var bs10 = React.useState(prevContractSample.deliveryCity); + var deliveryCity = bs10[0]; + var setDeliveryCity = bs10[1]; + var bs10b = React.useState(false); + var deliveryRegionOpen = bs10b[0]; + var setDeliveryRegionOpen = bs10b[1]; + var deliveryRegionClickInsideRef = React.useRef(false); + var bs11 = React.useState(prevContractSample.deliveryLocation); + var deliveryLocation = bs11[0]; + var setDeliveryLocation = bs11[1]; + var bs12 = React.useState(prevContractSample.remarks); + var remarks = bs12[0]; + var setRemarks = bs12[1]; + + var as1 = React.useState(prevContractSample.authorizedList.slice()); + var authorizedList = as1[0]; + var setAuthorizedList = as1[1]; + + var emptyRentalRow = { brand: '', model: '', plateNo: '', vin: '', monthRent: '', serviceItems: [{ project: '', fee: '', effectiveDate: '' }], deposit: '', remark: '' }; + var os1 = React.useState(prevContractSample.rentalOrders.map(function(r) { return { brand: r.brand, model: r.model, plateNo: r.plateNo, vin: r.vin, monthRent: r.monthRent, serviceItems: (r.serviceItems || []).map(function(s) { return { project: s.project, fee: s.fee, effectiveDate: s.effectiveDate }; }), deposit: r.deposit, remark: r.remark || '' }; })); + var rentalOrders = os1[0]; + var setRentalOrders = os1[1]; + var os1b = React.useState(null); + var serviceModalRowIndex = os1b[0]; + var setServiceModalRowIndex = os1b[1]; + var os1c = React.useState(prevContractSample.hydrogenBearer); + var hydrogenBearer = os1c[0]; + var setHydrogenBearer = os1c[1]; + var os1d = React.useState(prevContractSample.hydrogenPaymentMethod); + var hydrogenPaymentMethod = os1d[0]; + var setHydrogenPaymentMethod = os1d[1]; + var os1e = React.useState(prevContractSample.hydrogenPrepay); + var hydrogenPrepay = os1e[0]; + var setHydrogenPrepay = os1e[1]; + var os1f = React.useState(prevContractSample.returnHydrogenPrice); + var returnHydrogenPrice = os1f[0]; + var setReturnHydrogenPrice = os1f[1]; + var os1g = React.useState(null); + var plateNoFocusRow = os1g[0]; + var setPlateNoFocusRow = os1g[1]; + var os1h = React.useState(''); + var plateNoSearch = os1h[0]; + var setPlateNoSearch = os1h[1]; + var os1i = React.useState(null); + var serviceItemFocusRow = os1i[0]; + var setServiceItemFocusRow = os1i[1]; + var os1j = React.useState(''); + var serviceItemSearch = os1j[0]; + var setServiceItemSearch = os1j[1]; + var os1k = React.useState(null); + var plateNoDropdownRect = os1k[0]; + var setPlateNoDropdownRect = os1k[1]; + var os2 = React.useState(prevContractSample.feeTemplate); + var feeTemplate = os2[0]; + var setFeeTemplate = os2[1]; + var os3 = React.useState(prevContractSample.billingMethod); + var billingMethod = os3[0]; + var setBillingMethod = os3[1]; + + var cc1State = React.useState(false); + var cc1 = cc1State[0]; + var setCc1 = cc1State[1]; + var cc2State = React.useState(false); + var cc2 = cc2State[0]; + var setCc2 = cc2State[1]; + var cc3State = React.useState(false); + var cc3 = cc3State[0]; + var setCc3 = cc3State[1]; + var cc4State = React.useState(false); + var cc4 = cc4State[0]; + var setCc4 = cc4State[1]; + var cc5State = React.useState(false); + var cc5 = cc5State[0]; + var setCc5 = cc5State[1]; + var cc6State = React.useState(false); + var cc6 = cc6State[0]; + var setCc6 = cc6State[1]; + var reqSpecState = React.useState(false); + var reqSpecOpen = reqSpecState[0]; + var setReqSpecOpen = reqSpecState[1]; + var formErrorsState = React.useState({}); + var formErrors = formErrorsState[0]; + var setFormErrors = formErrorsState[1]; + + var filteredCustomers = customerList.filter(function(c) { return !customerSearch || c.name.indexOf(customerSearch) !== -1; }); + var currentDept = deptList.find(function(d) { return d.id === businessDept; }); + var ownerOptions = currentDept ? currentDept.owners : []; + var contractCode = (function() { + var cityCode = 'JX'; + var typeCode = 'ZL'; + var signCode = contractType === '正式合同' ? 'A' : 'B'; + var dateStr = effectiveDate ? effectiveDate.replace(/-/g, '') : '20260216'; + var deptCode = businessDept || 'YW1'; + return cityCode + typeCode + dateStr + deptCode + '01235' + signCode; + })(); + var originalContractCode = 'JXZL20260216YW101235A'; + var contractCodeDisplay = contractCode + '(转正式合同自:合同编号' + originalContractCode + ')'; + var mainVehicleModelsDisplay = (function() { + var models = []; + var seen = {}; + for (var i = 0; i < rentalOrders.length; i++) { + var m = rentalOrders[i].model; + if (m && !seen[m]) { seen[m] = true; models.push(m); } + } + return models.join('、'); + })(); + var calcRowServiceFee = function(row) { + var sum = 0; + for (var i = 0; i < (row.serviceItems || []).length; i++) { + var fee = parseFloat(row.serviceItems[i].fee); + if (!isNaN(fee)) sum += fee; + } + return sum.toFixed(2); + }; + var rentalTotalVehicles = rentalOrders.length; + var rentalTotalRentService = rentalOrders.reduce(function(s, r) { return s + (parseFloat(r.monthRent) || 0) + (parseFloat(calcRowServiceFee(r)) || 0); }, 0); + var rentalTotalDeposit = rentalOrders.reduce(function(s, r) { return s + (parseFloat(r.deposit) || 0); }, 0); + var rentalTotalHydrogen = (hydrogenPaymentMethod === '预付' && hydrogenBearer === '客户') ? (parseFloat(hydrogenPrepay) || 0) : 0; + + var selectCustomer = function(c) { + setSelectedCustomer(c); + setCustomerSearch(c ? c.name : ''); + setCustomerDropdownOpen(false); + }; + var deliveryRegionDisplay = deliveryProvince && deliveryCity ? deliveryProvince + ' / ' + deliveryCity : ''; + var selectDeliveryRegion = function(province, city) { + setDeliveryProvince(province); + setDeliveryCity(city); + setDeliveryRegionOpen(false); + }; + var scrollToCard = function(id) { + var el = document.getElementById(id); + if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' }); + }; + var handleOwnerFocus = function() { + if (!businessDept) setOwnerFocusError('请先选择业务部门'); + else setOwnerFocusError(''); + }; + var handleOwnerBlur = function() { setOwnerFocusError(''); }; + var openContractOriginal = function() { + if (contractOriginal && contractOriginal.file) { + var url = URL.createObjectURL(contractOriginal.file); + window.open(url); + } + }; + + var validateSubmitAndReview = function() { + var errs = {}; + if (!selectedCustomer) errs.customer = '请选择客户'; + if (!businessDept) errs.businessDept = '请选择业务部门'; + if (!contractOriginal) errs.contractOriginal = '请上传合同原件'; + if (!businessOwner) errs.businessOwner = '请选择业务负责人'; + if (!projectName || !projectName.trim()) errs.projectName = '请输入项目名称'; + if (!contractType) errs.contractType = '请选择合同类型'; + if (!effectiveDate) errs.effectiveDate = '请选择生效日期'; + if (!paymentMethod) errs.paymentMethod = '请选择付款方式'; + if (!endDate) errs.endDate = '请选择结束日期'; + if (!paymentPeriod) errs.paymentPeriod = '请选择付款周期'; + if (!signingCompany) errs.signingCompany = '请选择签约公司'; + if (!deliveryProvince || !deliveryCity) errs.deliveryRegion = '请选择交车区域'; + var authInvalid = authorizedList.some(function(a) { return !a.name || !a.name.trim() || !a.phone || !a.phone.trim() || !a.idCard || !a.idCard.trim(); }); + if (authInvalid) errs.authorizedList = '请完整填写被授权人姓名、联系电话、身份证'; + var rentalInvalid = rentalOrders.some(function(r) { return !r.brand || !r.model || !(r.monthRent && String(r.monthRent).trim()) || !(r.deposit && String(r.deposit).trim()); }); + if (rentalInvalid) errs.rentalOrders = '请完整填写租赁订单的品牌、型号、车辆月租金、保证金'; + if (!hydrogenBearer) errs.hydrogenBearer = '请选择氢费承担方'; + if (hydrogenBearer === '客户' && !hydrogenPaymentMethod) errs.hydrogenPaymentMethod = '请选择付款方式'; + if (hydrogenBearer === '客户' && hydrogenPaymentMethod === '预付' && (!hydrogenPrepay || !String(hydrogenPrepay).trim())) errs.hydrogenPrepay = '请输入氢气预付款'; + if (!returnHydrogenPrice || !String(returnHydrogenPrice).trim()) errs.returnHydrogenPrice = '请输入退还车氢气单价'; + if (!feeTemplate) errs.feeTemplate = '请选择费用模板'; + if (!billingMethod) errs.billingMethod = '请选择账单计算方式'; + setFormErrors(errs); + if (Object.keys(errs).length > 0) { + var firstId = errs.customer || errs.businessDept || errs.businessOwner ? 'card-customer' : (errs.projectName || errs.contractType || errs.effectiveDate || errs.paymentMethod || errs.endDate || errs.paymentPeriod || errs.signingCompany || errs.deliveryRegion || errs.contractOriginal) ? 'card-contract' : errs.authorizedList ? 'card-authorized' : (errs.rentalOrders || errs.hydrogenBearer || errs.hydrogenPaymentMethod || errs.hydrogenPrepay || errs.returnHydrogenPrice) ? 'card-rental' : errs.feeTemplate ? 'card-fee' : 'card-billing'; + setCc1(false); setCc2(false); setCc3(false); setCc4(false); setCc5(false); setCc6(false); + setTimeout(function() { var el = document.getElementById(firstId); if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' }); }, 100); + return false; + } + return true; + }; + + var addAuthorized = function() { + setAuthorizedList(authorizedList.concat([{ name: '', phone: '', idCard: '' }])); + }; + var removeAuthorized = function(index) { + var next = authorizedList.slice(0); + next.splice(index, 1); + setAuthorizedList(next.length ? next : [{ name: '', phone: '', idCard: '' }]); + }; + var updateAuthorized = function(index, field, value) { + var next = authorizedList.slice(0); + var cur = next[index] || {}; + var patch = {}; + patch[field] = value; + next[index] = Object.assign({}, cur, patch); + setAuthorizedList(next); + }; + + var addRentalRow = function() { + setRentalOrders(rentalOrders.concat([{ brand: '', model: '', plateNo: '', vin: '', monthRent: '', serviceItems: [{ project: '', fee: '', effectiveDate: '' }], deposit: '', remark: '' }])); + }; + var removeRentalRow = function(index) { + var next = rentalOrders.slice(0); + next.splice(index, 1); + setRentalOrders(next.length ? next : [{ brand: '', model: '', plateNo: '', vin: '', monthRent: '', serviceItems: [{ project: '', fee: '', effectiveDate: '' }], deposit: '', remark: '' }]); + }; + var updateRentalOrder = function(index, field, value) { + var next = rentalOrders.slice(0); + var cur = next[index] || {}; + var newRow = Object.assign({}, cur); + newRow[field] = value; + if (field === 'plateNo') { + var v = vehicleList.find(function(x) { return x.plateNo === value; }); + newRow.vin = v ? v.vin : ''; + } + if (field === 'brand') newRow.model = ''; + next[index] = newRow; + setRentalOrders(next); + }; + var openServiceModal = function(index) { setServiceModalRowIndex(index); }; + var closeServiceModal = function() { setServiceModalRowIndex(null); }; + var addServiceItem = function() { + if (serviceModalRowIndex === null) return; + var next = rentalOrders.slice(0); + var row = next[serviceModalRowIndex]; + var items = (row.serviceItems || []).concat([{ project: '', fee: '', effectiveDate: '' }]); + var newRow = {}; + for (var k in row) { if (row.hasOwnProperty(k) && k !== 'serviceItems') newRow[k] = row[k]; } + newRow.serviceItems = items; + next[serviceModalRowIndex] = newRow; + setRentalOrders(next); + }; + var removeServiceItem = function(siIndex) { + if (serviceModalRowIndex === null) return; + var next = rentalOrders.slice(0); + var row = next[serviceModalRowIndex]; + var items = (row.serviceItems || []).slice(0); + items.splice(siIndex, 1); + if (items.length === 0) items = [{ project: '', fee: '', effectiveDate: '' }]; + var newRow = {}; + for (var k in row) { if (row.hasOwnProperty(k) && k !== 'serviceItems') newRow[k] = row[k]; } + newRow.serviceItems = items; + next[serviceModalRowIndex] = newRow; + setRentalOrders(next); + }; + var updateServiceItem = function(siIndex, field, value) { + if (serviceModalRowIndex === null) return; + var next = rentalOrders.slice(0); + var row = next[serviceModalRowIndex]; + var items = (row.serviceItems || []).slice(0); + var item = items[siIndex] || {}; + var newItem = {}; + for (var k in item) { if (item.hasOwnProperty(k)) newItem[k] = item[k]; } + newItem[field] = value; + items[siIndex] = newItem; + var newRow = {}; + for (var rk in row) { if (row.hasOwnProperty(rk) && rk !== 'serviceItems') newRow[rk] = row[rk]; } + newRow.serviceItems = items; + next[serviceModalRowIndex] = newRow; + setRentalOrders(next); + }; + + React.useEffect(function() { + if (!deliveryRegionOpen) return; + var handler = function(e) { + if (deliveryRegionClickInsideRef.current) { deliveryRegionClickInsideRef.current = false; return; } + var el = document.getElementById('delivery-region-wrap'); + if (el && !el.contains(e.target)) setDeliveryRegionOpen(false); + }; + document.addEventListener('mousedown', handler); + return function() { document.removeEventListener('mousedown', handler); }; + }, [deliveryRegionOpen]); + + React.useEffect(function() { + if (plateNoFocusRow === null) { setPlateNoDropdownRect(null); return; } + var timer = setTimeout(function() { + var el = document.getElementById('plate-no-input-' + plateNoFocusRow); + if (el && el.getBoundingClientRect) { + var rect = el.getBoundingClientRect(); + setPlateNoDropdownRect({ top: rect.bottom + 2, left: rect.left, width: rect.width }); + } else { setPlateNoDropdownRect(null); } + }, 0); + return function() { clearTimeout(timer); }; + }, [plateNoFocusRow]); + + var styles = { + page: { padding: '16px 24px 48px', backgroundColor: '#f5f5f5', minHeight: '100vh', fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif', fontSize: 14 }, + breadcrumb: { marginBottom: 16, color: '#666' }, + breadcrumbSep: { margin: '0 8px', color: '#999' }, + anchorWrap: { position: 'fixed', top: 80, right: 24, zIndex: 100, backgroundColor: '#fff', borderRadius: 8, boxShadow: '0 2px 8px rgba(0,0,0,0.12)', padding: '12px 16px', minWidth: 160 }, + anchorItem: { display: 'block', padding: '6px 0', color: '#1890ff', cursor: 'pointer', border: 'none', background: 'none', width: '100%', textAlign: 'left', fontSize: 13 }, + card: { backgroundColor: '#fff', borderRadius: 8, marginBottom: 16, boxShadow: '0 1px 2px rgba(0,0,0,0.05)', overflow: 'hidden' }, + cardHeader: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '16px 20px', borderBottom: '1px solid #f0f0f0', cursor: 'pointer' }, + cardTitle: { fontSize: 16, fontWeight: 600, color: '#333' }, + cardToggle: { color: '#999', fontSize: 14 }, + cardBody: { padding: '20px 24px' }, + formRow: { display: 'flex', flexWrap: 'wrap', marginBottom: 16 }, + formCol: { flex: '0 0 33.33%', minWidth: 200, paddingRight: 16, marginBottom: 8 }, + formColFull: { flex: '0 0 100%', marginBottom: 8 }, + label: { display: 'block', marginBottom: 6, color: '#333' }, + labelRequired: { color: '#ff4d4f', marginRight: 4 }, + input: { width: '100%', padding: '8px 12px', border: '1px solid #d9d9d9', borderRadius: 4, fontSize: 14 }, + inputDisabled: { backgroundColor: '#f5f5f5', color: '#999', cursor: 'not-allowed' }, + inputError: { borderColor: '#ff4d4f' }, + select: { width: '100%', padding: '8px 12px', border: '1px solid #d9d9d9', borderRadius: 4, fontSize: 14 }, + textarea: { width: '100%', padding: '8px 12px', border: '1px solid #d9d9d9', borderRadius: 4, fontSize: 14, minHeight: 80, resize: 'vertical' }, + errMsg: { color: '#ff4d4f', fontSize: 12, marginTop: 4 }, + summaryList: { marginBottom: 16, display: 'flex', flexWrap: 'wrap', gap: 16 }, + summaryListItem: { flex: '0 0 calc(50% - 8px)', display: 'flex', alignItems: 'center', padding: '12px 16px', border: '1px solid #e8e8e8', borderRadius: 4, backgroundColor: '#fafafa', fontSize: 14, boxSizing: 'border-box' }, + summaryListLabel: { flex: '0 0 140px', color: '#666' }, + summaryListValue: { flex: 1, fontWeight: 600, color: '#333' }, + authRow: { display: 'flex', gap: 12, alignItems: 'flex-start', marginBottom: 12 }, + authInput: { flex: 1, padding: '8px 12px', border: '1px solid #d9d9d9', borderRadius: 4 }, + btnDel: { padding: '8px 16px', color: '#ff4d4f', border: '1px solid #ff4d4f', borderRadius: 4, backgroundColor: '#fff', cursor: 'pointer' }, + btnAdd: { padding: '8px 16px', color: '#1890ff', border: '1px dashed #1890ff', borderRadius: 4, backgroundColor: '#fff', cursor: 'pointer', marginBottom: 16 }, + footer: { position: 'fixed', bottom: 0, left: 0, right: 0, padding: '12px 24px', backgroundColor: '#fff', borderTop: '1px solid #e8e8e8', display: 'flex', gap: 12, justifyContent: 'flex-start', zIndex: 99 }, + btn: { padding: '8px 24px', borderRadius: 4, border: '1px solid #d9d9d9', cursor: 'pointer', fontSize: 14 }, + btnPrimary: { backgroundColor: '#1890ff', color: '#fff', borderColor: '#1890ff' }, + btnDefault: { backgroundColor: '#fff', color: '#333' }, + tag: { display: 'inline-block', padding: '2px 8px', marginRight: 8, marginBottom: 4, backgroundColor: '#e6f7ff', color: '#1890ff', borderRadius: 4, fontSize: 12 }, + regionCascader: { position: 'absolute', top: '100%', left: 0, right: 0, marginTop: 4, backgroundColor: '#fff', border: '1px solid #d9d9d9', borderRadius: 4, boxShadow: '0 2px 8px rgba(0,0,0,0.12)', zIndex: 10, display: 'flex', minHeight: 200 }, + regionCascaderCol: { flex: 1, borderRight: '1px solid #f0f0f0', overflowY: 'auto' }, + regionCascaderColLast: { flex: 1 }, + regionCascaderItem: { padding: '10px 12px', cursor: 'pointer' }, + rentalTable: { width: '100%', borderCollapse: 'collapse', fontSize: 13 }, + rentalTh: { padding: '10px 8px', textAlign: 'left', borderBottom: '1px solid #e8e8e8', backgroundColor: '#fafafa', fontWeight: 600 }, + rentalTd: { padding: '8px', borderBottom: '1px solid #f0f0f0', verticalAlign: 'top' }, + rentalTdCenter: { padding: '8px', borderBottom: '1px solid #f0f0f0', verticalAlign: 'middle' }, + rentalInput: { width: '100%', padding: '6px 10px', border: '1px solid #d9d9d9', borderRadius: 4, fontSize: 13 }, + rentalInputDisabled: { backgroundColor: '#f5f5f5', color: '#999' }, + modalMask: { position: 'fixed', left: 0, top: 0, right: 0, bottom: 0, backgroundColor: 'rgba(0,0,0,0.45)', zIndex: 1000, display: 'flex', alignItems: 'center', justifyContent: 'center' }, + modalBox: { backgroundColor: '#fff', borderRadius: 8, width: '90%', maxWidth: 720, maxHeight: '85vh', overflow: 'hidden', display: 'flex', flexDirection: 'column', boxShadow: '0 4px 20px rgba(0,0,0,0.15)' }, + modalHeader: { padding: '16px 20px', borderBottom: '1px solid #f0f0f0', fontSize: 16, fontWeight: 600 }, + modalBody: { padding: 20, overflow: 'auto', flex: 1 }, + btnGroup: { display: 'inline-flex', border: '1px solid #d9d9d9', borderRadius: 4, overflow: 'hidden' }, + btnGroupItem: { padding: '8px 16px', border: 'none', borderRight: '1px solid #d9d9d9', backgroundColor: '#fff', color: '#333', cursor: 'pointer', fontSize: 14 }, + btnGroupItemLast: { borderRight: 'none' }, + btnGroupItemActive: { backgroundColor: '#1890ff', color: '#fff', borderColor: '#1890ff', borderRightColor: '#1890ff' }, + feeSectionTitle: { fontSize: 15, fontWeight: 600, color: '#333', marginTop: 20, marginBottom: 10 }, + feeSectionTitleFirst: { marginTop: 0 }, + modalFormInput: { width: '100%', padding: '8px 12px', border: '1px solid #d9d9d9', borderRadius: 4, fontSize: 14, height: 36, boxSizing: 'border-box' } + }; + + var CardBlock = function(props) { + return React.createElement('div', { id: props.id, style: styles.card }, + React.createElement('div', { style: styles.cardHeader, onClick: function() { props.setCollapsed(!props.collapsed); } }, + React.createElement('span', { style: styles.cardTitle }, props.title), + React.createElement('span', { style: styles.cardToggle }, props.collapsed ? '展开' : '收起') + ), + !props.collapsed ? React.createElement('div', { style: styles.cardBody }, props.children) : null + ); + }; + + var FormItem = function(props) { + var colStyle = props.fullWidth ? styles.formColFull : (props.colStyle ? Object.assign({}, styles.formCol, props.colStyle) : styles.formCol); + return React.createElement('div', { style: colStyle }, + React.createElement('label', { style: styles.label }, props.required ? React.createElement('span', { style: styles.labelRequired }, '*') : null, props.label), + props.children, + props.error ? React.createElement('div', { style: styles.errMsg }, props.error) : null + ); + }; + + var customerOptions = customerList.map(function(c) { return React.createElement(Option, { key: c.id, value: c.id }, c.name); }); + var customerFields = React.createElement('div', { style: styles.formRow }, + React.createElement(FormItem, { label: '客户名称', required: true, error: formErrors.customer }, React.createElement(Select, { placeholder: '请选择或输入搜索客户', style: { width: '100%' }, value: selectedCustomer ? selectedCustomer.id : undefined, onChange: function(id) { var c = customerList.find(function(x) { return x.id === id; }); selectCustomer(c || null); }, showSearch: true, allowClear: true, filterOption: function(input, opt) { return opt.children && String(opt.children).toLowerCase().indexOf((input || '').toLowerCase()) >= 0; }, status: formErrors.customer ? 'error' : undefined }, customerOptions)), + React.createElement(FormItem, { label: '客户统一信用代码' }, React.createElement(Input, { value: selectedCustomer ? selectedCustomer.creditCode : '', disabled: true, style: { width: '100%' } })), + React.createElement(FormItem, { label: '客户地址' }, React.createElement(Input, { value: selectedCustomer ? selectedCustomer.address : '', disabled: true, style: { width: '100%' } })), + React.createElement(FormItem, { label: '客户联系人' }, React.createElement(Input, { value: selectedCustomer ? selectedCustomer.contact : '', disabled: true, style: { width: '100%' } })), + React.createElement(FormItem, { label: '客户电话' }, React.createElement(Input, { value: selectedCustomer ? selectedCustomer.phone : '', disabled: true, style: { width: '100%' } })), + React.createElement(FormItem, { label: '客户电子邮箱' }, React.createElement(Input, { value: selectedCustomer ? selectedCustomer.email : '', disabled: true, style: { width: '100%' } })), + React.createElement(FormItem, { label: '企业名称' }, React.createElement(Input, { value: selectedCustomer ? selectedCustomer.companyName : '', disabled: true, style: { width: '100%' } })), + React.createElement(FormItem, { label: '企业电话' }, React.createElement(Input, { value: selectedCustomer ? selectedCustomer.companyPhone : '', disabled: true, style: { width: '100%' } })), + React.createElement(FormItem, { label: '邮寄地址' }, React.createElement(Input, { value: selectedCustomer ? selectedCustomer.mailingAddress : '', disabled: true, style: { width: '100%' } })), + React.createElement(FormItem, { label: '开户银行' }, React.createElement(Input, { value: selectedCustomer ? selectedCustomer.bank : '', disabled: true, style: { width: '100%' } })), + React.createElement(FormItem, { label: '银行账号' }, React.createElement(Input, { value: selectedCustomer ? selectedCustomer.bankAccount : '', disabled: true, style: { width: '100%' } })), + React.createElement(FormItem, { label: '纳税人识别号' }, React.createElement(Input, { value: selectedCustomer ? selectedCustomer.taxId : '', disabled: true, style: { width: '100%' } })), + React.createElement(FormItem, { label: '业务部门', required: true, error: formErrors.businessDept }, React.createElement(Select, { placeholder: '请选择业务部门', style: { width: '100%' }, value: businessDept || undefined, onChange: function(v) { setBusinessDept(v || ''); setBusinessOwner(''); }, status: formErrors.businessDept ? 'error' : undefined }, deptList.map(function(d, i) { return React.createElement(Option, { key: i, value: d.id }, d.name); }))), + React.createElement(FormItem, { label: '业务负责人', required: true, error: formErrors.businessOwner || ownerFocusError }, React.createElement(Select, { placeholder: businessDept ? '请选择' : '请先选择业务部门', style: { width: '100%' }, value: businessOwner || undefined, onChange: function(v) { setBusinessOwner(v || ''); setOwnerFocusError(''); }, onFocus: handleOwnerFocus, onBlur: handleOwnerBlur, disabled: !businessDept, status: (formErrors.businessOwner || ownerFocusError) ? 'error' : undefined }, ownerOptions.map(function(o, i) { return React.createElement(Option, { key: i, value: o }, o); }))) + ); + + var contractFormRow1 = React.createElement('div', { style: styles.formRow }, + React.createElement(FormItem, { label: '项目名称', required: true, error: formErrors.projectName }, React.createElement(Input, { placeholder: '请输入项目名称', value: projectName, onChange: function(e) { setProjectName(e.target.value); }, status: formErrors.projectName ? 'error' : undefined, style: { width: '100%' } })), + React.createElement(FormItem, { label: '合同编码' }, React.createElement(Input, { value: contractCodeDisplay, disabled: true, style: { width: '100%' } })), + React.createElement(FormItem, { label: '合同类型', required: true, error: formErrors.contractType }, React.createElement(Select, { placeholder: '请选择合同类型', style: { width: '100%' }, value: contractType || undefined, onChange: function(v) { setContractType(v || ''); }, status: formErrors.contractType ? 'error' : undefined }, React.createElement(Option, { value: '正式合同' }, '正式合同'), React.createElement(Option, { value: '试用合同' }, '试用合同'))), + React.createElement(FormItem, { label: '生效日期', required: true, error: formErrors.effectiveDate }, React.createElement(DatePicker, { style: { width: '100%' }, format: 'YYYY-MM-DD', placeholder: '请选择生效日期', value: effectiveDate && window.moment ? window.moment(effectiveDate, 'YYYY-MM-DD') : null, onChange: function(d, dateStr) { setEffectiveDate(dateStr || ''); }, status: formErrors.effectiveDate ? 'error' : undefined })), + React.createElement(FormItem, { label: '付款方式', required: true, error: formErrors.paymentMethod }, React.createElement(Select, { placeholder: '请选择付款方式', style: { width: '100%' }, value: paymentMethod || undefined, onChange: function(v) { setPaymentMethod(v || ''); }, status: formErrors.paymentMethod ? 'error' : undefined }, React.createElement(Option, { value: '预付' }, '预付'), React.createElement(Option, { value: '后付' }, '后付'))), + React.createElement(FormItem, { label: '主要车型' }, React.createElement('div', { style: { padding: '8px 12px', minHeight: 36, border: '1px solid #d9d9d9', borderRadius: 4, backgroundColor: '#f5f5f5', display: 'flex', flexWrap: 'wrap', gap: 8, alignItems: 'center' } }, mainVehicleModelsDisplay ? mainVehicleModelsDisplay.split('、').map(function(m, i) { return React.createElement('span', { key: i, style: styles.tag }, m); }) : React.createElement('span', { style: { color: '#999' } }, '根据租赁订单自动反写'))), + React.createElement(FormItem, { label: '结束日期', required: true, error: formErrors.endDate }, React.createElement(DatePicker, { style: { width: '100%' }, format: 'YYYY-MM-DD', placeholder: '请选择结束日期', value: endDate && window.moment ? window.moment(endDate, 'YYYY-MM-DD') : null, onChange: function(d, dateStr) { setEndDate(dateStr || ''); }, status: formErrors.endDate ? 'error' : undefined })), + React.createElement(FormItem, { label: '付款周期', required: true, error: formErrors.paymentPeriod }, React.createElement(Select, { placeholder: '请选择', style: { width: '100%' }, value: paymentPeriod || undefined, onChange: function(v) { setPaymentPeriod(v || ''); }, status: formErrors.paymentPeriod ? 'error' : undefined }, [1,2,3,4,5,6,7,8,9,10,11,12].map(function(n) { return React.createElement(Option, { key: n, value: String(n) }, n + '个月'); }))), + React.createElement(FormItem, { label: '签约公司', required: true, error: formErrors.signingCompany }, React.createElement(Select, { placeholder: '请选择', style: { width: '100%' }, value: signingCompany || undefined, onChange: function(v) { setSigningCompany(v || ''); }, status: formErrors.signingCompany ? 'error' : undefined }, orgList.map(function(o, i) { return React.createElement(Option, { key: i, value: o }, o); }))), + React.createElement('div', { style: styles.formCol }, + React.createElement(FormItem, { label: '交车区域', required: true, error: formErrors.deliveryRegion }, React.createElement('div', { id: 'delivery-region-wrap', style: { position: 'relative' } }, React.createElement(Input, { style: Object.assign({}, formErrors.deliveryRegion ? { borderColor: '#ff4d4f' } : {}, { cursor: 'pointer', caretColor: 'transparent', width: '100%' }), placeholder: '请选择省-市', value: deliveryRegionDisplay, readOnly: true, onClick: function() { setDeliveryRegionOpen(!deliveryRegionOpen); } }), deliveryRegionOpen ? React.createElement('div', { style: styles.regionCascader, onMouseDown: function() { deliveryRegionClickInsideRef.current = true; } }, React.createElement('div', { style: styles.regionCascaderCol }, regionList.map(function(r, i) { var isActive = r.province === deliveryProvince; return React.createElement('div', { key: i, style: Object.assign({}, styles.regionCascaderItem, isActive ? { backgroundColor: '#e6f7ff', color: '#1890ff' } : {}), onMouseEnter: function(e) { if (!isActive) e.currentTarget.style.backgroundColor = '#f5f5f5'; }, onMouseLeave: function(e) { if (!isActive) e.currentTarget.style.backgroundColor = 'transparent'; }, onMouseDown: function(e) { e.preventDefault(); setDeliveryProvince(r.province); setDeliveryCity(''); } }, r.province); })), React.createElement('div', { style: styles.regionCascaderColLast }, deliveryProvince ? (regionList.find(function(x) { return x.province === deliveryProvince; }) || { cities: [] }).cities.map(function(c, i) { var isActive = c === deliveryCity; return React.createElement('div', { key: i, style: Object.assign({}, styles.regionCascaderItem, isActive ? { backgroundColor: '#e6f7ff', color: '#1890ff' } : {}), onMouseEnter: function(e) { if (!isActive) e.currentTarget.style.backgroundColor = '#f5f5f5'; }, onMouseLeave: function(e) { if (!isActive) e.currentTarget.style.backgroundColor = 'transparent'; }, onMouseDown: function(e) { e.preventDefault(); selectDeliveryRegion(deliveryProvince, c); } }, c); }) : React.createElement('div', { style: { padding: 16, color: '#999', fontSize: 13 } }, '请先选择省'))) : null)), + React.createElement(FormItem, { label: '合同原件', required: true, error: formErrors.contractOriginal }, + React.createElement('div', null, + React.createElement('div', { style: { color: '#999', fontSize: 12, marginBottom: 8 } }, '转正式合同时需重新上传合同原件附件'), + contractOriginal + ? React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap' } }, + React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' } }, + React.createElement('a', { href: '#', style: { color: '#1890ff' }, onClick: function(e) { e.preventDefault(); openContractOriginal(); } }, contractOriginal.name), + contractOriginal.size || contractOriginal.uploadTime ? React.createElement('span', { style: { color: '#999', fontSize: 12 } }, (contractOriginal.size ? contractOriginal.size : '') + (contractOriginal.size && contractOriginal.uploadTime ? ' · ' : '') + (contractOriginal.uploadTime || '')) : null + ), + React.createElement(Button, { type: 'button', size: 'small', danger: true, onClick: function() { setContractOriginal(null); } }, '删除') + ) + : React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8 } }, + React.createElement('input', { ref: contractOriginalRef, type: 'file', style: { display: 'none' }, onChange: function(e) { + var f = e.target.files && e.target.files[0]; + if (f) { + var sizeDisplay = f.size >= 1024 * 1024 ? (f.size / 1024 / 1024).toFixed(1) + ' MB' : (f.size / 1024).toFixed(1) + ' KB'; + var now = window.moment ? window.moment() : new Date(); + var uploadTimeStr = window.moment ? now.format('YYYY-MM-DD HH:mm') : now.getFullYear() + '-' + String(now.getMonth() + 1).padStart(2, '0') + '-' + String(now.getDate()).padStart(2, '0') + ' ' + String(now.getHours()).padStart(2, '0') + ':' + String(now.getMinutes()).padStart(2, '0'); + setContractOriginal({ name: f.name, file: f, size: sizeDisplay, uploadTime: uploadTimeStr }); + } + e.target.value = ''; + } }), + React.createElement(Button, { type: 'default', style: { padding: '8px 16px', border: '1px solid #d9d9d9', borderRadius: 4, backgroundColor: '#fff', color: '#333', cursor: 'pointer', fontSize: 14 }, onClick: function() { if (contractOriginalRef.current) contractOriginalRef.current.click(); } }, '上传附件') + ) + ) + ) + ), + React.createElement(FormItem, { label: '交车地点' }, React.createElement(Input, { placeholder: '请输入交车地点', value: deliveryLocation, onChange: function(e) { setDeliveryLocation(e.target.value); }, style: { width: '100%' } })) + ); + var contractFormRow2 = React.createElement('div', { style: styles.formRow }, React.createElement(FormItem, { label: '备注', fullWidth: true }, React.createElement(Input.TextArea, { placeholder: '请输入备注信息', value: remarks, onChange: function(e) { setRemarks(e.target.value); }, style: styles.textarea, rows: 4 }))); + + var authorizedContent = React.createElement('div', null, + React.createElement('div', { style: { display: 'flex', gap: 12, alignItems: 'center', marginBottom: 8 } }, React.createElement('span', { style: { flex: 1, fontWeight: 600, fontSize: 13 } }, React.createElement('span', { style: { color: '#ff4d4f', marginRight: 4 } }, '*'), '被授权人姓名'), React.createElement('span', { style: { flex: 1, fontWeight: 600, fontSize: 13 } }, React.createElement('span', { style: { color: '#ff4d4f', marginRight: 4 } }, '*'), '被授权人联系电话'), React.createElement('span', { style: { flex: 1, fontWeight: 600, fontSize: 13 } }, React.createElement('span', { style: { color: '#ff4d4f', marginRight: 4 } }, '*'), '被授权人身份证'), React.createElement('span', { style: { flex: '0 0 80px' } })), + authorizedList.map(function(item, index) { return React.createElement('div', { key: index, style: styles.authRow }, React.createElement(Input, { style: Object.assign({}, styles.authInput, formErrors.authorizedList ? { borderColor: '#ff4d4f' } : {}, { flex: 1 }), placeholder: '请输入被授权人姓名', value: item.name, onChange: function(e) { updateAuthorized(index, 'name', e.target.value); } }), React.createElement(Input, { style: Object.assign({}, styles.authInput, formErrors.authorizedList ? { borderColor: '#ff4d4f' } : {}, { flex: 1 }), placeholder: '请输入被授权人联系电话', value: item.phone, onChange: function(e) { updateAuthorized(index, 'phone', e.target.value); } }), React.createElement(Input, { style: Object.assign({}, styles.authInput, formErrors.authorizedList ? { borderColor: '#ff4d4f' } : {}, { flex: 1 }), placeholder: '请输入被授权人身份证号', value: item.idCard, onChange: function(e) { updateAuthorized(index, 'idCard', e.target.value); } }), React.createElement(Button, { type: 'button', danger: true, onClick: function() { removeAuthorized(index); } }, '删除')); }), + formErrors.authorizedList ? React.createElement('div', { style: styles.errMsg }, formErrors.authorizedList) : null, + React.createElement(Button, { type: 'dashed', style: { width: '100%', marginBottom: 16 }, onClick: addAuthorized }, '添加一行') + ); + + var hydrogenFormRow = (function() { + var hydrogenFields = []; + hydrogenFields.push(React.createElement(FormItem, { label: '氢费承担方', required: true, error: formErrors.hydrogenBearer }, React.createElement(Select, { style: { width: '100%' }, value: hydrogenBearer || undefined, onChange: function(v) { setHydrogenBearer(v || ''); setHydrogenPaymentMethod(v === '客户' ? '预付' : ''); }, status: formErrors.hydrogenBearer ? 'error' : undefined }, React.createElement(Option, { value: '我方' }, '我方'), React.createElement(Option, { value: '客户' }, '客户')))); + if (hydrogenBearer === '客户') { hydrogenFields.push(React.createElement(FormItem, { label: '付款方式', required: true, error: formErrors.hydrogenPaymentMethod }, React.createElement(Select, { style: { width: '100%' }, value: hydrogenPaymentMethod || undefined, onChange: function(v) { setHydrogenPaymentMethod(v || ''); }, status: formErrors.hydrogenPaymentMethod ? 'error' : undefined }, React.createElement(Option, { value: '预付' }, '预付'), React.createElement(Option, { value: '月付款' }, '月付款'), React.createElement(Option, { value: '自行结算' }, '自行结算')))); } + var hydrogenPrepayInput = React.createElement(Input, { placeholder: '0.00', value: hydrogenPrepay, onChange: function(e) { setHydrogenPrepay(e.target.value); }, addonAfter: '元', status: formErrors.hydrogenPrepay ? 'error' : undefined, style: { width: '100%' } }); + var returnHydrogenInput = React.createElement(Input, { placeholder: '0.00', value: returnHydrogenPrice, onChange: function(e) { setReturnHydrogenPrice(e.target.value); }, addonAfter: '元', status: formErrors.returnHydrogenPrice ? 'error' : undefined, style: { width: '100%' } }); + var returnHydrogenColStyle = hydrogenPaymentMethod === '预付' ? { flex: '1 1 0', minWidth: 180 } : { flex: '0 0 calc(50% - 8px)', minWidth: 180 }; + hydrogenFields.push(React.createElement('div', { key: 'hydrogen-amount-row', style: { display: 'flex', gap: 16, flex: '1 1 100%' } }, hydrogenPaymentMethod === '预付' ? React.createElement(FormItem, { label: '氢气预付款', required: true, error: formErrors.hydrogenPrepay, colStyle: { flex: '1 1 0', minWidth: 180 } }, hydrogenPrepayInput) : null, React.createElement(FormItem, { label: '退还车氢气单价', required: true, error: formErrors.returnHydrogenPrice, colStyle: returnHydrogenColStyle }, returnHydrogenInput))); + return React.createElement.apply(React, ['div', { style: Object.assign({}, styles.formRow, { marginTop: 20 }) }].concat(hydrogenFields)); + })(); + + var plateNoOptions = vehicleList.map(function(v) { return React.createElement(Option, { key: v.plateNo, value: v.plateNo }, v.plateNo); }); + var rentalTableBody = rentalOrders.map(function(row, idx) { + var modelOpts = row.brand ? (modelByBrand[row.brand] || []) : []; + return React.createElement('tr', { key: idx }, + React.createElement('td', { style: styles.rentalTdCenter }, idx + 1), + React.createElement('td', { style: styles.rentalTd }, React.createElement(Select, { style: { width: '100%', minWidth: 90 }, value: row.brand || undefined, onChange: function(v) { updateRentalOrder(idx, 'brand', v || ''); }, placeholder: '请选择' }, brandList.map(function(b, i) { return React.createElement(Option, { key: i, value: b }, b); }))), + React.createElement('td', { style: styles.rentalTd }, React.createElement(Select, { style: { width: '100%', minWidth: 90 }, value: row.model || undefined, onChange: function(v) { updateRentalOrder(idx, 'model', v || ''); }, disabled: !row.brand, placeholder: '请选择' }, modelOpts.map(function(m, i) { return React.createElement(Option, { key: i, value: m }, m); }))), + React.createElement('td', { style: styles.rentalTd }, React.createElement(Select, { style: { width: '100%' }, placeholder: '请选择或输入搜索', value: row.plateNo || undefined, onChange: function(v) { updateRentalOrder(idx, 'plateNo', v || ''); }, showSearch: true, allowClear: true, filterOption: function(input, opt) { return opt && opt.children && String(opt.children).toLowerCase().indexOf((input || '').toLowerCase()) >= 0; } }, plateNoOptions)), + React.createElement('td', { style: styles.rentalTd }, React.createElement(Input, { value: row.vin || '', disabled: true, style: { width: '100%' } })), + React.createElement('td', { style: styles.rentalTd }, React.createElement('div', { style: { display: 'flex', alignItems: 'center' } }, React.createElement(Input, { placeholder: '0.00', value: row.monthRent || '', onChange: function(e) { updateRentalOrder(idx, 'monthRent', e.target.value); }, style: styles.rentalInput }), React.createElement('span', { style: { marginLeft: 4, whiteSpace: 'nowrap' } }, '元'))), + React.createElement('td', { style: styles.rentalTdCenter }, React.createElement(Button, { type: 'link', size: 'small', onClick: function() { openServiceModal(idx); } }, '管理')), + React.createElement('td', { style: styles.rentalTdCenter }, calcRowServiceFee(row) + ' 元'), + React.createElement('td', { style: styles.rentalTd }, React.createElement('div', { style: { display: 'flex', alignItems: 'center' } }, React.createElement(Input, { placeholder: '0.00', value: row.deposit || '', onChange: function(e) { updateRentalOrder(idx, 'deposit', e.target.value); }, style: styles.rentalInput }), React.createElement('span', { style: { marginLeft: 4, whiteSpace: 'nowrap' } }, '元'))), + React.createElement('td', { style: styles.rentalTd }, React.createElement(Input, { placeholder: '备注', value: row.remark || '', onChange: function(e) { updateRentalOrder(idx, 'remark', e.target.value); }, style: Object.assign({}, styles.rentalInput, { width: '100%' }) })), + React.createElement('td', { style: styles.rentalTdCenter }, React.createElement(Button, { type: 'link', size: 'small', danger: true, onClick: function() { removeRentalRow(idx); } }, '删除')) + ); + }); + + var rentalSummary = React.createElement('div', { style: styles.summaryList }, + React.createElement('div', { style: styles.summaryListItem }, React.createElement('span', { style: styles.summaryListLabel }, '租赁车辆数'), React.createElement('span', { style: styles.summaryListValue }, rentalTotalVehicles + ' 辆')), + React.createElement('div', { style: styles.summaryListItem }, React.createElement('span', { style: styles.summaryListLabel }, '租金及服务费合计'), React.createElement('span', { style: styles.summaryListValue }, rentalTotalRentService.toFixed(2) + ' 元')), + React.createElement('div', { style: styles.summaryListItem }, React.createElement('span', { style: styles.summaryListLabel }, '保证金总额'), React.createElement('span', { style: styles.summaryListValue }, rentalTotalDeposit.toFixed(2) + ' 元')), + React.createElement('div', { style: styles.summaryListItem }, React.createElement('span', { style: styles.summaryListLabel }, '氢气预付款金额'), React.createElement('span', { style: styles.summaryListValue }, rentalTotalHydrogen.toFixed(2) + ' 元')) + ); + var reqStarStyle = { color: '#ff4d4f', marginRight: 4 }; + var reqStar = React.createElement('span', { style: reqStarStyle }, '*'); + var rentalTh1 = React.createElement('th', { style: Object.assign({}, styles.rentalTh, { width: 50, verticalAlign: 'middle' }) }, '序号'); + var rentalTh2 = React.createElement('th', { style: Object.assign({}, styles.rentalTh, { width: 100 }) }, reqStar, '品牌'); + var rentalTh3 = React.createElement('th', { style: Object.assign({}, styles.rentalTh, { width: 100 }) }, reqStar, '型号'); + var rentalTh4 = React.createElement('th', { style: Object.assign({}, styles.rentalTh, { width: 120 }) }, '车牌号'); + var rentalTh5 = React.createElement('th', { style: Object.assign({}, styles.rentalTh, { width: 160 }) }, '车辆识别代码'); + var rentalTh6 = React.createElement('th', { style: Object.assign({}, styles.rentalTh, { width: 120 }) }, reqStar, '车辆月租金'); + var rentalTh7 = React.createElement('th', { style: Object.assign({}, styles.rentalTh, { width: 80, verticalAlign: 'middle' }) }, '服务费项目'); + var rentalTh8 = React.createElement('th', { style: Object.assign({}, styles.rentalTh, { width: 90, verticalAlign: 'middle' }) }, '服务费'); + var rentalTh9 = React.createElement('th', { style: Object.assign({}, styles.rentalTh, { width: 100 }) }, reqStar, '保证金'); + var rentalTh10 = React.createElement('th', { style: Object.assign({}, styles.rentalTh, { width: 80 }) }, '备注'); + var rentalTh11 = React.createElement('th', { style: Object.assign({}, styles.rentalTh, { width: 60, verticalAlign: 'middle' }) }, '操作'); + var rentalTableThead = React.createElement('thead', null, React.createElement('tr', null, rentalTh1, rentalTh2, rentalTh3, rentalTh4, rentalTh5, rentalTh6, rentalTh7, rentalTh8, rentalTh9, rentalTh10, rentalTh11)); + var rentalTableTbody = React.createElement('tbody', null, rentalTableBody); + var rentalTableEl = React.createElement('table', { style: styles.rentalTable }, rentalTableThead, rentalTableTbody); + var rentalTableWrap = React.createElement('div', { style: { overflowX: 'auto', marginBottom: 16 } }, rentalTableEl); + var rentalContent = React.createElement('div', null, rentalSummary, formErrors.rentalOrders ? React.createElement('div', { style: styles.errMsg }, formErrors.rentalOrders) : null, rentalTableWrap, React.createElement(Button, { type: 'dashed', style: { marginTop: 12, width: '100%' }, onClick: addRentalRow }, '添加一行'), hydrogenFormRow); + + var feeTableHeader3 = React.createElement('tr', null, React.createElement('th', { style: styles.rentalTh }, '项目'), React.createElement('th', { style: styles.rentalTh }, '收费标准'), React.createElement('th', { style: styles.rentalTh }, '服务费')); + var feeTableHeader5 = React.createElement('tr', null, React.createElement('th', { style: styles.rentalTh }, '类别'), React.createElement('th', { style: styles.rentalTh }, '损坏部位'), React.createElement('th', { style: styles.rentalTh }, '配件'), React.createElement('th', { style: styles.rentalTh }, '数量'), React.createElement('th', { style: styles.rentalTh }, '费用明细')); + var makeFeeRow3 = function(r, i) { + var td1 = React.createElement('td', { style: styles.rentalTd }, r.project); + var td2 = React.createElement('td', { style: styles.rentalTd }, r.standard); + var td3 = React.createElement('td', { style: styles.rentalTd }, r.serviceFee); + return React.createElement('tr', { key: i }, td1, td2, td3); + }; + var makeFeeRow5 = function(r, i) { + var td1 = React.createElement('td', { style: styles.rentalTd }, r.category); + var td2 = React.createElement('td', { style: styles.rentalTd }, r.part); + var td3 = React.createElement('td', { style: styles.rentalTd }, r.partName); + var td4 = React.createElement('td', { style: styles.rentalTd }, r.qty); + var td5 = React.createElement('td', { style: styles.rentalTd }, r.feeDetail); + return React.createElement('tr', { key: i }, td1, td2, td3, td4, td5); + }; + var feeCertRows = feeTemplateCertFees.map(makeFeeRow3); + var feePenaltyRows = feeTemplatePenaltyFees.map(makeFeeRow3); + var feeConsumablesRows = feeTemplateConsumables.map(makeFeeRow5); + var feeOtherRows = feeTemplateOtherFees.map(makeFeeRow3); + var feeCertTable = React.createElement('table', { style: styles.rentalTable }, React.createElement('thead', null, feeTableHeader3), React.createElement('tbody', null, feeCertRows)); + var feePenaltyTable = React.createElement('table', { style: styles.rentalTable }, React.createElement('thead', null, feeTableHeader3), React.createElement('tbody', null, feePenaltyRows)); + var feeConsumablesTable = React.createElement('table', { style: styles.rentalTable }, React.createElement('thead', null, feeTableHeader5), React.createElement('tbody', null, feeConsumablesRows)); + var feeOtherTable = React.createElement('table', { style: styles.rentalTable }, React.createElement('thead', null, feeTableHeader3), React.createElement('tbody', null, feeOtherRows)); + var feeTemplateSelect = React.createElement('div', { style: styles.formRow }, React.createElement(FormItem, { label: '选择费用模板', required: true, error: formErrors.feeTemplate }, React.createElement(Select, { placeholder: '请选择费用模板', style: { width: '100%' }, value: feeTemplate || undefined, onChange: function(v) { setFeeTemplate(v || ''); }, status: formErrors.feeTemplate ? 'error' : undefined }, feeTemplates.map(function(f, i) { return React.createElement(Option, { key: i, value: f }, f); })))); + var feeTemplateBody = feeTemplate ? React.createElement('div', null, + React.createElement('div', { style: Object.assign({}, styles.feeSectionTitle, styles.feeSectionTitleFirst) }, '证照补办费用'), + feeCertTable, + React.createElement('div', { style: styles.feeSectionTitle }, '违约金费用'), + feePenaltyTable, + React.createElement('div', { style: styles.feeSectionTitle }, '易损件信息'), + feeConsumablesTable, + React.createElement('div', { style: styles.feeSectionTitle }, '其他费用信息'), + feeOtherTable + ) : null; + var feeContent = React.createElement('div', null, feeTemplateSelect, feeTemplateBody); + + var billingBtnBase = { padding: '12px 16px', border: '1px solid #d9d9d9', borderRadius: 0, backgroundColor: '#fff', color: '#333', cursor: 'pointer', fontSize: 14, textAlign: 'left', flex: 1, minWidth: 0, width: 0, height: 'auto', display: 'flex', flexDirection: 'column', alignItems: 'flex-start', justifyContent: 'flex-start', overflow: 'visible' }; + var billingContent = React.createElement('div', null, + React.createElement('div', { style: { display: 'flex', border: formErrors.billingMethod ? '1px solid #ff4d4f' : '1px solid #d9d9d9', borderRadius: 4, overflow: 'hidden', alignItems: 'stretch' } }, + React.createElement(Button, { type: 'button', style: Object.assign({}, billingBtnBase, { borderRight: '1px solid #d9d9d9' }, billingMethod === 'month' ? { backgroundColor: '#1890ff', color: '#fff', borderColor: '#1890ff' } : {}), onClick: function() { setBillingMethod('month'); } }, React.createElement('div', { style: { width: '100%' } }, React.createElement('div', { style: { fontWeight: 600, marginBottom: 6 } }, '按自然月结算'), React.createElement('div', { style: { fontSize: 12, opacity: 0.9, whiteSpace: 'normal', wordBreak: 'break-word', lineHeight: 1.5 } }, '账单按照第一个月计费日期开始-当月最后一天为第一期,之后按自然月方式形成每一期账单,例如付款周期为2个月,则第二期账单从2月1日-3月31日。'))), + React.createElement(Button, { type: 'button', style: Object.assign({}, billingBtnBase, billingMethod === 'period' ? { backgroundColor: '#1890ff', color: '#fff', borderColor: '#1890ff' } : {}), onClick: function() { setBillingMethod('period'); } }, React.createElement('div', { style: { width: '100%' } }, React.createElement('div', { style: { fontWeight: 600, marginBottom: 6 } }, '按付款周期天数结算'), React.createElement('div', { style: { fontSize: 12, opacity: 0.9, whiteSpace: 'normal', wordBreak: 'break-word', lineHeight: 1.5 } }, '账单按照合同基本信息中付款周期实际天数形成一期账单,例如付款周期为60天,则该账单账期为60天,每60天会自动生成新一期账单。'))) + ), + formErrors.billingMethod ? React.createElement('div', { style: styles.errMsg }, formErrors.billingMethod) : null + ); + + var serviceModalRows = serviceModalRowIndex !== null && rentalOrders[serviceModalRowIndex] ? rentalOrders[serviceModalRowIndex].serviceItems : []; + var serviceModalContent = serviceModalRowIndex !== null ? React.createElement('div', { style: styles.modalMask, onClick: function(e) { if (e.target === e.currentTarget) closeServiceModal(); } }, React.createElement('div', { style: styles.modalBox, onClick: function(e) { e.stopPropagation(); } }, React.createElement('div', { style: styles.modalHeader }, '服务项目'), React.createElement('div', { style: styles.modalBody }, React.createElement('table', { style: styles.rentalTable }, React.createElement('thead', null, React.createElement('tr', null, React.createElement('th', { style: styles.rentalTh }, '服务项目'), React.createElement('th', { style: styles.rentalTh }, '费用'), React.createElement('th', { style: styles.rentalTh }, '生效时间'), React.createElement('th', { style: Object.assign({}, styles.rentalTh, { width: 80 }) }, '操作'))), React.createElement('tbody', null, serviceModalRows.map(function(si, siIdx) { var filteredServiceOpts = serviceItemOptions.filter(function(o) { return !serviceItemSearch || o.indexOf(serviceItemSearch) !== -1; }); return React.createElement('tr', { key: siIdx }, React.createElement('td', { style: styles.rentalTd }, React.createElement(Select, { style: { width: '100%' }, placeholder: '请选择服务项目', value: si.project || undefined, onChange: function(v) { updateServiceItem(siIdx, 'project', v || ''); }, showSearch: true, filterOption: function(input, opt) { return opt && opt.children && String(opt.children).toLowerCase().indexOf((input || '').toLowerCase()) >= 0; }, allowClear: true }, serviceItemOptions.map(function(opt, oi) { return React.createElement(Option, { key: oi, value: opt }, opt); }))), React.createElement('td', { style: styles.rentalTd }, React.createElement(Input, { placeholder: '0.00', value: si.fee || '', onChange: function(e) { updateServiceItem(siIdx, 'fee', e.target.value); }, addonAfter: '元', style: { width: '100%' } })), React.createElement('td', { style: styles.rentalTd }, React.createElement(DatePicker, { style: { width: '100%' }, format: 'YYYY-MM-DD', placeholder: '请选择生效时间', value: si.effectiveDate && window.moment ? window.moment(si.effectiveDate, 'YYYY-MM-DD') : null, onChange: function(d, dateStr) { updateServiceItem(siIdx, 'effectiveDate', dateStr || ''); } })), React.createElement('td', { style: styles.rentalTd }, React.createElement(Button, { type: 'link', size: 'small', danger: true, onClick: function() { removeServiceItem(siIdx); } }, '删除'))); }))), React.createElement(Button, { type: 'dashed', style: { marginTop: 12, width: '100%' }, onClick: addServiceItem }, '添加一行')), React.createElement('div', { style: { padding: '12px 20px', borderTop: '1px solid #f0f0f0', textAlign: 'right', display: 'flex', gap: 12, justifyContent: 'flex-end' } }, React.createElement(Button, { type: 'primary', onClick: function() { closeServiceModal(); } }, '保存'), React.createElement(Button, { onClick: closeServiceModal }, '关闭')))) : null; + + var reqSpecH1 = { fontSize: 16, fontWeight: 600, marginBottom: 12, color: '#333' }; + var reqSpecH2 = { fontSize: 14, fontWeight: 600, marginTop: 16, marginBottom: 8, color: '#333' }; + var reqSpecH3 = { fontSize: 13, fontWeight: 600, marginTop: 10, marginBottom: 6, color: '#333' }; + var reqSpecP = { fontSize: 13, lineHeight: 1.6, marginBottom: 6, color: '#555' }; + var reqSpecLi = { fontSize: 13, lineHeight: 1.6, marginBottom: 4, marginLeft: 20, color: '#555' }; + var reqSpecBlock = { marginBottom: 8 }; + var reqSpecDoc = React.createElement('div', { style: { padding: '0 4px' } }, + React.createElement('div', { style: reqSpecBlock }, React.createElement('div', { style: reqSpecH2 }, '1.面包屑:'), React.createElement('div', { style: reqSpecP }, '1.1.业务管理-车辆租赁合同-转正式合同')), + React.createElement('div', { style: reqSpecBlock }, React.createElement('div', { style: reqSpecH2 }, '2.客户基本信息卡片:'), React.createElement('div', { style: reqSpecP }, '2.1.用于从客户列表中选择客户,并将该合同绑定业务部门及业务负责人;'), React.createElement('div', { style: reqSpecLi }, '2.1.1.客户名称:必选项,选择器,支持从输入框内输入内容进行模糊搜索,从客户信息列表中选择对应客户;'), React.createElement('div', { style: reqSpecLi }, '2.1.2.客户统一信用代码:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「信用代码」字段;'), React.createElement('div', { style: reqSpecLi }, '2.1.3.客户地址:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「客户地址」字段;'), React.createElement('div', { style: reqSpecLi }, '2.1.4.客户联系人:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「客户联系人」字段;'), React.createElement('div', { style: reqSpecLi }, '2.1.5.客户电话:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「客户电话」字段;'), React.createElement('div', { style: reqSpecLi }, '2.1.6.客户电子邮箱:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「客户电子邮箱」字段;'), React.createElement('div', { style: reqSpecLi }, '2.1.7.企业名称:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「企业名称」字段;'), React.createElement('div', { style: reqSpecLi }, '2.1.8.企业电话:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「企业电话」字段;'), React.createElement('div', { style: reqSpecLi }, '2.1.9.邮寄地址:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「邮寄地址」字段;'), React.createElement('div', { style: reqSpecLi }, '2.1.10.开户银行:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「开户银行」字段;'), React.createElement('div', { style: reqSpecLi }, '2.1.11.银行账号:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「银行账号」字段;'), React.createElement('div', { style: reqSpecLi }, '2.1.12.纳税人识别号:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「纳税人识别号」字段;'), React.createElement('div', { style: reqSpecLi }, '2.1.13.业务部门:必选项,选择器,从部门表中选择该租赁合同对应部门;'), React.createElement('div', { style: reqSpecLi }, '2.1.14.业务负责人:必选项,选择器,从已选业务部门下拉取对应业务负责人,未选择业务部门时,业务负责人输入框获取焦点时进行错误提示:请先选择业务部门;'), React.createElement('div', { style: reqSpecLi }, '2.1.15.合同原件:必填项,点击上传按钮,上传本地文件,支持doc、docx、pdf等格式。已上传则显示文件名,后方为删除,删除后可重新点击上传附件进行重新上传;')), + React.createElement('div', { style: reqSpecBlock }, React.createElement('div', { style: reqSpecH2 }, '3.合同基本信息卡片:'), React.createElement('div', { style: reqSpecP }, '3.1.用于定义租赁合同基本情况和付款方式;'), React.createElement('div', { style: reqSpecLi }, '3.1.1.项目名称:必填项,输入框,用于定义该合同对应项目名称,默认提示信息"请输入项目名称";'), React.createElement('div', { style: reqSpecLi }, '3.1.2.合同编码:按照合同编码规则自动生成;'), React.createElement('div', { style: reqSpecLi }, '合同编码规则:[城市简写][合同类型][签约时间][业务部门代码][顺序流水号][签署状态]'), React.createElement('div', { style: reqSpecLi }, '3.1.2.1.地区简写:如上海为SH,嘉兴为JX;'), React.createElement('div', { style: reqSpecLi }, '3.1.2.2.合同类型:采购合同为CG、租赁合同为ZL、自营合同为ZY;'), React.createElement('div', { style: reqSpecLi }, '3.1.2.3.签约时间:显示合同签约时间,如20260216'), React.createElement('div', { style: reqSpecLi }, '3.1.2.4.业务部门代码:显示合同签约业务部门信息,如YW1代表业务1部,YW2代表业务2部;'), React.createElement('div', { style: reqSpecLi }, '3.1.2.5.顺序流水号:实施5位编号补零规则,如01235,代表是集团第1235份合同;'), React.createElement('div', { style: reqSpecLi }, '3.1.2.6.签署状态:A为正式合同,B为试用合同;'), React.createElement('div', { style: reqSpecLi }, '如编号为:JXZL20260216YW101235A,则代表嘉兴业务1部在2026年2月16日签署的租赁正式合同,在集团中为第1235份;'), React.createElement('div', { style: reqSpecLi }, '3.1.3.合同类型:必选项,选择器,合同类型分为「正式合同」「试用合同」;'), React.createElement('div', { style: reqSpecLi }, '3.1.4.生效日期:必选项,日期选择器,格式为YYYY-MM-DD,精确至天;'), React.createElement('div', { style: reqSpecLi }, '3.1.5.付款方式:必选项,付款方式分为「预付」「后付」两种;'), React.createElement('div', { style: reqSpecLi }, '3.1.5.1.如果选择预付,则每笔账单以付款时间及账单计算方式,在账单日自动提前生成下月账单,同时生成时以消息/待办提醒对应业务人员;'), React.createElement('div', { style: reqSpecLi }, '3.1.5.2.如果选择后付,则每笔账单以付款时间及账单计算方式,在账单日自动提前生成下月账单,但只在退还车时,才以消息/待办提醒合同对应业务人员;'), React.createElement('div', { style: reqSpecLi }, '3.1.6.主要车型:输入框(禁用状态),根据租赁订单信息中所选车型,自动反写入输入框并以标签形式显示,支持多车型显示;'), React.createElement('div', { style: reqSpecLi }, '3.1.7.结束日期:必选项,日期选择器,格式为YYYY-MM-DD,精确至天;'), React.createElement('div', { style: reqSpecLi }, '3.1.7.1.合同结束日期前30天将以消息提醒方式提醒;'), React.createElement('div', { style: reqSpecLi }, '3.1.8.付款周期:必选项,选择器,支持1个月-12个月 12种付款周期,账单将以此周期进行定时生成;'), React.createElement('div', { style: reqSpecLi }, '3.1.9.签约公司:必选项,选择器,从组织机构表中获取所有根组织机构(如嘉兴羚牛、上海羚牛、广东羚牛等),默认显示录入合同人员所在机构;'), React.createElement('div', { style: reqSpecLi }, '3.1.10.交车区域:必选项,地区选择器,支持省-市2级,选择区域后,该任务生成交车任务时会自动推送至该区域负责运维人员;'), React.createElement('div', { style: reqSpecLi }, '3.1.11.交车地点:输入框,支持自定义输入交车地点;'), React.createElement('div', { style: reqSpecLi }, '3.1.12.备注:文本域,支持自定义输入备注信息;')) + ); + var reqSpecDocPart2 = React.createElement('div', { style: { padding: '0 4px' } }, + React.createElement('div', { style: reqSpecBlock }, React.createElement('div', { style: reqSpecH2 }, '4.被授权人信息卡片:'), React.createElement('div', { style: reqSpecP }, '4.1.用于定义租赁合同相关被授权人相关信息,被授权人交车单完成时需要通过手机短信调取E签宝进行签字确认;'), React.createElement('div', { style: reqSpecLi }, '4.1.1.被授权人:必填项,输入框,用于输入被授权人信息;'), React.createElement('div', { style: reqSpecLi }, '4.1.2.被授权人联系电话:必填项,输入框,用于输入被授权人联系电话,该电话后续需要接收E签宝签字链接;'), React.createElement('div', { style: reqSpecLi }, '4.1.3.被授权人身份证:必填项,输入框,用于输入被授权人身份证信息;'), React.createElement('div', { style: reqSpecLi }, '4.1.4.支持通过新增/删除一行的方式,创建或管理多个授权人,后续交车单完成时可选择多个授权人;')), + React.createElement('div', { style: reqSpecBlock }, React.createElement('div', { style: reqSpecH2 }, '5.租赁订单信息卡片:'), React.createElement('div', { style: reqSpecP }, '5.1.用于定义租赁订单租赁车辆品牌、型号、月租金、服务费、保证金等相关信息;'), React.createElement('div', { style: reqSpecLi }, '5.1.1.卡片上方为租赁车辆总计数据,包括租赁车辆数、租金及服务费合计、保证金总额、氢气预付款金额等相关信息;'), React.createElement('div', { style: reqSpecLi }, '5.1.1.1.租赁车辆数:显示下方租赁订单信息包含多少辆车;'), React.createElement('div', { style: reqSpecLi }, '5.1.1.2.租金及服务费合计:显示下方租赁订单信息中车辆租金总额及服务费总额;'), React.createElement('div', { style: reqSpecLi }, '5.1.1.3.保证金总额:显示下方租赁订单信息中车辆保证金总额;'), React.createElement('div', { style: reqSpecLi }, '5.1.1.4.氢气预付款金额:显示氢气预付款金额,如客户选择自行承担氢费或羚牛承担氢费,则该处为0不计入氢气预付款金额;'), React.createElement('div', { style: reqSpecLi }, '5.1.2.下方为列表,显示序号、品牌、型号、车牌号、车辆识别代码、车辆月租金(元)、服务费项目、服务费、保证金、备注;'), React.createElement('div', { style: reqSpecLi }, '默认显示一行空数据;'), React.createElement('div', { style: reqSpecLi }, '5.1.2.1.序号:自动按照条数生成,规则为1、2、3....以此类推;'), React.createElement('div', { style: reqSpecLi }, '5.1.2.2.品牌:必选项,选择器,从型号参数库中「品牌」字段拉取所有品牌;'), React.createElement('div', { style: reqSpecLi }, '5.1.2.3.型号:必选项,选择器,从型号参数库中「型号」字段拉取所有品牌;'), React.createElement('div', { style: reqSpecLi }, '5.1.2.4.车牌号:选填项,选择器(支持从输入框输入车牌号关键字下拉匹配);'), React.createElement('div', { style: reqSpecLi }, '5.1.2.5.车辆识别代码:输入框(禁用),显示该车辆对应车辆识别代码,从车辆表直接拉取;'), React.createElement('div', { style: reqSpecLi }, '5.1.2.6.车辆月租金(元):输入框,支持两位小数,用于输入该车辆月租金金额,输入框后缀为元;'), React.createElement('div', { style: reqSpecLi }, '5.1.2.7.服务费项目:显示管理按钮,点击弹出卡片,卡片标题为:服务项目,下方列表显示服务项目、费用、生效时间、操作;'), React.createElement('div', { style: reqSpecLi }, '5.1.2.7.1.服务项目:必选项,选择器(支持输入框输入服务项目关键字进行下拉匹配),选项包含:代处理费用、罚款、违章处理违约金、未参加安全培训、车辆出险、年检年审违约、停车费、设备损坏金(包含易损件)、清洗费、上门收车人工费、上门收车送车行驶费、上门收车基础服务费、保险上浮、保养费用、补办驾驶证、补办牌照、补办营运证、补办加氢证、借用备用钥匙、补配钥匙、租金、氢气费-客、退还车氢量差、能源费补缴、能源费退款、送车上门人工费、送车上门送车行驶费、送车上门基础服务费、保证金、氢气预付费、维修费用、ETC-客、ETC卡缺损费、ETC设备缺损费、电费-客、未结算保养费、未结算维修费、车损费、工具损坏或丢失费、证件费、广告损坏费、送车服务费、接车服务费、补办行驶证、超赔险、轮胎磨损费、无忧包、轮胎保、养护保、尾板;'), React.createElement('div', { style: reqSpecLi }, '5.1.2.7.2.费用:必填项,输入框,支持2位小数,输入框后缀为元;'), React.createElement('div', { style: reqSpecLi }, '5.1.2.7.3.生效时间:必选项,日期选择器,格式为YYYY-MM-DD,服务项目会以此时间提前3天进行消息通知;'), React.createElement('div', { style: reqSpecLi }, '5.1.2.7.4.操作:删除,点击删除直接删除该行数据;'), React.createElement('div', { style: reqSpecLi }, '5.1.2.7.5.新增一行数据:点击添加一行服务项目;'), React.createElement('div', { style: reqSpecLi }, '5.1.3.氢费承担方:必选项,填充按钮组,选项为我方、客户,默认为客户,选择承担方为我方时,无需选择付款方式、输入氢气预付款;'), React.createElement('div', { style: reqSpecLi }, '5.1.4.付款方式:必选项,填充按钮组,选项为预付、月付款、自行结算;'), React.createElement('div', { style: reqSpecLi }, '5.1.4.1.预付:指合同签署时客户就需预先付出的氢费款项;'), React.createElement('div', { style: reqSpecLi }, '5.1.4.2.月付款:指合同签署后,客户按照每月氢费实际账单,进行支付,设置为月付款时,每月账期时会提示对应业务管理中心-能源部完善氢费账单;'), React.createElement('div', { style: reqSpecLi }, '5.1.4.3.自行结算:指合同签署后,所有氢气费用由客户自行承担;'), React.createElement('div', { style: reqSpecLi }, '5.1.5.氢气预付款:必填项,输入框,支持2位小数,当付款方式为预付时,显示该输入框,氢气预付款金额会并入该合同交车前首付款中一并结算,并计入5.1.1.4.氢气预付款金额中;'), React.createElement('div', { style: reqSpecLi }, '5.1.6.退还车氢气单价:必填项,输入框,支持2位小数,后缀为元,该金额主要用于约定退还车时,与交车时氢气差值以此费用进行结算;')) + ); + var reqSpecDocPart3 = React.createElement('div', { style: { padding: '0 4px' } }, + React.createElement('div', { style: reqSpecBlock }, React.createElement('div', { style: reqSpecH2 }, '6.其他费用信息卡片:'), React.createElement('div', { style: reqSpecP }, '6.1.用于选择对应费用模板,展示证照补办费用、违约金费用、易损件费用、其他费用信息;'), React.createElement('div', { style: reqSpecLi }, '6.1.1.选择费用模板:必选项,通过选择通过费用模板预设好的费用金额明细,自动将该模板所有环节费用显示在合同中;'), React.createElement('div', { style: reqSpecLi }, '6.1.2.证照补办费用:单独标题显示,内容为列表,列表中包括项目、收费标准、服务费,选择费用模板后自动反显;'), React.createElement('div', { style: reqSpecLi }, '6.1.3.违约金费用:单独标题显示,内容为列表,列表中包含项目、收费标准、服务费,选择费用模板后自动反显;'), React.createElement('div', { style: reqSpecLi }, '6.1.4.易损件信息:单独标题显示,内容为列表,列表中包含类别、损坏部位、配件、数量、费用明细,选择费用模板后自动反显;'), React.createElement('div', { style: reqSpecLi }, '6.1.5.其他费用信息:单独标题显示,内容为列表,列表中包含项目、收费标准、服务费,选择费用模板后自动反显;')), + React.createElement('div', { style: reqSpecBlock }, React.createElement('div', { style: reqSpecH2 }, '7.账单计算方式卡片:'), React.createElement('div', { style: reqSpecP }, '7.1.必选项,填充按钮组,默认为按自然月结算,需要在两种账单计算方式二选一,可手动修改,用于定义租赁合同的账单计算方式,分为按付款周期天数结算、按自然月结算两种方式;'), React.createElement('div', { style: reqSpecLi }, '7.1.1.按付款周期天数结算:账单按照合同基本信息卡片中付款周期实际天数形成一期账单,例如付款周期为60天,则该账单账期为60天,每60天会自动生成新一期账单;'), React.createElement('div', { style: reqSpecLi }, '7.1.2.按自然月结算:账单按照第一个月计费日期开始-当月最后一天为第一期,之后按照付款周期设置,每个月第一天到对应月份最后一天的自然月方式,形成每一期账单,例如付款周期为2个月,则第二期账单从2月1日-3月31日,以此类推;')), + React.createElement('div', { style: reqSpecBlock }, React.createElement('div', { style: reqSpecH2 }, '8.最下方为提交并审核、保存、取消三个按钮;'), React.createElement('div', { style: reqSpecLi }, '8.1.点击提交并审核,toast提示:租赁合同已提交审核。同时该租赁合同进入租赁合同审核列表中;'), React.createElement('div', { style: reqSpecLi }, '8.2.点击保存,会存储租赁订单已填写内容,并加入租赁合同列表中,该条数据只能操作人自己查看并编辑,其他人无法操作;'), React.createElement('div', { style: reqSpecLi }, '8.3.点击取消,如当前页面有已编辑内容时,点击取消会进行二次提示,内容为:取消将会丢失所有已填写内容,是否确认?点击确认返回车辆租赁合同列表页;')), + React.createElement('div', { style: reqSpecBlock }, React.createElement('div', { style: reqSpecP }, '所有卡片支持收起/展开功能,通过点击卡片右侧收起/展开实现;并增加锚点功能,锚点固定于页面右上角,点击锚点对应卡片名称,页面自动跳转至该卡片所在区域;')) + ); + var reqSpecModalContent = reqSpecOpen ? React.createElement('div', { style: styles.modalMask, onClick: function(e) { if (e.target === e.currentTarget) setReqSpecOpen(false); } }, React.createElement('div', { style: Object.assign({}, styles.modalBox, { maxWidth: 640 }), onClick: function(e) { e.stopPropagation(); } }, React.createElement('div', { style: styles.modalHeader }, '需求说明'), React.createElement('div', { style: Object.assign({}, styles.modalBody, { maxHeight: '70vh', padding: '20px 24px' }) }, reqSpecDoc, reqSpecDocPart2, reqSpecDocPart3), React.createElement('div', { style: { padding: '12px 20px', borderTop: '1px solid #f0f0f0', textAlign: 'right' } }, React.createElement(Button, { onClick: function() { setReqSpecOpen(false); } }, '关闭')))) : null; + + return React.createElement('div', { style: styles.page }, + React.createElement('div', { style: { marginBottom: 16 } }, React.createElement('div', { style: styles.breadcrumb }, React.createElement('span', null, '业务管理'), React.createElement('span', { style: styles.breadcrumbSep }, ' / '), React.createElement('span', null, '车辆租赁合同'), React.createElement('span', { style: styles.breadcrumbSep }, ' / '), React.createElement('span', { style: { color: '#1890ff' } }, '转正式合同'))), + React.createElement('div', { style: styles.anchorWrap }, + React.createElement('div', { style: { marginBottom: 8, fontWeight: 600, fontSize: 14 } }, '锚点导航'), + React.createElement('button', { style: styles.anchorItem, onClick: function() { scrollToCard('card-customer'); } }, '客户基本信息'), + React.createElement('button', { style: styles.anchorItem, onClick: function() { scrollToCard('card-contract'); } }, '合同基本信息'), + React.createElement('button', { style: styles.anchorItem, onClick: function() { scrollToCard('card-authorized'); } }, '被授权人信息'), + React.createElement('button', { style: styles.anchorItem, onClick: function() { scrollToCard('card-rental'); } }, '租赁订单信息'), + React.createElement('button', { style: styles.anchorItem, onClick: function() { scrollToCard('card-fee'); } }, '其他费用信息'), + React.createElement('button', { style: styles.anchorItem, onClick: function() { scrollToCard('card-billing'); } }, '账单计算方式') + ), + React.createElement('div', { id: 'card-customer' }, React.createElement(CardBlock, { id: 'card-customer', title: '客户基本信息', collapsed: cc1, setCollapsed: setCc1 }, customerFields)), + React.createElement('div', { id: 'card-contract', style: { marginTop: 16 } }, React.createElement(CardBlock, { title: '合同基本信息', collapsed: cc2, setCollapsed: setCc2 }, React.createElement('div', null, contractFormRow1, contractFormRow2))), + React.createElement('div', { id: 'card-authorized', style: { marginTop: 16 } }, React.createElement(CardBlock, { title: '被授权人信息', collapsed: cc3, setCollapsed: setCc3 }, authorizedContent)), + React.createElement('div', { id: 'card-rental', style: { marginTop: 16 } }, React.createElement(CardBlock, { title: '租赁订单信息', collapsed: cc4, setCollapsed: setCc4 }, rentalContent)), + React.createElement('div', { id: 'card-fee', style: { marginTop: 16 } }, React.createElement(CardBlock, { title: '其他费用信息', collapsed: cc5, setCollapsed: setCc5 }, feeContent)), + React.createElement('div', { id: 'card-billing', style: { marginTop: 16 } }, React.createElement(CardBlock, { title: '账单计算方式', collapsed: cc6, setCollapsed: setCc6 }, billingContent)), + React.createElement('div', { style: { height: 60 } }), + serviceModalContent, + reqSpecModalContent, + React.createElement('div', { style: styles.footer }, React.createElement(Button, { type: 'primary', onClick: function() { if (validateSubmitAndReview()) { message.success('租赁合同已提交审核。'); } } }, '提交并审核'), React.createElement(Button, { onClick: function() { message.info('保存,加入租赁合同列表(仅操作人可查看编辑)'); } }, '保存'), React.createElement(Button, { onClick: function() { message.info('取消'); } }, '取消')) + ); +}; diff --git a/web端/车辆管理.jsx b/web端/车辆管理.jsx new file mode 100644 index 0000000..ed7c405 --- /dev/null +++ b/web端/车辆管理.jsx @@ -0,0 +1,591 @@ +// 【重要】必须使用 const Component 作为组件变量名 +// 车辆资产管理 - 车辆管理模块(中后台 Ant Design 原型) +const Component = function () { + var useState = React.useState; + var useMemo = React.useMemo; + var useCallback = React.useCallback; + var antd = window.antd; + + var Breadcrumb = antd.Breadcrumb; + var Cascader = antd.Cascader; + var Select = antd.Select; + var Input = antd.Input; + var Button = antd.Button; + var Table = antd.Table; + var Space = antd.Space; + var Dropdown = antd.Dropdown; + var Modal = antd.Modal; + var Upload = antd.Upload; + var Card = antd.Card; + var Tabs = antd.Tabs; + var message = antd.message; + var App = antd.App; + + // 筛选项状态 + var _region = useState([]); + var _vehicleType = useState(undefined); + var _brand = useState(undefined); + var _model = useState(undefined); + var _customer = useState(undefined); + var _department = useState(undefined); + var _contractNo = useState(undefined); + var _ownership = useState(undefined); + + var _plateFilter = useState(''); + var _selectedRowKeys = useState([]); + var _uploadModalVisible = useState(false); + var _confirmModalVisible = useState(false); + var _ocrLoadingVisible = useState(false); + var _currentRow = useState(null); + var _plateForm = useState({ vin: '', plateNo: '' }); + var _plateError = useState(''); + var _detailRecord = useState(null); + var _detailCardExpanded = useState(false); + var _detailTab = useState('型号参数'); + var _filterExpanded = useState(false); + var _requirementModalVisible = useState(false); + + // 省-市 地区数据(示例) + var regionOptions = [ + { value: 'guangdong', label: '广东省', children: [{ value: 'guangzhou', label: '广州市' }, { value: 'shenzhen', label: '深圳市' }] }, + { value: 'beijing', label: '北京市', children: [{ value: 'beijing', label: '北京市' }] }, + { value: 'shanghai', label: '上海市', children: [{ value: 'shanghai', label: '上海市' }] } + ]; + + // 车辆类型、品牌、型号、客户、部门、合同、登记所有权(模拟下拉数据) + var vehicleTypeOptions = [{ label: '小型轿车', value: 'type1' }, { label: 'SUV', value: 'type2' }, { label: '厢式货车', value: 'type3' }]; + var brandOptions = [{ label: '比亚迪', value: 'byd' }, { label: '特斯拉', value: 'tsl' }, { label: '蔚来', value: 'nio' }]; + var modelOptions = [{ label: '汉EV', value: 'han' }, { label: 'Model 3', value: 'm3' }, { label: 'ET5', value: 'et5' }]; + var customerOptions = [{ label: '无', value: 'none' }, { label: '客户A', value: 'c1' }, { label: '客户B', value: 'c2' }]; + var departmentOptions = [{ label: '无', value: 'none' }, { label: '华南区', value: 'd1' }, { label: '华东区', value: 'd2' }]; + var contractOptions = [{ label: 'HT-2024-001', value: 'HT-2024-001' }, { label: 'HT-2024-002', value: 'HT-2024-002' }]; + var ownershipOptions = [{ label: '某某租赁公司', value: 'o1' }, { label: '某某科技有限公司', value: 'o2' }]; + + // 表格数据(模拟 20 条,状态按产品枚举) + // 运营状态: 待运营、库存、租赁、自营、退出运营 + // 库位状态: 新车入库-待验车、新车入库-证照办理、库存车-可交付车、库存车-不可交付车、库存车-呆滞车、已交付车-租赁交车、已交付车-自营交车、已交付车-替换交车、退出运营-报废车、退出运营-三方退租车、退出运营-过户售车 + // 出库状态: 异动出库、调拨出库、展示出库、租赁交车、自营交车、替换交车、过户售车、外租退车、报废出库、无 + // 预占状态: 331 后迭代,暂用「未预占」 + // 整备状态: 待整备、整备中、正常、无 + // 过户状态: 过户中、内部过户完成、销售过户完成、无 + // 维修状态: 待服务站接单、维修中、正常 + // 证照状态: 正常、异常 + // 报废状态: 报废中、已报废、无 + var rawData = [ + { id: '1', region: '广东省/广州市', vin: 'LGWEF4A59NS123456', plateNo: '粤A12345', vehicleNo: 'V001', vehicleType: '小型轿车', brand: '比亚迪', model: '汉EV', color: '白色', parking: '天河停车场', customer: '客户A', department: '华南区', manager: '张三', operateStatus: '租赁', storageStatus: '已交付车-租赁交车', outStatus: '租赁交车', preemptStatus: '未预占', prepareStatus: '正常', transferStatus: '无', repairStatus: '正常', licenseStatus: '正常', scrapStatus: '无', ownership: '某某租赁公司', onlineStatus: '在线', year: '2023', mileage: '12580.50', purchaseDate: '2023-06-15', regDate: '2023-07-01', inspectExpire: '2025-07', lastDeliveryTime: '2024-01-10', lastDeliveryMile: '12000.00', lastReturnTime: '2024-02-01', lastReturnMile: '12580.50', scrapDate: '2038-12-31', contractNo: 'HT-2024-001', location: '广东省广州市天河区天河路100号', gpsTime: '2024-02-12 14:30' }, + { id: '2', region: '广东省/深圳市', vin: 'LGWEF4A59NS789012', plateNo: '粤B67890', vehicleNo: '-', vehicleType: 'SUV', brand: '特斯拉', model: 'Model 3', color: '黑色', parking: '-', customer: '客户B', department: '华南区', manager: '李四', operateStatus: '库存', storageStatus: '库存车-可交付车', outStatus: '无', preemptStatus: '未预占', prepareStatus: '待整备', transferStatus: '无', repairStatus: '正常', licenseStatus: '正常', scrapStatus: '无', ownership: '某某科技有限公司', onlineStatus: '离线', year: '2022', mileage: '25600.00', purchaseDate: '2022-08-20', regDate: '2022-09-01', inspectExpire: '2024-09', lastDeliveryTime: '2024-01-05', lastDeliveryMile: '25500.00', lastReturnTime: '2024-01-20', lastReturnMile: '25600.00', scrapDate: '2037-09-30', contractNo: '-', location: '广东省深圳市南山区科技园南路', gpsTime: '2024-02-11 09:00' }, + { id: '3', region: '广东省/广州市', vin: 'LSJA24U70PS001234', plateNo: '粤A88K88', vehicleNo: 'V003', vehicleType: '小型轿车', brand: '蔚来', model: 'ET5', color: '灰色', parking: '黄埔停车场', customer: '客户A', department: '华南区', manager: '王五', operateStatus: '租赁', storageStatus: '已交付车-租赁交车', outStatus: '租赁交车', preemptStatus: '未预占', prepareStatus: '正常', transferStatus: '无', repairStatus: '正常', licenseStatus: '正常', scrapStatus: '无', ownership: '某某租赁公司', onlineStatus: '在线', year: '2023', mileage: '8320.00', purchaseDate: '2023-09-01', regDate: '2023-09-20', inspectExpire: '2025-09', lastDeliveryTime: '2024-02-05', lastDeliveryMile: '8100.00', lastReturnTime: '2024-02-10', lastReturnMile: '8320.00', scrapDate: '2039-09-30', contractNo: 'HT-2024-002', location: '广东省广州市黄埔区开泰大道200号', gpsTime: '2024-02-12 10:15' }, + { id: '4', region: '北京市/北京市', vin: 'WVWZZZ3CZWE123456', plateNo: '京C12345', vehicleNo: '-', vehicleType: 'SUV', brand: '特斯拉', model: 'Model Y', color: '蓝色', parking: '朝阳停车场', customer: '无', department: '无', manager: '-', operateStatus: '自营', storageStatus: '已交付车-自营交车', outStatus: '自营交车', preemptStatus: '未预占', prepareStatus: '整备中', transferStatus: '无', repairStatus: '维修中', licenseStatus: '正常', scrapStatus: '无', ownership: '某某科技有限公司', onlineStatus: '离线', year: '2022', mileage: '18900.25', purchaseDate: '2022-11-10', regDate: '2022-12-01', inspectExpire: '2024-12', lastDeliveryTime: '2024-01-15', lastDeliveryMile: '18500.00', lastReturnTime: '2024-01-28', lastReturnMile: '18900.25', scrapDate: '2037-12-31', contractNo: '-', location: '北京市朝阳区望京街88号', gpsTime: '2024-02-10 16:00' }, + { id: '5', region: '上海市/上海市', vin: 'LSVAU2BR3NS567890', plateNo: '沪D66666', vehicleNo: 'V005', vehicleType: '厢式货车', brand: '比亚迪', model: '汉EV', color: '白色', parking: '-', customer: '客户C', department: '华东区', manager: '赵六', operateStatus: '租赁', storageStatus: '已交付车-租赁交车', outStatus: '租赁交车', preemptStatus: '未预占', prepareStatus: '正常', transferStatus: '无', repairStatus: '正常', licenseStatus: '异常', scrapStatus: '无', ownership: '某某租赁公司', onlineStatus: '在线', year: '2021', mileage: '45200.80', purchaseDate: '2021-05-20', regDate: '2021-06-15', inspectExpire: '2024-06', lastDeliveryTime: '2024-02-01', lastDeliveryMile: '44800.00', lastReturnTime: '2024-02-08', lastReturnMile: '45200.80', scrapDate: '2036-06-30', contractNo: 'HT-2024-003', location: '上海市浦东新区张江高科路500号', gpsTime: '2024-02-12 09:45' }, + { id: '6', region: '广东省/深圳市', vin: '5YJ3E1EA1NF123456', plateNo: '粤B12345', vehicleNo: '-', vehicleType: '小型轿车', brand: '特斯拉', model: 'Model 3', color: '红色', parking: '福田停车场', customer: '客户B', department: '华南区', manager: '李四', operateStatus: '租赁', storageStatus: '已交付车-租赁交车', outStatus: '租赁交车', preemptStatus: '未预占', prepareStatus: '正常', transferStatus: '无', repairStatus: '正常', licenseStatus: '正常', scrapStatus: '无', ownership: '某某租赁公司', onlineStatus: '在线', year: '2023', mileage: '5600.00', purchaseDate: '2023-03-08', regDate: '2023-04-01', inspectExpire: '2025-04', lastDeliveryTime: '2024-02-02', lastDeliveryMile: '5200.00', lastReturnTime: '2024-02-11', lastReturnMile: '5600.00', scrapDate: '2038-04-30', contractNo: 'HT-2024-004', location: '广东省深圳市福田区福华路188号', gpsTime: '2024-02-12 11:20' }, + { id: '7', region: '广东省/广州市', vin: 'LGWEF4A59NS234567', plateNo: '粤A99A99', vehicleNo: 'V007', vehicleType: '小型轿车', brand: '比亚迪', model: '汉EV', color: '黑色', parking: '天河停车场', customer: '客户A', department: '华南区', manager: '张三', operateStatus: '库存', storageStatus: '库存车-不可交付车', outStatus: '无', preemptStatus: '未预占', prepareStatus: '正常', transferStatus: '无', repairStatus: '正常', licenseStatus: '正常', scrapStatus: '无', ownership: '某某租赁公司', onlineStatus: '离线', year: '2022', mileage: '22100.30', purchaseDate: '2022-07-15', regDate: '2022-08-01', inspectExpire: '2024-08', lastDeliveryTime: '2024-01-20', lastDeliveryMile: '21800.00', lastReturnTime: '2024-02-05', lastReturnMile: '22100.30', scrapDate: '2037-08-31', contractNo: '-', location: '广东省广州市天河区体育西路200号', gpsTime: '2024-02-11 18:30' }, + { id: '8', region: '北京市/北京市', vin: 'WVWZZZ3CZWE789012', plateNo: '京E88888', vehicleNo: '-', vehicleType: 'SUV', brand: '蔚来', model: 'ET5', color: '绿色', parking: '-', customer: '客户D', department: '华北区', manager: '孙七', operateStatus: '自营', storageStatus: '已交付车-自营交车', outStatus: '自营交车', preemptStatus: '未预占', prepareStatus: '正常', transferStatus: '无', repairStatus: '正常', licenseStatus: '正常', scrapStatus: '无', ownership: '某某科技有限公司', onlineStatus: '在线', year: '2023', mileage: '11200.00', purchaseDate: '2023-01-12', regDate: '2023-02-01', inspectExpire: '2025-02', lastDeliveryTime: '2024-02-06', lastDeliveryMile: '11000.00', lastReturnTime: '2024-02-12', lastReturnMile: '11200.00', scrapDate: '2038-02-28', contractNo: 'HT-2024-005', location: '北京市海淀区中关村大街1号', gpsTime: '2024-02-12 08:00' }, + { id: '9', region: '上海市/上海市', vin: 'LSVAU2BR3NS111222', plateNo: '沪A12345', vehicleNo: '-', vehicleType: '小型轿车', brand: '比亚迪', model: '汉EV', color: '银色', parking: '浦东停车场', customer: '无', department: '无', manager: '-', operateStatus: '库存', storageStatus: '库存车-呆滞车', outStatus: '无', preemptStatus: '未预占', prepareStatus: '待整备', transferStatus: '无', repairStatus: '正常', licenseStatus: '正常', scrapStatus: '无', ownership: '某某租赁公司', onlineStatus: '离线', year: '2021', mileage: '38500.60', purchaseDate: '2021-10-05', regDate: '2021-11-01', inspectExpire: '2024-11', lastDeliveryTime: '2024-01-08', lastDeliveryMile: '38000.00', lastReturnTime: '2024-01-25', lastReturnMile: '38500.60', scrapDate: '2036-11-30', contractNo: '-', location: '上海市徐汇区漕溪路250号', gpsTime: '2024-02-09 14:00' }, + { id: '10', region: '广东省/深圳市', vin: '5YJ3E1EA2NF333444', plateNo: '粤B55B55', vehicleNo: 'V010', vehicleType: 'SUV', brand: '特斯拉', model: 'Model Y', color: '白色', parking: '南山停车场', customer: '客户B', department: '华南区', manager: '李四', operateStatus: '租赁', storageStatus: '已交付车-租赁交车', outStatus: '租赁交车', preemptStatus: '未预占', prepareStatus: '正常', transferStatus: '无', repairStatus: '正常', licenseStatus: '正常', scrapStatus: '无', ownership: '某某科技有限公司', onlineStatus: '在线', year: '2022', mileage: '16800.00', purchaseDate: '2022-04-18', regDate: '2022-05-10', inspectExpire: '2024-05', lastDeliveryTime: '2024-01-30', lastDeliveryMile: '16500.00', lastReturnTime: '2024-02-09', lastReturnMile: '16800.00', scrapDate: '2037-05-31', contractNo: 'HT-2024-006', location: '广东省深圳市南山区后海大道300号', gpsTime: '2024-02-12 13:10' }, + { id: '11', region: '广东省/广州市', vin: 'LSJA24U70PS555666', plateNo: '粤A11B22', vehicleNo: '-', vehicleType: '小型轿车', brand: '蔚来', model: 'ET5', color: '蓝色', parking: '番禺停车场', customer: '客户A', department: '华南区', manager: '王五', operateStatus: '租赁', storageStatus: '已交付车-替换交车', outStatus: '替换交车', preemptStatus: '未预占', prepareStatus: '正常', transferStatus: '无', repairStatus: '正常', licenseStatus: '正常', scrapStatus: '无', ownership: '某某租赁公司', onlineStatus: '在线', year: '2023', mileage: '7200.50', purchaseDate: '2023-07-20', regDate: '2023-08-05', inspectExpire: '2025-08', lastDeliveryTime: '2024-02-03', lastDeliveryMile: '7000.00', lastReturnTime: '2024-02-11', lastReturnMile: '7200.50', scrapDate: '2038-08-31', contractNo: 'HT-2024-007', location: '广东省广州市番禺区市桥街100号', gpsTime: '2024-02-12 12:00' }, + { id: '12', region: '北京市/北京市', vin: 'WVWZZZ3CZWE333555', plateNo: '京F33333', vehicleNo: '-', vehicleType: '厢式货车', brand: '比亚迪', model: '汉EV', color: '灰色', parking: '大兴停车场', customer: '客户D', department: '华北区', manager: '孙七', operateStatus: '库存', storageStatus: '库存车-不可交付车', outStatus: '无', preemptStatus: '未预占', prepareStatus: '待整备', transferStatus: '无', repairStatus: '正常', licenseStatus: '正常', scrapStatus: '无', ownership: '某某租赁公司', onlineStatus: '离线', year: '2020', mileage: '52100.00', purchaseDate: '2020-09-01', regDate: '2020-10-01', inspectExpire: '2024-10', lastDeliveryTime: '2024-01-12', lastDeliveryMile: '51800.00', lastReturnTime: '2024-01-30', lastReturnMile: '52100.00', scrapDate: '2035-10-31', contractNo: '-', location: '北京市大兴区亦庄经济开发区', gpsTime: '2024-02-08 11:00' }, + { id: '13', region: '上海市/上海市', vin: 'LSVAU2BR3NS777888', plateNo: '沪B99999', vehicleNo: 'V013', vehicleType: '小型轿车', brand: '特斯拉', model: 'Model 3', color: '黑色', parking: '-', customer: '客户C', department: '华东区', manager: '赵六', operateStatus: '租赁', storageStatus: '已交付车-租赁交车', outStatus: '租赁交车', preemptStatus: '未预占', prepareStatus: '正常', transferStatus: '内部过户完成', repairStatus: '正常', licenseStatus: '正常', scrapStatus: '无', ownership: '某某科技有限公司', onlineStatus: '在线', year: '2022', mileage: '19800.00', purchaseDate: '2022-12-01', regDate: '2023-01-05', inspectExpire: '2025-01', lastDeliveryTime: '2024-02-07', lastDeliveryMile: '19500.00', lastReturnTime: '2024-02-12', lastReturnMile: '19800.00', scrapDate: '2038-01-31', contractNo: 'HT-2024-008', location: '上海市闵行区莘庄镇莘松路88号', gpsTime: '2024-02-12 15:45' }, + { id: '14', region: '广东省/深圳市', vin: 'LGWEF4A59NS888999', plateNo: '粤B22C33', vehicleNo: '-', vehicleType: 'SUV', brand: '比亚迪', model: '汉EV', color: '白色', parking: '龙岗停车场', customer: '客户B', department: '华南区', manager: '李四', operateStatus: '租赁', storageStatus: '已交付车-租赁交车', outStatus: '租赁交车', preemptStatus: '未预占', prepareStatus: '正常', transferStatus: '无', repairStatus: '正常', licenseStatus: '正常', scrapStatus: '无', ownership: '某某租赁公司', onlineStatus: '在线', year: '2023', mileage: '4200.00', purchaseDate: '2023-10-10', regDate: '2023-11-01', inspectExpire: '2025-11', lastDeliveryTime: '2024-02-04', lastDeliveryMile: '4000.00', lastReturnTime: '2024-02-10', lastReturnMile: '4200.00', scrapDate: '2038-11-30', contractNo: 'HT-2024-009', location: '广东省深圳市龙岗区龙城大道500号', gpsTime: '2024-02-12 10:30' }, + { id: '15', region: '广东省/广州市', vin: 'LSJA24U70PS000111', plateNo: '粤A66D66', vehicleNo: 'V015', vehicleType: '小型轿车', brand: '蔚来', model: 'ET5', color: '红色', parking: '天河停车场', customer: '无', department: '无', manager: '-', operateStatus: '待运营', storageStatus: '新车入库-待验车', outStatus: '无', preemptStatus: '未预占', prepareStatus: '无', transferStatus: '无', repairStatus: '正常', licenseStatus: '异常', scrapStatus: '无', ownership: '某某科技有限公司', onlineStatus: '离线', year: '2021', mileage: '31200.40', purchaseDate: '2021-03-15', regDate: '2021-04-01', inspectExpire: '2024-04', lastDeliveryTime: '2024-01-18', lastDeliveryMile: '30800.00', lastReturnTime: '2024-02-02', lastReturnMile: '31200.40', scrapDate: '2036-04-30', contractNo: '-', location: '广东省广州市越秀区中山五路66号', gpsTime: '2024-02-07 09:00' }, + { id: '16', region: '北京市/北京市', vin: '5YJ3E1EA3NF222333', plateNo: '京G12345', vehicleNo: '-', vehicleType: '小型轿车', brand: '特斯拉', model: 'Model 3', color: '银色', parking: '西城停车场', customer: '客户D', department: '华北区', manager: '孙七', operateStatus: '租赁', storageStatus: '已交付车-租赁交车', outStatus: '租赁交车', preemptStatus: '未预占', prepareStatus: '正常', transferStatus: '无', repairStatus: '正常', licenseStatus: '正常', scrapStatus: '无', ownership: '某某租赁公司', onlineStatus: '在线', year: '2023', mileage: '9800.00', purchaseDate: '2023-05-01', regDate: '2023-05-20', inspectExpire: '2025-05', lastDeliveryTime: '2024-02-01', lastDeliveryMile: '9500.00', lastReturnTime: '2024-02-11', lastReturnMile: '9800.00', scrapDate: '2038-05-31', contractNo: 'HT-2024-010', location: '北京市西城区金融街28号', gpsTime: '2024-02-12 14:00' }, + { id: '17', region: '上海市/上海市', vin: 'LGWEF4A59NS444555', plateNo: '沪C11111', vehicleNo: '-', vehicleType: '小型轿车', brand: '比亚迪', model: '汉EV', color: '黑色', parking: '虹口停车场', customer: '客户C', department: '华东区', manager: '赵六', operateStatus: '库存', storageStatus: '库存车-可交付车', outStatus: '无', preemptStatus: '未预占', prepareStatus: '正常', transferStatus: '无', repairStatus: '正常', licenseStatus: '正常', scrapStatus: '无', ownership: '某某租赁公司', onlineStatus: '离线', year: '2022', mileage: '24500.00', purchaseDate: '2022-02-28', regDate: '2022-03-15', inspectExpire: '2024-03', lastDeliveryTime: '2024-01-22', lastDeliveryMile: '24200.00', lastReturnTime: '2024-02-06', lastReturnMile: '24500.00', scrapDate: '2037-03-31', contractNo: '-', location: '上海市虹口区四川北路1688号', gpsTime: '2024-02-11 17:00' }, + { id: '18', region: '广东省/深圳市', vin: 'LSVAU2BR3NS666777', plateNo: '粤B44E44', vehicleNo: 'V018', vehicleType: '厢式货车', brand: '比亚迪', model: '汉EV', color: '灰色', parking: '-', customer: '客户B', department: '华南区', manager: '李四', operateStatus: '租赁', storageStatus: '已交付车-租赁交车', outStatus: '租赁交车', preemptStatus: '未预占', prepareStatus: '正常', transferStatus: '无', repairStatus: '待服务站接单', licenseStatus: '正常', scrapStatus: '无', ownership: '某某科技有限公司', onlineStatus: '在线', year: '2020', mileage: '67800.25', purchaseDate: '2020-06-10', regDate: '2020-07-01', inspectExpire: '2024-07', lastDeliveryTime: '2024-01-25', lastDeliveryMile: '67500.00', lastReturnTime: '2024-02-09', lastReturnMile: '67800.25', scrapDate: '2035-07-31', contractNo: 'HT-2024-011', location: '广东省深圳市宝安区新安街道创业路', gpsTime: '2024-02-12 08:20' }, + { id: '19', region: '广东省/广州市', vin: '5YJ3E1EA4NF888999', plateNo: '粤A77F77', vehicleNo: '-', vehicleType: 'SUV', brand: '特斯拉', model: 'Model Y', color: '蓝色', parking: '白云停车场', customer: '客户A', department: '华南区', manager: '张三', operateStatus: '租赁', storageStatus: '已交付车-租赁交车', outStatus: '租赁交车', preemptStatus: '未预占', prepareStatus: '正常', transferStatus: '过户中', repairStatus: '正常', licenseStatus: '正常', scrapStatus: '无', ownership: '某某租赁公司', onlineStatus: '在线', year: '2023', mileage: '3500.00', purchaseDate: '2023-11-05', regDate: '2023-11-25', inspectExpire: '2025-11', lastDeliveryTime: '2024-02-08', lastDeliveryMile: '3200.00', lastReturnTime: '2024-02-12', lastReturnMile: '3500.00', scrapDate: '2038-11-30', contractNo: 'HT-2024-012', location: '广东省广州市白云区白云大道南888号', gpsTime: '2024-02-12 16:00' }, + { id: '20', region: '北京市/北京市', vin: 'LSJA24U70PS999000', plateNo: '京H88888', vehicleNo: '-', vehicleType: '小型轿车', brand: '蔚来', model: 'ET5', color: '白色', parking: '昌平停车场', customer: '客户D', department: '华北区', manager: '孙七', operateStatus: '退出运营', storageStatus: '退出运营-报废车', outStatus: '报废出库', preemptStatus: '未预占', prepareStatus: '无', transferStatus: '过户中', repairStatus: '正常', licenseStatus: '正常', scrapStatus: '报废中', ownership: '某某科技有限公司', onlineStatus: '离线', year: '2022', mileage: '15600.00', purchaseDate: '2022-06-20', regDate: '2022-07-10', inspectExpire: '2024-07', lastDeliveryTime: '2024-01-10', lastDeliveryMile: '15300.00', lastReturnTime: '2024-01-28', lastReturnMile: '15600.00', scrapDate: '2037-07-31', contractNo: '-', location: '北京市昌平区回龙观西大街100号', gpsTime: '2024-02-10 12:30' } + ]; + + var dataSource = useMemo(function () { + var plate = _plateFilter[0]; + if (!plate || plate.trim() === '') return rawData; + return rawData.filter(function (row) { + return row.plateNo && row.plateNo.indexOf(plate) !== -1; + }); + }, [rawData, _plateFilter[0]]); + + var onPlateFilterChange = useCallback(function (e) { + _plateFilter[1](e.target.value); + }, []); + + var onSelectChange = useCallback(function (keys) { + _selectedRowKeys[1](keys); + }, []); + + var rowSelection = { + selectedRowKeys: _selectedRowKeys[0], + onChange: onSelectChange + }; + + var handleExport = useCallback(function () { + if (_selectedRowKeys[0].length === 0) { + message.warning('请先勾选要导出的记录'); + return; + } + message.success('导出功能(联动多选):已选 ' + _selectedRowKeys[0].length + ' 条'); + }, []); + + var handleBatchImport = useCallback(function () { + message.info('批量导入:请上传文件(原型演示)'); + }, []); + + var goDetail = useCallback(function (record) { + _detailRecord[1](record); + }, []); + + var backToList = useCallback(function () { + _detailRecord[1](null); + }, []); + + var openUploadModal = useCallback(function (record) { + _currentRow[1](record); + _uploadModalVisible[1](true); + _confirmModalVisible[1](false); + _plateForm[1]({ vin: record.vin || '', plateNo: record.plateNo || '' }); + _plateError[1](''); + }, []); + + var closeUploadModal = useCallback(function () { + _uploadModalVisible[1](false); + _currentRow[1](null); + }, []); + + var startOcrThenConfirm = useCallback(function () { + _uploadModalVisible[1](false); + _ocrLoadingVisible[1](true); + setTimeout(function () { + _ocrLoadingVisible[1](false); + _confirmModalVisible[1](true); + }, 1500); + }, []); + + var closeConfirmModal = useCallback(function () { + _confirmModalVisible[1](false); + _currentRow[1](null); + _plateForm[1]({ vin: '', plateNo: '' }); + _plateError[1](''); + }, []); + + var onPlateFormChange = useCallback(function (field, value) { + _plateError[1](''); + _plateForm[1](function (prev) { + var next = {}; + next[field] = value; + return Object.assign({}, prev, next); + }); + }, []); + + var confirmPlate = useCallback(function () { + var row = _currentRow[0]; + var form = _plateForm[0]; + if (!row) return; + var vinMatch = form.vin && row.vin && form.vin.trim() === row.vin.trim(); + if (!form.vin || !form.plateNo) { + _plateError[1]('请填写车辆识别代号与车牌号'); + return; + } + if (!vinMatch) { + _plateError[1]('车辆识别代号与该车辆不匹配'); + return; + } + message.success('上牌信息已更新(原型演示)'); + closeConfirmModal(); + }, []); + + var getMoreMenuItems = function (record) { + return [ + { key: 'plate', label: '车辆上牌', onClick: function () { openUploadModal(record); } }, + { key: 'transfer', label: '车辆过户', onClick: function () { message.info('车辆过户(原型)'); } }, + { key: 'move', label: '车辆异动', onClick: function () { message.info('车辆异动(原型)'); } }, + { key: 'allocate', label: '车辆调拨', onClick: function () { message.info('车辆调拨(原型)'); } }, + { key: 'scrap', label: '车辆报废', onClick: function () { message.info('车辆报废(原型)'); } }, + { key: 'inspect', label: '车辆年审', onClick: function () { message.info('车辆年审(原型)'); } }, + { key: 'sell', label: '车辆出售', onClick: function () { message.info('车辆出售(原型)'); } }, + { key: 'fault', label: '故障提报', onClick: function () { message.info('故障提报(原型)'); } } + ]; + }; + + var columns = [ + { title: '运营城市', dataIndex: 'region', key: 'region', width: 140, fixed: 'left' }, + { title: '车辆识别代号', dataIndex: 'vin', key: 'vin', width: 180, fixed: 'left' }, + { title: '车牌号', dataIndex: 'plateNo', key: 'plateNo', width: 110, fixed: 'left' }, + { title: '车辆编号', dataIndex: 'vehicleNo', key: 'vehicleNo', width: 100 }, + { title: '车辆类型', dataIndex: 'vehicleType', key: 'vehicleType', width: 100 }, + { title: '品牌', dataIndex: 'brand', key: 'brand', width: 90 }, + { title: '型号', dataIndex: 'model', key: 'model', width: 100 }, + { title: '车身颜色', dataIndex: 'color', key: 'color', width: 90 }, + { title: '归属停车场', dataIndex: 'parking', key: 'parking', width: 120 }, + { title: '客户名称', dataIndex: 'customer', key: 'customer', width: 100 }, + { title: '业务部门', dataIndex: 'department', key: 'department', width: 100 }, + { title: '业务负责人', dataIndex: 'manager', key: 'manager', width: 100 }, + { title: '运营状态', dataIndex: 'operateStatus', key: 'operateStatus', width: 90 }, + { title: '库位状态', dataIndex: 'storageStatus', key: 'storageStatus', width: 180 }, + { title: '出库状态', dataIndex: 'outStatus', key: 'outStatus', width: 90 }, + { title: '整备状态', dataIndex: 'prepareStatus', key: 'prepareStatus', width: 90 }, + { title: '过户状态', dataIndex: 'transferStatus', key: 'transferStatus', width: 90 }, + { title: '维修状态', dataIndex: 'repairStatus', key: 'repairStatus', width: 90 }, + { title: '证照状态', dataIndex: 'licenseStatus', key: 'licenseStatus', width: 90 }, + { title: '报废状态', dataIndex: 'scrapStatus', key: 'scrapStatus', width: 90 }, + { title: '登记所有权', dataIndex: 'ownership', key: 'ownership', width: 160 }, + { title: '在线状态', dataIndex: 'onlineStatus', key: 'onlineStatus', width: 90 }, + { title: '出厂年份', dataIndex: 'year', key: 'year', width: 90 }, + { title: '行驶公里数(KM)', dataIndex: 'mileage', key: 'mileage', width: 130 }, + { title: '采购入库时间', dataIndex: 'purchaseDate', key: 'purchaseDate', width: 120 }, + { title: '行驶证注册日期', dataIndex: 'regDate', key: 'regDate', width: 130 }, + { title: '行驶证检验有效期', dataIndex: 'inspectExpire', key: 'inspectExpire', width: 130 }, + { title: '上次交车时间', dataIndex: 'lastDeliveryTime', key: 'lastDeliveryTime', width: 120 }, + { title: '上次交车里程(KM)', dataIndex: 'lastDeliveryMile', key: 'lastDeliveryMile', width: 140 }, + { title: '上次还车时间', dataIndex: 'lastReturnTime', key: 'lastReturnTime', width: 120 }, + { title: '上次还车里程(KM)', dataIndex: 'lastReturnMile', key: 'lastReturnMile', width: 140 }, + { title: '强制报废日期', dataIndex: 'scrapDate', key: 'scrapDate', width: 120 }, + { title: '合同编号', dataIndex: 'contractNo', key: 'contractNo', width: 120 }, + { title: '当前位置', dataIndex: 'location', key: 'location', width: 300 }, + { title: 'GPS最后上传时间', dataIndex: 'gpsTime', key: 'gpsTime', width: 160 }, + { + title: '操作', + key: 'action', + width: 140, + fixed: 'right', + render: function (_, record) { + return React.createElement(Space, null, + React.createElement(Button, { type: 'link', size: 'small', onClick: function () { goDetail(record); } }, '查看'), + React.createElement(Dropdown, { menu: { items: getMoreMenuItems(record) }, trigger: ['click'] }, + React.createElement(Button, { type: 'link', size: 'small' }, '更多') + ) + ); + } + } + ]; + + var filterControlStyle = { width: '100%' }; + var layoutStyle = { padding: '16px 24px', background: '#fff', minHeight: '100vh' }; + var filterLabelStyle = { marginBottom: 6, fontSize: 14, color: 'rgba(0,0,0,0.65)' }; + var filterItemStyle = { marginBottom: 12 }; + var toolbarStyle = { marginBottom: 12, display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 8 }; + var tableSingleLineStyle = '.vehicle-mgmt-table .ant-table-thead th,.vehicle-mgmt-table .ant-table-tbody td{white-space:nowrap;}'; + + // 车辆详情页:卡片标题、默认只显示前三列+收起/展开,下方 Tab+内容卡片 + var detailRecord = _detailRecord[0]; + if (detailRecord) { + var r = detailRecord; + var labelStyle = { color: 'rgba(0,0,0,0.45)', fontSize: 14, flexShrink: 0, minWidth: 100, textAlign: 'right' }; + var valueStyle = { color: 'rgba(0,0,0,0.85)', fontSize: 14, textAlign: 'left' }; + var linkStyle = { color: '#1890ff', cursor: 'pointer', fontSize: 14 }; + var cellStyle = { marginBottom: 16, minHeight: 22, display: 'flex', alignItems: 'center', gap: 8 }; + var link = function (text, onClick) { return React.createElement('a', { style: linkStyle, onClick: onClick }, text); }; + var detailCardStyle = { background: '#fff', borderRadius: 8, padding: 24, marginTop: 16, boxShadow: '0 1px 2px rgba(0,0,0,0.03)' }; + var gridColStyle = { flex: '1', minWidth: 180 }; + var field = function (label, val) { return React.createElement('div', { style: cellStyle }, React.createElement('span', { style: labelStyle }, label), React.createElement('span', { style: valueStyle }, val)); }; + var fieldLink = function (label, text, onClick) { return React.createElement('div', { style: cellStyle }, React.createElement('span', { style: labelStyle }, label), React.createElement('span', { style: valueStyle }, link(text, onClick))); }; + var detailFields = [ + { label: '车架号', node: field('车架号', r.vin || '-') }, + { label: '车辆编号', node: field('车辆编号', r.vehicleNo || '-') }, + { label: '实际所有权', node: field('实际所有权', r.ownership || '-') }, + { label: '合同编号', node: r.contractNo && r.contractNo !== '-' ? fieldLink('合同编号', r.contractNo, function () { message.info('合同详情(原型)'); }) : field('合同编号', r.contractNo || '-') }, + { label: '出库状态', node: field('出库状态', r.outStatus || '-') }, + { label: '过户状态', node: field('过户状态', r.transferStatus || '-') }, + { label: '车身颜色', node: field('车身颜色', r.color || '-') }, + { label: '采购交车日期', node: field('采购交车日期', r.purchaseDate || '-') }, + { label: '登记所有权', node: field('登记所有权', r.ownership || '-') }, + { label: '客户名称', node: field('客户名称', r.customer || '-') }, + { label: '整备状态', node: field('整备状态', r.prepareStatus || '-') }, + { label: '证照状态', node: field('证照状态', r.licenseStatus || '-') }, + { label: '资源分类', node: field('资源分类', 'XXXXXXXXX') }, + { label: '出厂年份', node: field('出厂年份', r.year || '-') }, + { label: '库位状态', node: field('库位状态', r.storageStatus || '-') }, + { label: '业务部门', node: field('业务部门', r.department || '-') }, + { label: '上次整备时间', node: field('上次整备时间', r.lastDeliveryTime || '-') }, + { label: '报废状态', node: field('报废状态', r.scrapStatus || '-') }, + { label: '资产评级', node: field('资产评级', 'XXXXXXXXX') }, + { label: '等评时间', node: field('等评时间', r.regDate || '-') }, + { label: '强制报废期', node: field('强制报废期', r.scrapDate || '-') }, + { label: '停车位置', node: field('停车位置', r.parking || '-') }, + { label: '业务负责人', node: r.manager && r.manager !== '-' ? fieldLink('业务负责人', r.manager, function () { message.info('负责人(原型)'); }) : field('业务负责人', r.manager || '-') }, + { label: '维修状态', node: field('维修状态', r.repairStatus || '-') }, + { label: '运营城市', node: field('运营城市', r.region ? r.region.replace(/\//g, '-') : '-') }, + { label: '下次年检时间', node: field('下次年检时间', r.inspectExpire || '-') }, + { label: '上次维修时间', node: field('上次维修时间', r.lastDeliveryTime || '-') }, + { label: 'GPS最后上传时间', node: field('GPS最后上传时间', r.gpsTime || '-') }, + { label: '车辆当前位置', node: fieldLink('车辆当前位置', r.location || '-', function () { message.info('查看位置(原型)'); }) } + ]; + var rowCount = _detailCardExpanded[0] ? 8 : 3; + var rows = []; + for (var i = 0; i < rowCount; i++) { + var rowCells = []; + for (var j = 0; j < 4; j++) { + var idx = i * 4 + j; + rowCells.push(React.createElement('div', { key: idx, style: gridColStyle }, detailFields[idx] ? detailFields[idx].node : null)); + } + rows.push(React.createElement('div', { key: i, style: { display: 'flex', gap: 24, marginBottom: i < rowCount - 1 ? 16 : 0 } }, rowCells)); + } + var tabContentCardStyle = { background: '#fff', borderRadius: 8, padding: 24, marginTop: 0, boxShadow: '0 1px 2px rgba(0,0,0,0.03)' }; + var tabContent = function (text) { return React.createElement('div', { style: { padding: '8px 0', color: 'rgba(0,0,0,0.65)', fontSize: 14 } }, text); }; + var sectionTitleStyle = { display: 'flex', alignItems: 'center', gap: 8, marginBottom: 16, fontSize: 15, fontWeight: 600, color: 'rgba(0,0,0,0.85)' }; + var sectionIconStyle = { width: 6, height: 6, borderRadius: 3, backgroundColor: '#1890ff', flexShrink: 0 }; + var kvLabelStyle = { color: 'rgba(0,0,0,0.45)', fontSize: 14, flexShrink: 0, minWidth: 90, textAlign: 'right' }; + var kvValueStyle = { color: 'rgba(0,0,0,0.85)', fontSize: 14, textAlign: 'left' }; + var kvItem = function (label, value, badge) { + var valNode = value; + if (badge) valNode = React.createElement('span', null, value, React.createElement('span', { style: { marginLeft: 6, padding: '0 6px', fontSize: 12, background: '#faad14', color: '#fff', borderRadius: 2 } }, badge)); + return React.createElement('div', { key: label, style: { display: 'flex', alignItems: 'center', gap: 8 } }, React.createElement('span', { style: kvLabelStyle }, label), React.createElement('span', { style: kvValueStyle }, valNode)); + }; + var modelParamRowStyle = { display: 'flex', gap: 24, marginBottom: 16 }; + var fourColRows = function (nodes) { + var out = []; + for (var i = 0; i < nodes.length; i += 4) { + var rowCells = []; + for (var j = 0; j < 4; j++) { + var idx = i + j; + rowCells.push(React.createElement('div', { key: idx, style: gridColStyle }, nodes[idx] || null)); + } + out.push(React.createElement('div', { key: i, style: modelParamRowStyle }, rowCells)); + } + return out; + }; + var maintenanceTableData = [ + { key: '1', no: 1, item: '变速器油', kmCycle: '60000', monthCycle: '24', labor: '0', material: '571', total: '571', lastKm: '' }, + { key: '2', no: 2, item: '变速器油', kmCycle: '60000', monthCycle: '24', labor: '0', material: '571', total: '571', lastKm: '' }, + { key: '3', no: 3, item: '变速器油', kmCycle: '60000', monthCycle: '24', labor: '0', material: '571', total: '571', lastKm: '5000' }, + { key: '4', no: 4, item: '变速器油', kmCycle: '60000', monthCycle: '24', labor: '0', material: '571', total: '571', lastKm: '5000' }, + { key: '5', no: 5, item: '变速器油', kmCycle: '60000', monthCycle: '24', labor: '0', material: '571', total: '571', lastKm: '5000' } + ]; + var maintenanceColumns = [ + { title: '序号', dataIndex: 'no', key: 'no', width: 60 }, + { title: '养护项目', dataIndex: 'item', key: 'item', width: 120 }, + { title: '保养公里周期(km)', dataIndex: 'kmCycle', key: 'kmCycle', width: 140 }, + { title: '保养时间周期(月)', dataIndex: 'monthCycle', key: 'monthCycle', width: 130 }, + { title: '工时费(元)', dataIndex: 'labor', key: 'labor', width: 100 }, + { title: '材料费(元)', dataIndex: 'material', key: 'material', width: 100 }, + { title: '合计', dataIndex: 'total', key: 'total', width: 80 }, + { title: '上次保养公里数(KM)', dataIndex: 'lastKm', key: 'lastKm', width: 140 } + ]; + var modelParamContent = React.createElement('div', { style: tabContentCardStyle }, + React.createElement('div', { style: sectionTitleStyle }, React.createElement('span', { style: sectionIconStyle }), '型号参数'), + React.createElement('div', { style: { marginBottom: 24 } }, fourColRows([ + kvItem('品牌', r.brand || '苏龙'), + kvItem('型号', r.model || '海格牌KLQ5180XYKFCEV'), + kvItem('车辆类型', r.vehicleType || '18吨双飞翼货车'), + kvItem('燃料种类', '氢'), + kvItem('整车尺寸', '5995mm x 2145mm x 3130mm'), + kvItem('车牌颜色', '绿牌') + ])), + React.createElement('div', { style: sectionTitleStyle }, React.createElement('span', { style: sectionIconStyle }), '轮胎情况'), + React.createElement('div', { style: { marginBottom: 24 } }, fourColRows([ + kvItem('轮胎数量', '8'), + kvItem('轮胎规格', '15/80R22.5') + ])), + React.createElement('div', { style: sectionTitleStyle }, React.createElement('span', { style: sectionIconStyle }), '电气系统'), + React.createElement('div', { style: { marginBottom: 24 } }, fourColRows([ + kvItem('电池类型', '磷酸铁锂'), + kvItem('电池厂家', 'XXXXXXXXXXXXX企业名称'), + kvItem('储电量', '100000 kWh'), + kvItem('续航里程', '200 KM') + ])), + React.createElement('div', { style: sectionTitleStyle }, React.createElement('span', { style: sectionIconStyle }), '供氢系统'), + React.createElement('div', { style: { marginBottom: 24 } }, fourColRows([ + kvItem('氢瓶容量', 'xxx L'), + kvItem('仪表盘模式', 'MPa'), + kvItem('续航里程', '1000 KM'), + kvItem('供氢系统厂家', 'XXXXXXXX企业') + ])), + React.createElement('div', { style: sectionTitleStyle }, React.createElement('span', { style: sectionIconStyle }), '其他系统'), + React.createElement('div', { style: { marginBottom: 24 } }, fourColRows([ + kvItem('冷机生产企业', 'XXXXXXXX企业'), + kvItem('电堆生产企业', 'XXXXXXXX企业') + ])), + React.createElement('div', { style: sectionTitleStyle }, React.createElement('span', { style: sectionIconStyle }), '保养参数'), + React.createElement(Table, { size: 'small', rowKey: 'key', columns: maintenanceColumns, dataSource: maintenanceTableData, pagination: false }) + ); + var detailTabItems = [ + { key: '型号参数', label: '型号参数', children: modelParamContent }, + { key: '后装设备', label: '后装设备', children: React.createElement('div', { style: tabContentCardStyle }, tabContent('后装设备列表(原型占位)')) }, + { key: '证照信息', label: '证照信息', children: React.createElement('div', { style: tabContentCardStyle }, tabContent('证照信息(原型占位)')) }, + { key: '租赁记录', label: '租赁记录', children: React.createElement('div', { style: tabContentCardStyle }, tabContent('租赁记录(原型占位)')) }, + { key: '保险记录', label: '保险记录', children: React.createElement('div', { style: tabContentCardStyle }, tabContent('保险记录(原型占位)')) }, + { key: '维修记录', label: '维修记录', children: React.createElement('div', { style: tabContentCardStyle }, tabContent('维修记录(原型占位)')) }, + { key: '事故记录', label: '事故记录', children: React.createElement('div', { style: tabContentCardStyle }, tabContent('事故记录(原型占位)')) }, + { key: '故障记录', label: '故障记录', children: React.createElement('div', { style: tabContentCardStyle }, tabContent('故障记录(原型占位)')) }, + { key: '违章记录', label: '违章记录', children: React.createElement('div', { style: tabContentCardStyle }, tabContent('违章记录(原型占位)')) }, + { key: '异动记录', label: '异动记录', children: React.createElement('div', { style: tabContentCardStyle }, tabContent('异动记录(原型占位)')) }, + { key: '调拨记录', label: '调拨记录', children: React.createElement('div', { style: tabContentCardStyle }, tabContent('调拨记录(原型占位)')) }, + { key: '整备记录', label: '整备记录', children: React.createElement('div', { style: tabContentCardStyle }, tabContent('整备记录(原型占位)')) } + ]; + return React.createElement(App, null, + React.createElement('div', { style: layoutStyle }, + React.createElement(Breadcrumb, { + style: { marginBottom: 16 }, + items: [ + { title: '运维管理' }, + { title: React.createElement('a', { onClick: backToList, style: { color: '#1890ff' } }, '车辆管理') }, + { title: '车辆详情' } + ] + }), + React.createElement('div', { style: detailCardStyle }, + React.createElement('div', { style: { fontSize: 16, fontWeight: 600, color: 'rgba(0,0,0,0.85)', marginBottom: 24 } }, '车辆详情'), + React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 12, marginBottom: 24 } }, + React.createElement('span', { style: { width: 8, height: 8, borderRadius: 4, backgroundColor: r.onlineStatus === '在线' ? '#52c41a' : '#d9d9d9', flexShrink: 0 } }), + React.createElement('span', { style: { fontSize: 16, fontWeight: 600, color: 'rgba(0,0,0,0.85)' } }, r.plateNo || '-'), + React.createElement(Button, { type: 'primary', size: 'small' }, r.operateStatus || '租赁') + ), + React.createElement('div', { style: { marginBottom: 24 } }, rows), + React.createElement('div', { style: { textAlign: 'center', paddingTop: 16, marginTop: 8, borderTop: '1px solid #f0f0f0' } }, + React.createElement(Button, { type: 'link', size: 'small', onClick: function () { _detailCardExpanded[1](!_detailCardExpanded[0]); } }, _detailCardExpanded[0] ? '收起' : '展开') + ) + ), + React.createElement('div', { style: Object.assign({}, detailCardStyle, { marginTop: 24 }) }, + React.createElement(Tabs, { + activeKey: _detailTab[0], + onChange: function (key) { _detailTab[1](key); }, + items: detailTabItems + }) + ) + ) + ); + } + + var requirementText = '# 车辆管理\n一个「车辆资产管理」后台的「车辆管理」模块,功能从上到下依次为:\n\n1.面包屑:\n运维管理-车辆管理\n\n2.筛选:\n2.1.运营城市:地区选择器,支持省-市2级地区选择;\n2.2.车辆类型:选择器,根据车辆类型表拉取;\n2.3.品牌:选择器,根据型号参数表拉取品牌字段;\n2.4.型号:选择器,根据型号参数表拉取型号字段;\n2.5.客户名称:选择器,根据租赁合同/自营合同对应客户名称拉取,并包含无;\n2.6.归属业务部门:选择器,根据租赁合同/自营合同对应归属业务部门拉取,并包含无;\n2.7.合同编号:选择器,支持输入框内输入内容模糊匹配选项;\n2.8.登记所有权:选择器,支持输入框内输入内容模糊匹配选项;\n\n3.列表:\n列表左上角为车牌号筛选框,输入车牌号可快速筛选列表内记录;右侧按钮为导出(联动多选)、批量导入,字段依次为运营城市、车辆识别代号、车牌号、车辆编号、车辆类型、品牌、型号、车身颜色、归属停车场、客户名称、业务部门、业务负责人、运营状态、库位状态、出库状态、预占状态、整备状态、过户状态、维修状态、证照状态、报废状态、登记所有权、在线状态、出厂年份、行驶公里数、采购入库时间、行驶证注册日期、行驶证检验有效期、上次交车时间、上次交车里程(KM)、上次还车时间、上次还车里程(KM)、强制报废日期、合同编号、当前位置、GPS最后上传时间、操作;\n3.1.运营城市:列固定在左侧,车辆实际运营城市,通过GPS当前位置获取,只显示至省-市2级;\n3.2.车辆识别代号:列固定在左侧,显示车辆识别代号(VIN码),为唯一值,采购、外部租赁等方式录入的车架号与系统现有车架号不能重复,导入亦然;\n3.3.车牌号:列固定在左侧,显示车牌号,主要通过上牌管理功能录入;\n3.4.车辆编号:显示车辆编号,主要由旧版迁移数据获取(这批车在港口需要单独以编号记录),其他车辆无此需求;\n3.5.车辆类型:显示车辆对应车辆类型,由车辆类型表中获取;\n3.6.品牌:显示车辆对应品牌,由型号参数表-品牌字段获取;\n3.7.型号:显示车辆对应型号,由型号参数表-型号字段获取;\n3.8.车身颜色:显示车辆对应颜色,由用户采购/外部租赁车辆合同时填写车身颜色(旧版迁移数据)字段获取;\n3.9.归属停车场:显示车辆归属停车场,交车出去租赁给客户的车辆显示为-,还车或发生异动时,根据运维记录,记录最后停放的停车场,停车场从停车场表中获取;\n3.10.客户名称:显示车辆租赁/自营合同发生时,该合同对应客户名称;\n3.11.业务部门:显示车辆租赁/自营合同发生时,该合同对应业务部门;\n3.12.业务负责人:显示车辆租赁/自营合同发生时,该合同对应业务负责人;\n3.13.运营状态:分为待运营、库存、租赁、自营、退出运营\n3.14.库位状态:分为新车入库-待验车、新车入库-证照办理、库存车-可交付车、库存车-不可交付车、库存车-呆滞车、已交付车-租赁交车、已交付车-自营交车、已交付车-替换交车、退出运营-报废车、退出运营-三方退租车、退出运营-过户售车;\n3.15.出库状态:分为异动出库、调拨出库、展示出库、租赁交车、自营交车、替换交车、过户售车、外租退车、报废出库、无;\n3.16.预占状态:331前占位,该功能331后迭代;\n3.17.整备状态:分为待整备、整备中、正常、无;\n3.18.过户状态:分为过户中、内部过户完成、销售过户完成、无;\n3.19.维修状态:分为待服务站接单、维修中、正常;\n3.20.证照状态:分为正常、异常;\n3.21.报废状态:分为报废中、已报废、无;\n3.22.登记所有权:行驶证对应所有人;\n3.23.在线状态:分为在线、离线,显示GPS在线状态,根据实际对接设备取状态;\n3.24.出厂年份:格式为YYYY,根据车辆采购合同时记录的出厂年份获取;\n3.25.行驶公里数(KM):精确至2位小数,根据最近一次交车/还车/备车/异动/调拨记录时记录的行驶公里数获取;\n3.26.采购入库时间:格式为YYYY-MM-DD,根据采购合同时,采购完成车辆验车单提交时的时间;\n3.27.行驶证注册日期:格式为YYYY-MM-DD,根据上牌管理中,最后一次上传行驶证时的行驶证注册日期获取;\n3.28.行驶证检验有效期:根据行驶证(副联)中,检验记录中检验有效期获取;\n3.29.上次交车时间:格式为YYYY-MM-DD,根据最近一次交车记录完成时间获取;\n3.30.上次交车里程(KM):精确至2位小数,根据最近一次交车记录中记录的里程获取;\n3.31.上次还车日期:格式为YYYY-MM-DD,根据最近一次还车记录完成时间获取;\n3.32.上次还车里程(KM):精确至2位小数,根据最近一次还车记录中记录的里程获取;\n3.33.强制报废日期:格式为YYYY-MM-DD,根据行驶证(副联)中强制报废期获取;\n3.34.合同编号:显示当前租赁/自营合同合同编号,如无则显示为-;\n3.35.当前位置:根据车辆GPS当前定位获取,格式为xxx省xx市xx区/县xxxxx路xxxxx号xxxxxx\n3.36.GPS最后上传时间:格式为YYYY-MM-DD HH:MM,显示车辆GPS最后一次获取时间\n3.37.操作:操作列固定在右侧,显示查看、更多,悬浮更多时,显示车辆上牌、车辆过户、车辆异动、车辆调拨、车辆报废、车辆年审、车辆出售、故障提报;\n3.37.1.查看,点击跳转车辆详情页;\n3.38.2.车辆上牌,点击后上传照片,OCR识别过程中卡片提示:识别中,请勿关闭页面,之后弹出卡片,标题为确认上牌信息,左侧为行驶证照片,右侧为车辆识别代号(输入框,根据行驶证照片反写,可编辑)与车牌号(输入框,根据行驶证照片反写,可编辑),下侧为取消和确认按钮,点击确认,判断车辆识别代码与车牌号是否一致,如不一致提示车辆识别代号与该车辆不匹配,如一致则更新车牌号和行驶证照片(具体参考车辆业务-上牌管理);\n3.38.3.车辆过户:\n3.38.4.车辆异动:\n3.38.5.车辆调拨:\n3.38.6.车辆报废:\n3.38.7.车辆年审:\n3.38.8.车辆出售;\n3.38.9.故障提报:'; + + return React.createElement(App, null, + 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', onClick: function () { _requirementModalVisible[1](true); } }, '查看需求说明') + ), + + React.createElement('div', { style: { marginBottom: 16, display: 'flex', gap: 24, alignItems: 'flex-start' } }, + React.createElement('div', { + style: { + display: 'grid', + gridTemplateColumns: '1fr 1fr 1fr', + gap: '16px 24px', + alignItems: 'start', + flex: 1, + minWidth: 0 + } + }, (function () { + var filterItems = [ + React.createElement('div', { key: 'region', style: filterItemStyle }, React.createElement('div', { style: filterLabelStyle }, '运营城市'), React.createElement(Cascader, { options: regionOptions, placeholder: '请选择', style: filterControlStyle, value: _region[0].length ? _region[0] : undefined, onChange: function (v) { _region[1](v || []); } })), + React.createElement('div', { key: 'type', style: filterItemStyle }, React.createElement('div', { style: filterLabelStyle }, '车辆类型'), React.createElement(Select, { placeholder: '请选择', style: filterControlStyle, options: vehicleTypeOptions, value: _vehicleType[0], onChange: _vehicleType[1], allowClear: true })), + React.createElement('div', { key: 'brand', style: filterItemStyle }, React.createElement('div', { style: filterLabelStyle }, '品牌'), React.createElement(Select, { placeholder: '请选择', style: filterControlStyle, options: brandOptions, value: _brand[0], onChange: _brand[1], allowClear: true })), + React.createElement('div', { key: 'model', style: filterItemStyle }, React.createElement('div', { style: filterLabelStyle }, '型号'), React.createElement(Select, { placeholder: '请选择', style: filterControlStyle, options: modelOptions, value: _model[0], onChange: _model[1], allowClear: true })), + React.createElement('div', { key: 'customer', style: filterItemStyle }, React.createElement('div', { style: filterLabelStyle }, '客户名称'), React.createElement(Select, { placeholder: '请选择', style: filterControlStyle, options: customerOptions, value: _customer[0], onChange: _customer[1], allowClear: true })), + React.createElement('div', { key: 'dept', style: filterItemStyle }, React.createElement('div', { style: filterLabelStyle }, '归属业务部门'), React.createElement(Select, { placeholder: '请选择', style: filterControlStyle, options: departmentOptions, value: _department[0], onChange: _department[1], allowClear: true })), + React.createElement('div', { key: 'contract', style: filterItemStyle }, React.createElement('div', { style: filterLabelStyle }, '合同编号'), React.createElement(Select, { placeholder: '请选择', style: filterControlStyle, options: contractOptions, value: _contractNo[0], onChange: _contractNo[1], allowClear: true, showSearch: true, filterOption: function (input, opt) { return (opt.label || '').toString().toLowerCase().indexOf((input || '').toLowerCase()) !== -1; } })), + React.createElement('div', { key: 'ownership', style: filterItemStyle }, React.createElement('div', { style: filterLabelStyle }, '登记所有权'), React.createElement(Select, { placeholder: '请选择', style: filterControlStyle, options: ownershipOptions, value: _ownership[0], onChange: _ownership[1], allowClear: true, showSearch: true, filterOption: function (input, opt) { return (opt.label || '').toString().toLowerCase().indexOf((input || '').toLowerCase()) !== -1; } })) + ]; + var limit = _filterExpanded[0] ? 8 : 6; + var out = []; + for (var i = 0; i < limit && i < filterItems.length; i++) { + out.push(filterItems[i]); + } + return out; + })()), + React.createElement('div', { style: { display: 'flex', flexDirection: 'column', alignItems: 'flex-end', gap: 8, flexShrink: 0 } }, + React.createElement(Button, { type: 'primary', onClick: function () { message.info('查询(原型)'); } }, '查询'), + React.createElement(Button, { onClick: function () { + _region[1]([]); + _vehicleType[1](undefined); + _brand[1](undefined); + _model[1](undefined); + _customer[1](undefined); + _department[1](undefined); + _contractNo[1](undefined); + _ownership[1](undefined); + } }, '重置'), + React.createElement(Button, { type: 'link', size: 'small', onClick: function () { _filterExpanded[1](!_filterExpanded[0]); } }, _filterExpanded[0] ? '收起' : '展开') + ) + ), + + React.createElement('div', { style: toolbarStyle }, + React.createElement(Input.Search, { + placeholder: '输入车牌号筛选', + allowClear: true, + value: _plateFilter[0], + onChange: onPlateFilterChange, + onSearch: onPlateFilterChange, + style: { width: 220 } + }), + React.createElement(Space, null, + React.createElement(Button, { onClick: handleExport }, '导出'), + React.createElement(Button, { onClick: handleBatchImport }, '批量导入') + ) + ), + + React.createElement(React.Fragment, null, + React.createElement('style', null, tableSingleLineStyle), + React.createElement('div', { className: 'vehicle-mgmt-table' }, + React.createElement(Table, { + rowKey: 'id', + columns: columns, + dataSource: dataSource, + rowSelection: rowSelection, + scroll: { x: 3800 }, + size: 'small', + pagination: { showSizeChanger: true, showQuickJumper: true, showTotal: function (t) { return '共 ' + t + ' 条'; } } + }) + ) + ), + + // 车辆上牌:上传/识别中 + React.createElement(Modal, { + title: '上传行驶证', + open: _uploadModalVisible[0], + onCancel: closeUploadModal, + footer: [ + React.createElement(Button, { key: 'cancel', onClick: closeUploadModal }, '取消'), + React.createElement(Button, { key: 'ok', type: 'primary', onClick: startOcrThenConfirm }, '开始识别') + ] + }, React.createElement(Upload.Dragger, { accept: 'image/*', multiple: false }, React.createElement('p', null, '点击或拖拽行驶证照片到此区域上传'))), + + React.createElement(Modal, { + title: '识别中,请勿关闭页面', + open: _ocrLoadingVisible[0], + footer: null, + closable: false, + maskClosable: false + }, React.createElement('div', { style: { textAlign: 'center', padding: '24px 0' } }, '正在识别行驶证信息...')), + + React.createElement(Modal, { + title: '确认上牌信息', + open: _confirmModalVisible[0], + onCancel: closeConfirmModal, + width: 640, + footer: [ + React.createElement(Button, { key: 'cancel', onClick: closeConfirmModal }, '取消'), + React.createElement(Button, { key: 'ok', type: 'primary', onClick: confirmPlate }, '确认') + ] + }, React.createElement('div', { style: { display: 'flex', gap: 24 } }, + React.createElement('div', { style: { flex: '0 0 240px' } }, + React.createElement('div', { style: { marginBottom: 8, color: '#666' } }, '行驶证照片'), + React.createElement('img', { src: 'https://picsum.photos/240/160', alt: '行驶证', style: { width: '100%', borderRadius: 8 } }) + ), + React.createElement('div', { style: { flex: 1 } }, + React.createElement('div', { style: { marginBottom: 12 } }, + React.createElement('span', { style: { marginRight: 8 } }, '车辆识别代号'), + React.createElement(Input, { + value: _plateForm[0].vin, + onChange: function (e) { onPlateFormChange('vin', e.target.value); }, + placeholder: '根据行驶证反写,可编辑', + style: { width: '100%' } + }) + ), + React.createElement('div', { style: { marginBottom: 12 } }, + React.createElement('span', { style: { marginRight: 8 } }, '车牌号'), + React.createElement(Input, { + value: _plateForm[0].plateNo, + onChange: function (e) { onPlateFormChange('plateNo', e.target.value); }, + placeholder: '根据行驶证反写,可编辑', + style: { width: '100%' } + }) + ), + _plateError[0] ? React.createElement('div', { style: { color: '#ff4d4f', fontSize: 12 } }, _plateError[0]) : null + ) + )), + React.createElement(Modal, { + title: '需求说明', + open: _requirementModalVisible[0], + onCancel: function () { _requirementModalVisible[1](false); }, + width: 720, + footer: React.createElement(Button, { onClick: function () { _requirementModalVisible[1](false); } }, '关闭') + }, React.createElement('div', { style: { maxHeight: 560, overflow: 'auto', whiteSpace: 'pre-wrap', fontSize: 13, lineHeight: 1.6, color: 'rgba(0,0,0,0.85)' } }, requirementText)) + ) + ); +}; diff --git a/web端/运维管理/.DS_Store b/web端/运维管理/.DS_Store new file mode 100644 index 0000000..feef741 Binary files /dev/null and b/web端/运维管理/.DS_Store differ diff --git a/web端/运维管理/基本数据维护/停车场管理.jsx b/web端/运维管理/基本数据维护/停车场管理.jsx new file mode 100644 index 0000000..27836d1 --- /dev/null +++ b/web端/运维管理/基本数据维护/停车场管理.jsx @@ -0,0 +1,1319 @@ +// 【重要】必须使用 const Component 作为组件变量名 +// 停车场管理 - 车辆资产管理后台模块 +// 设计变量参考 Arco Design Token: https://arco.design/react/docs/token +var ARCO_TOKEN = { + // 主色 / 品牌色 + primary: '#165DFF', + primaryHover: '#4080FF', + primaryActive: '#0E42D2', + // 功能色 + danger: '#F53F3F', + success: '#00B42A', + warning: '#FF7D00', + link: '#165DFF', + // 中性色 (Neutral) + neutral1: '#FFFFFF', + neutral2: '#F7F8FA', + neutral3: '#F2F3F5', + neutral4: '#E5E6EB', + neutral5: '#C9CDD4', + neutral6: '#86909C', + neutral7: '#4E5969', + neutral8: '#1D2129', + // 边框 + border: '#E5E6EB', + borderSecondary: '#C9CDD4', + // 填充/背景 + fill: '#F2F3F5', + fillSecondary: '#F7F8FA', + // 阴影 + shadowLight: '0 1px 2px rgba(0,0,0,0.05)', + shadowMedium: '0 2px 8px rgba(0,0,0,0.08)', + // 圆角 + radiusSmall: '2px', + radiusMedium: '4px', + radiusLarge: '8px', + // 间距 + spacing4: '4px', + spacing8: '8px', + spacing12: '12px', + spacing16: '16px', + spacing24: '24px', + // 字体 + fontSize14: '14px', + fontSize16: '16px', + fontFamily: '-apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", Arial, sans-serif' +}; + +const Component = function () { + var _useState = React.useState('list'); + var currentView = _useState[0]; + var setCurrentView = _useState[1]; + + var _useState2 = React.useState([]); + var regionProvince = _useState2[0]; + var setRegionProvince = _useState2[1]; + + var _useState3 = React.useState(''); + var regionCity = _useState3[0]; + var setRegionCity = _useState3[1]; + + var _useState4 = React.useState(''); + var filterParkingName = _useState4[0]; + var setFilterParkingName = _useState4[1]; + + var _useState5 = React.useState(''); + var filterManager = _useState5[0]; + var setFilterManager = _useState5[1]; + + var _useState5b = React.useState(false); + var parkingNameInputFocused = _useState5b[0]; + var setParkingNameInputFocused = _useState5b[1]; + + var _useState5d = React.useState(false); + var managerInputFocused = _useState5d[0]; + var setManagerInputFocused = _useState5d[1]; + + var _useState6 = React.useState(false); + var showColumnSettings = _useState6[0]; + var setShowColumnSettings = _useState6[1]; + + var _useState6b = React.useState(false); + var columnIconHover = _useState6b[0]; + var setColumnIconHover = _useState6b[1]; + + var _useState6c = React.useState(false); + var regionCascaderOpen = _useState6c[0]; + var setRegionCascaderOpen = _useState6c[1]; + + var _useState6d = React.useState(false); + var formRegionCascaderOpen = _useState6d[0]; + var setFormRegionCascaderOpen = _useState6d[1]; + + var _useState6e = React.useState(''); + var regionCascaderHover = _useState6e[0]; + var setRegionCascaderHover = _useState6e[1]; + var _useState6f = React.useState(''); + var formRegionCascaderHover = _useState6f[0]; + var setFormRegionCascaderHover = _useState6f[1]; + + var _useState7 = React.useState(null); + var vehicleDetailParkingId = _useState7[0]; + var setVehicleDetailParkingId = _useState7[1]; + + var _useState8 = React.useState(null); + var viewParking = _useState8[0]; + var setViewParking = _useState8[1]; + + var _useState9 = React.useState(null); + var editParking = _useState9[0]; + var setEditParking = _useState9[1]; + + var _useState10 = React.useState(null); + var deleteConfirmParking = _useState10[0]; + var setDeleteConfirmParking = _useState10[1]; + + var _useState11 = React.useState(''); + var toastMessage = _useState11[0]; + var setToastMessage = _useState11[1]; + + var _useState11b = React.useState(false); + var showRequirementModal = _useState11b[0]; + var setShowRequirementModal = _useState11b[1]; + + // 分页状态 + var _useState12 = React.useState(1); + var currentPage = _useState12[0]; + var setCurrentPage = _useState12[1]; + var _useState13 = React.useState(10); + var pageSize = _useState13[0]; + var setPageSize = _useState13[1]; + + // 新建表单状态 + var _useState12 = React.useState({ + regionProvince: '', + regionCity: '', + parkingName: '', + parkingSpaces: '', + address: '', + manager: '', + managerPhone: '', + contactName: '', + contactPhone: '', + leaseStart: '', + leaseEnd: '', + rentFee: '', + contractFile: null + }); + var formData = _useState12[0]; + var setFormData = _useState12[1]; + + // 列显示配置 + var defaultColumnsVisible = { + region: true, + parkingName: true, + parkedVehicles: true, + parkingSpaces: true, + address: true, + leaseStart: true, + leaseEnd: true, + manager: true, + managerPhone: true, + contactName: true, + contactPhone: true + }; + var _useState13 = React.useState(defaultColumnsVisible); + var columnsVisible = _useState13[0]; + var setColumnsVisible = _useState13[1]; + + // 模拟省-市数据 + var provinceList = [ + { code: 'gd', name: '广东省' }, + { code: 'zj', name: '浙江省' }, + { code: 'bj', name: '北京市' } + ]; + var cityMap = { + gd: [{ code: 'gz', name: '广州市' }, { code: 'sz', name: '深圳市' }], + zj: [{ code: 'hz', name: '杭州市' }, { code: 'nb', name: '宁波市' }], + bj: [{ code: 'bj', name: '北京市' }] + }; + + // 模拟停车场列表数据 + var mockParkingList = [ + { + id: '1', + regionProvince: '广东省', + regionCity: '广州市', + parkingName: '天河智慧停车场', + parkedCount: 12, + parkingSpaces: 50, + address: '广州市天河区体育西路123号', + leaseStart: '2023-01-01', + leaseEnd: '2025-12-31', + manager: '张明', + managerPhone: '13800138001', + contactName: '李华', + contactPhone: '13900139001', + vehicles: [ + { city: '广州', frameNo: 'LGW123456', plateNo: '粤A12345', code: 'V001', brand: '比亚迪', model: '秦', color: '白色', status: '正常', mileage: 12000, location: '天河区体育西路', gpsTime: '2025-02-06 10:30:00' }, + { city: '广州', frameNo: 'LGW789012', plateNo: '粤A67890', code: 'V002', brand: '特斯拉', model: 'Model 3', color: '黑色', status: '正常', mileage: 8000, location: '天河区体育西路', gpsTime: '2025-02-06 10:28:00' } + ] + }, + { + id: '2', + regionProvince: '广东省', + regionCity: '深圳市', + parkingName: '南山科技园停车场', + parkedCount: 0, + parkingSpaces: 80, + address: '深圳市南山区科技园南路88号', + leaseStart: '2022-06-01', + leaseEnd: '2025-05-31', + manager: '王芳', + managerPhone: '13700137001', + contactName: '赵强', + contactPhone: '13600136001', + vehicles: [] + }, + { + id: '3', + regionProvince: '浙江省', + regionCity: '杭州市', + parkingName: '西湖景区停车场', + parkedCount: 5, + parkingSpaces: 30, + address: '杭州市西湖区杨公堤1号', + leaseStart: '2024-01-01', + leaseEnd: '2026-12-31', + manager: '陈静', + managerPhone: '13500135001', + contactName: '刘洋', + contactPhone: '13400134001', + vehicles: [ + { city: '杭州', frameNo: 'HZ111', plateNo: '浙A11111', code: 'V003', brand: '小鹏', model: 'P7', color: '灰色', status: '维修', mileage: 15000, location: '西湖区杨公堤', gpsTime: '2025-02-05 18:00:00' } + ] + } + ]; + + // 获取所有唯一的停车场名称和负责人 + var getAllParkingNames = function () { + var names = []; + var seen = {}; + mockParkingList.forEach(function (item) { + if (item.parkingName && !seen[item.parkingName]) { + seen[item.parkingName] = true; + names.push(item.parkingName); + } + }); + return names.sort(); + }; + var getAllManagers = function () { + var managers = []; + var seen = {}; + mockParkingList.forEach(function (item) { + if (item.manager && !seen[item.manager]) { + seen[item.manager] = true; + managers.push(item.manager); + } + }); + return managers.sort(); + }; + var allParkingNames = getAllParkingNames(); + var allManagers = getAllManagers(); + + var getFilteredList = function () { + var list = mockParkingList; + if (regionProvince && regionProvince.length > 0) { + var p = provinceList.find(function (x) { return x.code === regionProvince; }); + var pName = p ? p.name : ''; + list = list.filter(function (item) { return item.regionProvince === pName; }); + } + if (regionCity) { + var cities = cityMap[regionProvince]; + var c = cities && cities.find(function (x) { return x.code === regionCity; }); + var cName = c ? c.name : ''; + list = list.filter(function (item) { return item.regionCity === cName; }); + } + if (filterParkingName) { + list = list.filter(function (item) { + return item.parkingName && item.parkingName.indexOf(filterParkingName) >= 0; + }); + } + if (filterManager) { + list = list.filter(function (item) { + return item.manager && item.manager.indexOf(filterManager) >= 0; + }); + } + return list; + }; + + var filteredList = getFilteredList(); + var totalItems = filteredList.length; + var totalPages = Math.ceil(totalItems / pageSize) || 1; + var validPage = currentPage > totalPages && totalPages > 0 ? 1 : (currentPage < 1 ? 1 : currentPage); + if (validPage !== currentPage && totalPages > 0) { + setCurrentPage(1); + validPage = 1; + } + var startIndex = (validPage - 1) * pageSize; + var endIndex = startIndex + pageSize; + var paginatedList = filteredList.slice(startIndex, endIndex); + + var handleExport = function () { + setToastMessage('导出功能为原型演示'); + setTimeout(function () { setToastMessage(''); }, 2000); + }; + + var handleDeleteConfirm = function () { + if (!deleteConfirmParking) return; + if (deleteConfirmParking.parkedCount > 0) { + setToastMessage('该停车场存在车辆,无法删除'); + setDeleteConfirmParking(null); + setTimeout(function () { setToastMessage(''); }, 2000); + return; + } + setToastMessage('删除成功'); + setDeleteConfirmParking(null); + setTimeout(function () { setToastMessage(''); }, 2000); + }; + + var handleFormChange = function (field, value) { + var next = {}; + for (var k in formData) { next[k] = formData[k]; } + next[field] = value; + setFormData(next); + }; + + var handleSubmitNew = function () { + if (!formData.regionProvince || !formData.regionCity) { + setToastMessage('请选择地区'); + setTimeout(function () { setToastMessage(''); }, 2000); + return; + } + if (!formData.parkingName || !formData.parkingName.trim()) { + setToastMessage('请填写停车场名称'); + setTimeout(function () { setToastMessage(''); }, 2000); + return; + } + if (!formData.leaseStart) { + setToastMessage('请选择租赁开始时间'); + setTimeout(function () { setToastMessage(''); }, 2000); + return; + } + if (!formData.leaseEnd) { + setToastMessage('请选择租赁到期时间'); + setTimeout(function () { setToastMessage(''); }, 2000); + return; + } + if (formData.leaseEnd < formData.leaseStart) { + setToastMessage('租赁到期时间不能早于租赁开始时间'); + setTimeout(function () { setToastMessage(''); }, 2000); + return; + } + if (!formData.rentFee || formData.rentFee.trim() === '') { + setToastMessage('请填写租赁费'); + setTimeout(function () { setToastMessage(''); }, 2000); + return; + } + setToastMessage('提交成功'); + setTimeout(function () { setToastMessage(''); }, 1500); + setCurrentView('list'); + setFormData({ + regionProvince: '', + regionCity: '', + parkingName: '', + parkingSpaces: '', + address: '', + manager: '', + managerPhone: '', + contactName: '', + contactPhone: '', + leaseStart: '', + leaseEnd: '', + rentFee: '', + contractFile: null + }); + }; + + var handleCancelNew = function () { + setCurrentView('list'); + setFormData({ + regionProvince: '', + regionCity: '', + parkingName: '', + parkingSpaces: '', + address: '', + manager: '', + managerPhone: '', + contactName: '', + contactPhone: '', + leaseStart: '', + leaseEnd: '', + rentFee: '', + contractFile: null + }); + }; + + var t = ARCO_TOKEN; + var styles = { + page: { padding: t.spacing24, fontFamily: t.fontFamily, backgroundColor: t.fill, minHeight: '100vh' }, + breadcrumb: { marginBottom: t.spacing16, fontSize: t.fontSize14, color: t.neutral6, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }, + breadcrumbLeft: { display: 'flex', alignItems: 'center' }, + breadcrumbLink: { color: t.link, textDecoration: 'none', marginRight: t.spacing8 }, + breadcrumbCurrent: { color: t.neutral8 }, + breadcrumbRight: { display: 'flex', alignItems: 'center' }, + requirementLink: { color: t.link, textDecoration: 'none', fontSize: t.fontSize14, cursor: 'pointer' }, + card: { backgroundColor: t.neutral1, borderRadius: t.radiusLarge, boxShadow: t.shadowLight, marginBottom: t.spacing16, padding: t.spacing16 }, + filterRow: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', justifyContent: 'space-between', gap: t.spacing12 }, + filterRowLeft: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: t.spacing12 }, + filterRowRight: { display: 'flex', alignItems: 'center', gap: t.spacing8, marginLeft: 'auto' }, + label: { marginRight: t.spacing8, fontSize: t.fontSize14, color: t.neutral8 }, + select: { padding: '0 10px', height: '32px', borderRadius: '2px', border: '1px solid ' + t.border, fontSize: t.fontSize14, boxSizing: 'border-box' }, + input: { padding: '0 10px', height: '32px', width: '100%', borderRadius: '2px', border: '1px solid ' + t.border, fontSize: t.fontSize14, boxSizing: 'border-box' }, + toolbar: { display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: t.spacing12, marginBottom: t.spacing16 }, + btn: { padding: t.spacing8 + ' ' + t.spacing16, borderRadius: t.radiusMedium, cursor: 'pointer', fontSize: t.fontSize14, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: '6px', boxSizing: 'border-box' }, + btnFixed: { width: '100px', height: '32px', padding: 0, borderRadius: '2px', lineHeight: '1' }, + btnIcon: { width: '14px', height: '14px', flexShrink: 0, display: 'block' }, + btnPrimary: { backgroundColor: t.primary, color: t.neutral1, border: 'none' }, + btnFillBlue: { backgroundColor: t.primary, color: t.neutral1, border: 'none' }, + btnOutlineBlue: { backgroundColor: t.neutral1, color: t.primary, border: '1px solid ' + t.primary }, + btnDefault: { backgroundColor: t.neutral1, color: t.neutral8, border: '1px solid ' + t.border }, + btnDanger: { backgroundColor: t.danger, color: t.neutral1, border: 'none' }, + tableWrap: { overflowX: 'auto', backgroundColor: t.neutral1, borderRadius: t.radiusMedium, border: '1px solid ' + t.neutral4 }, + table: { width: '100%', borderCollapse: 'separate', borderSpacing: 0, fontSize: t.fontSize14 }, + th: { textAlign: 'left', padding: '12px 16px', backgroundColor: t.fillSecondary, borderBottom: '1px solid ' + t.neutral4, fontWeight: 600, color: t.neutral8, fontSize: t.fontSize14, whiteSpace: 'nowrap' }, + td: { padding: '12px 16px', borderBottom: '1px solid ' + t.neutral4, color: t.neutral8, fontSize: t.fontSize14 }, + trHover: { backgroundColor: t.fillSecondary }, + pagination: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '16px', borderTop: '1px solid ' + t.neutral4, backgroundColor: t.neutral1 }, + paginationLeft: { display: 'flex', alignItems: 'center', gap: '8px', fontSize: t.fontSize14, color: t.neutral7 }, + paginationRight: { display: 'flex', alignItems: 'center', gap: '8px' }, + paginationSelect: { padding: '4px 8px', height: '28px', borderRadius: '2px', border: '1px solid ' + t.border, fontSize: t.fontSize14, backgroundColor: t.neutral1 }, + paginationBtn: { minWidth: '28px', height: '28px', padding: '0 8px', borderRadius: '2px', border: '1px solid ' + t.border, backgroundColor: t.neutral1, color: t.neutral8, cursor: 'pointer', fontSize: t.fontSize14, display: 'inline-flex', alignItems: 'center', justifyContent: 'center' }, + paginationBtnActive: { backgroundColor: t.primary, color: t.neutral1, borderColor: t.primary }, + paginationBtnDisabled: { opacity: 0.5, cursor: 'not-allowed' }, + paginationInput: { width: '50px', height: '28px', padding: '0 8px', borderRadius: '2px', border: '1px solid ' + t.border, fontSize: t.fontSize14, textAlign: 'center' }, + linkBlue: { color: t.link, cursor: 'pointer', textDecoration: 'none' }, + actionLink: { color: t.link, cursor: 'pointer', marginRight: t.spacing12, fontSize: t.fontSize14 }, + modalMask: { position: 'fixed', left: 0, top: 0, right: 0, bottom: 0, backgroundColor: 'rgba(0,0,0,0.45)', zIndex: 1000, display: 'flex', alignItems: 'center', justifyContent: 'center' }, + modalBox: { backgroundColor: t.neutral1, borderRadius: t.radiusLarge, maxWidth: '90%', maxHeight: '90%', overflow: 'auto', padding: t.spacing24, minWidth: '500px', position: 'relative' }, + modalTitle: { fontSize: t.fontSize16, fontWeight: 600, marginBottom: t.spacing16, color: t.neutral8 }, + modalHeader: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: t.spacing16 }, + modalCloseBtn: { position: 'absolute', right: t.spacing16, top: t.spacing16, width: '24px', height: '24px', display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', borderRadius: t.radiusMedium, backgroundColor: 'transparent', border: 'none', color: t.neutral6, fontSize: '18px', lineHeight: '1', padding: 0 }, + modalCloseBtnHover: { backgroundColor: t.fill, color: t.neutral8 }, + modalContent: { fontSize: t.fontSize14, color: t.neutral8, lineHeight: '1.6', whiteSpace: 'pre-wrap' }, + requirementSection: { marginBottom: t.spacing16 }, + requirementSectionTitle: { fontSize: t.fontSize16, fontWeight: 600, color: t.neutral8, marginBottom: t.spacing8 }, + requirementItem: { marginBottom: t.spacing8, paddingLeft: t.spacing16 }, + requirementSubItem: { marginBottom: t.spacing4, paddingLeft: t.spacing16, fontSize: t.fontSize14, color: t.neutral7 }, + modalFooter: { marginTop: t.spacing24, display: 'flex', justifyContent: 'flex-end', gap: t.spacing8 }, + toast: { position: 'fixed', top: t.spacing24, left: '50%', transform: 'translateX(-50%)', backgroundColor: 'rgba(0,0,0,0.75)', color: t.neutral1, padding: '10px 20px', borderRadius: t.radiusMedium, zIndex: 2000, fontSize: t.fontSize14 }, + formRow: { marginBottom: t.spacing16 }, + formLabel: { display: 'block', marginBottom: '6px', fontSize: t.fontSize14, color: t.neutral8 }, + formLabelReq: { color: t.danger, marginLeft: '2px' }, + vehicleCard: { border: '1px solid ' + t.neutral4, borderRadius: '6px', padding: t.spacing12, backgroundColor: t.fillSecondary, fontSize: '13px', color: t.neutral8 }, + vehicleCardRow: { marginBottom: t.spacing4 }, + vehicleModalBox: { backgroundColor: t.neutral1, borderRadius: t.radiusLarge, maxWidth: '1200px', width: '90%', maxHeight: '80vh', overflow: 'auto', padding: t.spacing24, boxShadow: t.shadowMedium }, + vehicleModalTitle: { fontSize: t.fontSize16, fontWeight: 600, marginBottom: t.spacing16, color: t.neutral8 }, + columnSettingsBox: { position: 'absolute', right: 0, top: '100%', marginTop: t.spacing4, backgroundColor: t.neutral1, border: '1px solid ' + t.border, borderRadius: t.radiusMedium, padding: 0, minWidth: '220px', boxShadow: t.shadowMedium, zIndex: 100 }, + columnSettingsHeader: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: t.spacing12, borderBottom: '1px solid ' + t.neutral4, fontSize: t.fontSize14, fontWeight: 600, color: t.neutral8 }, + columnSettingsBody: { padding: t.spacing12, maxHeight: '320px', overflowY: 'auto' }, + checkboxRow: { marginBottom: '6px', fontSize: t.fontSize14 }, + iconBtn: { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: '32px', height: '32px', padding: 0, margin: 0, backgroundColor: 'transparent', border: 'none', borderRadius: t.radiusMedium, cursor: 'pointer', color: t.neutral6, transition: 'background-color 0.2s, color 0.2s' }, + newPageLayout: { display: 'flex', gap: t.spacing24, flexWrap: 'wrap' }, + newPageForm: { flex: '2', minWidth: '400px' }, + newPageMap: { flex: '1', minWidth: '300px', height: '400px', backgroundColor: t.neutral4, borderRadius: t.radiusLarge, display: 'flex', alignItems: 'center', justifyContent: 'center', color: t.neutral6, fontSize: t.fontSize14 }, + cascaderWrap: { position: 'relative', width: '300px' }, + cascaderInput: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', width: '100%', height: '32px', padding: '0 10px', borderRadius: '2px', border: '1px solid ' + t.border, backgroundColor: t.neutral1, fontSize: t.fontSize14, color: t.neutral8, cursor: 'pointer', boxSizing: 'border-box' }, + cascaderInputFocus: { borderColor: t.primary, outline: 'none' }, + cascaderPlaceholder: { color: t.neutral6 }, + cascaderChevron: { marginLeft: '8px', color: t.neutral6, transition: 'transform 0.2s' }, + cascaderPanel: { position: 'absolute', left: 0, top: '100%', marginTop: '4px', backgroundColor: t.neutral1, borderRadius: t.radiusMedium, border: '1px solid ' + t.border, boxShadow: t.shadowMedium, zIndex: 100, display: 'flex', minWidth: '280px', maxHeight: '280px' }, + cascaderColumn: { minWidth: '120px', maxHeight: '280px', overflowY: 'auto', borderRight: '1px solid ' + t.neutral4 }, + cascaderColumnLast: { borderRight: 'none' }, + cascaderOption: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '8px 12px', fontSize: t.fontSize14, color: t.neutral8, cursor: 'pointer', whiteSpace: 'nowrap' }, + cascaderOptionHover: { backgroundColor: t.fill }, + autocompleteWrap: { position: 'relative', width: '300px' }, + autocompletePanel: { position: 'absolute', left: 0, top: '100%', marginTop: '4px', backgroundColor: t.neutral1, borderRadius: t.radiusMedium, border: '1px solid ' + t.border, boxShadow: t.shadowMedium, zIndex: 100, minWidth: '100%', maxHeight: '240px', overflowY: 'auto' }, + autocompleteOption: { padding: '8px 12px', fontSize: t.fontSize14, color: t.neutral8, cursor: 'pointer' }, + autocompleteOptionHover: { backgroundColor: t.fill } + }; + + return ( +
+ {/* 面包屑 */} +
+
+ 基本数据维护 + / + 停车场 +
+ +
+ + {currentView === 'list' && ( + + {/* 筛选栏 - 单独卡片 */} +
+
+
+ 地区: +
+
+ + {regionProvince && regionCity + ? (function () { + var p = provinceList.find(function (x) { return x.code === regionProvince; }); + var cities = cityMap[regionProvince]; + var c = cities && cities.find(function (x) { return x.code === regionCity; }); + return (p ? p.name : '') + (p && c ? ' - ' : '') + (c ? c.name : ''); + }()) + : '请选择省/市'} + + + + +
+ {regionCascaderOpen && ( + +
+
+
+ {provinceList.map(function (p) { + var isSelected = regionProvince === p.code; + var isHovered = regionCascaderHover === 'p-' + p.code; + return React.createElement('div', { + key: p.code, + style: Object.assign({}, styles.cascaderOption, (isSelected || isHovered) ? styles.cascaderOptionHover : {}), + onClick: function () { + setRegionProvince(p.code); + setRegionCity(''); + }, + onMouseEnter: function () { setRegionCascaderHover('p-' + p.code); }, + onMouseLeave: function () { setRegionCascaderHover(''); } + }, p.name, React.createElement('span', { style: { color: t.neutral6 } }, '>')); + })} +
+
+ {(cityMap[regionProvince] || []).map(function (c) { + var isSelected = regionCity === c.code; + var isHovered = regionCascaderHover === 'c-' + c.code; + return React.createElement('div', { + key: c.code, + style: Object.assign({}, styles.cascaderOption, (isSelected || isHovered) ? styles.cascaderOptionHover : {}), + onClick: function () { + setRegionCity(c.code); + setRegionCascaderOpen(false); + }, + onMouseEnter: function () { setRegionCascaderHover('c-' + c.code); }, + onMouseLeave: function () { setRegionCascaderHover(''); } + }, c.name); + })} +
+
+ + )} +
+ 停车场名称: +
+ + {(parkingNameInputFocused || filterParkingName) && allParkingNames.filter(function (name) { + return !filterParkingName || name.indexOf(filterParkingName) >= 0; + }).length > 0 && ( + +
+
+ {allParkingNames.filter(function (name) { + return !filterParkingName || name.indexOf(filterParkingName) >= 0; + }).map(function (name) { + var isSelected = filterParkingName === name; + return React.createElement('div', { + key: name, + style: Object.assign({}, styles.autocompleteOption, isSelected ? styles.autocompleteOptionHover : {}), + onMouseDown: function (e) { e.preventDefault(); }, + onClick: function () { + setFilterParkingName(name); + setParkingNameInputFocused(false); + }, + onMouseEnter: function (e) { e.currentTarget.style.backgroundColor = t.fill; }, + onMouseLeave: function (e) { if (!isSelected) e.currentTarget.style.backgroundColor = 'transparent'; } + }, name); + })} +
+ + )} +
+ 负责人: +
+ + {(managerInputFocused || filterManager) && allManagers.filter(function (manager) { + return !filterManager || manager.indexOf(filterManager) >= 0; + }).length > 0 && ( + +
+
+ {allManagers.filter(function (manager) { + return !filterManager || manager.indexOf(filterManager) >= 0; + }).map(function (manager) { + var isSelected = filterManager === manager; + return React.createElement('div', { + key: manager, + style: Object.assign({}, styles.autocompleteOption, isSelected ? styles.autocompleteOptionHover : {}), + onMouseDown: function (e) { e.preventDefault(); }, + onClick: function () { + setFilterManager(manager); + setManagerInputFocused(false); + }, + onMouseEnter: function (e) { e.currentTarget.style.backgroundColor = t.fill; }, + onMouseLeave: function (e) { if (!isSelected) e.currentTarget.style.backgroundColor = 'transparent'; } + }, manager); + })} +
+ + )} +
+
+
+ + +
+
+
+ + {/* 列表区 - 单独卡片 */} +
+
+ + +
+ + {showColumnSettings && ( + +
+
+
+ 列展示 + +
+
+ {Object.keys(columnsVisible).map(function (key) { + var labels = { + region: '地区', + parkingName: '停车场名称', + parkedVehicles: '停放车辆', + parkingSpaces: '车位数', + address: '地址', + leaseStart: '租赁开始时间', + leaseEnd: '租赁结束时间', + manager: '负责人', + managerPhone: '负责人联系方式', + contactName: '停车场联系人', + contactPhone: '停车场电话' + }; + return React.createElement( + 'div', + { key: key, style: styles.checkboxRow }, + React.createElement('label', { style: { display: 'flex', alignItems: 'center', cursor: 'pointer' }}, + React.createElement('input', { + type: 'checkbox', + checked: columnsVisible[key], + onChange: function () { + var next = {}; + for (var k in columnsVisible) { next[k] = columnsVisible[k]; } + next[key] = !columnsVisible[key]; + setColumnsVisible(next); + } + }), + React.createElement('span', { style: { marginLeft: '8px' } }, labels[key] || key) + ) + ); + })} +
+
+ + )} +
+
+ + {/* 表格 */} +
+
+ + + {columnsVisible.region && React.createElement('th', { style: styles.th }, '地区')} + {columnsVisible.parkingName && React.createElement('th', { style: styles.th }, '停车场名称')} + {columnsVisible.parkedVehicles && React.createElement('th', { style: styles.th }, '停放车辆')} + {columnsVisible.parkingSpaces && React.createElement('th', { style: styles.th }, '车位数')} + {columnsVisible.address && React.createElement('th', { style: styles.th }, '地址')} + {columnsVisible.leaseStart && React.createElement('th', { style: styles.th }, '租赁开始时间')} + {columnsVisible.leaseEnd && React.createElement('th', { style: styles.th }, '租赁结束时间')} + {columnsVisible.manager && React.createElement('th', { style: styles.th }, '负责人')} + {columnsVisible.managerPhone && React.createElement('th', { style: styles.th }, '负责人联系方式')} + {columnsVisible.contactName && React.createElement('th', { style: styles.th }, '停车场联系人')} + {columnsVisible.contactPhone && React.createElement('th', { style: styles.th }, '停车场电话')} + + + + + {paginatedList.length === 0 ? ( + React.createElement('tr', {}, + React.createElement('td', { + colSpan: Object.keys(columnsVisible).filter(function (k) { return columnsVisible[k]; }).length + 1, + style: Object.assign({}, styles.td, { textAlign: 'center', color: t.neutral6, padding: '40px' }) + }, '暂无数据') + ) + ) : paginatedList.map(function (row) { + return React.createElement( + 'tr', + { key: row.id, style: { transition: 'background-color 0.2s' }, onMouseEnter: function (e) { e.currentTarget.style.backgroundColor = t.fillSecondary; }, onMouseLeave: function (e) { e.currentTarget.style.backgroundColor = 'transparent'; } }, + columnsVisible.region && React.createElement('td', { style: styles.td }, row.regionProvince + '-' + row.regionCity), + columnsVisible.parkingName && React.createElement('td', { style: styles.td }, row.parkingName), + columnsVisible.parkedVehicles && React.createElement('td', { style: styles.td }, + React.createElement('span', { + style: styles.linkBlue, + onClick: function () { + if (row.parkedCount > 0 && row.vehicles && row.vehicles.length > 0) { + setVehicleDetailParkingId(row.id); + } else { + setToastMessage('该停车场暂无车辆'); + setTimeout(function () { setToastMessage(''); }, 2000); + } + } + }, row.parkedCount) + ), + columnsVisible.parkingSpaces && React.createElement('td', { style: styles.td }, row.parkingSpaces), + columnsVisible.address && React.createElement('td', { style: styles.td }, row.address), + columnsVisible.leaseStart && React.createElement('td', { style: styles.td }, row.leaseStart), + columnsVisible.leaseEnd && React.createElement('td', { style: styles.td }, row.leaseEnd), + columnsVisible.manager && React.createElement('td', { style: styles.td }, row.manager), + columnsVisible.managerPhone && React.createElement('td', { style: styles.td }, row.managerPhone), + columnsVisible.contactName && React.createElement('td', { style: styles.td }, row.contactName), + columnsVisible.contactPhone && React.createElement('td', { style: styles.td }, row.contactPhone), + React.createElement('td', { style: styles.td }, + React.createElement('a', { + style: styles.actionLink, + onClick: function (e) { e.preventDefault(); setViewParking(row); } + }, '查看'), + React.createElement('a', { + style: styles.actionLink, + onClick: function (e) { e.preventDefault(); setEditParking(row); } + }, '编辑'), + React.createElement('a', { + style: Object.assign({}, styles.actionLink, { color: t.danger }), + onClick: function (e) { e.preventDefault(); setDeleteConfirmParking(row); } + }, '删除') + ) + ); + })} + +
操作
+ + + {/* 分页 */} + {totalItems > 0 && ( +
+
+ 共 {totalItems} 条 + 每页显示 + + +
+
+ + {(function () { + var pages = []; + var showPages = 5; + var startPage = Math.max(1, currentPage - Math.floor(showPages / 2)); + var endPage = Math.min(totalPages, startPage + showPages - 1); + if (endPage - startPage < showPages - 1) { + startPage = Math.max(1, endPage - showPages + 1); + } + if (startPage > 1) { + pages.push(React.createElement('button', { + key: 1, + style: styles.paginationBtn, + onClick: function () { setCurrentPage(1); }, + onMouseLeave: function (e) { e.currentTarget.style.borderColor = t.border; e.currentTarget.style.color = t.neutral8; } + }, '1')); + if (startPage > 2) { + pages.push(React.createElement('span', { key: 'ellipsis1', style: { padding: '0 4px', color: t.neutral7 } }, '...')); + } + } + for (var i = startPage; i <= endPage; i++) { + var isActive = i === currentPage; + pages.push(React.createElement('button', { + key: i, + style: Object.assign({}, styles.paginationBtn, isActive ? styles.paginationBtnActive : {}), + onClick: function (page) { return function () { setCurrentPage(page); }; }(i), + onMouseLeave: function (e) { if (!isActive) { e.currentTarget.style.borderColor = t.border; e.currentTarget.style.color = t.neutral8; } } + }, i)); + } + if (endPage < totalPages) { + if (endPage < totalPages - 1) { + pages.push(React.createElement('span', { key: 'ellipsis2', style: { padding: '0 4px', color: t.neutral7 } }, '...')); + } + pages.push(React.createElement('button', { + key: totalPages, + style: styles.paginationBtn, + onClick: function () { setCurrentPage(totalPages); }, + onMouseLeave: function (e) { e.currentTarget.style.borderColor = t.border; e.currentTarget.style.color = t.neutral8; } + }, totalPages)); + } + return pages; + })()} + + 跳至 + = 1 && page <= totalPages) { + setCurrentPage(page); + } + }} + onKeyDown={function (e) { + if (e.key === 'Enter') { + var page = parseInt(e.target.value, 10); + if (page >= 1 && page <= totalPages) { + setCurrentPage(page); + } + } + }} + /> + +
+
+ )} + + + + )} + + {/* 新建停车场页面 */} + {currentView === 'new' && ( +
+
新建停车场
+
+
+
+ 地区 * +
+
+ + {formData.regionProvince && formData.regionCity + ? (function () { + var p = provinceList.find(function (x) { return x.code === formData.regionProvince; }); + var cities = cityMap[formData.regionProvince]; + var c = cities && cities.find(function (x) { return x.code === formData.regionCity; }); + return (p ? p.name : '') + (p && c ? ' - ' : '') + (c ? c.name : ''); + }()) + : '请选择省/市'} + + + + +
+ {formRegionCascaderOpen && ( + +
+
+
+ {provinceList.map(function (p) { + var isSelected = formData.regionProvince === p.code; + var isHovered = formRegionCascaderHover === 'p-' + p.code; + return React.createElement('div', { + key: p.code, + style: Object.assign({}, styles.cascaderOption, (isSelected || isHovered) ? styles.cascaderOptionHover : {}), + onClick: function () { + handleFormChange('regionProvince', p.code); + handleFormChange('regionCity', ''); + }, + onMouseEnter: function () { setFormRegionCascaderHover('p-' + p.code); }, + onMouseLeave: function () { setFormRegionCascaderHover(''); } + }, p.name, React.createElement('span', { style: { color: t.neutral6 } }, '>')); + })} +
+
+ {(cityMap[formData.regionProvince] || []).map(function (c) { + var isSelected = formData.regionCity === c.code; + var isHovered = formRegionCascaderHover === 'c-' + c.code; + return React.createElement('div', { + key: c.code, + style: Object.assign({}, styles.cascaderOption, (isSelected || isHovered) ? styles.cascaderOptionHover : {}), + onClick: function () { + handleFormChange('regionCity', c.code); + setFormRegionCascaderOpen(false); + }, + onMouseEnter: function () { setFormRegionCascaderHover('c-' + c.code); }, + onMouseLeave: function () { setFormRegionCascaderHover(''); } + }, c.name); + })} +
+
+ + )} +
+
+
+ 停车场名称 * + +
+
+ 车位数 + +
+
+ 地址 + +
+
+ 负责人 + +
+
+ 负责人电话 + +
+
+ 停车场联系人 + +
+
+ 停车场电话 + +
+
+ 租赁开始时间 * + +
+
+ 租赁到期时间 * + +
+
+ 租赁费 * + + + + +
+
+ 租赁合同 + + 支持上传PDF +
+
+
+ 地图区域(输入地址可定位,拖动反写地址) + {formData.address && React.createElement('div', { style: { marginTop: '8px', fontSize: '12px' } }, '当前地址:' + formData.address)} +
+
+
+ + +
+
+ )} + + {/* 编辑弹窗 - 与新增相同可编辑 */} + {editParking && ( +
+
+
编辑停车场
+
+ 地区 * + {editParking.regionProvince}-{editParking.regionCity} +
+
+ 停车场名称 + +
+
+ 车位数 + +
+
+ 地址 + +
+
+ 负责人 / 负责人电话 + + +
+
+ 停车场联系人 / 停车场电话 + + +
+
+ 租赁开始时间 / 租赁结束时间 + {editParking.leaseStart} / {editParking.leaseEnd} +
+
+ + +
+
+
+ )} + + {/* 查看弹窗 - 与新增相同但只读 */} + {viewParking && ( +
+
+
查看停车场
+
+ 地区 + {viewParking.regionProvince}-{viewParking.regionCity} +
+
+ 停车场名称 + {viewParking.parkingName} +
+
+ 车位数 + {viewParking.parkingSpaces} +
+
+ 地址 + {viewParking.address} +
+
+ 负责人 / 负责人电话 + {viewParking.manager} / {viewParking.managerPhone} +
+
+ 停车场联系人 / 停车场电话 + {viewParking.contactName} / {viewParking.contactPhone} +
+
+ 租赁开始时间 / 租赁结束时间 + {viewParking.leaseStart} / {viewParking.leaseEnd} +
+
+ +
+
+
+ )} + + {/* 删除确认弹窗 */} + {deleteConfirmParking && ( +
+
+
确认删除
+

是否确认删除该停车场?

+
+ + +
+
+
+ )} + + {/* 车辆信息弹框 */} + {vehicleDetailParkingId && (function () { + var row = mockParkingList.find(function (r) { return r.id === vehicleDetailParkingId; }); + if (!row || !row.vehicles || row.vehicles.length === 0) { + return null; + } + return React.createElement( + 'div', + { style: styles.modalMask, onClick: function () { setVehicleDetailParkingId(null); } }, + React.createElement('div', { + style: styles.vehicleModalBox, + onClick: function (e) { e.stopPropagation(); } + }, + React.createElement('div', { style: styles.vehicleModalTitle }, row.parkingName + ' - 车辆明细'), + React.createElement('div', { style: styles.tableWrap }, + React.createElement('table', { style: styles.table }, + React.createElement('thead', {}, + React.createElement('tr', {}, + 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 }, '当前位置'), + React.createElement('th', { style: styles.th }, 'GPS最后上传时间') + ) + ), + React.createElement('tbody', {}, + row.vehicles.map(function (v, i) { + return React.createElement('tr', { key: i }, + React.createElement('td', { style: styles.td }, v.city), + React.createElement('td', { style: styles.td }, v.frameNo), + React.createElement('td', { style: styles.td }, v.plateNo), + React.createElement('td', { style: styles.td }, v.code), + React.createElement('td', { style: styles.td }, v.brand), + React.createElement('td', { style: styles.td }, v.model), + React.createElement('td', { style: styles.td }, v.color), + React.createElement('td', { style: styles.td }, v.status), + React.createElement('td', { style: styles.td }, v.mileage), + React.createElement('td', { style: styles.td }, v.location), + React.createElement('td', { style: styles.td }, v.gpsTime) + ); + }) + ) + ) + ), + React.createElement('div', { style: styles.modalFooter }, + React.createElement('button', { + style: Object.assign({}, styles.btn, styles.btnDefault), + onClick: function () { setVehicleDetailParkingId(null); } + }, '关闭') + ) + ) + ); + })()} + + {/* 需求说明弹窗 */} + {showRequirementModal && ( +
+
+ +
+
+
筛选:
+
包括地区(级联选择器,省-市)、停车场名称(选择器,支持输入框中模糊搜索)、负责人(选择器,支持输入框中模糊搜索)
+
+
+
停车场列表:
+
支持新建、导出、列设置
+
字段显示依次为地区、停车场名称、停放车辆、车位数、地址、租赁开始时间、租赁结束时间、负责人、负责人联系方式、停车场联系人、停车场联系方式
+
1. 地区字段显示精确至省-市即可
+
2. 停车场名称字段显示实际停车场名称
+
3. 停放车辆显示停车场当前停放车辆数,车辆数标注为重点色(如蓝色),点击车辆数以卡片模式展开对应车辆明细,车辆明细显示运营城市、车架号、车牌号、车辆编号、品牌、型号、车身颜色、整备状态、行驶公里数、当前位置、GPS最后上传时间
+
4. 车位数显示停车场实际车位数
+
5. 地址显示停车场实际地址
+
6. 租赁开始时间显示停车场合同实际租赁时间,显示格式为年月日
+
7. 租赁结束时间显示停车场合同实际租赁时间,显示格式为年月日
+
8. 负责人显示停车场实际负责人姓名
+
9. 负责人联系方式显示停车场实际负责人手机号
+
10. 停车场联系人显示停车场联系人姓名
+
11. 停车场电话显示停车场联系人手机号
+
12. 操作列支持查看、编辑、删除
+
查看与新增页面相同,只是所有信息输入为禁用状态
+
点击删除进行二次确认,提示"是否确认删除该停车场",同时判断停车场是否有车辆数据,如有车辆数据则提示"该停车场存在车辆,无法删除",如停车场无车辆,则toast提示删除成功
+
+
+
+
+ )} + + {toastMessage && React.createElement('div', { style: styles.toast }, toastMessage)} +
+ ); +}; +if (typeof window !== 'undefined') { + window.Component = Component; + function mount() { + var rootEl = document.getElementById('root'); + if (rootEl && window.ReactDOM && window.React) { + var root = ReactDOM.createRoot(rootEl); + root.render(React.createElement(Component)); + } + } + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', mount); + } else { + setTimeout(mount, 0); + } +} diff --git a/web端/运维管理/车辆业务/上牌管理.jsx b/web端/运维管理/车辆业务/上牌管理.jsx new file mode 100755 index 0000000..f8d612c --- /dev/null +++ b/web端/运维管理/车辆业务/上牌管理.jsx @@ -0,0 +1,938 @@ +// 【重要】必须使用 const Component 作为组件变量名 +// 车辆上牌管理 - 车辆资产管理后台模块 +var ARCO_TOKEN = { + primary: '#165DFF', + primaryHover: '#4080FF', + danger: '#F53F3F', + success: '#00B42A', + neutral1: '#FFFFFF', + neutral2: '#F7F8FA', + neutral3: '#F2F3F5', + neutral4: '#E5E6EB', + neutral5: '#C9CDD4', + neutral6: '#86909C', + neutral7: '#4E5969', + neutral8: '#1D2129', + border: '#E5E6EB', + fill: '#F2F3F5', + fillSecondary: '#F7F8FA', + shadowLight: '0 1px 2px rgba(0,0,0,0.05)', + shadowMedium: '0 2px 8px rgba(0,0,0,0.08)', + radiusSmall: '2px', + radiusMedium: '4px', + radiusLarge: '8px', + spacing8: '8px', + spacing12: '12px', + spacing16: '16px', + spacing24: '24px', + fontSize14: '14px', + fontSize16: '16px', + fontFamily: '-apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", Arial, sans-serif', + link: '#165DFF' +}; + +const Component = function () { + var antd = window.antd; + var Input = antd.Input; + var Select = antd.Select; + var Button = antd.Button; + var DatePicker = antd.DatePicker; + var Table = antd.Table; + var Modal = antd.Modal; + var message = antd.message; + var Option = Select.Option; + var Spin = antd.Spin; + + var _useState = React.useState(''); + var filterDateStart = _useState[0]; + var setFilterDateStart = _useState[1]; + + var _useState2 = React.useState(''); + var filterDateEnd = _useState2[0]; + var setFilterDateEnd = _useState2[1]; + + var _useState3 = React.useState(''); + var filterOperator = _useState3[0]; + var setFilterOperator = _useState3[1]; + + var _useState4 = React.useState(''); + var filterPlateNo = _useState4[0]; + var setFilterPlateNo = _useState4[1]; + + var _useState5 = React.useState(''); + var filterVin = _useState5[0]; + var setFilterVin = _useState5[1]; + + var _useState5h = React.useState(''); + var appliedDateStart = _useState5h[0]; + var setAppliedDateStart = _useState5h[1]; + var _useState5i = React.useState(''); + var appliedDateEnd = _useState5i[0]; + var setAppliedDateEnd = _useState5i[1]; + var _useState5j = React.useState(''); + var appliedOperator = _useState5j[0]; + var setAppliedOperator = _useState5j[1]; + var _useState5k = React.useState(''); + var appliedPlateNo = _useState5k[0]; + var setAppliedPlateNo = _useState5k[1]; + var _useState5l = React.useState(''); + var appliedVin = _useState5l[0]; + var setAppliedVin = _useState5l[1]; + + var _useState6 = React.useState(1); + var currentPage = _useState6[0]; + var setCurrentPage = _useState6[1]; + + var _useState7 = React.useState(10); + var pageSize = _useState7[0]; + var setPageSize = _useState7[1]; + + var _useState9 = React.useState(null); + var viewPhotoRecord = _useState9[0]; + var setViewPhotoRecord = _useState9[1]; + + var _useState10 = React.useState(false); + var ocrModalVisible = _useState10[0]; + var setOcrModalVisible = _useState10[1]; + + var _useState11 = React.useState(false); + var confirmModalVisible = _useState11[0]; + var setConfirmModalVisible = _useState11[1]; + + var _useState12 = React.useState(null); + var confirmData = _useState12[0]; + var setConfirmData = _useState12[1]; + + var _useState13 = React.useState([]); + var batchConfirmList = _useState13[0]; + var setBatchConfirmList = _useState13[1]; + + var _useState14 = React.useState(0); + var batchConfirmIndex = _useState14[0]; + var setBatchConfirmIndex = _useState14[1]; + + var _useState15 = React.useState(false); + var isBatchMode = _useState15[0]; + var setIsBatchMode = _useState15[1]; + + var _useState15b = React.useState(0); + var batchTotalCount = _useState15b[0]; + var setBatchTotalCount = _useState15b[1]; + + var _useState16 = React.useState(''); + var confirmVin = _useState16[0]; + var setConfirmVin = _useState16[1]; + + var _useState17 = React.useState(''); + var confirmPlateNo = _useState17[0]; + var setConfirmPlateNo = _useState17[1]; + + var _useState17a = React.useState(''); + var confirmScrapDate = _useState17a[0]; + var setConfirmScrapDate = _useState17a[1]; + + var _useState17b = React.useState(''); + var confirmInspectionExpiry = _useState17b[0]; + var setConfirmInspectionExpiry = _useState17b[1]; + + var _useState18b = React.useState(false); + var showRequirementModal = _useState18b[0]; + var setShowRequirementModal = _useState18b[1]; + + var _useState18c = React.useState(false); + var batchTaskCardVisible = _useState18c[0]; + var setBatchTaskCardVisible = _useState18c[1]; + + var fileInputRef = React.useRef(null); + var batchFullListRef = React.useRef(null); + var batchUploadFromCardRef = React.useRef(false); + var batchFileInputRef = React.useRef(null); + + var _useState18e = React.useState([]); + var batchTaskList = _useState18e[0]; + var setBatchTaskList = _useState18e[1]; + + var mockVehicleList = [ + { id: 'v001', frameNo: 'LGW123456', brand: '比亚迪', model: '秦', vehicleType: '轿车' }, + { id: 'v002', frameNo: 'LGW789012', brand: '特斯拉', model: 'Model 3', vehicleType: '轿车' }, + { id: 'v003', frameNo: 'HZ111222', brand: '小鹏', model: 'P7', vehicleType: '轿车' } + ]; + + var initialRecordList = [ + { + id: 'r001', + plateDate: '2025-02-01', + operator: '张明', + plateNo: '粤A12345', + vin: 'LGW123456', + vehicleType: '轿车', + brand: '比亚迪', + model: '秦', + photoUrl: 'https://picsum.photos/300/200?random=1' + }, + { + id: 'r002', + plateDate: '2025-02-03', + operator: '王芳', + plateNo: '粤A67890', + vin: 'LGW789012', + vehicleType: '轿车', + brand: '特斯拉', + model: 'Model 3', + photoUrl: 'https://picsum.photos/300/200?random=2' + } + ]; + + // 近5条批量上牌任务(时间、照片数量、完成进度);进度100%时有 items 用于「识别」进入确认界面 + var getInitialBatchTaskList = function () { + return [ + { id: 'bt1', createTime: '2025-02-12 10:30', photoCount: 3, progress: 100, items: [ + { photoUrl: 'https://picsum.photos/300/200?random=b1', vin: 'LGW123456', plateNo: '粤A11111', vehicle: mockVehicleList[0] }, + { photoUrl: 'https://picsum.photos/300/200?random=b2', vin: 'LGW789012', plateNo: '粤A22222', vehicle: mockVehicleList[1] }, + { photoUrl: 'https://picsum.photos/300/200?random=b3', vin: 'HZ111222', plateNo: '粤A33333', vehicle: mockVehicleList[2] } + ]}, + { id: 'bt2', createTime: '2025-02-12 09:15', photoCount: 5, progress: 100, items: [ + { photoUrl: 'https://picsum.photos/300/200?random=b4', vin: 'LGW123456', plateNo: '粤A44444', vehicle: mockVehicleList[0] }, + { photoUrl: 'https://picsum.photos/300/200?random=b5', vin: 'LGW789012', plateNo: '粤A55555', vehicle: mockVehicleList[1] } + ]}, + { id: 'bt3', createTime: '2025-02-11 16:20', photoCount: 4, progress: 80, items: null }, + { id: 'bt4', createTime: '2025-02-11 14:00', photoCount: 2, progress: 50, items: null }, + { id: 'bt5', createTime: '2025-02-10 11:30', photoCount: 6, progress: 30, items: null } + ]; + }; + + React.useEffect(function () { + setBatchTaskList(function (prev) { + if (prev.length === 0) return getInitialBatchTaskList(); + return prev; + }); + }, []); + + var _useState18 = React.useState(initialRecordList); + var recordList = _useState18[0]; + var setRecordList = _useState18[1]; + + var getUniqueOperators = function () { + var seen = {}; + var list = []; + recordList.forEach(function (r) { + if (r.operator && !seen[r.operator]) { + seen[r.operator] = true; + list.push(r.operator); + } + }); + return list.sort(); + }; + + var getUniquePlateNos = function () { + var seen = {}; + var list = []; + recordList.forEach(function (r) { + if (r.plateNo && !seen[r.plateNo]) { + seen[r.plateNo] = true; + list.push(r.plateNo); + } + }); + return list.sort(); + }; + + var getUniqueVins = function () { + var seen = {}; + var list = []; + recordList.forEach(function (r) { + if (r.vin && !seen[r.vin]) { + seen[r.vin] = true; + list.push(r.vin); + } + }); + mockVehicleList.forEach(function (v) { + if (v.frameNo && !seen[v.frameNo]) { + seen[v.frameNo] = true; + list.push(v.frameNo); + } + }); + return list.sort(); + }; + + var allOperators = getUniqueOperators(); + var allPlateNos = getUniquePlateNos(); + var allVins = getUniqueVins(); + + var getFilteredList = function () { + var list = recordList; + if (appliedDateStart) { + list = list.filter(function (r) { return r.plateDate >= appliedDateStart; }); + } + if (appliedDateEnd) { + list = list.filter(function (r) { return r.plateDate <= appliedDateEnd; }); + } + if (appliedOperator) { + list = list.filter(function (r) { + return r.operator && r.operator.indexOf(appliedOperator) >= 0; + }); + } + if (appliedPlateNo) { + list = list.filter(function (r) { + return r.plateNo && r.plateNo.indexOf(appliedPlateNo) >= 0; + }); + } + if (appliedVin) { + list = list.filter(function (r) { + return r.vin && r.vin.indexOf(appliedVin) >= 0; + }); + } + return list; + }; + + var filteredList = getFilteredList(); + var totalItems = filteredList.length; + var totalPages = Math.ceil(totalItems / pageSize) || 1; + var validPage = currentPage > totalPages && totalPages > 0 ? 1 : (currentPage < 1 ? 1 : currentPage); + var startIndex = (validPage - 1) * pageSize; + var endIndex = startIndex + pageSize; + var paginatedList = filteredList.slice(startIndex, endIndex); + + var findVehicleByVin = function (vin) { + return mockVehicleList.find(function (v) { return v.frameNo === vin; }); + }; + + var todayStr = function () { + var d = new Date(); + var y = d.getFullYear(); + var m = (d.getMonth() + 1).toString(); + var day = d.getDate().toString(); + if (m.length === 1) { m = '0' + m; } + if (day.length === 1) { day = '0' + day; } + return y + '-' + m + '-' + day; + }; + + var sampleScrapDates = ['2035-12-31', '2030-06-15', '2028-03-20']; + var sampleInspectionExpiries = ['2026-06', '2025-12', '2027-03']; + var getSampleScrapDate = function () { return sampleScrapDates[Math.floor(Math.random() * sampleScrapDates.length)]; }; + var getSampleInspectionExpiry = function () { return sampleInspectionExpiries[Math.floor(Math.random() * sampleInspectionExpiries.length)]; }; + + var processFileAndGetItem = function (file, callback) { + var reader = new FileReader(); + reader.onload = function () { + var photoUrl = reader.result; + var mockVin = mockVehicleList[0].frameNo; + var mockPlate = '粤A' + Math.floor(Math.random() * 90000 + 10000).toString(); + var vehicle = findVehicleByVin(mockVin); + var item = { + photoUrl: photoUrl, + vin: mockVin, + plateNo: mockPlate, + vehicle: vehicle + }; + callback(item); + }; + reader.readAsDataURL(file); + }; + + var simulateOcrAndConfirm = function (items, isMulti) { + setOcrModalVisible(false); + if (isMulti && items.length > 1) { + batchFullListRef.current = items.slice(); + setBatchConfirmList(items); + setBatchConfirmIndex(0); + setBatchTotalCount(items.length); + setConfirmData(items[0]); + setConfirmVin(items[0].vin); + setConfirmPlateNo(items[0].plateNo); + setConfirmScrapDate(getSampleScrapDate()); + setConfirmInspectionExpiry(getSampleInspectionExpiry()); + setConfirmModalVisible(true); + setIsBatchMode(true); + } else if (items.length > 0) { + setConfirmData(items[0]); + setConfirmVin(items[0].vin); + setConfirmPlateNo(items[0].plateNo); + setConfirmScrapDate(getSampleScrapDate()); + setConfirmInspectionExpiry(getSampleInspectionExpiry()); + setConfirmModalVisible(true); + setIsBatchMode(false); + } + }; + + var handleUpload = function (e, isBatch) { + var files = e.target.files; + if (!files || files.length === 0) return; + // 从批量上传弹框内「上传车牌」触发:不进入识别/确认页,只在弹框中新增一条识别任务 + if (batchUploadFromCardRef.current) { + batchUploadFromCardRef.current = false; + var fileCount = files.length; + var collected = []; + var processed = 0; + var checkDone = function () { + processed = processed + 1; + if (processed === fileCount) { + var now = new Date(); + var timeStr = now.getFullYear() + '-' + String(now.getMonth() + 1).padStart(2, '0') + '-' + String(now.getDate()).padStart(2, '0') + ' ' + String(now.getHours()).padStart(2, '0') + ':' + String(now.getMinutes()).padStart(2, '0'); + setBatchTaskList(function (prev) { + var next = [{ id: 'bt' + Date.now(), createTime: timeStr, photoCount: collected.length, progress: 100, items: collected }].concat(prev); + return next.slice(0, 5); + }); + } + }; + for (var i = 0; i < fileCount; i++) { + (function (idx) { + processFileAndGetItem(files[idx], function (item) { + collected.push(item); + checkDone(); + }); + })(i); + } + e.target.value = ''; + return; + } + setOcrModalVisible(true); + var fileCount = files.length; + var collected = []; + var processed = 0; + var checkDone = function () { + processed = processed + 1; + if (processed === fileCount) { + setTimeout(function () { + simulateOcrAndConfirm(collected, isBatch && collected.length > 1); + }, 1500); + } + }; + for (var i = 0; i < fileCount; i++) { + (function (idx) { + processFileAndGetItem(files[idx], function (item) { + collected.push(item); + checkDone(); + }); + })(i); + } + e.target.value = ''; + }; + + var handleConfirmSubmit = function () { + var vehicle = confirmData && confirmData.vehicle; + var newRecord = { + id: 'r' + Date.now(), + plateDate: todayStr(), + operator: '当前用户', + plateNo: confirmPlateNo, + vin: confirmVin, + vehicleType: vehicle ? vehicle.vehicleType : '轿车', + brand: vehicle ? vehicle.brand : '-', + model: vehicle ? vehicle.model : '-', + photoUrl: confirmData && confirmData.photoUrl ? confirmData.photoUrl : 'https://picsum.photos/300/200?random=' + Date.now() + }; + var nextList = recordList.slice(); + nextList.unshift(newRecord); + setRecordList(nextList); + message.success('车辆上牌成功'); + if (isBatchMode && batchConfirmList.length > 1) { + var nextBatch = batchConfirmList.slice(1); + var nextItem = nextBatch[0]; + setBatchConfirmList(nextBatch); + setBatchConfirmIndex(batchConfirmIndex + 1); + if (nextItem) { + setConfirmData(nextItem); + setConfirmVin(nextItem.vin); + setConfirmPlateNo(nextItem.plateNo); + setConfirmScrapDate(getSampleScrapDate()); + setConfirmInspectionExpiry(getSampleInspectionExpiry()); + } else { + var fullList = batchFullListRef.current; + if (fullList && fullList.length > 0) { + var now = new Date(); + var timeStr = now.getFullYear() + '-' + String(now.getMonth() + 1).padStart(2, '0') + '-' + String(now.getDate()).padStart(2, '0') + ' ' + String(now.getHours()).padStart(2, '0') + ':' + String(now.getMinutes()).padStart(2, '0'); + setBatchTaskList(function (prev) { + var next = [{ id: 'bt' + Date.now(), createTime: timeStr, photoCount: fullList.length, progress: 100, items: fullList }].concat(prev); + return next.slice(0, 5); + }); + batchFullListRef.current = null; + } + setConfirmModalVisible(false); + setConfirmData(null); + setConfirmVin(''); + setConfirmPlateNo(''); + setConfirmScrapDate(''); + setConfirmInspectionExpiry(''); + setBatchConfirmList([]); + setBatchTotalCount(0); + setIsBatchMode(false); + } + } else { + setConfirmModalVisible(false); + setConfirmData(null); + setConfirmVin(''); + setConfirmPlateNo(''); + setConfirmScrapDate(''); + setConfirmInspectionExpiry(''); + setBatchConfirmList([]); + setBatchTotalCount(0); + setIsBatchMode(false); + } + }; + + var handleConfirmCancel = function () { + setConfirmModalVisible(false); + setConfirmData(null); + setConfirmVin(''); + setConfirmPlateNo(''); + setConfirmScrapDate(''); + setConfirmInspectionExpiry(''); + setBatchConfirmList([]); + setBatchConfirmIndex(0); + setBatchTotalCount(0); + setIsBatchMode(false); + }; + + var t = ARCO_TOKEN; + var styles = { + page: { padding: t.spacing24, fontFamily: t.fontFamily, backgroundColor: t.fill, minHeight: '100vh' }, + breadcrumb: { marginBottom: t.spacing16, fontSize: t.fontSize14, color: t.neutral6, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }, + breadcrumbLeft: { display: 'flex', alignItems: 'center' }, + breadcrumbLink: { color: t.link, textDecoration: 'none', marginRight: t.spacing8 }, + breadcrumbCurrent: { color: t.neutral8 }, + breadcrumbRight: { display: 'flex', alignItems: 'center' }, + requirementLink: { color: t.link, textDecoration: 'none', fontSize: t.fontSize14, cursor: 'pointer' }, + card: { backgroundColor: t.neutral1, borderRadius: t.radiusLarge, boxShadow: t.shadowLight, marginBottom: t.spacing16, padding: t.spacing16 }, + filterRow: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: t.spacing12 }, + filterRowRight: { display: 'flex', alignItems: 'center', gap: t.spacing8, marginLeft: 'auto' }, + label: { marginRight: t.spacing8, fontSize: t.fontSize14, color: t.neutral8, whiteSpace: 'nowrap' }, + input: { padding: '0 10px', height: '32px', width: '180px', borderRadius: '2px', border: '1px solid ' + t.border, fontSize: t.fontSize14, boxSizing: 'border-box' }, + btn: { padding: t.spacing8 + ' ' + t.spacing16, borderRadius: t.radiusMedium, cursor: 'pointer', fontSize: t.fontSize14, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: '6px', boxSizing: 'border-box' }, + btnFixed: { width: '82px', height: '32px', padding: 0, borderRadius: '2px', lineHeight: '1' }, + btnIcon: { width: '14px', height: '14px', flexShrink: 0, display: 'block' }, + btnFillBlue: { backgroundColor: t.primary, color: t.neutral1, border: 'none' }, + btnOutlineBlue: { backgroundColor: t.neutral1, color: t.primary, border: '1px solid ' + t.primary }, + btnDefault: { backgroundColor: t.neutral1, color: t.neutral8, border: '1px solid ' + t.border }, + btnSize82: { width: '82px', height: '32px', padding: 0, lineHeight: '1', boxSizing: 'border-box' }, + toolbar: { display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: t.spacing12, marginBottom: t.spacing16 }, + tableWrap: { overflowX: 'auto', backgroundColor: t.neutral1, borderRadius: t.radiusMedium, border: '1px solid ' + t.neutral4 }, + table: { width: '100%', borderCollapse: 'separate', borderSpacing: 0, fontSize: t.fontSize14 }, + th: { textAlign: 'left', padding: '12px 16px', backgroundColor: t.fillSecondary, borderBottom: '1px solid ' + t.neutral4, fontWeight: 600, color: t.neutral8, fontSize: t.fontSize14, whiteSpace: 'nowrap' }, + td: { padding: '12px 16px', borderBottom: '1px solid ' + t.neutral4, color: t.neutral8, fontSize: t.fontSize14 }, + pagination: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '16px', borderTop: '1px solid ' + t.neutral4, backgroundColor: t.neutral1 }, + paginationLeft: { display: 'flex', alignItems: 'center', gap: '8px', fontSize: t.fontSize14, color: t.neutral7 }, + paginationRight: { display: 'flex', alignItems: 'center', gap: '8px' }, + paginationSelect: { padding: '4px 8px', height: '28px', borderRadius: '2px', border: '1px solid ' + t.border, fontSize: t.fontSize14, backgroundColor: t.neutral1 }, + paginationBtn: { minWidth: '28px', height: '28px', padding: '0 8px', borderRadius: '2px', border: '1px solid ' + t.border, backgroundColor: t.neutral1, color: t.neutral8, cursor: 'pointer', fontSize: t.fontSize14, display: 'inline-flex', alignItems: 'center', justifyContent: 'center' }, + paginationBtnActive: { backgroundColor: t.primary, color: t.neutral1, borderColor: t.primary }, + paginationBtnDisabled: { opacity: 0.5, cursor: 'not-allowed' }, + paginationInput: { width: '50px', height: '28px', padding: '0 8px', borderRadius: '2px', border: '1px solid ' + t.border, fontSize: t.fontSize14, textAlign: 'center' }, + actionLink: { color: t.link, cursor: 'pointer', marginRight: t.spacing12, fontSize: t.fontSize14 }, + modalMask: { position: 'fixed', left: 0, top: 0, right: 0, bottom: 0, backgroundColor: 'rgba(0,0,0,0.45)', zIndex: 1000, display: 'flex', alignItems: 'center', justifyContent: 'center' }, + modalBox: { backgroundColor: t.neutral1, borderRadius: t.radiusLarge, maxWidth: '90%', maxHeight: '90%', overflow: 'auto', padding: t.spacing24, minWidth: '500px', position: 'relative' }, + modalTitle: { fontSize: t.fontSize16, fontWeight: 600, marginBottom: t.spacing16, color: t.neutral8 }, + modalFooter: { marginTop: t.spacing24, display: 'flex', justifyContent: 'flex-end', gap: t.spacing8 }, + toast: { position: 'fixed', top: t.spacing24, left: '50%', transform: 'translateX(-50%)', backgroundColor: 'rgba(0,0,0,0.75)', color: t.neutral1, padding: '10px 20px', borderRadius: t.radiusMedium, zIndex: 2000, fontSize: t.fontSize14 }, + autocompleteWrap: { position: 'relative', width: '180px', display: 'inline-block' }, + autocompletePanel: { position: 'absolute', left: 0, top: '100%', marginTop: '4px', backgroundColor: t.neutral1, borderRadius: t.radiusMedium, border: '1px solid ' + t.border, boxShadow: t.shadowMedium, zIndex: 100, minWidth: '100%', maxHeight: '200px', overflowY: 'auto' }, + autocompleteOption: { padding: '8px 12px', fontSize: t.fontSize14, color: t.neutral8, cursor: 'pointer' }, + autocompleteOptionHover: { backgroundColor: t.fill }, + confirmCard: { display: 'flex', gap: t.spacing24, marginTop: t.spacing16 }, + confirmPhoto: { flex: '0 0 300px', height: '200px', borderRadius: t.radiusMedium, overflow: 'hidden', backgroundColor: t.neutral3 }, + confirmPhotoImg: { width: '100%', height: '100%', objectFit: 'cover' }, + confirmForm: { flex: 1, display: 'flex', flexDirection: 'column', gap: t.spacing16 }, + formLabel: { display: 'block', marginBottom: '6px', fontSize: t.fontSize14, color: t.neutral8 }, + formInput: { padding: '8px 12px', height: '36px', borderRadius: t.radiusMedium, border: '1px solid ' + t.border, fontSize: t.fontSize14, width: '100%', boxSizing: 'border-box' }, + photoViewModal: { maxWidth: '800px', textAlign: 'center' }, + photoViewImg: { maxWidth: '100%', maxHeight: '70vh', borderRadius: t.radiusMedium }, + modalCloseBtn: { position: 'absolute', right: t.spacing16, top: t.spacing16, width: '24px', height: '24px', display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', borderRadius: t.radiusMedium, backgroundColor: 'transparent', border: 'none', color: t.neutral6, fontSize: '18px', lineHeight: '1', padding: 0 }, + modalContent: { fontSize: t.fontSize14, color: t.neutral8, lineHeight: '1.6' }, + requirementSection: { marginBottom: t.spacing16 }, + requirementSectionTitle: { fontSize: t.fontSize16, fontWeight: 600, color: t.neutral8, marginBottom: t.spacing8 }, + flowWrap: { marginTop: t.spacing8, marginBottom: t.spacing16 }, + flowCol: { display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 0 }, + flowArrow: { width: '2px', height: '20px', backgroundColor: t.neutral5 }, + flowNodeOval: { padding: '8px 20px', borderRadius: '20px', border: '1px solid ' + t.neutral5, backgroundColor: t.neutral2, fontSize: t.fontSize14, color: t.neutral8 }, + flowNodeRect: { padding: '8px 20px', border: '1px solid ' + t.neutral5, backgroundColor: t.neutral1, fontSize: t.fontSize14, color: t.neutral8 }, + flowNodeDiamond: { padding: '10px 16px', border: '1px solid ' + t.neutral5, backgroundColor: t.fillSecondary, fontSize: t.fontSize14, color: t.neutral8, transform: 'rotate(0deg)', width: '140px', textAlign: 'center', boxSizing: 'border-box', clipPath: 'polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%)' }, + flowNodeDiamondWrap: { padding: '8px 16px', border: '1px solid ' + t.neutral5, backgroundColor: t.fillSecondary, fontSize: t.fontSize14, color: t.neutral8, minWidth: '120px', textAlign: 'center' }, + flowNodeToast: { padding: '8px 16px', borderRadius: '8px', border: '1px solid ' + t.neutral4, backgroundColor: t.neutral2, fontSize: t.fontSize14, color: t.neutral7 }, + flowRow: { display: 'flex', alignItems: 'flex-start', justifyContent: 'center', gap: '24px', flexWrap: 'wrap' }, + flowBranch: { display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 0 }, + requirementItem: { marginBottom: t.spacing8, paddingLeft: t.spacing16 }, + requirementSubItem: { marginBottom: t.spacing4, paddingLeft: t.spacing16, fontSize: t.fontSize14, color: t.neutral7 }, + batchTaskCardHeader: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: t.spacing16 }, + batchTaskTable: { width: '100%', borderCollapse: 'collapse', fontSize: t.fontSize14 }, + batchTaskTh: { textAlign: 'left', padding: '10px 12px', borderBottom: '1px solid ' + t.neutral4, color: t.neutral7, fontWeight: 500 }, + batchTaskTd: { padding: '10px 12px', borderBottom: '1px solid ' + t.neutral4, color: t.neutral8 }, + progressWrap: { width: '120px', height: '8px', backgroundColor: t.neutral4, borderRadius: '4px', overflow: 'hidden' }, + progressBar: { height: '100%', backgroundColor: t.primary, borderRadius: '4px', transition: 'width 0.2s' }, + recognizeLink: { color: t.link, cursor: 'pointer', fontSize: t.fontSize14 }, + dateRangeWrap: { position: 'relative', display: 'inline-block' }, + dateRangeTrigger: { display: 'flex', alignItems: 'center', height: '32px', padding: '0 12px', border: '1px solid ' + t.border, borderRadius: '2px', backgroundColor: t.neutral1, cursor: 'pointer', minWidth: '280px', boxSizing: 'border-box' }, + dateRangeTriggerFocused: { borderColor: t.primary, outline: 'none' }, + dateRangeLabel: { display: 'flex', alignItems: 'center', gap: '6px', padding: '0 8px', height: '100%', fontSize: t.fontSize14, color: t.neutral8 }, + dateRangeLabelActive: { backgroundColor: 'rgba(22,93,255,0.1)', color: t.primary, borderBottom: '2px solid ' + t.primary }, + dateRangeLabelText: { whiteSpace: 'nowrap' }, + dateRangeDash: { color: t.neutral5, margin: '0 4px', fontSize: t.fontSize14 }, + dateRangeIcon: { marginLeft: 'auto', width: '16px', height: '16px', color: t.neutral6, flexShrink: 0 }, + dateRangePanel: { position: 'absolute', left: 0, top: '100%', marginTop: '4px', backgroundColor: t.neutral1, borderRadius: t.radiusMedium, border: '1px solid ' + t.border, boxShadow: t.shadowMedium, zIndex: 100, padding: '16px', display: 'flex', gap: '24px' }, + dateRangeCalendar: { width: '280px' }, + dateRangeCalendarHeader: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '12px', padding: '0 4px' }, + dateRangeCalendarTitle: { fontSize: t.fontSize14, fontWeight: 500, color: t.neutral8 }, + dateRangeCalendarNav: { display: 'flex', alignItems: 'center', gap: '4px' }, + dateRangeNavBtn: { width: '28px', height: '28px', display: 'flex', alignItems: 'center', justifyContent: 'center', border: 'none', backgroundColor: 'transparent', color: t.neutral6, cursor: 'pointer', borderRadius: t.radiusSmall }, + dateRangeNavBtnHover: { color: t.primary, backgroundColor: t.fill }, + dateRangeWeekRow: { display: 'flex', marginBottom: '4px' }, + dateRangeWeekCell: { width: '36px', height: '28px', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '12px', color: t.neutral6 }, + dateRangeDayRow: { display: 'flex' }, + dateRangeDayCell: { width: '36px', height: '36px', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', fontSize: t.fontSize14, cursor: 'pointer', borderRadius: t.radiusSmall, color: t.neutral8 }, + dateRangeDayCellOther: { color: t.neutral5 }, + dateRangeDayCellSelected: { color: t.primary }, + dateRangeDayCellInRange: { backgroundColor: 'rgba(22,93,255,0.08)', color: t.primary }, + dateRangeDayDot: { width: '4px', height: '4px', borderRadius: '50%', backgroundColor: t.primary, marginTop: '2px' } + }; + + var renderFilterSelect = function (value, setValue, options, placeholder) { + var opts = (options || []).map(function (o) { return React.createElement(Option, { key: o, value: o }, o); }); + return React.createElement(Select, { + placeholder: placeholder || '请选择或输入搜索', + style: { width: 180 }, + value: value || undefined, + onChange: function (v) { setValue(v || ''); }, + showSearch: true, + allowClear: true, + filterOption: function (input, opt) { + var c = opt && opt.children; + return c && String(c).toLowerCase().indexOf((input || '').toLowerCase()) >= 0; + } + }, opts); + }; + + return React.createElement( + 'div', + { style: styles.page }, + React.createElement( + 'div', + { style: styles.breadcrumb }, + React.createElement( + 'div', + { style: styles.breadcrumbLeft }, + React.createElement('a', { href: '#', style: styles.breadcrumbLink, onClick: function (e) { e.preventDefault(); } }, '运维管理'), + React.createElement('span', { style: { marginRight: '8px' } }, '/'), + React.createElement('a', { href: '#', style: styles.breadcrumbLink, onClick: function (e) { e.preventDefault(); } }, '车辆业务'), + React.createElement('span', { style: { marginRight: '8px' } }, '/'), + React.createElement('span', { style: styles.breadcrumbCurrent }, '上牌管理') + ), + React.createElement( + 'div', + { style: styles.breadcrumbRight }, + React.createElement('a', { href: '#', style: styles.requirementLink, onClick: function (e) { e.preventDefault(); setShowRequirementModal(true); } }, '查看需求说明') + ) + ), + React.createElement( + 'div', + { style: styles.card }, + React.createElement( + 'div', + { style: styles.filterRow }, + React.createElement('span', { style: styles.label }, '上牌日期:'), + React.createElement(DatePicker, { + style: { width: 180 }, + format: 'YYYY-MM-DD', + placeholder: '开始日期', + value: filterDateStart && window.moment ? window.moment(filterDateStart, 'YYYY-MM-DD') : null, + onChange: function (d, dateStr) { setFilterDateStart(dateStr || ''); } + }), + React.createElement('span', { style: { color: t.neutral6, margin: '0 4px' } }, '至'), + React.createElement(DatePicker, { + style: { width: 180 }, + format: 'YYYY-MM-DD', + placeholder: '结束日期', + value: filterDateEnd && window.moment ? window.moment(filterDateEnd, 'YYYY-MM-DD') : null, + onChange: function (d, dateStr) { setFilterDateEnd(dateStr || ''); } + }), + React.createElement('span', { style: styles.label }, '操作人:'), + renderFilterSelect(filterOperator, setFilterOperator, allOperators, '请选择操作人'), + React.createElement('span', { style: styles.label }, '车牌号:'), + renderFilterSelect(filterPlateNo, setFilterPlateNo, allPlateNos, '请选择车牌号'), + React.createElement('span', { style: styles.label }, '车辆识别代码:'), + renderFilterSelect(filterVin, setFilterVin, allVins, '请选择车辆识别代码'), + React.createElement( + 'div', + { style: styles.filterRowRight }, + React.createElement(Button, { + type: 'primary', + onClick: function () { + setAppliedDateStart(filterDateStart); + setAppliedDateEnd(filterDateEnd); + setAppliedOperator(filterOperator); + setAppliedPlateNo(filterPlateNo); + setAppliedVin(filterVin); + setCurrentPage(1); + message.success('查询成功'); + } + }, '查询'), + React.createElement(Button, { + onClick: function () { + setFilterDateStart(''); + setFilterDateEnd(''); + setAppliedDateStart(''); + setAppliedDateEnd(''); + setFilterOperator(''); + setFilterPlateNo(''); + setFilterVin(''); + setAppliedOperator(''); + setAppliedPlateNo(''); + setAppliedVin(''); + setCurrentPage(1); + } + }, '重置') + ) + ) + ), + React.createElement( + 'div', + { style: styles.card }, + React.createElement( + 'div', + { style: styles.toolbar }, + React.createElement('input', { + type: 'file', + ref: fileInputRef, + accept: 'image/*', + style: { display: 'none' }, + onChange: function (e) { handleUpload(e, false); } + }), + React.createElement('input', { + type: 'file', + ref: batchFileInputRef, + accept: 'image/*', + multiple: true, + style: { display: 'none' }, + onChange: function (e) { handleUpload(e, true); } + }), + React.createElement(Button, { + type: 'primary', + onClick: function () { if (fileInputRef.current) fileInputRef.current.click(); } + }, '新增'), + React.createElement(Button, { + onClick: function () { setBatchTaskCardVisible(true); } + }, '批量上传') + ), + React.createElement(Table, { + rowKey: 'id', + size: 'small', + columns: [ + { title: '上牌日期', dataIndex: 'plateDate', key: 'plateDate', width: 120 }, + { title: '操作人', dataIndex: 'operator', key: 'operator', width: 100 }, + { title: '车牌号', dataIndex: 'plateNo', key: 'plateNo', width: 120 }, + { title: '车辆识别代码', dataIndex: 'vin', key: 'vin', width: 140 }, + { title: '车辆类型', dataIndex: 'vehicleType', key: 'vehicleType', width: 100 }, + { title: '品牌', dataIndex: 'brand', key: 'brand', width: 100 }, + { title: '型号', dataIndex: 'model', key: 'model', width: 120 }, + { title: '操作', key: 'action', width: 80, render: function (_, row) { return React.createElement(Button, { type: 'link', size: 'small', onClick: function () { setViewPhotoRecord(row); } }, '查看'); } } + ], + dataSource: paginatedList, + pagination: { + current: validPage, + pageSize: pageSize, + total: totalItems, + showSizeChanger: true, + showQuickJumper: true, + pageSizeOptions: ['10', '20', '50', '100'], + showTotal: function (total) { return '共 ' + total + ' 条'; }, + onChange: function (page, size) { + setCurrentPage(page); + if (size !== pageSize) setPageSize(size); + } + } + }) + ), + React.createElement(Modal, { + title: '识别中,请勿关闭页面', + visible: ocrModalVisible, + footer: null, + closable: false, + maskClosable: false, + children: React.createElement('div', { style: { padding: '24px 0', textAlign: 'center' } }, + React.createElement(Spin, { size: 'large' }), + React.createElement('p', { style: { marginTop: 12, color: t.neutral6 } }, '正在识别行驶证信息...') + ) + }), + React.createElement(Modal, { + title: isBatchMode && batchTotalCount > 1 ? '确认上牌信息(' + (batchTotalCount - batchConfirmList.length + 1) + '/' + batchTotalCount + ')' : '确认上牌信息', + visible: confirmModalVisible && !!confirmData, + onCancel: handleConfirmCancel, + onOk: handleConfirmSubmit, + okText: '确认', + cancelText: '取消', + width: 560, + children: confirmData ? React.createElement( + 'div', + { style: styles.confirmCard }, + React.createElement('div', { style: styles.confirmPhoto }, + React.createElement('img', { src: confirmData.photoUrl, alt: '行驶证', style: styles.confirmPhotoImg }) + ), + React.createElement('div', { style: styles.confirmForm }, + React.createElement('div', { style: { marginBottom: 16 } }, + React.createElement('label', { style: styles.formLabel }, '车辆识别代号'), + React.createElement(Input, { + style: { width: '100%' }, + value: confirmVin, + onChange: function (e) { setConfirmVin(e.target.value); } + }) + ), + React.createElement('div', { style: { marginBottom: 16 } }, + React.createElement('label', { style: styles.formLabel }, '车牌号'), + React.createElement(Input, { + style: { width: '100%' }, + value: confirmPlateNo, + onChange: function (e) { setConfirmPlateNo(e.target.value); } + }) + ), + React.createElement('div', { style: { marginBottom: 16 } }, + React.createElement('label', { style: styles.formLabel }, '强制报废日期'), + React.createElement(Input, { + style: { width: '100%' }, + placeholder: 'YYYY-MM-DD', + value: confirmScrapDate, + onChange: function (e) { setConfirmScrapDate(e.target.value); } + }) + ), + React.createElement('div', null, + React.createElement('label', { style: styles.formLabel }, '检验有效期'), + React.createElement(Input, { + style: { width: '100%' }, + placeholder: 'YYYY-MM', + value: confirmInspectionExpiry, + onChange: function (e) { setConfirmInspectionExpiry(e.target.value); } + }) + ) + ) + ) : null + }), + React.createElement(Modal, { + title: '行驶证照片', + visible: !!viewPhotoRecord, + footer: React.createElement(Button, { onClick: function () { setViewPhotoRecord(null); } }, '关闭'), + onCancel: function () { setViewPhotoRecord(null); }, + width: 640, + children: viewPhotoRecord ? React.createElement('img', { src: viewPhotoRecord.photoUrl, alt: '行驶证', style: Object.assign({}, styles.photoViewImg, { width: '100%' }) }) : null + }), + React.createElement(Modal, { + title: '批量上牌任务', + visible: batchTaskCardVisible, + onCancel: function () { setBatchTaskCardVisible(false); }, + footer: React.createElement(Button, { onClick: function () { setBatchTaskCardVisible(false); } }, '关闭'), + width: 560, + children: React.createElement(React.Fragment, null, + React.createElement('div', { style: Object.assign({}, styles.batchTaskCardHeader, { marginBottom: 16 }) }, + React.createElement(Button, { + type: 'primary', + onClick: function () { + batchUploadFromCardRef.current = true; + if (batchFileInputRef.current) batchFileInputRef.current.click(); + } + }, '批量上传') + ), + React.createElement(Table, { + rowKey: 'id', + size: 'small', + columns: [ + { title: '任务时间', dataIndex: 'createTime', key: 'createTime', width: 160 }, + { title: '照片数量', dataIndex: 'photoCount', key: 'photoCount', width: 100, render: function (c) { return c + ' 张'; } }, + { title: '完成进度', key: 'progress', width: 160, render: function (_, task) { + return React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8 } }, + React.createElement('div', { style: styles.progressWrap }, + React.createElement('div', { style: Object.assign({}, styles.progressBar, { width: (task.progress || 0) + '%' }) }) + ), + React.createElement('span', { style: { fontSize: t.fontSize14, color: t.neutral7, minWidth: 36 } }, (task.progress || 0) + '%') + ); + } }, + { title: '操作', key: 'action', width: 80, render: function (_, task) { + return task.progress === 100 && task.items && task.items.length > 0 + ? React.createElement(Button, { type: 'link', size: 'small', onClick: function () { + setBatchConfirmList(task.items); + setBatchConfirmIndex(0); + setBatchTotalCount(task.items.length); + setConfirmData(task.items[0]); + setConfirmVin(task.items[0].vin); + setConfirmPlateNo(task.items[0].plateNo); + setConfirmScrapDate(getSampleScrapDate()); + setConfirmInspectionExpiry(getSampleInspectionExpiry()); + setConfirmModalVisible(true); + setIsBatchMode(true); + setBatchTaskCardVisible(false); + } }, '识别') + : React.createElement('span', { style: { color: t.neutral5, fontSize: t.fontSize14 } }, '-'); + } } + ], + dataSource: batchTaskList.slice(0, 5), + pagination: false + }) + ) + }), + React.createElement(Modal, { + title: '需求说明', + visible: showRequirementModal, + onCancel: function () { setShowRequirementModal(false); }, + footer: React.createElement(Button, { onClick: function () { setShowRequirementModal(false); } }, '关闭'), + width: 720, + children: React.createElement('div', { style: styles.modalContent }, + React.createElement('div', { style: Object.assign({}, styles.requirementSection, { marginBottom: t.spacing24 }) }, + React.createElement('div', { style: Object.assign({}, styles.requirementSectionTitle, { fontSize: '18px', marginBottom: t.spacing12 }) }, '上牌管理'), + React.createElement('div', { style: Object.assign({}, styles.requirementItem, { marginTop: 0, color: t.neutral7 }) }, '用以识别行驶证正反面,识别车架号、车牌号、强制报废日期(YYYY-MM-DD)、检验有效期(YYYY-MM)') + ), + React.createElement('div', { style: styles.requirementSection }, + React.createElement('div', { style: styles.requirementSectionTitle }, '1.面包屑:'), + React.createElement('div', { style: styles.requirementItem }, '运维管理-车辆业务-上牌管理') + ), + React.createElement('div', { style: styles.requirementSection }, + React.createElement('div', { style: styles.requirementSectionTitle }, '2.筛选:'), + React.createElement('div', { style: styles.requirementSubItem }, '2.1.上牌日期:双日历日期选择器,支持选择开始-结束时间,支持手动修改日期,但检验格式及判断结束日期不得早于开始日期;'), + React.createElement('div', { style: styles.requirementSubItem }, '2.2.操作人:选择器,支持从输入框输入内容模糊搜索,默认提示内容为:请选择操作人;'), + React.createElement('div', { style: styles.requirementSubItem }, '2.3.车牌号:选择器,支持从输入框输入内容模糊搜索,默认提示内容为:请选择车牌号;'), + React.createElement('div', { style: styles.requirementSubItem }, '2.4.车辆识别代码:选择器,支持从输入框输入内容模糊搜索,默认提示内容为:请选择车辆识别代码;') + ), + React.createElement('div', { style: styles.requirementSection }, + React.createElement('div', { style: styles.requirementSectionTitle }, '3.列表:'), + React.createElement('div', { style: styles.requirementItem }, '列表右侧按钮为新增、批量上传,字段依次为上牌日期、操作人、车牌号、车辆识别代码、车辆类型、品牌、型号、操作;'), + React.createElement('div', { style: styles.requirementSubItem }, '3.1.上牌日期:车辆上牌操作完成日期,格式为YYYY-MM-DD'), + React.createElement('div', { style: styles.requirementSubItem }, '3.2.操作人:车辆上牌记录操作用户姓名;'), + React.createElement('div', { style: styles.requirementSubItem }, '3.3.车牌号:显示上牌车牌号;通过新增按钮上传行驶证照片后,通过OCR识别技术,自动识别出该车辆识别代号对应车牌号,确认无误提交后自动反写'), + React.createElement('div', { style: styles.requirementSubItem }, '3.4.车辆识别代码:显示上牌车辆车辆识别代码;'), + React.createElement('div', { style: styles.requirementSubItem }, '3.5.车辆类型:显示该车辆识别代码对应车辆类型;'), + React.createElement('div', { style: styles.requirementSubItem }, '3.6.品牌:显示该车辆识别代码对应品牌;'), + React.createElement('div', { style: styles.requirementSubItem }, '3.7.型号:显示该车辆识别代码对应型号;'), + React.createElement('div', { style: styles.requirementSubItem }, '3.8.操作:行驶证,点击放大预览行驶证照片;'), + React.createElement('div', { style: styles.requirementItem }, '下方增加分页功能,支持选择单页数据条数;') + ), + React.createElement('div', { style: styles.requirementSection }, + React.createElement('div', { style: styles.requirementSectionTitle }, '4.新增:'), + React.createElement('div', { style: styles.requirementSubItem }, '4.1.点击新增按钮,上传行驶证照片附件,上传后OCR识别过程中弹出卡片提示:识别中,请勿关闭页面;如果照片识别失败,则提示:识别失败,请重新尝试;'), + React.createElement('div', { style: styles.requirementSubItem }, '4.2.上传成功后确认卡片中显示照片、车辆识别代码、车牌号、强制报废日期、检验有效期,可通过二次编辑进行校正;'), + React.createElement('div', { style: styles.requirementSubItem }, '4.3.点击卡片页面底部"确认"按钮,确认根据车辆识别代码判断是否存在该车辆,如果是则toast提示"上牌成功",并在列表中生成操作,如果否则toast提示:该车辆不存在。') + ), + React.createElement('div', { style: styles.requirementSection }, + React.createElement('div', { style: styles.requirementSectionTitle }, '5.批量上传:'), + React.createElement('div', { style: styles.requirementItem }, '5.1.点击批量上牌按钮,弹出批量上牌任务卡片,再点击右上角上传车牌,上传多张行驶证照片附件,上传后弹出批量上牌任务卡片:卡片中记录最近5条批量上牌任务,任务字段为任务时间、照片数量、识别错误、完成进度、操作;'), + React.createElement('div', { style: styles.requirementSubItem }, '5.1.1.任务时间:上传批量照片的时间,精确至分钟,格式为YYYY-MM-DD HH:MM;'), + React.createElement('div', { style: styles.requirementSubItem }, '5.1.2.照片数量:显示这一批照片总数;'), + React.createElement('div', { style: styles.requirementSubItem }, '5.1.3.识别错误:显示识别失败的照片总数,hover时气泡卡片显示识别失败的照片名称,以;分隔;'), + React.createElement('div', { style: styles.requirementSubItem }, '5.1.4.完成进度:显示ocr识别进度条和已完成百分比;'), + React.createElement('div', { style: styles.requirementSubItem }, '5.1.5.操作:已完成任务操作栏显示识别,未完成任务操作栏显示-;'), + React.createElement('div', { style: styles.requirementItem }, '5.2.识别:点击识别确认卡片中当前张数、显示照片、车辆识别代码、车牌号、强制报废日期、检验有效期,可通过二次编辑进行校正;当前张数显示在确认上牌信息标题右侧,格式为(当前/总计),依次操作直到完成所有记录提交完成,全部完成后toast提示:批量上传成功;'), + React.createElement('div', { style: styles.requirementItem }, '5.3.在识别确认卡片中点击确认后将立刻上传该照片,可再次通过点击批量上传拉取上传任务框的方式,自动跳转至最后一条上传记录进行操作;'), + React.createElement('div', { style: styles.requirementItem }, '5.4.批量上传任务中照片全部识别完成后,不再显示识别按钮;'), + React.createElement('div', { style: styles.requirementItem }, '5.5.具体识别流程请参考axure菜单目录中上牌管理下的流程图;') + ) + ) + }) + ); +}; +if (typeof window !== 'undefined') { + window.Component = Component; + function mount() { + var rootEl = document.getElementById('root'); + if (rootEl && window.ReactDOM && window.React) { + var root = ReactDOM.createRoot(rootEl); + root.render(React.createElement(Component)); + } + } + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', mount); + } else { + setTimeout(mount, 0); + } +} diff --git a/web端/运维管理/车辆业务/交车管理.jsx b/web端/运维管理/车辆业务/交车管理.jsx new file mode 100644 index 0000000..db935ce --- /dev/null +++ b/web端/运维管理/车辆业务/交车管理.jsx @@ -0,0 +1,538 @@ +// 【重要】必须使用 const Component 作为组件变量名 +// 车辆业务 - 交车管理(ONEOS运管平台,布局参照新增租赁合同) + +const Component = function () { + var useState = React.useState; + var useCallback = React.useCallback; + var useMemo = React.useMemo; + + var antd = window.antd; + var Breadcrumb = antd.Breadcrumb; + var Card = antd.Card; + var DatePicker = antd.DatePicker; + var Select = antd.Select; + var Button = antd.Button; + var Tabs = antd.Tabs; + var Table = antd.Table; + var Popover = antd.Popover; + var Cascader = antd.Cascader; + var Modal = antd.Modal; + var message = antd.message; + + var RangePicker = DatePicker.RangePicker; + + var filterState = useState({ + contractCode: undefined, + projectName: undefined, + customerName: undefined, + deliveryRegion: undefined, + dateStart: '', + dateEnd: '', + deliveryPerson: undefined + }); + var filters = filterState[0]; + var setFilters = filterState[1]; + var appliedFilterState = useState({ + contractCode: undefined, + projectName: undefined, + customerName: undefined, + deliveryRegion: undefined, + dateStart: '', + dateEnd: '', + deliveryPerson: undefined + }); + var appliedFilters = appliedFilterState[0]; + var setAppliedFilters = appliedFilterState[1]; + + var activeTabState = useState('pending'); + var activeTab = activeTabState[0]; + var setActiveTab = activeTabState[1]; + var pageState = useState(1); + var pageSizeState = useState(10); + var requirementModalOpen = useState(false); + var setRequirementModalOpen = requirementModalOpen[1]; + + var requirementDocContent = '交车管理\n\n1.面包屑:\n1.1.运维管理-车辆业务-交车管理\n\n2.筛选:\n2.1.合同编码:选择器,默认为所有合同;提示信息为:请输入或选择合同编码,支持从输入框输入内容进行模糊搜索,下拉显示结果;\n2.2.项目名称:选择器,默认为所有项目;提示信息为:请输入或选择项目名称,支持从输入框输入内容进行模糊搜索,下拉显示结果;\n3.3.客户名称:选择器,默认为所有客户;提示信息为:请输入或选择客户名称,支持从输入框输入内容进行模糊搜索,下拉显示结果;\n3.4.交车区域:地区选择器,支持省-市2级筛选;\n3.5.交车时间:日期选择器,默认提示信息为:请选择交车开始时间 请选择交车结束时间,单输入框,双日历,支持时间段选择,精确至天,格式为:YYYY-MM-DD - YYYY-MM-DD;\n3.6.交车人:选择器,默认为所有交车人;提示信息为:请输入或选择交车人姓名,支持从输入框输入内容进行模糊搜索,下拉显示结果;\n3.7.查询:点击查询,根据单个或多个筛选条件(且)联动表格进行查询;\n3.8.重置:点击清空查询条件至默认;\n\n3.列表:\n分为两个tab:待处理、历史记录\n3.1.待处理:显示以下字段:\n3.1.1.预计交车时间:支持单日及开始-结束日期两种方式,格式为:YYYY-MM-DD及YYYY-MM-DD至YYYY-MM-DD,取自对应交车任务中预计交车时间;\n3.1.2.任务发布时间:显示交车任务单生成时间,格式为:YYYY-MM-DD HH:MM;\n3.1.3.合同编码:显示租赁/自营合同编码;\n3.1.4.项目名称:显示租赁/自营合同项目名称;\n3.1.5.客户名称:显示租赁/自营合同客户名称;\n3.1.6.交车数量:显示交车数量,重点色显示,点击弹出气泡卡片,列表显示:车辆类型、品牌、型号、车牌号;\n 3.1.6.1.车辆类型:显示车辆类型;\n 3.1.6.2.品牌:显示车辆品牌;\n 3.1.6.3.型号:显示车辆型号;\n 3.1.6.4.车牌号:显示交车车辆车牌号,如果该车还未交车则显示为-;\n3.1.7.交车区域:显示交车区域,交车区域来自车辆租赁合同-交车区域,格式为省-市;\n3.1.8.交车地点:显示交车地点,交车地点来自车辆租赁合同-交车地点,显示详细地址;\n3.1.9.操作:查看、交车;\n 3.1.8.1.交车:点击跳转交车单页面;\n\n3.2.历史记录:显示以下字段:\n3.2.1.预计交车时间:支持单日及开始-结束日期两种方式,格式为:YYYY-MM-DD及YYYY-MM-DD至YYYY-MM-DD,取自对应交车任务中预计交车时间;\n3.2.2.任务发布时间:显示交车任务单生成时间,格式为:YYYY-MM-DD HH:MM;\n3.2.3.交车完成时间:显示交车单完成时间,格式为:YYYY-MM-DD HH:MM;\n3.2.4.交车人:显示交车单最终提交用户;\n3.2.5.合同编码:显示租赁/自营合同编码;\n3.2.6.项目名称:显示租赁/自营合同项目名称;\n3.2.7.客户名称:显示租赁/自营合同客户名称;\n3.2.8.交车数量:显示交车数,重点色显示,点击弹出气泡卡片,列表显示:车辆类型、品牌、型号、车牌号、实际交车日期、交车人;\n 3.2.8.1.车辆类型:显示车辆类型;\n 3.2.8.2.品牌:显示车辆品牌;\n 3.2.8.3.型号:显示车辆型号;\n 3.2.8.4.车牌号:显示交车车辆车牌号,如果该车还未交车则显示为-;\n 3.2.8.5.实际交车日期:显示实际交车日期,格式为YYYY-MM-DD,如该车还未交车则显示为-;\n 3.2.8.6.交车人:显示实际交车人用户姓名,如该车还未还车则显示为-;\n3.2.9.交车区域:显示交车区域,交车区域来自车辆租赁合同-交车区域,格式为省-市;\n3.2.10.交车地点:显示交车地点,交车地点来自车辆租赁合同-交车地点,显示详细地址;\n3.2.11.操作:查看;\n 3.2.11.1.查看:点击跳转查看交车单页面;'; + + // 交车区域:省-市 二级 + var regionOptions = [ + { value: 'zhejiang', label: '浙江省', children: [{ value: 'hangzhou', label: '杭州市' }, { value: 'jiaxing', label: '嘉兴市' }, { value: 'ningbo', label: '宁波市' }] }, + { value: 'shanghai', label: '上海市', children: [{ value: 'shanghai', label: '上海市' }] }, + { value: 'guangdong', label: '广东省', children: [{ value: 'guangzhou', label: '广州市' }, { value: 'shenzhen', label: '深圳市' }] } + ]; + + var contractCodeOptions = [ + { value: 'HT-ZL-2025-001', label: 'HT-ZL-2025-001' }, + { value: 'HT-ZL-2025-002', label: 'HT-ZL-2025-002' }, + { value: 'HT-ZL-2025-003', label: 'HT-ZL-2025-003' } + ]; + var projectNameOptions = [ + { value: 'p1', label: '嘉兴氢能示范项目' }, + { value: 'p2', label: '上海物流租赁项目' }, + { value: 'p3', label: '杭州城配租赁项目' } + ]; + var customerNameOptions = [ + { value: 'c1', label: '嘉兴某某物流有限公司' }, + { value: 'c2', label: '上海某某运输公司' }, + { value: 'c3', label: '杭州某某租赁有限公司' } + ]; + var deliveryPersonOptions = [ + { value: '张三', label: '张三' }, + { value: '李四', label: '李四' }, + { value: '王五', label: '王五' } + ]; + + // 待处理 mock(10条样例) + var pendingListState = useState([ + { id: 'd1', expectedDate: '2025-02-28', taskPublishTime: '2025-02-20 09:00', contractCode: 'HT-ZL-2025-001', projectName: '嘉兴氢能示范项目', customerName: '嘉兴某某物流有限公司', deliveryCount: 2, vehicleList: [{ vehicleType: '厢式货车', brand: '东风', model: 'DFH1180', plateNo: '京A12345' }, { vehicleType: '平板货车', brand: '福田', model: 'BJ1180', plateNo: '-' }], deliveryRegion: '浙江省-嘉兴市', deliveryAddress: '嘉兴市南湖区科技大道1号' }, + { id: 'd2', expectedDate: '2025-03-01 至 2025-03-05', taskPublishTime: '2025-02-21 10:30', contractCode: 'HT-ZL-2025-002', projectName: '上海物流租赁项目', customerName: '上海某某运输公司', deliveryCount: 1, vehicleList: [{ vehicleType: '厢式货车', brand: '江淮', model: 'HFC1180', plateNo: '-' }], deliveryRegion: '上海市-上海市', deliveryAddress: '浦东新区张江高科技园区' }, + { id: 'd3', expectedDate: '2025-02-25', taskPublishTime: '2025-02-18 14:00', contractCode: 'HT-ZL-2025-003', projectName: '杭州城配租赁项目', customerName: '杭州某某租赁有限公司', deliveryCount: 3, vehicleList: [{ vehicleType: '栏板货车', brand: '重汽', model: 'ZZ1180', plateNo: '浙A10001' }, { vehicleType: '厢式货车', brand: '东风', model: 'DFH1190', plateNo: '-' }, { vehicleType: '平板货车', brand: '福田', model: 'BJ1190', plateNo: '-' }], deliveryRegion: '浙江省-杭州市', deliveryAddress: '余杭区未来科技城' }, + { id: 'd4', expectedDate: '2025-03-08', taskPublishTime: '2025-02-25 08:15', contractCode: 'HT-ZL-2025-004', projectName: '嘉兴氢能示范项目', customerName: '嘉兴某某物流有限公司', deliveryCount: 1, vehicleList: [{ vehicleType: '厢式货车', brand: '重汽', model: 'ZZ1160', plateNo: '-' }], deliveryRegion: '浙江省-嘉兴市', deliveryAddress: '嘉兴市秀洲区洪兴西路288号' }, + { id: 'd5', expectedDate: '2025-03-10 至 2025-03-12', taskPublishTime: '2025-02-26 11:20', contractCode: 'HT-ZL-2025-005', projectName: '上海物流租赁项目', customerName: '上海某某运输公司', deliveryCount: 2, vehicleList: [{ vehicleType: '平板货车', brand: '福田', model: 'BJ1180', plateNo: '-' }, { vehicleType: '栏板货车', brand: '东风', model: 'DFH1160', plateNo: '-' }], deliveryRegion: '上海市-上海市', deliveryAddress: '闵行区莘庄工业区申富路669号' }, + { id: 'd6', expectedDate: '2025-03-15', taskPublishTime: '2025-03-01 09:30', contractCode: 'HT-ZL-2025-006', projectName: '杭州城配租赁项目', customerName: '杭州某某租赁有限公司', deliveryCount: 4, vehicleList: [{ vehicleType: '厢式货车', brand: '江淮', model: 'HFC1180', plateNo: '-' }, { vehicleType: '厢式货车', brand: '东风', model: 'DFH1180', plateNo: '-' }, { vehicleType: '平板货车', brand: '福田', model: 'BJ1190', plateNo: '-' }, { vehicleType: '栏板货车', brand: '重汽', model: 'ZZ1180', plateNo: '-' }], deliveryRegion: '浙江省-杭州市', deliveryAddress: '萧山区市心北路108号' }, + { id: 'd7', expectedDate: '2025-03-18', taskPublishTime: '2025-03-03 14:00', contractCode: 'HT-ZL-2025-007', projectName: '嘉兴氢能示范项目', customerName: '嘉兴某某物流有限公司', deliveryCount: 1, vehicleList: [{ vehicleType: '平板货车', brand: '福田', model: 'BJ1180', plateNo: '浙F80088' }], deliveryRegion: '浙江省-嘉兴市', deliveryAddress: '嘉兴市经开区昌盛路1号' }, + { id: 'd8', expectedDate: '2025-03-20 至 2025-03-22', taskPublishTime: '2025-03-05 10:00', contractCode: 'HT-ZL-2025-008', projectName: '上海物流租赁项目', customerName: '上海某某运输公司', deliveryCount: 3, vehicleList: [{ vehicleType: '厢式货车', brand: '江淮', model: 'HFC1190', plateNo: '-' }, { vehicleType: '栏板货车', brand: '重汽', model: 'ZZ1190', plateNo: '-' }, { vehicleType: '厢式货车', brand: '东风', model: 'DFH1190', plateNo: '-' }], deliveryRegion: '上海市-上海市', deliveryAddress: '宝山区沪太路5008号' }, + { id: 'd9', expectedDate: '2025-03-25', taskPublishTime: '2025-03-08 08:45', contractCode: 'HT-ZL-2025-009', projectName: '杭州城配租赁项目', customerName: '杭州某某租赁有限公司', deliveryCount: 2, vehicleList: [{ vehicleType: '栏板货车', brand: '东风', model: 'DFH1160', plateNo: '-' }, { vehicleType: '平板货车', brand: '福田', model: 'BJ1180', plateNo: '浙A20002' }], deliveryRegion: '浙江省-杭州市', deliveryAddress: '滨江区网商路699号' }, + { id: 'd10', expectedDate: '2025-03-28', taskPublishTime: '2025-03-10 16:20', contractCode: 'HT-ZL-2025-010', projectName: '嘉兴氢能示范项目', customerName: '嘉兴某某物流有限公司', deliveryCount: 1, vehicleList: [{ vehicleType: '厢式货车', brand: '重汽', model: 'ZZ1180', plateNo: '-' }], deliveryRegion: '浙江省-嘉兴市', deliveryAddress: '嘉兴市南湖区广益路与由拳路交叉口' } + ]); + var pendingList = pendingListState[0]; + + // 历史记录 mock(10条样例,含交车完成时间、交车人,vehicleList 含实际交车日期、交车人) + var historyListState = useState([ + { id: 'h1', expectedDate: '2025-02-15', taskPublishTime: '2025-02-10 09:00', completeTime: '2025-02-15 14:30', deliveryPerson: '张三', contractCode: 'HT-ZL-2024-001', projectName: '嘉兴氢能示范项目', customerName: '嘉兴某某物流有限公司', deliveryCount: 2, vehicleList: [{ vehicleType: '厢式货车', brand: '东风', model: 'DFH1180', plateNo: '京A12345', actualDate: '2025-02-15', deliveryPerson: '张三' }, { vehicleType: '平板货车', brand: '福田', model: 'BJ1180', plateNo: '京C11111', actualDate: '2025-02-15', deliveryPerson: '李四' }], deliveryRegion: '浙江省-嘉兴市', deliveryAddress: '嘉兴市南湖区科技大道1号' }, + { id: 'h2', expectedDate: '2025-02-10 至 2025-02-12', taskPublishTime: '2025-02-05 10:00', completeTime: '2025-02-12 11:00', deliveryPerson: '王五', contractCode: 'HT-ZL-2024-002', projectName: '上海物流租赁项目', customerName: '上海某某运输公司', deliveryCount: 1, vehicleList: [{ vehicleType: '厢式货车', brand: '江淮', model: 'HFC1180', plateNo: '沪A20002', actualDate: '2025-02-11', deliveryPerson: '王五' }], deliveryRegion: '上海市-上海市', deliveryAddress: '浦东新区张江路100号' }, + { id: 'h3', expectedDate: '2025-02-18', taskPublishTime: '2025-02-12 08:30', completeTime: '2025-02-18 15:00', deliveryPerson: '张三', contractCode: 'HT-ZL-2024-003', projectName: '杭州城配租赁项目', customerName: '杭州某某租赁有限公司', deliveryCount: 3, vehicleList: [{ vehicleType: '栏板货车', brand: '重汽', model: 'ZZ1180', plateNo: '浙A10001', actualDate: '2025-02-18', deliveryPerson: '张三' }, { vehicleType: '厢式货车', brand: '东风', model: 'DFH1190', plateNo: '浙A10002', actualDate: '2025-02-18', deliveryPerson: '李四' }, { vehicleType: '平板货车', brand: '福田', model: 'BJ1190', plateNo: '浙A10003', actualDate: '2025-02-18', deliveryPerson: '王五' }], deliveryRegion: '浙江省-杭州市', deliveryAddress: '余杭区未来科技城' }, + { id: 'h4', expectedDate: '2025-02-20', taskPublishTime: '2025-02-14 11:00', completeTime: '2025-02-20 10:15', deliveryPerson: '李四', contractCode: 'HT-ZL-2024-004', projectName: '嘉兴氢能示范项目', customerName: '嘉兴某某物流有限公司', deliveryCount: 1, vehicleList: [{ vehicleType: '厢式货车', brand: '江淮', model: 'HFC1180', plateNo: '浙F60001', actualDate: '2025-02-20', deliveryPerson: '李四' }], deliveryRegion: '浙江省-嘉兴市', deliveryAddress: '嘉兴市秀洲区洪兴西路288号' }, + { id: 'h5', expectedDate: '2025-02-22 至 2025-02-24', taskPublishTime: '2025-02-16 09:45', completeTime: '2025-02-24 16:30', deliveryPerson: '王五', contractCode: 'HT-ZL-2024-005', projectName: '上海物流租赁项目', customerName: '上海某某运输公司', deliveryCount: 2, vehicleList: [{ vehicleType: '平板货车', brand: '福田', model: 'BJ1180', plateNo: '沪B30001', actualDate: '2025-02-23', deliveryPerson: '王五' }, { vehicleType: '栏板货车', brand: '东风', model: 'DFH1160', plateNo: '沪B30002', actualDate: '2025-02-24', deliveryPerson: '张三' }], deliveryRegion: '上海市-上海市', deliveryAddress: '闵行区莘庄工业区申富路669号' }, + { id: 'h6', expectedDate: '2025-02-25', taskPublishTime: '2025-02-18 14:20', completeTime: '2025-02-25 11:00', deliveryPerson: '张三', contractCode: 'HT-ZL-2024-006', projectName: '杭州城配租赁项目', customerName: '杭州某某租赁有限公司', deliveryCount: 1, vehicleList: [{ vehicleType: '厢式货车', brand: '重汽', model: 'ZZ1160', plateNo: '浙A40001', actualDate: '2025-02-25', deliveryPerson: '张三' }], deliveryRegion: '浙江省-杭州市', deliveryAddress: '萧山区市心北路108号' }, + { id: 'h7', expectedDate: '2025-02-28', taskPublishTime: '2025-02-20 10:10', completeTime: '2025-02-28 14:45', deliveryPerson: '李四', contractCode: 'HT-ZL-2024-007', projectName: '嘉兴氢能示范项目', customerName: '嘉兴某某物流有限公司', deliveryCount: 2, vehicleList: [{ vehicleType: '厢式货车', brand: '东风', model: 'DFH1180', plateNo: '浙F70001', actualDate: '2025-02-28', deliveryPerson: '李四' }, { vehicleType: '平板货车', brand: '福田', model: 'BJ1180', plateNo: '浙F70002', actualDate: '2025-02-28', deliveryPerson: '王五' }], deliveryRegion: '浙江省-嘉兴市', deliveryAddress: '嘉兴市经开区昌盛路1号' }, + { id: 'h8', expectedDate: '2025-03-02', taskPublishTime: '2025-02-22 08:00', completeTime: '2025-03-02 09:30', deliveryPerson: '王五', contractCode: 'HT-ZL-2024-008', projectName: '上海物流租赁项目', customerName: '上海某某运输公司', deliveryCount: 1, vehicleList: [{ vehicleType: '栏板货车', brand: '江淮', model: 'HFC1160', plateNo: '沪A50001', actualDate: '2025-03-02', deliveryPerson: '王五' }], deliveryRegion: '上海市-上海市', deliveryAddress: '宝山区沪太路5008号' }, + { id: 'h9', expectedDate: '2025-03-05 至 2025-03-07', taskPublishTime: '2025-02-25 15:30', completeTime: '2025-03-07 13:20', deliveryPerson: '张三', contractCode: 'HT-ZL-2024-009', projectName: '杭州城配租赁项目', customerName: '杭州某某租赁有限公司', deliveryCount: 2, vehicleList: [{ vehicleType: '厢式货车', brand: '江淮', model: 'HFC1190', plateNo: '浙A60001', actualDate: '2025-03-06', deliveryPerson: '张三' }, { vehicleType: '栏板货车', brand: '重汽', model: 'ZZ1190', plateNo: '浙A60002', actualDate: '2025-03-07', deliveryPerson: '李四' }], deliveryRegion: '浙江省-杭州市', deliveryAddress: '滨江区网商路699号' }, + { id: 'h10', expectedDate: '2025-03-10', taskPublishTime: '2025-03-01 11:00', completeTime: '2025-03-10 10:00', deliveryPerson: '李四', contractCode: 'HT-ZL-2024-010', projectName: '嘉兴氢能示范项目', customerName: '嘉兴某某物流有限公司', deliveryCount: 1, vehicleList: [{ vehicleType: '平板货车', brand: '福田', model: 'BJ1190', plateNo: '浙F90001', actualDate: '2025-03-10', deliveryPerson: '李四' }], deliveryRegion: '浙江省-嘉兴市', deliveryAddress: '嘉兴市南湖区广益路与由拳路交叉口' } + ]); + var historyList = historyListState[0]; + + var filteredPending = useMemo(function () { + var list = pendingList.slice(); + if (appliedFilters.contractCode) list = list.filter(function (r) { return (r.contractCode || '').indexOf(appliedFilters.contractCode) !== -1; }); + if (appliedFilters.projectName) list = list.filter(function (r) { return r.projectName === appliedFilters.projectName; }); + if (appliedFilters.customerName) list = list.filter(function (r) { return r.customerName === appliedFilters.customerName; }); + if (appliedFilters.deliveryRegion) list = list.filter(function (r) { return (r.deliveryRegion || '').indexOf(appliedFilters.deliveryRegion) !== -1; }); + if (appliedFilters.dateStart) list = list.filter(function (r) { var d = (r.expectedDate || '').slice(0, 10); return d >= appliedFilters.dateStart; }); + if (appliedFilters.dateEnd) list = list.filter(function (r) { var d = (r.expectedDate || '').slice(0, 10); return d <= appliedFilters.dateEnd; }); + return list; + }, [pendingList, appliedFilters]); + + var filteredHistory = useMemo(function () { + var list = historyList.slice(); + if (appliedFilters.contractCode) list = list.filter(function (r) { return (r.contractCode || '').indexOf(appliedFilters.contractCode) !== -1; }); + if (appliedFilters.projectName) list = list.filter(function (r) { return r.projectName === appliedFilters.projectName; }); + if (appliedFilters.customerName) list = list.filter(function (r) { return r.customerName === appliedFilters.customerName; }); + if (appliedFilters.deliveryRegion) list = list.filter(function (r) { return (r.deliveryRegion || '').indexOf(appliedFilters.deliveryRegion) !== -1; }); + if (appliedFilters.dateStart) list = list.filter(function (r) { var d = (r.completeTime || '').slice(0, 10); return d >= appliedFilters.dateStart; }); + if (appliedFilters.dateEnd) list = list.filter(function (r) { var d = (r.completeTime || '').slice(0, 10); return d <= appliedFilters.dateEnd; }); + if (appliedFilters.deliveryPerson) list = list.filter(function (r) { return (r.deliveryPerson || '').indexOf(appliedFilters.deliveryPerson) !== -1; }); + return list; + }, [historyList, appliedFilters]); + + var page = pageState[0]; + var setPage = pageState[1]; + var pageSize = pageSizeState[0]; + var setPageSize = pageSizeState[1]; + var totalCount = activeTab === 'pending' ? filteredPending.length : filteredHistory.length; + var displayPending = useMemo(function () { + var start = (page - 1) * pageSize; + return filteredPending.slice(start, start + pageSize); + }, [filteredPending, page, pageSize]); + var displayHistory = useMemo(function () { + var start = (page - 1) * pageSize; + return filteredHistory.slice(start, start + pageSize); + }, [filteredHistory, page, pageSize]); + + var handleQuery = useCallback(function () { + var regionVal = filters.deliveryRegion; + if (Array.isArray(regionVal) && regionVal.length >= 2) { + var prov = regionOptions.find(function (r) { return r.value === regionVal[0]; }); + var city = prov && prov.children && prov.children.find(function (c) { return c.value === regionVal[1]; }); + regionVal = prov && city ? prov.label + '-' + city.label : undefined; + } else if (Array.isArray(regionVal) && regionVal.length === 1) { + var p = regionOptions.find(function (r) { return r.value === regionVal[0]; }); + regionVal = p ? p.label : undefined; + } + setAppliedFilters({ + contractCode: filters.contractCode, + projectName: filters.projectName, + customerName: filters.customerName, + deliveryRegion: regionVal, + dateStart: filters.dateStart, + dateEnd: filters.dateEnd, + deliveryPerson: filters.deliveryPerson + }); + setPage(1); + }, [filters]); + + var handleReset = useCallback(function () { + var empty = { contractCode: undefined, projectName: undefined, customerName: undefined, deliveryRegion: undefined, dateStart: '', dateEnd: '', deliveryPerson: undefined }; + setFilters(empty); + setAppliedFilters(empty); + setPage(1); + }, []); + + var handleExport = useCallback(function () { + var rows = activeTab === 'pending' ? filteredPending : filteredHistory; + if (!rows || rows.length === 0) { + message.warning('当前无数据可导出'); + return; + } + var escapeCsv = function (v) { + var s = v == null ? '' : String(v); + if (s.indexOf(',') !== -1 || s.indexOf('"') !== -1 || s.indexOf('\n') !== -1) return '"' + s.replace(/"/g, '""') + '"'; + return s; + }; + var headers; + var rowToCells = function (r) { + if (activeTab === 'pending') { + return [r.expectedDate, r.taskPublishTime, r.contractCode, r.projectName, r.customerName, r.deliveryCount, r.deliveryRegion, r.deliveryAddress]; + } + return [r.expectedDate, r.taskPublishTime, r.completeTime, r.deliveryPerson, r.contractCode, r.projectName, r.customerName, r.deliveryCount, r.deliveryRegion, r.deliveryAddress]; + }; + if (activeTab === 'pending') { + headers = ['预计交车时间', '任务发布时间', '合同编码', '项目名称', '客户名称', '交车数量', '交车区域', '交车地点']; + } else { + headers = ['预计交车时间', '任务发布时间', '交车完成时间', '交车人', '合同编码', '项目名称', '客户名称', '交车数量', '交车区域', '交车地点']; + } + var csv = headers.map(escapeCsv).join(',') + '\n'; + rows.forEach(function (r) { + csv += rowToCells(r).map(escapeCsv).join(',') + '\n'; + }); + var blob = new Blob(['\ufeff' + csv], { type: 'text/csv;charset=utf-8' }); + var url = URL.createObjectURL(blob); + var a = document.createElement('a'); + a.href = url; + a.download = '交车管理_' + (activeTab === 'pending' ? '待处理' : '历史记录') + '_' + new Date().getTime() + '.csv'; + a.click(); + URL.revokeObjectURL(url); + message.success('导出成功'); + }, [activeTab, filteredPending, filteredHistory]); + + var dateRangeValue = useMemo(function () { + if (!filters.dateStart && !filters.dateEnd) return null; + try { + if (typeof window !== 'undefined' && window.dayjs && filters.dateStart && filters.dateEnd) { + return [window.dayjs(filters.dateStart), window.dayjs(filters.dateEnd)]; + } + } catch (e) {} + return null; + }, [filters.dateStart, filters.dateEnd]); + + var onDateRangeChange = useCallback(function (dates, dateStrings) { + setFilters(function (f) { + var g = {}; for (var k in f) g[k] = f[k]; + g.dateStart = (dateStrings && dateStrings[0]) || ''; + g.dateEnd = (dateStrings && dateStrings[1]) || ''; + return g; + }); + }, []); + + // 交车区域 Cascader 的 value:用 appliedFilters 反推或存 value 数组。筛选用字符串比较,表单用 Cascader 选省-市 + var deliveryRegionValue = useMemo(function () { + var s = filters.deliveryRegion; + if (!s || typeof s !== 'string') return undefined; + var parts = s.split('-'); + if (parts.length < 2) return undefined; + for (var i = 0; i < regionOptions.length; i++) { + var prov = regionOptions[i]; + if (prov.label !== parts[0]) continue; + for (var j = 0; j < (prov.children || []).length; j++) { + if (prov.children[j].label === parts[1]) return [prov.value, prov.children[j].value]; + } + } + return undefined; + }, [filters.deliveryRegion]); + + // 列表列:待处理 + var popoverTableStyle = { width: '100%', borderCollapse: 'collapse', fontSize: 12 }; + var popoverThStyle = { padding: '6px 8px', textAlign: 'left', borderBottom: '1px solid #f0f0f0', backgroundColor: '#fafafa', fontWeight: 600 }; + var popoverTdStyle = { padding: '6px 8px', borderBottom: '1px solid #f0f0f0' }; + + function renderPendingQuantity(record) { + var list = record.vehicleList || []; + var content = React.createElement('div', { style: { padding: 8, minWidth: 320 } }, + React.createElement('div', { style: { marginBottom: 8, fontWeight: 600 } }, '车辆明细'), + React.createElement('table', { style: popoverTableStyle }, + React.createElement('thead', null, + React.createElement('tr', null, + React.createElement('th', { style: popoverThStyle }, '车辆类型'), + React.createElement('th', { style: popoverThStyle }, '品牌'), + React.createElement('th', { style: popoverThStyle }, '型号'), + React.createElement('th', { style: popoverThStyle }, '车牌号') + ) + ), + React.createElement('tbody', null, + list.map(function (v, i) { + return React.createElement('tr', { key: i }, + React.createElement('td', { style: popoverTdStyle }, v.vehicleType || '-'), + React.createElement('td', { style: popoverTdStyle }, v.brand || '-'), + React.createElement('td', { style: popoverTdStyle }, v.model || '-'), + React.createElement('td', { style: popoverTdStyle }, v.plateNo || '-') + ); + }) + ) + ) + ); + return React.createElement(Popover, { content: content, title: null }, + React.createElement('span', { style: { color: '#1890ff', cursor: 'pointer', fontWeight: 600 } }, record.deliveryCount + ' 辆') + ); + } + + function renderHistoryQuantity(record) { + var list = record.vehicleList || []; + var content = React.createElement('div', { style: { padding: 8, minWidth: 420 } }, + React.createElement('div', { style: { marginBottom: 8, fontWeight: 600 } }, '车辆明细'), + React.createElement('table', { style: popoverTableStyle }, + React.createElement('thead', null, + React.createElement('tr', null, + React.createElement('th', { style: popoverThStyle }, '车辆类型'), + React.createElement('th', { style: popoverThStyle }, '品牌'), + React.createElement('th', { style: popoverThStyle }, '型号'), + React.createElement('th', { style: popoverThStyle }, '车牌号'), + React.createElement('th', { style: popoverThStyle }, '实际交车日期'), + React.createElement('th', { style: popoverThStyle }, '交车人') + ) + ), + React.createElement('tbody', null, + list.map(function (v, i) { + return React.createElement('tr', { key: i }, + React.createElement('td', { style: popoverTdStyle }, v.vehicleType || '-'), + React.createElement('td', { style: popoverTdStyle }, v.brand || '-'), + React.createElement('td', { style: popoverTdStyle }, v.model || '-'), + React.createElement('td', { style: popoverTdStyle }, v.plateNo || '-'), + React.createElement('td', { style: popoverTdStyle }, v.actualDate || '-'), + React.createElement('td', { style: popoverTdStyle }, v.deliveryPerson || '-') + ); + }) + ) + ) + ); + return React.createElement(Popover, { content: content, title: null }, + React.createElement('span', { style: { color: '#1890ff', cursor: 'pointer', fontWeight: 600 } }, record.deliveryCount + ' 辆') + ); + } + + var pendingColumns = [ + { title: '预计交车时间', dataIndex: 'expectedDate', key: 'expectedDate', width: 260, ellipsis: true }, + { title: '任务发布时间', dataIndex: 'taskPublishTime', key: 'taskPublishTime', width: 160, ellipsis: true }, + { title: '合同编码', dataIndex: 'contractCode', key: 'contractCode', width: 160, ellipsis: true }, + { title: '项目名称', dataIndex: 'projectName', key: 'projectName', width: 160, ellipsis: true }, + { title: '客户名称', dataIndex: 'customerName', key: 'customerName', width: 180, ellipsis: true }, + { title: '交车数量', key: 'deliveryCount', width: 100, render: function (_, r) { return renderPendingQuantity(r); } }, + { title: '交车区域', dataIndex: 'deliveryRegion', key: 'deliveryRegion', width: 130, ellipsis: true }, + { title: '交车地点', dataIndex: 'deliveryAddress', key: 'deliveryAddress', width: 220, ellipsis: true }, + { title: '操作', key: 'action', width: 80, fixed: 'right', render: function (_, r) { + return React.createElement(Button, { type: 'link', size: 'small', onClick: function () { message.info('跳转查看交车单'); } }, '查看'); + } } + ]; + + var historyColumns = [ + { title: '预计交车时间', dataIndex: 'expectedDate', key: 'expectedDate', width: 260, ellipsis: true }, + { title: '任务发布时间', dataIndex: 'taskPublishTime', key: 'taskPublishTime', width: 160, ellipsis: true }, + { title: '交车完成时间', dataIndex: 'completeTime', key: 'completeTime', width: 160, ellipsis: true }, + { title: '交车人', dataIndex: 'deliveryPerson', key: 'deliveryPerson', width: 100, ellipsis: true }, + { title: '合同编码', dataIndex: 'contractCode', key: 'contractCode', width: 160, ellipsis: true }, + { title: '项目名称', dataIndex: 'projectName', key: 'projectName', width: 160, ellipsis: true }, + { title: '客户名称', dataIndex: 'customerName', key: 'customerName', width: 180, ellipsis: true }, + { title: '交车数量', key: 'deliveryCount', width: 100, render: function (_, r) { return renderHistoryQuantity(r); } }, + { title: '交车区域', dataIndex: 'deliveryRegion', key: 'deliveryRegion', width: 130, ellipsis: true }, + { title: '交车地点', dataIndex: 'deliveryAddress', key: 'deliveryAddress', width: 220, ellipsis: true }, + { title: '操作', key: 'action', width: 80, fixed: 'right', render: function () { + return React.createElement(Button, { type: 'link', size: 'small', onClick: function () { message.info('跳转查看交车单页面'); } }, '查看'); + } } + ]; + + var tablePagination = useMemo(function () { + return { + current: page, + pageSize: pageSize, + total: totalCount, + showSizeChanger: true, + showTotal: function (t) { return '共 ' + t + ' 条'; }, + pageSizeOptions: ['10', '20', '50'], + onChange: function (p, size) { setPage(p); if (size !== pageSize) setPageSize(size); } + }; + }, [page, pageSize, totalCount]); + + var styles = { + page: { padding: '16px 24px 48px', backgroundColor: '#f5f5f5', minHeight: '100vh', fontSize: 14 }, + breadcrumb: { marginBottom: 16, color: '#666' }, + breadcrumbSep: { margin: '0 8px', color: '#999' }, + card: { backgroundColor: '#fff', borderRadius: 8, marginBottom: 16, boxShadow: '0 1px 2px rgba(0,0,0,0.05)', overflow: 'hidden' }, + cardBody: { padding: '20px 24px' }, + formRow: { display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '16px 24px', alignItems: 'start' }, + formItem: { marginBottom: 4 }, + formLabel: { display: 'block', marginBottom: 4, color: '#333' } + }; + + var breadcrumbItems = [ + { title: '运维管理' }, + { title: '车辆业务' }, + { title: '交车管理' } + ]; + + return React.createElement('div', { style: styles.page }, + React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 } }, + React.createElement(Breadcrumb, { items: breadcrumbItems }), + React.createElement(Button, { type: 'link', onClick: function () { setRequirementModalOpen(true); } }, '查看需求说明') + ), + React.createElement(Card, { style: { marginBottom: 16 } }, + React.createElement('div', { style: styles.cardBody }, + React.createElement('div', { style: styles.formRow }, + React.createElement('div', null, + React.createElement('div', { style: styles.formLabel }, '合同编码'), + React.createElement(Select, { + placeholder: '请输入或选择合同编码', + allowClear: true, + showSearch: true, + optionFilterProp: 'label', + value: filters.contractCode, + onChange: function (v) { setFilters(function (f) { var g = {}; for (var k in f) g[k] = f[k]; g.contractCode = v; return g; }); }, + style: { width: '100%' }, + options: contractCodeOptions + }) + ), + React.createElement('div', null, + React.createElement('div', { style: styles.formLabel }, '项目名称'), + React.createElement(Select, { + placeholder: '请输入或选择项目名称', + allowClear: true, + showSearch: true, + optionFilterProp: 'label', + value: filters.projectName, + onChange: function (v) { setFilters(function (f) { var g = {}; for (var k in f) g[k] = f[k]; g.projectName = v; return g; }); }, + style: { width: '100%' }, + options: projectNameOptions + }) + ), + React.createElement('div', null, + React.createElement('div', { style: styles.formLabel }, '客户名称'), + React.createElement(Select, { + placeholder: '请输入或选择客户名称', + allowClear: true, + showSearch: true, + optionFilterProp: 'label', + value: filters.customerName, + onChange: function (v) { setFilters(function (f) { var g = {}; for (var k in f) g[k] = f[k]; g.customerName = v; return g; }); }, + style: { width: '100%' }, + options: customerNameOptions + }) + ), + React.createElement('div', null, + React.createElement('div', { style: styles.formLabel }, '交车区域'), + React.createElement(Cascader, { + options: regionOptions, + placeholder: '请选择省-市', + allowClear: true, + style: { width: '100%' }, + value: deliveryRegionValue, + onChange: function (value) { + var s; + if (value && value.length >= 2) { + var prov = regionOptions.find(function (r) { return r.value === value[0]; }); + var city = prov && prov.children && prov.children.find(function (c) { return c.value === value[1]; }); + s = prov && city ? prov.label + '-' + city.label : undefined; + } else { s = undefined; } + setFilters(function (f) { var g = {}; for (var k in f) g[k] = f[k]; g.deliveryRegion = s; return g; }); + }, + displayRender: function (labels) { return labels && labels.length ? labels.join(' / ') : ''; } + }) + ), + React.createElement('div', null, + React.createElement('div', { style: styles.formLabel }, '交车时间'), + React.createElement(RangePicker, { + style: { width: '100%' }, + placeholder: ['请选择交车开始时间', '请选择交车结束时间'], + value: dateRangeValue, + onChange: onDateRangeChange + }) + ), + React.createElement('div', null, + React.createElement('div', { style: styles.formLabel }, '交车人'), + React.createElement(Select, { + placeholder: '请输入或选择交车人姓名', + allowClear: true, + showSearch: true, + optionFilterProp: 'label', + value: filters.deliveryPerson, + onChange: function (v) { setFilters(function (f) { var g = {}; for (var k in f) g[k] = f[k]; g.deliveryPerson = v; return g; }); }, + style: { width: '100%' }, + options: deliveryPersonOptions + }) + ) + ), + React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 16 } }, + React.createElement(Button, { onClick: handleReset }, '重置'), + React.createElement(Button, { type: 'primary', onClick: handleQuery }, '查询') + ) + ) + ), + React.createElement(Card, null, + React.createElement(Tabs, { + activeKey: activeTab, + onChange: function (k) { setActiveTab(k); setPage(1); }, + tabBarExtraContent: React.createElement(Button, { onClick: handleExport }, '导出'), + items: [ + { + key: 'pending', + label: '待处理', + children: React.createElement(Table, { + columns: pendingColumns, + dataSource: displayPending, + rowKey: 'id', + pagination: tablePagination, + scroll: { x: 1490 }, + size: 'middle' + }) + }, + { + key: 'history', + label: '历史记录', + children: React.createElement(Table, { + columns: historyColumns, + dataSource: displayHistory, + rowKey: 'id', + pagination: tablePagination, + scroll: { x: 1650 }, + size: 'middle' + }) + } + ] + }) + ), + React.createElement(Modal, { + title: '需求说明', + open: requirementModalOpen[0], + onCancel: function () { setRequirementModalOpen(false); }, + footer: React.createElement(Button, { onClick: function () { setRequirementModalOpen(false); } }, '关闭'), + width: 640, + destroyOnClose: true + }, React.createElement('div', { style: { maxHeight: 560, overflowY: 'auto', whiteSpace: 'pre-wrap', lineHeight: 1.6, fontSize: 13 } }, requirementDocContent)) + ); +}; + +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..209b767 --- /dev/null +++ b/web端/运维管理/车辆业务/后装设备.jsx @@ -0,0 +1,481 @@ +// 【重要】必须使用 const Component 作为组件变量名 +// 后装设备 - 运维管理-车辆业务 +var ARCO_TOKEN = { + primary: '#165DFF', + primaryHover: '#4080FF', + danger: '#F53F3F', + success: '#00B42A', + neutral1: '#FFFFFF', + neutral2: '#F7F8FA', + neutral3: '#F2F3F5', + neutral4: '#E5E6EB', + neutral5: '#C9CDD4', + neutral6: '#86909C', + neutral7: '#4E5969', + neutral8: '#1D2129', + border: '#E5E6EB', + fill: '#F2F3F5', + fillSecondary: '#F7F8FA', + shadowLight: '0 1px 2px rgba(0,0,0,0.05)', + radiusMedium: '4px', + radiusLarge: '8px', + spacing8: '8px', + spacing12: '12px', + spacing16: '16px', + spacing24: '24px', + fontSize14: '14px', + fontSize16: '16px', + fontFamily: '-apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", Arial, sans-serif', + link: '#165DFF' +}; + +var DEVICE_TYPES = ['GPS', '尾板', '车身广告', 'G7安全套件', 'G7普通设备', 'G7温控设备', '备胎']; + +var MOCK_VEHICLE_OPTIONS = ['粤A12345', '粤A67890', '粤B11111', '粤B22222', '粤C33333', '京A88888']; + +var MOCK_LIST = [ + { id: '1', deviceType: 'GPS', plateNo: '粤A12345', supplier: '深圳智联', lastOpType: '安装', createTime: '2025-02-20 10:30', removeTime: '', createOperator: '张明', removeOperator: '' }, + { id: '2', deviceType: '尾板', plateNo: '粤A67890', supplier: '广州机械', lastOpType: '拆除', createTime: '2025-02-01 09:00', removeTime: '2025-02-18 14:20', createOperator: '李强', removeOperator: '王芳' }, + { id: '3', deviceType: '车身广告', plateNo: '粤B11111', supplier: '东莞广告', lastOpType: '安装', createTime: '2025-02-15 09:00', removeTime: '', createOperator: '李强', removeOperator: '' }, + { id: '4', deviceType: 'G7安全套件', plateNo: '粤A12345', supplier: 'G7科技', lastOpType: '安装', createTime: '2025-02-10 16:45', removeTime: '', createOperator: '张明', removeOperator: '' }, + { id: '5', deviceType: '备胎', plateNo: '粤C33333', supplier: '华南轮胎', lastOpType: '拆除', createTime: '2025-01-20 08:00', removeTime: '2025-02-08 11:20', createOperator: '赵六', removeOperator: '赵六' } +]; + +const Component = function () { + var antd = window.antd; + var Select = antd.Select; + var Button = antd.Button; + var DatePicker = antd.DatePicker; + var Table = antd.Table; + var Modal = antd.Modal; + var Input = antd.Input; + var message = antd.message; + var Option = Select.Option; + var RangePicker = DatePicker.RangePicker; + var Tabs = antd.Tabs; + + var _useStateTab = React.useState('installed'); + var activeTab = _useStateTab[0]; + var setActiveTab = _useStateTab[1]; + + var _useState = React.useState(''); + var filterDeviceType = _useState[0]; + var setFilterDeviceType = _useState[1]; + + var _useState2 = React.useState(''); + var filterVehicle = _useState2[0]; + var setFilterVehicle = _useState2[1]; + + var _useState3 = React.useState([]); + var filterDateRange = _useState3[0]; + var setFilterDateRange = _useState3[1]; + + var _useState4 = React.useState({ deviceType: '', vehicle: '', dateStart: '', dateEnd: '' }); + var appliedFilter = _useState4[0]; + var setAppliedFilter = _useState4[1]; + + var _useState5 = React.useState(1); + var currentPage = _useState5[0]; + var setCurrentPage = _useState5[1]; + + var _useState6 = React.useState(10); + var pageSize = _useState6[0]; + var setPageSize = _useState6[1]; + + var _useState7 = React.useState(MOCK_LIST); + var dataList = _useState7[0]; + var setDataList = _useState7[1]; + + var _useState8 = React.useState(null); + var viewRecord = _useState8[0]; + var setViewRecord = _useState8[1]; + + var _useState9 = React.useState(null); + var editRecord = _useState9[0]; + var setEditRecord = _useState9[1]; + + var _useState10 = React.useState(null); + var removeConfirmRecord = _useState10[0]; + var setRemoveConfirmRecord = _useState10[1]; + + var _useState11 = React.useState(false); + var addModalVisible = _useState11[0]; + var setAddModalVisible = _useState11[1]; + + var _useState12 = React.useState(false); + var showRequirementModal = _useState12[0]; + var setShowRequirementModal = _useState12[1]; + + var applyBaseFilter = function (list) { + if (appliedFilter.deviceType) { + list = list.filter(function (r) { return r.deviceType === appliedFilter.deviceType; }); + } + if (appliedFilter.vehicle) { + list = list.filter(function (r) { return r.plateNo && r.plateNo.indexOf(appliedFilter.vehicle) >= 0; }); + } + return list; + }; + + var installedList = dataList.filter(function (r) { return !r.removeTime || r.removeTime === ''; }); + var removedList = dataList.filter(function (r) { return r.removeTime && r.removeTime !== ''; }); + + var installedFiltered = applyBaseFilter(installedList).filter(function (r) { + if (appliedFilter.dateStart && r.createTime) { if (r.createTime.slice(0, 10) < appliedFilter.dateStart) return false; } + if (appliedFilter.dateEnd && r.createTime) { if (r.createTime.slice(0, 10) > appliedFilter.dateEnd) return false; } + return true; + }); + var removedFiltered = applyBaseFilter(removedList).filter(function (r) { + if (appliedFilter.dateStart && r.removeTime) { if (r.removeTime.slice(0, 10) < appliedFilter.dateStart) return false; } + if (appliedFilter.dateEnd && r.removeTime) { if (r.removeTime.slice(0, 10) > appliedFilter.dateEnd) return false; } + return true; + }); + + var filteredList = activeTab === 'installed' ? installedFiltered : removedFiltered; + var totalItems = filteredList.length; + var totalPages = Math.ceil(totalItems / pageSize) || 1; + var validPage = currentPage > totalPages && totalPages > 0 ? 1 : (currentPage < 1 ? 1 : currentPage); + var startIndex = (validPage - 1) * pageSize; + var paginatedList = filteredList.slice(startIndex, startIndex + pageSize); + + var handleQuery = function () { + var dateStart = ''; + var dateEnd = ''; + if (filterDateRange && filterDateRange.length >= 2 && filterDateRange[0] && filterDateRange[1]) { + if (filterDateRange[0].format) { + dateStart = filterDateRange[0].format('YYYY-MM-DD'); + dateEnd = filterDateRange[1].format('YYYY-MM-DD'); + } else if (window.dayjs) { + dateStart = window.dayjs(filterDateRange[0]).format('YYYY-MM-DD'); + dateEnd = window.dayjs(filterDateRange[1]).format('YYYY-MM-DD'); + } + } + setAppliedFilter({ + deviceType: filterDeviceType, + vehicle: filterVehicle, + dateStart: dateStart, + dateEnd: dateEnd + }); + setCurrentPage(1); + message.success('查询成功'); + }; + + var handleReset = function () { + setFilterDeviceType(''); + setFilterVehicle(''); + setFilterDateRange([]); + setAppliedFilter({ deviceType: '', vehicle: '', dateStart: '', dateEnd: '' }); + setCurrentPage(1); + }; + + var handleRemoveConfirm = function (row) { + var nowStr = '2025-02-24 15:00'; + if (window.dayjs) { nowStr = window.dayjs().format('YYYY-MM-DD HH:mm'); } + setDataList(dataList.map(function (r) { + if (r.id === row.id) { + return { id: r.id, deviceType: r.deviceType, plateNo: r.plateNo, supplier: r.supplier, lastOpType: '拆除', createTime: r.createTime, removeTime: nowStr, createOperator: r.createOperator, removeOperator: '当前用户' }; + } + return r; + })); + setRemoveConfirmRecord(null); + message.success('拆除成功'); + }; + + var t = ARCO_TOKEN; + var styles = { + page: { padding: t.spacing24, fontFamily: t.fontFamily, backgroundColor: t.fill, minHeight: '100vh' }, + breadcrumb: { marginBottom: t.spacing16, fontSize: t.fontSize14, color: t.neutral6, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }, + breadcrumbLeft: { display: 'flex', alignItems: 'center' }, + breadcrumbLink: { color: t.link, textDecoration: 'none', marginRight: '8px' }, + breadcrumbCurrent: { color: t.neutral8 }, + requirementLink: { color: t.link, textDecoration: 'none', fontSize: t.fontSize14, cursor: 'pointer' }, + modalContent: { fontSize: t.fontSize14, color: t.neutral8, lineHeight: 1.6 }, + requirementSection: { marginBottom: t.spacing16 }, + requirementSectionTitle: { fontSize: t.fontSize16, fontWeight: 600, color: t.neutral8, marginBottom: 8 }, + requirementItem: { marginBottom: 8, paddingLeft: 16 }, + requirementSubItem: { marginBottom: 4, paddingLeft: 16, color: t.neutral7 }, + card: { backgroundColor: t.neutral1, borderRadius: t.radiusLarge, boxShadow: t.shadowLight, marginBottom: t.spacing16, padding: t.spacing16 }, + filterRow: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: t.spacing12 }, + label: { marginRight: t.spacing8, fontSize: t.fontSize14, color: t.neutral8, whiteSpace: 'nowrap' }, + filterRight: { marginLeft: 'auto', display: 'flex', gap: t.spacing8 }, + toolbar: { display: 'flex', justifyContent: 'flex-end', gap: t.spacing12, marginBottom: t.spacing8 } + }; + + var deviceTypeOptions = DEVICE_TYPES.map(function (type) { + return React.createElement(Option, { key: type, value: type }, type); + }); + + var vehicleOptions = MOCK_VEHICLE_OPTIONS.map(function (v) { + return React.createElement(Option, { key: v, value: v }, v); + }); + + return React.createElement( + 'div', + { style: styles.page }, + React.createElement( + 'div', + { style: styles.breadcrumb }, + React.createElement('div', { style: styles.breadcrumbLeft }, + React.createElement('a', { href: '#', style: styles.breadcrumbLink, onClick: function (e) { e.preventDefault(); } }, '运维管理'), + React.createElement('span', { style: { marginRight: '8px' } }, '/'), + React.createElement('a', { href: '#', style: styles.breadcrumbLink, onClick: function (e) { e.preventDefault(); } }, '车辆业务'), + React.createElement('span', { style: { marginRight: '8px' } }, '/'), + React.createElement('span', { style: styles.breadcrumbCurrent }, '后装设备') + ), + React.createElement('a', { href: '#', style: styles.requirementLink, onClick: function (e) { e.preventDefault(); setShowRequirementModal(true); } }, '查看需求说明') + ), + React.createElement( + 'div', + { style: styles.card }, + React.createElement( + 'div', + { style: styles.filterRow }, + React.createElement('span', { style: styles.label }, '设备类型:'), + React.createElement(Select, { + placeholder: '请选择设备类型', + style: { width: 160 }, + value: filterDeviceType || undefined, + onChange: function (v) { setFilterDeviceType(v || ''); }, + allowClear: true + }, deviceTypeOptions), + React.createElement('span', { style: styles.label }, '使用车辆:'), + React.createElement(Select, { + placeholder: '请选择或输入搜索', + style: { width: 180 }, + value: filterVehicle || undefined, + onChange: function (v) { setFilterVehicle(v || ''); }, + showSearch: true, + allowClear: true, + filterOption: function (input, opt) { + var c = opt && opt.children; + return c && String(c).toLowerCase().indexOf((input || '').toLowerCase()) >= 0; + } + }, vehicleOptions), + React.createElement('span', { style: styles.label }, '安装时间:'), + React.createElement(RangePicker, { + style: { width: 260 }, + format: 'YYYY-MM-DD', + placeholder: ['开始日期', '结束日期'], + value: filterDateRange, + onChange: function (dates) { setFilterDateRange(dates || []); } + }), + React.createElement('div', { style: styles.filterRight }, + React.createElement(Button, { type: 'primary', onClick: handleQuery }, '查询'), + React.createElement(Button, { onClick: handleReset }, '重置') + ) + ) + ), + React.createElement( + 'div', + { style: styles.card }, + React.createElement( + 'div', + { style: styles.toolbar }, + activeTab === 'installed' && React.createElement(Button, { type: 'primary', onClick: function () { message.info('请查看运维管理-车辆业务-后装设备-新增后装设备'); } }, '新增'), + React.createElement(Button, { onClick: function () { message.info('根据筛选条件导出excel'); } }, '导出') + ), + React.createElement(Tabs, { + activeKey: activeTab, + onChange: function (key) { setActiveTab(key); setCurrentPage(1); }, + items: [ + { + key: 'installed', + label: '已安装', + children: React.createElement(Table, { + rowKey: 'id', + size: 'small', + columns: [ + { title: '设备类型', dataIndex: 'deviceType', key: 'deviceType', width: 120 }, + { title: '车牌号', dataIndex: 'plateNo', key: 'plateNo', width: 120 }, + { title: '供应商', dataIndex: 'supplier', key: 'supplier', width: 120 }, + { title: '安装时间', dataIndex: 'createTime', key: 'createTime', width: 160 }, + { title: '安装人', dataIndex: 'createOperator', key: 'createOperator', width: 100 }, + { + title: '操作', + key: 'action', + width: 160, + render: function (_, row) { + return React.createElement(React.Fragment, null, + React.createElement(Button, { type: 'link', size: 'small', onClick: function () { message.info('请查看运维管理-车辆业务-后装设备-查看'); } }, '查看'), + React.createElement(Button, { type: 'link', size: 'small', onClick: function () { message.info('请查看运维管理-车辆业务-后装设备-编辑'); } }, '编辑'), + React.createElement(Button, { type: 'link', size: 'small', danger: true, onClick: function () { setRemoveConfirmRecord(row); } }, '拆除') + ); + } + } + ], + dataSource: paginatedList, + pagination: { + current: validPage, + pageSize: pageSize, + total: totalItems, + showSizeChanger: true, + showQuickJumper: true, + pageSizeOptions: ['10', '20', '50', '100'], + showTotal: function (total) { return '共 ' + total + ' 条'; }, + onChange: function (page, size) { + setCurrentPage(page); + if (size !== pageSize) setPageSize(size); + } + } + }) + }, + { + key: 'removed', + label: '拆除记录', + children: React.createElement(Table, { + rowKey: 'id', + size: 'small', + columns: [ + { title: '设备类型', dataIndex: 'deviceType', key: 'deviceType', width: 120 }, + { title: '车牌号', dataIndex: 'plateNo', key: 'plateNo', width: 120 }, + { title: '供应商', dataIndex: 'supplier', key: 'supplier', width: 120 }, + { title: '拆除时间', dataIndex: 'removeTime', key: 'removeTime', width: 160 }, + { title: '拆除人', dataIndex: 'removeOperator', key: 'removeOperator', width: 100 }, + { + title: '操作', + key: 'action', + width: 80, + render: function (_, row) { + return React.createElement(Button, { type: 'link', size: 'small', onClick: function () { message.info('请查看运维管理-车辆业务-后装设备-查看'); } }, '查看'); + } + } + ], + dataSource: paginatedList, + pagination: { + current: validPage, + pageSize: pageSize, + total: totalItems, + showSizeChanger: true, + showQuickJumper: true, + pageSizeOptions: ['10', '20', '50', '100'], + showTotal: function (total) { return '共 ' + total + ' 条'; }, + onChange: function (page, size) { + setCurrentPage(page); + if (size !== pageSize) setPageSize(size); + } + } + }) + } + ] + }) + ), + viewRecord && React.createElement(Modal, { + title: '查看后装设备', + open: !!viewRecord, + onCancel: function () { setViewRecord(null); }, + footer: React.createElement(Button, { onClick: function () { setViewRecord(null); } }, '关闭'), + width: 520, + children: viewRecord ? React.createElement('div', { style: { lineHeight: '2' } }, + React.createElement('div', null, '设备类型:', viewRecord.deviceType), + React.createElement('div', null, '车牌号:', viewRecord.plateNo), + React.createElement('div', null, '供应商:', viewRecord.supplier), + React.createElement('div', null, '安装时间:', viewRecord.createTime || '-'), + React.createElement('div', null, '安装人:', viewRecord.createOperator || '-'), + React.createElement('div', null, '拆除时间:', viewRecord.removeTime || '-'), + React.createElement('div', null, '拆除人:', viewRecord.removeOperator || '-') + ) : null + }), + editRecord && React.createElement(Modal, { + title: '编辑后装设备', + open: !!editRecord, + onCancel: function () { setEditRecord(null); }, + onOk: function () { + setEditRecord(null); + message.success('保存成功'); + }, + okText: '保存', + cancelText: '取消', + width: 520, + children: editRecord ? React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 16 } }, + React.createElement('div', null, React.createElement('span', { style: styles.label }, '设备类型'), React.createElement(Select, { style: { width: '100%' }, value: editRecord.deviceType, options: DEVICE_TYPES.map(function (d) { return { label: d, value: d }; }) })), + React.createElement('div', null, React.createElement('span', { style: styles.label }, '车牌号'), React.createElement(Input, { style: { width: '100%' }, value: editRecord.plateNo, readOnly: true })), + React.createElement('div', null, React.createElement('span', { style: styles.label }, '供应商'), React.createElement(Input, { style: { width: '100%' }, defaultValue: editRecord.supplier })) + ) : null + }), + removeConfirmRecord && React.createElement(Modal, { + title: '确认拆除', + open: !!removeConfirmRecord, + onCancel: function () { setRemoveConfirmRecord(null); }, + onOk: function () { handleRemoveConfirm(removeConfirmRecord); }, + okText: '确认拆除', + cancelText: '取消', + okButtonProps: { danger: true }, + children: removeConfirmRecord ? React.createElement('div', null, '确定要拆除该车辆「', removeConfirmRecord.deviceType, '」后装设备吗?') : null + }), + React.createElement(Modal, { + title: '需求说明', + open: showRequirementModal, + onCancel: function () { setShowRequirementModal(false); }, + footer: React.createElement(Button, { onClick: function () { setShowRequirementModal(false); } }, '关闭'), + width: 720, + children: React.createElement('div', { style: styles.modalContent }, + React.createElement('div', { style: styles.requirementSection }, + React.createElement('div', { style: styles.requirementSectionTitle }, '1. 面包屑:'), + React.createElement('div', { style: styles.requirementItem }, '1.1. 运维管理-车辆业务-后装设备') + ), + React.createElement('div', { style: styles.requirementSection }, + React.createElement('div', { style: styles.requirementSectionTitle }, '2. 筛选:'), + React.createElement('div', { style: styles.requirementItem }, '2.1. 支持通过设备类型、使用车辆、安装日期进行管理,点击查询后,筛选条件与列表内容联动。点击重置会回到默认筛选条件并在列表展示结果:'), + React.createElement('div', { style: styles.requirementSubItem }, '2.1.1. 设备类型:选择器,包括GPS、尾板、车身广告、G7安全套件、G7普通设备、G7温控设备、备胎;'), + React.createElement('div', { style: styles.requirementSubItem }, '2.1.2. 使用车辆:选择器,支持通过输入框对输入内容模糊搜索,并下拉对应选项;'), + React.createElement('div', { style: styles.requirementSubItem }, '2.1.3. 安装时间:日期选择器,支持单输入框内双日历选择开始-结束时间;') + ), + React.createElement('div', { style: styles.requirementSection }, + React.createElement('div', { style: styles.requirementSectionTitle }, '3. 列表:'), + React.createElement('div', { style: styles.requirementItem }, '列表分为2个tab,已安装/拆除记录;'), + React.createElement('div', { style: styles.requirementItem }, '当已安装列表对应车辆存在车身广告/尾板时,在备车时会自动拉取车身广告/尾板为有的状态;'), + React.createElement('div', { style: styles.requirementItem }, '当已安装列表不存在车身广告/尾板时,在备车时车身广告/尾板为无的状态;'), + React.createElement('div', { style: styles.requirementItem }, '如果在备车/交车时手动取消或勾选车身广告尾板,并完成提交,后装设备列表也会新增一条该车辆的车身广告/尾板安装数据;'), + React.createElement('div', { style: styles.requirementSectionTitle }, '3.1. 已安装:'), + React.createElement('div', { style: styles.requirementSubItem }, '3.1.1. 列表展示所有后装设备信息,字段依次为:设备类型、车牌号、供应商、安装时间、安装人、操作;列表右上角为新增、导出;'), + React.createElement('div', { style: styles.requirementSubItem }, '3.1.1.1. 设备类型:显示后装设备类型,包括:GPS、尾板、车身广告、G7安全套件、G7普通设备、G7温控设备、备胎;'), + React.createElement('div', { style: styles.requirementSubItem }, '3.1.1.2. 车牌号:显示后装设备对应车牌号信息;'), + React.createElement('div', { style: styles.requirementSubItem }, '3.1.1.3. 供应商:显示后装设备供应商信息;'), + React.createElement('div', { style: styles.requirementSubItem }, '3.1.1.4. 安装时间:显示后装设备安装时间,格式为YYYY-MM-DD HH:MM;'), + React.createElement('div', { style: styles.requirementSubItem }, '3.1.1.5. 安装人:显示后装设备安装用户名称;'), + React.createElement('div', { style: styles.requirementSubItem }, '3.1.1.6. 操作:支持查看、编辑、拆除;'), + React.createElement('div', { style: styles.requirementSectionTitle }, '3.2. 拆除记录:'), + React.createElement('div', { style: styles.requirementSubItem }, '3.2.1. 列表展示所有后装设备拆除记录,字段依次为:设备类型、车牌号、供应商、拆除时间、拆除人、操作;列表右上角为新增、导出;'), + React.createElement('div', { style: styles.requirementSubItem }, '3.2.1.1. 设备类型:显示后装设备类型,包括:GPS、尾板、车身广告、G7安全套件、G7普通设备、G7温控设备、备胎;'), + React.createElement('div', { style: styles.requirementSubItem }, '3.2.1.2. 车牌号:显示后装设备对应车牌号信息;'), + React.createElement('div', { style: styles.requirementSubItem }, '3.2.1.3. 供应商:显示后装设备供应商信息;'), + React.createElement('div', { style: styles.requirementSubItem }, '3.2.1.4. 拆除时间:显示后装设备拆除时间,格式为YYYY-MM-DD HH:MM;'), + React.createElement('div', { style: styles.requirementSubItem }, '3.2.1.5. 拆除人:显示后装设备拆除用户名称;'), + React.createElement('div', { style: styles.requirementSubItem }, '3.2.1.6. 操作:支持查看;') + ) + ) + }), + addModalVisible && React.createElement(Modal, { + title: '新增后装设备', + open: addModalVisible, + onCancel: function () { setAddModalVisible(false); }, + onOk: function () { + setDataList([{ id: String(Date.now()), deviceType: 'GPS', plateNo: '粤A12345', supplier: '新供应商', lastOpType: '安装', createTime: '2025-02-24 12:00', removeTime: '', createOperator: '当前用户', removeOperator: '' }].concat(dataList)); + setAddModalVisible(false); + message.success('新增成功'); + }, + okText: '确定', + cancelText: '取消', + width: 520, + children: React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 16 } }, + React.createElement('div', null, React.createElement('span', { style: styles.label }, '设备类型'), React.createElement(Select, { style: { width: '100%' }, placeholder: '请选择', options: DEVICE_TYPES.map(function (d) { return { label: d, value: d }; }) })), + React.createElement('div', null, React.createElement('span', { style: styles.label }, '使用车辆'), React.createElement(Select, { style: { width: '100%' }, placeholder: '请选择或搜索', showSearch: true, options: MOCK_VEHICLE_OPTIONS.map(function (v) { return { label: v, value: v }; }) })), + React.createElement('div', null, React.createElement('span', { style: styles.label }, '供应商'), React.createElement(Input, { style: { width: '100%' }, placeholder: '请输入供应商' })) + ) + }) + ); +}; +if (typeof window !== 'undefined') { + window.Component = Component; + function mount() { + var rootEl = document.getElementById('root'); + if (rootEl && window.ReactDOM && window.React) { + var root = ReactDOM.createRoot(rootEl); + root.render(React.createElement(Component)); + } + } + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', mount); + } else { + setTimeout(mount, 0); + } +} diff --git a/web端/运维管理/车辆业务/备车管理.jsx b/web端/运维管理/车辆业务/备车管理.jsx new file mode 100755 index 0000000..f2adfdb --- /dev/null +++ b/web端/运维管理/车辆业务/备车管理.jsx @@ -0,0 +1,652 @@ +// 【重要】必须使用 const Component 作为组件变量名 +// 备车管理 - 车辆资产管理后台(按 antd 规范) + +const Component = function () { + var useState = React.useState; + var useCallback = React.useCallback; + var useMemo = React.useMemo; + + var antd = window.antd; + var Breadcrumb = antd.Breadcrumb; + var Card = antd.Card; + var DatePicker = antd.DatePicker; + var Select = antd.Select; + var Button = antd.Button; + var Tabs = antd.Tabs; + var Table = antd.Table; + var Modal = antd.Modal; + var Steps = antd.Steps; + var Input = antd.Input; + var Radio = antd.Radio; + var Space = antd.Space; + var Row = antd.Row; + var Col = antd.Col; + var message = antd.message; + + var RangePicker = DatePicker.RangePicker; + + // 当前视图:'list' | 'add' + var viewState = useState('list'); + var currentView = viewState[0]; + var setCurrentView = viewState[1]; + + var filterState = useState({ + dateStart: '', + dateEnd: '', + operator: undefined, + plateNo: undefined, + vehicleType: undefined, + parkingLot: undefined + }); + var filters = filterState[0]; + var setFilters = filterState[1]; + // 实际参与列表筛选的条件(点击查询后与 filters 同步) + var appliedFilterState = useState({ + dateStart: '', + dateEnd: '', + operator: undefined, + plateNo: undefined, + vehicleType: undefined, + parkingLot: undefined + }); + var appliedFilters = appliedFilterState[0]; + var setAppliedFilters = appliedFilterState[1]; + + var activeTabState = useState('completed'); + var activeTab = activeTabState[0]; + var setActiveTab = activeTabState[1]; + + var defaultInspection = [{ checkType: '灯光', checkItem: '前大灯', result: '正常', treadDepth: '', remark: '' }, { checkType: '灯光', checkItem: '尾灯', result: '正常', treadDepth: '', remark: '' }, { checkType: '轮胎', checkItem: '前左胎', result: '正常', treadDepth: '5.2mm', remark: '' }, { checkType: '车身', checkItem: '车身外观', result: '正常', treadDepth: '', remark: '' }]; + var completedListState = useState([ + { id: '1', completeDate: '2025-02-10 09:30', operator: '张三', vehicleType: '厢式货车', plateNo: '京A12345', brand: '东风', model: 'DFH1180', vin: 'LGHXCAE28M1234567', parkingLot: '朝阳停车场', bodyAd: '有', adPhotoCount: 3, adPhotos: ['https://picsum.photos/200/150?random=1', 'https://picsum.photos/200/150?random=2', 'https://picsum.photos/200/150?random=3'], tailboard: '有', hasTrailer: '有', trailerPlate: '京B67890', flawPhotoCount: 2, flawPhotos: ['https://picsum.photos/200/150?random=4', 'https://picsum.photos/200/150?random=5'], inspectionList: [{ checkType: '灯光', checkItem: '前大灯', result: '正常', treadDepth: '', remark: '' }, { checkType: '灯光', checkItem: '尾灯', result: '正常', treadDepth: '', remark: '' }, { checkType: '轮胎', checkItem: '前左胎', result: '正常', treadDepth: '5.2mm', remark: '' }, { checkType: '车身', checkItem: '车身外观', result: '异常', treadDepth: '', remark: '有划痕' }] }, + { id: '2', completeDate: '2025-02-09 10:15', operator: '李四', vehicleType: '平板货车', plateNo: '京C11111', brand: '福田', model: 'BJ1180', vin: 'LGHXCAE28M7654321', parkingLot: '海淀停车场', bodyAd: '无', adPhotoCount: 0, adPhotos: [], tailboard: '无', hasTrailer: '无', trailerPlate: '', flawPhotoCount: 1, flawPhotos: ['https://picsum.photos/200/150?random=6'], inspectionList: [{ checkType: '灯光', checkItem: '前大灯', result: '正常', treadDepth: '', remark: '' }, { checkType: '轮胎', checkItem: '前左胎', result: '异常', treadDepth: '3.1mm', remark: '需更换' }] }, + { id: '3', completeDate: '2025-02-08 11:00', operator: '王五', vehicleType: '厢式货车', plateNo: '京D22222', brand: '江淮', model: 'HFC1180', vin: 'LGHXCAE28M8888888', parkingLot: '丰台停车场', bodyAd: '有', adPhotoCount: 2, adPhotos: ['https://picsum.photos/200/150?random=7', 'https://picsum.photos/200/150?random=8'], tailboard: '有', hasTrailer: '无', trailerPlate: '', flawPhotoCount: 0, flawPhotos: [], inspectionList: defaultInspection }, + { id: '4', completeDate: '2025-02-07 13:45', operator: '赵六', vehicleType: '栏板货车', plateNo: '京E33333', brand: '重汽', model: 'ZZ1180', vin: 'LGHXCAE28M7777777', parkingLot: '西城停车场', bodyAd: '无', adPhotoCount: 0, adPhotos: [], tailboard: '无', hasTrailer: '有', trailerPlate: '京E33334', flawPhotoCount: 1, flawPhotos: ['https://picsum.photos/200/150?random=9'], inspectionList: defaultInspection }, + { id: '5', completeDate: '2025-02-06 08:20', operator: '张三', vehicleType: '平板货车', plateNo: '京A23456', brand: '东风', model: 'DFH1190', vin: 'LGHXCAE28M6666666', parkingLot: '朝阳停车场', bodyAd: '有', adPhotoCount: 1, adPhotos: ['https://picsum.photos/200/150?random=10'], tailboard: '有', hasTrailer: '无', trailerPlate: '', flawPhotoCount: 0, flawPhotos: [], inspectionList: defaultInspection }, + { id: '6', completeDate: '2025-02-05 14:30', operator: '李四', vehicleType: '厢式货车', plateNo: '京B34567', brand: '福田', model: 'BJ1190', vin: 'LGHXCAE28M5555555', parkingLot: '海淀停车场', bodyAd: '无', adPhotoCount: 0, adPhotos: [], tailboard: '无', hasTrailer: '无', trailerPlate: '', flawPhotoCount: 2, flawPhotos: ['https://picsum.photos/200/150?random=11', 'https://picsum.photos/200/150?random=12'], inspectionList: defaultInspection }, + { id: '7', completeDate: '2025-02-04 16:00', operator: '王五', vehicleType: '栏板货车', plateNo: '京C45678', brand: '江淮', model: 'HFC1190', vin: 'LGHXCAE28M4444444', parkingLot: '丰台停车场', bodyAd: '有', adPhotoCount: 2, adPhotos: ['https://picsum.photos/200/150?random=13', 'https://picsum.photos/200/150?random=14'], tailboard: '有', hasTrailer: '有', trailerPlate: '京C45679', flawPhotoCount: 0, flawPhotos: [], inspectionList: defaultInspection }, + { id: '8', completeDate: '2025-02-03 09:00', operator: '赵六', vehicleType: '厢式货车', plateNo: '京D56789', brand: '重汽', model: 'ZZ1190', vin: 'LGHXCAE28M3333333', parkingLot: '西城停车场', bodyAd: '无', adPhotoCount: 0, adPhotos: [], tailboard: '有', hasTrailer: '无', trailerPlate: '', flawPhotoCount: 1, flawPhotos: ['https://picsum.photos/200/150?random=15'], inspectionList: defaultInspection }, + { id: '9', completeDate: '2025-02-02 10:30', operator: '张三', vehicleType: '平板货车', plateNo: '京E67890', brand: '东风', model: 'DFH1200', vin: 'LGHXCAE28M2222222', parkingLot: '朝阳停车场', bodyAd: '有', adPhotoCount: 3, adPhotos: ['https://picsum.photos/200/150?random=16', 'https://picsum.photos/200/150?random=17', 'https://picsum.photos/200/150?random=18'], tailboard: '无', hasTrailer: '无', trailerPlate: '', flawPhotoCount: 0, flawPhotos: [], inspectionList: defaultInspection }, + { id: '10', completeDate: '2025-02-01 15:45', operator: '李四', vehicleType: '厢式货车', plateNo: '京A78901', brand: '福田', model: 'BJ1200', vin: 'LGHXCAE28M1111111', parkingLot: '海淀停车场', bodyAd: '无', adPhotoCount: 0, adPhotos: [], tailboard: '有', hasTrailer: '有', trailerPlate: '京A78902', flawPhotoCount: 0, flawPhotos: [], inspectionList: defaultInspection } + ]); + var completedList = completedListState[0]; + var setCompletedList = completedListState[1]; + + var pendingListState = useState([ + { id: 'p1', operateDate: '2025-02-10 14:30', operator: '王五', vehicleType: '厢式货车', plateNo: '京F10001', brand: '江淮', model: 'HFC1180', vin: 'LGHXCAE28M9990001', parkingLot: '丰台停车场', bodyAd: '有', adPhotoCount: 2, adPhotos: ['https://picsum.photos/200/150?random=19', 'https://picsum.photos/200/150?random=20'], tailboard: '有', hasTrailer: '无', trailerPlate: '', flawPhotoCount: 0, flawPhotos: [] }, + { id: 'p2', operateDate: '2025-02-09 09:15', operator: '赵六', vehicleType: '平板货车', plateNo: '京F10002', brand: '重汽', model: 'ZZ1180', vin: 'LGHXCAE28M9990002', parkingLot: '西城停车场', bodyAd: '无', adPhotoCount: 0, adPhotos: [], tailboard: '无', hasTrailer: '无', trailerPlate: '', flawPhotoCount: 1, flawPhotos: ['https://picsum.photos/200/150?random=21'] }, + { id: 'p3', operateDate: '2025-02-08 11:45', operator: '张三', vehicleType: '栏板货车', plateNo: '京F10003', brand: '东风', model: 'DFH1180', vin: 'LGHXCAE28M9990003', parkingLot: '朝阳停车场', bodyAd: '有', adPhotoCount: 1, adPhotos: ['https://picsum.photos/200/150?random=22'], tailboard: '有', hasTrailer: '有', trailerPlate: '京F10004', flawPhotoCount: 0, flawPhotos: [] }, + { id: 'p4', operateDate: '2025-02-07 08:00', operator: '李四', vehicleType: '厢式货车', plateNo: '京F10005', brand: '福田', model: 'BJ1180', vin: 'LGHXCAE28M9990004', parkingLot: '海淀停车场', bodyAd: '无', adPhotoCount: 0, adPhotos: [], tailboard: '无', hasTrailer: '无', trailerPlate: '', flawPhotoCount: 0, flawPhotos: [] }, + { id: 'p5', operateDate: '2025-02-06 13:20', operator: '王五', vehicleType: '厢式货车', plateNo: '京F10006', brand: '江淮', model: 'HFC1190', vin: 'LGHXCAE28M9990005', parkingLot: '丰台停车场', bodyAd: '有', adPhotoCount: 3, adPhotos: ['https://picsum.photos/200/150?random=23', 'https://picsum.photos/200/150?random=24', 'https://picsum.photos/200/150?random=25'], tailboard: '有', hasTrailer: '无', trailerPlate: '', flawPhotoCount: 2, flawPhotos: ['https://picsum.photos/200/150?random=26', 'https://picsum.photos/200/150?random=27'] }, + { id: 'p6', operateDate: '2025-02-05 15:30', operator: '赵六', vehicleType: '平板货车', plateNo: '京F10007', brand: '重汽', model: 'ZZ1190', vin: 'LGHXCAE28M9990006', parkingLot: '西城停车场', bodyAd: '无', adPhotoCount: 0, adPhotos: [], tailboard: '无', hasTrailer: '有', trailerPlate: '京F10008', flawPhotoCount: 0, flawPhotos: [] }, + { id: 'p7', operateDate: '2025-02-04 10:00', operator: '张三', vehicleType: '厢式货车', plateNo: '京F10009', brand: '东风', model: 'DFH1190', vin: 'LGHXCAE28M9990007', parkingLot: '朝阳停车场', bodyAd: '有', adPhotoCount: 2, adPhotos: ['https://picsum.photos/200/150?random=28', 'https://picsum.photos/200/150?random=29'], tailboard: '有', hasTrailer: '无', trailerPlate: '', flawPhotoCount: 0, flawPhotos: [] }, + { id: 'p8', operateDate: '2025-02-03 16:45', operator: '李四', vehicleType: '栏板货车', plateNo: '京F10010', brand: '福田', model: 'BJ1190', vin: 'LGHXCAE28M9990008', parkingLot: '海淀停车场', bodyAd: '无', adPhotoCount: 0, adPhotos: [], tailboard: '无', hasTrailer: '无', trailerPlate: '', flawPhotoCount: 1, flawPhotos: ['https://picsum.photos/200/150?random=30'] }, + { id: 'p9', operateDate: '2025-02-02 09:30', operator: '王五', vehicleType: '厢式货车', plateNo: '京F10011', brand: '江淮', model: 'HFC1200', vin: 'LGHXCAE28M9990009', parkingLot: '丰台停车场', bodyAd: '有', adPhotoCount: 1, adPhotos: ['https://picsum.photos/200/150?random=31'], tailboard: '有', hasTrailer: '有', trailerPlate: '京F10012', flawPhotoCount: 0, flawPhotos: [] }, + { id: 'p10', operateDate: '2025-02-01 14:00', operator: '赵六', vehicleType: '平板货车', plateNo: '京F10013', brand: '重汽', model: 'ZZ1200', vin: 'LGHXCAE28M9990010', parkingLot: '西城停车场', bodyAd: '无', adPhotoCount: 0, adPhotos: [], tailboard: '无', hasTrailer: '无', trailerPlate: '', flawPhotoCount: 0, flawPhotos: [] } + ]); + var pendingList = pendingListState[0]; + var setPendingList = pendingListState[1]; + + var pageState = useState(1); + var page = pageState[0]; + var setPage = pageState[1]; + var pageSizeState = useState(10); + var pageSize = pageSizeState[0]; + var setPageSize = pageSizeState[1]; + + var operatorOptions = useMemo(function () { return [{ value: '张三', label: '张三' }, { value: '李四', label: '李四' }, { value: '王五', label: '王五' }, { value: '赵六', label: '赵六' }]; }, []); + var plateOptions = useMemo(function () { return [{ value: '京A12345', label: '京A12345' }, { value: '京C11111', label: '京C11111' }, { value: '京D22222', label: '京D22222' }, { value: '京E33333', label: '京E33333' }]; }, []); + var vehicleTypeOptions = useMemo(function () { return [{ value: '厢式货车', label: '厢式货车' }, { value: '平板货车', label: '平板货车' }, { value: '栏板货车', label: '栏板货车' }]; }, []); + var parkingOptions = useMemo(function () { return [{ value: '朝阳停车场', label: '朝阳停车场' }, { value: '海淀停车场', label: '海淀停车场' }, { value: '丰台停车场', label: '丰台停车场' }, { value: '西城停车场', label: '西城停车场' }]; }, []); + + var reqDocOpenState = useState(false); + var photoModalState = useState({ open: false, photos: [], currentIndex: 0 }); + + var addStepState = useState(1); + var addFormState = useState({ + plateNo: '', + vehicleType: '', + brand: '', + vin: '', + bodyAd: '无', + adPhotos: [], + tailboard: '无', + hasTrailer: '', + trailerPlate: '', + flawPhotos: [], + inspectionList: [ + { checkType: '灯光', checkItem: '前大灯', result: '正常', treadDepth: '', remark: '' }, + { checkType: '灯光', checkItem: '尾灯', result: '正常', treadDepth: '', remark: '' }, + { checkType: '轮胎', checkItem: '前左胎', result: '正常', treadDepth: '', remark: '' }, + { checkType: '车身', checkItem: '车身外观', result: '正常', treadDepth: '', remark: '' } + ] + }); + + var filteredCompleted = useMemo(function () { + var list = completedList.slice(); + if (appliedFilters.operator) list = list.filter(function (r) { return r.operator.indexOf(appliedFilters.operator) !== -1; }); + if (appliedFilters.plateNo) list = list.filter(function (r) { return r.plateNo.indexOf(appliedFilters.plateNo) !== -1; }); + if (appliedFilters.vehicleType) list = list.filter(function (r) { return r.vehicleType.indexOf(appliedFilters.vehicleType) !== -1; }); + if (appliedFilters.parkingLot) list = list.filter(function (r) { return r.parkingLot.indexOf(appliedFilters.parkingLot) !== -1; }); + if (appliedFilters.dateStart) list = list.filter(function (r) { var d = (r.completeDate || '').slice(0, 10); return d >= appliedFilters.dateStart; }); + if (appliedFilters.dateEnd) list = list.filter(function (r) { var d = (r.completeDate || '').slice(0, 10); return d <= appliedFilters.dateEnd; }); + return list.sort(function (a, b) { return (b.completeDate || '').localeCompare(a.completeDate || ''); }); + }, [completedList, appliedFilters]); + + var filteredPending = useMemo(function () { + var list = pendingList.slice(); + if (appliedFilters.operator) list = list.filter(function (r) { return r.operator.indexOf(appliedFilters.operator) !== -1; }); + if (appliedFilters.plateNo) list = list.filter(function (r) { return r.plateNo.indexOf(appliedFilters.plateNo) !== -1; }); + if (appliedFilters.vehicleType) list = list.filter(function (r) { return r.vehicleType.indexOf(appliedFilters.vehicleType) !== -1; }); + if (appliedFilters.parkingLot) list = list.filter(function (r) { return r.parkingLot.indexOf(appliedFilters.parkingLot) !== -1; }); + if (appliedFilters.dateStart) list = list.filter(function (r) { var d = (r.operateDate || '').slice(0, 10); return d >= appliedFilters.dateStart; }); + if (appliedFilters.dateEnd) list = list.filter(function (r) { var d = (r.operateDate || '').slice(0, 10); return d <= appliedFilters.dateEnd; }); + return list.sort(function (a, b) { return (b.operateDate || '').localeCompare(a.operateDate || ''); }); + }, [pendingList, appliedFilters]); + + var displayCompleted = useMemo(function () { + var start = (page - 1) * pageSize; + return filteredCompleted.slice(start, start + pageSize); + }, [filteredCompleted, page, pageSize]); + + var displayPending = useMemo(function () { + var start = (page - 1) * pageSize; + return filteredPending.slice(start, start + pageSize); + }, [filteredPending, page, pageSize]); + + var totalCompleted = filteredCompleted.length; + var totalPending = filteredPending.length; + var totalCount = activeTab === 'completed' ? totalCompleted : totalPending; + + var handleQuery = useCallback(function () { + setAppliedFilters({ dateStart: filters.dateStart, dateEnd: filters.dateEnd, operator: filters.operator, plateNo: filters.plateNo, vehicleType: filters.vehicleType, parkingLot: filters.parkingLot }); + setPage(1); + }, [filters.dateStart, filters.dateEnd, filters.operator, filters.plateNo, filters.vehicleType, filters.parkingLot]); + var handleReset = useCallback(function () { + var empty = { dateStart: '', dateEnd: '', operator: undefined, plateNo: undefined, vehicleType: undefined, parkingLot: undefined }; + setFilters(empty); + setAppliedFilters(empty); + setPage(1); + }, []); + + var handleExport = useCallback(function () { + if (activeTab !== 'completed') { + message.warning('请在「已完成」Tab 下导出'); + return; + } + var rows = filteredCompleted; + var headers = ['备车时间', '备车人', '车辆类型', '车牌号', '品牌', '型号', '车辆识别代码', '停车场', '车身广告', '广告照片', '尾板', '是否有挂', '挂车牌号', '瑕疵照片']; + var csv = headers.join(',') + '\n'; + rows.forEach(function (r) { + csv += [r.completeDate, r.operator, r.vehicleType, r.plateNo, r.brand, r.model, r.vin, r.parkingLot, r.bodyAd, (r.adPhotoCount || 0) + '张', r.tailboard, r.hasTrailer, r.trailerPlate || '', (r.flawPhotoCount || 0) + '张'].join(',') + '\n'; + }); + var blob = new Blob(['\ufeff' + csv], { type: 'text/csv;charset=utf-8' }); + var url = URL.createObjectURL(blob); + var a = document.createElement('a'); + a.href = url; + a.download = '备车管理导出_' + new Date().getTime() + '.csv'; + a.click(); + URL.revokeObjectURL(url); + message.success('导出成功'); + }, [activeTab, filteredCompleted]); + + var openPhotoModal = useCallback(function (photos, index) { + if (!photos || photos.length === 0) return; + photoModalState[1]({ open: true, photos: photos, currentIndex: index || 0 }); + }, []); + + var addForm = addFormState[0]; + var setAddForm = addFormState[1]; + var addStep = addStepState[0]; + var setAddStep = addStepState[1]; + + var handleAddSave = useCallback(function () { + var d = new Date(); + var pad = function (n) { return String(n).padStart(2, '0'); }; + var dateTimeStr = d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()) + ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes()); + var item = { + id: 'p' + Date.now(), + operateDate: dateTimeStr, + operator: '当前用户', + vehicleType: addForm.vehicleType || '厢式货车', + plateNo: addForm.plateNo, + brand: addForm.brand || '-', + model: addForm.model || '-', + vin: addForm.vin, + parkingLot: addForm.parkingLot || '朝阳停车场', + bodyAd: addForm.bodyAd, + adPhotoCount: (addForm.adPhotos && addForm.adPhotos.length) || 0, + adPhotos: addForm.adPhotos || [], + tailboard: addForm.tailboard, + hasTrailer: addForm.trailerPlate ? '有' : '无', + trailerPlate: addForm.trailerPlate || '', + flawPhotoCount: (addForm.flawPhotos && addForm.flawPhotos.length) || 0, + flawPhotos: addForm.flawPhotos || [] + }; + setPendingList(pendingList.concat([item])); + setCurrentView('list'); + setActiveTab('pending'); + setAddForm({ plateNo: '', vehicleType: '', brand: '', vin: '', bodyAd: '无', adPhotos: [], tailboard: '无', hasTrailer: '', trailerPlate: '', flawPhotos: [], inspectionList: addForm.inspectionList }); + setAddStep(1); + message.success('已保存至待提交列表'); + }, [addForm, pendingList]); + + var handleAddNext = useCallback(function () { + if (!addForm.plateNo) { + message.warning('请选择车牌号'); + return; + } + setAddStep(2); + }, [addForm.plateNo]); + + var handleAddSubmit = useCallback(function () { + var d = new Date(); + var pad = function (n) { return String(n).padStart(2, '0'); }; + var dateTimeStr = d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()) + ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes()); + var item = { + id: String(Date.now()), + completeDate: dateTimeStr, + operator: '当前用户', + vehicleType: addForm.vehicleType || '厢式货车', + plateNo: addForm.plateNo, + brand: addForm.brand || '-', + model: addForm.model || '-', + vin: addForm.vin, + parkingLot: addForm.parkingLot || '朝阳停车场', + bodyAd: addForm.bodyAd, + adPhotoCount: (addForm.adPhotos && addForm.adPhotos.length) || 0, + adPhotos: addForm.adPhotos || [], + tailboard: addForm.tailboard, + hasTrailer: addForm.trailerPlate ? '有' : '无', + trailerPlate: addForm.trailerPlate || '', + flawPhotoCount: (addForm.flawPhotos && addForm.flawPhotos.length) || 0, + flawPhotos: addForm.flawPhotos || [], + inspectionList: addForm.inspectionList || [] + }; + setCompletedList(completedList.concat([item])); + setCurrentView('list'); + setActiveTab('completed'); + setAddForm({ plateNo: '', vehicleType: '', brand: '', vin: '', bodyAd: '无', adPhotos: [], tailboard: '无', hasTrailer: '', trailerPlate: '', flawPhotos: [], inspectionList: addForm.inspectionList }); + setAddStep(1); + message.success('提交成功'); + }, [addForm, completedList]); + + var onPlateSelect = useCallback(function (plateNo) { + setAddForm(function (prev) { + var next = {}; + for (var k in prev) next[k] = prev[k]; + next.plateNo = plateNo; + next.vehicleType = '厢式货车'; + next.brand = '东风'; + next.vin = 'LGHXCAE28M' + Math.floor(Math.random() * 10000000); + return next; + }); + }, []); + + var requirementDocContent = '#.面包屑:运维管理-车辆业务-备车管理,支持点击对应模块跳转\n\n#筛选:支持通过备车日期、备车人、车牌号、车辆类型、停车场等方式进行筛选;\n1.备车日期:日期选择器,点击显示两个日历控件,支持选择开始和结束时间,时间格式为YYYY-MM-DD;\n2.备车人:选择器,支持从输入框输入内容模糊搜索展示对应选项;\n3.车牌号:选择器,支持从输入框输入内容模糊搜索展示对应选项;\n4.车辆类型:选择器,支持从输入框输入内容模糊搜索展示对应选项;\n5.停车场:选择器,支持从输入框输入内容模糊搜索展示对应选项;\n6.右侧显示查询、重置按钮,点击查询按钮按筛选条件显示列表内容,点击重置清除筛选条件并按默认条件显示列表内容;\n\n#列表:\n列表分为两个tab,分别为:已完成、待检查;已完成列表右侧包含新增、导出按钮,下方增加分页功能,支持选择单页显示数据记录条数;\n1.已完成tab显示完成车辆记录及备车检查两个步骤表单并点击提交的记录;\n2.待检查tab显示新增备车时只点击保存,但未提交的记录;\n3.点击新增按钮,打开新页面进行新增表单页;\n4.导出需全选/多选对应数据记录,再点击导出下载csv;\n5.列表以区域进行数据权限划分,用户表中对应区域的用户只能查询自己区域的数据;'; + + // 日期范围变化(antd RangePicker 返回 dayjs 或 [string, string] 依项目而定,此处用字符串存) + function onDateRangeChange(dates, dateStrings) { + setFilters(function (f) { + var g = {}; + for (var k in f) g[k] = f[k]; + g.dateStart = (dateStrings && dateStrings[0]) || ''; + g.dateEnd = (dateStrings && dateStrings[1]) || ''; + return g; + }); + } + + var dateRangeValue = useMemo(function () { + if (!filters.dateStart && !filters.dateEnd) return null; + try { + if (typeof window.dayjs === 'function' && filters.dateStart && filters.dateEnd) { + return [window.dayjs(filters.dateStart), window.dayjs(filters.dateEnd)]; + } + } catch (e) {} + return null; + }, [filters.dateStart, filters.dateEnd]); + + // 已完成表格列(含多选、照片链接、操作列查看按钮,点击不打开新页面) + function getCompletedColumns(openPhoto) { + return [ + { title: '备车时间', dataIndex: 'completeDate', key: 'completeDate', width: 160 }, + { title: '备车人', dataIndex: 'operator', key: 'operator', width: 90 }, + { title: '车辆类型', dataIndex: 'vehicleType', key: 'vehicleType', width: 100 }, + { title: '品牌', dataIndex: 'brand', key: 'brand', width: 80 }, + { title: '型号', dataIndex: 'model', key: 'model', width: 100 }, + { title: '车牌号', dataIndex: 'plateNo', key: 'plateNo', width: 100 }, + { title: '车辆识别代码', dataIndex: 'vin', key: 'vin', width: 180 }, + { title: '停车场', dataIndex: 'parkingLot', key: 'parkingLot', width: 110 }, + { title: '车身广告', dataIndex: 'bodyAd', key: 'bodyAd', width: 90 }, + { title: '广告照片', key: 'adPhotos', width: 90, render: function (_, r) { + var photos = r.adPhotos && r.adPhotos.length > 0 ? r.adPhotos : []; + return photos.length > 0 ? React.createElement(Button, { type: 'link', size: 'small', onClick: function (e) { if (e) { e.preventDefault(); e.stopPropagation(); } openPhoto(photos, 0); } }, photos.length + '张') : '-'; + }}, + { title: '尾板', dataIndex: 'tailboard', key: 'tailboard', width: 70 }, + { title: '是否有挂', dataIndex: 'hasTrailer', key: 'hasTrailer', width: 90 }, + { title: '挂车牌号', dataIndex: 'trailerPlate', key: 'trailerPlate', width: 100, render: function (v) { return v || '-'; } }, + { title: '瑕疵照片', key: 'flawPhotos', width: 90, render: function (_, r) { + var photos = r.flawPhotos && r.flawPhotos.length > 0 ? r.flawPhotos : []; + return photos.length > 0 ? React.createElement(Button, { type: 'link', size: 'small', onClick: function (e) { if (e) { e.preventDefault(); e.stopPropagation(); } openPhoto(photos, 0); } }, photos.length + '张') : '-'; + }}, + { title: '操作', key: 'action', width: 80, fixed: 'right', render: function (_, r) { + return React.createElement(Button, { type: 'link', size: 'small', onClick: function () {} }, '查看'); + }} + ]; + } + + function getPendingColumns(openPhoto, onEdit) { + return [ + { title: '创建时间', dataIndex: 'operateDate', key: 'operateDate', width: 160 }, + { title: '备车人', dataIndex: 'operator', key: 'operator', width: 90 }, + { title: '车辆类型', dataIndex: 'vehicleType', key: 'vehicleType', width: 100 }, + { title: '品牌', dataIndex: 'brand', key: 'brand', width: 80 }, + { title: '型号', dataIndex: 'model', key: 'model', width: 100 }, + { title: '车牌号', dataIndex: 'plateNo', key: 'plateNo', width: 100 }, + { title: '车辆识别代码', dataIndex: 'vin', key: 'vin', width: 180 }, + { title: '停车场', dataIndex: 'parkingLot', key: 'parkingLot', width: 110 }, + { title: '车身广告', dataIndex: 'bodyAd', key: 'bodyAd', width: 90 }, + { title: '广告照片', key: 'adPhotos', width: 90, render: function (_, r) { + var photos = (r.adPhotos && r.adPhotos.length > 0) ? r.adPhotos : []; + return photos.length > 0 ? React.createElement(Button, { type: 'link', size: 'small', onClick: function (e) { if (e) { e.preventDefault(); e.stopPropagation(); } openPhoto(photos, 0); } }, photos.length + '张') : '-'; + }}, + { title: '尾板', dataIndex: 'tailboard', key: 'tailboard', width: 70 }, + { title: '是否有挂', dataIndex: 'hasTrailer', key: 'hasTrailer', width: 90 }, + { title: '挂车牌号', dataIndex: 'trailerPlate', key: 'trailerPlate', width: 100, render: function (v) { return v || '-'; } }, + { title: '瑕疵照片', key: 'flawPhotos', width: 90, render: function (_, r) { + var photos = (r.flawPhotos && r.flawPhotos.length > 0) ? r.flawPhotos : []; + return photos.length > 0 ? React.createElement(Button, { type: 'link', size: 'small', onClick: function (e) { if (e) { e.preventDefault(); e.stopPropagation(); } openPhoto(photos, 0); } }, photos.length + '张') : '-'; + }}, + { title: '操作', key: 'action', width: 80, fixed: 'right', render: function (_, r) { + return React.createElement(Button, { type: 'link', size: 'small', onClick: function () { + setAddForm(function (prev) { + var n = {}; + for (var k in prev) n[k] = prev[k]; + n.plateNo = r.plateNo; n.vehicleType = r.vehicleType; n.brand = r.brand; n.vin = r.vin; + n.bodyAd = r.bodyAd; n.adPhotos = r.adPhotos || []; n.tailboard = r.tailboard; + n.trailerPlate = r.trailerPlate || ''; n.flawPhotos = r.flawPhotos || []; + return n; + }); + setAddStep(1); + setCurrentView('add'); + } }, '编辑'); + }} + ]; + } + + var tablePagination = useMemo(function () { + return { + current: page, + pageSize: pageSize, + total: totalCount, + showSizeChanger: true, + showTotal: function (t) { return '共 ' + t + ' 条'; }, + pageSizeOptions: ['10', '20', '50'], + onChange: function (p, size) { + setPage(p); + if (size !== pageSize) setPageSize(size); + } + }; + }, [page, pageSize, totalCount]); + + // —————— 列表页 —————— + function ListView() { + var breadcrumbItems = [ + { title: '运维管理' }, + { title: '车辆业务' }, + { title: '备车管理' } + ]; + return React.createElement('div', { style: { padding: 24, background: '#f5f5f5', minHeight: '100vh' } }, + React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 } }, + React.createElement(Breadcrumb, { items: breadcrumbItems }), + React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { reqDocOpenState[1](true); } }, '查看需求说明') + ), + React.createElement(Modal, { + title: '需求说明', + open: reqDocOpenState[0], + onCancel: function () { reqDocOpenState[1](false); }, + footer: null, + width: 720, + styles: { body: { maxHeight: '70vh', overflow: 'auto', whiteSpace: 'pre-wrap', fontSize: 14 } } + }, requirementDocContent.split('\n').map(function (line, idx) { + var trimmed = line; + if (trimmed.indexOf('#') === 0) { + return React.createElement('div', { key: idx, style: { fontWeight: 'bold', marginTop: idx === 0 ? 0 : 16, marginBottom: 8, paddingBottom: 4, borderBottom: '1px solid #f0f0f0' } }, trimmed.replace(/^#\.?/, '').trim()); + } + if (/^\d+\./.test(trimmed)) return React.createElement('div', { key: idx, style: { marginLeft: 12, marginBottom: 4 } }, trimmed); + if (trimmed === '') return React.createElement('div', { key: idx, style: { height: 12 } }); + return React.createElement('div', { key: idx, style: { marginBottom: 8 } }, trimmed); + })), + React.createElement(Card, { style: { marginBottom: 16 } }, + React.createElement('div', { + style: { + display: 'grid', + gridTemplateColumns: '1fr 1fr 1fr', + gap: '16px 24px', + alignItems: 'start', + flex: 1, + minWidth: 0 + } + }, + React.createElement('div', null, + React.createElement('div', { style: { marginBottom: 4 } }, '备车日期'), + React.createElement(RangePicker, { + value: dateRangeValue, + onChange: onDateRangeChange, + style: { width: '100%' }, + placeholder: ['开始日期', '结束日期'] + }) + ), + React.createElement('div', null, + React.createElement('div', { style: { marginBottom: 4 } }, '备车人'), + React.createElement(Select, { + placeholder: '请选择备车人', + allowClear: true, + showSearch: true, + optionFilterProp: 'label', + value: filters.operator, + onChange: function (v) { setFilters(function (f) { var g = {}; for (var k in f) g[k] = f[k]; g.operator = v; return g; }); }, + style: { width: '100%' }, + options: operatorOptions + }) + ), + React.createElement('div', null, + React.createElement('div', { style: { marginBottom: 4 } }, '车牌号'), + React.createElement(Select, { + placeholder: '请选择车牌号', + allowClear: true, + showSearch: true, + optionFilterProp: 'label', + value: filters.plateNo, + onChange: function (v) { setFilters(function (f) { var g = {}; for (var k in f) g[k] = f[k]; g.plateNo = v; return g; }); }, + style: { width: '100%' }, + options: plateOptions + }) + ), + React.createElement('div', null, + React.createElement('div', { style: { marginBottom: 4 } }, '车辆类型'), + React.createElement(Select, { + placeholder: '请选择车辆类型', + allowClear: true, + showSearch: true, + optionFilterProp: 'label', + value: filters.vehicleType, + onChange: function (v) { setFilters(function (f) { var g = {}; for (var k in f) g[k] = f[k]; g.vehicleType = v; return g; }); }, + style: { width: '100%' }, + options: vehicleTypeOptions + }) + ), + React.createElement('div', null, + React.createElement('div', { style: { marginBottom: 4 } }, '停车场'), + React.createElement(Select, { + placeholder: '请选择停车场', + allowClear: true, + showSearch: true, + optionFilterProp: 'label', + value: filters.parkingLot, + onChange: function (v) { setFilters(function (f) { var g = {}; for (var k in f) g[k] = f[k]; g.parkingLot = v; return g; }); }, + style: { width: '100%' }, + options: parkingOptions + }) + ) + ), + React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 16 } }, + React.createElement(Button, { onClick: handleReset }, '重置'), + React.createElement(Button, { type: 'primary', onClick: handleQuery }, '查询') + ) + ), + React.createElement(Card, null, + React.createElement(Tabs, { + activeKey: activeTab, + onChange: function (k) { setActiveTab(k); setPage(1); }, + tabBarExtraContent: React.createElement('div', { style: { display: 'flex', gap: 8 } }, + React.createElement(Button, { type: 'primary', onClick: function () {} }, '新增'), + activeTab === 'completed' ? React.createElement(Button, { onClick: handleExport }, '导出') : null + ), + items: [ + { + key: 'completed', + label: '已完成', + children: React.createElement(Table, { + columns: getCompletedColumns(openPhotoModal), + dataSource: displayCompleted, + rowKey: 'id', + pagination: tablePagination, + scroll: { x: 1400 }, + size: 'middle' + }) + }, + { + key: 'pending', + label: '待提交', + children: React.createElement(Table, { + columns: getPendingColumns(openPhotoModal), + dataSource: displayPending, + rowKey: 'id', + pagination: tablePagination, + scroll: { x: 1400 }, + size: 'middle' + }) + } + ] + }) + ), + // 照片预览:全屏遮罩 + 放大图片,点击遮罩关闭 + photoModalState[0].open && photoModalState[0].photos && photoModalState[0].photos.length > 0 ? React.createElement('div', { + style: { + position: 'fixed', + top: 0, + left: 0, + right: 0, + bottom: 0, + backgroundColor: 'rgba(0,0,0,0.75)', + zIndex: 1000, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + cursor: 'pointer' + }, + onClick: function () { photoModalState[1]({ open: false, photos: [], currentIndex: 0 }); } + }, React.createElement('div', { + style: { position: 'relative', display: 'flex', flexDirection: 'column', alignItems: 'center', cursor: 'default', maxWidth: '100%', maxHeight: '100%' }, + onClick: function (e) { if (e) e.stopPropagation(); } + }, + React.createElement('img', { + src: photoModalState[0].photos[photoModalState[0].currentIndex] || photoModalState[0].photos[0], + alt: '', + style: { maxWidth: '90vw', maxHeight: '85vh', objectFit: 'contain' } + }), + photoModalState[0].photos.length > 1 ? React.createElement('div', { style: { marginTop: 16, display: 'flex', alignItems: 'center', gap: 16 } }, + React.createElement(Button, { + disabled: photoModalState[0].currentIndex === 0, + onClick: function (e) { e.stopPropagation(); photoModalState[1]({ open: true, photos: photoModalState[0].photos, currentIndex: Math.max(0, photoModalState[0].currentIndex - 1) }); } + }, '上一张'), + React.createElement('span', { style: { color: '#fff' } }, (photoModalState[0].currentIndex + 1) + ' / ' + photoModalState[0].photos.length), + React.createElement(Button, { + disabled: photoModalState[0].currentIndex >= photoModalState[0].photos.length - 1, + onClick: function (e) { e.stopPropagation(); photoModalState[1]({ open: true, photos: photoModalState[0].photos, currentIndex: Math.min(photoModalState[0].photos.length - 1, photoModalState[0].currentIndex + 1) }); } + }, '下一张') + ) : null + )) : null + ); + } + + // —————— 新增页 —————— + function AddView() { + var form = addFormState[0]; + var setForm = addFormState[1]; + var step = addStepState[0]; + var addBreadcrumbItems = [ + { title: '运维管理' }, + { title: '车辆业务' }, + { title: '备车管理' }, + { title: '新增备车' } + ]; + return React.createElement('div', { style: { padding: 24, background: '#f5f5f5', minHeight: '100vh' } }, + React.createElement(Breadcrumb, { items: addBreadcrumbItems, style: { marginBottom: 16 } }), + React.createElement(Card, null, + React.createElement(Steps, { current: step - 1, style: { marginBottom: 24 }, items: [ + { title: '步骤1 车辆记录' }, + { title: '步骤2 备车检查' } + ] }), + step === 1 ? React.createElement(Row, { gutter: [16, 16] }, + React.createElement(Col, { span: 24 }, React.createElement('span', { style: { color: '#666', marginRight: 8 } }, '车牌号 *'), React.createElement(Select, { placeholder: '请选择车牌号(仅库存车辆)', allowClear: true, showSearch: true, optionFilterProp: 'label', value: form.plateNo || undefined, onChange: onPlateSelect, style: { width: 200 }, options: plateOptions })), + React.createElement(Col, { span: 24 }, React.createElement('span', { style: { color: '#666', marginRight: 8 } }, '车辆类型'), React.createElement(Input, { value: form.vehicleType, readOnly: true, style: { width: 200, background: '#f5f5f5' } })), + React.createElement(Col, { span: 24 }, React.createElement('span', { style: { color: '#666', marginRight: 8 } }, '品牌'), React.createElement(Input, { value: form.brand, readOnly: true, style: { width: 200, background: '#f5f5f5' } })), + React.createElement(Col, { span: 24 }, React.createElement('span', { style: { color: '#666', marginRight: 8 } }, '车架号'), React.createElement(Input, { value: form.vin, readOnly: true, style: { width: 200, background: '#f5f5f5' } })), + React.createElement(Col, { span: 24 }, React.createElement('span', { style: { color: '#666', marginRight: 8 } }, '车身广告 *'), React.createElement(Radio.Group, { value: form.bodyAd, onChange: function (e) { setForm(function (p) { var n = {}; for (var k in p) n[k] = p[k]; n.bodyAd = e.target.value; return n; }); }, options: [{ value: '无', label: '无' }, { value: '有', label: '有' }] })), + form.bodyAd === '有' ? React.createElement(Col, { span: 24 }, React.createElement('span', { style: { color: '#666', marginRight: 8 } }, '广告照片(最多5张)'), React.createElement(Space, null, (form.adPhotos || []).slice(0, 5).map(function (url, i) { return React.createElement('img', { key: i, src: url, alt: '', style: { width: 60, height: 60, objectFit: 'cover', borderRadius: 4 } }); }), (form.adPhotos || []).length < 5 ? React.createElement(Button, { onClick: function () { setForm(function (p) { var n = {}; for (var k in p) n[k] = p[k]; n.adPhotos = (p.adPhotos || []).concat(['https://picsum.photos/200/150?r=' + Date.now()]); return n; }); } }, '上传') : null)) : null, + React.createElement(Col, { span: 24 }, React.createElement('span', { style: { color: '#666', marginRight: 8 } }, '尾板 *'), React.createElement(Radio.Group, { value: form.tailboard, onChange: function (e) { setForm(function (p) { var n = {}; for (var k in p) n[k] = p[k]; n.tailboard = e.target.value; return n; }); }, options: [{ value: '无', label: '无' }, { value: '有', label: '有' }] })), + React.createElement(Col, { span: 24 }, React.createElement('span', { style: { color: '#666', marginRight: 8 } }, '挂车牌号(有挂时填写)'), React.createElement(Input, { value: form.trailerPlate, onChange: function (e) { setForm(function (p) { var n = {}; for (var k in p) n[k] = p[k]; n.trailerPlate = e.target.value; return n; }); }, placeholder: '不填视为无挂', style: { width: 200 } })), + React.createElement(Col, { span: 24 }, React.createElement('span', { style: { color: '#666', marginRight: 8 } }, '瑕疵照片(最多5张)'), React.createElement(Space, null, (form.flawPhotos || []).slice(0, 5).map(function (url, i) { return React.createElement('img', { key: i, src: url, alt: '', style: { width: 60, height: 60, objectFit: 'cover', borderRadius: 4 } }); }), (form.flawPhotos || []).length < 5 ? React.createElement(Button, { onClick: function () { setForm(function (p) { var n = {}; for (var k in p) n[k] = p[k]; n.flawPhotos = (p.flawPhotos || []).concat(['https://picsum.photos/200/150?f=' + Date.now()]); return n; }); } }, '上传') : null)), + React.createElement(Col, { span: 24 }, React.createElement(Space, null, React.createElement(Button, { type: 'primary', onClick: handleAddNext }, '下一步'), React.createElement(Button, { onClick: handleAddSave }, '保存'), React.createElement(Button, { onClick: function () { setCurrentView('list'); } }, '取消'))) + ) : React.createElement('div', null, + React.createElement('div', { style: { marginBottom: 16, fontWeight: 600 } }, '备车检查'), + React.createElement(Table, { + dataSource: form.inspectionList || [], + rowKey: function (_, i) { return i; }, + pagination: false, + columns: [ + { title: '检查类型', dataIndex: 'checkType', width: 100 }, + { title: '检查项', dataIndex: 'checkItem', width: 120 }, + { title: '检查项结果', dataIndex: 'result', width: 120, render: function (val, record, i) { + return React.createElement(Select, { value: val, onChange: function (v) { setForm(function (p) { var n = {}; for (var k in p) n[k] = p[k]; var list = (p.inspectionList || []).slice(); list[i] = Object.assign({}, list[i], { result: v }); n.inspectionList = list; return n; }); }, style: { width: '100%' }, options: [{ value: '正常', label: '正常' }, { value: '异常', label: '异常' }] }); + }}, + { title: '胎纹深度(轮胎时)', key: 'treadDepth', width: 140, render: function (_, record, i) { + if (record.checkItem && record.checkItem.indexOf('胎') !== -1) { + return React.createElement(Input, { value: record.treadDepth, onChange: function (e) { setForm(function (p) { var n = {}; for (var k in p) n[k] = p[k]; var list = (p.inspectionList || []).slice(); list[i] = Object.assign({}, list[i], { treadDepth: e.target.value }); n.inspectionList = list; return n; }); }, placeholder: '胎纹深度' }); + } + return '-'; + }}, + { title: '备注', dataIndex: 'remark', render: function (val, record, i) { + return React.createElement(Input, { value: val, onChange: function (e) { setForm(function (p) { var n = {}; for (var k in p) n[k] = p[k]; var list = (p.inspectionList || []).slice(); list[i] = Object.assign({}, list[i], { remark: e.target.value }); n.inspectionList = list; return n; }); }, placeholder: '备注' }); + }} + ] + }), + React.createElement('div', { style: { marginTop: 16 } }, React.createElement(Space, null, React.createElement(Button, { type: 'primary', onClick: handleAddSubmit }, '提交'), React.createElement(Button, { onClick: handleAddSave }, '保存'), React.createElement(Button, { onClick: function () { setCurrentView('list'); } }, '取消'))) + ) + ) + ); + } + + return ListView(); +}; + +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..9aa6f3c --- /dev/null +++ b/web端/运维管理/车辆业务/新增后装设备.jsx @@ -0,0 +1,143 @@ +// 【重要】必须使用 const Component 作为组件变量名 +// 新增后装设备 - 运维管理-车辆业务 +var ARCO_TOKEN = { + primary: '#165DFF', + primaryHover: '#4080FF', + neutral1: '#FFFFFF', + neutral4: '#E5E6EB', + neutral6: '#86909C', + neutral8: '#1D2129', + border: '#E5E6EB', + fill: '#F2F3F5', + shadowLight: '0 1px 2px rgba(0,0,0,0.05)', + radiusLarge: '8px', + spacing16: '16px', + spacing24: '24px', + fontSize14: '14px', + fontSize16: '16px', + fontFamily: '-apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", Arial, sans-serif', + link: '#165DFF' +}; + +var DEVICE_TYPES = ['GPS', '尾板', '车身广告', 'G7安全套件', 'G7普通设备', 'G7温控设备', '备胎']; + +var MOCK_VEHICLE_OPTIONS = ['粤A12345', '粤A67890', '粤B11111', '粤B22222', '粤C33333', '京A88888']; + +const Component = function () { + var antd = window.antd; + var Select = antd.Select; + var Button = antd.Button; + var Input = antd.Input; + var message = antd.message; + var Option = Select.Option; + + var _useState = React.useState(''); + var deviceType = _useState[0]; + var setDeviceType = _useState[1]; + + var _useState2 = React.useState(''); + var vehicle = _useState2[0]; + var setVehicle = _useState2[1]; + + var _useState3 = React.useState(''); + var supplier = _useState3[0]; + var setSupplier = _useState3[1]; + + var handleSubmit = function () { + message.success('新增成功'); + setDeviceType(''); + setVehicle(''); + setSupplier(''); + }; + + var t = ARCO_TOKEN; + var styles = { + page: { padding: t.spacing24, fontFamily: t.fontFamily, backgroundColor: t.fill, minHeight: '100vh' }, + breadcrumb: { marginBottom: t.spacing16, fontSize: t.fontSize14, color: t.neutral6, display: 'flex', alignItems: 'center' }, + breadcrumbLink: { color: t.link, textDecoration: 'none', marginRight: '8px' }, + breadcrumbCurrent: { color: t.neutral8 }, + card: { backgroundColor: t.neutral1, borderRadius: t.radiusLarge, boxShadow: t.shadowLight, marginBottom: t.spacing16, padding: t.spacing24, maxWidth: 560 }, + label: { display: 'block', marginBottom: 8, fontSize: t.fontSize14, color: t.neutral8 }, + row: { marginBottom: t.spacing16 }, + actions: { marginTop: t.spacing24, display: 'flex', gap: 12 } + }; + + var deviceTypeOptions = DEVICE_TYPES.map(function (type) { + return React.createElement(Option, { key: type, value: type }, type); + }); + + var vehicleOptions = MOCK_VEHICLE_OPTIONS.map(function (v) { + return React.createElement(Option, { key: v, value: v }, v); + }); + + return React.createElement( + 'div', + { style: styles.page }, + React.createElement( + 'div', + { style: styles.breadcrumb }, + React.createElement('a', { href: '#', style: styles.breadcrumbLink, onClick: function (e) { e.preventDefault(); } }, '运维管理'), + React.createElement('span', { style: { marginRight: '8px' } }, '/'), + React.createElement('a', { href: '#', style: styles.breadcrumbLink, onClick: function (e) { e.preventDefault(); } }, '车辆业务'), + React.createElement('span', { style: { marginRight: '8px' } }, '/'), + React.createElement('span', { style: styles.breadcrumbCurrent }, '新增后装设备') + ), + React.createElement( + 'div', + { style: styles.card }, + React.createElement('div', { style: { fontSize: t.fontSize16, fontWeight: 600, marginBottom: t.spacing24, color: t.neutral8 } }, '新增后装设备'), + React.createElement('div', { style: styles.row }, + React.createElement('label', { style: styles.label }, '设备类型'), + React.createElement(Select, { + placeholder: '请选择设备类型', + style: { width: '100%' }, + value: deviceType || undefined, + onChange: function (v) { setDeviceType(v || ''); }, + allowClear: true + }, deviceTypeOptions) + ), + React.createElement('div', { style: styles.row }, + React.createElement('label', { style: styles.label }, '使用车辆'), + React.createElement(Select, { + placeholder: '请选择或输入搜索', + style: { width: '100%' }, + value: vehicle || undefined, + onChange: function (v) { setVehicle(v || ''); }, + showSearch: true, + allowClear: true, + filterOption: function (input, opt) { + var c = opt && opt.children; + return c && String(c).toLowerCase().indexOf((input || '').toLowerCase()) >= 0; + } + }, vehicleOptions) + ), + React.createElement('div', { style: styles.row }, + React.createElement('label', { style: styles.label }, '供应商'), + React.createElement(Input, { + placeholder: '请输入供应商', + value: supplier, + onChange: function (e) { setSupplier(e.target.value); } + }) + ), + React.createElement('div', { style: styles.actions }, + React.createElement(Button, { type: 'primary', onClick: handleSubmit }, '提交'), + React.createElement(Button, { onClick: function () { setDeviceType(''); setVehicle(''); setSupplier(''); } }, '重置') + ) + ) + ); +}; +if (typeof window !== 'undefined') { + window.Component = Component; + function mount() { + var rootEl = document.getElementById('root'); + if (rootEl && window.ReactDOM && window.React) { + var root = ReactDOM.createRoot(rootEl); + root.render(React.createElement(Component)); + } + } + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', mount); + } else { + setTimeout(mount, 0); + } +} diff --git a/web端/运维管理/车辆业务/新增备车.jsx b/web端/运维管理/车辆业务/新增备车.jsx new file mode 100644 index 0000000..5f12c7e --- /dev/null +++ b/web端/运维管理/车辆业务/新增备车.jsx @@ -0,0 +1,472 @@ +// 【重要】必须使用 const Component 作为组件变量名 +// 车辆业务 - 新增备车(ONEOS运管平台,布局参照新增租赁合同) + +const Component = function () { + var useState = React.useState; + var useCallback = React.useCallback; + var useMemo = React.useMemo; + var useRef = React.useRef; + + var antd = window.antd; + var Breadcrumb = antd.Breadcrumb; + var Card = antd.Card; + var Select = antd.Select; + var Input = antd.Input; + var Button = antd.Button; + var Switch = antd.Switch; + var Drawer = antd.Drawer; + var Table = antd.Table; + var message = antd.message; + var Modal = antd.Modal; + var Space = antd.Space; + + // 车牌管理 mock:车牌号及对应车辆信息、后装设备(车身广告、尾板) + var plateManageList = [ + { plateNo: '京A12345', vehicleType: '厢式货车', brand: '东风', model: 'DFH1180', vin: 'LGHXCAE28M1234567', bodyAd: true, tailboard: true }, + { plateNo: '京C11111', vehicleType: '平板货车', brand: '福田', model: 'BJ1180', vin: 'LGHXCAE28M7654321', bodyAd: false, tailboard: false }, + { plateNo: '京D22222', vehicleType: '厢式货车', brand: '江淮', model: 'HFC1180', vin: 'LGHXCAE28M8888888', bodyAd: true, tailboard: false }, + { plateNo: '京E33333', vehicleType: '栏板货车', brand: '重汽', model: 'ZZ1180', vin: 'LGHXCAE28M7777777', bodyAd: false, tailboard: true }, + { plateNo: '京F10001', vehicleType: '厢式货车', brand: '江淮', model: 'HFC1180', vin: 'LGHXCAE28M9990001', bodyAd: true, tailboard: true } + ]; + + // 备车检查单:类别 -> 检查项目列表 + var inspectionCategoryItems = { + '车灯': ['大灯', '转向灯', '小灯', '示廓灯', '刹车灯', '倒车灯', '牌照灯', '防雾灯', '室内灯'], + '仪表盘': ['氢系统指示', '电控系统指示', '数值清晰准确', '故障报警灯'], + '驾驶室': ['点烟器', '车窗升降', '按键开关', '雨刮器', '内后视镜是否正常', '内/外门把手', '安全带', '空调冷暖风', '仪表盘', '门锁功能', '手刹', '车钥匙功能是否正常', '喇叭', '音响功能', '遮阳板', '主副驾座椅', '方向盘', '内饰干净整洁'], + '轮胎': ['前左胎', '前右胎', '后左胎', '后右胎'], + '液位检查': ['冷却液', '制动液', '玻璃水'], + '外观检查': ['车身外观', '漆面', '玻璃'], + '车辆外观': ['整车外观'], + '其他': ['其他检查项'], + '随车工具': ['三角牌', '灭火器', '反光背心'], + '随车证件': ['行驶证', '营运证', '保险单'], + '整车': ['整车状态'], + '燃料电池系统': ['氢系统', '储氢瓶'], + '冷机': ['冷机运行'], + '制动系统': ['制动踏板', '驻车制动'] + }; + + function buildInspectionList() { + var list = []; + var categories = Object.keys(inspectionCategoryItems); + for (var i = 0; i < categories.length; i++) { + var cat = categories[i]; + var items = inspectionCategoryItems[cat]; + for (var j = 0; j < items.length; j++) { + list.push({ category: cat, checkItem: items[j], checked: true, remark: '' }); + } + } + return list; + } + + var formState = useState({ + plateNo: '', + vehicleType: '', + brand: '', + model: '', + vin: '', + bodyAd: true, + adPhotos: [], + tailboard: false, + trailerPlate: '', + flawPhotos: [], + inspectionList: buildInspectionList() + }); + var form = formState[0]; + var setForm = formState[1]; + + var drawerOpenState = useState(false); + var photoPreviewState = useState({ open: false, url: null }); + var reqDocOpenState = useState(false); + var adPhotoInputRef = useRef(null); + var flawPhotoInputRef = useRef(null); + var plateOptions = useMemo(function () { + return plateManageList.map(function (p) { return { value: p.plateNo, label: p.plateNo }; }); + }, []); + + var onPlateSelect = useCallback(function (plateNo) { + var vehicle = plateManageList.find(function (p) { return p.plateNo === plateNo; }); + if (!vehicle) return; + setForm(function (prev) { + var next = {}; + for (var k in prev) next[k] = prev[k]; + next.plateNo = vehicle.plateNo; + next.vehicleType = vehicle.vehicleType || ''; + next.brand = vehicle.brand || ''; + next.model = vehicle.model || ''; + next.vin = vehicle.vin || ''; + next.bodyAd = !!vehicle.bodyAd; + next.tailboard = !!vehicle.tailboard; + return next; + }); + }, []); + + var updateInspection = useCallback(function (index, field, value) { + setForm(function (prev) { + var n = {}; + for (var k in prev) n[k] = prev[k]; + var list = (prev.inspectionList || []).slice(); + var row = list[index] || {}; + list[index] = Object.assign({}, row, { [field]: value }); + n.inspectionList = list; + return n; + }); + }, []); + + var addAdPhoto = useCallback(function () { + if ((form.adPhotos || []).length >= 4) { message.warning('最多上传4张'); return; } + if (adPhotoInputRef.current) adPhotoInputRef.current.click(); + }, [form.adPhotos]); + var onAdPhotoFileChange = useCallback(function (e) { + var files = e.target.files; + if (!files || files.length === 0) return; + setForm(function (p) { + var current = p.adPhotos || []; + var remain = 4 - current.length; + if (remain <= 0) return p; + var urls = []; + for (var i = 0; i < files.length && urls.length < remain; i++) { + if (files[i].type.indexOf('image') !== -1) urls.push(URL.createObjectURL(files[i])); + } + if (urls.length === 0) return p; + var n = {}; for (var k in p) n[k] = p[k]; n.adPhotos = current.concat(urls); return n; + }); + e.target.value = ''; + }, []); + var removeAdPhoto = useCallback(function (idx) { + setForm(function (p) { + var n = {}; + for (var k in p) n[k] = p[k]; + var arr = (p.adPhotos || []).slice(); + var url = arr[idx]; + if (url && url.indexOf('blob:') === 0) URL.revokeObjectURL(url); + arr.splice(idx, 1); + n.adPhotos = arr; + return n; + }); + }, []); + var addFlawPhoto = useCallback(function () { + if ((form.flawPhotos || []).length >= 4) { message.warning('最多上传4张'); return; } + if (flawPhotoInputRef.current) flawPhotoInputRef.current.click(); + }, [form.flawPhotos]); + var onFlawPhotoFileChange = useCallback(function (e) { + var files = e.target.files; + if (!files || files.length === 0) return; + setForm(function (p) { + var current = p.flawPhotos || []; + var remain = 4 - current.length; + if (remain <= 0) return p; + var urls = []; + for (var i = 0; i < files.length && urls.length < remain; i++) { + if (files[i].type.indexOf('image') !== -1) urls.push(URL.createObjectURL(files[i])); + } + if (urls.length === 0) return p; + var n = {}; for (var k in p) n[k] = p[k]; n.flawPhotos = current.concat(urls); return n; + }); + e.target.value = ''; + }, []); + var removeFlawPhoto = useCallback(function (idx) { + setForm(function (p) { + var n = {}; + for (var k in p) n[k] = p[k]; + var arr = (p.flawPhotos || []).slice(); + var url = arr[idx]; + if (url && url.indexOf('blob:') === 0) URL.revokeObjectURL(url); + arr.splice(idx, 1); + n.flawPhotos = arr; + return n; + }); + }, []); + + var handleSubmit = useCallback(function () { + if (!form.plateNo) { message.warning('请选择车牌号'); return; } + message.success('备车成功'); + // 保存数据并跳转至备车管理-已完成(实际项目接路由) + if (typeof window !== 'undefined' && window.history) window.history.back(); + }, [form.plateNo]); + var handleSave = useCallback(function () { + if (!form.plateNo) { message.warning('请选择车牌号'); return; } + message.success('暂存成功'); + // 保存数据并跳转至备车管理-待提交 + if (typeof window !== 'undefined' && window.history) window.history.back(); + }, [form.plateNo]); + + // 布局样式(参照新增租赁合同) + var styles = { + page: { padding: '16px 24px 80px', backgroundColor: '#f5f5f5', minHeight: '100vh', fontSize: 14 }, + breadcrumb: { marginBottom: 16, color: '#666' }, + breadcrumbSep: { margin: '0 8px', color: '#999' }, + card: { backgroundColor: '#fff', borderRadius: 8, marginBottom: 16, boxShadow: '0 1px 2px rgba(0,0,0,0.05)', overflow: 'hidden' }, + cardBody: { padding: '20px 24px' }, + formRow: { display: 'flex', flexWrap: 'wrap', marginBottom: 16 }, + formCol: { flex: '0 0 33.33%', minWidth: 200, paddingRight: 16, marginBottom: 8, boxSizing: 'border-box' }, + formColFull: { flex: '0 0 100%', marginBottom: 8, boxSizing: 'border-box' }, + label: { display: 'block', marginBottom: 6, color: '#333' }, + labelRequired: { color: '#ff4d4f', marginRight: 4 }, + footer: { position: 'fixed', bottom: 0, left: 0, right: 0, padding: '12px 24px', backgroundColor: '#fff', borderTop: '1px solid #e8e8e8', display: 'flex', gap: 12, zIndex: 99 } + }; + + var FormItem = function (props) { + var colStyle = props.fullWidth ? styles.formColFull : styles.formCol; + return React.createElement('div', { style: colStyle }, + React.createElement('label', { style: styles.label }, + props.required ? React.createElement('span', { style: styles.labelRequired }, '*') : null, + props.label + ), + props.children + ); + }; + + var inspectionList = form.inspectionList || []; + var categoryRowSpans = useMemo(function () { + var list = inspectionList; + var spans = []; + var i = 0; + while (i < list.length) { + var cat = list[i].category; + var start = i; + while (i < list.length && list[i].category === cat) i++; + spans[start] = i - start; + for (var j = start + 1; j < i; j++) spans[j] = 0; + } + return spans; + }, [form.inspectionList]); + + var inspectionColumns = [ + { + title: '类别', + dataIndex: 'category', + key: 'category', + width: 120, + onCell: function (_, index) { return { rowSpan: categoryRowSpans[index] !== undefined ? categoryRowSpans[index] : 1 }; } + }, + { title: '检查项目', dataIndex: 'checkItem', key: 'checkItem', width: 180 }, + { + title: '检查情况', + key: 'checked', + width: 100, + render: function (_, record, index) { + return React.createElement(Switch, { + checked: record.checked !== false, + onChange: function (checked) { updateInspection(index, 'checked', checked); } + }); + } + }, + { + title: '备注', + dataIndex: 'remark', + key: 'remark', + render: function (val, record, index) { + return React.createElement(Input, { + value: val, + onChange: function (e) { updateInspection(index, 'remark', e.target.value); }, + placeholder: '备注' + }); + } + } + ]; + + // 方形拍照图标(广告照片与瑕疵共用) + var cameraIconBox = function (onClick) { + return React.createElement('div', { + role: 'button', + tabIndex: 0, + onClick: onClick, + style: { width: 64, height: 64, border: '1px dashed #d9d9d9', borderRadius: 4, display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', background: '#fafafa', boxSizing: 'border-box' } + }, React.createElement('svg', { width: 28, height: 28, viewBox: '0 0 24 24', fill: 'none', stroke: '#999', strokeWidth: 2 }, + React.createElement('path', { d: 'M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z' }), + React.createElement('circle', { cx: 12, cy: 13, r: 4 }) + )); + }; + + var requirementDocContent = '新增备车\n\n1.面包屑:\n1.1.运维管理-车辆业务-备车管理-新增备车\n\n2.表单:\n2.1.车牌号:必选项,选择器,支持从输入框内输入内容进行模糊搜索,默认拉取「车牌管理」中所有「车牌号」;\n2.2.车辆类型:输入框禁用,选择车牌号后自动拉取该车牌号对应「车辆类型」;\n2.3.品牌:输入框禁用,选择车牌号后自动拉取该车牌号对应「品牌」;\n2.4.型号:输入框禁用,选择车牌号后自动拉取该车牌号对应「型号」\n2.5.车辆识别代码:输入框禁用,选择车牌号后自动拉取该车牌号「车辆识别代码」;\n2.6.车身广告:开关,选择车辆后拉取该车辆「后装设备」「车身广告」,如果该车辆有「车身广告」则勾选为开,如果无则勾选为无。同时如果手动进行操作,会同步到「后装设备」「车身广告」中,安装时间以该条备车记录提交成功为准;\n2.7.广告照片:图片上传,最多支持上传4张图片,支持主流照片格式。上传后,上传按钮后方横排显示已上传图片缩略图,支持点击预览和删除;\n2.8.尾板:开关,选择车辆后拉取该车辆「后装设备」「尾板」,如果该车辆有「尾板」则勾选为开,如果无则勾选为无。同时如果手动进行操作,会同步到「后装设备」「尾板」中,安装时间以该条备车记录提交成功为准;\n2.9.是否有挂:输入框,提示信息为:请输入挂车牌号,不输入为无挂;\n2.10.瑕疵:图片上传,最多支持上传4张图片,支持主流照片格式。上传后,上传按钮后方横排显示已上传图片缩略图,支持点击预览和删除;\n2.11.车辆检查:按钮,文字为备车检查单,点击右侧展开抽屉,抽屉内显示列表,字段为类别、检查项目、选择、备注;\n 2.11.1.类别:分为车灯、仪表盘、驾驶室、轮胎、液位检查、外观检查、车辆外观、其他、随车工具、随车证件、整车、燃料电池系统、冷机、制动系统;\n 2.11.2.检查项目:车灯类别对应(大灯、转向灯、小灯、示廓灯、刹车灯、倒车灯、牌照灯、防雾灯、室内灯)、仪表盘对应(氢系统指示、电控系统指示、数值清晰准确、故障报警灯)、驾驶室对应(点烟器、车窗升降、按键开关、雨刮器、内后视镜是否正常、内/外门把手、安全带、空调冷暖风、仪表盘、门锁功能、手刹、车钥匙功能是否正常、喇叭、音响功能、遮阳板、主副驾座椅、方向盘、内饰干净整洁)\n 2.11.3.检查情况:开关,在检查项目每项后方显示,默认为开,可手动进行关闭;\n 2.11.4.备注:输入框;\n2.12.下方为提交和保存;\n 2.12.1.提交:点击提交,toast提示:备车成功,同时保存该条数据并跳转至「备车管理」「已完成」;\n 2.12.2.保存:点击保存,toast提示:暂存成功,同时保存该条数据并跳转至「备车管理」「待提交」;'; + + var breadcrumbNodes = [ + React.createElement('span', { key: '1' }, '运维管理'), + React.createElement('span', { key: '2', style: styles.breadcrumbSep }, ' / '), + React.createElement('span', { key: '3' }, '车辆业务'), + React.createElement('span', { key: '4', style: styles.breadcrumbSep }, ' / '), + React.createElement('span', { key: '5' }, '备车管理'), + React.createElement('span', { key: '6', style: styles.breadcrumbSep }, ' / '), + React.createElement('span', { key: '7', style: { color: '#1890ff' } }, '新增备车') + ]; + + return React.createElement('div', { style: styles.page }, + React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 } }, + React.createElement('div', { style: styles.breadcrumb }, breadcrumbNodes), + React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { reqDocOpenState[1](true); } }, '查看需求说明') + ), + React.createElement('div', { style: styles.card }, + React.createElement('div', { style: styles.cardBody }, + React.createElement('div', { style: styles.formRow }, + React.createElement(FormItem, { label: '车牌号', required: true }, + React.createElement(Select, { + placeholder: '请选择车牌号', + style: { width: '100%' }, + value: form.plateNo || undefined, + onChange: onPlateSelect, + showSearch: true, + allowClear: true, + optionFilterProp: 'label', + filterOption: function (input, opt) { + return (opt && opt.label && String(opt.label).toLowerCase().indexOf((input || '').toLowerCase()) >= 0); + }, + options: plateOptions + }) + ), + React.createElement(FormItem, { label: '车辆类型' }, + React.createElement(Input, { value: form.vehicleType, disabled: true, style: { width: '100%', background: '#f5f5f5' } }) + ), + React.createElement(FormItem, { label: '品牌' }, + React.createElement(Input, { value: form.brand, disabled: true, style: { width: '100%', background: '#f5f5f5' } }) + ), + React.createElement(FormItem, { label: '型号' }, + React.createElement(Input, { value: form.model, disabled: true, style: { width: '100%', background: '#f5f5f5' } }) + ), + React.createElement(FormItem, { label: '车辆识别代码' }, + React.createElement(Input, { value: form.vin, disabled: true, style: { width: '100%', background: '#f5f5f5' } }) + ), + React.createElement('div', { style: styles.formCol, key: 'ph0' }), + React.createElement(FormItem, { label: '车身广告' }, + React.createElement(Switch, { + checked: form.bodyAd, + checkedChildren: '开', + unCheckedChildren: '关', + onChange: function (checked) { + setForm(function (p) { var n = {}; for (var k in p) n[k] = p[k]; n.bodyAd = checked; return n; }); + } + }) + ), + React.createElement('div', { style: styles.formCol, key: 'ph1' }), + React.createElement('div', { style: styles.formCol, key: 'ph2' }), + React.createElement(FormItem, { label: '尾板' }, + React.createElement(Switch, { + checked: form.tailboard, + checkedChildren: '开', + unCheckedChildren: '关', + onChange: function (checked) { + setForm(function (p) { var n = {}; for (var k in p) n[k] = p[k]; n.tailboard = checked; return n; }); + } + }) + ), + React.createElement(FormItem, { label: '广告照片', fullWidth: true }, + React.createElement('div', { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 8 } }, + React.createElement('input', { type: 'file', ref: adPhotoInputRef, accept: 'image/*', multiple: true, style: { display: 'none' }, onChange: onAdPhotoFileChange }), + (form.adPhotos || []).length < 4 ? cameraIconBox(addAdPhoto) : null, + (form.adPhotos || []).map(function (url, i) { + return React.createElement('span', { key: i, style: { position: 'relative', display: 'inline-block' } }, + React.createElement('img', { + src: url, + alt: '', + style: { width: 64, height: 64, objectFit: 'cover', borderRadius: 4, cursor: 'pointer' }, + onClick: function () { photoPreviewState[1]({ open: true, url: url }); } + }), + React.createElement('span', { + style: { position: 'absolute', top: -6, right: -6, width: 20, height: 20, borderRadius: '50%', background: '#ff4d4f', color: '#fff', fontSize: 12, display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer' }, + onClick: function () { removeAdPhoto(i); } + }, '×') + ); + }) + ) + ), + React.createElement(FormItem, { label: '是否有挂' }, + React.createElement(Input, { + placeholder: '请输入挂车牌号,不输入为无挂', + value: form.trailerPlate, + onChange: function (e) { + setForm(function (p) { var n = {}; for (var k in p) n[k] = p[k]; n.trailerPlate = e.target.value; return n; }); + }, + style: { width: '100%', maxWidth: 320 } + }) + ), + React.createElement(FormItem, { label: '瑕疵', fullWidth: true }, + React.createElement('div', { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 8 } }, + React.createElement('input', { type: 'file', ref: flawPhotoInputRef, accept: 'image/*', multiple: true, style: { display: 'none' }, onChange: onFlawPhotoFileChange }), + (form.flawPhotos || []).length < 4 ? cameraIconBox(addFlawPhoto) : null, + (form.flawPhotos || []).map(function (url, i) { + return React.createElement('span', { key: i, style: { position: 'relative', display: 'inline-block' } }, + React.createElement('img', { + src: url, + alt: '', + style: { width: 64, height: 64, objectFit: 'cover', borderRadius: 4, cursor: 'pointer' }, + onClick: function () { photoPreviewState[1]({ open: true, url: url }); } + }), + React.createElement('span', { + style: { position: 'absolute', top: -6, right: -6, width: 20, height: 20, borderRadius: '50%', background: '#ff4d4f', color: '#fff', fontSize: 12, display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer' }, + onClick: function () { removeFlawPhoto(i); } + }, '×') + ); + }) + ) + ), + React.createElement(FormItem, { label: '车辆检查', fullWidth: true }, + React.createElement(Button, { type: 'default', onClick: function () { drawerOpenState[1](true); } }, '备车检查单') + ) + ) + ) + ), + React.createElement('div', { style: styles.footer }, + React.createElement(Button, { type: 'primary', onClick: handleSubmit }, '提交'), + React.createElement(Button, { onClick: handleSave }, '保存'), + React.createElement(Button, { onClick: function () { if (typeof window !== 'undefined' && window.history) window.history.back(); } }, '取消') + ), + React.createElement(Drawer, { + title: '备车检查单', + placement: 'right', + width: 640, + open: drawerOpenState[0], + onClose: function () { drawerOpenState[1](false); }, + styles: { body: { display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden', paddingBottom: 0 } }, + footer: React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: 8 } }, + React.createElement(Button, { onClick: function () { drawerOpenState[1](false); } }, '返回'), + React.createElement(Button, { type: 'primary', onClick: function () { drawerOpenState[1](false); message.success('检查单已保存'); } }, '提交') + ) + }, + React.createElement('div', { style: { flex: 1, minHeight: 0, overflow: 'auto' } }, + React.createElement(Table, { + columns: inspectionColumns, + dataSource: form.inspectionList || [], + rowKey: function (_, i) { return i; }, + pagination: false, + size: 'small' + }) + ) + ), + React.createElement(Modal, { + title: '需求说明', + open: reqDocOpenState[0], + onCancel: function () { reqDocOpenState[1](false); }, + footer: null, + width: 720, + styles: { body: { maxHeight: '70vh', overflow: 'auto', whiteSpace: 'pre-wrap', fontSize: 14, lineHeight: 1.8 } } + }, requirementDocContent), + photoPreviewState[0].open && photoPreviewState[0].url + ? React.createElement(Modal, { + title: '预览', + open: true, + footer: null, + onCancel: function () { photoPreviewState[1]({ open: false, url: null }); } + }, React.createElement('img', { src: photoPreviewState[0].url, alt: '', style: { width: '100%' } })) + : null + ); +}; + +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..815b0e1 --- /dev/null +++ b/web端/运维管理/车辆业务/查看备车.jsx @@ -0,0 +1,275 @@ +// 【重要】必须使用 const Component 作为组件变量名 +// 车辆业务 - 查看备车(与新增备车相同布局,全部只读) + +const Component = function () { + var useState = React.useState; + var useMemo = React.useMemo; + + var antd = window.antd; + var Breadcrumb = antd.Breadcrumb; + var Card = antd.Card; + var Input = antd.Input; + var Button = antd.Button; + var Drawer = antd.Drawer; + var Table = antd.Table; + var Modal = antd.Modal; + + var plateManageList = [ + { plateNo: '京A12345', vehicleType: '厢式货车', brand: '东风', model: 'DFH1180', vin: 'LGHXCAE28M1234567', bodyAd: true, tailboard: true }, + { plateNo: '京C11111', vehicleType: '平板货车', brand: '福田', model: 'BJ1180', vin: 'LGHXCAE28M7654321', bodyAd: false, tailboard: false }, + { plateNo: '京D22222', vehicleType: '厢式货车', brand: '江淮', model: 'HFC1180', vin: 'LGHXCAE28M8888888', bodyAd: true, tailboard: false }, + { plateNo: '京E33333', vehicleType: '栏板货车', brand: '重汽', model: 'ZZ1180', vin: 'LGHXCAE28M7777777', bodyAd: false, tailboard: true }, + { plateNo: '京F10001', vehicleType: '厢式货车', brand: '江淮', model: 'HFC1180', vin: 'LGHXCAE28M9990001', bodyAd: true, tailboard: true } + ]; + + var inspectionCategoryItems = { + '车灯': ['大灯', '转向灯', '小灯', '示廓灯', '刹车灯', '倒车灯', '牌照灯', '防雾灯', '室内灯'], + '仪表盘': ['氢系统指示', '电控系统指示', '数值清晰准确', '故障报警灯'], + '驾驶室': ['点烟器', '车窗升降', '按键开关', '雨刮器', '内后视镜是否正常', '内/外门把手', '安全带', '空调冷暖风', '仪表盘', '门锁功能', '手刹', '车钥匙功能是否正常', '喇叭', '音响功能', '遮阳板', '主副驾座椅', '方向盘', '内饰干净整洁'], + '轮胎': ['前左胎', '前右胎', '后左胎', '后右胎'], + '液位检查': ['冷却液', '制动液', '玻璃水'], + '外观检查': ['车身外观', '漆面', '玻璃'], + '车辆外观': ['整车外观'], + '其他': ['其他检查项'], + '随车工具': ['三角牌', '灭火器', '反光背心'], + '随车证件': ['行驶证', '营运证', '保险单'], + '整车': ['整车状态'], + '燃料电池系统': ['氢系统', '储氢瓶'], + '冷机': ['冷机运行'], + '制动系统': ['制动踏板', '驻车制动'] + }; + + function buildInspectionList() { + var list = []; + var categories = Object.keys(inspectionCategoryItems); + for (var i = 0; i < categories.length; i++) { + var cat = categories[i]; + var items = inspectionCategoryItems[cat]; + for (var j = 0; j < items.length; j++) { + list.push({ category: cat, checkItem: items[j], checked: true, remark: '' }); + } + } + return list; + } + + // 只读:一条已填写的备车记录(不可编辑) + var form = useMemo(function () { + return { + plateNo: '京A12345', + vehicleType: '厢式货车', + brand: '东风', + model: 'DFH1180', + vin: 'LGHXCAE28M1234567', + bodyAd: true, + adPhotos: ['https://picsum.photos/200/150?r=1', 'https://picsum.photos/200/150?r=2'], + tailboard: true, + trailerPlate: '京B67890', + flawPhotos: ['https://picsum.photos/200/150?f=1'], + inspectionList: buildInspectionList() + }; + }, []); + + var drawerOpenState = useState(false); + var photoPreviewState = useState({ open: false, url: null }); + var reqDocOpenState = useState(false); + + var styles = { + page: { padding: '16px 24px 80px', backgroundColor: '#f5f5f5', minHeight: '100vh', fontSize: 14 }, + breadcrumb: { marginBottom: 16, color: '#666' }, + breadcrumbSep: { margin: '0 8px', color: '#999' }, + card: { backgroundColor: '#fff', borderRadius: 8, marginBottom: 16, boxShadow: '0 1px 2px rgba(0,0,0,0.05)', overflow: 'hidden' }, + cardBody: { padding: '20px 24px' }, + formRow: { display: 'flex', flexWrap: 'wrap', marginBottom: 16 }, + formCol: { flex: '0 0 33.33%', minWidth: 200, paddingRight: 16, marginBottom: 8, boxSizing: 'border-box' }, + formColFull: { flex: '0 0 100%', marginBottom: 8, boxSizing: 'border-box' }, + label: { display: 'block', marginBottom: 6, color: '#333' }, + labelRequired: { color: '#ff4d4f', marginRight: 4 }, + footer: { position: 'fixed', bottom: 0, left: 0, right: 0, padding: '12px 24px', backgroundColor: '#fff', borderTop: '1px solid #e8e8e8', display: 'flex', gap: 12, zIndex: 99 } + }; + + var FormItem = function (props) { + var colStyle = props.fullWidth ? styles.formColFull : styles.formCol; + return React.createElement('div', { style: colStyle }, + React.createElement('label', { style: styles.label }, + props.required ? React.createElement('span', { style: styles.labelRequired }, '*') : null, + props.label + ), + props.children + ); + }; + + var inspectionList = form.inspectionList || []; + var categoryRowSpans = useMemo(function () { + var list = inspectionList; + var spans = []; + var i = 0; + while (i < list.length) { + var cat = list[i].category; + var start = i; + while (i < list.length && list[i].category === cat) i++; + spans[start] = i - start; + for (var j = start + 1; j < i; j++) spans[j] = 0; + } + return spans; + }, [form.inspectionList]); + + var inspectionColumns = [ + { + title: '类别', + dataIndex: 'category', + key: 'category', + width: 120, + onCell: function (_, index) { return { rowSpan: categoryRowSpans[index] !== undefined ? categoryRowSpans[index] : 1 }; } + }, + { title: '检查项目', dataIndex: 'checkItem', key: 'checkItem', width: 180 }, + { + title: '检查情况', + key: 'checked', + width: 100, + render: function (_, record) { return record.checked !== false ? '开' : '关'; } + }, + { title: '备注', dataIndex: 'remark', key: 'remark', render: function (val) { return val || '-'; } } + ]; + + var requirementDocContent = '新增备车\n\n1.面包屑:\n1.1.运维管理-车辆业务-备车管理-新增备车\n\n2.表单:\n2.1.车牌号:必选项,选择器,支持从输入框内输入内容进行模糊搜索,默认拉取「车牌管理」中所有「车牌号」;\n2.2.车辆类型:输入框禁用,选择车牌号后自动拉取该车牌号对应「车辆类型」;\n2.3.品牌:输入框禁用,选择车牌号后自动拉取该车牌号对应「品牌」;\n2.4.型号:输入框禁用,选择车牌号后自动拉取该车牌号对应「型号」\n2.5.车辆识别代码:输入框禁用,选择车牌号后自动拉取该车牌号「车辆识别代码」;\n2.6.车身广告:开关,选择车辆后拉取该车辆「后装设备」「车身广告」,如果该车辆有「车身广告」则勾选为开,如果无则勾选为无。同时如果手动进行操作,会同步到「后装设备」「车身广告」中,安装时间以该条备车记录提交成功为准;\n2.7.广告照片:图片上传,最多支持上传4张图片,支持主流照片格式。上传后,上传按钮后方横排显示已上传图片缩略图,支持点击预览和删除;\n2.8.尾板:开关,选择车辆后拉取该车辆「后装设备」「尾板」,如果该车辆有「尾板」则勾选为开,如果无则勾选为无。同时如果手动进行操作,会同步到「后装设备」「尾板」中,安装时间以该条备车记录提交成功为准;\n2.9.是否有挂:输入框,提示信息为:请输入挂车牌号,不输入为无挂;\n2.10.瑕疵:图片上传,最多支持上传4张图片,支持主流照片格式。上传后,上传按钮后方横排显示已上传图片缩略图,支持点击预览和删除;\n2.11.车辆检查:按钮,文字为备车检查单,点击右侧展开抽屉,抽屉内显示列表,字段为类别、检查项目、选择、备注;\n 2.11.1.类别:分为车灯、仪表盘、驾驶室、轮胎、液位检查、外观检查、车辆外观、其他、随车工具、随车证件、整车、燃料电池系统、冷机、制动系统;\n 2.11.2.检查项目:车灯类别对应(大灯、转向灯、小灯、示廓灯、刹车灯、倒车灯、牌照灯、防雾灯、室内灯)、仪表盘对应(氢系统指示、电控系统指示、数值清晰准确、故障报警灯)、驾驶室对应(点烟器、车窗升降、按键开关、雨刮器、内后视镜是否正常、内/外门把手、安全带、空调冷暖风、仪表盘、门锁功能、手刹、车钥匙功能是否正常、喇叭、音响功能、遮阳板、主副驾座椅、方向盘、内饰干净整洁)\n 2.11.3.检查情况:开关,在检查项目每项后方显示,默认为开,可手动进行关闭;\n 2.11.4.备注:输入框;\n2.12.下方为提交和保存;\n 2.12.1.提交:点击提交,toast提示:备车成功,同时保存该条数据并跳转至「备车管理」「已完成」;\n 2.12.2.保存:点击保存,toast提示:暂存成功,同时保存该条数据并跳转至「备车管理」「待提交」;'; + + var breadcrumbNodes = [ + React.createElement('span', { key: '1' }, '运维管理'), + React.createElement('span', { key: '2', style: styles.breadcrumbSep }, ' / '), + React.createElement('span', { key: '3' }, '车辆业务'), + React.createElement('span', { key: '4', style: styles.breadcrumbSep }, ' / '), + React.createElement('span', { key: '5' }, '备车管理'), + React.createElement('span', { key: '6', style: styles.breadcrumbSep }, ' / '), + React.createElement('span', { key: '7', style: { color: '#1890ff' } }, '查看备车') + ]; + + return React.createElement('div', { style: styles.page }, + React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 } }, + React.createElement('div', { style: styles.breadcrumb }, breadcrumbNodes), + React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { reqDocOpenState[1](true); } }, '查看需求说明') + ), + React.createElement('div', { style: styles.card }, + React.createElement('div', { style: styles.cardBody }, + React.createElement('div', { style: styles.formRow }, + React.createElement(FormItem, { label: '车牌号', required: true }, + React.createElement(Input, { value: form.plateNo, disabled: true, style: { width: '100%', background: '#f5f5f5' } }) + ), + React.createElement(FormItem, { label: '车辆类型' }, + React.createElement(Input, { value: form.vehicleType, disabled: true, style: { width: '100%', background: '#f5f5f5' } }) + ), + React.createElement(FormItem, { label: '品牌' }, + React.createElement(Input, { value: form.brand, disabled: true, style: { width: '100%', background: '#f5f5f5' } }) + ), + React.createElement(FormItem, { label: '型号' }, + React.createElement(Input, { value: form.model, disabled: true, style: { width: '100%', background: '#f5f5f5' } }) + ), + React.createElement(FormItem, { label: '车辆识别代码' }, + React.createElement(Input, { value: form.vin, disabled: true, style: { width: '100%', background: '#f5f5f5' } }) + ), + React.createElement('div', { style: styles.formCol, key: 'ph0' }), + React.createElement(FormItem, { label: '车身广告' }, + React.createElement(Input, { value: form.bodyAd ? '开' : '关', disabled: true, style: { width: '100%', background: '#f5f5f5' } }) + ), + React.createElement('div', { style: styles.formCol, key: 'ph1' }), + React.createElement('div', { style: styles.formCol, key: 'ph2' }), + React.createElement(FormItem, { label: '尾板' }, + React.createElement(Input, { value: form.tailboard ? '开' : '关', disabled: true, style: { width: '100%', background: '#f5f5f5' } }) + ), + React.createElement(FormItem, { label: '广告照片', fullWidth: true }, + React.createElement('div', { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 8 } }, + (form.adPhotos || []).map(function (url, i) { + return React.createElement('img', { + key: i, + src: url, + alt: '', + style: { width: 64, height: 64, objectFit: 'cover', borderRadius: 4, cursor: 'pointer' }, + onClick: function () { photoPreviewState[1]({ open: true, url: url }); } + }); + }), + (form.adPhotos || []).length === 0 ? React.createElement('span', { style: { color: '#999' } }, '无') : null + ) + ), + React.createElement(FormItem, { label: '是否有挂' }, + React.createElement(Input, { value: form.trailerPlate || '无', disabled: true, style: { width: '100%', maxWidth: 320, background: '#f5f5f5' } }) + ), + React.createElement(FormItem, { label: '瑕疵', fullWidth: true }, + React.createElement('div', { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 8 } }, + (form.flawPhotos || []).map(function (url, i) { + return React.createElement('img', { + key: i, + src: url, + alt: '', + style: { width: 64, height: 64, objectFit: 'cover', borderRadius: 4, cursor: 'pointer' }, + onClick: function () { photoPreviewState[1]({ open: true, url: url }); } + }); + }), + (form.flawPhotos || []).length === 0 ? React.createElement('span', { style: { color: '#999' } }, '无') : null + ) + ), + React.createElement(FormItem, { label: '车辆检查', fullWidth: true }, + React.createElement(Button, { type: 'default', onClick: function () { drawerOpenState[1](true); } }, '备车检查单') + ) + ) + ) + ), + React.createElement('div', { style: styles.footer }, + React.createElement(Button, { onClick: function () { if (typeof window !== 'undefined' && window.history) window.history.back(); } }, '返回') + ), + React.createElement(Drawer, { + title: '备车检查单', + placement: 'right', + width: 640, + open: drawerOpenState[0], + onClose: function () { drawerOpenState[1](false); }, + styles: { body: { display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden', paddingBottom: 0 } }, + footer: React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end' } }, + React.createElement(Button, { onClick: function () { drawerOpenState[1](false); } }, '返回') + ) + }, + React.createElement('div', { style: { flex: 1, minHeight: 0, overflow: 'auto' } }, + React.createElement(Table, { + columns: inspectionColumns, + dataSource: form.inspectionList || [], + rowKey: function (_, i) { return i; }, + pagination: false, + size: 'small' + }) + ) + ), + React.createElement(Modal, { + title: '需求说明', + open: reqDocOpenState[0], + onCancel: function () { reqDocOpenState[1](false); }, + footer: null, + width: 720, + styles: { body: { maxHeight: '70vh', overflow: 'auto', whiteSpace: 'pre-wrap', fontSize: 14, lineHeight: 1.8 } } + }, requirementDocContent), + photoPreviewState[0].open && photoPreviewState[0].url + ? React.createElement(Modal, { + title: '预览', + open: true, + footer: null, + onCancel: function () { photoPreviewState[1]({ open: false, url: null }); } + }, React.createElement('img', { src: photoPreviewState[0].url, alt: '', style: { width: '100%' } })) + : null + ); +}; + +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..2293371 --- /dev/null +++ b/web端/运维管理/车辆业务/调拨管理.jsx @@ -0,0 +1,2240 @@ +// 【重要】必须使用 const Component 作为组件变量名 +// 调拨管理 - 车辆资产管理后台模块 +// 设计变量参考 Arco Design Token: https://arco.design/react/docs/token +var ARCO_TOKEN = { + primary: '#165DFF', + primaryHover: '#4080FF', + primaryActive: '#0E42D2', + danger: '#F53F3F', + success: '#00B42A', + warning: '#FF7D00', + link: '#165DFF', + neutral1: '#FFFFFF', + neutral2: '#F7F8FA', + neutral3: '#F2F3F5', + neutral4: '#E5E6EB', + neutral5: '#C9CDD4', + neutral6: '#86909C', + neutral7: '#4E5969', + neutral8: '#1D2129', + border: '#E5E6EB', + borderSecondary: '#C9CDD4', + fill: '#F2F3F5', + fillSecondary: '#F7F8FA', + shadowLight: '0 1px 2px rgba(0,0,0,0.05)', + shadowMedium: '0 2px 8px rgba(0,0,0,0.08)', + radiusSmall: '2px', + radiusMedium: '4px', + radiusLarge: '8px', + spacing4: '4px', + spacing8: '8px', + spacing12: '12px', + spacing16: '16px', + spacing24: '24px', + fontSize14: '14px', + fontSize16: '16px', + fontFamily: '"PingFang SC", "苹方-简", -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif' +}; + +var APPROVAL_STATUS = [ + { value: 'pending_submit', label: '待提交' }, + { value: 'pending_approval', label: '待审批' }, + { value: 'approving', label: '审批中' }, + { value: 'completed', label: '审批完成' }, + { value: 'rejected', label: '审批驳回' }, + { value: 'withdrawn', label: '撤回' } +]; + +var TRANSFER_METHODS = [ + { value: 'third_party', label: '第三方运输' }, + { value: 'driver', label: '司机驾驶' } +]; + +var provinceList = [ + { code: 'gd', name: '广东省' }, + { code: 'zj', name: '浙江省' }, + { code: 'bj', name: '北京市' } +]; +var cityMap = { + gd: [{ code: 'gz', name: '广州市' }, { code: 'sz', name: '深圳市' }], + zj: [{ code: 'hz', name: '杭州市' }, { code: 'nb', name: '宁波市' }], + bj: [{ code: 'bj', name: '北京市' }] +}; + +// 运维部用户(操作人、创建人来源) +var mockOpsUsers = [ + { id: 'u1', name: '张明', dept: '运维部' }, + { id: 'u2', name: '王芳', dept: '运维部' }, + { id: 'u3', name: '李华', dept: '运维部' }, + { id: 'u4', name: '赵强', dept: '运维部' }, + { id: 'u5', name: '陈静', dept: '运维部' } +]; + +// 库存车辆(含车辆类型、品牌、型号、停车场、氢量单位等,用于反写) +var mockStockVehicles = [ + { id: 'v1', plateNo: '粤A12345', status: '库存', vehicleType: '轿车', brand: '比亚迪', model: '秦', departParking: '天河智慧停车场', h2Unit: '%' }, + { id: 'v2', plateNo: '粤A67890', status: '库存', vehicleType: '轿车', brand: '特斯拉', model: 'Model 3', departParking: '南山科技园停车场', h2Unit: 'MPa' }, + { id: 'v3', plateNo: '浙A11111', status: '库存', vehicleType: 'SUV', brand: '小鹏', model: 'P7', departParking: '西湖景区停车场', h2Unit: 'KG' }, + { id: 'v4', plateNo: '浙B22222', status: '库存', vehicleType: '轿车', brand: '蔚来', model: 'ET5', departParking: '宁波江北停车场', h2Unit: '%' }, + { id: 'v5', plateNo: '京A88888', status: '库存', vehicleType: 'MPV', brand: '理想', model: 'L9', departParking: '朝阳区停车场', h2Unit: 'MPa' }, + { id: 'v6', plateNo: '粤B11111', status: '库存', vehicleType: '轿车', brand: '比亚迪', model: '汉', departParking: '福田停车场', h2Unit: 'KG' } +]; + +// 审批节点(查看时步骤条) +var mockApprovalSteps = [ + { key: 'node1', name: '区域负责人审批' }, + { key: 'node2', name: '运营负责人审批' }, + { key: 'node3', name: '财务确认' } +]; + +const Component = function () { + // 视图:list | add | edit | view + var _sv = React.useState('list'); + var currentView = _sv[0]; + var setCurrentView = _sv[1]; + + // Tab:in_progress | history + var _tab = React.useState('in_progress'); + var listTab = _tab[0]; + var setListTab = _tab[1]; + + // 筛选 + var _fd = React.useState(''); + var filterDateStart = _fd[0]; + var setFilterDateStart = _fd[1]; + var _fe = React.useState(''); + var filterDateEnd = _fe[0]; + var setFilterDateEnd = _fe[1]; + var _fo = React.useState(''); + var filterOperator = _fo[0]; + var setFilterOperator = _fo[1]; + var _frdStart = React.useState(''); + var filterReceiveDateStart = _frdStart[0]; + var setFilterReceiveDateStart = _frdStart[1]; + var _frdEnd = React.useState(''); + var filterReceiveDateEnd = _frdEnd[0]; + var setFilterReceiveDateEnd = _frdEnd[1]; + var _frc = React.useState(''); + var filterReceiver = _frc[0]; + var setFilterReceiver = _frc[1]; + var _fpl = React.useState(''); + var filterPlateNo = _fpl[0]; + var setFilterPlateNo = _fpl[1]; + var _fdp = React.useState(''); + var filterDepartProvince = _fdp[0]; + var setFilterDepartProvince = _fdp[1]; + var _fdc = React.useState(''); + var filterDepartCity = _fdc[0]; + var setFilterDepartCity = _fdc[1]; + var _ftp = React.useState(''); + var filterTransferProvince = _ftp[0]; + var setFilterTransferProvince = _ftp[1]; + var _ftc = React.useState(''); + var filterTransferCity = _ftc[0]; + var setFilterTransferCity = _ftc[1]; + var _fm = React.useState(''); + var filterMethod = _fm[0]; + var setFilterMethod = _fm[1]; + var _fas = React.useState(''); + var filterApprovalStatus = _fas[0]; + var setFilterApprovalStatus = _fas[1]; + + // 已应用的筛选条件(仅点击查询后更新,列表按此筛选) + var _applied = React.useState({}); + var appliedFilters = _applied[0]; + var setAppliedFilters = _applied[1]; + + // 筛选下拉展开 + var _ao = React.useState(false); + var filterOperatorOpen = _ao[0]; + var setFilterOperatorOpen = _ao[1]; + var _arc = React.useState(false); + var filterReceiverOpen = _arc[0]; + var setFilterReceiverOpen = _arc[1]; + var _ap = React.useState(false); + var filterPlateOpen = _ap[0]; + var setFilterPlateOpen = _ap[1]; + var _cd = React.useState(false); + var filterCascaderDepartOpen = _cd[0]; + var setFilterCascaderDepartOpen = _cd[1]; + var _ct = React.useState(false); + var filterCascaderTransferOpen = _ct[0]; + var setFilterCascaderTransferOpen = _ct[1]; + var _drOpen = React.useState(false); + var filterDateRangeOpen = _drOpen[0]; + var setFilterDateRangeOpen = _drOpen[1]; + var _drRecOpen = React.useState(false); + var filterReceiveDateRangeOpen = _drRecOpen[0]; + var setFilterReceiveDateRangeOpen = _drRecOpen[1]; + function getInitialViewMonth(valueStr) { + if (valueStr && /^\d{4}-\d{2}-\d{2}$/.test(valueStr)) return valueStr.substring(0, 7); + var d = new Date(); + return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0'); + } + var _drViewL = React.useState(''); + var filterDateRangeViewLeft = _drViewL[0]; + var setFilterDateRangeViewLeft = _drViewL[1]; + var _drViewR = React.useState(''); + var filterDateRangeViewRight = _drViewR[0]; + var setFilterDateRangeViewRight = _drViewR[1]; + var _drRecViewL = React.useState(''); + var filterReceiveDateRangeViewLeft = _drRecViewL[0]; + var setFilterReceiveDateRangeViewLeft = _drRecViewL[1]; + var _drRecViewR = React.useState(''); + var filterReceiveDateRangeViewRight = _drRecViewR[0]; + var setFilterReceiveDateRangeViewRight = _drRecViewR[1]; + + // 分页 + var _cp = React.useState(1); + var currentPage = _cp[0]; + var setCurrentPage = _cp[1]; + var _ps = React.useState(20); + var pageSize = _ps[0]; + var setPageSize = _ps[1]; + + // 调拨记录列表:每条主记录含 lines(按车牌号展开,每车牌一行),hasReceiveRecord 表示是否已补充接收 + // 进行中 10 条(20 行),默认每页 20 条,第一页同时展示「接收记录」「编辑」「撤回」供开发参考 + var _mockList = React.useState((function () { + var regions = [ + { p: 'gd', c: 'gz', label: '广东省-广州市' }, + { p: 'gd', c: 'sz', label: '广东省-深圳市' }, + { p: 'zj', c: 'hz', label: '浙江省-杭州市' }, + { p: 'zj', c: 'nb', label: '浙江省-宁波市' }, + { p: 'bj', c: 'bj', label: '北京市-北京市' } + ]; + var users = [{ id: 'u1', name: '张明' }, { id: 'u2', name: '王芳' }, { id: 'u3', name: '李华' }, { id: 'u4', name: '赵强' }, { id: 'u5', name: '陈静' }]; + var vehicles = [ + { plateNo: '粤A12345', vehicleType: '轿车', brand: '比亚迪', model: '秦', departParking: '天河智慧停车场', h2Unit: '%' }, + { plateNo: '粤A67890', vehicleType: '轿车', brand: '特斯拉', model: 'Model 3', departParking: '南山科技园停车场', h2Unit: 'MPa' }, + { plateNo: '浙A11111', vehicleType: 'SUV', brand: '小鹏', model: 'P7', departParking: '西湖景区停车场', h2Unit: 'KG' }, + { plateNo: '浙B22222', vehicleType: '轿车', brand: '蔚来', model: 'ET5', departParking: '宁波江北停车场', h2Unit: '%' }, + { plateNo: '京A88888', vehicleType: 'MPV', brand: '理想', model: 'L9', departParking: '朝阳区停车场', h2Unit: 'MPa' }, + { plateNo: '粤B11111', vehicleType: '轿车', brand: '比亚迪', model: '汉', departParking: '福田停车场', h2Unit: 'KG' } + ]; + var reasons = ['区域调配需求', '司机驾驶调拨', '维修后回调', '新车投放', '租赁到期回调']; + var carriers = [{ name: '顺丰物流', contact: '400-111-2222' }, { name: '德邦物流', contact: '95353' }]; + var list = []; + var tid = 1; + function makeTransfer(inProgress, hasReceive, count) { + for (var c = 0; c < count; c++) { + var status = inProgress ? ['pending_submit', 'pending_approval', 'approving', 'rejected', 'withdrawn'][tid % 5] : 'completed'; + var hasRec = inProgress ? false : hasReceive; + var dep = regions[tid % regions.length]; + var trans = regions[(tid + 1) % regions.length]; + var u = users[tid % users.length]; + var d = '2025-' + String((tid % 12) + 1).padStart(2, '0') + '-' + String((tid % 20) + 1).padStart(2, '0'); + var createTime = d + ' ' + String(9 + (tid % 8)).padStart(2, '0') + ':' + (tid % 2 ? '15' : '30'); + var lineCount = 2; + var lines = []; + for (var L = 0; L < lineCount; L++) { + var v = vehicles[(tid + L) % vehicles.length]; + lines.push({ + plateNo: v.plateNo, + vehicleType: v.vehicleType, + brand: v.brand, + model: v.model, + departParking: v.departParking, + departMileage: 1000 + tid * 100 + L * 50, + departH2: (60 + tid + L) + (v.h2Unit === 'KG' ? '' : v.h2Unit), + departH2Unit: v.h2Unit, + departElec: 80 + tid + L, + receiveMileage: hasRec ? 1100 + tid * 100 + L * 50 : null, + receiveH2: hasRec ? (65 + tid + L) + v.h2Unit : null, + receiveElec: hasRec ? 85 + tid + L : null, + receiveParking: hasRec ? '抵达停车场' + tid : null, + receiverName: hasRec ? users[(tid + 1) % users.length].name : null + }); + } + list.push({ + id: 't' + tid, + transferDate: d, + departProvince: dep.p, + departCity: dep.c, + departLabel: dep.label, + operatorId: u.id, + operatorName: u.name, + transferProvince: trans.p, + transferCity: trans.c, + transferLabel: trans.label, + transferMethod: tid % 2 ? 'third_party' : 'driver', + transferReason: reasons[tid % reasons.length], + carrierName: carriers[tid % 2].name, + carrierContact: carriers[tid % 2].contact, + approvalStatus: status, + hasReceiveRecord: hasRec, + receiveDate: hasRec ? '2025-' + String((tid % 12) + 1).padStart(2, '0') + '-' + String((tid % 15) + 10).padStart(2, '0') : '', + receiverId: hasRec ? users[(tid + 1) % users.length].id : '', + receiverName: hasRec ? users[(tid + 1) % users.length].name : '', + createTime: createTime, + lines: lines + }); + tid++; + } + } + makeTransfer(false, false, 2); // 进行中:审批完成但未补充接收(2 条,排在最前) + makeTransfer(true, false, 18); // 进行中:待提交/待审批/审批中/驳回/撤回(共 20 条进行中) + makeTransfer(false, true, 20); // 历史记录:审批完成且已补充接收(20 条) + return list; + }())); + var mockTransferList = _mockList[0]; + var setMockTransferList = _mockList[1]; + + // 新增/编辑表单(编辑、查看、接收仍用 formData) + var defaultForm = { + transferDate: '', + receiveDate: '', + operatorId: '', + operatorName: '', + selectedVehicleIds: [], + transferMethod: '', + transferReason: '', + departProvince: '', + departCity: '', + transferProvince: '', + transferCity: '', + carrierName: '', + carrierContact: '' + }; + var _form = React.useState(defaultForm); + var formData = _form[0]; + var setFormData = _form[1]; + + // 新增页专用:上方 7 项 + 下方可编辑行表 + var defaultAddForm = { + transferDate: '', + departProvince: '', + departCity: '', + transferProvince: '', + transferCity: '', + transferMethod: '', + transferReason: '', + carrierName: '', + carrierContact: '' + }; + var _addForm = React.useState(defaultAddForm); + var addFormData = _addForm[0]; + var setAddFormData = _addForm[1]; + var addFormLineIdRef = React.useRef(0); + function createEmptyAddLine() { + addFormLineIdRef.current = (addFormLineIdRef.current || 0) + 1; + return { + rowId: 'addRow-' + addFormLineIdRef.current, + vehicleId: '', + plateNo: '', + vehicleType: '', + brand: '', + model: '', + departParking: '', + departMileage: '', + departH2: '', + departH2Unit: '', + departElec: '' + }; + } + var _addLines = React.useState(function () { return [{ rowId: 'addRow-1', vehicleId: '', plateNo: '', vehicleType: '', brand: '', model: '', departParking: '', departMileage: '', departH2: '', departH2Unit: '', departElec: '' }]; }); + var addFormLines = _addLines[0]; + var setAddFormLines = _addLines[1]; + var _plateOpen = React.useState(null); + var plateSelectOpenRowId = _plateOpen[0]; + var setPlateSelectOpenRowId = _plateOpen[1]; + var _plateKw = React.useState(''); + var plateSearchKeyword = _plateKw[0]; + var setPlateSearchKeyword = _plateKw[1]; + var _addDepartCascader = React.useState(false); + var addDepartCascaderOpen = _addDepartCascader[0]; + var setAddDepartCascaderOpen = _addDepartCascader[1]; + var _addAcceptCascader = React.useState(false); + var addAcceptCascaderOpen = _addAcceptCascader[0]; + var setAddAcceptCascaderOpen = _addAcceptCascader[1]; + + // 编辑页专用:行表 + 车牌选择展开态(与新增页结构一致) + var editFormLineIdRef = React.useRef(0); + function createEmptyEditLine() { + editFormLineIdRef.current = editFormLineIdRef.current + 1; + return { + rowId: 'editRow-' + editFormLineIdRef.current, + vehicleId: '', + plateNo: '', + vehicleType: '', + brand: '', + model: '', + departParking: '', + departMileage: '', + departH2: '', + departH2Unit: '', + departElec: '', + receiveMileage: '', + receiveH2: '', + receiveElec: '' + }; + } + function buildEditFormLinesFromRecord(transfer) { + if (!transfer || !transfer.lines || !transfer.lines.length) return [createEmptyEditLine()]; + editFormLineIdRef.current = Math.max(editFormLineIdRef.current || 0, transfer.lines.length); + return transfer.lines.map(function (line, idx) { + var v = mockStockVehicles.find(function (x) { return x.plateNo === line.plateNo; }); + return { + rowId: 'editRow-' + (idx + 1), + vehicleId: v ? v.id : (line.vehicleId || ''), + plateNo: line.plateNo || '', + vehicleType: line.vehicleType || (v && v.vehicleType) || '', + brand: line.brand || (v && v.brand) || '', + model: line.model || (v && v.model) || '', + departParking: line.departParking || (v && v.departParking) || '', + departMileage: line.departMileage != null && line.departMileage !== '' ? line.departMileage : '', + departH2: line.departH2 != null && line.departH2 !== '' ? line.departH2 : '', + departH2Unit: (line.departH2Unit || (v && v.h2Unit) || ''), + departElec: line.departElec != null && line.departElec !== '' ? line.departElec : '', + receiveMileage: line.receiveMileage != null && line.receiveMileage !== '' ? line.receiveMileage : '', + receiveH2: line.receiveH2 != null && line.receiveH2 !== '' ? line.receiveH2 : '', + receiveElec: line.receiveElec != null && line.receiveElec !== '' ? line.receiveElec : '' + }; + }); + } + var _editFormLines = React.useState([]); + var editFormLines = _editFormLines[0]; + var setEditFormLines = _editFormLines[1]; + var _editPlateOpen = React.useState(null); + var editPlateSelectOpenRowId = _editPlateOpen[0]; + var setEditPlateSelectOpenRowId = _editPlateOpen[1]; + var _editPlateKw = React.useState(''); + var editPlateSearchKeyword = _editPlateKw[0]; + var setEditPlateSearchKeyword = _editPlateKw[1]; + + function updateEditLine(rowId, updates) { + setEditFormLines(function (prev) { + return prev.map(function (l) { + if (l.rowId !== rowId) return l; + var next = {}; + for (var k in l) next[k] = l[k]; + for (var k in updates) next[k] = updates[k]; + return next; + }); + }); + } + function getEditPagePlateOptions(rowId) { + var usedElsewhere = function (v) { + return editFormLines.some(function (l) { return l.rowId !== rowId && l.vehicleId === v.id; }); + }; + var list = mockStockVehicles.filter(function (v) { return !usedElsewhere(v); }); + var kw = (editPlateSearchKeyword || '').trim(); + if (kw) list = list.filter(function (v) { return (v.plateNo || '').indexOf(kw) >= 0; }); + return list; + } + + // 编辑/查看 当前记录 + var _edit = React.useState(null); + var editRecord = _edit[0]; + var setEditRecord = _edit[1]; + var _view = React.useState(null); + var viewRecord = _view[0]; + var setViewRecord = _view[1]; + + // 新增页级联/穿梭框等 UI 状态 + var _formDp = React.useState(false); + var formDepartCascaderOpen = _formDp[0]; + var setFormDepartCascaderOpen = _formDp[1]; + var _formTp = React.useState(false); + var formTransferCascaderOpen = _formTp[0]; + var setFormTransferCascaderOpen = _formTp[1]; + var _formOp = React.useState(false); + var formOperatorOpen = _formOp[0]; + var setFormOperatorOpen = _formOp[1]; + // 穿梭框:左侧/右侧选中的 id 列表(用于多选后点击添加/移除) + var _leftSel = React.useState([]); + var leftSelectedIds = _leftSel[0]; + var setLeftSelectedIds = _leftSel[1]; + var _rightSel = React.useState([]); + var rightSelectedIds = _rightSel[0]; + var setRightSelectedIds = _rightSel[1]; + + var _toast = React.useState(''); + var toastMessage = _toast[0]; + var setToastMessage = _toast[1]; + var _reqModal = React.useState(false); + var showRequirementModal = _reqModal[0]; + var setShowRequirementModal = _reqModal[1]; + var _withdrawConfirm = React.useState(null); + var withdrawConfirmRecord = _withdrawConfirm[0]; + var setWithdrawConfirmRecord = _withdrawConfirm[1]; + var _selectedRows = React.useState([]); + var selectedRowIds = _selectedRows[0]; + var setSelectedRowIds = _selectedRows[1]; + var _receiveForm = React.useState({ receiveDate: '', receiverName: '张明', lines: [] }); + var receiveFormData = _receiveForm[0]; + var setReceiveFormData = _receiveForm[1]; + + function getStatusLabel(value) { + var s = APPROVAL_STATUS.find(function (x) { return x.value === value; }); + return s ? s.label : value; + } + function getMethodLabel(value) { + var m = TRANSFER_METHODS.find(function (x) { return x.value === value; }); + return m ? m.label : value; + } + // 氢量只显示一次单位:若值已带 %/MPa/KG 则不追加单位 + function formatH2(val, unit) { + if (val == null || val === '') return '-'; + var s = String(val); + if (s.match(/%|MPa|KG$/)) return s; + return s + (unit || ''); + } + + // 将主记录按 lines 展开为行(每车牌一行),并进行中/历史记录 tab 筛选 + function getFlattenedRows() { + var rows = []; + mockTransferList.forEach(function (t) { + var inProgress = ['pending_submit', 'pending_approval', 'approving', 'rejected', 'withdrawn'].indexOf(t.approvalStatus) >= 0 || (t.approvalStatus === 'completed' && !t.hasReceiveRecord); + var inHistory = t.approvalStatus === 'completed' && t.hasReceiveRecord; + if (listTab === 'in_progress' && !inProgress) return; + if (listTab === 'history' && !inHistory) return; + (t.lines || []).forEach(function (line) { + rows.push({ transfer: t, line: line, rowId: t.id + '-' + (line.plateNo || '') }); + }); + }); + return rows; + } + + function getFilteredList() { + var list = getFlattenedRows(); + var a = appliedFilters; + list = list.filter(function (row) { + var t = row.transfer; + var line = row.line; + if (a.filterDateStart && t.transferDate < a.filterDateStart) return false; + if (a.filterDateEnd && t.transferDate > a.filterDateEnd) return false; + if (a.filterOperator && (!t.operatorName || t.operatorName.indexOf(a.filterOperator) < 0)) return false; + if (a.filterReceiveDateStart && t.receiveDate && t.receiveDate < a.filterReceiveDateStart) return false; + if (a.filterReceiveDateEnd && t.receiveDate && t.receiveDate > a.filterReceiveDateEnd) return false; + if (a.filterReceiver && (!t.receiverName || t.receiverName.indexOf(a.filterReceiver) < 0)) return false; + if (a.filterPlateNo && (!line.plateNo || line.plateNo.indexOf(a.filterPlateNo) < 0)) return false; + if (a.filterDepartProvince && t.departProvince !== a.filterDepartProvince) return false; + if (a.filterDepartCity && t.departCity !== a.filterDepartCity) return false; + if (a.filterTransferProvince && t.transferProvince !== a.filterTransferProvince) return false; + if (a.filterTransferCity && t.transferCity !== a.filterTransferCity) return false; + if (a.filterMethod && t.transferMethod !== a.filterMethod) return false; + if (a.filterApprovalStatus && t.approvalStatus !== a.filterApprovalStatus) return false; + return true; + }); + return list; + } + + var filteredList = getFilteredList(); + var totalItems = filteredList.length; + var totalPages = Math.ceil(totalItems / pageSize) || 1; + var validPage = currentPage > totalPages && totalPages > 0 ? 1 : (currentPage < 1 ? 1 : currentPage); + if (validPage !== currentPage && totalPages > 0) { + setCurrentPage(1); + validPage = 1; + } + var startIndex = (validPage - 1) * pageSize; + var endIndex = startIndex + pageSize; + var paginatedList = filteredList.slice(startIndex, endIndex); + + function handleFormChange(field, value) { + var next = {}; + for (var k in formData) { next[k] = formData[k]; } + next[field] = value; + setFormData(next); + } + + function handleAddFormChange(field, value) { + var next = {}; + for (var k in addFormData) { next[k] = addFormData[k]; } + next[field] = value; + setAddFormData(next); + } + + function validateAddForm(submitForApproval) { + if (!addFormData.transferDate) { + setToastMessage('请选择调拨日期'); + setTimeout(function () { setToastMessage(''); }, 2000); + return false; + } + if (!addFormData.departProvince || !addFormData.departCity) { + setToastMessage('请选择出发区域'); + setTimeout(function () { setToastMessage(''); }, 2000); + return false; + } + if (!addFormData.transferProvince || !addFormData.transferCity) { + setToastMessage('请选择接收区域'); + setTimeout(function () { setToastMessage(''); }, 2000); + return false; + } + if (!addFormData.transferMethod) { + setToastMessage('请选择调拨方式'); + setTimeout(function () { setToastMessage(''); }, 2000); + return false; + } + if (!addFormData.transferReason || !addFormData.transferReason.trim()) { + setToastMessage('请填写调拨原因'); + setTimeout(function () { setToastMessage(''); }, 2000); + return false; + } + if (!addFormData.carrierName || !addFormData.carrierName.trim()) { + setToastMessage('请填写运输方名称'); + setTimeout(function () { setToastMessage(''); }, 2000); + return false; + } + if (!addFormData.carrierContact || !addFormData.carrierContact.trim()) { + setToastMessage('请填写运输方联系方式'); + setTimeout(function () { setToastMessage(''); }, 2000); + return false; + } + var validLines = addFormLines.filter(function (row) { return row.vehicleId && row.plateNo; }); + if (validLines.length === 0) { + setToastMessage('请至少添加一行并选择车牌号'); + setTimeout(function () { setToastMessage(''); }, 2000); + return false; + } + for (var i = 0; i < addFormLines.length; i++) { + var row = addFormLines[i]; + if (!row.plateNo) continue; + var rowNum = i + 1; + if (row.departMileage === '' || row.departMileage === undefined || row.departMileage === null) { + setToastMessage('请填写第' + rowNum + '行出发里程(KM)'); + setTimeout(function () { setToastMessage(''); }, 2000); + return false; + } + if (row.departH2 === '' || row.departH2 === undefined || row.departH2 === null) { + setToastMessage('请填写第' + rowNum + '行出发氢量'); + setTimeout(function () { setToastMessage(''); }, 2000); + return false; + } + if (row.departElec === '' || row.departElec === undefined || row.departElec === null) { + setToastMessage('请填写第' + rowNum + '行出发电量'); + setTimeout(function () { setToastMessage(''); }, 2000); + return false; + } + } + return true; + } + + function buildAddRecord(approvalStatus) { + var dp = provinceList.find(function (x) { return x.code === addFormData.departProvince; }); + var dc = (cityMap[addFormData.departProvince] || []).find(function (x) { return x.code === addFormData.departCity; }); + var tp = provinceList.find(function (x) { return x.code === addFormData.transferProvince; }); + var tc = (cityMap[addFormData.transferProvince] || []).find(function (x) { return x.code === addFormData.transferCity; }); + var newId = 't' + (mockTransferList.length + 1); + var now = new Date(); + var createTimeStr = now.getFullYear() + '-' + String(now.getMonth() + 1).padStart(2, '0') + '-' + String(now.getDate()).padStart(2, '0') + ' ' + String(now.getHours()).padStart(2, '0') + ':' + String(now.getMinutes()).padStart(2, '0'); + var validLines = addFormLines.filter(function (row) { return row.vehicleId && row.plateNo; }); + var lines = validLines.map(function (row) { + return { + plateNo: row.plateNo, + vehicleType: row.vehicleType || '-', + brand: row.brand || '-', + model: row.model || '-', + departParking: row.departParking || '-', + departMileage: row.departMileage != null && row.departMileage !== '' ? Number(row.departMileage) : null, + departH2: row.departH2 != null && row.departH2 !== '' ? row.departH2 : '', + departH2Unit: row.departH2Unit || '', + departElec: row.departElec != null && row.departElec !== '' ? Number(row.departElec) : null, + receiveMileage: null, + receiveH2: null, + receiveElec: null, + receiveParking: null, + receiverName: null + }; + }); + return { + id: newId, + transferDate: addFormData.transferDate, + operatorId: 'u1', + operatorName: '张明', + departProvince: addFormData.departProvince, + departCity: addFormData.departCity, + departLabel: (dp ? dp.name : '') + '-' + (dc ? dc.name : ''), + transferProvince: addFormData.transferProvince, + transferCity: addFormData.transferCity, + transferLabel: (tp ? tp.name : '') + '-' + (tc ? tc.name : ''), + transferMethod: addFormData.transferMethod, + transferReason: addFormData.transferReason || '', + carrierName: addFormData.carrierName || '', + carrierContact: addFormData.carrierContact || '', + approvalStatus: approvalStatus, + hasReceiveRecord: false, + receiveDate: '', + receiverId: '', + receiverName: '', + createTime: createTimeStr, + lines: lines + }; + } + + function handleAddSave() { + if (!validateAddForm(false)) return; + var newRecord = buildAddRecord('pending_submit'); + setMockTransferList(mockTransferList.concat([newRecord])); + setToastMessage('保存成功'); + setTimeout(function () { setToastMessage(''); }, 2000); + setCurrentView('list'); + setAddFormData(defaultAddForm); + setAddFormLines([createEmptyAddLine()]); + setPlateSelectOpenRowId(null); + } + + function handleAddSubmit() { + if (!validateAddForm(true)) return; + var newRecord = buildAddRecord('pending_approval'); + setMockTransferList(mockTransferList.concat([newRecord])); + setToastMessage('已提交审核'); + setTimeout(function () { setToastMessage(''); }, 2000); + setCurrentView('list'); + setAddFormData(defaultAddForm); + setAddFormLines([createEmptyAddLine()]); + setPlateSelectOpenRowId(null); + } + + function handleAddCancel() { + setCurrentView('list'); + setAddFormData(defaultAddForm); + setAddFormLines([createEmptyAddLine()]); + setPlateSelectOpenRowId(null); + } + + function updateAddLine(rowId, updates) { + setAddFormLines(function (prev) { + return prev.map(function (l) { + if (l.rowId !== rowId) return l; + var next = {}; + for (var k in l) next[k] = l[k]; + for (var k in updates) next[k] = updates[k]; + return next; + }); + }); + } + + function getAddPagePlateOptions(rowId) { + var usedElsewhere = function (v) { + return addFormLines.some(function (l) { return l.rowId !== rowId && l.vehicleId === v.id; }); + }; + var list = mockStockVehicles.filter(function (v) { return !usedElsewhere(v); }); + var kw = (plateSearchKeyword || '').trim(); + if (kw) list = list.filter(function (v) { return (v.plateNo || '').indexOf(kw) >= 0; }); + return list; + } + + function renderAddPage() { + var grid = styles.formGrid; + return React.createElement('div', {}, + React.createElement('div', { style: Object.assign({}, styles.card, { marginBottom: t.spacing16 }) }, + React.createElement('div', { style: grid }, + React.createElement('div', { style: styles.formRow }, + React.createElement('label', { style: styles.formLabel }, '调拨日期', React.createElement('span', { style: styles.formLabelReq }, '*')), + React.createElement('input', { style: styles.input, type: 'date', value: addFormData.transferDate, onChange: function (e) { handleAddFormChange('transferDate', e.target.value); } }) + ), + React.createElement('div', { style: styles.formRow }, + React.createElement('label', { style: styles.formLabel }, '出发区域', React.createElement('span', { style: styles.formLabelReq }, '*')), + renderCascader(addFormData.departProvince, addFormData.departCity, function (v) { setAddFormData(function (prev) { var n = {}; for (var k in prev) n[k] = prev[k]; n.departProvince = v; n.departCity = ''; return n; }); }, function (v) { setAddFormData(function (prev) { var n = {}; for (var k in prev) n[k] = prev[k]; n.departCity = v; return n; }); }, addDepartCascaderOpen, setAddDepartCascaderOpen) + ), + React.createElement('div', { style: styles.formRow }, + React.createElement('label', { style: styles.formLabel }, '接收区域', React.createElement('span', { style: styles.formLabelReq }, '*')), + renderCascader(addFormData.transferProvince, addFormData.transferCity, function (v) { setAddFormData(function (prev) { var n = {}; for (var k in prev) n[k] = prev[k]; n.transferProvince = v; n.transferCity = ''; return n; }); }, function (v) { setAddFormData(function (prev) { var n = {}; for (var k in prev) n[k] = prev[k]; n.transferCity = v; return n; }); }, addAcceptCascaderOpen, setAddAcceptCascaderOpen) + ), + React.createElement('div', { style: styles.formRow }, + React.createElement('label', { style: styles.formLabel }, '调拨方式', React.createElement('span', { style: styles.formLabelReq }, '*')), + React.createElement('div', { style: styles.btnGroup }, + React.createElement('button', { type: 'button', style: Object.assign({}, styles.btnGroupItem, addFormData.transferMethod === 'third_party' ? styles.btnGroupItemActive : {}), onClick: function () { handleAddFormChange('transferMethod', 'third_party'); } }, '第三方运输'), + React.createElement('button', { type: 'button', style: Object.assign({}, styles.btnGroupItem, styles.btnGroupItemLast, addFormData.transferMethod === 'driver' ? styles.btnGroupItemActive : {}), onClick: function () { handleAddFormChange('transferMethod', 'driver'); } }, '司机驾驶') + ) + ), + React.createElement('div', { style: Object.assign({}, styles.formRow, styles.formGridFull) }, + React.createElement('label', { style: styles.formLabel }, '调拨原因', React.createElement('span', { style: styles.formLabelReq }, '*')), + React.createElement('textarea', { style: styles.textarea, value: addFormData.transferReason, onChange: function (e) { handleAddFormChange('transferReason', e.target.value); }, placeholder: '请输入调拨原因' }) + ), + React.createElement('div', { style: styles.formRow }, + React.createElement('label', { style: styles.formLabel }, '运输方名称', React.createElement('span', { style: styles.formLabelReq }, '*')), + React.createElement('input', { style: styles.input, value: addFormData.carrierName, onChange: function (e) { handleAddFormChange('carrierName', e.target.value); }, placeholder: '请输入运输方名称或司机姓名' }) + ), + React.createElement('div', { style: styles.formRow }, + React.createElement('label', { style: styles.formLabel }, '运输方联系方式', React.createElement('span', { style: styles.formLabelReq }, '*')), + React.createElement('input', { style: styles.input, value: addFormData.carrierContact, onChange: function (e) { handleAddFormChange('carrierContact', e.target.value); }, placeholder: '请输入运输方联系方式' }) + ) + ) + ), + React.createElement('div', { style: styles.card }, + React.createElement('div', { style: styles.tableWrapAddPage }, + React.createElement('table', { style: styles.table }, + React.createElement('thead', {}, + React.createElement('tr', {}, + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '车牌号', React.createElement('span', { style: styles.formLabelReq }, '*')), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '车辆类型'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '品牌'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '型号'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '出发停车场'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '出发里程(KM)', React.createElement('span', { style: styles.formLabelReq }, '*')), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '出发氢量', React.createElement('span', { style: styles.formLabelReq }, '*')), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '出发电量', React.createElement('span', { style: styles.formLabelReq }, '*')), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '操作') + ) + ), + React.createElement('tbody', {}, + addFormLines.map(function (row, rowIndex) { + var isPlateOpen = plateSelectOpenRowId === row.rowId; + var plateOptions = getAddPagePlateOptions(row.rowId); + var isEvenRow = rowIndex % 2 === 1; + return React.createElement('tr', { key: row.rowId, style: isEvenRow ? styles.trStripe : styles.trOdd }, + React.createElement('td', { style: Object.assign({}, styles.td, styles.tdNowrap) }, + React.createElement('div', { style: styles.autocompleteWrap }, + React.createElement('input', { + style: Object.assign({}, styles.input, { minWidth: '120px' }), + placeholder: '请选择', + value: isPlateOpen ? plateSearchKeyword : (row.plateNo || ''), + onChange: function (e) { setPlateSearchKeyword(e.target.value); setPlateSelectOpenRowId(row.rowId); }, + onFocus: function () { setPlateSelectOpenRowId(row.rowId); setPlateSearchKeyword(row.plateNo || ''); }, + onBlur: function () { setTimeout(function () { setPlateSelectOpenRowId(null); }, 200); } + }), + isPlateOpen && React.createElement(React.Fragment, {}, + React.createElement('div', { style: { position: 'fixed', left: 0, top: 0, right: 0, bottom: 0, zIndex: 1049 }, onClick: function () { setPlateSelectOpenRowId(null); } }), + React.createElement('div', { style: Object.assign({}, styles.autocompletePanel, styles.autocompletePanelOverflow), onClick: function (e) { e.stopPropagation(); } }, + plateOptions.length === 0 + ? React.createElement('div', { style: { padding: '8px 12px', fontSize: t.fontSize14, color: t.neutral6 } }, '无匹配的库存车辆') + : plateOptions.map(function (v) { + return React.createElement('div', { + key: v.id, + style: styles.autocompleteOption, + onMouseDown: function (e) { e.preventDefault(); }, + onClick: function () { + updateAddLine(row.rowId, { vehicleId: v.id, plateNo: v.plateNo, vehicleType: v.vehicleType || '', brand: v.brand || '', model: v.model || '', departParking: v.departParking || '', departH2Unit: v.h2Unit || '' }); + setPlateSelectOpenRowId(null); + setPlateSearchKeyword(''); + } + }, v.plateNo); + }) + ) + ) + ) + ), + React.createElement('td', { style: Object.assign({}, styles.td, styles.tdNowrap) }, row.vehicleType || '-'), + React.createElement('td', { style: Object.assign({}, styles.td, styles.tdNowrap) }, row.brand || '-'), + React.createElement('td', { style: Object.assign({}, styles.td, styles.tdNowrap) }, row.model || '-'), + React.createElement('td', { style: Object.assign({}, styles.td, styles.tdNowrap) }, row.departParking || '-'), + React.createElement('td', { style: Object.assign({}, styles.td, styles.tdNowrap) }, + React.createElement('input', { style: Object.assign({}, styles.input, { width: '80px' }), type: 'number', value: row.departMileage, onChange: function (e) { updateAddLine(row.rowId, { departMileage: e.target.value }); }, placeholder: '' }) + ), + React.createElement('td', { style: Object.assign({}, styles.td, styles.tdNowrap) }, + React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center' } }, + React.createElement('input', { style: Object.assign({}, styles.input, { width: '80px' }), value: row.departH2, onChange: function (e) { updateAddLine(row.rowId, { departH2: e.target.value }); }, placeholder: '' }), + row.departH2Unit ? React.createElement('span', { style: { marginLeft: '4px', color: t.neutral6, fontSize: t.fontSize14 } }, row.departH2Unit) : null + ) + ), + React.createElement('td', { style: Object.assign({}, styles.td, styles.tdNowrap) }, + React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center' } }, + React.createElement('input', { style: Object.assign({}, styles.input, { width: '80px' }), value: row.departElec, onChange: function (e) { updateAddLine(row.rowId, { departElec: e.target.value }); }, placeholder: '' }), + React.createElement('span', { style: { marginLeft: '4px', color: t.neutral6, fontSize: t.fontSize14 } }, 'kWh') + ) + ), + React.createElement('td', { style: Object.assign({}, styles.td, styles.tdNowrap) }, + React.createElement('a', { href: '#', style: styles.actionLink, onClick: function (e) { + e.preventDefault(); + if (addFormLines.length <= 1) { setToastMessage('至少保留一行'); setTimeout(function () { setToastMessage(''); }, 2000); return; } + setAddFormLines(addFormLines.filter(function (l) { return l.rowId !== row.rowId; })); + } }, '删除') + ) + ); + }) + ) + ) + ), + React.createElement('div', { style: { marginTop: t.spacing12 } }, + React.createElement('button', { type: 'button', style: Object.assign({}, styles.btn, styles.btnDefault), onClick: function () { setAddFormLines(addFormLines.concat([createEmptyAddLine()])); } }, '新增一行') + ), + React.createElement('div', { style: styles.modalFooter }, + React.createElement('button', { style: Object.assign({}, styles.btn, styles.btnDefault), onClick: handleAddCancel }, '取消'), + React.createElement('button', { style: Object.assign({}, styles.btn, styles.btnDefault), onClick: handleAddSave }, '保存'), + React.createElement('button', { style: Object.assign({}, styles.btn, styles.btnPrimary), onClick: handleAddSubmit }, '提交审核') + ) + ) + ); + } + + function renderEditPage() { + var grid = styles.formGrid; + return React.createElement('div', {}, + React.createElement('div', { style: Object.assign({}, styles.card, { marginBottom: t.spacing16 }) }, + React.createElement('div', { style: grid }, + React.createElement('div', { style: styles.formRow }, + React.createElement('label', { style: styles.formLabel }, '调拨日期', React.createElement('span', { style: styles.formLabelReq }, '*')), + React.createElement('input', { style: styles.input, type: 'date', value: formData.transferDate, onChange: function (e) { handleFormChange('transferDate', e.target.value); } }) + ), + React.createElement('div', { style: styles.formRow }, + React.createElement('label', { style: styles.formLabel }, '出发区域', React.createElement('span', { style: styles.formLabelReq }, '*')), + renderCascader(formData.departProvince, formData.departCity, function (v) { setFormData(function (prev) { var n = {}; for (var k in prev) n[k] = prev[k]; n.departProvince = v; n.departCity = ''; return n; }); }, function (v) { setFormData(function (prev) { var n = {}; for (var k in prev) n[k] = prev[k]; n.departCity = v; return n; }); }, formDepartCascaderOpen, setFormDepartCascaderOpen) + ), + React.createElement('div', { style: styles.formRow }, + React.createElement('label', { style: styles.formLabel }, '接收区域', React.createElement('span', { style: styles.formLabelReq }, '*')), + renderCascader(formData.transferProvince, formData.transferCity, function (v) { setFormData(function (prev) { var n = {}; for (var k in prev) n[k] = prev[k]; n.transferProvince = v; n.transferCity = ''; return n; }); }, function (v) { setFormData(function (prev) { var n = {}; for (var k in prev) n[k] = prev[k]; n.transferCity = v; return n; }); }, formTransferCascaderOpen, setFormTransferCascaderOpen) + ), + React.createElement('div', { style: styles.formRow }, + React.createElement('label', { style: styles.formLabel }, '调拨方式', React.createElement('span', { style: styles.formLabelReq }, '*')), + React.createElement('div', { style: styles.btnGroup }, + React.createElement('button', { type: 'button', style: Object.assign({}, styles.btnGroupItem, formData.transferMethod === 'third_party' ? styles.btnGroupItemActive : {}), onClick: function () { handleFormChange('transferMethod', 'third_party'); } }, '第三方运输'), + React.createElement('button', { type: 'button', style: Object.assign({}, styles.btnGroupItem, styles.btnGroupItemLast, formData.transferMethod === 'driver' ? styles.btnGroupItemActive : {}), onClick: function () { handleFormChange('transferMethod', 'driver'); } }, '司机驾驶') + ) + ), + React.createElement('div', { style: Object.assign({}, styles.formRow, styles.formGridFull) }, + React.createElement('label', { style: styles.formLabel }, '调拨原因', React.createElement('span', { style: styles.formLabelReq }, '*')), + React.createElement('textarea', { style: styles.textarea, value: formData.transferReason, onChange: function (e) { handleFormChange('transferReason', e.target.value); }, placeholder: '请输入调拨原因' }) + ), + React.createElement('div', { style: styles.formRow }, + React.createElement('label', { style: styles.formLabel }, '运输方名称', React.createElement('span', { style: styles.formLabelReq }, '*')), + React.createElement('input', { style: styles.input, value: formData.carrierName, onChange: function (e) { handleFormChange('carrierName', e.target.value); }, placeholder: '请输入运输方名称或司机姓名' }) + ), + React.createElement('div', { style: styles.formRow }, + React.createElement('label', { style: styles.formLabel }, '运输方联系方式', React.createElement('span', { style: styles.formLabelReq }, '*')), + React.createElement('input', { style: styles.input, value: formData.carrierContact, onChange: function (e) { handleFormChange('carrierContact', e.target.value); }, placeholder: '请输入运输方联系方式' }) + ), + React.createElement('div', { style: styles.formRow }, + React.createElement('label', { style: styles.formLabel }, '接收日期', React.createElement('span', { style: styles.formLabelReq }, '*')), + React.createElement('input', { style: styles.input, type: 'date', value: formData.receiveDate, onChange: function (e) { handleFormChange('receiveDate', e.target.value); } }) + ) + ) + ), + React.createElement('div', { style: styles.card }, + React.createElement('div', { style: styles.tableWrapAddPage }, + React.createElement('table', { style: styles.table }, + React.createElement('thead', {}, + React.createElement('tr', {}, + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '车牌号', React.createElement('span', { style: styles.formLabelReq }, '*')), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '车辆类型'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '品牌'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '型号'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '出发停车场'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '出发里程(KM)', React.createElement('span', { style: styles.formLabelReq }, '*')), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '接收里程(KM)'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '出发氢量', React.createElement('span', { style: styles.formLabelReq }, '*')), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '接收氢量', React.createElement('span', { style: styles.formLabelReq }, '*')), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '出发电量', React.createElement('span', { style: styles.formLabelReq }, '*')), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '接收电量', React.createElement('span', { style: styles.formLabelReq }, '*')), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '操作') + ) + ), + React.createElement('tbody', {}, + editFormLines.map(function (row, rowIndex) { + var isPlateOpen = editPlateSelectOpenRowId === row.rowId; + var plateOptions = getEditPagePlateOptions(row.rowId); + var isEvenRow = rowIndex % 2 === 1; + return React.createElement('tr', { key: row.rowId, style: isEvenRow ? styles.trStripe : styles.trOdd }, + React.createElement('td', { style: Object.assign({}, styles.td, styles.tdNowrap) }, + React.createElement('div', { style: styles.autocompleteWrap }, + React.createElement('input', { + style: Object.assign({}, styles.input, { minWidth: '120px' }), + placeholder: '请选择', + value: isPlateOpen ? editPlateSearchKeyword : (row.plateNo || ''), + onChange: function (e) { setEditPlateSearchKeyword(e.target.value); setEditPlateSelectOpenRowId(row.rowId); }, + onFocus: function () { setEditPlateSelectOpenRowId(row.rowId); setEditPlateSearchKeyword(row.plateNo || ''); }, + onBlur: function () { setTimeout(function () { setEditPlateSelectOpenRowId(null); }, 200); } + }), + isPlateOpen && React.createElement(React.Fragment, {}, + React.createElement('div', { style: { position: 'fixed', left: 0, top: 0, right: 0, bottom: 0, zIndex: 1049 }, onClick: function () { setEditPlateSelectOpenRowId(null); } }), + React.createElement('div', { style: Object.assign({}, styles.autocompletePanel, styles.autocompletePanelOverflow), onClick: function (e) { e.stopPropagation(); } }, + plateOptions.length === 0 + ? React.createElement('div', { style: { padding: '8px 12px', fontSize: t.fontSize14, color: t.neutral6 } }, '无匹配的库存车辆') + : plateOptions.map(function (v) { + return React.createElement('div', { + key: v.id, + style: styles.autocompleteOption, + onMouseDown: function (e) { e.preventDefault(); }, + onClick: function () { + updateEditLine(row.rowId, { vehicleId: v.id, plateNo: v.plateNo, vehicleType: v.vehicleType || '', brand: v.brand || '', model: v.model || '', departParking: v.departParking || '', departH2Unit: v.h2Unit || '' }); + setEditPlateSelectOpenRowId(null); + setEditPlateSearchKeyword(''); + } + }, v.plateNo); + }) + ) + ) + ) + ), + React.createElement('td', { style: Object.assign({}, styles.td, styles.tdNowrap) }, row.vehicleType || '-'), + React.createElement('td', { style: Object.assign({}, styles.td, styles.tdNowrap) }, row.brand || '-'), + React.createElement('td', { style: Object.assign({}, styles.td, styles.tdNowrap) }, row.model || '-'), + React.createElement('td', { style: Object.assign({}, styles.td, styles.tdNowrap) }, row.departParking || '-'), + React.createElement('td', { style: Object.assign({}, styles.td, styles.tdNowrap) }, + React.createElement('input', { style: Object.assign({}, styles.input, { width: '80px' }), type: 'number', value: row.departMileage, onChange: function (e) { updateEditLine(row.rowId, { departMileage: e.target.value }); }, placeholder: '' }) + ), + React.createElement('td', { style: Object.assign({}, styles.td, styles.tdNowrap) }, + React.createElement('input', { style: Object.assign({}, styles.input, { width: '80px' }), type: 'number', value: row.receiveMileage, onChange: function (e) { updateEditLine(row.rowId, { receiveMileage: e.target.value }); }, placeholder: '' }) + ), + React.createElement('td', { style: Object.assign({}, styles.td, styles.tdNowrap) }, + React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center' } }, + React.createElement('input', { style: Object.assign({}, styles.input, { width: '80px' }), value: row.departH2, onChange: function (e) { updateEditLine(row.rowId, { departH2: e.target.value }); }, placeholder: '' }), + row.departH2Unit ? React.createElement('span', { style: { marginLeft: '4px', color: t.neutral6, fontSize: t.fontSize14 } }, row.departH2Unit) : null + ) + ), + React.createElement('td', { style: Object.assign({}, styles.td, styles.tdNowrap) }, + React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center' } }, + React.createElement('input', { style: Object.assign({}, styles.input, { width: '80px' }), value: row.receiveH2, onChange: function (e) { updateEditLine(row.rowId, { receiveH2: e.target.value }); }, placeholder: '' }), + row.departH2Unit ? React.createElement('span', { style: { marginLeft: '4px', color: t.neutral6, fontSize: t.fontSize14 } }, row.departH2Unit) : null + ) + ), + React.createElement('td', { style: Object.assign({}, styles.td, styles.tdNowrap) }, + React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center' } }, + React.createElement('input', { style: Object.assign({}, styles.input, { width: '80px' }), value: row.departElec, onChange: function (e) { updateEditLine(row.rowId, { departElec: e.target.value }); }, placeholder: '' }), + React.createElement('span', { style: { marginLeft: '4px', color: t.neutral6, fontSize: t.fontSize14 } }, 'kWh') + ) + ), + React.createElement('td', { style: Object.assign({}, styles.td, styles.tdNowrap) }, + React.createElement('input', { style: Object.assign({}, styles.input, { width: '80px' }), type: 'number', value: row.receiveElec, onChange: function (e) { updateEditLine(row.rowId, { receiveElec: e.target.value }); }, placeholder: '' }) + ), + React.createElement('td', { style: Object.assign({}, styles.td, styles.tdNowrap) }, + React.createElement('a', { href: '#', style: styles.actionLink, onClick: function (e) { + e.preventDefault(); + if (editFormLines.length <= 1) { setToastMessage('至少保留一行'); setTimeout(function () { setToastMessage(''); }, 2000); return; } + setEditFormLines(editFormLines.filter(function (l) { return l.rowId !== row.rowId; })); + } }, '删除') + ) + ); + }) + ) + ) + ), + React.createElement('div', { style: { marginTop: t.spacing12 } }, + React.createElement('button', { type: 'button', style: Object.assign({}, styles.btn, styles.btnDefault), onClick: function () { setEditFormLines(editFormLines.concat([createEmptyEditLine()])); } }, '新增一行') + ), + React.createElement('div', { style: styles.modalFooter }, + React.createElement('button', { style: Object.assign({}, styles.btn, styles.btnDefault), onClick: function () { setCurrentView('list'); setEditRecord(null); setFormData(defaultForm); setEditFormLines([]); setEditPlateSelectOpenRowId(null); setEditPlateSearchKeyword(''); } }, '取消'), + React.createElement('button', { style: Object.assign({}, styles.btn, styles.btnDefault), onClick: handleEditSave }, '保存'), + React.createElement('button', { style: Object.assign({}, styles.btn, styles.btnPrimary), onClick: function () { handleEditSave(); } }, '提交') + ) + ) + ); + } + + // 查看页:与接收记录页布局、字段一致,全部只读禁用(调拨信息 + 接收日期 + 明细表) + function renderViewPage(record) { + if (!record) return null; + var lines = record.lines || []; + return React.createElement('div', { style: styles.card }, + React.createElement('div', { style: styles.modalTitle }, '查看调拨'), + renderFormContent(true, true), + React.createElement('div', { style: styles.formRow }, + React.createElement('label', { style: styles.formLabel }, '接收日期'), + React.createElement('span', { style: { fontSize: t.fontSize14, color: t.neutral8 } }, record.receiveDate || '-') + ), + React.createElement('div', { style: Object.assign({}, styles.formRow, { marginTop: t.spacing24 }) }, + React.createElement('div', { style: styles.tableWrap }, + React.createElement('table', { style: styles.table }, + React.createElement('thead', {}, + React.createElement('tr', {}, + 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 }, '出发里程(KM)'), + React.createElement('th', { style: styles.th }, '接收里程(KM)'), + React.createElement('th', { style: styles.th }, '出发氢量'), + React.createElement('th', { style: styles.th }, '接收氢量'), + React.createElement('th', { style: styles.th }, '出发电量'), + React.createElement('th', { style: styles.th }, '接收电量(kWh)') + ) + ), + React.createElement('tbody', {}, + lines.map(function (line, idx) { + var isEvenRow = idx % 2 === 1; + return React.createElement('tr', { key: idx, style: isEvenRow ? styles.trStripe : styles.trOdd }, + React.createElement('td', { style: styles.td }, line.plateNo || '-'), + React.createElement('td', { style: styles.td }, line.vehicleType || '-'), + React.createElement('td', { style: styles.td }, line.brand || '-'), + React.createElement('td', { style: styles.td }, line.model || '-'), + React.createElement('td', { style: styles.td }, line.departParking || '-'), + React.createElement('td', { style: styles.td }, line.departMileage != null ? line.departMileage : '-'), + React.createElement('td', { style: styles.td }, line.receiveMileage != null ? line.receiveMileage : '-'), + React.createElement('td', { style: styles.td }, formatH2(line.departH2, line.departH2Unit)), + React.createElement('td', { style: styles.td }, formatH2(line.receiveH2, line.departH2Unit)), + React.createElement('td', { style: styles.td }, line.departElec != null ? line.departElec + ' kWh' : '-'), + React.createElement('td', { style: styles.td }, line.receiveElec != null ? line.receiveElec + ' kWh' : '-') + ); + }) + ) + ) + ) + ), + React.createElement('div', { style: styles.modalFooter }, + React.createElement('button', { style: Object.assign({}, styles.btn, styles.btnDefault), onClick: function () { setViewRecord(null); setCurrentView('list'); } }, '返回') + ) + ); + } + + function validateEditForm() { + if (!formData.transferDate) { + setToastMessage('请选择调拨日期'); + setTimeout(function () { setToastMessage(''); }, 2000); + return false; + } + if (!formData.departProvince || !formData.departCity) { + setToastMessage('请选择出发区域'); + setTimeout(function () { setToastMessage(''); }, 2000); + return false; + } + if (!formData.transferProvince || !formData.transferCity) { + setToastMessage('请选择接收区域'); + setTimeout(function () { setToastMessage(''); }, 2000); + return false; + } + if (!formData.transferMethod) { + setToastMessage('请选择调拨方式'); + setTimeout(function () { setToastMessage(''); }, 2000); + return false; + } + if (!formData.transferReason || !formData.transferReason.trim()) { + setToastMessage('请填写调拨原因'); + setTimeout(function () { setToastMessage(''); }, 2000); + return false; + } + if (!formData.carrierName || !formData.carrierName.trim()) { + setToastMessage('请填写运输方名称'); + setTimeout(function () { setToastMessage(''); }, 2000); + return false; + } + if (!formData.carrierContact || !formData.carrierContact.trim()) { + setToastMessage('请填写运输方联系方式'); + setTimeout(function () { setToastMessage(''); }, 2000); + return false; + } + if (!formData.receiveDate) { + setToastMessage('请选择接收日期'); + setTimeout(function () { setToastMessage(''); }, 2000); + return false; + } + var validLines = editFormLines.filter(function (row) { return row.plateNo; }); + if (validLines.length === 0) { + setToastMessage('请至少保留一行并选择车牌号'); + setTimeout(function () { setToastMessage(''); }, 2000); + return false; + } + for (var i = 0; i < editFormLines.length; i++) { + var row = editFormLines[i]; + if (!row.plateNo) continue; + var rowNum = i + 1; + if (row.departMileage === '' || row.departMileage === undefined || row.departMileage === null) { + setToastMessage('请填写第' + rowNum + '行出发里程(KM)'); + setTimeout(function () { setToastMessage(''); }, 2000); + return false; + } + if (row.departH2 === '' || row.departH2 === undefined || row.departH2 === null) { + setToastMessage('请填写第' + rowNum + '行出发氢量'); + setTimeout(function () { setToastMessage(''); }, 2000); + return false; + } + if (row.departElec === '' || row.departElec === undefined || row.departElec === null) { + setToastMessage('请填写第' + rowNum + '行出发电量'); + setTimeout(function () { setToastMessage(''); }, 2000); + return false; + } + if (row.receiveH2 === '' || row.receiveH2 === undefined || row.receiveH2 === null) { + setToastMessage('请填写第' + rowNum + '行接收氢量'); + setTimeout(function () { setToastMessage(''); }, 2000); + return false; + } + if (row.receiveElec === '' || row.receiveElec === undefined || row.receiveElec === null) { + setToastMessage('请填写第' + rowNum + '行接收电量'); + setTimeout(function () { setToastMessage(''); }, 2000); + return false; + } + } + return true; + } + + function handleEditSave() { + if (!editRecord) return; + if (!validateEditForm()) return; + var dp = provinceList.find(function (x) { return x.code === formData.departProvince; }); + var dc = (cityMap[formData.departProvince] || []).find(function (x) { return x.code === formData.departCity; }); + var tp = provinceList.find(function (x) { return x.code === formData.transferProvince; }); + var tc = (cityMap[formData.transferProvince] || []).find(function (x) { return x.code === formData.transferCity; }); + var validRows = editFormLines.filter(function (row) { return row.plateNo; }); + var lines = validRows.map(function (row) { + var existing = (editRecord.lines || []).find(function (l) { return l.plateNo === row.plateNo; }); + return { + plateNo: row.plateNo, + vehicleType: row.vehicleType || '-', + brand: row.brand || '-', + model: row.model || '-', + departParking: row.departParking || '-', + departMileage: row.departMileage != null && row.departMileage !== '' ? Number(row.departMileage) : null, + departH2: row.departH2 != null && row.departH2 !== '' ? row.departH2 : '', + departH2Unit: row.departH2Unit || '', + departElec: row.departElec != null && row.departElec !== '' ? Number(row.departElec) : null, + receiveMileage: row.receiveMileage !== '' && row.receiveMileage !== undefined ? Number(row.receiveMileage) : (existing && existing.receiveMileage), + receiveH2: row.receiveH2 != null && row.receiveH2 !== '' ? row.receiveH2 : (existing && existing.receiveH2), + receiveElec: row.receiveElec !== '' && row.receiveElec !== undefined ? Number(row.receiveElec) : (existing && existing.receiveElec), + receiveParking: existing && existing.receiveParking, + receiverName: existing && existing.receiverName + }; + }); + var updated = { + id: editRecord.id, + transferDate: formData.transferDate, + operatorId: editRecord.operatorId, + operatorName: editRecord.operatorName, + departProvince: formData.departProvince, + departCity: formData.departCity, + departLabel: (dp ? dp.name : '') + '-' + (dc ? dc.name : ''), + transferProvince: formData.transferProvince, + transferCity: formData.transferCity, + transferLabel: (tp ? tp.name : '') + '-' + (tc ? tc.name : ''), + transferMethod: formData.transferMethod, + transferReason: formData.transferReason || '', + carrierName: formData.carrierName || '', + carrierContact: formData.carrierContact || '', + approvalStatus: editRecord.approvalStatus, + hasReceiveRecord: editRecord.hasReceiveRecord, + receiveDate: formData.receiveDate, + receiverId: editRecord.receiverId, + receiverName: editRecord.receiverName, + createTime: editRecord.createTime, + lines: lines + }; + setMockTransferList(mockTransferList.map(function (r) { return r.id === editRecord.id ? updated : r; })); + setToastMessage('保存成功'); + setTimeout(function () { setToastMessage(''); }, 2000); + setEditRecord(null); + setCurrentView('list'); + setFormData(defaultForm); + setEditFormLines([]); + setEditPlateSelectOpenRowId(null); + setEditPlateSearchKeyword(''); + } + + function handleWithdraw(transfer) { + if (!transfer) return; + setMockTransferList(mockTransferList.map(function (r) { + if (r.id === transfer.id) { + var next = {}; + for (var k in r) next[k] = r[k]; + next.approvalStatus = 'withdrawn'; + return next; + } + return r; + })); + setWithdrawConfirmRecord(null); + setToastMessage('撤回成功'); + setTimeout(function () { setToastMessage(''); }, 2000); + } + + function setFormDataFromTransfer(transfer) { + if (!transfer || !transfer.lines) return; + var plateNos = transfer.lines.map(function (l) { return l.plateNo; }); + var selectedIds = mockStockVehicles.filter(function (v) { return plateNos.indexOf(v.plateNo) >= 0; }).map(function (v) { return v.id; }); + setFormData({ + transferDate: transfer.transferDate, + operatorId: transfer.operatorId, + operatorName: transfer.operatorName, + selectedVehicleIds: selectedIds, + transferMethod: transfer.transferMethod, + transferReason: transfer.transferReason || '', + departProvince: transfer.departProvince || '', + departCity: transfer.departCity || '', + transferProvince: transfer.transferProvince || '', + transferCity: transfer.transferCity || '', + carrierName: transfer.carrierName || '', + carrierContact: transfer.carrierContact || '' + }); + } + + function openEdit(transfer) { + if (!transfer || !transfer.lines) return; + setEditRecord(transfer); + setFormData({ + transferDate: transfer.transferDate, + receiveDate: transfer.receiveDate || '', + operatorId: transfer.operatorId, + operatorName: transfer.operatorName, + selectedVehicleIds: [], + transferMethod: transfer.transferMethod, + transferReason: transfer.transferReason || '', + departProvince: transfer.departProvince || '', + departCity: transfer.departCity || '', + transferProvince: transfer.transferProvince || '', + transferCity: transfer.transferCity || '', + carrierName: transfer.carrierName || '', + carrierContact: transfer.carrierContact || '' + }); + setEditFormLines(buildEditFormLinesFromRecord(transfer)); + setEditPlateSelectOpenRowId(null); + setEditPlateSearchKeyword(''); + setCurrentView('edit'); + } + + var t = ARCO_TOKEN; + var styles = { + page: { padding: t.spacing24, fontFamily: t.fontFamily, backgroundColor: t.fill, minHeight: '100vh' }, + breadcrumb: { marginBottom: t.spacing16, fontSize: t.fontSize14, color: t.neutral6, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }, + breadcrumbLeft: { display: 'flex', alignItems: 'center' }, + breadcrumbLink: { color: t.link, textDecoration: 'none', marginRight: t.spacing8 }, + breadcrumbCurrent: { color: t.neutral8 }, + breadcrumbRight: { display: 'flex', alignItems: 'center' }, + requirementLink: { color: t.link, textDecoration: 'none', fontSize: t.fontSize14, cursor: 'pointer' }, + modalCloseBtn: { position: 'absolute', right: t.spacing16, top: t.spacing16, width: '24px', height: '24px', display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', borderRadius: t.radiusMedium, backgroundColor: 'transparent', border: 'none', color: t.neutral6, fontSize: '18px', lineHeight: '1', padding: 0 }, + requirementSection: { marginBottom: t.spacing16 }, + requirementSectionTitle: { fontSize: t.fontSize16, fontWeight: 600, color: t.neutral8, marginBottom: t.spacing8 }, + requirementItem: { marginBottom: t.spacing8, paddingLeft: t.spacing16, fontSize: t.fontSize14, color: t.neutral7 }, + requirementSubItem: { marginBottom: t.spacing4, paddingLeft: t.spacing24, fontSize: t.fontSize14, color: t.neutral7 }, + card: { backgroundColor: t.neutral1, borderRadius: t.radiusLarge, boxShadow: t.shadowLight, marginBottom: t.spacing16, padding: t.spacing24 + ' 20px' }, + searchPanel: { paddingBottom: t.spacing24, marginBottom: t.spacing24, borderBottom: '1px solid ' + t.neutral4 }, + searchPanelInner: { display: 'flex', alignItems: 'flex-start', gap: t.spacing16 }, + searchGridWrap: { flex: 1, minWidth: 0 }, + searchGrid: { display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: t.spacing16 + ' ' + t.spacing24 }, + searchField: { display: 'flex', flexDirection: 'row', alignItems: 'center', gap: '20px', minWidth: 0 }, + searchFieldControl: { flex: 1, minWidth: 0, overflow: 'hidden' }, + searchFieldLabel: { fontSize: t.fontSize14, color: t.neutral8, whiteSpace: 'nowrap', flexShrink: 0 }, + searchDivider: { width: '1px', backgroundColor: t.neutral4, alignSelf: 'stretch', flexShrink: 0 }, + searchActions: { display: 'flex', flexDirection: 'column', gap: t.spacing8, justifyContent: 'center', flexShrink: 0 }, + filterGrid: { display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: t.spacing16 }, + filterItem: { display: 'flex', alignItems: 'center', gap: t.spacing8, flexWrap: 'wrap' }, + filterItemFull: { gridColumn: '1 / -1', display: 'flex', justifyContent: 'flex-end', gap: t.spacing8 }, + label: { marginRight: t.spacing8, fontSize: t.fontSize14, color: t.neutral8, whiteSpace: 'nowrap' }, + filterLabel: { marginRight: t.spacing8, fontSize: t.fontSize14, color: t.neutral8, whiteSpace: 'nowrap', minWidth: '80px', textAlign: 'right' }, + input: { padding: '0 10px', height: '32px', width: '100%', minWidth: '140px', borderRadius: '2px', border: '1px solid ' + t.border, fontSize: t.fontSize14, fontFamily: t.fontFamily, boxSizing: 'border-box', outline: 'none', transition: 'border-color 0.2s, box-shadow 0.2s', textAlign: 'left' }, + select: { padding: '0 32px 0 12px', height: '32px', minWidth: '120px', borderRadius: '2px', border: '1px solid ' + t.border, fontSize: t.fontSize14, fontFamily: t.fontFamily, color: t.neutral8, backgroundColor: t.neutral1, cursor: 'pointer', outline: 'none', appearance: 'none', WebkitAppearance: 'none', backgroundImage: 'url("data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' width=\'12\' height=\'12\' viewBox=\'0 0 48 48\'%3E%3Cpath fill=\'%2386909C\' d=\'M24 32l-12-12h24z\'/%3E%3C/svg%3E")', backgroundRepeat: 'no-repeat', backgroundPosition: 'right 20px center', transition: 'border-color 0.2s, box-shadow 0.2s', textAlign: 'left' }, + btn: { padding: t.spacing8 + ' ' + t.spacing16, borderRadius: t.radiusMedium, cursor: 'pointer', fontSize: t.fontSize14, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: '6px' }, + btnFixed: { height: '32px', padding: '0 16px' }, + btnPrimary: { backgroundColor: t.primary, color: t.neutral1, border: 'none' }, + btnDefault: { backgroundColor: t.neutral1, color: t.neutral8, border: '1px solid ' + t.border }, + btnOutline: { backgroundColor: t.neutral1, color: t.primary, border: '1px solid ' + t.primary }, + toolbar: { display: 'flex', justifyContent: 'flex-end', marginBottom: t.spacing16 }, + tabs: { display: 'flex', borderBottom: '1px solid ' + t.border, marginBottom: t.spacing16 }, + tab: { padding: '10px 16px', cursor: 'pointer', fontSize: t.fontSize14, color: t.neutral7 }, + tabActive: { color: t.primary, borderBottom: '2px solid ' + t.primary, marginBottom: '-1px' }, + tableWrap: { overflowX: 'auto', border: '1px solid ' + t.neutral4, borderRadius: t.radiusMedium }, + tableWrapAddPage: { overflow: 'visible', border: '1px solid ' + t.neutral4, borderRadius: t.radiusMedium }, + table: { width: '100%', borderCollapse: 'collapse', fontSize: t.fontSize14 }, + th: { textAlign: 'left', padding: '0 16px', height: '40px', boxSizing: 'border-box', backgroundColor: t.fillSecondary, borderBottom: '1px solid ' + t.neutral4, fontWeight: 600, color: t.neutral8, verticalAlign: 'middle' }, + thNowrap: { whiteSpace: 'nowrap' }, + thStickyRight: { position: 'sticky', right: 0, backgroundColor: t.fillSecondary, boxShadow: '-2px 0 4px rgba(0,0,0,0.06)', zIndex: 1 }, + tdStickyRight: { position: 'sticky', right: 0, backgroundColor: t.neutral1, boxShadow: '-2px 0 4px rgba(0,0,0,0.06)', zIndex: 1 }, + tdStickyRightStripe: { position: 'sticky', right: 0, backgroundColor: t.fillSecondary, boxShadow: '-2px 0 4px rgba(0,0,0,0.06)', zIndex: 1 }, + td: { padding: '0 16px', height: '42px', boxSizing: 'border-box', borderBottom: '1px solid ' + t.neutral4, color: t.neutral8, verticalAlign: 'middle' }, + tdNowrap: { whiteSpace: 'nowrap' }, + trStripe: { backgroundColor: t.fillSecondary }, + trOdd: { backgroundColor: t.neutral1 }, + actionLink: { color: t.link, cursor: 'pointer', marginRight: t.spacing12, fontSize: t.fontSize14 }, + pagination: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '16px', borderTop: '1px solid ' + t.neutral4 }, + paginationLeft: { display: 'flex', alignItems: 'center', gap: '8px', fontSize: t.fontSize14, color: t.neutral7 }, + paginationRight: { display: 'flex', alignItems: 'center', gap: '8px' }, + paginationSelect: { padding: '4px 8px', height: '28px', borderRadius: '2px', border: '1px solid ' + t.border, fontSize: t.fontSize14, fontFamily: t.fontFamily }, + paginationBtn: { minWidth: '28px', height: '28px', padding: '0 8px', borderRadius: '2px', border: '1px solid ' + t.border, backgroundColor: t.neutral1, cursor: 'pointer', fontSize: t.fontSize14 }, + paginationBtnActive: { backgroundColor: t.primary, color: t.neutral1, borderColor: t.primary }, + paginationBtnDisabled: { opacity: 0.5, cursor: 'not-allowed' }, + cascaderWrap: { position: 'relative', width: '100%', minWidth: 0 }, + cascaderInput: { display: 'flex', alignItems: 'center', justifyContent: 'flex-start', width: '100%', height: '32px', padding: '0 32px 0 12px', borderRadius: '2px', border: '1px solid ' + t.border, cursor: 'pointer', fontSize: t.fontSize14, fontFamily: t.fontFamily, color: t.neutral8, backgroundColor: t.neutral1, outline: 'none', transition: 'border-color 0.2s, box-shadow 0.2s', boxSizing: 'border-box', textAlign: 'left', backgroundImage: 'url("data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' width=\'12\' height=\'12\' viewBox=\'0 0 48 48\'%3E%3Cpath fill=\'%2386909C\' d=\'M24 32l-12-12h24z\'/%3E%3C/svg%3E")', backgroundRepeat: 'no-repeat', backgroundPosition: 'right 20px center' }, + cascaderPanel: { position: 'absolute', left: 0, top: '100%', marginTop: '4px', backgroundColor: t.neutral1, border: '1px solid ' + t.border, borderRadius: '2px', boxShadow: '0 4px 10px rgba(0,0,0,0.08)', zIndex: 1050, display: 'flex', minWidth: '240px', maxHeight: '256px' }, + cascaderColumn: { minWidth: '100px', maxHeight: '256px', overflowY: 'auto', borderRight: '1px solid ' + t.neutral4 }, + cascaderColumnLast: { borderRight: 'none' }, + cascaderOption: { padding: '8px 12px', fontSize: t.fontSize14, fontFamily: t.fontFamily, color: t.neutral8, cursor: 'pointer', transition: 'background-color 0.1s' }, + cascaderOptionHover: { backgroundColor: t.neutral2 }, + autocompleteWrap: { position: 'relative', minWidth: '140px' }, + autocompleteWrapFull: { width: '100%', minWidth: 0, maxWidth: '100%' }, + autocompleteTrigger: { padding: '0 32px 0 12px', height: '32px', minWidth: '140px', borderRadius: '2px', border: '1px solid ' + t.border, fontSize: t.fontSize14, fontFamily: t.fontFamily, color: t.neutral8, backgroundColor: t.neutral1, boxSizing: 'border-box', outline: 'none', transition: 'border-color 0.2s, box-shadow 0.2s', textAlign: 'left', backgroundImage: 'url("data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' width=\'12\' height=\'12\' viewBox=\'0 0 48 48\'%3E%3Cpath fill=\'%2386909C\' d=\'M24 32l-12-12h24z\'/%3E%3C/svg%3E")', backgroundRepeat: 'no-repeat', backgroundPosition: 'right 20px center' }, + autocompleteTriggerFull: { width: '100%', minWidth: 0, maxWidth: '100%' }, + autocompletePanel: { position: 'absolute', left: 0, top: '100%', marginTop: '4px', backgroundColor: t.neutral1, border: '1px solid ' + t.border, borderRadius: '2px', boxShadow: '0 4px 10px rgba(0,0,0,0.08)', zIndex: 100, minWidth: '100%', maxHeight: '256px', overflowY: 'auto', padding: '4px 0' }, + autocompletePanelOverflow: { zIndex: 1050 }, + autocompleteOption: { padding: '8px 12px', fontSize: t.fontSize14, fontFamily: t.fontFamily, color: t.neutral8, cursor: 'pointer', transition: 'background-color 0.1s' }, + dateRangeWrap: { position: 'relative', width: '100%' }, + dateRangeTrigger: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', width: '100%', height: '32px', padding: '0 10px 0 12px', borderRadius: '2px', border: '1px solid ' + t.border, fontSize: t.fontSize14, fontFamily: t.fontFamily, color: t.neutral8, backgroundColor: t.neutral1, cursor: 'pointer', outline: 'none', transition: 'border-color 0.2s, box-shadow 0.2s', boxSizing: 'border-box', textAlign: 'left' }, + dateRangeTriggerFocus: { borderColor: t.primary, boxShadow: '0 0 0 2px rgba(22,93,255,0.2)' }, + dateRangeDropdown: { position: 'absolute', left: 0, top: '100%', marginTop: '4px', backgroundColor: t.neutral1, border: '1px solid ' + t.border, borderRadius: '4px', boxShadow: '0 4px 10px rgba(0,0,0,0.12)', zIndex: 1050, padding: '12px 16px', minWidth: '480px' }, + dateRangeCalendars: { display: 'flex', gap: '24px' }, + dateRangeCalendar: { minWidth: '200px' }, + dateRangeCalendarHeader: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '12px' }, + dateRangeCalendarNav: { display: 'flex', alignItems: 'center', gap: '4px' }, + dateRangeCalendarNavBtn: { width: '28px', height: '28px', display: 'flex', alignItems: 'center', justifyContent: 'center', border: 'none', borderRadius: '2px', backgroundColor: 'transparent', color: t.neutral7, cursor: 'pointer', fontSize: '14px' }, + dateRangeCalendarTitle: { fontSize: t.fontSize14, fontWeight: 600, color: t.neutral8 }, + dateRangeCalendarWeekdays: { display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: '2px', marginBottom: '4px', fontSize: '12px', color: t.neutral6 }, + dateRangeCalendarGrid: { display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: '2px' }, + dateRangeDay: { width: '28px', height: '28px', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '13px', color: t.neutral8, cursor: 'pointer', borderRadius: '2px' }, + dateRangeDayOther: { color: t.neutral5 }, + dateRangeDaySelected: { backgroundColor: t.primary, color: t.neutral1 }, + dateRangeDayInRange: { backgroundColor: 'rgba(22,93,255,0.15)', color: t.primary }, + dateRangeDayToday: { position: 'relative', fontWeight: 600 }, + formRow: { marginBottom: t.spacing16 }, + formLabel: { display: 'block', marginBottom: '6px', fontSize: t.fontSize14, color: t.neutral8 }, + formLabelReq: { color: t.danger, marginLeft: '2px' }, + formGrid: { display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: t.spacing24, maxWidth: '100%' }, + formGridFull: { gridColumn: '1 / -1' }, + textarea: { width: '100%', minHeight: '80px', padding: '8px 10px', borderRadius: '2px', border: '1px solid ' + t.border, fontSize: t.fontSize14, fontFamily: t.fontFamily, resize: 'vertical', boxSizing: 'border-box' }, + radioGroup: { display: 'flex', gap: t.spacing16, alignItems: 'center' }, + radioItem: { display: 'flex', alignItems: 'center', cursor: 'pointer' }, + btnGroup: { display: 'inline-flex', border: '1px solid ' + t.border, borderRadius: t.radiusMedium, overflow: 'hidden' }, + btnGroupItem: { padding: '0 16px', height: '32px', border: 'none', borderRight: '1px solid ' + t.border, cursor: 'pointer', fontSize: t.fontSize14, backgroundColor: t.neutral1, color: t.neutral7 }, + btnGroupItemLast: { borderRight: 'none' }, + btnGroupItemActive: { backgroundColor: t.primary, color: t.neutral1 }, + transferBox: { display: 'flex', alignItems: 'stretch', gap: t.spacing12, minHeight: '240px', border: '1px solid ' + t.border, borderRadius: t.radiusMedium, padding: t.spacing12 }, + transferColumn: { flex: 1, border: '1px solid ' + t.neutral4, borderRadius: t.radiusSmall, overflow: 'hidden', display: 'flex', flexDirection: 'column' }, + transferColumnHead: { padding: '8px 12px', backgroundColor: t.fillSecondary, fontSize: t.fontSize14, fontWeight: 600 }, + transferColumnList: { flex: 1, overflowY: 'auto', padding: '4px' }, + transferItem: { padding: '8px 12px', fontSize: t.fontSize14, cursor: 'pointer', borderRadius: t.radiusSmall }, + transferItemSelected: { backgroundColor: t.primary, color: t.neutral1 }, + transferBtns: { display: 'flex', flexDirection: 'column', justifyContent: 'center', gap: t.spacing8 }, + stepBar: { display: 'flex', alignItems: 'center', marginBottom: t.spacing24, padding: t.spacing16, backgroundColor: t.fillSecondary, borderRadius: t.radiusMedium }, + stepItem: { display: 'flex', alignItems: 'center', flex: 1 }, + stepDot: { width: '24px', height: '24px', borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '12px', marginRight: '8px' }, + stepDotDone: { backgroundColor: t.success, color: t.neutral1 }, + stepDotCurrent: { backgroundColor: t.primary, color: t.neutral1 }, + stepDotPending: { backgroundColor: t.neutral4, color: t.neutral6 }, + modalMask: { position: 'fixed', left: 0, top: 0, right: 0, bottom: 0, backgroundColor: 'rgba(0,0,0,0.45)', zIndex: 1000, display: 'flex', alignItems: 'center', justifyContent: 'center' }, + modalBox: { backgroundColor: t.neutral1, borderRadius: t.radiusLarge, maxWidth: '90%', maxHeight: '90%', overflow: 'auto', padding: t.spacing24, minWidth: '600px', position: 'relative' }, + modalTitle: { fontSize: t.fontSize16, fontWeight: 600, marginBottom: t.spacing16 }, + modalFooter: { marginTop: t.spacing24, display: 'flex', justifyContent: 'flex-end', gap: t.spacing8 }, + toast: { position: 'fixed', top: t.spacing24, left: '50%', transform: 'translateX(-50%)', backgroundColor: 'rgba(0,0,0,0.75)', color: t.neutral1, padding: '10px 20px', borderRadius: t.radiusMedium, zIndex: 2000, fontSize: t.fontSize14 } + }; + + // 列表运输方名称、运输方联系方式列样式(支持长文本换行) + var carrierInfoTdStyle = Object.assign({}, styles.td, { whiteSpace: 'normal', verticalAlign: 'middle', minWidth: '100px', wordBreak: 'break-word', overflowWrap: 'break-word' }); + + // 穿梭框:左侧可选车辆 = 库存中未在 formData.selectedVehicleIds 的 + var leftVehicles = mockStockVehicles.filter(function (v) { return formData.selectedVehicleIds.indexOf(v.id) < 0; }); + var rightVehicles = mockStockVehicles.filter(function (v) { return formData.selectedVehicleIds.indexOf(v.id) >= 0; }); + + function renderFilterSelect(options, value, onChange, open, setOpen, placeholder, filterFn, fullWidth) { + var list = filterFn ? options.filter(filterFn) : options; + var wrapStyle = fullWidth ? Object.assign({}, styles.autocompleteWrap, styles.autocompleteWrapFull) : styles.autocompleteWrap; + var triggerBase = fullWidth ? Object.assign({}, styles.autocompleteTrigger, styles.autocompleteTriggerFull) : styles.autocompleteTrigger; + var triggerStyle = Object.assign({}, triggerBase, open ? { borderColor: t.primary, boxShadow: '0 0 0 2px rgba(22,93,255,0.2)' } : {}); + return React.createElement('div', { style: wrapStyle }, + React.createElement('input', { + style: triggerStyle, + className: 'arco-select-trigger', + placeholder: placeholder, + value: value, + onChange: function (e) { onChange(e.target.value); }, + onFocus: function () { setOpen(true); }, + onBlur: function () { setTimeout(function () { setOpen(false); }, 200); } + }), + open && list.length > 0 && React.createElement(React.Fragment, {}, + React.createElement('div', { style: { position: 'fixed', left: 0, top: 0, right: 0, bottom: 0, zIndex: 99 }, onClick: function () { setOpen(false); } }), + React.createElement('div', { style: styles.autocompletePanel, onClick: function (e) { e.stopPropagation(); } }, + list.map(function (opt) { + var label = typeof opt === 'string' ? opt : (opt.label || opt.name || opt.plateNo); + var val = typeof opt === 'string' ? opt : (opt.value || opt.id || opt.plateNo); + return React.createElement('div', { + key: val, + style: styles.autocompleteOption, + className: 'arco-select-option', + onMouseDown: function (e) { e.preventDefault(); }, + onMouseEnter: function (e) { e.currentTarget.style.backgroundColor = t.neutral2; }, + onMouseLeave: function (e) { e.currentTarget.style.backgroundColor = 'transparent'; }, + onClick: function () { onChange(typeof opt === 'string' ? opt : (opt.name || opt.plateNo || val)); setOpen(false); } + }, label); + }) + ) + ) + ); + } + + function getCalendarWeeks(year, month) { + var d = new Date(year, month - 1, 1); + var firstDay = d.getDay(); + var daysInMonth = new Date(year, month, 0).getDate(); + var weeks = []; + var week = []; + for (var i = 0; i < firstDay; i++) { + var prev = new Date(year, month - 1, -firstDay + i + 1); + week.push({ date: prev.getDate(), yyyymmdd: prev.getFullYear() + '-' + String(prev.getMonth() + 1).padStart(2, '0') + '-' + String(prev.getDate()).padStart(2, '0'), isCurrentMonth: false }); + } + for (var j = 1; j <= daysInMonth; j++) { + week.push({ date: j, yyyymmdd: year + '-' + String(month).padStart(2, '0') + '-' + String(j).padStart(2, '0'), isCurrentMonth: true }); + if (week.length === 7) { weeks.push(week); week = []; } + } + if (week.length) { + var nextMonth = month === 12 ? 1 : month + 1; + var nextYear = month === 12 ? year + 1 : year; + for (var k = week.length; k < 7; k++) { + var dayNum = k - week.length + 1; + week.push({ date: dayNum, yyyymmdd: nextYear + '-' + String(nextMonth).padStart(2, '0') + '-' + String(dayNum).padStart(2, '0'), isCurrentMonth: false }); + } + weeks.push(week); + } + return weeks; + } + function renderDateRangePicker(startValue, endValue, setStart, setEnd, open, setOpen, viewLeft, viewRight, setViewLeft, setViewRight) { + var displayText = (startValue && endValue) ? (startValue + ' - ' + endValue) : (startValue ? (startValue + ' - 结束日期') : '开始日期 - 结束日期'); + var triggerStyle = Object.assign({}, styles.dateRangeTrigger, open ? styles.dateRangeTriggerFocus : {}); + var today = new Date(); + var todayStr = today.getFullYear() + '-' + String(today.getMonth() + 1).padStart(2, '0') + '-' + String(today.getDate()).padStart(2, '0'); + var leftYM = viewLeft || getInitialViewMonth(startValue); + var rightYM = viewRight || (function () { + var p = leftYM.split('-'); + var y = parseInt(p[0], 10); + var m = parseInt(p[1], 10); + if (m === 12) return (y + 1) + '-01'; + return y + '-' + String(m + 1).padStart(2, '0'); + }()); + var leftParts = leftYM.split('-'); + var rightParts = rightYM.split('-'); + var leftWeeks = getCalendarWeeks(parseInt(leftParts[0], 10), parseInt(leftParts[1], 10)); + var rightWeeks = getCalendarWeeks(parseInt(rightParts[0], 10), parseInt(rightParts[1], 10)); + function goPrevMonth(isLeftCalendar) { + var ym = isLeftCalendar ? leftYM : rightYM; + var p = ym.split('-'); + var y = parseInt(p[0], 10); + var m = parseInt(p[1], 10); + if (m === 1) { y--; m = 12; } else m--; + var next = y + '-' + String(m).padStart(2, '0'); + if (isLeftCalendar) { setViewLeft(next); setViewRight(m === 12 ? (y + 1) + '-01' : y + '-' + String(m + 1).padStart(2, '0')); } + else { setViewRight(next); setViewLeft(m === 1 ? (y - 1) + '-12' : y + '-' + String(m - 1).padStart(2, '0')); } + } + function goNextMonth(isLeftCalendar) { + var ym = isLeftCalendar ? leftYM : rightYM; + var p = ym.split('-'); + var y = parseInt(p[0], 10); + var m = parseInt(p[1], 10); + if (m === 12) { y++; m = 1; } else m++; + var next = y + '-' + String(m).padStart(2, '0'); + if (isLeftCalendar) { setViewLeft(next); setViewRight(m === 12 ? (y + 1) + '-01' : y + '-' + String(m + 1).padStart(2, '0')); } + else { setViewRight(next); setViewLeft(m === 1 ? (y - 1) + '-12' : y + '-' + String(m - 1).padStart(2, '0')); } + } + function onSelectDate(yyyymmdd) { + if (!startValue || (startValue && endValue)) { + setStart(yyyymmdd); + setEnd(''); + } else { + if (yyyymmdd < startValue) { setEnd(startValue); setStart(yyyymmdd); } + else setEnd(yyyymmdd); + } + } + function renderOneCalendar(weeks, isLeft) { + return React.createElement('div', { style: styles.dateRangeCalendar }, + React.createElement('div', { style: styles.dateRangeCalendarHeader }, + React.createElement('div', { style: styles.dateRangeCalendarNav }, + React.createElement('button', { type: 'button', style: styles.dateRangeCalendarNavBtn, onClick: function () { goPrevMonth(isLeft); } }, '\u00AB'), + React.createElement('button', { type: 'button', style: styles.dateRangeCalendarNavBtn, onClick: function () { goPrevMonth(isLeft); } }, '\u2039') + ), + React.createElement('span', { style: styles.dateRangeCalendarTitle }, (isLeft ? leftYM : rightYM).replace('-', '\u5E74') + '\u6708'), + React.createElement('div', { style: styles.dateRangeCalendarNav }, + React.createElement('button', { type: 'button', style: styles.dateRangeCalendarNavBtn, onClick: function () { goNextMonth(isLeft); } }, '\u203A'), + React.createElement('button', { type: 'button', style: styles.dateRangeCalendarNavBtn, onClick: function () { goNextMonth(isLeft); } }, '\u00BB') + ) + ), + React.createElement('div', { style: styles.dateRangeCalendarWeekdays }, ['日', '一', '二', '三', '四', '五', '六'].map(function (w) { return React.createElement('div', { key: w }, w); })), + React.createElement('div', { style: styles.dateRangeCalendarGrid }, + weeks.map(function (week, wi) { + return week.map(function (day, di) { + var isSelected = day.yyyymmdd === startValue || day.yyyymmdd === endValue; + var isInRange = startValue && endValue && day.yyyymmdd >= startValue && day.yyyymmdd <= endValue; + var isToday = day.yyyymmdd === todayStr; + var dayStyle = Object.assign({}, styles.dateRangeDay, !day.isCurrentMonth ? styles.dateRangeDayOther : {}, isSelected ? styles.dateRangeDaySelected : {}, isInRange && !isSelected ? styles.dateRangeDayInRange : {}, isToday ? styles.dateRangeDayToday : {}); + return React.createElement('div', { + key: wi + '-' + di, + style: dayStyle, + onClick: function () { onSelectDate(day.yyyymmdd); } + }, day.date); + }); + }) + ) + ); + } + return React.createElement('div', { style: styles.dateRangeWrap }, + React.createElement('div', { + style: triggerStyle, + onClick: function () { + if (!open) { + setViewLeft(getInitialViewMonth(startValue)); + var l = getInitialViewMonth(startValue); + var p = l.split('-'); + var y = parseInt(p[0], 10); + var m = parseInt(p[1], 10); + setViewRight(m === 12 ? (y + 1) + '-01' : y + '-' + String(m + 1).padStart(2, '0')); + } + setOpen(!open); + } + }, React.createElement('span', { style: { color: (startValue || endValue) ? t.neutral8 : t.neutral6 } }, displayText), React.createElement('span', { style: { width: '16px', height: '16px', flexShrink: 0, marginLeft: '8px', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', color: t.neutral6 } }, React.createElement('svg', { width: 16, height: 16, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 2 }, React.createElement('rect', { x: 3, y: 4, width: 18, height: 18, rx: 2 }), React.createElement('line', { x1: 16, y1: 2, x2: 16, y2: 6 }), React.createElement('line', { x1: 8, y1: 2, x2: 8, y2: 6 }), React.createElement('line', { x1: 3, y1: 10, x2: 21, y2: 10 })))), + open && React.createElement(React.Fragment, {}, + React.createElement('div', { style: { position: 'fixed', left: 0, top: 0, right: 0, bottom: 0, zIndex: 1049 }, onClick: function () { setOpen(false); } }), + React.createElement('div', { style: styles.dateRangeDropdown, onClick: function (e) { e.stopPropagation(); } }, + React.createElement('div', { style: styles.dateRangeCalendars }, + renderOneCalendar(leftWeeks, true), + renderOneCalendar(rightWeeks, false) + ) + ) + ) + ); + } + + function renderCascader(provinceCode, cityCode, setProvince, setCity, open, setOpen) { + var p = provinceList.find(function (x) { return x.code === provinceCode; }); + var c = (cityMap[provinceCode] || []).find(function (x) { return x.code === cityCode; }); + var display = (p ? p.name : '') + (p && c ? ' - ' : '') + (c ? c.name : ''); + var cascaderTriggerStyle = Object.assign({}, styles.cascaderInput, open ? { borderColor: t.primary, boxShadow: '0 0 0 2px rgba(22,93,255,0.2)' } : {}); + return React.createElement('div', { style: styles.cascaderWrap }, + React.createElement('div', { + style: cascaderTriggerStyle, + className: 'arco-select-trigger', + onClick: function () { setOpen(!open); } + }, React.createElement('span', { style: { color: (provinceCode && cityCode) ? t.neutral8 : t.neutral6, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, display || '请选择省-市')), + open && React.createElement(React.Fragment, {}, + React.createElement('div', { style: { position: 'fixed', left: 0, top: 0, right: 0, bottom: 0, zIndex: 1049 }, onClick: function () { setOpen(false); } }), + React.createElement('div', { style: styles.cascaderPanel, onClick: function (e) { e.stopPropagation(); } }, + React.createElement('div', { style: styles.cascaderColumn }, + provinceList.map(function (pr) { + return React.createElement('div', { + key: pr.code, + style: styles.cascaderOption, + className: 'arco-select-option', + onMouseEnter: function (e) { e.currentTarget.style.backgroundColor = t.neutral2; }, + onMouseLeave: function (e) { e.currentTarget.style.backgroundColor = 'transparent'; }, + onClick: function () { setProvince(pr.code); setCity(''); } + }, pr.name); + }) + ), + React.createElement('div', { style: Object.assign({}, styles.cascaderColumn, styles.cascaderColumnLast) }, + (cityMap[provinceCode] || []).map(function (ci) { + return React.createElement('div', { + key: ci.code, + style: styles.cascaderOption, + className: 'arco-select-option', + onMouseEnter: function (e) { e.currentTarget.style.backgroundColor = t.neutral2; }, + onMouseLeave: function (e) { e.currentTarget.style.backgroundColor = 'transparent'; }, + onClick: function () { setCity(ci.code); setOpen(false); } + }, ci.name); + }) + ) + ) + ) + ); + } + + function renderFormContent(readonly, isView) { + var disabled = readonly === true; + var grid = styles.formGrid; + return React.createElement('div', { style: grid }, + React.createElement('div', { style: styles.formRow }, + React.createElement('label', { style: styles.formLabel }, '调拨日期', !disabled && React.createElement('span', { style: styles.formLabelReq }, '*')), + disabled ? React.createElement('span', {}, formData.transferDate) : React.createElement('input', { style: styles.input, type: 'date', value: formData.transferDate, onChange: function (e) { handleFormChange('transferDate', e.target.value); } }) + ), + React.createElement('div', { style: styles.formRow }, + React.createElement('label', { style: styles.formLabel }, '操作人', !disabled && React.createElement('span', { style: styles.formLabelReq }, '*')), + disabled ? React.createElement('span', {}, formData.operatorName) : renderFilterSelect(mockOpsUsers, formData.operatorName, function (v) { handleFormChange('operatorName', v); var u = mockOpsUsers.find(function (x) { return x.name === v; }); if (u) handleFormChange('operatorId', u.id); }, formOperatorOpen, setFormOperatorOpen, '支持模糊搜索', function (u) { return !formData.operatorName || (u.name && u.name.indexOf(formData.operatorName) >= 0); }) + ), + React.createElement('div', { style: Object.assign({}, styles.formRow, styles.formGridFull) }, + React.createElement('label', { style: styles.formLabel }, '车牌号', !disabled && React.createElement('span', { style: styles.formLabelReq }, '*')), + disabled ? React.createElement('span', {}, (formData.selectedVehicleIds && mockStockVehicles.filter(function (v) { return formData.selectedVehicleIds.indexOf(v.id) >= 0; }).map(function (v) { return v.plateNo; }).join('、')) || '-') : React.createElement('div', { style: styles.transferBox }, + React.createElement('div', { style: styles.transferColumn }, + React.createElement('div', { style: styles.transferColumnHead }, '库存车辆'), + React.createElement('div', { style: styles.transferColumnList }, + leftVehicles.map(function (v) { + var selected = leftSelectedIds.indexOf(v.id) >= 0; + return React.createElement('div', { + key: v.id, + style: Object.assign({}, styles.transferItem, selected ? styles.transferItemSelected : {}), + onClick: function () { + if (selected) setLeftSelectedIds(leftSelectedIds.filter(function (id) { return id !== v.id; })); + else setLeftSelectedIds(leftSelectedIds.concat([v.id])); + } + }, v.plateNo); + }) + ) + ), + React.createElement('div', { style: styles.transferBtns }, + React.createElement('button', { style: Object.assign({}, styles.btn, styles.btnDefault), title: '添加', onClick: function () { + handleFormChange('selectedVehicleIds', (formData.selectedVehicleIds || []).concat(leftSelectedIds)); + setLeftSelectedIds([]); + } }, '→'), + React.createElement('button', { style: Object.assign({}, styles.btn, styles.btnDefault), title: '移除', onClick: function () { + handleFormChange('selectedVehicleIds', (formData.selectedVehicleIds || []).filter(function (id) { return rightSelectedIds.indexOf(id) < 0; })); + setRightSelectedIds([]); + } }, '←') + ), + React.createElement('div', { style: styles.transferColumn }, + React.createElement('div', { style: styles.transferColumnHead }, '已选车辆'), + React.createElement('div', { style: styles.transferColumnList }, + rightVehicles.map(function (v) { + var selected = rightSelectedIds.indexOf(v.id) >= 0; + return React.createElement('div', { + key: v.id, + style: Object.assign({}, styles.transferItem, selected ? styles.transferItemSelected : {}), + onClick: function () { + if (selected) setRightSelectedIds(rightSelectedIds.filter(function (id) { return id !== v.id; })); + else setRightSelectedIds(rightSelectedIds.concat([v.id])); + } + }, v.plateNo); + }) + ) + ) + ) + ), + React.createElement('div', { style: styles.formRow }, + React.createElement('label', { style: styles.formLabel }, '调拨方式', !disabled && React.createElement('span', { style: styles.formLabelReq }, '*')), + disabled ? React.createElement('span', {}, getMethodLabel(formData.transferMethod)) : React.createElement('div', { style: styles.radioGroup }, + TRANSFER_METHODS.map(function (m) { + return React.createElement('label', { key: m.value, style: styles.radioItem }, + React.createElement('input', { type: 'radio', name: 'transferMethod', value: m.value, checked: formData.transferMethod === m.value, onChange: function () { handleFormChange('transferMethod', m.value); } }), + React.createElement('span', { style: { marginLeft: '6px' } }, m.label) + ); + }) + ) + ), + React.createElement('div', { style: Object.assign({}, styles.formRow, styles.formGridFull) }, + React.createElement('label', { style: styles.formLabel }, '调拨原因', !disabled && React.createElement('span', { style: styles.formLabelReq }, '*')), + disabled ? React.createElement('span', {}, formData.transferReason || '-') : React.createElement('textarea', { style: styles.textarea, value: formData.transferReason, onChange: function (e) { handleFormChange('transferReason', e.target.value); }, placeholder: '请输入' }) + ), + React.createElement('div', { style: styles.formRow }, + React.createElement('label', { style: styles.formLabel }, '出发区域', !disabled && React.createElement('span', { style: styles.formLabelReq }, '*')), + disabled ? React.createElement('span', {}, (function () { + var r = viewRecord || editRecord; + if (r && r.departLabel) return r.departLabel; + if (formData.departProvince && formData.departCity) { + var p = provinceList.find(function (x) { return x.code === formData.departProvince; }); + var c = (cityMap[formData.departProvince] || []).find(function (x) { return x.code === formData.departCity; }); + return (p ? p.name : '') + '-' + (c ? c.name : ''); + } + return '-'; + }()) ) : renderCascader(formData.departProvince, formData.departCity, function (v) { setFormData(function (prev) { var n = {}; for (var k in prev) n[k] = prev[k]; n.departProvince = v; n.departCity = ''; return n; }); }, function (v) { setFormData(function (prev) { var n = {}; for (var k in prev) n[k] = prev[k]; n.departCity = v; return n; }); }, formDepartCascaderOpen, setFormDepartCascaderOpen) + ), + React.createElement('div', { style: styles.formRow }, + React.createElement('label', { style: styles.formLabel }, '接收区域', !disabled && React.createElement('span', { style: styles.formLabelReq }, '*')), + disabled ? React.createElement('span', {}, (function () { + var r = viewRecord || editRecord; + if (r && r.transferLabel) return r.transferLabel; + if (formData.transferProvince && formData.transferCity) { + var p = provinceList.find(function (x) { return x.code === formData.transferProvince; }); + var c = (cityMap[formData.transferProvince] || []).find(function (x) { return x.code === formData.transferCity; }); + return (p ? p.name : '') + '-' + (c ? c.name : ''); + } + return '-'; + }()) ) : renderCascader(formData.transferProvince, formData.transferCity, function (v) { setFormData(function (prev) { var n = {}; for (var k in prev) n[k] = prev[k]; n.transferProvince = v; n.transferCity = ''; return n; }); }, function (v) { setFormData(function (prev) { var n = {}; for (var k in prev) n[k] = prev[k]; n.transferCity = v; return n; }); }, formTransferCascaderOpen, setFormTransferCascaderOpen) + ), + React.createElement('div', { style: styles.formRow }, + React.createElement('label', { style: styles.formLabel }, '运输方名称', !disabled && React.createElement('span', { style: styles.formLabelReq }, '*')), + disabled ? React.createElement('span', {}, (viewRecord || editRecord || formData).carrierName || '-') : React.createElement('input', { style: styles.input, value: formData.carrierName, onChange: function (e) { handleFormChange('carrierName', e.target.value); }, placeholder: '请输入' }) + ), + React.createElement('div', { style: styles.formRow }, + React.createElement('label', { style: styles.formLabel }, '运输方联系方式', !disabled && React.createElement('span', { style: styles.formLabelReq }, '*')), + disabled ? React.createElement('span', {}, (viewRecord || editRecord || formData).carrierContact || '-') : React.createElement('input', { style: styles.input, value: formData.carrierContact, onChange: function (e) { handleFormChange('carrierContact', e.target.value); }, placeholder: '请输入' }) + ) + ); + } + + return ( + React.createElement('div', { style: styles.page }, + React.createElement('style', { + dangerouslySetInnerHTML: { + __html: '.arco-select-trigger:hover{border-color:#C9CDD4 !important;}' + + 'select:hover{border-color:#C9CDD4 !important;}' + + 'select:focus{border-color:#165DFF !important;box-shadow:0 0 0 2px rgba(22,93,255,0.2) !important;}' + + '.arco-select-option:hover{background-color:#F7F8FA !important;}' + } + }), + React.createElement('div', { style: styles.breadcrumb }, + React.createElement('div', { style: styles.breadcrumbLeft }, + React.createElement('a', { href: '#', style: styles.breadcrumbLink, onClick: function (e) { e.preventDefault(); } }, '运维管理'), + React.createElement('span', { style: { marginRight: '8px' } }, '/'), + React.createElement('a', { href: '#', style: styles.breadcrumbLink, onClick: function (e) { e.preventDefault(); } }, '车辆业务'), + React.createElement('span', { style: { marginRight: '8px' } }, '/'), + React.createElement('span', { style: styles.breadcrumbCurrent }, '调拨管理') + ), + React.createElement('div', { style: styles.breadcrumbRight }, + React.createElement('a', { href: '#', style: styles.requirementLink, onClick: function (e) { e.preventDefault(); setShowRequirementModal(true); } }, '查看需求说明') + ) + ), + + currentView === 'list' && React.createElement(React.Fragment, {}, + React.createElement('div', { style: styles.card }, + React.createElement('div', { style: styles.searchPanel }, + React.createElement('div', { style: styles.searchPanelInner }, + React.createElement('div', { style: styles.searchGridWrap }, + React.createElement('div', { style: styles.searchGrid }, + React.createElement('div', { style: styles.searchField }, + React.createElement('span', { style: styles.searchFieldLabel }, '调拨日期'), + React.createElement('div', { style: styles.searchFieldControl }, renderDateRangePicker(filterDateStart, filterDateEnd, setFilterDateStart, setFilterDateEnd, filterDateRangeOpen, setFilterDateRangeOpen, filterDateRangeViewLeft, filterDateRangeViewRight, setFilterDateRangeViewLeft, setFilterDateRangeViewRight)) + ), + React.createElement('div', { style: styles.searchField }, + React.createElement('span', { style: styles.searchFieldLabel }, '调拨人'), + React.createElement('div', { style: styles.searchFieldControl }, renderFilterSelect(mockOpsUsers.map(function (u) { return u.name; }), filterOperator, setFilterOperator, filterOperatorOpen, setFilterOperatorOpen, '模糊搜索', function (n) { return !filterOperator || n.indexOf(filterOperator) >= 0; }, true)) + ), + React.createElement('div', { style: styles.searchField }, + React.createElement('span', { style: styles.searchFieldLabel }, '接收日期'), + React.createElement('div', { style: styles.searchFieldControl }, renderDateRangePicker(filterReceiveDateStart, filterReceiveDateEnd, setFilterReceiveDateStart, setFilterReceiveDateEnd, filterReceiveDateRangeOpen, setFilterReceiveDateRangeOpen, filterReceiveDateRangeViewLeft, filterReceiveDateRangeViewRight, setFilterReceiveDateRangeViewLeft, setFilterReceiveDateRangeViewRight)) + ), + React.createElement('div', { style: styles.searchField }, + React.createElement('span', { style: styles.searchFieldLabel }, '接收人'), + React.createElement('div', { style: styles.searchFieldControl }, renderFilterSelect(mockOpsUsers.map(function (u) { return u.name; }), filterReceiver, setFilterReceiver, filterReceiverOpen, setFilterReceiverOpen, '模糊搜索', function (n) { return !filterReceiver || n.indexOf(filterReceiver) >= 0; }, true)) + ), + React.createElement('div', { style: styles.searchField }, + React.createElement('span', { style: styles.searchFieldLabel }, '车牌号'), + React.createElement('div', { style: styles.searchFieldControl }, renderFilterSelect(mockStockVehicles.map(function (v) { return v.plateNo; }), filterPlateNo, setFilterPlateNo, filterPlateOpen, setFilterPlateOpen, '模糊搜索', function (p) { return !filterPlateNo || p.indexOf(filterPlateNo) >= 0; }, true)) + ), + React.createElement('div', { style: styles.searchField }, + React.createElement('span', { style: styles.searchFieldLabel }, '出发区域'), + React.createElement('div', { style: styles.searchFieldControl }, renderCascader(filterDepartProvince, filterDepartCity, setFilterDepartProvince, setFilterDepartCity, filterCascaderDepartOpen, setFilterCascaderDepartOpen)) + ), + React.createElement('div', { style: styles.searchField }, + React.createElement('span', { style: styles.searchFieldLabel }, '调拨区域'), + React.createElement('div', { style: styles.searchFieldControl }, renderCascader(filterTransferProvince, filterTransferCity, setFilterTransferProvince, setFilterTransferCity, filterCascaderTransferOpen, setFilterCascaderTransferOpen)) + ), + React.createElement('div', { style: styles.searchField }, + React.createElement('span', { style: styles.searchFieldLabel }, '调拨方式'), + React.createElement('div', { style: styles.searchFieldControl }, + React.createElement('select', { + style: Object.assign({}, styles.select, { width: '100%', minWidth: 0 }), + value: filterMethod, + onChange: function (e) { setFilterMethod(e.target.value); } + }, React.createElement('option', { value: '' }, '全部'), TRANSFER_METHODS.map(function (m) { return React.createElement('option', { key: m.value, value: m.value }, m.label); })) + ) + ), + React.createElement('div', { style: styles.searchField }, + React.createElement('span', { style: styles.searchFieldLabel }, '审批状态'), + React.createElement('div', { style: styles.searchFieldControl }, + React.createElement('select', { + style: Object.assign({}, styles.select, { width: '100%', minWidth: 0 }), + value: filterApprovalStatus, + onChange: function (e) { setFilterApprovalStatus(e.target.value); } + }, React.createElement('option', { value: '' }, '全部'), APPROVAL_STATUS.map(function (s) { return React.createElement('option', { key: s.value, value: s.value }, s.label); })) + ) + ) + ) + ), + React.createElement('div', { style: styles.searchDivider }), + React.createElement('div', { style: styles.searchActions }, + React.createElement('button', { + style: Object.assign({}, styles.btn, styles.btnPrimary, styles.btnFixed), + onClick: function () { + setAppliedFilters({ + filterDateStart: filterDateStart, + filterDateEnd: filterDateEnd, + filterOperator: filterOperator, + filterReceiveDateStart: filterReceiveDateStart, + filterReceiveDateEnd: filterReceiveDateEnd, + filterReceiver: filterReceiver, + filterPlateNo: filterPlateNo, + filterDepartProvince: filterDepartProvince, + filterDepartCity: filterDepartCity, + filterTransferProvince: filterTransferProvince, + filterTransferCity: filterTransferCity, + filterMethod: filterMethod, + filterApprovalStatus: filterApprovalStatus + }); + setCurrentPage(1); + setToastMessage('查询成功'); + setTimeout(function () { setToastMessage(''); }, 1500); + } + }, '查询'), + React.createElement('button', { + style: Object.assign({}, styles.btn, styles.btnDefault, styles.btnFixed), + onClick: function () { + setFilterDateStart(''); setFilterDateEnd(''); setFilterReceiveDateStart(''); setFilterReceiveDateEnd(''); setFilterOperator(''); setFilterReceiver(''); setFilterPlateNo(''); + setFilterDepartProvince(''); setFilterDepartCity(''); setFilterTransferProvince(''); setFilterTransferCity(''); + setFilterMethod(''); setFilterApprovalStatus(''); + setAppliedFilters({}); + setCurrentPage(1); + } + }, '重置') + ) + ) + ), + React.createElement('div', { style: styles.tabs }, + React.createElement('div', { style: Object.assign({}, styles.tab, listTab === 'in_progress' ? styles.tabActive : {}), onClick: function () { setListTab('in_progress'); setCurrentPage(1); setSelectedRowIds([]); } }, '进行中'), + React.createElement('div', { style: Object.assign({}, styles.tab, listTab === 'history' ? styles.tabActive : {}), onClick: function () { setListTab('history'); setCurrentPage(1); setSelectedRowIds([]); } }, '历史记录') + ), + React.createElement('div', { style: styles.toolbar }, + listTab === 'in_progress' && React.createElement('button', { style: Object.assign({}, styles.btn, styles.btnPrimary, styles.btnFixed), onClick: function () { setAddFormData(defaultAddForm); setAddFormLines([createEmptyAddLine()]); setCurrentView('add'); } }, '新增'), + React.createElement('button', { style: Object.assign({}, styles.btn, styles.btnDefault, styles.btnFixed), onClick: function () { setToastMessage(selectedRowIds.length ? '导出' + selectedRowIds.length + '条' : '请先勾选要导出的数据'); setTimeout(function () { setToastMessage(''); }, 2000); } }, '导出') + ), + React.createElement('div', { style: styles.tableWrap }, + React.createElement('table', { style: styles.table }, + React.createElement('thead', {}, + React.createElement('tr', {}, + listTab === 'in_progress' ? React.createElement(React.Fragment, {}, + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap, { width: '32px' }) }, React.createElement('input', { type: 'checkbox', checked: paginatedList.length > 0 && selectedRowIds.length === paginatedList.length, onChange: function (e) { setSelectedRowIds(e.target.checked ? paginatedList.map(function (r) { return r.rowId; }) : []); } })), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '调拨日期'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '出发区域'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '调拨人'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '接收区域'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '调拨方式'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '调拨原因'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '车牌号'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '车辆类型'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '品牌'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '型号'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '运输方名称'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '运输方联系方式'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '出发里程(KM)'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '出发氢量'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '出发电量'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '出发停车场'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '创建日期'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap, styles.thStickyRight) }, '操作') + ) : React.createElement(React.Fragment, {}, + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap, { width: '32px' }) }, React.createElement('input', { type: 'checkbox', checked: paginatedList.length > 0 && selectedRowIds.length === paginatedList.length, onChange: function (e) { setSelectedRowIds(e.target.checked ? paginatedList.map(function (r) { return r.rowId; }) : []); } })), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '调拨日期'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '出发区域'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '调拨人'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '接收日期'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '接收区域'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '调拨方式'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '调拨原因'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '接收人'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '车牌号'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '车辆类型'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '品牌'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '型号'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '运输方名称'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '运输方联系方式'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '出发里程(KM)'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '接收里程(KM)'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '出发氢量'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '接收氢量'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '出发电量'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '接收电量'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '出发停车场'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '接收停车场'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap) }, '创建日期'), + React.createElement('th', { style: Object.assign({}, styles.th, styles.thNowrap, styles.thStickyRight) }, '操作') + ) + ) + ), + React.createElement('tbody', {}, + paginatedList.length === 0 + ? React.createElement('tr', {}, React.createElement('td', { colSpan: listTab === 'in_progress' ? 19 : 25, style: Object.assign({}, styles.td, { textAlign: 'center', color: t.neutral6 }) }, '暂无数据')) + : paginatedList.map(function (row, rowIndex) { + var tr = row.transfer; + var line = row.line; + var canReceive = tr.approvalStatus === 'completed' && !tr.hasReceiveRecord; + var canEdit = ['pending_submit', 'rejected', 'withdrawn'].indexOf(tr.approvalStatus) >= 0; + var canWithdraw = tr.approvalStatus === 'approving'; + var checked = selectedRowIds.indexOf(row.rowId) >= 0; + var isEvenRow = rowIndex % 2 === 1; + var tdOneLine = Object.assign({}, styles.td, styles.tdNowrap); + return React.createElement('tr', { key: row.rowId, style: isEvenRow ? styles.trStripe : styles.trOdd }, + React.createElement('td', { style: tdOneLine }, React.createElement('input', { type: 'checkbox', checked: checked, onChange: function (e) { setSelectedRowIds(e.target.checked ? selectedRowIds.concat([row.rowId]) : selectedRowIds.filter(function (id) { return id !== row.rowId; })); } })), + listTab === 'in_progress' ? React.createElement(React.Fragment, {}, + React.createElement('td', { style: tdOneLine }, tr.transferDate), + React.createElement('td', { style: tdOneLine }, tr.departLabel || '-'), + React.createElement('td', { style: tdOneLine }, tr.operatorName), + React.createElement('td', { style: tdOneLine }, tr.transferLabel || '-'), + React.createElement('td', { style: tdOneLine }, getMethodLabel(tr.transferMethod)), + React.createElement('td', { style: tdOneLine }, tr.transferReason || '-'), + React.createElement('td', { style: tdOneLine }, line.plateNo || '-'), + React.createElement('td', { style: tdOneLine }, line.vehicleType || '-'), + React.createElement('td', { style: tdOneLine }, line.brand || '-'), + React.createElement('td', { style: tdOneLine }, line.model || '-'), + React.createElement('td', { style: carrierInfoTdStyle }, tr.carrierName || '-'), + React.createElement('td', { style: carrierInfoTdStyle }, tr.carrierContact || '-'), + React.createElement('td', { style: tdOneLine }, line.departMileage != null ? line.departMileage : '-'), + React.createElement('td', { style: tdOneLine }, formatH2(line.departH2, line.departH2Unit)), + React.createElement('td', { style: tdOneLine }, line.departElec != null ? line.departElec + ' kWh' : '-'), + React.createElement('td', { style: tdOneLine }, line.departParking || '-'), + React.createElement('td', { style: tdOneLine }, tr.createTime), + React.createElement('td', { style: Object.assign({}, tdOneLine, isEvenRow ? styles.tdStickyRightStripe : styles.tdStickyRight) }, + canReceive && React.createElement('a', { style: styles.actionLink, onClick: function (e) { + e.preventDefault(); + setViewRecord(tr); + setFormDataFromTransfer(tr); + setReceiveFormData({ + receiveDate: '', + receiverName: '张明', + lines: (tr.lines || []).map(function (l) { + return { + plateNo: l.plateNo, + vehicleType: l.vehicleType, + brand: l.brand, + model: l.model, + departParking: l.departParking, + departMileage: l.departMileage, + departH2: l.departH2, + departH2Unit: l.departH2Unit, + departElec: l.departElec, + receiveMileage: l.receiveMileage != null ? l.receiveMileage : '', + receiveH2: l.receiveH2 != null ? l.receiveH2 : '', + receiveElec: l.receiveElec != null ? l.receiveElec : '' + }; + }) + }); + setCurrentView('receive'); + } }, '接收记录'), + canEdit && React.createElement('a', { style: styles.actionLink, onClick: function (e) { e.preventDefault(); openEdit(tr); } }, '编辑'), + canWithdraw && React.createElement('a', { style: styles.actionLink, onClick: function (e) { e.preventDefault(); setWithdrawConfirmRecord(tr); } }, '撤回') + ) + ) : React.createElement(React.Fragment, {}, + React.createElement('td', { style: tdOneLine }, tr.transferDate), + React.createElement('td', { style: tdOneLine }, tr.departLabel || '-'), + React.createElement('td', { style: tdOneLine }, tr.operatorName), + React.createElement('td', { style: tdOneLine }, tr.receiveDate || '-'), + React.createElement('td', { style: tdOneLine }, tr.transferLabel || '-'), + React.createElement('td', { style: tdOneLine }, getMethodLabel(tr.transferMethod)), + React.createElement('td', { style: tdOneLine }, tr.transferReason || '-'), + React.createElement('td', { style: tdOneLine }, tr.receiverName || '-'), + React.createElement('td', { style: tdOneLine }, line.plateNo || '-'), + React.createElement('td', { style: tdOneLine }, line.vehicleType || '-'), + React.createElement('td', { style: tdOneLine }, line.brand || '-'), + React.createElement('td', { style: tdOneLine }, line.model || '-'), + React.createElement('td', { style: carrierInfoTdStyle }, tr.carrierName || '-'), + React.createElement('td', { style: carrierInfoTdStyle }, tr.carrierContact || '-'), + React.createElement('td', { style: tdOneLine }, line.departMileage != null ? line.departMileage : '-'), + React.createElement('td', { style: tdOneLine }, line.receiveMileage != null ? line.receiveMileage : '-'), + React.createElement('td', { style: tdOneLine }, formatH2(line.departH2, line.departH2Unit)), + React.createElement('td', { style: tdOneLine }, formatH2(line.receiveH2, line.departH2Unit)), + React.createElement('td', { style: tdOneLine }, line.departElec != null ? line.departElec + ' kWh' : '-'), + React.createElement('td', { style: tdOneLine }, line.receiveElec != null ? line.receiveElec + ' kWh' : '-'), + React.createElement('td', { style: tdOneLine }, line.departParking || '-'), + React.createElement('td', { style: tdOneLine }, line.receiveParking || '-'), + React.createElement('td', { style: tdOneLine }, tr.createTime), + React.createElement('td', { style: Object.assign({}, tdOneLine, isEvenRow ? styles.tdStickyRightStripe : styles.tdStickyRight) }, React.createElement('a', { style: styles.actionLink, onClick: function (e) { e.preventDefault(); setViewRecord(tr); setFormDataFromTransfer(tr); setCurrentView('view'); } }, '查看')) + ) + ); + }) + ) + ) + ), + totalItems > 0 && React.createElement('div', { style: styles.pagination }, + React.createElement('div', { style: styles.paginationLeft }, + React.createElement('span', {}, '共 ' + totalItems + ' 条'), + React.createElement('span', { style: { marginLeft: '16px' } }, '每页'), + React.createElement('select', { style: styles.paginationSelect, value: pageSize, onChange: function (e) { setPageSize(Number(e.target.value)); setCurrentPage(1); } }, + React.createElement('option', { value: 10 }, '10'), + React.createElement('option', { value: 20 }, '20'), + React.createElement('option', { value: 50 }, '50'), + React.createElement('option', { value: 100 }, '100') + ), + React.createElement('span', {}, '条') + ), + React.createElement('div', { style: styles.paginationRight }, + React.createElement('button', { style: Object.assign({}, styles.paginationBtn, currentPage <= 1 ? styles.paginationBtnDisabled : {}), disabled: currentPage <= 1, onClick: function () { if (currentPage > 1) setCurrentPage(currentPage - 1); } }, '上一页'), + React.createElement('span', { style: { margin: '0 8px' } }, currentPage + ' / ' + totalPages), + React.createElement('button', { style: Object.assign({}, styles.paginationBtn, currentPage >= totalPages ? styles.paginationBtnDisabled : {}), disabled: currentPage >= totalPages, onClick: function () { if (currentPage < totalPages) setCurrentPage(currentPage + 1); } }, '下一页') + ) + ) + ) + ), + + currentView === 'add' && React.createElement(React.Fragment, {}, + React.createElement('div', { style: { marginBottom: t.spacing16, fontSize: t.fontSize16, fontWeight: 600, color: t.neutral8 } }, '新增调拨'), + renderAddPage() + ), + currentView === 'edit' && React.createElement(React.Fragment, {}, + React.createElement('div', { style: { marginBottom: t.spacing16, fontSize: t.fontSize16, fontWeight: 600, color: t.neutral8 } }, '编辑调拨'), + renderEditPage() + ), + + withdrawConfirmRecord && React.createElement('div', { style: styles.modalMask, onClick: function () { setWithdrawConfirmRecord(null); } }, + React.createElement('div', { style: Object.assign({}, styles.modalBox, { minWidth: '360px' }), onClick: function (e) { e.stopPropagation(); } }, + React.createElement('div', { style: styles.modalTitle }, '确认撤回'), + React.createElement('p', { style: { margin: '0 0 16px', fontSize: t.fontSize14 } }, '是否确认撤回该调拨记录?'), + React.createElement('div', { style: styles.modalFooter }, + React.createElement('button', { style: Object.assign({}, styles.btn, styles.btnDefault), onClick: function () { setWithdrawConfirmRecord(null); } }, '取消'), + React.createElement('button', { style: Object.assign({}, styles.btn, styles.btnPrimary), onClick: function () { handleWithdraw(withdrawConfirmRecord); } }, '确认') + ) + ) + ), + + currentView === 'view' && viewRecord && renderViewPage(viewRecord), + + currentView === 'receive' && viewRecord && React.createElement('div', { style: styles.card }, + React.createElement('div', { style: styles.modalTitle }, '接收记录'), + renderFormContent(true, true), + React.createElement('div', { style: styles.formRow }, + React.createElement('label', { style: styles.formLabel }, '接收日期', React.createElement('span', { style: styles.formLabelReq }, '*')), + React.createElement('input', { style: styles.input, type: 'date', value: receiveFormData.receiveDate, onChange: function (e) { setReceiveFormData(Object.assign({}, receiveFormData, { receiveDate: e.target.value })); } }) + ), + React.createElement('div', { style: Object.assign({}, styles.formRow, { marginTop: t.spacing24 }) }, + React.createElement('div', { style: styles.tableWrap }, + React.createElement('table', { style: styles.table }, + React.createElement('thead', {}, + React.createElement('tr', {}, + 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 }, '出发里程(KM)'), + React.createElement('th', { style: styles.th }, '接收里程(KM)', React.createElement('span', { style: styles.formLabelReq }, '*')), + React.createElement('th', { style: styles.th }, '出发氢量'), + React.createElement('th', { style: styles.th }, '接收氢量', React.createElement('span', { style: styles.formLabelReq }, '*')), + React.createElement('th', { style: styles.th }, '出发电量'), + React.createElement('th', { style: styles.th }, '接收电量', React.createElement('span', { style: styles.formLabelReq }, '*')) + ) + ), + React.createElement('tbody', {}, + (receiveFormData.lines || []).map(function (line, idx) { + var isEvenRow = idx % 2 === 1; + return React.createElement('tr', { key: idx, style: isEvenRow ? styles.trStripe : styles.trOdd }, + React.createElement('td', { style: styles.td }, line.plateNo), + React.createElement('td', { style: styles.td }, line.vehicleType), + React.createElement('td', { style: styles.td }, line.brand), + React.createElement('td', { style: styles.td }, line.model), + React.createElement('td', { style: styles.td }, line.departParking), + React.createElement('td', { style: styles.td }, line.departMileage != null ? line.departMileage : '-'), + React.createElement('td', { style: styles.td }, React.createElement('input', { style: Object.assign({}, styles.input, { width: '80px' }), type: 'number', value: line.receiveMileage, onChange: function (e) { var next = receiveFormData.lines.slice(); next[idx] = Object.assign({}, next[idx], { receiveMileage: e.target.value }); setReceiveFormData(Object.assign({}, receiveFormData, { lines: next })); } })), + React.createElement('td', { style: styles.td }, formatH2(line.departH2, line.departH2Unit)), + React.createElement('td', { style: styles.td }, React.createElement(React.Fragment, {}, React.createElement('input', { style: Object.assign({}, styles.input, { width: '80px' }), value: line.receiveH2, onChange: function (e) { var next = receiveFormData.lines.slice(); next[idx] = Object.assign({}, next[idx], { receiveH2: e.target.value }); setReceiveFormData(Object.assign({}, receiveFormData, { lines: next })); } }), React.createElement('span', { style: { marginLeft: '4px' } }, (line.departH2Unit && !String(line.receiveH2 || '').match(/%|MPa|KG$/)) ? line.departH2Unit : ''))), + React.createElement('td', { style: styles.td }, (line.departElec != null ? line.departElec : '-') + ' kWh'), + React.createElement('td', { style: styles.td }, React.createElement('input', { style: Object.assign({}, styles.input, { width: '80px' }), type: 'number', value: line.receiveElec, onChange: function (e) { var next = receiveFormData.lines.slice(); next[idx] = Object.assign({}, next[idx], { receiveElec: e.target.value }); setReceiveFormData(Object.assign({}, receiveFormData, { lines: next })); } })) + ); + }) + ) + ) + ) + ), + React.createElement('div', { style: styles.modalFooter }, + React.createElement('button', { style: Object.assign({}, styles.btn, styles.btnDefault), onClick: function () { setViewRecord(null); setCurrentView('list'); } }, '取消'), + React.createElement('button', { style: Object.assign({}, styles.btn, styles.btnDefault), onClick: function () { + if (!receiveFormData.receiveDate) { setToastMessage('请选择接收日期'); setTimeout(function () { setToastMessage(''); }, 2000); return; } + var recLines = receiveFormData.lines || []; + for (var ri = 0; ri < recLines.length; ri++) { + var fl = recLines[ri]; + var rn = ri + 1; + if (fl.receiveMileage === '' || fl.receiveMileage === undefined || fl.receiveMileage === null) { setToastMessage('请填写第' + rn + '行接收里程(KM)'); setTimeout(function () { setToastMessage(''); }, 2000); return; } + if (fl.receiveH2 === '' || fl.receiveH2 === undefined || fl.receiveH2 === null) { setToastMessage('请填写第' + rn + '行接收氢量'); setTimeout(function () { setToastMessage(''); }, 2000); return; } + if (fl.receiveElec === '' || fl.receiveElec === undefined || fl.receiveElec === null) { setToastMessage('请填写第' + rn + '行接收电量'); setTimeout(function () { setToastMessage(''); }, 2000); return; } + } + var updatedLines = (viewRecord.lines || []).map(function (l, i) { + var formLine = (receiveFormData.lines || [])[i] || {}; + var next = {}; + for (var k in l) next[k] = l[k]; + next.receiveMileage = formLine.receiveMileage !== '' && formLine.receiveMileage !== undefined ? Number(formLine.receiveMileage) : null; + next.receiveH2 = formLine.receiveH2 !== '' && formLine.receiveH2 !== undefined ? formLine.receiveH2 : null; + next.receiveElec = formLine.receiveElec !== '' && formLine.receiveElec !== undefined ? Number(formLine.receiveElec) : null; + next.receiveParking = '抵达停车场'; + next.receiverName = receiveFormData.receiverName; + return next; + }); + var updated = {}; + for (var k in viewRecord) updated[k] = viewRecord[k]; + updated.hasReceiveRecord = true; + updated.receiveDate = receiveFormData.receiveDate; + updated.receiverId = 'u1'; + updated.receiverName = receiveFormData.receiverName; + updated.lines = updatedLines; + setMockTransferList(mockTransferList.map(function (r) { return r.id === viewRecord.id ? updated : r; })); + setViewRecord(updated); + setToastMessage('保存成功'); + setTimeout(function () { setToastMessage(''); }, 2000); + } }, '保存'), + React.createElement('button', { style: Object.assign({}, styles.btn, styles.btnPrimary), onClick: function () { + if (!receiveFormData.receiveDate) { setToastMessage('请选择接收日期'); setTimeout(function () { setToastMessage(''); }, 2000); return; } + var recLines2 = receiveFormData.lines || []; + for (var ri2 = 0; ri2 < recLines2.length; ri2++) { + var fl2 = recLines2[ri2]; + var rn2 = ri2 + 1; + if (fl2.receiveMileage === '' || fl2.receiveMileage === undefined || fl2.receiveMileage === null) { setToastMessage('请填写第' + rn2 + '行接收里程(KM)'); setTimeout(function () { setToastMessage(''); }, 2000); return; } + if (fl2.receiveH2 === '' || fl2.receiveH2 === undefined || fl2.receiveH2 === null) { setToastMessage('请填写第' + rn2 + '行接收氢量'); setTimeout(function () { setToastMessage(''); }, 2000); return; } + if (fl2.receiveElec === '' || fl2.receiveElec === undefined || fl2.receiveElec === null) { setToastMessage('请填写第' + rn2 + '行接收电量'); setTimeout(function () { setToastMessage(''); }, 2000); return; } + } + var updatedLines = (viewRecord.lines || []).map(function (l, i) { + var formLine = (receiveFormData.lines || [])[i] || {}; + var next = {}; + for (var k in l) next[k] = l[k]; + next.receiveMileage = formLine.receiveMileage !== '' && formLine.receiveMileage !== undefined ? Number(formLine.receiveMileage) : null; + next.receiveH2 = formLine.receiveH2 !== '' && formLine.receiveH2 !== undefined ? formLine.receiveH2 : null; + next.receiveElec = formLine.receiveElec !== '' && formLine.receiveElec !== undefined ? Number(formLine.receiveElec) : null; + next.receiveParking = '抵达停车场'; + next.receiverName = receiveFormData.receiverName; + return next; + }); + var updated = {}; + for (var k in viewRecord) updated[k] = viewRecord[k]; + updated.hasReceiveRecord = true; + updated.receiveDate = receiveFormData.receiveDate; + updated.receiverId = 'u1'; + updated.receiverName = receiveFormData.receiverName; + updated.lines = updatedLines; + setMockTransferList(mockTransferList.map(function (r) { return r.id === viewRecord.id ? updated : r; })); + setToastMessage('保存成功'); + setTimeout(function () { setToastMessage(''); }, 2000); + setViewRecord(null); + setCurrentView('list'); + } }, '提交') + ) + ), + + showRequirementModal && React.createElement('div', { + style: styles.modalMask, + onClick: function () { setShowRequirementModal(false); } + }, React.createElement('div', { + style: Object.assign({}, styles.modalBox, { minWidth: '640px', maxWidth: '800px' }), + onClick: function (e) { e.stopPropagation(); } + }, + React.createElement('button', { + style: styles.modalCloseBtn, + onClick: function () { setShowRequirementModal(false); }, + title: '关闭' + }, '×'), + React.createElement('div', { style: { paddingRight: '32px', paddingBottom: t.spacing24, lineHeight: '1.65', maxHeight: '85vh', overflowY: 'auto', fontSize: t.fontSize14, color: t.neutral7 } }, + React.createElement('div', { style: Object.assign({}, styles.requirementSection, { marginTop: 0 }) }, + React.createElement('div', { style: styles.requirementSectionTitle }, '面包屑'), + React.createElement('div', { style: styles.requirementItem }, '运维管理-车辆业务-调拨管理') + ), + React.createElement('div', { style: styles.requirementSection }, + React.createElement('div', { style: styles.requirementSectionTitle }, '筛选'), + React.createElement('div', { style: styles.requirementItem }, '1.调拨日期:日期选择器,支持选择开始-结束时间'), + React.createElement('div', { style: styles.requirementItem }, '2.调拨人:选择器,支持从输入框输入内容模糊搜索'), + React.createElement('div', { style: styles.requirementItem }, '3.接收日期:日期选择器,支持选择开始-结束时间'), + React.createElement('div', { style: styles.requirementItem }, '4.接收人:选择器,支持从输入框输入内容模糊搜索'), + React.createElement('div', { style: styles.requirementItem }, '5.车牌号:选择器,支持从输入框输入内容模糊搜索'), + React.createElement('div', { style: styles.requirementItem }, '6.出发区域:级联选择,支持省市二级级联'), + React.createElement('div', { style: styles.requirementItem }, '7.调拨区域:级联选择,支持省市二级级联'), + React.createElement('div', { style: styles.requirementItem }, '8.调拨方式:选择器,状态分为:第三方运输、司机驾驶'), + React.createElement('div', { style: styles.requirementItem }, '9.审批状态:选择器,状态分为:待提交、待审批、审批中、审批完成、审批驳回、撤回'), + React.createElement('div', { style: Object.assign({}, styles.requirementItem, { marginTop: t.spacing8, marginBottom: t.spacing4 }) }, '审批状态说明(该说明只做需求说明,AI无需分析):'), + React.createElement('div', { style: Object.assign({}, styles.requirementSubItem, { paddingLeft: t.spacing24 }) }, + React.createElement('div', { style: { marginBottom: '2px' } }, 'a.待提交:新增完成后选择保存,但未提交审核;'), + React.createElement('div', { style: { marginBottom: '2px' } }, 'b.待审批:已提交审核,但未通过第一个流程节点审核;'), + React.createElement('div', { style: { marginBottom: '2px' } }, 'c.审批中:已提交审核,至少完成1个以上节点审核,但未完成最终节点审核;'), + React.createElement('div', { style: { marginBottom: '2px' } }, 'd.审批完成:已提交审核,并完成最终节点审核;'), + React.createElement('div', { style: { marginBottom: '2px' } }, 'e.审批驳回:已提交审核,但任意节点审核未通过,审批驳回的记录支持修改后重新提交审核;'), + React.createElement('div', {}, 'f.撤回:已提交审核,但完成最终审核前主动撤回,撤回的记录支持修改后重新提交审核;') + ) + ), + React.createElement('div', { style: styles.requirementSection }, + React.createElement('div', { style: styles.requirementSectionTitle }, '列表'), + React.createElement('div', { style: styles.requirementItem }, '列表由两个tab页组成,分别为:进行中、历史记录;'), + React.createElement('div', { style: Object.assign({}, styles.requirementItem, { marginTop: t.spacing12, marginBottom: t.spacing4 }) }, 'a.进行中tab:显示审批状态为待提交、待审批、审批完成、审核驳回、撤回的记录以及未补充接收记录的数据:'), + React.createElement('div', { style: Object.assign({}, styles.requirementSubItem, { paddingLeft: t.spacing24, marginBottom: t.spacing8 }) }, '列表上方右侧为新增、导出(支持多选)按钮,下方为分页功能,支持选择单页显示多少条数据记录,显示字段按以下顺序'), + [ '1.调拨日期:新建调拨记录时填写的调拨日期,格式为YYYY-MM-DD', '2.出发区域:显示该车辆出发时区域', '3.调拨人:新建调拨记录的操作人', '4.接收区域:新建调拨记录时填写的接收区域', '5.调拨方式:新建调拨记录时选择的状态', '6.调拨原因:新建调拨记录时输入的调拨原因', '7.运输方名称:新建调拨记录时输入的运输方名称', '8.运输方联系方式:新建调拨记录时输入的运输方联系方式', '9.车牌号:新建调拨记录时填写的车牌号,每个车牌号形成一条单独记录;', '10.车辆类型:根据车牌号自动反写车辆类型', '11.品牌:根据车牌号自动反写品牌', '12.型号:根据车牌号自动反写型号', '13.运输方信息:新建调拨记录时手动输入的运输方名称及联系方式,在列表中显示为两行,上方为运输方名称,下方为联系方式', '14.出发里程(KM):新建调拨记录时填写的出发里程', '15.出发氢量:新建调拨记录时填写的出发氢量,单位根据车辆型号对应氢气计量单位自动匹配,分为%、MPa、KG,分别给出一些样例', '16.出发电量:新建调拨记录时填写的出发电量,单位为kWh', '17.出发停车场:新建调拨记录时该车辆出发时的停车场', '18.创建日期:显示该记录创建日期', '19.操作:接收记录、编辑、撤回;操作记录说明如下' ].map(function (txt, i) { return React.createElement('div', { key: i, style: Object.assign({}, styles.requirementSubItem, { paddingLeft: t.spacing24, marginBottom: '2px' }) }, txt); }), + React.createElement('div', { style: Object.assign({}, styles.requirementSubItem, { paddingLeft: t.spacing24 }) }, + React.createElement('div', { style: { marginBottom: '2px' } }, 'a.接收记录:点击后与新增页样式和字段相同,但只有接收日期、抵达里程(KM)、抵达氢量、抵达电量、接收停车场等字段可以操作,仅审核状态为审核完成时显示该功能'), + React.createElement('div', { style: { marginBottom: '2px' } }, 'b.编辑:审核状态为待提交、审批驳回、撤回的数据可进行编辑,其他审核状态不显示编辑,点击编辑后页面与新增页样式相同所有字段可编辑;'), + React.createElement('div', {}, 'c.撤回:点击弹出框进行二次确认,点击弹出框确认按钮toast提示撤回成功,点击弹出框取消按钮,关闭弹出框;仅在审批状态为审批中可撤回,如审批状态为已完成则不显示撤回;') + ), + React.createElement('div', { style: Object.assign({}, styles.requirementItem, { marginTop: t.spacing16, marginBottom: t.spacing4 }) }, 'b.历史记录tab:'), + React.createElement('div', { style: Object.assign({}, styles.requirementSubItem, { paddingLeft: t.spacing24, marginBottom: t.spacing8 }) }, '显示审批状态为审核完成以及完成补充接收记录的数据:列表上方右侧为导出(支持多选)按钮,下方为分页功能,支持选择单页显示多少条数据记录'), + [ '1.调拨日期:新建调拨记录时填写的调拨日期,格式为YYYY-MM-DD', '2.出发区域:显示该车辆出发时区域', '3.调拨人:新建调拨记录的操作人', '4.接收日期:补充接收记录时填写的接收日期,格式为YYYY-MM-DD', '5.接收区域:新建调拨记录时填写的接收区域', '6.调拨方式:新建调拨记录时选择的状态', '7.调拨原因:新建调拨记录时输入的调拨原因', '8.运输方名称:新建调拨记录时输入的运输方名称', '9.运输方联系方式:新建调拨记录时输入的运输方联系方式', '10.接收人:补充接收记录时的操作人', '11.车牌号:新建调拨记录时填写的车牌号,每个车牌号形成一条单独记录;', '12.车辆类型:根据车牌号自动反写车辆类型', '13.品牌:根据车牌号自动反写品牌', '14.型号:根据车牌号自动反写型号', '15.运输方信息:新建调拨记录时手动输入的运输方名称及联系方式,在列表中显示为两行,上方为运输方名称,下方为联系方式', '16.出发里程(KM):新建调拨记录时填写的出发里程', '17.接收里程(KM):补充接收记录时填写的抵达里程', '18.出发氢量:新建调拨记录时填写的出发氢量,单位根据车辆型号对应氢气计量单位自动匹配,分为%、MPa、KG,分别给出一些样例', '19.接收氢量:补充接收记录时填写的抵达氢量,单位根据车辆型号对应氢气计量单位自动匹配,分为%、MPa、KG,分别给出一些样例', '20.出发电量:新建调拨记录时填写的出发电量,单位为kWh', '21.接收电量:补充接收记录时填写的抵达电量,单位为kWh', '22.出发停车场:新建调拨记录时该车辆出发时的停车场', '23.接收停车场:补充接收记录时该车辆抵达场', '24.创建日期:显示该记录创建日期', '25.操作:查看;操作记录说明如下' ].map(function (txt, i) { return React.createElement('div', { key: i, style: Object.assign({}, styles.requirementSubItem, { paddingLeft: t.spacing24, marginBottom: '2px' }) }, txt); }), + React.createElement('div', { style: Object.assign({}, styles.requirementSubItem, { paddingLeft: t.spacing24 }) }, 'a.查看:与接收记录页所有字段和布局相同,但所有字段控件均为禁用状态;') + ), + React.createElement('div', { style: styles.requirementSection }, + React.createElement('div', { style: styles.requirementSectionTitle }, '新增'), + React.createElement('div', { style: styles.requirementItem }, '点击新增按钮以打开新页面的方式进行操作。'), + React.createElement('div', { style: styles.requirementItem }, '页面分为上下两部分组成,上方以3列布局,显示以下字段:'), + [ '1.调拨日期:必填项,日期选择器,格式为YYYY-MM-DD', '2.出发区域:必填项,级联选择器,显示格式为:省-市', '3.接受区域:必填项,级联选择器,显示格式为:省-市', '4.调拨方式:必填项,按钮式选择器,选项分别为第三方运输、司机驾驶', '5.调拨原因:必填项,文本域', '6.运输方名称:必填项,输入框,输入框提示信息为请输入运输方名称或司机姓名', '7.运输方联系方式:必填项,输入框,输入框提示信息为请输入运输方联系方式' ].map(function (txt, i) { return React.createElement('div', { key: i, style: Object.assign({}, styles.requirementSubItem, { paddingLeft: t.spacing24 }) }, txt); }), + React.createElement('div', { style: Object.assign({}, styles.requirementItem, { marginTop: t.spacing8 }) }, '页面下方以可编辑列表方式显示以下字段,默认显示一行数据,列表下方为新增一行按钮,点击新增一行数据'), + [ '1.车牌号:选择器,输入框支持模糊搜索,只能选择状态为库存的车辆', '2.车辆类型:直接显示内容,根据车牌号自动反写车辆类型', '3.品牌:直接显示内容,根据车牌号自动反写品牌', '4.型号:直接显示内容,根据车牌号自动反写型号', '5.出发停车场:直接显示内容,根据车牌号当前停车场自动反写', '6.出发里程(KM):输入框', '7.出发氢量:输入框,后缀为单位,单位根据该车辆型号参数内氢系统仪表盘单位字段自动反写单位,如未设置则不显示后缀单位', '8.出发电量:输入框,后缀为单位,单位为kWh', '9.操作:删除,点击后删除该行数据' ].map(function (txt, i) { return React.createElement('div', { key: i, style: Object.assign({}, styles.requirementSubItem, { paddingLeft: t.spacing24 }) }, txt); }), + React.createElement('div', { style: Object.assign({}, styles.requirementItem, { marginTop: t.spacing8 }) }, '页面最下方为提交审核、保存、取消按钮'), + React.createElement('div', { style: Object.assign({}, styles.requirementSubItem, { paddingLeft: t.spacing24 }) }, '点击提交审核提交并转入调拨审核流程;点击保存,保存数据但不提交审核;点击取消,跳转回调拨列表页') + ), + React.createElement('div', { style: styles.requirementSection }, + React.createElement('div', { style: styles.requirementSectionTitle }, '接收记录'), + React.createElement('div', { style: styles.requirementItem }, '上方显示新增页所有字段和布局,但所有组件为禁用状态,除此外新增一个可编辑字段'), + React.createElement('div', { style: Object.assign({}, styles.requirementSubItem, { paddingLeft: t.spacing24 }) }, '1.接收日期:必填项,日期选择器,格式为YYYY-MM-DD'), + React.createElement('div', { style: Object.assign({}, styles.requirementItem, { marginTop: t.spacing8 }) }, '页面下方以可编辑列表方式显示以下字段'), + [ '2.车牌号:文字方式显示新增时记录的车牌号', '3.车辆类型:文字方式显示新增时记录的车牌号对应车辆类型', '4.品牌:文字方式显示新增时记录的车牌号对应品牌', '5.型号:文字方式显示新增时记录的车牌号对应型号', '6.出发停车场:文字方式显示新增时记录的车牌号对应停车场', '7.出发里程(KM):文字方式显示新增时记录的出发里程', '8.接收里程(KM):输入框', '9.出发氢量:文字显示新增时记录的车牌号对应出发氢量,并显示单位', '10.接收氢量:输入框,后缀为单位,单位根据车辆型号对应氢气计量单位自动匹配,分为%、MPa、KG', '11.出发电量:文字显示新增时记录的车牌号对应出发电量,单位为kWh', '12.接收电量:输入框,后缀为单位,单位为kWh' ].map(function (txt, i) { return React.createElement('div', { key: i, style: Object.assign({}, styles.requirementSubItem, { paddingLeft: t.spacing24 }) }, txt); }) + ), + React.createElement('div', { style: styles.requirementSection }, + React.createElement('div', { style: styles.requirementSectionTitle }, '导出'), + React.createElement('div', { style: styles.requirementItem }, '支持选择器选择对应数据进行导出') + ) + ) + )), + + toastMessage && React.createElement('div', { style: styles.toast }, toastMessage) + ) + ); +}; + +if (typeof window !== 'undefined') { + window.Component = Component; + function mount() { + var rootEl = document.getElementById('root'); + if (rootEl && window.ReactDOM && window.React) { + var root = ReactDOM.createRoot(rootEl); + root.render(React.createElement(Component)); + } + } + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', mount); + } else { + setTimeout(mount, 0); + } +} diff --git a/小程序/App.jsx b/小程序/App.jsx new file mode 100644 index 0000000..da83cd1 --- /dev/null +++ b/小程序/App.jsx @@ -0,0 +1,176 @@ +// 【重要】必须使用 const Component 作为组件变量名 +// 集成登录页 + 工作台 于单页面 +const Component = function () { + var _React$useState = React.useState("login"); + var page = _React$useState[0]; + var setPage = _React$useState[1]; + + // 登录页状态 + var _React$useState2 = React.useState(""); + var account = _React$useState2[0]; + var setAccount = _React$useState2[1]; + var _React$useState3 = React.useState(""); + var password = _React$useState3[0]; + var setPassword = _React$useState3[1]; + var _React$useState4 = React.useState(false); + var agreed = _React$useState4[0]; + var setAgreed = _React$useState4[1]; + + // 工作台状态 + var _React$useState5 = React.useState(0); + var activeTab = _React$useState5[0]; + var setActiveTab = _React$useState5[1]; + + var todos = [ + { id: 1, title: "审批设备巡检报告", status: "待处理", time: "今天" }, + { id: 2, title: "处理加氢站异常告警", status: "进行中", time: "今天" }, + { id: 3, title: "完成月度资产盘点", status: "待处理", time: "本周" } + ]; + var stats = [ + { label: "待办任务", value: "8", color: "#07c160" }, + { label: "本周完成", value: "23", color: "#576b95" }, + { label: "告警待处理", value: "2", color: "#fa5151" } + ]; + var navItems = [ + { key: 0, icon: "\uD83D\uDCCA", text: "工作台" }, + { key: 1, icon: "\uD83D\uDCC4", text: "业务" }, + { key: 2, icon: "\uD83D\uDC64", text: "我的" } + ]; + + var handleLogin = function () { + if (!agreed) { + alert("请先阅读并同意用户服务协议和隐私政策"); + return; + } + setPage("workbench"); + }; + + var styles = { + loginPage: { + minHeight: "100vh", + backgroundColor: "#f7f7f7", + padding: "40px 24px 24px", + boxSizing: "border-box", + display: "flex", + flexDirection: "column", + alignItems: "center" + }, + logo: { width: "80px", height: "80px", backgroundColor: "#07c160", borderRadius: "16px", display: "flex", alignItems: "center", justifyContent: "center", marginBottom: "20px", color: "#fff", fontSize: "16px", fontWeight: "bold", textAlign: "center", lineHeight: "1.3" }, + title: { fontSize: "20px", fontWeight: "600", color: "#333", textAlign: "center", marginBottom: "40px", lineHeight: "1.5", maxWidth: "280px" }, + form: { width: "100%", maxWidth: "340px" }, + inputGroup: { marginBottom: "16px" }, + input: { width: "100%", height: "48px", padding: "0 16px", fontSize: "16px", border: "1px solid #e5e5e5", borderRadius: "8px", backgroundColor: "#fff", boxSizing: "border-box", outline: "none" }, + checkboxRow: { display: "flex", alignItems: "center", marginBottom: "24px", cursor: "pointer" }, + checkbox: { width: "18px", height: "18px", marginRight: "8px", accentColor: "#07c160", cursor: "pointer" }, + agreement: { fontSize: "14px", color: "#666", lineHeight: "1.5" }, + link: { color: "#07c160", textDecoration: "none" }, + loginBtn: { width: "100%", height: "48px", backgroundColor: "#07c160", color: "#fff", fontSize: "17px", fontWeight: "500", border: "none", borderRadius: "8px", cursor: "pointer", marginBottom: "32px" }, + loginBtnDisabled: { width: "100%", height: "48px", backgroundColor: "#b0b0b0", color: "#fff", fontSize: "17px", fontWeight: "500", border: "none", borderRadius: "8px", cursor: "not-allowed", marginBottom: "32px" }, + divider: { display: "flex", alignItems: "center", width: "100%", maxWidth: "340px", marginBottom: "24px" }, + dividerLine: { flex: 1, height: "1px", backgroundColor: "#e5e5e5" }, + dividerText: { padding: "0 16px", fontSize: "14px", color: "#999" }, + phoneLogin: { display: "flex", flexDirection: "column", alignItems: "center" }, + iconLabel: { fontSize: "14px", color: "#999", marginTop: "8px" }, + iconBtn: { width: "48px", height: "48px", borderRadius: "50%", backgroundColor: "#fff", border: "1px solid #e5e5e5", display: "flex", alignItems: "center", justifyContent: "center", cursor: "pointer", fontSize: "24px" }, + workbenchPage: { minHeight: "100vh", backgroundColor: "#f7f7f7", paddingBottom: "70px", boxSizing: "border-box" }, + header: { backgroundColor: "#fff", padding: "24px 20px", marginBottom: "12px" }, + headerTitle: { fontSize: "22px", fontWeight: "600", color: "#333", marginBottom: "4px" }, + headerSub: { fontSize: "14px", color: "#999" }, + statsRow: { display: "flex", padding: "0 12px 12px", marginBottom: "12px" }, + statCard: { flex: 1, backgroundColor: "#fff", borderRadius: "8px", padding: "20px 12px", textAlign: "center", margin: "0 6px" }, + statValue: { fontSize: "24px", fontWeight: "600", marginBottom: "8px" }, + statLabel: { fontSize: "13px", color: "#999" }, + section: { backgroundColor: "#fff", padding: "20px", marginBottom: "12px" }, + sectionTitle: { fontSize: "17px", fontWeight: "600", color: "#333", marginBottom: "16px", display: "flex", justifyContent: "space-between", alignItems: "center" }, + sectionMore: { fontSize: "14px", color: "#07c160" }, + todoItem: { display: "flex", alignItems: "center", padding: "14px 0", borderBottom: "1px solid #f0f0f0" }, + todoLeft: { flex: 1 }, + todoTitle: { fontSize: "15px", color: "#333", marginBottom: "4px" }, + todoMeta: { fontSize: "12px", color: "#999" }, + todoStatus: { fontSize: "12px", padding: "4px 10px", borderRadius: "4px", backgroundColor: "#e8f5e9", color: "#07c160" }, + todoStatusDoing: { fontSize: "12px", padding: "4px 10px", borderRadius: "4px", backgroundColor: "#fff3e0", color: "#ff9800" }, + bottomNav: { position: "fixed", bottom: 0, left: 0, right: 0, height: "60px", backgroundColor: "#fff", display: "flex", borderTop: "1px solid #e5e5e5", boxSizing: "border-box" }, + navItem: { flex: 1, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", cursor: "pointer" }, + navIcon: { fontSize: "24px", marginBottom: "4px" }, + navText: { fontSize: "11px", color: "#999" } + }; + + if (page === "login") { + return React.createElement("div", { style: styles.loginPage }, + React.createElement("style", null, "input::placeholder{color:#999;opacity:1;}"), + React.createElement("div", { style: styles.logo }, "羚牛氢能"), + React.createElement("h1", { style: styles.title }, "数字化资产ONE-OS运管平台"), + React.createElement("div", { style: styles.form }, + React.createElement("div", { style: styles.inputGroup }, + React.createElement("input", { style: styles.input, type: "text", placeholder: "请输入账号信息", value: account, onChange: function (e) { setAccount(e.target.value); } }) + ), + React.createElement("div", { style: styles.inputGroup }, + React.createElement("input", { style: styles.input, type: "password", placeholder: "请输入密码", value: password, onChange: function (e) { setPassword(e.target.value); } }) + ), + React.createElement("label", { style: styles.checkboxRow }, + React.createElement("input", { style: styles.checkbox, type: "checkbox", checked: agreed, onChange: function (e) { setAgreed(e.target.checked); } }), + React.createElement("span", { style: styles.agreement }, + "阅读并同意", + React.createElement("a", { href: "#", style: styles.link }, "《用户服务协议》"), + "和", + React.createElement("a", { href: "#", style: styles.link }, "《隐私政策》") + ) + ), + React.createElement("button", { style: agreed ? styles.loginBtn : styles.loginBtnDisabled, onClick: handleLogin, disabled: !agreed }, "登录") + ), + React.createElement("div", { style: styles.divider }, + React.createElement("div", { style: styles.dividerLine }), + React.createElement("span", { style: styles.dividerText }, "其他登录方式"), + React.createElement("div", { style: styles.dividerLine }) + ), + React.createElement("div", { style: styles.phoneLogin }, + React.createElement("div", { style: styles.iconBtn, title: "手机号登录" }, "\uD83D\uDCF1"), + React.createElement("span", { style: styles.iconLabel }, "授权登录") + ) + ); + } + + return React.createElement("div", { style: styles.workbenchPage }, + React.createElement("div", { style: styles.header }, + React.createElement("div", { style: styles.headerTitle }, "工作台"), + React.createElement("div", { style: styles.headerSub }, "欢迎使用 ONE-OS 运管平台") + ), + React.createElement("div", { style: styles.statsRow }, + stats.map(function (stat, i) { + var valueStyle = { fontSize: "24px", fontWeight: "600", marginBottom: "8px", color: stat.color }; + return React.createElement("div", { key: i, style: styles.statCard }, + React.createElement("div", { style: valueStyle }, stat.value), + React.createElement("div", { style: styles.statLabel }, stat.label) + ); + }) + ), + React.createElement("div", { style: styles.section }, + React.createElement("div", { style: styles.sectionTitle }, + React.createElement("span", null, "待办任务"), + React.createElement("span", { style: styles.sectionMore }, "全部") + ), + todos.map(function (todo, i) { + var isLast = i === todos.length - 1; + var statusStyle = todo.status === "进行中" ? styles.todoStatusDoing : styles.todoStatus; + var itemStyle = isLast ? { display: "flex", alignItems: "center", padding: "14px 0", borderBottom: "none" } : styles.todoItem; + return React.createElement("div", { key: todo.id, style: itemStyle }, + React.createElement("div", { style: styles.todoLeft }, + React.createElement("div", { style: styles.todoTitle }, todo.title), + React.createElement("div", { style: styles.todoMeta }, todo.time) + ), + React.createElement("span", { style: statusStyle }, todo.status) + ); + }) + ), + React.createElement("div", { style: styles.bottomNav }, + navItems.map(function (item) { + var isActive = activeTab === item.key; + var textStyle = isActive ? { fontSize: "11px", color: "#07c160" } : styles.navText; + return React.createElement("div", { key: item.key, style: styles.navItem, onClick: function () { setActiveTab(item.key); } }, + React.createElement("div", { style: styles.navIcon }, item.icon), + React.createElement("div", { style: textStyle }, item.text) + ); + }) + ) + ); +}; diff --git a/小程序/LoginPage.jsx b/小程序/LoginPage.jsx new file mode 100644 index 0000000..7d9ebc8 --- /dev/null +++ b/小程序/LoginPage.jsx @@ -0,0 +1,286 @@ +// 【重要】必须使用 const Component 作为组件变量名 +const LoginPage = function (props) { + var onLoginSuccess = (props && props.onLoginSuccess) ? props.onLoginSuccess : null; + var _React$useState = React.useState(""); + var account = _React$useState[0]; + var setAccount = _React$useState[1]; + + var _React$useState2 = React.useState(""); + var password = _React$useState2[0]; + var setPassword = _React$useState2[1]; + + var _React$useState3 = React.useState(false); + var agreed = _React$useState3[0]; + var setAgreed = _React$useState3[1]; + + var handleAccountChange = function (e) { + setAccount(e.target.value); + }; + + var handlePasswordChange = function (e) { + setPassword(e.target.value); + }; + + var handleAgreedChange = function (e) { + setAgreed(e.target.checked); + }; + + var handleLogin = function () { + if (!agreed) { + alert("请先阅读并同意用户服务协议和隐私政策"); + return; + } + if (onLoginSuccess) { + onLoginSuccess(); + } else { + alert("登录功能演示"); + } + }; + + var styles = { + page: { + minHeight: "100vh", + backgroundColor: "#f7f7f7", + padding: "40px 24px 24px", + boxSizing: "border-box", + display: "flex", + flexDirection: "column", + alignItems: "center" + }, + logo: { + width: "80px", + height: "80px", + backgroundColor: "#07c160", + borderRadius: "16px", + display: "flex", + alignItems: "center", + justifyContent: "center", + marginBottom: "20px", + color: "#fff", + fontSize: "16px", + fontWeight: "bold", + textAlign: "center", + lineHeight: "1.3" + }, + title: { + fontSize: "20px", + fontWeight: "600", + color: "#333", + textAlign: "center", + marginBottom: "40px", + lineHeight: "1.5", + maxWidth: "280px" + }, + form: { + width: "100%", + maxWidth: "340px" + }, + inputGroup: { + marginBottom: "16px" + }, + input: { + width: "100%", + height: "48px", + padding: "0 16px", + fontSize: "16px", + border: "1px solid #e5e5e5", + borderRadius: "8px", + backgroundColor: "#fff", + boxSizing: "border-box", + outline: "none" + }, + inputFocus: { + borderColor: "#07c160" + }, + checkboxRow: { + display: "flex", + alignItems: "center", + marginBottom: "24px", + cursor: "pointer" + }, + checkbox: { + width: "18px", + height: "18px", + marginRight: "8px", + accentColor: "#07c160", + cursor: "pointer" + }, + agreement: { + fontSize: "14px", + color: "#666", + lineHeight: "1.5" + }, + link: { + color: "#07c160", + textDecoration: "none" + }, + loginBtn: { + width: "100%", + height: "48px", + backgroundColor: "#07c160", + color: "#fff", + fontSize: "17px", + fontWeight: "500", + border: "none", + borderRadius: "8px", + cursor: "pointer", + marginBottom: "32px" + }, + loginBtnDisabled: { + width: "100%", + height: "48px", + backgroundColor: "#b0b0b0", + color: "#fff", + fontSize: "17px", + fontWeight: "500", + border: "none", + borderRadius: "8px", + cursor: "not-allowed", + marginBottom: "32px" + }, + divider: { + display: "flex", + alignItems: "center", + width: "100%", + maxWidth: "340px", + marginBottom: "24px" + }, + dividerLine: { + flex: 1, + height: "1px", + backgroundColor: "#e5e5e5" + }, + dividerText: { + padding: "0 16px", + fontSize: "14px", + color: "#999" + }, + phoneLogin: { + display: "flex", + flexDirection: "column", + alignItems: "center" + }, + iconLabel: { + fontSize: "14px", + color: "#999", + marginTop: "8px" + }, + iconBtn: { + width: "48px", + height: "48px", + borderRadius: "50%", + backgroundColor: "#fff", + border: "1px solid #e5e5e5", + display: "flex", + alignItems: "center", + justifyContent: "center", + cursor: "pointer", + fontSize: "24px" + } + }; + + return React.createElement( + "div", + { style: styles.page }, + React.createElement("style", null, "input::placeholder{color:#999;opacity:1;}"), + React.createElement( + "div", + { style: styles.logo }, + "羚牛氢能" + ), + React.createElement( + "h1", + { style: styles.title }, + "\u6570\u5B57\u5316\u8D44\u4EA7ONE-OS\u8FD0\u7BA1\u5E73\u53F0" + ), + React.createElement( + "div", + { style: styles.form }, + React.createElement( + "div", + { style: styles.inputGroup }, + React.createElement("input", { + style: styles.input, + type: "text", + placeholder: "请输入账号信息", + value: account, + onChange: handleAccountChange + }) + ), + React.createElement( + "div", + { style: styles.inputGroup }, + React.createElement("input", { + style: styles.input, + type: "password", + placeholder: "请输入密码", + value: password, + onChange: handlePasswordChange + }) + ), + React.createElement( + "label", + { style: styles.checkboxRow }, + React.createElement("input", { + style: styles.checkbox, + type: "checkbox", + checked: agreed, + onChange: handleAgreedChange + }), + React.createElement( + "span", + { style: styles.agreement }, + "阅读并同意", + React.createElement( + "a", + { href: "#", style: styles.link }, + "《用户服务协议》" + ), + "和", + React.createElement( + "a", + { href: "#", style: styles.link }, + "《隐私政策》" + ) + ) + ), + React.createElement( + "button", + { + style: agreed ? styles.loginBtn : styles.loginBtnDisabled, + onClick: handleLogin, + disabled: !agreed + }, + "\u767B\u5F55" + ) + ), + React.createElement( + "div", + { style: styles.divider }, + React.createElement("div", { style: styles.dividerLine }), + React.createElement( + "span", + { style: styles.dividerText }, + "\u5176\u4ED6\u767B\u5F55\u65B9\u5F0F" + ), + React.createElement("div", { style: styles.dividerLine }) + ), + React.createElement( + "div", + { style: styles.phoneLogin }, + React.createElement( + "div", + { style: styles.iconBtn, title: "手机号登录" }, + "\uD83D\uDCF1" + ), + React.createElement( + "span", + { style: styles.iconLabel }, + "授权登录" + ) + ) + ); +}; + +// Axhub 要求必须定义 Component 变量 +var Component = LoginPage; diff --git a/小程序/WorkbenchPage.jsx b/小程序/WorkbenchPage.jsx new file mode 100644 index 0000000..6394a68 --- /dev/null +++ b/小程序/WorkbenchPage.jsx @@ -0,0 +1,232 @@ +const WorkbenchPage = function () { + var _React$useState = React.useState(0); + var activeTab = _React$useState[0]; + var setActiveTab = _React$useState[1]; + + var todos = [ + { id: 1, title: "审批设备巡检报告", status: "待处理", time: "今天" }, + { id: 2, title: "处理加氢站异常告警", status: "进行中", time: "今天" }, + { id: 3, title: "完成月度资产盘点", status: "待处理", time: "本周" } + ]; + + var stats = [ + { label: "待办任务", value: "8", color: "#07c160" }, + { label: "本周完成", value: "23", color: "#576b95" }, + { label: "告警待处理", value: "2", color: "#fa5151" } + ]; + + var navItems = [ + { key: 0, icon: "\uD83D\uDCCA", text: "工作台" }, + { key: 1, icon: "\uD83D\uDCC4", text: "业务" }, + { key: 2, icon: "\uD83D\uDC64", text: "我的" } + ]; + + var handleNavClick = function (key) { + setActiveTab(key); + }; + + var styles = { + page: { + minHeight: "100vh", + backgroundColor: "#f7f7f7", + paddingBottom: "70px", + boxSizing: "border-box" + }, + header: { + backgroundColor: "#fff", + padding: "24px 20px", + marginBottom: "12px" + }, + headerTitle: { + fontSize: "22px", + fontWeight: "600", + color: "#333", + marginBottom: "4px" + }, + headerSub: { + fontSize: "14px", + color: "#999" + }, + statsRow: { + display: "flex", + padding: "0 12px 12px", + marginBottom: "12px" + }, + statCard: { + flex: 1, + backgroundColor: "#fff", + borderRadius: "8px", + padding: "20px 12px", + textAlign: "center", + margin: "0 6px" + }, + statValue: { + fontSize: "24px", + fontWeight: "600", + marginBottom: "8px" + }, + statLabel: { + fontSize: "13px", + color: "#999" + }, + section: { + backgroundColor: "#fff", + padding: "20px", + marginBottom: "12px" + }, + sectionTitle: { + fontSize: "17px", + fontWeight: "600", + color: "#333", + marginBottom: "16px", + display: "flex", + justifyContent: "space-between", + alignItems: "center" + }, + sectionMore: { + fontSize: "14px", + color: "#07c160" + }, + todoItem: { + display: "flex", + alignItems: "center", + padding: "14px 0", + borderBottom: "1px solid #f0f0f0" + }, + todoItemLast: { + borderBottom: "none" + }, + todoLeft: { + flex: 1 + }, + todoTitle: { + fontSize: "15px", + color: "#333", + marginBottom: "4px" + }, + todoMeta: { + fontSize: "12px", + color: "#999" + }, + todoStatus: { + fontSize: "12px", + padding: "4px 10px", + borderRadius: "4px", + backgroundColor: "#e8f5e9", + color: "#07c160" + }, + todoStatusDoing: { + fontSize: "12px", + padding: "4px 10px", + borderRadius: "4px", + backgroundColor: "#fff3e0", + color: "#ff9800" + }, + bottomNav: { + position: "fixed", + bottom: 0, + left: 0, + right: 0, + height: "60px", + backgroundColor: "#fff", + display: "flex", + borderTop: "1px solid #e5e5e5", + boxSizing: "border-box" + }, + navItem: { + flex: 1, + display: "flex", + flexDirection: "column", + alignItems: "center", + justifyContent: "center", + cursor: "pointer" + }, + navIcon: { + fontSize: "24px", + marginBottom: "4px" + }, + navText: { + fontSize: "11px", + color: "#999" + }, + navTextActive: { + color: "#07c160" + } + }; + + return React.createElement( + "div", + { style: styles.page }, + React.createElement( + "div", + { style: styles.header }, + React.createElement("div", { style: styles.headerTitle }, "工作台"), + React.createElement("div", { style: styles.headerSub }, "欢迎使用 ONE-OS 运管平台") + ), + React.createElement( + "div", + { style: styles.statsRow }, + stats.map(function (stat, i) { + var valueStyle = { + fontSize: "24px", + fontWeight: "600", + marginBottom: "8px", + color: stat.color + }; + return React.createElement( + "div", + { key: i, style: styles.statCard }, + React.createElement("div", { style: valueStyle }, stat.value), + React.createElement("div", { style: styles.statLabel }, stat.label) + ); + }) + ), + React.createElement( + "div", + { style: styles.section }, + React.createElement( + "div", + { style: styles.sectionTitle }, + React.createElement("span", null, "待办任务"), + React.createElement("span", { style: styles.sectionMore }, "全部") + ), + todos.map(function (todo, i) { + var isLast = i === todos.length - 1; + var statusStyle = todo.status === "进行中" ? styles.todoStatusDoing : styles.todoStatus; + var itemStyle = isLast ? { display: "flex", alignItems: "center", padding: "14px 0", borderBottom: "none" } : styles.todoItem; + return React.createElement( + "div", + { key: todo.id, style: itemStyle }, + React.createElement( + "div", + { style: styles.todoLeft }, + React.createElement("div", { style: styles.todoTitle }, todo.title), + React.createElement("div", { style: styles.todoMeta }, todo.time) + ), + React.createElement("span", { style: statusStyle }, todo.status) + ); + }) + ), + React.createElement( + "div", + { style: styles.bottomNav }, + navItems.map(function (item) { + var isActive = activeTab === item.key; + var textStyle = isActive ? { fontSize: "11px", color: "#07c160" } : styles.navText; + return React.createElement( + "div", + { + key: item.key, + style: styles.navItem, + onClick: function () { handleNavClick(item.key); } + }, + React.createElement("div", { style: styles.navIcon }, item.icon), + React.createElement("div", { style: textStyle }, item.text) + ); + }) + ) + ); +}; + +// Axhub 要求必须定义 Component 变量 +var Component = WorkbenchPage; diff --git a/小程序/login-preview.html b/小程序/login-preview.html new file mode 100644 index 0000000..9cb0e35 --- /dev/null +++ b/小程序/login-preview.html @@ -0,0 +1,20 @@ + + + + + + ONE-OS运管平台 + + + + + + +
+ + + + diff --git a/小程序/workbench-preview.html b/小程序/workbench-preview.html new file mode 100644 index 0000000..81dfaeb --- /dev/null +++ b/小程序/workbench-preview.html @@ -0,0 +1,20 @@ + + + + + + 工作台 - 数字化资产ONE-OS运管平台 + + + + + + +
+ + + +