From 49d9e3675d2adb08f498b6247d094566d352bfd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=86=95?= Date: Sat, 21 Mar 2026 13:19:27 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E8=BF=90=E7=BB=B4-=E8=BD=A6=E8=BE=86):=20?= =?UTF-8?q?=E5=BC=82=E5=8A=A8=E7=AE=A1=E7=90=86=E6=9F=A5=E7=9C=8B/?= =?UTF-8?q?=E7=BB=93=E6=9D=9F=E5=BC=82=E5=8A=A8/=E7=BC=96=E8=BE=91?= =?UTF-8?q?=E3=80=81=E5=A4=87=E4=BB=B6=E4=B8=8E=E9=9C=80=E6=B1=82=E8=AF=B4?= =?UTF-8?q?=E6=98=8E=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made-with: Cursor --- web端/运维管理/备件管理/仓库管理.jsx | 498 +++++++++++++++ web端/运维管理/备件管理/备件库存.jsx | 584 ++++++++++++++++++ web端/运维管理/车辆业务/异动管理-查看.jsx | 311 ++++++---- web端/运维管理/车辆业务/异动管理-结束异动.jsx | 464 ++++++++++++++ web端/运维管理/车辆业务/异动管理-编辑.jsx | 564 +++++++++++++++++ web端/运维管理/车辆业务/异动管理.jsx | 458 +++++++++++++- web端/运维管理/车辆管理-查看.jsx | 51 +- web端/需求说明/运维管理-仓库管理/交车任务 | 19 + web端/需求说明/运维管理-仓库管理/备件库存 | 20 + 9 files changed, 2812 insertions(+), 157 deletions(-) create mode 100644 web端/运维管理/备件管理/仓库管理.jsx create mode 100644 web端/运维管理/备件管理/备件库存.jsx create mode 100644 web端/运维管理/车辆业务/异动管理-结束异动.jsx create mode 100644 web端/运维管理/车辆业务/异动管理-编辑.jsx create mode 100644 web端/需求说明/运维管理-仓库管理/交车任务 create mode 100644 web端/需求说明/运维管理-仓库管理/备件库存 diff --git a/web端/运维管理/备件管理/仓库管理.jsx b/web端/运维管理/备件管理/仓库管理.jsx new file mode 100644 index 0000000..31d34c3 --- /dev/null +++ b/web端/运维管理/备件管理/仓库管理.jsx @@ -0,0 +1,498 @@ +// 【重要】必须使用 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 Card = antd.Card; + var Button = antd.Button; + var Drawer = antd.Drawer; + var Input = antd.Input; + var Table = antd.Table; + var Select = antd.Select; + var DatePicker = antd.DatePicker; + var Modal = antd.Modal; + var message = antd.message; + + var RangePicker = DatePicker.RangePicker; + + function filterOption(input, option) { + var label = (option && (option.label || option.children)) || ''; + return String(label).toLowerCase().indexOf(String(input || '').toLowerCase()) >= 0; + } + + function pad2(n) { + return n < 10 ? '0' + n : '' + n; + } + + // 格式化:YYYY-MM-DD HH:MM(尽量保持输入原样) + function fmtYMDHM(v) { + if (v === null || v === undefined) return '-'; + var s = String(v).trim(); + if (!s) return '-'; + if (/^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}/.test(s)) return s.slice(0, 16); + if (/^\d{4}-\d{2}-\d{2}$/.test(s)) return s + ' 00:00'; + try { + var d = new Date(s.replace(/-/g, '/')); + if (isNaN(d.getTime())) return s; + return d.getFullYear() + '-' + pad2(d.getMonth() + 1) + '-' + pad2(d.getDate()) + ' ' + pad2(d.getHours()) + ':' + pad2(d.getMinutes()); + } catch (e) { + return s; + } + } + + function matchRange(valStr, range) { + if (!range || !range[0] || !range[1]) return true; + var s = ''; + var e = ''; + if (range[0] && range[0].format) s = range[0].format('YYYY-MM-DD'); + if (range[1] && range[1].format) e = range[1].format('YYYY-MM-DD'); + if (!s && !e) return true; + var v = String(valStr || '').slice(0, 10); + if (s && v < s) return false; + if (e && v > e) return false; + return true; + } + + var layoutStyle = { padding: '16px 24px', background: '#f5f5f5', minHeight: '100vh' }; + var filterLabelStyle = { marginBottom: 6, fontSize: 14, color: 'rgba(0,0,0,0.65)' }; + var filterItemStyle = { marginBottom: 12 }; + var filterControlStyle = { width: '100%' }; + var tableSingleLineStyle = '.warehouse-list-table .ant-table-thead th,.warehouse-list-table .ant-table-tbody td{white-space:nowrap;}'; + + // mock 列表数据(仓库是否为空用 itemCount 表示) + var warehousesState = useState(function () { + return [ + { id: 'wh1', warehouseCode: 'WH-001', warehouseName: '天河备件库', warehouseAddress: '广州市天河区xx路1号', warehouseManager: '张明', creatorName: '张明', createTime: '2026-02-20 09:30', itemCount: 12 }, + { id: 'wh2', warehouseCode: 'WH-002', warehouseName: '西湖服务备件库', warehouseAddress: '杭州市西湖区xx街88号', warehouseManager: '王芳', creatorName: '王芳', createTime: '2026-02-21 14:10', itemCount: 0 }, + { id: 'wh3', warehouseCode: 'WH-003', warehouseName: '上海浦东备件仓', warehouseAddress: '上海市浦东新区xx大道55号', warehouseManager: '李华', creatorName: '李华', createTime: '2026-02-22 08:45', itemCount: 5 }, + { id: 'wh4', warehouseCode: 'WH-004', warehouseName: '深圳南山备件仓', warehouseAddress: '深圳市南山区xx科技园3栋', warehouseManager: '赵强', creatorName: '赵强', createTime: '2026-02-23 10:20', itemCount: 0 }, + { id: 'wh5', warehouseCode: 'WH-005', warehouseName: '成都高新备件库', warehouseAddress: '成都市高新区xx路12号', warehouseManager: '陈静', creatorName: '陈静', createTime: '2026-02-24 11:05', itemCount: 9 }, + { id: 'wh6', warehouseCode: 'WH-006', warehouseName: '宁波江北备件库', warehouseAddress: '宁波市江北区xx街道6号', warehouseManager: '张明', creatorName: '张明', createTime: '2026-02-25 16:40', itemCount: 0 }, + { id: 'wh7', warehouseCode: 'WH-007', warehouseName: '北京朝阳备件仓', warehouseAddress: '北京市朝阳区xx路77号', warehouseManager: '王芳', creatorName: '王芳', createTime: '2026-02-26 09:15', itemCount: 21 }, + { id: 'wh8', warehouseCode: 'WH-008', warehouseName: '重庆两江备件库', warehouseAddress: '重庆市两江新区xx大道18号', warehouseManager: '李华', creatorName: '李华', createTime: '2026-02-27 07:45', itemCount: 2 } + ]; + }); + var warehouses = warehousesState[0]; + var setWarehouses = warehousesState[1]; + + var defaultDraftState = { warehouseName: undefined, creatorName: undefined, createTimeRange: null }; + + var draftState = useState(Object.assign({}, defaultDraftState)); + var draft = draftState[0]; + var setDraft = draftState[1]; + + var appliedState = useState(Object.assign({}, defaultDraftState)); + var applied = appliedState[0]; + var setApplied = appliedState[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 requirementModalState = useState(false); + var requirementModalOpen = requirementModalState[0]; + var setRequirementModalOpen = requirementModalState[1]; + + // 新增抽屉 + var addDrawerOpenState = useState(false); + var addDrawerOpen = addDrawerOpenState[0]; + var setAddDrawerOpen = addDrawerOpenState[1]; + + var addDrawerModeState = useState('add'); // 'add' | 'edit' + var addDrawerMode = addDrawerModeState[0]; + var setAddDrawerMode = addDrawerModeState[1]; + + var editingWarehouseIdState = useState(null); + var editingWarehouseId = editingWarehouseIdState[0]; + var setEditingWarehouseId = editingWarehouseIdState[1]; + + var addFormState = useState({ + warehouseCode: '', + warehouseName: '', + warehouseAddress: '', + warehouseManager: undefined + }); + var addForm = addFormState[0]; + var setAddForm = addFormState[1]; + + var requirementDocContent = [ + '一个「数字化资产ONEOS运管平台」中的「仓库管理」模块', + '#面包屑:运维管理-备件库-仓库管理', + '', + '1.筛选:', + '1.1.仓库名称:选择器,支持输入框内输入内容模糊搜索下拉匹配结果;', + '1.2.创建人:选择器,下拉显示创建人;', + '1.3.创建时间:日期选择器,支持单输入框双日历通过开始-结束时间筛选;', + '', + '2.列表;右侧为新增;', + '2.1.序号:1.2.3.以此类推;', + '2.2.仓库编码:显示仓库编码;', + '2.3.仓库名称:显示仓库名称;', + '2.4.仓库地址:显示仓库地址;', + '2.5.仓库管理人:显示仓库管理人;', + '2.6.创建人:显示创建人;', + '2.7.创建时间:显示创建时间,格式为:YYYY-MM-DD HH:MM;', + '2.8.操作:编辑、删除;', + ' 2.8.1.编辑:跳转仓库设置-编辑页;', + ' 2.8.2.删除:点击进行二次确认,确认后判断仓库是否为空,如果仓库非空则提示:清空仓库后才可删除该仓库,仓库为空则提示:删除成功;' + ].join('\n'); + + var creatorOptions = useMemo(function () { + // 创建人下拉来源:固定示例 + 兼容 mock 数据变更 + var set = new Set(); + setWarehouses; // keep linter friendly for unused setters in some generators + (warehouses || []).forEach(function (r) { if (r && r.creatorName) set.add(r.creatorName); }); + if (set.size === 0) { + ['张明', '王芳', '李华', '赵强', '陈静'].forEach(function (v) { set.add(v); }); + } + return Array.from(set).map(function (v) { return { value: v, label: v }; }); + }, [warehouses]); + + var warehouseManagerOptions = useMemo(function () { + var set = new Set(); + (warehouses || []).forEach(function (r) { if (r && r.warehouseManager) set.add(r.warehouseManager); }); + if (set.size === 0) { + ['张明', '王芳', '李华', '赵强', '陈静'].forEach(function (v) { set.add(v); }); + } + return Array.from(set).map(function (v) { return { value: v, label: v }; }); + }, [warehouses]); + + var warehouseNameOptions = useMemo(function () { + var set = new Set(); + (warehouses || []).forEach(function (r) { if (r && r.warehouseName) set.add(r.warehouseName); }); + return Array.from(set).map(function (v) { return { value: v, label: v }; }); + }, [warehouses]); + + var filtered = useMemo(function () { + return (warehouses || []).filter(function (r) { + if (applied.warehouseName && r.warehouseName !== applied.warehouseName) return false; + if (applied.creatorName && r.creatorName !== applied.creatorName) return false; + if (!matchRange(r.createTime, applied.createTimeRange)) return false; + return true; + }); + }, [warehouses, applied]); + + var paged = useMemo(function () { + var start = (page - 1) * pageSize; + return filtered.slice(start, start + pageSize); + }, [filtered, page, pageSize]); + + var serialStart = (page - 1) * pageSize; + + var handleQuery = useCallback(function () { + setApplied({ + warehouseName: draft.warehouseName, + creatorName: draft.creatorName, + createTimeRange: draft.createTimeRange + }); + setPage(1); + }, [draft]); + + var handleReset = useCallback(function () { + setDraft(Object.assign({}, defaultDraftState)); + setApplied(Object.assign({}, defaultDraftState)); + setPage(1); + }, []); + + var handleAdd = useCallback(function () { + setAddDrawerMode('add'); + setEditingWarehouseId(null); + setAddForm({ warehouseCode: '', warehouseName: '', warehouseAddress: '', warehouseManager: undefined }); + setAddDrawerOpen(true); + }, []); + + var handleEdit = useCallback(function (row) { + if (!row) return; + setAddDrawerMode('edit'); + setEditingWarehouseId(row.id); + setAddForm({ + warehouseCode: row.warehouseCode || '', + warehouseName: row.warehouseName || '', + warehouseAddress: row.warehouseAddress || '', + warehouseManager: row.warehouseManager + }); + setAddDrawerOpen(true); + }, []); + + var handleAddCancel = useCallback(function () { + setAddDrawerOpen(false); + setAddDrawerMode('add'); + setEditingWarehouseId(null); + setAddForm({ warehouseCode: '', warehouseName: '', warehouseAddress: '', warehouseManager: undefined }); + }, []); + + function nowYMDHM() { + var d = new Date(); + return d.getFullYear() + '-' + pad2(d.getMonth() + 1) + '-' + pad2(d.getDate()) + ' ' + pad2(d.getHours()) + ':' + pad2(d.getMinutes()); + } + + var handleAddSubmit = useCallback(function () { + var code = String(addForm.warehouseCode || '').trim(); + var name = String(addForm.warehouseName || '').trim(); + var address = String(addForm.warehouseAddress || '').trim(); + var manager = addForm.warehouseManager ? addForm.warehouseManager : undefined; + + // 仅仓库编码、仓库名称必填;地址/管理人选填 + if (!code) { + message.warning('请填写仓库编码'); + return; + } + if (!name) { + message.warning('请填写仓库名称'); + return; + } + + if (addDrawerMode === 'edit') { + if (!editingWarehouseId) { + message.error('无法定位要编辑的仓库记录'); + return; + } + setWarehouses(function (prev) { + return (prev || []).map(function (r) { + if (!r || r.id !== editingWarehouseId) return r; + return Object.assign({}, r, { + warehouseCode: code, + warehouseName: name, + warehouseAddress: address, + warehouseManager: manager + }); + }); + }); + setAddDrawerOpen(false); + setAddDrawerMode('add'); + setEditingWarehouseId(null); + message.success('编辑成功'); + return; + } + + var item = { + id: 'wh' + Date.now(), + warehouseCode: code, + warehouseName: name, + warehouseAddress: address, + warehouseManager: manager, + creatorName: '当前用户', + createTime: nowYMDHM(), + itemCount: 0 + }; + + setWarehouses(function (prev) { + return [item].concat(prev || []); + }); + setAddDrawerOpen(false); + setAddDrawerMode('add'); + setEditingWarehouseId(null); + message.success('新增成功'); + }, [addForm, addDrawerMode, editingWarehouseId, setWarehouses, setAddDrawerMode, setEditingWarehouseId]); + + var handleDelete = useCallback(function (row) { + if (!row) return; + Modal.confirm({ + title: '确认删除', + content: '请确认是否删除该仓库', + okText: '确认', + cancelText: '取消', + onOk: function () { + if (row.itemCount > 0) { + message.warning('清空仓库后才可删除该仓库'); + return; + } + setWarehouses(function (prev) { + return (prev || []).filter(function (r) { return r.id !== row.id; }); + }); + message.success('删除成功'); + } + }); + }, [setWarehouses]); + + var columns = useMemo(function () { + return [ + { + title: '序号', + key: 'serial', + width: 70, + fixed: 'left', + render: function (_, r, idx) { + return serialStart + idx + 1; + } + }, + { title: '仓库编码', dataIndex: 'warehouseCode', key: 'warehouseCode', width: 160, fixed: 'left' }, + { title: '仓库名称', dataIndex: 'warehouseName', key: 'warehouseName', width: 180, ellipsis: true }, + { title: '仓库地址', dataIndex: 'warehouseAddress', key: 'warehouseAddress', width: 240, ellipsis: true }, + { title: '仓库管理人', dataIndex: 'warehouseManager', key: 'warehouseManager', width: 140 }, + { title: '创建人', dataIndex: 'creatorName', key: 'creatorName', width: 120 }, + { + title: '创建时间', + dataIndex: 'createTime', + key: 'createTime', + width: 200, + render: function (v) { return fmtYMDHM(v); } + }, + { + title: '操作', + key: 'action', + width: 180, + fixed: 'right', + render: function (_, r) { + return React.createElement('div', { style: { display: 'flex', gap: 8, alignItems: 'center' } }, + React.createElement(Button, { type: 'link', size: 'small', onClick: function () { handleEdit(r); } }, '编辑'), + React.createElement(Button, { type: 'link', size: 'small', danger: true, onClick: function () { handleDelete(r); } }, '删除') + ); + } + } + ]; + }, [serialStart, handleDelete]); + + return React.createElement('div', { style: layoutStyle }, + React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16, flexWrap: 'wrap', gap: 8 } }, + React.createElement(Breadcrumb, { items: [{ title: '运维管理' }, { title: '备件库' }, { title: '仓库管理' }] }), + React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalOpen(true); } }, '查看需求说明') + ), + + React.createElement(Modal, { + title: '需求说明', + open: requirementModalOpen, + onCancel: function () { setRequirementModalOpen(false); }, + width: 720, + footer: React.createElement(Button, { onClick: function () { setRequirementModalOpen(false); } }, '关闭'), + bodyStyle: { maxHeight: '70vh', overflow: 'auto' } + }, React.createElement('div', { style: { whiteSpace: 'pre-wrap', fontSize: 13, lineHeight: 1.6, color: 'rgba(0,0,0,0.85)' } }, requirementDocContent)), + + React.createElement(Card, { style: { marginBottom: 16 } }, + React.createElement('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '16px 24px', alignItems: 'start' } }, + React.createElement('div', { style: filterItemStyle }, + React.createElement('div', { style: filterLabelStyle }, '仓库名称'), + React.createElement(Select, { + placeholder: '请输入或选择仓库名称', + style: filterControlStyle, + value: draft.warehouseName, + onChange: function (v) { setDraft(function (p) { return Object.assign({}, p, { warehouseName: v }); }); }, + allowClear: true, + showSearch: true, + options: warehouseNameOptions, + filterOption: filterOption + }) + ), + + React.createElement('div', { style: filterItemStyle }, + React.createElement('div', { style: filterLabelStyle }, '创建人'), + React.createElement(Select, { + placeholder: '请选择创建人', + style: filterControlStyle, + value: draft.creatorName, + onChange: function (v) { setDraft(function (p) { return Object.assign({}, p, { creatorName: v }); }); }, + allowClear: true, + showSearch: true, + options: creatorOptions + }) + ), + + React.createElement('div', { style: filterItemStyle }, + React.createElement('div', { style: filterLabelStyle }, '创建时间'), + React.createElement(RangePicker, { + style: filterControlStyle, + value: draft.createTimeRange, + onChange: function (v) { setDraft(function (p) { return Object.assign({}, p, { createTimeRange: v }); }); }, + placeholder: ['开始时间', '结束时间'], + showTime: false + }) + ) + ), + + 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('div', { style: { display: 'flex', justifyContent: 'flex-end', marginBottom: 12 } }, + React.createElement(Button, { type: 'primary', onClick: handleAdd }, '新增') + ), + + React.createElement('style', null, tableSingleLineStyle), + React.createElement('div', { className: 'warehouse-list-table' }, + React.createElement(Table, { + rowKey: 'id', + columns: columns, + dataSource: paged, + scroll: { x: 1200 }, + size: 'small', + pagination: { + current: page, + pageSize: pageSize, + total: filtered.length, + showSizeChanger: true, + showQuickJumper: true, + showTotal: function (t) { return '共 ' + t + ' 条'; }, + onChange: function (pg, ps) { setPage(pg); if (ps) setPageSize(ps); } + } + }) + ) + ), + + React.createElement(Drawer, { + title: addDrawerMode === 'edit' ? '编辑仓库' : '新增仓库', + placement: 'right', + width: 460, + open: addDrawerOpen, + onClose: handleAddCancel, + styles: { body: { padding: 24 } }, + footer: React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: 8 } }, + React.createElement(Button, { type: 'primary', onClick: handleAddSubmit }, '提交'), + React.createElement(Button, { onClick: handleAddCancel }, '取消') + ) + }, + React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 14 } }, + React.createElement('div', null, + React.createElement('div', { style: { marginBottom: 6, color: '#666', fontSize: 14 } }, '仓库编码'), + React.createElement(Input, { + value: addForm.warehouseCode, + disabled: addDrawerMode === 'edit', + onChange: function (e) { setAddForm(function (p) { return Object.assign({}, p, { warehouseCode: e && e.target ? e.target.value : '' }); }); }, + placeholder: '请输入仓库编码' + }) + ), + React.createElement('div', null, + React.createElement('div', { style: { marginBottom: 6, color: '#666', fontSize: 14 } }, '仓库名称'), + React.createElement(Input, { + value: addForm.warehouseName, + onChange: function (e) { setAddForm(function (p) { return Object.assign({}, p, { warehouseName: e && e.target ? e.target.value : '' }); }); }, + placeholder: '请输入仓库名称' + }) + ), + React.createElement('div', null, + React.createElement('div', { style: { marginBottom: 6, color: '#666', fontSize: 14 } }, '仓库地址'), + React.createElement(Input, { + value: addForm.warehouseAddress, + onChange: function (e) { setAddForm(function (p) { return Object.assign({}, p, { warehouseAddress: e && e.target ? e.target.value : '' }); }); }, + placeholder: '请输入仓库地址' + }) + ), + React.createElement('div', null, + React.createElement('div', { style: { marginBottom: 6, color: '#666', fontSize: 14 } }, '仓库管理人'), + React.createElement(Select, { + placeholder: '请选择人员', + value: addForm.warehouseManager, + onChange: function (v) { setAddForm(function (p) { return Object.assign({}, p, { warehouseManager: v }); }); }, + style: { width: '100%' }, + allowClear: true, + showSearch: true, + filterOption: filterOption, + options: warehouseManagerOptions + }) + ) + ) + ) + ); +}; + diff --git a/web端/运维管理/备件管理/备件库存.jsx b/web端/运维管理/备件管理/备件库存.jsx new file mode 100644 index 0000000..0dcb69f --- /dev/null +++ b/web端/运维管理/备件管理/备件库存.jsx @@ -0,0 +1,584 @@ +// 【重要】必须使用 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 Card = antd.Card; + var Button = antd.Button; + var Table = antd.Table; + var Select = antd.Select; + var Modal = antd.Modal; + var Drawer = antd.Drawer; + var Form = antd.Form; + var Input = antd.Input; + var Tabs = antd.Tabs; + var message = antd.message; + + function filterOption(input, option) { + var label = (option && (option.label || option.children)) || ''; + return String(label).toLowerCase().indexOf(String(input || '').toLowerCase()) >= 0; + } + + var layoutStyle = { padding: '16px 24px', background: '#f5f5f5', minHeight: '100vh' }; + var filterLabelStyle = { marginBottom: 6, fontSize: 14, color: 'rgba(0,0,0,0.65)' }; + var filterItemStyle = { marginBottom: 12 }; + var filterControlStyle = { width: '100%' }; + var tableStyle = '.spare-stock-table .ant-table-thead th,.spare-stock-table .ant-table-tbody td{white-space:nowrap;}' + + '.spare-stock-table .col-stock-qty{text-align:left !important;}' + + '.spare-stock-table th.col-stock-qty{text-align:left !important;}'; + var tabsAlignStyle = '.spare-stock-main-tabs .ant-tabs-nav{align-items:center;margin-bottom:0;}'; + + // 适配车型:选项 value 与列表一致;label 附带关联型号(与基本数据-型号参数对应) + var vehicleModelSpecMap = { + '飞驰49t': '海格牌KLQ5180XYKFCEV', + '宇通49t': 'KLQ6129', + '现代4.5t': 'BJ5180', + '跃进4.5t': '帅铃Q6', + '苏龙18t': '海格牌KLQ5180XYKFCEV' + }; + var allVehicleModelOptions = useMemo(function () { + return [ + { value: '飞驰49t', label: '飞驰49t' }, + { value: '宇通49t', label: '宇通49t' }, + { value: '现代4.5t', label: '现代4.5t' }, + { value: '跃进4.5t', label: '跃进4.5t' }, + { value: '苏龙18t', label: '苏龙18t' } + ]; + }, []); + var addDrawerVehicleOptions = useMemo(function () { + return allVehicleModelOptions.map(function (opt) { + var spec = vehicleModelSpecMap[opt.value]; + return { + value: opt.value, + label: spec ? opt.value + '(型号:' + spec + ')' : opt.label + }; + }); + }, [allVehicleModelOptions]); + + // 样例数据:新件库 11 条 + 易损件库 5 条(入库/出库与业务截图一致) + var listState = useState(function () { + var XJ = '新件库'; + var XJCode = 'WH-XJK'; + var YS = '易损件库'; + var YSCode = 'WH-YSK'; + return [ + { id: 'ss-xj-01', warehouseCode: XJCode, warehouseName: XJ, spareCode: 'FC49t-XJ-0001', spareName: '去离子柱(飞驰49t-新件)', vehicleModels: ['飞驰49t'], stockQty: 34, totalInbound: 42, totalOutbound: 8 }, + { id: 'ss-xj-02', warehouseCode: XJCode, warehouseName: XJ, spareCode: 'FC49t-XJ-0002', spareName: '燃料电池空气滤芯(飞驰49t-新件)', vehicleModels: ['飞驰49t'], stockQty: 28, totalInbound: 55, totalOutbound: 27 }, + { id: 'ss-xj-03', warehouseCode: XJCode, warehouseName: XJ, spareCode: 'YT49t-XJ-0001', spareName: '去离子柱(宇通49t-新件)', vehicleModels: ['宇通49t'], stockQty: 20, totalInbound: 28, totalOutbound: 8 }, + { id: 'ss-xj-04', warehouseCode: XJCode, warehouseName: XJ, spareCode: 'YT49t-XJ-0002', spareName: '燃料电池空气滤芯(宇通49t-新件)', vehicleModels: ['宇通49t'], stockQty: 12, totalInbound: 18, totalOutbound: 6 }, + { id: 'ss-xj-05', warehouseCode: XJCode, warehouseName: XJ, spareCode: 'YT49t-XJ-0005', spareName: '燃料电池去离子水(宇通49t-新件)', vehicleModels: ['宇通49t'], stockQty: 5, totalInbound: 5, totalOutbound: 0 }, + { id: 'ss-xj-06', warehouseCode: XJCode, warehouseName: XJ, spareCode: 'YT49t-XJ-0029', spareName: '小循环滤网(宇通49t-新件)', vehicleModels: ['宇通49t'], stockQty: 11, totalInbound: 13, totalOutbound: 2 }, + { id: 'ss-xj-07', warehouseCode: XJCode, warehouseName: XJ, spareCode: 'YT49t-XJ-0030', spareName: '大循环滤网(宇通49t-新件)', vehicleModels: ['宇通49t'], stockQty: 10, totalInbound: 10, totalOutbound: 0 }, + { id: 'ss-xj-08', warehouseCode: XJCode, warehouseName: XJ, spareCode: 'XD4.5t-XJ-0008', spareName: '后尾灯(现代4.5t-新件)', vehicleModels: ['现代4.5t'], stockQty: 18, totalInbound: 40, totalOutbound: 22 }, + { id: 'ss-xj-09', warehouseCode: XJCode, warehouseName: XJ, spareCode: 'YJ4.5t-XJ-0035', spareName: '12V蓄电池(跃进4.5t-新件)', vehicleModels: ['跃进4.5t'], stockQty: 23, totalInbound: 26, totalOutbound: 3 }, + { id: 'ss-xj-10', warehouseCode: XJCode, warehouseName: XJ, spareCode: 'SL18t-XJ-0001', spareName: '去离子柱(苏龙18t-新件)', vehicleModels: ['苏龙18t'], stockQty: 11, totalInbound: 12, totalOutbound: 1 }, + { id: 'ss-xj-11', warehouseCode: XJCode, warehouseName: XJ, spareCode: 'SL18t-XJ-0002', spareName: '燃料电池空气滤芯(苏龙18t-新件)', vehicleModels: ['苏龙18t'], stockQty: 16, totalInbound: 16, totalOutbound: 0 }, + { id: 'ss-ys-01', warehouseCode: YSCode, warehouseName: YS, spareCode: 'TY-YS-0001', spareName: 'H1灯泡-易损件', vehicleModels: [], stockQty: 55, totalInbound: 55, totalOutbound: 0 }, + { id: 'ss-ys-02', warehouseCode: YSCode, warehouseName: YS, spareCode: 'TY-YS-0002', spareName: 'H3灯泡-易损件', vehicleModels: [], stockQty: 50, totalInbound: 50, totalOutbound: 0 }, + { id: 'ss-ys-03', warehouseCode: YSCode, warehouseName: YS, spareCode: 'TY-YS-0003', spareName: 'H7灯泡-易损件', vehicleModels: [], stockQty: 53, totalInbound: 53, totalOutbound: 0 }, + { id: 'ss-ys-04', warehouseCode: YSCode, warehouseName: YS, spareCode: 'TY-YS-0004', spareName: '小灯泡-易损件', vehicleModels: [], stockQty: 52, totalInbound: 52, totalOutbound: 0 }, + { id: 'ss-ys-05', warehouseCode: YSCode, warehouseName: YS, spareCode: 'TY-YS-0005', spareName: '2405灯泡-易损件', vehicleModels: [], stockQty: 110, totalInbound: 110, totalOutbound: 0 } + ]; + }); + var list = listState[0]; + + // 入库 / 出库记录样例(原型) + var inboundListState = useState(function () { + return [ + { id: 'in-001', billNo: 'RK20250227001', inboundTime: '2025-02-27 09:12', warehouseName: '新件库', spareCode: 'FC49t-XJ-0001', spareName: '去离子柱(飞驰49t-新件)', qty: 4, operator: '张三' }, + { id: 'in-002', billNo: 'RK20250226088', inboundTime: '2025-02-26 14:30', warehouseName: '新件库', spareCode: 'YT49t-XJ-0002', spareName: '燃料电池空气滤芯(宇通49t-新件)', qty: 6, operator: '李四' }, + { id: 'in-003', billNo: 'RK20250225102', inboundTime: '2025-02-25 11:05', warehouseName: '易损件库', spareCode: 'TY-YS-0001', spareName: 'H1灯泡-易损件', qty: 20, operator: '王五' }, + { id: 'in-004', billNo: 'RK20250224056', inboundTime: '2025-02-24 16:20', warehouseName: '新件库', spareCode: 'SL18t-XJ-0002', spareName: '燃料电池空气滤芯(苏龙18t-新件)', qty: 8, operator: '张三' } + ]; + }); + var inboundList = inboundListState[0]; + + var outboundListState = useState(function () { + return [ + { id: 'out-001', billNo: 'CK20250227012', outboundTime: '2025-02-27 10:08', warehouseName: '新件库', spareCode: 'FC49t-XJ-0002', spareName: '燃料电池空气滤芯(飞驰49t-新件)', qty: 2, recipient: '维修一组' }, + { id: 'out-002', billNo: 'CK20250226995', outboundTime: '2025-02-26 09:45', warehouseName: '新件库', spareCode: 'XD4.5t-XJ-0008', spareName: '后尾灯(现代4.5t-新件)', qty: 1, recipient: '维修二组' }, + { id: 'out-003', billNo: 'CK20250225880', outboundTime: '2025-02-25 13:22', warehouseName: '易损件库', spareCode: 'TY-YS-0003', spareName: 'H7灯泡-易损件', qty: 5, recipient: '场站 A' }, + { id: 'out-004', billNo: 'CK20250224771', outboundTime: '2025-02-24 08:50', warehouseName: '新件库', spareCode: 'YJ4.5t-XJ-0035', spareName: '12V蓄电池(跃进4.5t-新件)', qty: 1, recipient: '维修一组' } + ]; + }); + var outboundList = outboundListState[0]; + + var inboundPageState = useState(1); + var inboundPage = inboundPageState[0]; + var setInboundPage = inboundPageState[1]; + var inboundPageSizeState = useState(10); + var inboundPageSize = inboundPageSizeState[0]; + var setInboundPageSize = inboundPageSizeState[1]; + + var outboundPageState = useState(1); + var outboundPage = outboundPageState[0]; + var setOutboundPage = outboundPageState[1]; + var outboundPageSizeState = useState(10); + var outboundPageSize = outboundPageSizeState[0]; + var setOutboundPageSize = outboundPageSizeState[1]; + + var mainTabState = useState('stock'); + var mainTab = mainTabState[0]; + var setMainTab = mainTabState[1]; + + var defaultDraft = { + warehouseName: undefined, + spareCode: undefined, + spareName: undefined, + vehicleModels: [] + }; + + var draftState = useState(Object.assign({}, defaultDraft)); + var draft = draftState[0]; + var setDraft = draftState[1]; + + var appliedState = useState(Object.assign({}, defaultDraft)); + var applied = appliedState[0]; + var setApplied = appliedState[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 requirementModalState = useState(false); + var requirementModalOpen = requirementModalState[0]; + var setRequirementModalOpen = requirementModalState[1]; + + var addDrawerState = useState(false); + var addDrawerOpen = addDrawerState[0]; + var setAddDrawerOpen = addDrawerState[1]; + + var addForm = Form.useForm()[0]; + + var requirementDocContent = [ + '一个「数字化资产ONEOS运管平台」中的「备件库存」模块', + '#面包屑:运维管理-备件库-备件库存', + '', + '1.筛选:', + '1.1.仓库名称:选择器,支持输入框内输入模糊搜索,下拉匹配正确项;', + '1.2.备件编码:选择器,支持输入框内输入模糊搜索,下拉匹配正确项;', + '1.3.备件名称:选择器,支持输入框内输入模糊搜索,下拉匹配正确项;', + '1.4.适配车型:选择器,支持多选,显示所有型号;', + '', + '2.列表;右侧为新增物料信息、导出按钮', + '2.1.序号:1.2.3.以此类推;', + '2.2.仓库编码:显示仓库编码;', + '2.3.仓库名称:显示仓库名称;', + '2.4.备件编码:显示备件编码;', + '2.5.备件名称:显示备件名称;', + '2.6.适配车型:显示适配车型,一个备件可能有多个车型;', + '2.7.库存数量:显示库存数量;', + '2.8.累积入库数量:显示该备件入库数量总和;', + '2.9.累积出库数量:显示该备件出库数量总和;', + '2.10.右下角为分页功能,支持选择单页显示数据条数;' + ].join('\n'); + + var warehouseNameOptions = useMemo(function () { + var set = new Set(); + (list || []).forEach(function (r) { if (r && r.warehouseName) set.add(r.warehouseName); }); + return Array.from(set).map(function (v) { return { value: v, label: v }; }); + }, [list]); + + var spareCodeOptions = useMemo(function () { + var set = new Set(); + (list || []).forEach(function (r) { if (r && r.spareCode) set.add(r.spareCode); }); + return Array.from(set).map(function (v) { return { value: v, label: v }; }); + }, [list]); + + var spareNameOptions = useMemo(function () { + var set = new Set(); + (list || []).forEach(function (r) { if (r && r.spareName) set.add(r.spareName); }); + return Array.from(set).map(function (v) { return { value: v, label: v }; }); + }, [list]); + + function rowMatchesVehicleModels(row, selected) { + if (!selected || selected.length === 0) return true; + var models = row.vehicleModels || []; + for (var i = 0; i < selected.length; i++) { + if (models.indexOf(selected[i]) >= 0) return true; + } + return false; + } + + var filtered = useMemo(function () { + return (list || []).filter(function (r) { + if (applied.warehouseName && r.warehouseName !== applied.warehouseName) return false; + if (applied.spareCode && r.spareCode !== applied.spareCode) return false; + if (applied.spareName && r.spareName !== applied.spareName) return false; + if (!rowMatchesVehicleModels(r, applied.vehicleModels)) return false; + return true; + }); + }, [list, applied]); + + var paged = useMemo(function () { + var start = (page - 1) * pageSize; + return filtered.slice(start, start + pageSize); + }, [filtered, page, pageSize]); + + var serialStart = (page - 1) * pageSize; + + var handleQuery = useCallback(function () { + setApplied({ + warehouseName: draft.warehouseName, + spareCode: draft.spareCode, + spareName: draft.spareName, + vehicleModels: draft.vehicleModels ? draft.vehicleModels.slice() : [] + }); + setPage(1); + }, [draft]); + + var handleReset = useCallback(function () { + setDraft(Object.assign({}, defaultDraft)); + setApplied(Object.assign({}, defaultDraft)); + setPage(1); + }, []); + + var handleAdd = useCallback(function () { + addForm.resetFields(); + setAddDrawerOpen(true); + }, [addForm]); + + var handleAddDrawerClose = useCallback(function () { + setAddDrawerOpen(false); + addForm.resetFields(); + }, [addForm]); + + var handleAddDrawerSubmit = useCallback(function () { + addForm.validateFields().then(function () { + message.success('已保存(原型,联调时对接建档接口)'); + setAddDrawerOpen(false); + addForm.resetFields(); + }).catch(function () {}); + }, [addForm]); + + var handleExport = useCallback(function () { + message.success('导出当前筛选结果(原型,联调时对接导出接口)'); + }, []); + + var columns = useMemo(function () { + return [ + { + title: '序号', + key: 'serial', + width: 70, + fixed: 'left', + render: function (_, r, idx) { + return serialStart + idx + 1; + } + }, + { title: '仓库编码', dataIndex: 'warehouseCode', key: 'warehouseCode', width: 120, fixed: 'left' }, + { title: '仓库名称', dataIndex: 'warehouseName', key: 'warehouseName', width: 160, ellipsis: true }, + { title: '备件编码', dataIndex: 'spareCode', key: 'spareCode', width: 120 }, + { title: '备件名称', dataIndex: 'spareName', key: 'spareName', width: 260, ellipsis: true }, + { + title: '适配车型', + key: 'vehicleModels', + width: 220, + ellipsis: true, + render: function (_, r) { + var arr = r.vehicleModels || []; + return arr.length ? arr.join('、') : '-'; + } + }, + { + title: '库存数量', + dataIndex: 'stockQty', + key: 'stockQty', + width: 100, + align: 'left', + className: 'col-stock-qty', + onHeaderCell: function () { return { className: 'col-stock-qty' }; }, + render: function (v) { + if (v == null) return '-'; + return React.createElement('span', { + style: { + color: 'var(--ant-color-primary, #1677ff)', + fontWeight: 600, + fontSize: 15 + } + }, v); + } + }, + { + title: '累积入库数量', + dataIndex: 'totalInbound', + key: 'totalInbound', + width: 130, + render: function (v) { return v != null ? Number(v).toFixed(1) : '-'; } + }, + { + title: '累积出库数量', + dataIndex: 'totalOutbound', + key: 'totalOutbound', + width: 130, + render: function (v) { return v != null ? Number(v).toFixed(1) : '-'; } + } + ]; + }, [serialStart]); + + var inboundSerialStart = (inboundPage - 1) * inboundPageSize; + var inboundPaged = useMemo(function () { + var start = (inboundPage - 1) * inboundPageSize; + return (inboundList || []).slice(start, start + inboundPageSize); + }, [inboundList, inboundPage, inboundPageSize]); + + var outboundSerialStart = (outboundPage - 1) * outboundPageSize; + var outboundPaged = useMemo(function () { + var start = (outboundPage - 1) * outboundPageSize; + return (outboundList || []).slice(start, start + outboundPageSize); + }, [outboundList, outboundPage, outboundPageSize]); + + var inboundColumns = useMemo(function () { + return [ + { title: '序号', key: 'serial', width: 70, render: function (_, r, idx) { return inboundSerialStart + idx + 1; } }, + { title: '入库单号', dataIndex: 'billNo', key: 'billNo', width: 150 }, + { title: '入库时间', dataIndex: 'inboundTime', key: 'inboundTime', width: 160 }, + { title: '仓库名称', dataIndex: 'warehouseName', key: 'warehouseName', width: 120 }, + { title: '备件编码', dataIndex: 'spareCode', key: 'spareCode', width: 130 }, + { title: '备件名称', dataIndex: 'spareName', key: 'spareName', ellipsis: true }, + { title: '入库数量', dataIndex: 'qty', key: 'qty', width: 100 }, + { title: '经办人', dataIndex: 'operator', key: 'operator', width: 100 } + ]; + }, [inboundSerialStart]); + + var outboundColumns = useMemo(function () { + return [ + { title: '序号', key: 'serial', width: 70, render: function (_, r, idx) { return outboundSerialStart + idx + 1; } }, + { title: '出库单号', dataIndex: 'billNo', key: 'billNo', width: 150 }, + { title: '出库时间', dataIndex: 'outboundTime', key: 'outboundTime', width: 160 }, + { title: '仓库名称', dataIndex: 'warehouseName', key: 'warehouseName', width: 120 }, + { title: '备件编码', dataIndex: 'spareCode', key: 'spareCode', width: 130 }, + { title: '备件名称', dataIndex: 'spareName', key: 'spareName', ellipsis: true }, + { title: '出库数量', dataIndex: 'qty', key: 'qty', width: 100 }, + { title: '领用方', dataIndex: 'recipient', key: 'recipient', width: 120 } + ]; + }, [outboundSerialStart]); + + var tabItems = useMemo(function () { + var stockTab = React.createElement(Card, { style: { marginTop: 0 } }, + React.createElement('style', null, tableStyle), + React.createElement('div', { className: 'spare-stock-table' }, + React.createElement(Table, { + rowKey: 'id', + columns: columns, + dataSource: paged, + scroll: { x: 1460 }, + size: 'small', + pagination: { + current: page, + pageSize: pageSize, + total: filtered.length, + showSizeChanger: true, + showQuickJumper: true, + showTotal: function (t) { return '共 ' + t + ' 条'; }, + onChange: function (pg, ps) { setPage(pg); if (ps) setPageSize(ps); } + } + }) + ) + ); + + var inboundTab = React.createElement(Card, null, + React.createElement(Table, { + rowKey: 'id', + columns: inboundColumns, + dataSource: inboundPaged, + size: 'small', + scroll: { x: 1200 }, + pagination: { + current: inboundPage, + pageSize: inboundPageSize, + total: (inboundList || []).length, + showSizeChanger: true, + showQuickJumper: true, + showTotal: function (t) { return '共 ' + t + ' 条'; }, + onChange: function (pg, ps) { setInboundPage(pg); if (ps) setInboundPageSize(ps); } + } + }) + ); + + var outboundTab = React.createElement(Card, null, + React.createElement(Table, { + rowKey: 'id', + columns: outboundColumns, + dataSource: outboundPaged, + size: 'small', + scroll: { x: 1200 }, + pagination: { + current: outboundPage, + pageSize: outboundPageSize, + total: (outboundList || []).length, + showSizeChanger: true, + showQuickJumper: true, + showTotal: function (t) { return '共 ' + t + ' 条'; }, + onChange: function (pg, ps) { setOutboundPage(pg); if (ps) setOutboundPageSize(ps); } + } + }) + ); + + return [ + { key: 'stock', label: '备件库存', children: stockTab }, + { key: 'inbound', label: '入库记录', children: inboundTab }, + { key: 'outbound', label: '出库记录', children: outboundTab } + ]; + }, [ + columns, paged, page, pageSize, filtered.length, + inboundColumns, inboundPaged, inboundPage, inboundPageSize, inboundList, + outboundColumns, outboundPaged, outboundPage, outboundPageSize, outboundList, + tableStyle + ]); + + return React.createElement('div', { style: layoutStyle }, + React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16, flexWrap: 'wrap', gap: 8 } }, + React.createElement(Breadcrumb, { items: [{ title: '运维管理' }, { title: '备件库' }, { title: '备件库存' }] }), + React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalOpen(true); } }, '查看需求说明') + ), + + React.createElement(Modal, { + title: '需求说明', + open: requirementModalOpen, + onCancel: function () { setRequirementModalOpen(false); }, + width: 720, + footer: React.createElement(Button, { onClick: function () { setRequirementModalOpen(false); } }, '关闭'), + bodyStyle: { maxHeight: '70vh', overflow: 'auto' } + }, React.createElement('div', { style: { whiteSpace: 'pre-wrap', fontSize: 13, lineHeight: 1.6, color: 'rgba(0,0,0,0.85)' } }, requirementDocContent)), + + React.createElement(Drawer, { + title: '新增物料信息', + placement: 'right', + width: 440, + open: addDrawerOpen, + onClose: handleAddDrawerClose, + destroyOnClose: true, + footer: React.createElement('div', { style: { textAlign: 'right' } }, + React.createElement(Button, { onClick: handleAddDrawerClose, style: { marginRight: 8 } }, '取消'), + React.createElement(Button, { type: 'primary', onClick: handleAddDrawerSubmit }, '确定') + ) + }, + React.createElement(Form, { + form: addForm, + layout: 'vertical', + preserve: false + }, + React.createElement(Form.Item, { + name: 'spareCode', + label: '备件编码', + rules: [{ required: true, message: '请输入备件编码' }] + }, React.createElement(Input, { placeholder: '请输入备件编码', allowClear: true })), + React.createElement(Form.Item, { + name: 'spareName', + label: '备件名称', + rules: [{ required: true, message: '请输入备件名称' }] + }, React.createElement(Input, { placeholder: '请输入备件名称', allowClear: true })), + React.createElement(Form.Item, { + name: 'unit', + label: '计算单位', + rules: [{ required: true, message: '请输入计算单位' }] + }, React.createElement(Input, { placeholder: '请输入计算单位', allowClear: true })), + React.createElement(Form.Item, { + name: 'vehicleModels', + label: '适配车型', + rules: [{ required: true, message: '请选择适配车型', type: 'array', min: 1 }] + }, React.createElement(Select, { + mode: 'multiple', + placeholder: '请选择车型(可多选,选项含关联型号)', + allowClear: true, + options: addDrawerVehicleOptions, + showSearch: true, + filterOption: filterOption, + maxTagCount: 'responsive' + })), + React.createElement(Form.Item, { + name: 'alertThreshold', + label: '告警阈值', + extra: React.createElement('span', { style: { color: 'rgba(0,0,0,0.45)', fontSize: 12 } }, '备件库存低于阈值会推送提示用户') + }, React.createElement(Input, { placeholder: '请设置备件告警阈值', allowClear: true })) + ) + ), + + mainTab === 'stock' ? React.createElement(Card, { style: { marginBottom: 16 } }, + React.createElement('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', gap: '16px 24px', alignItems: 'start' } }, + React.createElement('div', { style: filterItemStyle }, + React.createElement('div', { style: filterLabelStyle }, '仓库名称'), + React.createElement(Select, { + placeholder: '请输入或选择仓库名称', + style: filterControlStyle, + value: draft.warehouseName, + onChange: function (v) { setDraft(function (p) { return Object.assign({}, p, { warehouseName: v }); }); }, + allowClear: true, + showSearch: true, + options: warehouseNameOptions, + filterOption: filterOption + }) + ), + React.createElement('div', { style: filterItemStyle }, + React.createElement('div', { style: filterLabelStyle }, '备件编码'), + React.createElement(Select, { + placeholder: '请输入或选择备件编码', + style: filterControlStyle, + value: draft.spareCode, + onChange: function (v) { setDraft(function (p) { return Object.assign({}, p, { spareCode: v }); }); }, + allowClear: true, + showSearch: true, + options: spareCodeOptions, + filterOption: filterOption + }) + ), + React.createElement('div', { style: filterItemStyle }, + React.createElement('div', { style: filterLabelStyle }, '备件名称'), + React.createElement(Select, { + placeholder: '请输入或选择备件名称', + style: filterControlStyle, + value: draft.spareName, + onChange: function (v) { setDraft(function (p) { return Object.assign({}, p, { spareName: v }); }); }, + allowClear: true, + showSearch: true, + options: spareNameOptions, + filterOption: filterOption + }) + ), + React.createElement('div', { style: filterItemStyle }, + React.createElement('div', { style: filterLabelStyle }, '适配车型'), + React.createElement(Select, { + mode: 'multiple', + placeholder: '请选择型号(可多选)', + style: filterControlStyle, + value: draft.vehicleModels, + onChange: function (v) { setDraft(function (p) { return Object.assign({}, p, { vehicleModels: v || [] }); }); }, + allowClear: true, + options: allVehicleModelOptions, + maxTagCount: 'responsive' + }) + ) + ), + 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 }, '查询') + ) + ) : null, + + React.createElement('style', null, tabsAlignStyle), + React.createElement(Tabs, { + className: 'spare-stock-main-tabs', + activeKey: mainTab, + onChange: setMainTab, + items: tabItems, + destroyInactiveTabPane: false, + tabBarExtraContent: mainTab === 'stock' + ? React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8 } }, + React.createElement(Button, { type: 'primary', onClick: handleAdd }, '新增物料信息'), + React.createElement(Button, { onClick: handleExport }, '导出') + ) + : null, + tabBarStyle: { marginBottom: 0 } + }) + ); +}; diff --git a/web端/运维管理/车辆业务/异动管理-查看.jsx b/web端/运维管理/车辆业务/异动管理-查看.jsx index f1f3464..98225d7 100644 --- a/web端/运维管理/车辆业务/异动管理-查看.jsx +++ b/web端/运维管理/车辆业务/异动管理-查看.jsx @@ -1,5 +1,5 @@ // 【重要】必须使用 const Component 作为组件变量名 -// 运维管理 - 车辆业务 - 异动管理 - 查看(只读) +// 运维管理 - 车辆业务 - 异动管理 - 查看(布局对齐「结束异动」,全部只读) const Component = function () { var useState = React.useState; @@ -27,10 +27,25 @@ const Component = function () { return null; } - var layoutStyle = { padding: '16px 24px', background: '#f5f5f5', minHeight: '100vh' }; + function fmtPlannedMileage(v) { + if (v == null || v === '') return '-'; + var n = Number(v); + if (isNaN(n)) return String(v); + return n.toFixed(2); + } + + function fmtNum2Display(v) { + if (v === null || v === undefined || v === '') return ''; + var n = Number(v); + if (isNaN(n)) return String(v); + return n.toFixed(2); + } + + var layoutStyle = { padding: '16px 24px', background: '#f5f5f5', minHeight: '100vh', paddingBottom: 80 }; var labelStyle = { marginBottom: 6, fontSize: 14, color: 'rgba(0,0,0,0.65)' }; var formItemStyle = { marginBottom: 12 }; var controlStyle = { width: '100%' }; + var placeholderSelectVehicle = '请先选择车辆'; var destinationTypeOptions = useMemo(function () { return [ @@ -40,24 +55,6 @@ const Component = function () { ]; }, []); - var destinationParkingOptions = useMemo(function () { - return [ - { value: '天河智慧停车场', label: '天河智慧停车场' }, - { value: '南山科技园停车场', label: '南山科技园停车场' }, - { value: '西湖景区停车场', label: '西湖景区停车场' }, - { value: '宁波江北停车场', label: '宁波江北停车场' }, - { value: '张江园区停车场', label: '张江园区停车场' } - ]; - }, []); - - var destinationRepairOptions = useMemo(function () { - return [ - { value: '杭州拱墅维修站', label: '杭州拱墅维修站' }, - { value: '广州天河维修站', label: '广州天河维修站' }, - { value: '上海浦东维修站', label: '上海浦东维修站' } - ]; - }, []); - var changeTypeOptions = useMemo(function () { return [ { value: '维修', label: '维修' }, @@ -77,77 +74,47 @@ const Component = function () { ]; }, []); - // 原型:详情接口返回的只读数据(字段与「异动管理.新增」一致,并含审批状态) - var form = { - startTime: getInitialDateTime('2026-02-20 09:30'), - plannedEndTime: getInitialDateTime('2026-02-22 18:00'), - destinationType: '维修站', - destinationName: '广州天河维修站', - destinationNameOther: '', - changeType: '维修', - plannedMileageKm: '45.50', - remark: '车辆需进站检修制动系统,预计两日内完成。', - approvalStatus: '审批中' - }; + // 原型:详情接口返回(联调时由路由/接口填充);字段布局对齐「结束异动」 + var movementReadonly = useMemo(function () { + return { + startTime: getInitialDateTime('2026-02-20 09:30'), + plannedEndTime: getInitialDateTime('2026-02-22 18:00'), + destinationType: '维修站', + destinationName: '广州天河维修站', + changeType: '维修', + plannedMileageKm: '45.50', + remark: '车辆需进站检修制动系统,预计两日内完成。', + approvalStatus: '审批中', + endDateTime: getInitialDateTime('2026-02-22 17:45') + }; + }, []); - var vehicles = [ - { - id: 1, - plateNo: '粤A12345', - vehicleType: '厢式货车', - brand: '东风', - model: 'DFH1180', - departParking: '天河智慧停车场', - startMileageKm: '15230.12', - startHydrogen: '28.30', - h2Unit: 'MPa', - startElectricKwh: '68.40' - }, - { - id: 2, - plateNo: '浙A11111', - vehicleType: 'SUV', - brand: '小鹏', - model: 'P7', - departParking: '西湖景区停车场', - startMileageKm: '12010.00', - startHydrogen: '60.00', - h2Unit: '%', - startElectricKwh: '55.20' - } - ]; + // 查看页仅展示单辆车(1 条记录;联调时详情接口按单车辆返回) + var vehicles = useMemo(function () { + return [ + { + id: 1, + plateNo: '粤A12345', + vehicleType: '厢式货车', + brand: '东风', + model: 'DFH1180', + departParking: '天河智慧停车场', + startMileageKm: '15230.12', + startHydrogen: '28.30', + h2Unit: 'MPa', + startElectricKwh: '68.40', + endMileageKm: '15280.55', + endHydrogen: '26.80', + endElectricKwh: '70.00' + } + ]; + }, []); - var destinationNameLabel = form.destinationType === '维修站' ? '维修站' : (form.destinationType === '停车场' ? '停车场' : '目的地名称'); + function rowVehicleSelected(r) { + return !!(r && String(r.plateNo || '').trim()); + } - var destinationNameNode = (function () { - if (form.destinationType === '其他') { - return React.createElement(Input, { - value: form.destinationNameOther || '-', - disabled: true - }); - } - var opts = form.destinationType === '维修站' ? destinationRepairOptions : destinationParkingOptions; - return React.createElement(Select, { - style: controlStyle, - value: form.destinationName, - options: opts, - disabled: true - }); - })(); - - var requirementModalState = useState(false); - var requirementModalOpen = requirementModalState[0]; - var setRequirementModalOpen = requirementModalState[1]; - - var requirementDocContent = [ - '一个「数字化资产ONEOS运管平台」中的「异动管理」「查看」模块', - '#面包屑:运维管理-车辆业务-异动管理-查看', - '页面布局与字段与「异动管理.新增」一致,所有表单项均为只读,仅供查询展示。', - '', - '1.异动情况:异动开始/预计结束日期、审批状态、异动目的地、目的地名称、异动类型、预计异动里程、备注 — 全部禁用不可编辑。', - '2.车辆信息:车牌号、车辆类型、品牌、型号、出发停车场、异动开始里程、异动开始氢量、异动开始电量 — 全部禁用;无新增行与删除操作。', - '3.底部仅提供「返回」按钮,返回异动管理列表(原型)。' - ].join('\n'); + var destLabel = movementReadonly.destinationType === '维修站' ? '维修站名称' : (movementReadonly.destinationType === '停车场' ? '停车场名称' : '目的地名称'); var vehicleColumns = useMemo(function () { return [ @@ -155,6 +122,7 @@ const Component = function () { title: '车牌号', key: 'plateNo', width: 140, + fixed: 'left', render: function (_, r) { return React.createElement(Input, { value: r.plateNo || '-', disabled: true }); } @@ -164,7 +132,7 @@ const Component = function () { key: 'vehicleType', width: 120, render: function (_, r) { - return React.createElement(Input, { value: r.vehicleType || '-', disabled: true }); + return React.createElement(Input, { value: rowVehicleSelected(r) ? (r.vehicleType || '-') : '请先选择车辆', disabled: true }); } }, { @@ -172,7 +140,7 @@ const Component = function () { key: 'brand', width: 100, render: function (_, r) { - return React.createElement(Input, { value: r.brand || '-', disabled: true }); + return React.createElement(Input, { value: rowVehicleSelected(r) ? (r.brand || '-') : '请先选择车辆', disabled: true }); } }, { @@ -180,7 +148,7 @@ const Component = function () { key: 'model', width: 120, render: function (_, r) { - return React.createElement(Input, { value: r.model || '-', disabled: true }); + return React.createElement(Input, { value: rowVehicleSelected(r) ? (r.model || '-') : '请先选择车辆', disabled: true }); } }, { @@ -188,7 +156,7 @@ const Component = function () { key: 'departParking', width: 160, render: function (_, r) { - return React.createElement(Input, { value: r.departParking || '-', disabled: true }); + return React.createElement(Input, { value: rowVehicleSelected(r) ? (r.departParking || '-') : '请先选择车辆', disabled: true }); } }, { @@ -196,7 +164,27 @@ const Component = function () { key: 'startMileageKm', width: 160, render: function (_, r) { - return React.createElement(Input, { value: r.startMileageKm || '-', disabled: true, addonAfter: 'km' }); + return React.createElement(Input, { + value: rowVehicleSelected(r) ? fmtNum2Display(r.startMileageKm) : '', + disabled: true, + addonAfter: 'km', + placeholder: rowVehicleSelected(r) ? undefined : placeholderSelectVehicle + }); + } + }, + { + title: '异动结束里程', + key: 'endMileageKm', + width: 160, + render: function (_, r) { + if (!rowVehicleSelected(r)) { + return React.createElement(Input, { value: '', disabled: true, placeholder: placeholderSelectVehicle }); + } + return React.createElement(Input, { + value: fmtNum2Display(r.endMileageKm), + disabled: true, + addonAfter: 'km' + }); } }, { @@ -204,8 +192,27 @@ const Component = function () { key: 'startHydrogen', width: 150, render: function (_, r) { + var unit = rowVehicleSelected(r) ? (r.h2Unit || '') : ''; + return React.createElement(Input, Object.assign({ + value: rowVehicleSelected(r) ? fmtNum2Display(r.startHydrogen) : '', + disabled: true, + placeholder: rowVehicleSelected(r) ? undefined : placeholderSelectVehicle + }, unit ? { addonAfter: unit } : {})); + } + }, + { + title: '异动结束氢量', + key: 'endHydrogen', + width: 150, + render: function (_, r) { + if (!rowVehicleSelected(r)) { + return React.createElement(Input, { value: '', disabled: true, placeholder: placeholderSelectVehicle }); + } var unit = r.h2Unit || ''; - return React.createElement(Input, { value: r.startHydrogen || '-', disabled: true, addonAfter: unit || '-' }); + return React.createElement(Input, Object.assign({ + value: fmtNum2Display(r.endHydrogen), + disabled: true + }, unit ? { addonAfter: unit } : {})); } }, { @@ -213,12 +220,47 @@ const Component = function () { key: 'startElectricKwh', width: 150, render: function (_, r) { - return React.createElement(Input, { value: r.startElectricKwh || '-', disabled: true, addonAfter: 'kWh' }); + return React.createElement(Input, { + value: rowVehicleSelected(r) ? fmtNum2Display(r.startElectricKwh) : '', + disabled: true, + addonAfter: 'kWh', + placeholder: rowVehicleSelected(r) ? undefined : placeholderSelectVehicle + }); + } + }, + { + title: '异动结束电量', + key: 'endElectricKwh', + width: 150, + render: function (_, r) { + if (!rowVehicleSelected(r)) { + return React.createElement(Input, { value: '', disabled: true, placeholder: placeholderSelectVehicle }); + } + return React.createElement(Input, { + value: fmtNum2Display(r.endElectricKwh), + disabled: true, + addonAfter: 'kWh' + }); } } ]; }, []); + var requirementModalState = useState(false); + var requirementModalOpen = requirementModalState[0]; + var setRequirementModalOpen = requirementModalState[1]; + + var requirementDocContent = [ + '一个「数字化资产ONEOS运管平台」中的「异动管理」「查看」模块', + '#面包屑:运维管理-车辆业务-异动管理-查看', + '页面布局与字段与「异动管理-结束异动」一致;另含「审批状态」只读展示。', + '所有表单项、表格单元均为禁用只读,仅供查询展示。', + '', + '1.异动情况:异动开始/预计结束日期、异动目的地、目的地名称、异动类型、预计异动里程、审批状态、异动结束时间、备注 — 全部只读。', + '2.车辆信息:与结束异动表列一致(含异动结束里程/氢量/电量);仅展示单辆车(表格 1 行)— 全部只读;无新增行与删除。', + '3.底部仅「返回」按钮,返回异动管理列表(原型)。' + ].join('\n'); + return React.createElement(App, null, React.createElement('div', { style: layoutStyle }, React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16, flexWrap: 'wrap', gap: 8 } }, @@ -243,7 +285,7 @@ const Component = function () { style: controlStyle, showTime: { format: 'HH:mm' }, format: 'YYYY-MM-DD HH:mm', - value: form.startTime, + value: movementReadonly.startTime, disabled: true, inputReadOnly: true }) @@ -254,59 +296,67 @@ const Component = function () { style: controlStyle, showTime: { format: 'HH:mm' }, format: 'YYYY-MM-DD HH:mm', - value: form.plannedEndTime, + value: movementReadonly.plannedEndTime, disabled: true, inputReadOnly: true }) ), + React.createElement('div', { style: formItemStyle }, + React.createElement('div', { style: labelStyle }, '异动目的地'), + React.createElement(Select, { + style: controlStyle, + value: movementReadonly.destinationType, + options: destinationTypeOptions, + disabled: true + }) + ), + React.createElement('div', { style: formItemStyle }, + React.createElement('div', { style: labelStyle }, destLabel), + React.createElement(Input, { value: movementReadonly.destinationName || '-', disabled: true }) + ), + React.createElement('div', { style: formItemStyle }, + React.createElement('div', { style: labelStyle }, '异动类型'), + React.createElement(Select, { + style: controlStyle, + value: movementReadonly.changeType, + options: changeTypeOptions, + disabled: true + }) + ), + React.createElement('div', { style: formItemStyle }, + React.createElement('div', { style: labelStyle }, '预计异动里程'), + React.createElement(Input, { + value: fmtPlannedMileage(movementReadonly.plannedMileageKm), + disabled: true, + addonAfter: 'km' + }) + ), React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '审批状态'), React.createElement(Select, { style: controlStyle, - value: form.approvalStatus, + value: movementReadonly.approvalStatus, options: approvalStatusOptions, disabled: true }) ), - + React.createElement('div', { style: formItemStyle }), + React.createElement('div', { style: formItemStyle }), React.createElement('div', { style: formItemStyle }, - React.createElement('div', { style: labelStyle }, '异动目的地'), - React.createElement(Select, { + React.createElement('div', { style: labelStyle }, '异动结束时间'), + React.createElement(DatePicker, { style: controlStyle, - value: form.destinationType, - options: destinationTypeOptions, - disabled: true - }) - ), - React.createElement('div', { style: formItemStyle }, - React.createElement('div', { style: labelStyle }, destinationNameLabel), - destinationNameNode - ), - React.createElement('div', { style: formItemStyle }, - React.createElement('div', { style: labelStyle }, '异动类型'), - React.createElement(Select, { - style: controlStyle, - value: form.changeType, - options: changeTypeOptions, - disabled: true - }) - ), - - React.createElement('div', { style: formItemStyle }, - React.createElement('div', { style: labelStyle }, '预计异动里程'), - React.createElement(Input, { - value: form.plannedMileageKm || '-', + showTime: { format: 'HH:mm' }, + format: 'YYYY-MM-DD HH:mm', + value: movementReadonly.endDateTime, disabled: true, - addonAfter: 'km' + inputReadOnly: true }) ), - React.createElement('div', { style: formItemStyle }), - React.createElement('div', { style: formItemStyle }), - React.createElement('div', { style: Object.assign({}, formItemStyle, { gridColumn: 'span 3' }) }, React.createElement('div', { style: labelStyle }, '备注'), React.createElement(Input.TextArea, { - value: form.remark || '-', + value: movementReadonly.remark || '-', disabled: true, autoSize: { minRows: 3, maxRows: 6 } }) @@ -321,11 +371,10 @@ const Component = function () { dataSource: vehicles, size: 'small', pagination: false, - scroll: { x: 1180 } + scroll: { x: 1780 } }) ), - React.createElement('div', { style: { height: 56 } }), React.createElement('div', { style: { position: 'fixed', diff --git a/web端/运维管理/车辆业务/异动管理-结束异动.jsx b/web端/运维管理/车辆业务/异动管理-结束异动.jsx new file mode 100644 index 0000000..d3c226b --- /dev/null +++ b/web端/运维管理/车辆业务/异动管理-结束异动.jsx @@ -0,0 +1,464 @@ +// 【重要】必须使用 const Component 作为组件变量名 +// 运维管理 - 车辆业务 - 异动管理 - 结束异动 + +const Component = function () { + var useState = React.useState; + var useMemo = React.useMemo; + var useCallback = React.useCallback; + + var antd = window.antd; + var App = antd.App; + var Breadcrumb = antd.Breadcrumb; + var Card = antd.Card; + var Button = antd.Button; + var Input = antd.Input; + var Select = antd.Select; + var DatePicker = antd.DatePicker; + var Table = antd.Table; + var Modal = antd.Modal; + var message = antd.message; + + function getInitialDateTime(str) { + try { + if (window.dayjs) return window.dayjs(str); + } catch (e1) {} + try { + if (window.moment) return window.moment(str, 'YYYY-MM-DD HH:mm'); + } catch (e2) {} + return null; + } + + function fmtPlannedMileage(v) { + if (v == null || v === '') return '-'; + var n = Number(v); + if (isNaN(n)) return String(v); + return n.toFixed(2); + } + + function fmtNum2Display(v) { + if (v === null || v === undefined || v === '') return ''; + var n = Number(v); + if (isNaN(n)) return String(v); + return n.toFixed(2); + } + + function toFixed2Input(v) { + var s = String(v === null || v === undefined ? '' : v); + s = s.replace(/[^\d.]/g, ''); + var firstDot = s.indexOf('.'); + if (firstDot >= 0) { + s = s.slice(0, firstDot + 1) + s.slice(firstDot + 1).replace(/\./g, ''); + } + if (firstDot >= 0) { + var a = s.split('.'); + s = a[0] + '.' + (a[1] || '').slice(0, 2); + } + return s; + } + + var layoutStyle = { padding: '16px 24px', background: '#f5f5f5', minHeight: '100vh', paddingBottom: 80 }; + var labelStyle = { marginBottom: 6, fontSize: 14, color: 'rgba(0,0,0,0.65)' }; + var formItemStyle = { marginBottom: 12 }; + var controlStyle = { width: '100%' }; + var placeholderSelectVehicle = '请先选择车辆'; + var requiredMarkStyle = { color: '#ff4d4f', marginRight: 4, fontFamily: 'SimSun, sans-serif' }; + + function labelWithRequired(text, required) { + return React.createElement('div', { style: labelStyle }, + required ? React.createElement('span', { style: requiredMarkStyle }, '*') : null, + text + ); + } + + var destinationTypeOptions = useMemo(function () { + return [ + { value: '停车场', label: '停车场' }, + { value: '维修站', label: '维修站' }, + { value: '其他', label: '其他' } + ]; + }, []); + + var changeTypeOptions = useMemo(function () { + return [ + { value: '维修', label: '维修' }, + { value: '保养', label: '保养' }, + { value: '年审', label: '年审' }, + { value: '其他', label: '其他' } + ]; + }, []); + + // 原型:从列表跳转带入的异动与车辆(联调时由路由/接口填充) + var movementReadonly = useMemo(function () { + return { + startTime: getInitialDateTime('2026-02-24 08:15'), + plannedEndTime: getInitialDateTime('2026-02-24 12:00'), + destinationType: '其他', + destinationName: '车管所检测线', + changeType: '年审', + plannedMileageKm: '8.20', + remark: '年审上线检测,预计半日完成。' + }; + }, []); + + // 车辆信息:结束里程/氢量/电量可编辑(规则同新增页开始三项) + var vehiclesState = useState([ + { + id: 1, + plateNo: '粤B11111', + vehicleType: '轿车', + brand: '比亚迪', + model: '汉', + departParking: '南山科技园停车场', + startMileageKm: '8020.50', + startHydrogen: '55.00', + h2Unit: '%', + startElectricKwh: '62.10', + endMileageKm: '8055.20', + endHydrogen: '52.30', + endElectricKwh: '58.88' + } + ]); + var vehicles = vehiclesState[0]; + var setVehicles = vehiclesState[1]; + + var updateVehicleRow = useCallback(function (index, patch) { + setVehicles(function (prev) { + var list = prev.slice(); + var cur = list[index] || {}; + list[index] = Object.assign({}, cur, patch); + return list; + }); + }, []); + + // 1.7 异动结束时间:必填,YYYY-MM-DD HH:mm + var endDateTimeState = useState(getInitialDateTime('2026-02-24 11:30')); + var endDateTime = endDateTimeState[0]; + var setEndDateTime = endDateTimeState[1]; + + function rowVehicleSelected(r) { + return !!(r && String(r.plateNo || '').trim()); + } + + var vehicleColumns = useMemo(function () { + return [ + { + title: '车牌号', + key: 'plateNo', + width: 140, + fixed: 'left', + render: function (_, r) { + return React.createElement(Input, { value: r.plateNo || '-', disabled: true }); + } + }, + { + title: '车辆类型', + key: 'vehicleType', + width: 120, + render: function (_, r) { + return React.createElement(Input, { value: rowVehicleSelected(r) ? (r.vehicleType || '-') : '请先选择车辆', disabled: true }); + } + }, + { + title: '品牌', + key: 'brand', + width: 100, + render: function (_, r) { + return React.createElement(Input, { value: rowVehicleSelected(r) ? (r.brand || '-') : '请先选择车辆', disabled: true }); + } + }, + { + title: '型号', + key: 'model', + width: 120, + render: function (_, r) { + return React.createElement(Input, { value: rowVehicleSelected(r) ? (r.model || '-') : '请先选择车辆', disabled: true }); + } + }, + { + title: '出发停车场', + key: 'departParking', + width: 160, + render: function (_, r) { + return React.createElement(Input, { value: rowVehicleSelected(r) ? (r.departParking || '-') : '请先选择车辆', disabled: true }); + } + }, + { + title: '异动开始里程', + key: 'startMileageKm', + width: 160, + render: function (_, r) { + return React.createElement(Input, { + value: rowVehicleSelected(r) ? fmtNum2Display(r.startMileageKm) : '', + disabled: true, + addonAfter: 'km', + placeholder: rowVehicleSelected(r) ? undefined : placeholderSelectVehicle + }); + } + }, + { + title: React.createElement('span', null, React.createElement('span', { style: requiredMarkStyle }, '*'), '异动结束里程'), + key: 'endMileageKm', + width: 160, + render: function (_, r, index) { + if (!rowVehicleSelected(r)) { + return React.createElement(Input, { value: '', disabled: true, placeholder: placeholderSelectVehicle }); + } + return React.createElement(Input, { + value: r.endMileageKm, + onChange: function (e) { updateVehicleRow(index, { endMileageKm: toFixed2Input(e.target.value) }); }, + placeholder: '0.00', + addonAfter: 'km' + }); + } + }, + { + title: '异动开始氢量', + key: 'startHydrogen', + width: 150, + render: function (_, r) { + var unit = rowVehicleSelected(r) ? (r.h2Unit || '') : ''; + return React.createElement(Input, Object.assign({ + value: rowVehicleSelected(r) ? fmtNum2Display(r.startHydrogen) : '', + disabled: true, + placeholder: rowVehicleSelected(r) ? undefined : placeholderSelectVehicle + }, unit ? { addonAfter: unit } : {})); + } + }, + { + title: React.createElement('span', null, React.createElement('span', { style: requiredMarkStyle }, '*'), '异动结束氢量'), + key: 'endHydrogen', + width: 150, + render: function (_, r, index) { + if (!rowVehicleSelected(r)) { + return React.createElement(Input, { value: '', disabled: true, placeholder: placeholderSelectVehicle }); + } + var unit = r.h2Unit || ''; + return React.createElement(Input, Object.assign({ + value: r.endHydrogen, + onChange: function (e) { updateVehicleRow(index, { endHydrogen: toFixed2Input(e.target.value) }); }, + placeholder: '0.00' + }, unit ? { addonAfter: unit } : {})); + } + }, + { + title: '异动开始电量', + key: 'startElectricKwh', + width: 150, + render: function (_, r) { + return React.createElement(Input, { + value: rowVehicleSelected(r) ? fmtNum2Display(r.startElectricKwh) : '', + disabled: true, + addonAfter: 'kWh', + placeholder: rowVehicleSelected(r) ? undefined : placeholderSelectVehicle + }); + } + }, + { + title: React.createElement('span', null, React.createElement('span', { style: requiredMarkStyle }, '*'), '异动结束电量'), + key: 'endElectricKwh', + width: 150, + render: function (_, r, index) { + if (!rowVehicleSelected(r)) { + return React.createElement(Input, { value: '', disabled: true, placeholder: placeholderSelectVehicle }); + } + return React.createElement(Input, { + value: r.endElectricKwh, + onChange: function (e) { updateVehicleRow(index, { endElectricKwh: toFixed2Input(e.target.value) }); }, + placeholder: '0.00', + addonAfter: 'kWh' + }); + } + } + ]; + }, [updateVehicleRow]); + + var requirementModalState = useState(false); + var requirementModalOpen = requirementModalState[0]; + var setRequirementModalOpen = requirementModalState[1]; + + var requirementDocContent = [ + '一个「数字化资产ONEOS运管平台」中的「异动管理」「结束异动」模块', + '#面包屑:运维管理-车辆业务-异动管理-结束异动', + '', + '1.异动情况:', + '1.1.异动开始日期:显示异动开始日期:YYYY-MM-DD HH:MM;', + '1.2.异动预计结束日期:显示异动预计结束日期,格式为:YYYY-MM-DD HH:MM;', + '1.3.异动目的地:显示异动目的地,包括:停车场、维修站、其他;', + '1.4.目的地名称:显示目的地名称,包括:停车场名称、维修站名称、其他;', + '1.5.异动类型:显示异动类型,包括:维修、保养、年审、其他;', + '1.6.预计异动里程:显示预计异动里程,支持2位小数,后缀为km;', + '1.7.异动结束时间:必填项,日期选择器,格式为:YYYY-MM-DD HH:MM;', + '1.8.备注:显示备注信息;', + '', + '2.车辆信息:', + '2.1.车牌号:输入框(禁用),显示车牌号;', + '2.2.车辆类型:根据所选车辆类型自动反写,默认提示为请先选择车辆;', + '2.3.品牌:根据所选车辆品牌自动反写,默认提示为请先选择车辆;', + '2.4.型号:根据所选车辆型号自动反写,默认提示为请先选择车辆;', + '2.5.出发停车场:根据所选车辆出发时停车场自动反写,默认提示为请先选择车辆;', + '2.6.异动开始里程:输入框(禁用),精确至2位小数,后缀为km;', + '2.7.异动结束里程:必填项,输入框,精确至2位小数,后缀为km;', + '2.8.异动开始氢量:输入框(禁用),精确至2位小数,后缀为%或MPa,根据所选车辆型号中获取;', + '2.9.异动结束氢量:必填项,输入框,精确至2位小数,后缀为%或MPa,根据所选车辆型号中获取;', + '2.10.异动开始电量:输入框(禁用),精确至2位小数,后缀为kWh;', + '2.11.异动结束电量:必填项,输入框,精确至2位小数,后缀为kWh;', + '2.12.异动结束电量:必填项,输入框,精确至2位小数,后缀为kWh;', + '', + '3.底部为提交、取消;', + '3.1.提交:提交进行必填项校验,提交成功后计入历史记录;', + '3.2.取消:点击取消,返回列表;' + ].join('\n'); + + var handleSubmit = useCallback(function () { + if (!endDateTime) { + message.error('请选择异动结束时间'); + return; + } + for (var i = 0; i < (vehicles || []).length; i++) { + var r = vehicles[i]; + if (!rowVehicleSelected(r)) continue; + if (!String(r.endMileageKm || '').trim()) { + message.error('请填写异动结束里程'); + return; + } + if (!String(r.endHydrogen || '').trim()) { + message.error('请填写异动结束氢量'); + return; + } + if (!String(r.endElectricKwh || '').trim()) { + message.error('请填写异动结束电量'); + return; + } + } + message.success('提交成功(原型,联调时对接结束异动接口)'); + }, [vehicles, endDateTime]); + + var handleCancel = useCallback(function () { + message.info('取消并返回(原型,联调时路由返回上一页)'); + }, []); + + var destLabel = movementReadonly.destinationType === '维修站' ? '维修站名称' : (movementReadonly.destinationType === '停车场' ? '停车场名称' : '目的地名称'); + + return React.createElement(App, null, + React.createElement('div', { style: layoutStyle }, + React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16, flexWrap: 'wrap', gap: 8 } }, + React.createElement(Breadcrumb, { items: [{ title: '运维管理' }, { title: '车辆业务' }, { title: '异动管理' }, { title: '结束异动' }] }), + React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalOpen(true); } }, '查看需求说明') + ), + + React.createElement(Modal, { + title: '需求说明', + open: requirementModalOpen, + onCancel: function () { setRequirementModalOpen(false); }, + width: 720, + footer: React.createElement(Button, { onClick: function () { setRequirementModalOpen(false); } }, '关闭'), + bodyStyle: { maxHeight: '70vh', overflow: 'auto' } + }, React.createElement('div', { style: { whiteSpace: 'pre-wrap', fontSize: 13, lineHeight: 1.6, color: 'rgba(0,0,0,0.85)' } }, requirementDocContent)), + + React.createElement(Card, { title: '异动情况', style: { marginBottom: 16 } }, + React.createElement('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '16px 24px', alignItems: 'start' } }, + React.createElement('div', { style: formItemStyle }, + React.createElement('div', { style: labelStyle }, '异动开始日期'), + React.createElement(DatePicker, { + style: controlStyle, + showTime: { format: 'HH:mm' }, + format: 'YYYY-MM-DD HH:mm', + value: movementReadonly.startTime, + disabled: true, + inputReadOnly: true + }) + ), + React.createElement('div', { style: formItemStyle }, + React.createElement('div', { style: labelStyle }, '异动预计结束日期'), + React.createElement(DatePicker, { + style: controlStyle, + showTime: { format: 'HH:mm' }, + format: 'YYYY-MM-DD HH:mm', + value: movementReadonly.plannedEndTime, + disabled: true, + inputReadOnly: true + }) + ), + React.createElement('div', { style: formItemStyle }, + React.createElement('div', { style: labelStyle }, '异动目的地'), + React.createElement(Select, { + style: controlStyle, + value: movementReadonly.destinationType, + options: destinationTypeOptions, + disabled: true + }) + ), + React.createElement('div', { style: formItemStyle }, + React.createElement('div', { style: labelStyle }, destLabel), + React.createElement(Input, { value: movementReadonly.destinationName || '-', disabled: true }) + ), + React.createElement('div', { style: formItemStyle }, + React.createElement('div', { style: labelStyle }, '异动类型'), + React.createElement(Select, { + style: controlStyle, + value: movementReadonly.changeType, + options: changeTypeOptions, + disabled: true + }) + ), + React.createElement('div', { style: formItemStyle }, + React.createElement('div', { style: labelStyle }, '预计异动里程'), + React.createElement(Input, { + value: fmtPlannedMileage(movementReadonly.plannedMileageKm), + disabled: true, + addonAfter: 'km' + }) + ), + React.createElement('div', { style: formItemStyle }, + labelWithRequired('异动结束时间', true), + React.createElement(DatePicker, { + style: controlStyle, + showTime: { format: 'HH:mm' }, + format: 'YYYY-MM-DD HH:mm', + value: endDateTime, + onChange: function (d) { setEndDateTime(d); }, + placeholder: '请选择异动结束时间' + }) + ), + React.createElement('div', { style: Object.assign({}, formItemStyle, { gridColumn: 'span 3' }) }, + React.createElement('div', { style: labelStyle }, '备注'), + React.createElement(Input.TextArea, { + value: movementReadonly.remark || '-', + disabled: true, + autoSize: { minRows: 3, maxRows: 6 } + }) + ) + ) + ), + + React.createElement(Card, { title: '车辆信息', style: { marginBottom: 16 } }, + React.createElement(Table, { + rowKey: 'id', + columns: vehicleColumns, + dataSource: vehicles, + size: 'small', + pagination: false, + scroll: { x: 1780 } + }) + ), + + React.createElement('div', { + style: { + position: 'fixed', + left: 0, + right: 0, + bottom: 0, + padding: '12px 24px', + background: '#fff', + borderTop: '1px solid #f0f0f0', + display: 'flex', + gap: 8, + zIndex: 10 + } + }, + 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..62e2969 --- /dev/null +++ b/web端/运维管理/车辆业务/异动管理-编辑.jsx @@ -0,0 +1,564 @@ +// 【重要】必须使用 const Component 作为组件变量名 +// 运维管理 - 车辆业务 - 异动管理 - 编辑 + +const Component = function () { + var useState = React.useState; + var useMemo = React.useMemo; + var useCallback = React.useCallback; + + var antd = window.antd; + var App = antd.App; + var Breadcrumb = antd.Breadcrumb; + var Card = antd.Card; + var Button = antd.Button; + var Input = antd.Input; + var Select = antd.Select; + var DatePicker = antd.DatePicker; + var Table = antd.Table; + var Modal = antd.Modal; + var message = antd.message; + + function filterOption(input, option) { + var label = (option && (option.label || option.children)) || ''; + return String(label).toLowerCase().indexOf(String(input || '').toLowerCase()) >= 0; + } + + function toFixed2Input(v) { + var s = String(v === null || v === undefined ? '' : v); + s = s.replace(/[^\d.]/g, ''); + var firstDot = s.indexOf('.'); + if (firstDot >= 0) { + s = s.slice(0, firstDot + 1) + s.slice(firstDot + 1).replace(/\./g, ''); + } + if (firstDot >= 0) { + var a = s.split('.'); + s = a[0] + '.' + (a[1] || '').slice(0, 2); + } + return s; + } + + var layoutStyle = { padding: '16px 24px', background: '#f5f5f5', minHeight: '100vh' }; + var labelStyle = { marginBottom: 6, fontSize: 14, color: 'rgba(0,0,0,0.65)' }; + var formItemStyle = { marginBottom: 12 }; + var controlStyle = { width: '100%' }; + var reqStarStyle = { color: '#ff4d4f', marginRight: 4 }; + var reqStar = React.createElement('span', { style: reqStarStyle }, '*'); + + // 车辆表(用于车牌选择与反写) + var vehicleDb = useMemo(function () { + return [ + { plateNo: '粤A12345', vehicleType: '厢式货车', brand: '东风', model: 'DFH1180', departParking: '天河智慧停车场', h2Unit: 'MPa' }, + { plateNo: '粤B11111', vehicleType: '轿车', brand: '比亚迪', model: '汉', departParking: '南山科技园停车场', h2Unit: '%' }, + { plateNo: '浙A11111', vehicleType: 'SUV', brand: '小鹏', model: 'P7', departParking: '西湖景区停车场', h2Unit: '%' }, + { plateNo: '浙B22222', vehicleType: '轿车', brand: '蔚来', model: 'ET5', departParking: '宁波江北停车场', h2Unit: 'MPa' }, + { plateNo: '沪A30003', vehicleType: '厢式货车', brand: '福田', model: 'BJ1180', departParking: '张江园区停车场', h2Unit: 'MPa' } + ]; + }, []); + + var plateOptions = useMemo(function () { + return vehicleDb.map(function (v) { return { value: v.plateNo, label: v.plateNo }; }); + }, [vehicleDb]); + + var destinationTypeOptions = useMemo(function () { + return [ + { value: '停车场', label: '停车场' }, + { value: '维修站', label: '维修站' }, + { value: '其他', label: '其他' } + ]; + }, []); + + var destinationParkingOptions = useMemo(function () { + return [ + { value: '天河智慧停车场', label: '天河智慧停车场' }, + { value: '南山科技园停车场', label: '南山科技园停车场' }, + { value: '西湖景区停车场', label: '西湖景区停车场' }, + { value: '宁波江北停车场', label: '宁波江北停车场' }, + { value: '张江园区停车场', label: '张江园区停车场' } + ]; + }, []); + + var destinationRepairOptions = useMemo(function () { + return [ + { value: '杭州拱墅维修站', label: '杭州拱墅维修站' }, + { value: '广州天河维修站', label: '广州天河维修站' }, + { value: '上海浦东维修站', label: '上海浦东维修站' } + ]; + }, []); + + var changeTypeOptions = useMemo(function () { + return [ + { value: '维修', label: '维修' }, + { value: '保养', label: '保养' }, + { value: '年审', label: '年审' }, + { value: '其他', label: '其他' } + ]; + }, []); + + var requirementModalState = useState(false); + var requirementModalOpen = requirementModalState[0]; + var setRequirementModalOpen = requirementModalState[1]; + + var requirementDocContent = [ + '一个「数字化资产ONEOS运管平台」中的「异动管理」「编辑」模块', + '#面包屑:运维管理-车辆业务-异动管理-编辑', + '', + '1.异动情况;', + '1.1.异动开始日期:必填项,日期选择器,格式为:YYYY-MM-DD HH:MM;', + '1.2.异动预计结束日期:必填项,日期选择器,格式为:YYYY-MM-DD HH:MM;', + '1.3.异动目的地:必填项,选择器,选项为:停车场、维修站、其他;', + '2.4.目的地名称:必填项,如异动目的地为停车场,则此处为停车场(选择器),如异动目的地为维修站,则此处为维修站(选择器),如异动目的地为其他,则此处为输入框(自定义输入);', + '2.5.异动类型:必填项,选择器,选项为:维修、保养、年审、其他;', + '2.6.预计异动里程:输入框,支持2位小数输入,后缀为km;', + '2.7.备注:文本域,支持自定义输入;', + '', + '2.车辆信息:', + '2.1.车牌号:显示车牌号,支持输入框模糊搜索下拉匹配对应选项;', + '2.2.车辆类型:根据所选车辆类型自动反写,默认提示为请先选择车辆;', + '2.3.品牌:根据所选车辆品牌自动反写,默认提示为请先选择车辆;', + '2.4.型号:根据所选车辆型号自动反写,默认提示为请先选择车辆;', + '2.5.出发停车场:根据所选车辆出发时停车场自动反写,默认提示为请先选择车辆;', + '2.6.异动开始里程:必填项,输入框,精确至2位小数,后缀为km;', + '2.7.异动开始氢量:必填项,输入框,精确至2位小数,后缀为%或MPa,根据所选车辆型号中获取;', + '2.8.异动开始电量:必填项,输入框,精确至2位小数,后缀为kWh;', + '2.9.操作:删除;', + '2.10.新增一行:铺满整行;', + '', + '3.底部为提交审核、保存、取消;' + ].join('\n'); + + var formState = useState({ + startTime: null, + plannedEndTime: null, + destinationType: undefined, + destinationName: undefined, + destinationNameOther: '', + changeType: undefined, + plannedMileageKm: '', + remark: '' + }); + var form = formState[0]; + var setForm = formState[1]; + + var errorsState = useState({}); + var errors = errorsState[0]; + var setErrors = errorsState[1]; + + var rowIdRef = React.useRef(2); + var vehiclesState = useState([ + { + id: 1, + plateNo: undefined, + vehicleType: '', + brand: '', + model: '', + departParking: '', + h2Unit: '', + startMileageKm: '', + startHydrogen: '', + startElectricKwh: '' + } + ]); + var vehicles = vehiclesState[0]; + var setVehicles = vehiclesState[1]; + + var updateForm = useCallback(function (patch) { + setForm(function (p) { return Object.assign({}, p, patch); }); + }, []); + + var updateRow = useCallback(function (index, patch) { + setVehicles(function (prev) { + var list = prev.slice(); + var cur = list[index] || {}; + list[index] = Object.assign({}, cur, patch); + return list; + }); + }, []); + + var handlePlateChange = useCallback(function (index, plateNo) { + var v = vehicleDb.find(function (x) { return x.plateNo === plateNo; }); + updateRow(index, { + plateNo: plateNo, + vehicleType: v ? v.vehicleType : '', + brand: v ? v.brand : '', + model: v ? v.model : '', + departParking: v ? v.departParking : '', + h2Unit: v ? v.h2Unit : '' + }); + }, [vehicleDb, updateRow]); + + var addRow = useCallback(function () { + setVehicles(function (prev) { + return prev.concat([{ + id: rowIdRef.current++, + plateNo: undefined, + vehicleType: '', + brand: '', + model: '', + departParking: '', + h2Unit: '', + startMileageKm: '', + startHydrogen: '', + startElectricKwh: '' + }]); + }); + }, []); + + var removeRow = useCallback(function (index) { + setVehicles(function (prev) { + var list = prev.slice(); + list.splice(index, 1); + if (list.length === 0) { + return [{ + id: rowIdRef.current++, + plateNo: undefined, + vehicleType: '', + brand: '', + model: '', + departParking: '', + h2Unit: '', + startMileageKm: '', + startHydrogen: '', + startElectricKwh: '' + }]; + } + return list; + }); + }, []); + + function validate() { + var e = {}; + if (!form.startTime) e.startTime = '请选择异动开始日期'; + if (!form.plannedEndTime) e.plannedEndTime = '请选择异动预计结束日期'; + if (!form.destinationType) e.destinationType = '请选择异动目的地'; + if (form.destinationType === '其他') { + if (!String(form.destinationNameOther || '').trim()) e.destinationName = '请输入目的地名称'; + } else { + if (!form.destinationName) e.destinationName = '请选择目的地名称'; + } + if (!form.changeType) e.changeType = '请选择异动类型'; + + var effectiveRows = (vehicles || []).filter(function (r) { return r && String(r.plateNo || '').trim(); }); + if (effectiveRows.length === 0) e.vehicleList = '请至少选择一辆车'; + + for (var i = 0; i < (vehicles || []).length; i++) { + var r = vehicles[i] || {}; + if (!String(r.plateNo || '').trim()) continue; + if (!String(r.startMileageKm || '').trim()) e['row_' + i + '_startMileageKm'] = '请输入异动开始里程'; + if (!String(r.startHydrogen || '').trim()) e['row_' + i + '_startHydrogen'] = '请输入异动开始氢量'; + if (!String(r.startElectricKwh || '').trim()) e['row_' + i + '_startElectricKwh'] = '请输入异动开始电量'; + } + + setErrors(e); + return Object.keys(e).length === 0; + } + + var handleSubmitAudit = useCallback(function () { + if (!validate()) { + message.error('请完善必填项后再提交'); + return; + } + Modal.confirm({ + title: '确认提交审核?', + content: '提交后将进入审批流程(原型)。', + okText: '提交审核', + cancelText: '取消', + onOk: function () { + message.success('已提交审核(原型)'); + } + }); + }, [form, vehicles]); + + var handleSave = useCallback(function () { + message.info('已保存(原型,不做校验)'); + }, []); + + var handleCancel = useCallback(function () { + Modal.confirm({ + title: '是否确认取消', + content: '取消将会丢失所有已填写内容,是否确认?', + okText: '确认', + cancelText: '取消', + onOk: function () { + message.info('返回异动管理列表页(原型)'); + } + }); + }, []); + + // 目的地名称控件:随异动目的地变化 + var destinationNameLabel = form.destinationType === '维修站' ? '维修站' : (form.destinationType === '停车场' ? '停车场' : '目的地名称'); + var destinationNameNode = (function () { + if (form.destinationType === '其他') { + return React.createElement(Input, { + placeholder: '请输入目的地名称', + value: form.destinationNameOther, + onChange: function (e) { updateForm({ destinationNameOther: e.target.value }); }, + status: errors.destinationName ? 'error' : undefined + }); + } + var opts = form.destinationType === '维修站' ? destinationRepairOptions : destinationParkingOptions; + return React.createElement(Select, { + placeholder: '请选择' + destinationNameLabel, + style: controlStyle, + value: form.destinationName, + onChange: function (v) { updateForm({ destinationName: v }); }, + allowClear: true, + showSearch: true, + options: opts, + filterOption: filterOption, + status: errors.destinationName ? 'error' : undefined, + disabled: !form.destinationType + }); + })(); + + var vehicleColumns = useMemo(function () { + return [ + { + title: React.createElement('span', null, reqStar, '车牌号'), + key: 'plateNo', + width: 140, + render: function (_, r, index) { + return React.createElement(Select, { + placeholder: '请输入或选择车牌号', + style: { width: '100%' }, + value: r.plateNo, + onChange: function (v) { handlePlateChange(index, v); }, + allowClear: true, + showSearch: true, + options: plateOptions, + filterOption: filterOption, + status: errors.vehicleList ? 'error' : undefined + }); + } + }, + { + title: '车辆类型', + key: 'vehicleType', + width: 120, + render: function (_, r) { + return React.createElement(Input, { value: r.vehicleType ? r.vehicleType : '请先选择车辆', disabled: true }); + } + }, + { + title: '品牌', + key: 'brand', + width: 100, + render: function (_, r) { + return React.createElement(Input, { value: r.brand ? r.brand : '请先选择车辆', disabled: true }); + } + }, + { + title: '型号', + key: 'model', + width: 120, + render: function (_, r) { + return React.createElement(Input, { value: r.model ? r.model : '请先选择车辆', disabled: true }); + } + }, + { + title: '出发停车场', + key: 'departParking', + width: 160, + render: function (_, r) { + return React.createElement(Input, { value: r.departParking ? r.departParking : '请先选择车辆', disabled: true }); + } + }, + { + title: React.createElement('span', null, reqStar, '异动开始里程'), + key: 'startMileageKm', + width: 160, + render: function (_, r, index) { + var k = 'row_' + index + '_startMileageKm'; + return React.createElement(Input, { + value: r.startMileageKm, + onChange: function (e) { updateRow(index, { startMileageKm: toFixed2Input(e.target.value) }); }, + placeholder: '0.00', + addonAfter: 'km', + status: errors[k] ? 'error' : undefined + }); + } + }, + { + title: React.createElement('span', null, reqStar, '异动开始氢量'), + key: 'startHydrogen', + width: 150, + render: function (_, r, index) { + var k = 'row_' + index + '_startHydrogen'; + var unit = r.h2Unit || '%/MPa'; + return React.createElement(Input, { + value: r.startHydrogen, + onChange: function (e) { updateRow(index, { startHydrogen: toFixed2Input(e.target.value) }); }, + placeholder: '0.00', + addonAfter: unit, + status: errors[k] ? 'error' : undefined + }); + } + }, + { + title: React.createElement('span', null, reqStar, '异动开始电量'), + key: 'startElectricKwh', + width: 150, + render: function (_, r, index) { + var k = 'row_' + index + '_startElectricKwh'; + return React.createElement(Input, { + value: r.startElectricKwh, + onChange: function (e) { updateRow(index, { startElectricKwh: toFixed2Input(e.target.value) }); }, + placeholder: '0.00', + addonAfter: 'kWh', + status: errors[k] ? 'error' : undefined + }); + } + }, + { + title: '操作', + key: 'action', + width: 80, + fixed: 'right', + render: function (_, __, index) { + return React.createElement(Button, { type: 'link', danger: true, size: 'small', onClick: function () { removeRow(index); } }, '删除'); + } + } + ]; + }, [plateOptions, errors, handlePlateChange, updateRow, removeRow]); + + return React.createElement(App, null, + React.createElement('div', { style: layoutStyle }, + React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16, flexWrap: 'wrap', gap: 8 } }, + React.createElement(Breadcrumb, { items: [{ title: '运维管理' }, { title: '车辆业务' }, { title: '异动管理' }, { title: '编辑' }] }), + React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalOpen(true); } }, '查看需求说明') + ), + + React.createElement(Modal, { + title: '需求说明', + open: requirementModalOpen, + onCancel: function () { setRequirementModalOpen(false); }, + width: 720, + footer: React.createElement(Button, { onClick: function () { setRequirementModalOpen(false); } }, '关闭'), + bodyStyle: { maxHeight: '70vh', overflow: 'auto' } + }, React.createElement('div', { style: { whiteSpace: 'pre-wrap', fontSize: 13, lineHeight: 1.6, color: 'rgba(0,0,0,0.85)' } }, requirementDocContent)), + + React.createElement(Card, { title: '异动情况', style: { marginBottom: 16 } }, + React.createElement('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '16px 24px', alignItems: 'start' } }, + React.createElement('div', { style: formItemStyle }, + React.createElement('div', { style: labelStyle }, reqStar, '异动开始日期'), + React.createElement(DatePicker, { + style: controlStyle, + showTime: { format: 'HH:mm' }, + format: 'YYYY-MM-DD HH:mm', + placeholder: '请选择异动开始日期', + value: form.startTime, + onChange: function (v) { updateForm({ startTime: v }); }, + status: errors.startTime ? 'error' : undefined + }), + errors.startTime ? React.createElement('div', { style: { marginTop: 4, color: '#ff4d4f', fontSize: 12 } }, errors.startTime) : null + ), + React.createElement('div', { style: formItemStyle }, + React.createElement('div', { style: labelStyle }, reqStar, '异动预计结束日期'), + React.createElement(DatePicker, { + style: controlStyle, + showTime: { format: 'HH:mm' }, + format: 'YYYY-MM-DD HH:mm', + placeholder: '请选择异动预计结束日期', + value: form.plannedEndTime, + onChange: function (v) { updateForm({ plannedEndTime: v }); }, + status: errors.plannedEndTime ? 'error' : undefined + }), + errors.plannedEndTime ? React.createElement('div', { style: { marginTop: 4, color: '#ff4d4f', fontSize: 12 } }, errors.plannedEndTime) : null + ), + React.createElement('div', { style: formItemStyle }, + React.createElement('div', { style: labelStyle }, reqStar, '异动目的地'), + React.createElement(Select, { + placeholder: '请选择异动目的地', + style: controlStyle, + value: form.destinationType, + onChange: function (v) { + setForm(function (p) { + return Object.assign({}, p, { + destinationType: v, + destinationName: undefined, + destinationNameOther: '' + }); + }); + }, + allowClear: true, + options: destinationTypeOptions, + status: errors.destinationType ? 'error' : undefined + }), + errors.destinationType ? React.createElement('div', { style: { marginTop: 4, color: '#ff4d4f', fontSize: 12 } }, errors.destinationType) : null + ), + + React.createElement('div', { style: formItemStyle }, + React.createElement('div', { style: labelStyle }, reqStar, destinationNameLabel), + destinationNameNode, + errors.destinationName ? React.createElement('div', { style: { marginTop: 4, color: '#ff4d4f', fontSize: 12 } }, errors.destinationName) : null + ), + React.createElement('div', { style: formItemStyle }, + React.createElement('div', { style: labelStyle }, reqStar, '异动类型'), + React.createElement(Select, { + placeholder: '请选择异动类型', + style: controlStyle, + value: form.changeType, + onChange: function (v) { updateForm({ changeType: v }); }, + allowClear: true, + options: changeTypeOptions, + status: errors.changeType ? 'error' : undefined + }), + errors.changeType ? React.createElement('div', { style: { marginTop: 4, color: '#ff4d4f', fontSize: 12 } }, errors.changeType) : null + ), + React.createElement('div', { style: formItemStyle }, + React.createElement('div', { style: labelStyle }, '预计异动里程'), + React.createElement(Input, { + placeholder: '0.00', + value: form.plannedMileageKm, + onChange: function (e) { updateForm({ plannedMileageKm: toFixed2Input(e.target.value) }); }, + addonAfter: 'km' + }) + ), + + React.createElement('div', { style: Object.assign({}, formItemStyle, { gridColumn: 'span 3' }) }, + React.createElement('div', { style: labelStyle }, '备注'), + React.createElement(Input.TextArea, { + placeholder: '请输入备注', + value: form.remark, + onChange: function (e) { updateForm({ remark: e.target.value }); }, + autoSize: { minRows: 3, maxRows: 6 } + }) + ) + ) + ), + + React.createElement(Card, { title: '车辆信息', style: { marginBottom: 16 } }, + errors.vehicleList ? React.createElement('div', { style: { marginBottom: 12, color: '#ff4d4f', fontSize: 12 } }, errors.vehicleList) : null, + React.createElement(Table, { + rowKey: 'id', + columns: vehicleColumns, + dataSource: vehicles, + size: 'small', + pagination: false, + scroll: { x: 1250 } + }), + React.createElement(Button, { type: 'dashed', style: { marginTop: 12, width: '100%' }, onClick: addRow }, '新增一行') + ), + + React.createElement('div', { style: { height: 56 } }), + React.createElement('div', { + style: { + position: 'fixed', + left: 0, + right: 0, + bottom: 0, + padding: '12px 24px', + background: '#fff', + borderTop: '1px solid #f0f0f0', + display: 'flex', + gap: 8, + zIndex: 10 + } + }, + React.createElement(Button, { type: 'primary', onClick: handleSubmitAudit }, '提交审核'), + React.createElement(Button, { onClick: handleSave }, '保存'), + React.createElement(Button, { onClick: handleCancel }, '取消') + ) + ) + ); +}; + diff --git a/web端/运维管理/车辆业务/异动管理.jsx b/web端/运维管理/车辆业务/异动管理.jsx index 201e63c..62985e4 100644 --- a/web端/运维管理/车辆业务/异动管理.jsx +++ b/web端/运维管理/车辆业务/异动管理.jsx @@ -16,6 +16,7 @@ const Component = function () { var DatePicker = antd.DatePicker; var message = antd.message; var Modal = antd.Modal; + var Tabs = antd.Tabs; var RangePicker = DatePicker.RangePicker; @@ -45,11 +46,18 @@ const Component = function () { return s === '待提交' || s === '驳回' || s === '撤回'; } + /** 已结束异动(进入历史记录 Tab) */ + function isMovementEnded(r) { + if (!r) return false; + return !!(r.movementEndedAt || r.movementEnded); + } + var layoutStyle = { padding: '16px 24px', background: '#f5f5f5', minHeight: '100vh' }; var filterLabelStyle = { marginBottom: 6, fontSize: 14, color: 'rgba(0,0,0,0.65)' }; var filterItemStyle = { marginBottom: 12 }; var filterControlStyle = { width: '100%' }; var tableSingleLineStyle = '.movement-list-table .ant-table-thead th,.movement-list-table .ant-table-tbody td{white-space:nowrap;}'; + var movementTabsBarStyle = '.movement-list-tabs .ant-tabs-nav{align-items:center;margin-bottom:0;}'; var moveTypeOptions = useMemo(function () { return [ @@ -98,6 +106,10 @@ const Component = function () { var pageSize = pageSizeState[0]; var setPageSize = pageSizeState[1]; + var listTabState = useState('ongoing'); + var listTab = listTabState[0]; + var setListTab = listTabState[1]; + var requirementModalState = useState(false); var requirementModalOpen = requirementModalState[0]; var setRequirementModalOpen = requirementModalState[1]; @@ -112,7 +124,8 @@ const Component = function () { '1.3.车牌号:选择器,输入框支持模糊搜索下拉匹配结果;', '1.4.右侧为查询、重置按钮;', '', - '2.列表:', + '2.列表:分为2个tab:进行中、历史记录;', + '#进行中:', '2.1.异动开始日期:显示车辆异动开始日期,格式为:YYYY-MM-DD HH:MM;', '2.2.异动预计结束日期:显示车辆异动结束日期,格式为:YYYY-MM-DD HH:MM;', '2.3.审批状态:分为:待提交、审批中、审批完成、驳回、撤回;', @@ -123,12 +136,34 @@ const Component = function () { '2.8.车牌号:显示车牌号;', '2.9.异动开始里程(km):显示车辆异动开始里程;', '2.10.异动开始电量(kWh):显示车辆异动开始电量;', - '2.11.异动开始氢量;显示车辆异动开始氢量,带单位,单位根据型号参数表获取,分为%和MPa;', + '2.11.异动开始氢量:显示车辆异动开始氢量,带单位,单位根据型号参数表获取,分为%和MPa;', '2.12.创建人:显示发起人姓名;', '2.13.创建时间:显示发起时间,格式为:YYYY-MM-DD HH:MM;', - '2.14.操作:查看、编辑;', + '2.14.操作:查看、编辑、撤回、结束异动;', ' 2.14.1.查看:点击跳转异动-查看页;', ' 2.14.2.编辑:点击跳转异动-编辑页,只有审批状态为:待提交、驳回、撤回时显示编辑;', + ' 2.14.3.撤回:点击进行二次确认,确认后撤回该数据并将审批状态修改为:撤回;', + ' 2.14.4.结束异动:当审批状态为审批完成后显示结束异动,点击跳转异动-结束异动页;', + '', + '#历史记录:', + '3.1.异动开始日期:显示车辆异动开始日期,格式为:YYYY-MM-DD HH:MM;', + '3.2.异动预计结束日期:显示车辆异动结束日期,格式为:YYYY-MM-DD HH:MM;', + '3.3.审批状态:分为:待提交、审批中、审批完成、驳回、撤回;', + '3.4.异动目的地:显示车辆异动目的地,选项包括:停车场、维修站、其他;', + '3.5.目的地名称:显示车辆异动目的地名称;', + '3.6.异动类型:显示车辆异动类型,选项包括:维修、保养、年审、其他;', + '3.7.预计异动里程(km):显示车辆预计异动里程;', + '3.8.车牌号:显示车牌号;', + '3.9.异动开始里程(km):显示车辆异动开始里程;', + '3.10.异动结束里程(km):显示车辆异动结束里程;', + '3.11.异动开始电量(kWh):显示车辆异动开始电量;', + '3.12.异动结束电量(kWh):显示车辆异动结束电量;', + '3.13.异动开始氢量:显示车辆异动开始氢量,带单位,单位根据型号参数表获取,分为%和MPa;', + '3.14.异动结束氢量:显示车辆异动结束氢量,带单位,单位根据型号参数表获取,分为%和MPa;', + '3.15.创建人:显示发起人姓名;', + '3.16.创建时间:显示发起时间,格式为:YYYY-MM-DD HH:MM;', + '3.17.操作:查看;', + ' 3.17.1.查看:点击跳转异动-查看页;', '', '3.列表右下方为分页功能,支持单页显示条数设置;' ].join('\n'); @@ -304,10 +339,202 @@ const Component = function () { h2Unit: '%', creatorName: '陈静', createTime: '2026-03-01 05:40' + }, + // 历史记录样例(已结束异动,共 10 条) + { + id: 'mv-h-01', + moveStartAt: '2025-11-08 08:00', + moveEndExpectedAt: '2025-11-10 18:00', + approvalStatus: '审批完成', + destination: '维修站', + destinationName: '广州白云氢能维保中心', + moveType: '维修', + expectedMileageKm: '28.5', + plateNo: '粤A12345', + startMileageKm: '14800.00', + startElectricKwh: '70.00', + startHydrogen: '30.00', + h2Unit: 'MPa', + creatorName: '张明', + createTime: '2025-11-07 16:20', + movementEndedAt: '2025-11-10 19:30', + movementEnded: true + }, + { + id: 'mv-h-02', + moveStartAt: '2025-11-15 09:30', + moveEndExpectedAt: '2025-11-15 17:00', + approvalStatus: '审批完成', + destination: '停车场', + destinationName: '深圳湾口岸停车场', + moveType: '保养', + expectedMileageKm: '15.2', + plateNo: '粤B11111', + startMileageKm: '9200.50', + startElectricKwh: '58.30', + startHydrogen: '62.00', + h2Unit: '%', + creatorName: '王芳', + createTime: '2025-11-14 14:00', + movementEndedAt: '2025-11-15 17:45', + movementEnded: true + }, + { + id: 'mv-h-03', + moveStartAt: '2025-12-02 07:15', + moveEndExpectedAt: '2025-12-02 12:30', + approvalStatus: '审批完成', + destination: '其他', + destinationName: '杭州车管所年检线', + moveType: '年审', + expectedMileageKm: '9.8', + plateNo: '浙A11111', + startMileageKm: '11550.20', + startElectricKwh: '64.00', + startHydrogen: '48.50', + h2Unit: '%', + creatorName: '李华', + createTime: '2025-12-01 18:10', + movementEndedAt: '2025-12-02 13:20', + movementEnded: true + }, + { + id: 'mv-h-04', + moveStartAt: '2025-12-18 13:00', + moveEndExpectedAt: '2025-12-20 09:00', + approvalStatus: '审批完成', + destination: '维修站', + destinationName: '上海嘉定氢能服务站', + moveType: '维修', + expectedMileageKm: '42.0', + plateNo: '沪A30003', + startMileageKm: '10100.00', + startElectricKwh: '66.50', + startHydrogen: '27.20', + h2Unit: 'MPa', + creatorName: '赵强', + createTime: '2025-12-17 11:30', + movementEndedAt: '2025-12-20 10:15', + movementEnded: true + }, + { + id: 'mv-h-05', + moveStartAt: '2026-01-05 10:20', + moveEndExpectedAt: '2026-01-06 16:00', + approvalStatus: '审批完成', + destination: '停车场', + destinationName: '南京南站枢纽停车场', + moveType: '其他', + expectedMileageKm: '19.6', + plateNo: '苏E88888', + startMileageKm: '22800.00', + startElectricKwh: '45.80', + startHydrogen: '50.00', + h2Unit: '%', + creatorName: '陈静', + createTime: '2026-01-04 09:45', + movementEndedAt: '2026-01-06 17:00', + movementEnded: true + }, + { + id: 'mv-h-06', + moveStartAt: '2026-01-12 08:45', + moveEndExpectedAt: '2026-01-12 15:30', + approvalStatus: '审批完成', + destination: '维修站', + destinationName: '北京亦庄氢能维修站', + moveType: '保养', + expectedMileageKm: '11.3', + plateNo: '京A66666', + startMileageKm: '8750.00', + startElectricKwh: '72.00', + startHydrogen: '32.00', + h2Unit: 'MPa', + creatorName: '王芳', + createTime: '2026-01-11 17:00', + movementEndedAt: '2026-01-12 16:10', + movementEnded: true + }, + { + id: 'mv-h-07', + moveStartAt: '2026-01-22 14:00', + moveEndExpectedAt: '2026-01-23 11:00', + approvalStatus: '审批完成', + destination: '其他', + destinationName: '重庆两江新区检测站', + moveType: '年审', + expectedMileageKm: '33.0', + plateNo: '渝A99999', + startMileageKm: '17200.00', + startElectricKwh: '49.20', + startHydrogen: '59.00', + h2Unit: '%', + creatorName: '李华', + createTime: '2026-01-22 08:30', + movementEndedAt: '2026-01-23 12:00', + movementEnded: true + }, + { + id: 'mv-h-08', + moveStartAt: '2026-02-01 09:00', + moveEndExpectedAt: '2026-02-03 18:00', + approvalStatus: '审批完成', + destination: '维修站', + destinationName: '成都龙泉驿维保基地', + moveType: '维修', + expectedMileageKm: '55.8', + plateNo: '川A77777', + startMileageKm: '33800.00', + startElectricKwh: '35.00', + startHydrogen: '25.00', + h2Unit: 'MPa', + creatorName: '赵强', + createTime: '2026-01-31 16:40', + movementEndedAt: '2026-02-03 19:00', + movementEnded: true + }, + { + id: 'mv-h-09', + moveStartAt: '2026-02-10 06:30', + moveEndExpectedAt: '2026-02-10 14:00', + approvalStatus: '审批完成', + destination: '停车场', + destinationName: '青岛前湾港停车场', + moveType: '保养', + expectedMileageKm: '12.5', + plateNo: '鲁B55555', + startMileageKm: '11050.00', + startElectricKwh: '63.80', + startHydrogen: '61.00', + h2Unit: '%', + creatorName: '陈静', + createTime: '2026-02-09 20:15', + movementEndedAt: '2026-02-10 15:30', + movementEnded: true + }, + { + id: 'mv-h-10', + moveStartAt: '2026-02-18 11:20', + moveEndExpectedAt: '2026-02-19 10:00', + approvalStatus: '审批完成', + destination: '其他', + destinationName: '宁波北仑港区临时点', + moveType: '其他', + expectedMileageKm: '24.0', + plateNo: '浙B22222', + startMileageKm: '14950.00', + startElectricKwh: '57.00', + startHydrogen: '23.50', + h2Unit: 'MPa', + creatorName: '张明', + createTime: '2026-02-17 15:00', + movementEndedAt: '2026-02-19 11:20', + movementEnded: true } ]; }); var list = listState[0]; + var setList = listState[1]; function matchRange(valStr, range) { if (!range || !range[0] || !range[1]) return true; @@ -333,7 +560,13 @@ const Component = function () { }); } - var filtered = useMemo(function () { return applyFilters(list); }, [list, applied]); + var filtered = useMemo(function () { + var base = applyFilters(list); + if (listTab === 'ongoing') { + return base.filter(function (r) { return !isMovementEnded(r); }); + } + return base.filter(function (r) { return isMovementEnded(r); }); + }, [list, applied, listTab]); var paged = useMemo(function () { var start = (page - 1) * pageSize; @@ -352,6 +585,118 @@ const Component = function () { setPage(1); }, []); + var handleAdd = useCallback(function () { + message.info('新增异动(原型,联调时跳转新建页或打开表单)'); + }, []); + + function fmtNowYMDHM() { + var d = new Date(); + var p2 = function (n) { return n < 10 ? '0' + n : '' + n; }; + return d.getFullYear() + '-' + p2(d.getMonth() + 1) + '-' + p2(d.getDate()) + ' ' + p2(d.getHours()) + ':' + p2(d.getMinutes()); + } + + var handleEndMovement = useCallback(function (recordId) { + Modal.confirm({ + title: '确认结束异动', + content: '确定结束该条异动吗?确认后将归档至「历史记录」,仅可查看。', + okText: '确认结束', + cancelText: '取消', + onOk: function () { + var endedAt = fmtNowYMDHM(); + setList(function (prev) { + return (prev || []).map(function (row) { + if (row.id !== recordId) return row; + return Object.assign({}, row, { movementEndedAt: endedAt, movementEnded: true }); + }); + }); + message.success('结束异动成功'); + } + }); + }, []); + + var handleWithdrawMovement = useCallback(function (recordId) { + Modal.confirm({ + title: '确认撤回', + content: '确定要撤回该条异动申请吗?确认后审批状态将变更为「撤回」,可再次编辑后提交。', + okText: '确认撤回', + cancelText: '取消', + onOk: function () { + setList(function (prev) { + return (prev || []).map(function (row) { + if (row.id !== recordId) return row; + return Object.assign({}, row, { approvalStatus: '撤回' }); + }); + }); + message.success('撤回成功'); + } + }); + }, []); + + function formatStartHydrogenForExport(r) { + var u = r.h2Unit || ''; + if (r.startHydrogen == null || r.startHydrogen === '') return '-'; + return String(r.startHydrogen) + (u ? ' ' + u : ''); + } + + var handleExport = useCallback(function () { + var rows = filtered; + 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 || s.indexOf('\r') !== -1) { + return '"' + s.replace(/"/g, '""') + '"'; + } + return s; + }; + var headers = [ + '异动开始日期', + '异动预计结束日期', + '审批状态', + '异动目的地', + '目的地名称', + '异动类型', + '预计异动里程(km)', + '车牌号', + '异动开始里程(km)', + '异动开始电量(kWh)', + '异动开始氢量', + '创建人', + '创建时间' + ]; + var rowToCells = function (r) { + return [ + fmtYMDHM(r.moveStartAt), + fmtYMDHM(r.moveEndExpectedAt), + r.approvalStatus, + r.destination, + r.destinationName, + r.moveType, + r.expectedMileageKm, + r.plateNo, + r.startMileageKm, + r.startElectricKwh, + formatStartHydrogenForExport(r), + r.creatorName, + fmtYMDHM(r.createTime) + ]; + }; + 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 = '异动管理导出_' + (listTab === 'ongoing' ? '进行中' : '历史记录') + '_' + new Date().getTime() + '.csv'; + a.click(); + URL.revokeObjectURL(url); + message.success('已导出 ' + rows.length + ' 条记录'); + }, [filtered, listTab]); + var columns = useMemo(function () { return [ { title: '异动开始日期', dataIndex: 'moveStartAt', key: 'moveStartAt', width: 160, render: function (v) { return fmtYMDHM(v); } }, @@ -378,12 +723,29 @@ const Component = function () { { title: '操作', key: 'action', - width: 120, + width: 200, fixed: 'right', render: function (_, r) { var nodes = [ React.createElement(Button, { key: 'view', type: 'link', size: 'small', onClick: function () { message.info('跳转异动-查看页(原型)ID:' + r.id); } }, '查看') ]; + if (String(r.approvalStatus || '') === '审批中') { + nodes.push(React.createElement(Button, { + key: 'withdraw', + type: 'link', + size: 'small', + danger: true, + onClick: function () { handleWithdrawMovement(r.id); } + }, '撤回')); + } + if (String(r.approvalStatus || '') === '审批完成' && !isMovementEnded(r)) { + nodes.push(React.createElement(Button, { + key: 'end', + type: 'link', + size: 'small', + onClick: function () { handleEndMovement(r.id); } + }, '结束异动')); + } if (canShowMovementEdit(r.approvalStatus)) { nodes.push(React.createElement(Button, { key: 'edit', type: 'link', size: 'small', onClick: function () { message.info('跳转异动-编辑页(原型)ID:' + r.id); } }, '编辑')); } @@ -391,7 +753,7 @@ const Component = function () { } } ]; - }, []); + }, [handleWithdrawMovement, handleEndMovement]); return React.createElement(App, null, React.createElement('div', { style: layoutStyle }, @@ -455,25 +817,73 @@ const Component = function () { ), React.createElement(Card, null, - React.createElement('style', null, tableSingleLineStyle), - React.createElement('div', { className: 'movement-list-table' }, - React.createElement(Table, { - rowKey: 'id', - columns: columns, - dataSource: paged, - scroll: { x: 2240 }, - size: 'small', - pagination: { - current: page, - pageSize: pageSize, - total: filtered.length, - showSizeChanger: true, - showQuickJumper: true, - showTotal: function (t) { return '共 ' + t + ' 条'; }, - onChange: function (pg, ps) { setPage(pg); if (ps) setPageSize(ps); } + React.createElement('style', null, movementTabsBarStyle), + React.createElement(Tabs, { + className: 'movement-list-tabs', + activeKey: listTab, + onChange: function (k) { + setListTab(k); + setPage(1); + }, + destroyInactiveTabPane: true, + tabBarExtraContent: React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8 } }, + React.createElement(Button, { type: 'primary', onClick: handleAdd }, '新增'), + React.createElement(Button, { onClick: handleExport }, '导出') + ), + tabBarStyle: { marginBottom: 0 }, + items: [ + { + key: 'ongoing', + label: '进行中', + children: React.createElement('div', { style: { marginTop: 16 } }, + React.createElement('style', null, tableSingleLineStyle), + React.createElement('div', { className: 'movement-list-table' }, + React.createElement(Table, { + rowKey: 'id', + columns: columns, + dataSource: paged, + scroll: { x: 2240 }, + size: 'small', + pagination: { + current: page, + pageSize: pageSize, + total: filtered.length, + showSizeChanger: true, + showQuickJumper: true, + showTotal: function (t) { return '共 ' + t + ' 条'; }, + onChange: function (pg, ps) { setPage(pg); if (ps) setPageSize(ps); } + } + }) + ) + ) + }, + { + key: 'history', + label: '历史记录', + children: React.createElement('div', { style: { marginTop: 16 } }, + React.createElement('style', null, tableSingleLineStyle), + React.createElement('div', { className: 'movement-list-table' }, + React.createElement(Table, { + rowKey: 'id', + columns: columns, + dataSource: paged, + scroll: { x: 2240 }, + size: 'small', + pagination: { + current: page, + pageSize: pageSize, + total: filtered.length, + showSizeChanger: true, + showQuickJumper: true, + showTotal: function (t) { return '共 ' + t + ' 条'; }, + onChange: function (pg, ps) { setPage(pg); if (ps) setPageSize(ps); } + } + }) + ) + ) } - }) - ) + ] + }) ) ) ); diff --git a/web端/运维管理/车辆管理-查看.jsx b/web端/运维管理/车辆管理-查看.jsx index b2d03cb..1e94b67 100644 --- a/web端/运维管理/车辆管理-查看.jsx +++ b/web端/运维管理/车辆管理-查看.jsx @@ -80,12 +80,23 @@ const Component = function () { // 证照管理 Tab:证件照片占位(可替换为真实 URL,支持 Image 预览) var licensePhotoPlaceholder = 'data:image/svg+xml,' + encodeURIComponent('行驶证/证照示意图'); + function certPhotoPlaceholder(captionText) { + return 'data:image/svg+xml,' + encodeURIComponent('' + String(captionText || '') + ''); + } var certificateDetail = { drivingLicense: { img: licensePhotoPlaceholder, regDate: '2025-07-01', scrapDate: '2038-12-31', inspectExpire: '2027-07-31' }, operationPermit: { img: licensePhotoPlaceholder, permitNo: '营330482001234', regDate: '2025-07-15', inspectExpire: '2026-07-14', certExpire: '2030-07-14' }, passPermit: { img: licensePhotoPlaceholder, code: 'TX-ZJ-2025-0088', area: '浙江省嘉兴市平湖市行政辖区', expire: '2026-12-31' }, registrationCert: { img: licensePhotoPlaceholder }, - h2Permit: { img: licensePhotoPlaceholder, permitCode: 'JQZ-2025-F069001', cardCode: 'JK-88A901256', inspectDate: '2025-06-15' }, + h2Permit: { + photos: [ + { label: '特种设备使用登记证', src: certPhotoPlaceholder('特种设备使用登记证') }, + { label: '特种设备使用标志', src: certPhotoPlaceholder('特种设备使用标志') } + ], + permitCode: 'JQZ-2025-F069001', + cardCode: 'JK-88A901256', + inspectDate: '2025-06-15' + }, h2Card: { img: licensePhotoPlaceholder, stationName: '嘉兴港区某某加氢站' }, safetyValve: { img: licensePhotoPlaceholder, inspectDate: '2025-05-10', cycle: '12个月', validUntil: '2026-05-09' }, pressureGauge: { img: licensePhotoPlaceholder, inspectDate: '2025-05-10', cycle: '12个月', gaugeValidUntil: '2026-05-09' }, @@ -545,6 +556,42 @@ const Component = function () { ) ); } + /** 证照多图:每项 { label, src },如加氢证下登记证 + 使用标志 */ + function CertSectionCardMulti(title, photoSlots, fieldNodes) { + var right = fieldNodes && fieldNodes.length + ? React.createElement('div', { + style: { + flex: 1, + minWidth: 260, + display: 'flex', + alignItems: 'center', + alignSelf: 'stretch' + } + }, React.createElement('div', { style: { width: '100%' } }, fieldNodes)) + : null; + var slots = photoSlots || []; + var photosRow = React.createElement('div', { style: { display: 'flex', gap: 16, flexWrap: 'wrap', flexShrink: 0, alignItems: 'flex-start' } }, + slots.map(function (slot, idx) { + return React.createElement('div', { key: idx, style: { display: 'flex', flexDirection: 'column' } }, + React.createElement('div', { style: { marginBottom: 8, color: 'rgba(0,0,0,0.45)', fontSize: 13 } }, slot.label || '证件照片'), + React.createElement(Image, { + width: 168, + height: 112, + style: { objectFit: 'cover', borderRadius: 4, border: '1px solid #f0f0f0', background: '#fafafa' }, + src: slot.src, + alt: (slot.label || title) + '', + preview: true + }) + ); + }) + ); + return React.createElement(Card, { key: title, size: 'small', style: { marginBottom: 16 }, title: title }, + React.createElement('div', { style: { display: 'flex', gap: 24, flexWrap: 'wrap', alignItems: 'stretch' } }, + photosRow, + right + ) + ); + } var cd = certificateDetail; var licenseManagementTabContent = React.createElement('div', { style: { padding: '0 4px' } }, @@ -565,7 +612,7 @@ const Component = function () { CertFieldRow('有效期', cd.passPermit.expire) ]), CertSectionCard('登记证', cd.registrationCert.img, null), - CertSectionCard('加氢证', cd.h2Permit.img, [ + CertSectionCardMulti('加氢证', cd.h2Permit.photos, [ CertFieldRow('加氢证编码', cd.h2Permit.permitCode), CertFieldRow('加氢卡编码', cd.h2Permit.cardCode), CertFieldRow('检验日期', cd.h2Permit.inspectDate) diff --git a/web端/需求说明/运维管理-仓库管理/交车任务 b/web端/需求说明/运维管理-仓库管理/交车任务 new file mode 100644 index 0000000..5e59f02 --- /dev/null +++ b/web端/需求说明/运维管理-仓库管理/交车任务 @@ -0,0 +1,19 @@ +一个「数字化资产ONEOS运管平台」中的「仓库管理」模块 +#面包屑:运维管理-备件库-仓库管理 + +1.筛选: +1.1.仓库名称:选择器,支持输入框内输入内容模糊搜索下拉匹配结果; +1.2.创建人:选择器,下拉显示创建人; +1.3.创建时间:日期选择器,支持单输入框双日历通过开始-结束时间筛选; + +2.列表;右侧为新增; +2.1.序号:1.2.3.以此类推; +2.2.仓库编码:显示仓库编码; +2.3.仓库名称:显示仓库名称; +2.4.仓库地址:显示仓库地址; +2.5.仓库管理人:显示仓库管理人; +2.6.创建人:显示创建人; +2.7.创建时间:显示创建时间,格式为:YYYY-MM-DD HH:MM; +2.8.操作:编辑、删除; + 2.8.1.编辑:跳转仓库设置-编辑页; + 2.8.2.删除:点击进行二次确认,确认后判断仓库是否为空,如果仓库非空则提示:清空仓库后才可删除该仓库,仓库为空则提示:删除成功; diff --git a/web端/需求说明/运维管理-仓库管理/备件库存 b/web端/需求说明/运维管理-仓库管理/备件库存 new file mode 100644 index 0000000..946c021 --- /dev/null +++ b/web端/需求说明/运维管理-仓库管理/备件库存 @@ -0,0 +1,20 @@ +一个「数字化资产ONEOS运管平台」中的「备件库存」模块 +#面包屑:运维管理-备件库-备件库存 + +1.筛选: +1.1.仓库名称:选择器,支持输入框内输入模糊搜索,下拉匹配正确项; +1.2.备件编码:选择器,支持输入框内输入模糊搜索,下拉匹配正确项; +1.3.备件名称:选择器,支持输入框内输入模糊搜索,下拉匹配正确项; +1.4.适配车型:选择器,支持多选,显示所有型号; + +2.列表;右侧为新增、导出按钮 +2.1.序号:1.2.3.以此类推; +2.2.仓库编码:显示仓库编码; +2.3.仓库名称:显示仓库名称; +2.4.备件编码:显示备件编码; +2.5.备件名称:显示备件名称; +2.6.适配车型:显示适配车型,一个备件可能有多个车型; +2.7.库存数量:显示库存数量; +2.8.累积入库数量:显示该备件入库数量总和; +2.9.累积出库数量:显示该备件出库数量总和; +2.10.右下角为分页功能,支持选择单页显示数据条数;