Initial commit: ONE-OS project
Made-with: Cursor
This commit is contained in:
15
src/App.tsx
Normal file
15
src/App.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
||||
import VehicleManage from '@/pages/VehicleManage';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path="/" element={<VehicleManage />} />
|
||||
<Route path="/vehicle/:id" element={<VehicleManage />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
10
src/main.tsx
Normal file
10
src/main.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import '@arco-design/web-react/dist/css/arco.css';
|
||||
import App from './App';
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
116
src/pages/VehicleManage/VehiclePlateModal.tsx
Normal file
116
src/pages/VehicleManage/VehiclePlateModal.tsx
Normal file
@@ -0,0 +1,116 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Modal, Form, Input, Upload, Image, Message } from '@arco-design/web-react';
|
||||
import type { VehicleRecord } from '@/types/vehicle';
|
||||
|
||||
type Props = {
|
||||
visible: boolean;
|
||||
vehicle: VehicleRecord | null;
|
||||
onCancel: () => void;
|
||||
onOk: (vin: string, plateNo: string) => void;
|
||||
};
|
||||
|
||||
export default function VehiclePlateModal({ visible, vehicle, onCancel, onOk }: Props) {
|
||||
const [ocrLoading, setOcrLoading] = useState(false);
|
||||
const [licenseImageUrl, setLicenseImageUrl] = useState<string>('');
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const handleUploadChange = (fileList: { file?: File; url?: string }[]) => {
|
||||
const file = fileList[0]?.file;
|
||||
if (!file) return;
|
||||
const url = URL.createObjectURL(file);
|
||||
setLicenseImageUrl(url);
|
||||
setOcrLoading(true);
|
||||
// 模拟 OCR 识别,实际对接 OCR 接口
|
||||
setTimeout(() => {
|
||||
form.setFieldsValue({ vin: vehicle?.vin ?? '', plateNo: vehicle?.plateNo ?? '' });
|
||||
setOcrLoading(false);
|
||||
}, 1500);
|
||||
};
|
||||
|
||||
const handleOk = () => {
|
||||
form.validate().then((values) => {
|
||||
const v = (values.vin ?? '').trim();
|
||||
const p = (values.plateNo ?? '').trim();
|
||||
if (!v || !p) {
|
||||
Message.warning('请填写车辆识别代号与车牌号');
|
||||
return;
|
||||
}
|
||||
if (vehicle && v !== vehicle.vin) {
|
||||
Message.error('车辆识别代号与该车辆不匹配');
|
||||
return;
|
||||
}
|
||||
onOk(v, p);
|
||||
reset();
|
||||
}).catch(() => {});
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
setLicenseImageUrl('');
|
||||
setOcrLoading(false);
|
||||
form.resetFields();
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
reset();
|
||||
onCancel();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
title="确认上牌信息"
|
||||
visible={visible}
|
||||
onOk={handleOk}
|
||||
onCancel={handleCancel}
|
||||
okText="确认"
|
||||
cancelText="取消"
|
||||
style={{ width: 720 }}
|
||||
unmountOnExit
|
||||
afterClose={reset}
|
||||
>
|
||||
<div style={{ display: 'flex', gap: 24, minHeight: 280 }}>
|
||||
<div style={{ flex: '0 0 320px' }}>
|
||||
<div style={{ marginBottom: 8, color: 'var(--color-text-2)' }}>行驶证照片</div>
|
||||
{licenseImageUrl ? (
|
||||
<Image
|
||||
width={320}
|
||||
height={200}
|
||||
src={licenseImageUrl}
|
||||
style={{ objectFit: 'contain', background: 'var(--color-fill-2)', borderRadius: 4 }}
|
||||
/>
|
||||
) : (
|
||||
<Upload
|
||||
accept="image/*"
|
||||
listType="picture-card"
|
||||
limit={1}
|
||||
onChange={handleUploadChange}
|
||||
customRequest={(option) => {
|
||||
const { file } = option;
|
||||
if (file) handleUploadChange([{ file: file as File }]);
|
||||
}}
|
||||
>
|
||||
<div style={{ padding: 20, textAlign: 'center' }}>上传行驶证照片</div>
|
||||
</Upload>
|
||||
)}
|
||||
</div>
|
||||
<div style={{ flex: 1 }}>
|
||||
{ocrLoading ? (
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: 200 }}>
|
||||
<span style={{ color: 'var(--color-text-2)' }}>识别中,请勿关闭页面</span>
|
||||
</div>
|
||||
) : (
|
||||
<Form form={form} layout="vertical" initialValues={{ vin: vehicle?.vin ?? '', plateNo: vehicle?.plateNo ?? '' }}>
|
||||
<Form.Item label="车辆识别代号(VIN)" field="vin" rules={[{ required: true, message: '请填写车辆识别代号' }]}>
|
||||
<Input placeholder="根据行驶证照片反写,可编辑" />
|
||||
</Form.Item>
|
||||
<Form.Item label="车牌号" field="plateNo" rules={[{ required: true, message: '请填写车牌号' }]}>
|
||||
<Input placeholder="根据行驶证照片反写,可编辑" />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
384
src/pages/VehicleManage/index.tsx
Normal file
384
src/pages/VehicleManage/index.tsx
Normal file
@@ -0,0 +1,384 @@
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import {
|
||||
Breadcrumb,
|
||||
Form,
|
||||
Grid,
|
||||
Select,
|
||||
Cascader,
|
||||
Input,
|
||||
Button,
|
||||
Space,
|
||||
Table,
|
||||
Card,
|
||||
Message,
|
||||
Dropdown,
|
||||
Menu,
|
||||
Checkbox,
|
||||
Modal,
|
||||
Upload,
|
||||
} from '@arco-design/web-react';
|
||||
import type { TableColumnProps } from '@arco-design/web-react/es/Table';
|
||||
import {
|
||||
IconExport,
|
||||
IconImport,
|
||||
IconMore,
|
||||
IconSearch,
|
||||
IconRefresh,
|
||||
} from '@arco-design/web-react/icon';
|
||||
import {
|
||||
regionOptions,
|
||||
vehicleTypeOptions,
|
||||
brandOptions,
|
||||
modelOptions,
|
||||
customerOptions,
|
||||
businessDeptOptions,
|
||||
contractNoOptions,
|
||||
registeredOwnerOptions,
|
||||
mockVehicleList,
|
||||
} from '@/services/vehicle';
|
||||
import type { VehicleRecord, VehicleFilterForm } from '@/types/vehicle';
|
||||
import VehiclePlateModal from './VehiclePlateModal';
|
||||
|
||||
const { Row, Col } = Grid;
|
||||
|
||||
const defaultFilter: VehicleFilterForm = {};
|
||||
|
||||
export default function VehicleManage() {
|
||||
const [filter, setFilter] = useState<VehicleFilterForm>(defaultFilter);
|
||||
const [plateNoQuick, setPlateNoQuick] = useState('');
|
||||
const [selectedRowKeys, setSelectedRowKeys] = useState<string[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState<VehicleRecord[]>([]);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [page, setPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(20);
|
||||
const [plateModalVisible, setPlateModalVisible] = useState(false);
|
||||
const [plateModalVehicle, setPlateModalVehicle] = useState<VehicleRecord | null>(null);
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const loadList = useCallback(
|
||||
(p = page, ps = pageSize) => {
|
||||
setLoading(true);
|
||||
setTimeout(() => {
|
||||
const res = mockVehicleList({
|
||||
...filter,
|
||||
plateNo: plateNoQuick || undefined,
|
||||
page: p,
|
||||
pageSize: ps,
|
||||
});
|
||||
setData(res.list);
|
||||
setTotal(res.total);
|
||||
setLoading(false);
|
||||
}, 300);
|
||||
},
|
||||
[filter, plateNoQuick, page, pageSize]
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
loadList();
|
||||
}, [loadList]);
|
||||
|
||||
const onFilterSubmit = (values: VehicleFilterForm) => {
|
||||
setFilter(values);
|
||||
setPage(1);
|
||||
loadList(1, pageSize);
|
||||
};
|
||||
|
||||
const onFilterReset = () => {
|
||||
form.resetFields();
|
||||
setFilter(defaultFilter);
|
||||
setPlateNoQuick('');
|
||||
setPage(1);
|
||||
loadList(1, pageSize);
|
||||
};
|
||||
|
||||
const onExport = () => {
|
||||
if (selectedRowKeys.length === 0) {
|
||||
Message.warning('请先勾选要导出的车辆');
|
||||
return;
|
||||
}
|
||||
Message.info(`导出 ${selectedRowKeys.length} 条记录(联调时对接导出接口)`);
|
||||
};
|
||||
|
||||
const onBatchImport = () => {
|
||||
Message.info('批量导入(联调时对接导入接口)');
|
||||
};
|
||||
|
||||
const onView = (record: VehicleRecord) => {
|
||||
Message.info(`查看车辆详情:${record.plateNo || record.vin}`);
|
||||
// 实际可: navigate(`/vehicle/detail/${record.id}`);
|
||||
};
|
||||
|
||||
const onPlate = (record: VehicleRecord) => {
|
||||
setPlateModalVehicle(record);
|
||||
setPlateModalVisible(true);
|
||||
};
|
||||
|
||||
const onPlateModalOk = (vin: string, plateNo: string) => {
|
||||
if (plateModalVehicle && (vin !== plateModalVehicle.vin || !plateNo)) {
|
||||
Message.error('车辆识别代号与该车辆不匹配');
|
||||
return;
|
||||
}
|
||||
Message.success('上牌信息已更新');
|
||||
setPlateModalVisible(false);
|
||||
setPlateModalVehicle(null);
|
||||
loadList();
|
||||
};
|
||||
|
||||
const moreMenu = (record: VehicleRecord) => (
|
||||
<Menu>
|
||||
<Menu.Item key="plate" onClick={() => onPlate(record)}>车辆上牌</Menu.Item>
|
||||
<Menu.Item key="transfer">车辆过户</Menu.Item>
|
||||
<Menu.Item key="move">车辆异动</Menu.Item>
|
||||
<Menu.Item key="allocate">车辆调拨</Menu.Item>
|
||||
<Menu.Item key="scrap">车辆报废</Menu.Item>
|
||||
<Menu.Item key="inspect">车辆年审</Menu.Item>
|
||||
<Menu.Item key="sale">车辆出售</Menu.Item>
|
||||
<Menu.Item key="fault">故障提报</Menu.Item>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
const columns: TableColumnProps<VehicleRecord>[] = [
|
||||
{ title: '运营城市', dataIndex: 'operationCity', width: 120, fixed: 'left' },
|
||||
{ title: '车辆识别代号', dataIndex: 'vin', width: 180, fixed: 'left' },
|
||||
{ title: '车牌号', dataIndex: 'plateNo', width: 100, fixed: 'left' },
|
||||
{ title: '车辆编号', dataIndex: 'vehicleNo', width: 100, render: (v) => v || '-' },
|
||||
{ title: '车辆类型', dataIndex: 'vehicleType', width: 100 },
|
||||
{ title: '品牌', dataIndex: 'brand', width: 90 },
|
||||
{ title: '型号', dataIndex: 'model', width: 90 },
|
||||
{ title: '车身颜色', dataIndex: 'bodyColor', width: 90 },
|
||||
{ title: '归属停车场', dataIndex: 'parkingLot', width: 110 },
|
||||
{ title: '客户名称', dataIndex: 'customerName', width: 100 },
|
||||
{ title: '业务部门', dataIndex: 'businessDept', width: 100 },
|
||||
{ title: '业务负责人', dataIndex: 'businessOwner', width: 100 },
|
||||
{ title: '运营状态', dataIndex: 'operationStatus', width: 90 },
|
||||
{ title: '库位状态', dataIndex: 'libStatus', width: 180 },
|
||||
{ title: '出库状态', dataIndex: 'outboundStatus', width: 100 },
|
||||
{ title: '整备状态', dataIndex: 'prepStatus', width: 90 },
|
||||
{ title: '过户状态', dataIndex: 'transferStatus', width: 120 },
|
||||
{ title: '维修状态', dataIndex: 'repairStatus', width: 110 },
|
||||
{ title: '证照状态', dataIndex: 'licenseStatus', width: 90 },
|
||||
{ title: '报废状态', dataIndex: 'scrapStatus', width: 90 },
|
||||
{ title: '登记所有权', dataIndex: 'registeredOwner', width: 160 },
|
||||
{ title: '在线状态', dataIndex: 'onlineStatus', width: 90 },
|
||||
{ title: '出厂年份', dataIndex: 'manufactureYear', width: 90 },
|
||||
{ title: '行驶公里数(KM)', dataIndex: 'mileage', width: 120, render: (v) => v?.toFixed(2) ?? '-' },
|
||||
{ title: '采购入库时间', dataIndex: 'purchaseTime', width: 120 },
|
||||
{ title: '行驶证注册日期', dataIndex: 'licenseRegisterDate', width: 130 },
|
||||
{ title: '行驶证检验有效期', dataIndex: 'licenseExpiry', width: 130 },
|
||||
{ title: '上次交车时间', dataIndex: 'lastDeliveryTime', width: 120 },
|
||||
{ title: '上次交车里程(KM)', dataIndex: 'lastDeliveryMileage', width: 130, render: (v) => (v ? Number(v).toFixed(2) : '-') },
|
||||
{ title: '上次还车时间', dataIndex: 'lastReturnTime', width: 120 },
|
||||
{ title: '上次还车里程(KM)', dataIndex: 'lastReturnMileage', width: 130, render: (v) => (v ? Number(v).toFixed(2) : '-') },
|
||||
{ title: '强制报废日期', dataIndex: 'forceScrapDate', width: 120 },
|
||||
{ title: '合同编号', dataIndex: 'contractNo', width: 110 },
|
||||
{ title: '当前位置', dataIndex: 'currentLocation', width: 300 },
|
||||
{ title: 'GPS最后上传时间', dataIndex: 'gpsLastTime', width: 160 },
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'op',
|
||||
width: 140,
|
||||
fixed: 'right',
|
||||
render: (_, record) => (
|
||||
<Space>
|
||||
<Button type="text" size="small" onClick={() => onView(record)}>
|
||||
查看
|
||||
</Button>
|
||||
<Dropdown trigger="hover" position="br" droplist={moreMenu(record)}>
|
||||
<Button type="text" size="small" icon={<IconMore />}>
|
||||
更多
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const watchedBrand = Form.useWatch('brand', form);
|
||||
const modelOptionsByBrand = watchedBrand ? (modelOptions as Record<string, { value: string; label: string }[]>)[watchedBrand] ?? [] : [];
|
||||
|
||||
return (
|
||||
<div style={{ padding: 16, background: 'var(--color-fill-2)', minHeight: '100vh' }}>
|
||||
<Breadcrumb style={{ marginBottom: 16 }}>
|
||||
<Breadcrumb.Item>运维管理</Breadcrumb.Item>
|
||||
<Breadcrumb.Item>车辆管理</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
|
||||
<Card title="筛选" style={{ marginBottom: 16 }}>
|
||||
<Form
|
||||
form={form}
|
||||
layout="inline"
|
||||
initialValues={defaultFilter}
|
||||
onSubmit={onFilterSubmit}
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
<Row gutter={16}>
|
||||
<Col span={6}>
|
||||
<Form.Item label="运营城市" field="operationCity">
|
||||
<Cascader
|
||||
allowClear
|
||||
placeholder="请选择省-市"
|
||||
options={regionOptions}
|
||||
style={{ width: '100%' }}
|
||||
changeOnSelect
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Form.Item label="车辆类型" field="vehicleType">
|
||||
<Select allowClear placeholder="请选择" options={vehicleTypeOptions} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Form.Item label="品牌" field="brand">
|
||||
<Select allowClear placeholder="请选择" options={brandOptions} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Form.Item label="型号" field="model">
|
||||
<Select
|
||||
allowClear
|
||||
placeholder="请选择"
|
||||
options={modelOptionsByBrand}
|
||||
style={{ width: '100%' }}
|
||||
disabled={!watchedBrand}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Form.Item label="客户名称" field="customerName">
|
||||
<Select allowClear placeholder="请选择" options={customerOptions} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Form.Item label="归属业务部门" field="businessDept">
|
||||
<Select allowClear placeholder="请选择" options={businessDeptOptions} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Form.Item label="合同编号" field="contractNo">
|
||||
<Select
|
||||
allowClear
|
||||
showSearch
|
||||
placeholder="输入模糊匹配"
|
||||
options={contractNoOptions}
|
||||
style={{ width: '100%' }}
|
||||
filterOption={(input, option) =>
|
||||
(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
|
||||
}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Form.Item label="登记所有权" field="registeredOwner">
|
||||
<Select
|
||||
allowClear
|
||||
showSearch
|
||||
placeholder="输入模糊匹配"
|
||||
options={registeredOwnerOptions}
|
||||
style={{ width: '100%' }}
|
||||
filterOption={(input, option) =>
|
||||
(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
|
||||
}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Form.Item style={{ marginBottom: 0 }}>
|
||||
<Space>
|
||||
<Button type="primary" htmlType="submit" icon={<IconSearch />}>
|
||||
查询
|
||||
</Button>
|
||||
<Button icon={<IconRefresh />} onClick={onFilterReset}>
|
||||
重置
|
||||
</Button>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
|
||||
<Space>
|
||||
<Input
|
||||
placeholder="输入车牌号快速筛选"
|
||||
value={plateNoQuick}
|
||||
onChange={setPlateNoQuick}
|
||||
style={{ width: 200 }}
|
||||
allowClear
|
||||
onPressEnter={() => loadList(1, pageSize)}
|
||||
/>
|
||||
<Button type="primary" icon={<IconSearch />} onClick={() => { setPage(1); loadList(1, pageSize); }}>
|
||||
筛选
|
||||
</Button>
|
||||
</Space>
|
||||
<Space>
|
||||
<Button icon={<IconExport />} onClick={onExport}>
|
||||
导出(联动多选)
|
||||
</Button>
|
||||
<Button icon={<IconImport />} onClick={onBatchImport}>
|
||||
批量导入
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
<Table
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
data={data}
|
||||
columns={[
|
||||
{
|
||||
title: (
|
||||
<Checkbox
|
||||
checked={selectedRowKeys.length === data.length && data.length > 0}
|
||||
indeterminate={selectedRowKeys.length > 0 && selectedRowKeys.length < data.length}
|
||||
onChange={(checked) => setSelectedRowKeys(checked ? data.map((r) => r.id) : [])}
|
||||
/>
|
||||
),
|
||||
width: 48,
|
||||
fixed: 'left',
|
||||
render: (_, record) => (
|
||||
<Checkbox
|
||||
checked={selectedRowKeys.includes(record.id)}
|
||||
onChange={(checked) =>
|
||||
setSelectedRowKeys((prev) =>
|
||||
checked ? [...prev, record.id] : prev.filter((k) => k !== record.id)
|
||||
)
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
...columns,
|
||||
]}
|
||||
scroll={{ x: 3800 }}
|
||||
pagination={{
|
||||
current: page,
|
||||
pageSize,
|
||||
total,
|
||||
showTotal: true,
|
||||
showJumper: true,
|
||||
sizeCanChange: true,
|
||||
pageSizeChange: (ps) => {
|
||||
setPageSize(ps);
|
||||
setPage(1);
|
||||
loadList(1, ps);
|
||||
},
|
||||
onChange: (p) => {
|
||||
setPage(p);
|
||||
loadList(p, pageSize);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<VehiclePlateModal
|
||||
visible={plateModalVisible}
|
||||
vehicle={plateModalVehicle}
|
||||
onCancel={() => { setPlateModalVisible(false); setPlateModalVehicle(null); }}
|
||||
onOk={onPlateModalOk}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
145
src/services/vehicle.ts
Normal file
145
src/services/vehicle.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
import type { VehicleRecord, VehicleFilterForm } from '@/types/vehicle';
|
||||
|
||||
/** 模拟省-市二级地区 */
|
||||
export const regionOptions = [
|
||||
{
|
||||
value: 'guangdong',
|
||||
label: '广东省',
|
||||
children: [
|
||||
{ value: 'guangzhou', label: '广州市' },
|
||||
{ value: 'shenzhen', label: '深圳市' },
|
||||
{ value: 'dongguan', label: '东莞市' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'zhejiang',
|
||||
label: '浙江省',
|
||||
children: [
|
||||
{ value: 'hangzhou', label: '杭州市' },
|
||||
{ value: 'ningbo', label: '宁波市' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'jiangsu',
|
||||
label: '江苏省',
|
||||
children: [
|
||||
{ value: 'nanjing', label: '南京市' },
|
||||
{ value: 'suzhou', label: '苏州市' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
/** 车辆类型(从车辆类型表拉取) */
|
||||
export const vehicleTypeOptions = [
|
||||
{ value: 'light_truck', label: '轻卡' },
|
||||
{ value: 'van', label: '厢式车' },
|
||||
{ value: 'suv', label: 'SUV' },
|
||||
{ value: 'sedan', label: '轿车' },
|
||||
];
|
||||
|
||||
/** 品牌(从型号参数表拉取) */
|
||||
export const brandOptions = [
|
||||
{ value: 'brand_a', label: '品牌A' },
|
||||
{ value: 'brand_b', label: '品牌B' },
|
||||
{ value: 'brand_c', label: '品牌C' },
|
||||
];
|
||||
|
||||
/** 型号(从型号参数表拉取,可按品牌联动) */
|
||||
export const modelOptions: Record<string, { value: string; label: string }[]> = {
|
||||
brand_a: [
|
||||
{ value: 'model_a1', label: '型号A1' },
|
||||
{ value: 'model_a2', label: '型号A2' },
|
||||
],
|
||||
brand_b: [
|
||||
{ value: 'model_b1', label: '型号B1' },
|
||||
{ value: 'model_b2', label: '型号B2' },
|
||||
],
|
||||
brand_c: [
|
||||
{ value: 'model_c1', label: '型号C1' },
|
||||
],
|
||||
};
|
||||
|
||||
/** 客户名称(租赁/自营合同客户,含「无」) */
|
||||
export const customerOptions = [
|
||||
{ value: '__none__', label: '无' },
|
||||
{ value: 'customer_1', label: '客户甲' },
|
||||
{ value: 'customer_2', label: '客户乙' },
|
||||
{ value: 'customer_3', label: '客户丙' },
|
||||
];
|
||||
|
||||
/** 归属业务部门(含「无」) */
|
||||
export const businessDeptOptions = [
|
||||
{ value: '__none__', label: '无' },
|
||||
{ value: 'dept_1', label: '业务一部' },
|
||||
{ value: 'dept_2', label: '业务二部' },
|
||||
{ value: 'dept_3', label: '业务三部' },
|
||||
];
|
||||
|
||||
/** 合同编号列表(支持模糊匹配,此处为示例) */
|
||||
export const contractNoOptions = [
|
||||
{ value: 'HT-2024-001', label: 'HT-2024-001' },
|
||||
{ value: 'HT-2024-002', label: 'HT-2024-002' },
|
||||
{ value: 'HT-2024-003', label: 'HT-2024-003' },
|
||||
{ value: 'ZY-2024-001', label: 'ZY-2024-001' },
|
||||
];
|
||||
|
||||
/** 登记所有权列表(支持模糊匹配) */
|
||||
export const registeredOwnerOptions = [
|
||||
{ value: 'owner_1', label: '某某物流有限公司' },
|
||||
{ value: 'owner_2', label: '某某租赁有限公司' },
|
||||
];
|
||||
|
||||
function randomItem<T>(arr: T[]): T {
|
||||
return arr[Math.floor(Math.random() * arr.length)];
|
||||
}
|
||||
|
||||
/** 生成模拟列表数据 */
|
||||
export function mockVehicleList(params: VehicleFilterForm & { plateNo?: string; page?: number; pageSize?: number }): { list: VehicleRecord[]; total: number } {
|
||||
const total = 48;
|
||||
const list: VehicleRecord[] = [];
|
||||
const cities = ['广东省广州市', '广东省深圳市', '浙江省杭州市', '江苏省南京市'];
|
||||
for (let i = 1; i <= (params.pageSize ?? 20); i++) {
|
||||
const idx = ((params.page ?? 1) - 1) * (params.pageSize ?? 20) + i;
|
||||
if (idx > total) break;
|
||||
list.push({
|
||||
id: `v-${idx}`,
|
||||
operationCity: randomItem(cities),
|
||||
vin: `L${String(idx).padStart(6, '0')}${Math.random().toString(36).slice(2, 11).toUpperCase()}`,
|
||||
plateNo: `粤A${String(10000 + idx).slice(-5)}`,
|
||||
vehicleNo: idx <= 10 ? `VN-${idx}` : '',
|
||||
vehicleType: randomItem(vehicleTypeOptions).label,
|
||||
brand: randomItem(brandOptions).label,
|
||||
model: randomItem(modelOptions.brand_a).label,
|
||||
bodyColor: ['白', '黑', '银', '蓝'][idx % 4],
|
||||
parkingLot: idx % 3 === 0 ? '-' : `停车场${(idx % 3) + 1}`,
|
||||
customerName: idx % 4 === 0 ? '-' : `客户${(idx % 4)}`,
|
||||
businessDept: idx % 4 === 0 ? '-' : `业务${(idx % 4)}部`,
|
||||
businessOwner: idx % 4 === 0 ? '-' : `负责人${idx % 5}`,
|
||||
operationStatus: randomItem(['待运营', '库存', '租赁', '自营', '退出运营']),
|
||||
libStatus: randomItem(['库存车-可交付车', '已交付车-租赁交车', '新车入库-待验车']),
|
||||
outboundStatus: randomItem(['无', '租赁交车', '异动出库']),
|
||||
preemptStatus: '-',
|
||||
prepStatus: randomItem(['待整备', '整备中', '正常', '无']),
|
||||
transferStatus: randomItem(['无', '过户中', '内部过户完成']),
|
||||
repairStatus: randomItem(['待服务站接单', '维修中', '正常']),
|
||||
licenseStatus: randomItem(['正常', '异常']),
|
||||
scrapStatus: randomItem(['无', '报废中', '已报废']),
|
||||
registeredOwner: '某某物流有限公司',
|
||||
onlineStatus: randomItem(['在线', '离线']),
|
||||
manufactureYear: `${2020 + (idx % 5)}`,
|
||||
mileage: Number((10000 + idx * 500 + Math.random() * 200).toFixed(2)),
|
||||
purchaseTime: `2022-0${(idx % 9) + 1}-15`,
|
||||
licenseRegisterDate: `2022-0${(idx % 9) + 1}-01`,
|
||||
licenseExpiry: `2025-0${(idx % 9) + 1}-01`,
|
||||
lastDeliveryTime: idx % 2 === 0 ? `2024-01-${String(10 + (idx % 20)).padStart(2, '0')}` : '-',
|
||||
lastDeliveryMileage: idx % 2 === 0 ? Number((12000 + idx * 100).toFixed(2)) : 0,
|
||||
lastReturnTime: idx % 2 === 1 ? `2024-02-${String(5 + (idx % 20)).padStart(2, '0')}` : '-',
|
||||
lastReturnMileage: idx % 2 === 1 ? Number((12500 + idx * 100).toFixed(2)) : 0,
|
||||
forceScrapDate: `2030-12-31`,
|
||||
contractNo: idx % 3 === 0 ? '-' : `HT-2024-00${(idx % 3) + 1}`,
|
||||
currentLocation: `广东省广州市天河区某某路${idx}号`,
|
||||
gpsLastTime: `2024-02-13 ${String(10 + (idx % 12)).padStart(2, '0')}:${String(idx % 60).padStart(2, '0')}`,
|
||||
});
|
||||
}
|
||||
return { list, total };
|
||||
}
|
||||
129
src/types/vehicle.ts
Normal file
129
src/types/vehicle.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
/** 车辆管理相关类型 */
|
||||
|
||||
export type OperationCity = { province: string; city: string };
|
||||
export type VehicleTypeItem = { value: string; label: string };
|
||||
export type ModelParamItem = { brand: string; model: string };
|
||||
export type CustomerItem = { value: string; label: string };
|
||||
export type DeptItem = { value: string; label: string };
|
||||
|
||||
export const OPERATION_STATUS_OPTIONS = [
|
||||
{ value: 'pending', label: '待运营' },
|
||||
{ value: 'in_stock', label: '库存' },
|
||||
{ value: 'lease', label: '租赁' },
|
||||
{ value: 'self', label: '自营' },
|
||||
{ value: 'exit', label: '退出运营' },
|
||||
] as const;
|
||||
|
||||
export const LIB_STATUS_OPTIONS = [
|
||||
{ value: 'new_pending', label: '新车入库-待验车' },
|
||||
{ value: 'new_license', label: '新车入库-证照办理' },
|
||||
{ value: 'stock_ok', label: '库存车-可交付车' },
|
||||
{ value: 'stock_no', label: '库存车-不可交付车' },
|
||||
{ value: 'stock_slow', label: '库存车-呆滞车' },
|
||||
{ value: 'out_lease', label: '已交付车-租赁交车' },
|
||||
{ value: 'out_self', label: '已交付车-自营交车' },
|
||||
{ value: 'out_replace', label: '已交付车-替换交车' },
|
||||
{ value: 'exit_scrap', label: '退出运营-报废车' },
|
||||
{ value: 'exit_third', label: '退出运营-三方退租车' },
|
||||
{ value: 'exit_sale', label: '退出运营-过户售车' },
|
||||
] as const;
|
||||
|
||||
export const OUTBOUND_STATUS_OPTIONS = [
|
||||
{ value: 'move', label: '异动出库' },
|
||||
{ value: 'transfer', label: '调拨出库' },
|
||||
{ value: 'show', label: '展示出库' },
|
||||
{ value: 'lease_deliver', label: '租赁交车' },
|
||||
{ value: 'self_deliver', label: '自营交车' },
|
||||
{ value: 'replace_deliver', label: '替换交车' },
|
||||
{ value: 'sale', label: '过户售车' },
|
||||
{ value: 'lease_return', label: '外租退车' },
|
||||
{ value: 'scrap', label: '报废出库' },
|
||||
{ value: 'none', label: '无' },
|
||||
] as const;
|
||||
|
||||
export const PREP_STATUS_OPTIONS = [
|
||||
{ value: 'pending', label: '待整备' },
|
||||
{ value: 'doing', label: '整备中' },
|
||||
{ value: 'normal', label: '正常' },
|
||||
{ value: 'none', label: '无' },
|
||||
] as const;
|
||||
|
||||
export const TRANSFER_STATUS_OPTIONS = [
|
||||
{ value: 'doing', label: '过户中' },
|
||||
{ value: 'internal_done', label: '内部过户完成' },
|
||||
{ value: 'sale_done', label: '销售过户完成' },
|
||||
{ value: 'none', label: '无' },
|
||||
] as const;
|
||||
|
||||
export const REPAIR_STATUS_OPTIONS = [
|
||||
{ value: 'pending', label: '待服务站接单' },
|
||||
{ value: 'doing', label: '维修中' },
|
||||
{ value: 'normal', label: '正常' },
|
||||
] as const;
|
||||
|
||||
export const LICENSE_STATUS_OPTIONS = [
|
||||
{ value: 'normal', label: '正常' },
|
||||
{ value: 'abnormal', label: '异常' },
|
||||
] as const;
|
||||
|
||||
export const SCRAP_STATUS_OPTIONS = [
|
||||
{ value: 'doing', label: '报废中' },
|
||||
{ value: 'done', label: '已报废' },
|
||||
{ value: 'none', label: '无' },
|
||||
] as const;
|
||||
|
||||
export const ONLINE_STATUS_OPTIONS = [
|
||||
{ value: 'online', label: '在线' },
|
||||
{ value: 'offline', label: '离线' },
|
||||
] as const;
|
||||
|
||||
export interface VehicleRecord {
|
||||
id: string;
|
||||
operationCity: string;
|
||||
vin: string;
|
||||
plateNo: string;
|
||||
vehicleNo: string;
|
||||
vehicleType: string;
|
||||
brand: string;
|
||||
model: string;
|
||||
bodyColor: string;
|
||||
parkingLot: string;
|
||||
customerName: string;
|
||||
businessDept: string;
|
||||
businessOwner: string;
|
||||
operationStatus: string;
|
||||
libStatus: string;
|
||||
outboundStatus: string;
|
||||
preemptStatus: string;
|
||||
prepStatus: string;
|
||||
transferStatus: string;
|
||||
repairStatus: string;
|
||||
licenseStatus: string;
|
||||
scrapStatus: string;
|
||||
registeredOwner: string;
|
||||
onlineStatus: string;
|
||||
manufactureYear: string;
|
||||
mileage: number;
|
||||
purchaseTime: string;
|
||||
licenseRegisterDate: string;
|
||||
licenseExpiry: string;
|
||||
lastDeliveryTime: string;
|
||||
lastDeliveryMileage: number;
|
||||
lastReturnTime: string;
|
||||
lastReturnMileage: number;
|
||||
forceScrapDate: string;
|
||||
contractNo: string;
|
||||
currentLocation: string;
|
||||
gpsLastTime: string;
|
||||
}
|
||||
|
||||
export interface VehicleFilterForm {
|
||||
operationCity?: string[];
|
||||
vehicleType?: string;
|
||||
brand?: string;
|
||||
model?: string;
|
||||
customerName?: string;
|
||||
businessDept?: string;
|
||||
contractNo?: string;
|
||||
registeredOwner?: string;
|
||||
}
|
||||
Reference in New Issue
Block a user