hikvision-web 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.html +12 -0
- package/package.json +27 -0
- package/src/App.css +48 -0
- package/src/App.css.new +48 -0
- package/src/App.tsx +157 -0
- package/src/App.tsx.new +132 -0
- package/src/index.css +39 -0
- package/src/main.tsx +14 -0
- package/src/pages/BindingPage.tsx +351 -0
- package/src/pages/CardForm.tsx +173 -0
- package/src/pages/CardList.tsx +193 -0
- package/src/pages/DashboardPage.tsx +300 -0
- package/src/pages/GroupPage.tsx +336 -0
- package/src/pages/OrgPage.tsx +198 -0
- package/src/pages/OrgTreePage.tsx +203 -0
- package/src/pages/PersonDetail.tsx +146 -0
- package/src/pages/PersonForm.tsx +199 -0
- package/src/pages/PersonList.tsx +174 -0
- package/src/pages/PersonTreePage.tsx +563 -0
- package/src/pages/SyncPage.tsx +29 -0
- package/src/pages/SystemPage.tsx +758 -0
- package/src/pages/VehicleForm.tsx +231 -0
- package/src/pages/VehicleList.tsx +199 -0
- package/src/services/api.ts +128 -0
- package/src/store/appStore.ts +159 -0
- package/src/utils/constants.ts +105 -0
- package/tsconfig.json +21 -0
- package/tsconfig.node.json +10 -0
- package/vite.config.ts +16 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 车辆表单页面(添加/编辑车辆)
|
|
3
|
+
*
|
|
4
|
+
* 重要概念澄清(2026-05-23):
|
|
5
|
+
* - 车辆群组 (car-group):停车场功能,用于车辆出入控制
|
|
6
|
+
* - 车辆分类 (category):系统预置分类(固定车、临时车等)+ 自定义群组
|
|
7
|
+
*
|
|
8
|
+
* 注意:车辆群组仅用于停车场管理,与人员通行无关
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { useEffect, useState } from 'react';
|
|
12
|
+
import { useParams, useNavigate } from 'react-router-dom';
|
|
13
|
+
import { Form, Input, Select, Button, message, Card, Spin, Space } from 'antd';
|
|
14
|
+
import { ArrowLeftOutlined, SaveOutlined, ReloadOutlined } from '@ant-design/icons';
|
|
15
|
+
import { vehicleApi, vehicleGroupApi, personApi } from '../services/api';
|
|
16
|
+
import { SYSTEM_CATEGORY_ID_ARRAY, getCategoryName } from '../utils/constants';
|
|
17
|
+
|
|
18
|
+
const { Option } = Select;
|
|
19
|
+
|
|
20
|
+
interface PersonItem {
|
|
21
|
+
personId: string;
|
|
22
|
+
personName: string;
|
|
23
|
+
orgName?: string;
|
|
24
|
+
jobNo?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface CarGroup {
|
|
28
|
+
groupId: string;
|
|
29
|
+
groupName: string;
|
|
30
|
+
groupType: '0' | '1';
|
|
31
|
+
type?: 'system' | 'custom';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default function VehicleForm() {
|
|
35
|
+
const { vehicleId } = useParams();
|
|
36
|
+
const navigate = useNavigate();
|
|
37
|
+
const isEdit = !!vehicleId;
|
|
38
|
+
const [form] = Form.useForm();
|
|
39
|
+
const [loading, setLoading] = useState(false);
|
|
40
|
+
const [submitting, setSubmitting] = useState(false);
|
|
41
|
+
const [persons, setPersons] = useState<PersonItem[]>([]);
|
|
42
|
+
const [vehicleGroups, setVehicleGroups] = useState<CarGroup[]>([]);
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
loadPersons();
|
|
46
|
+
loadVehicleGroups();
|
|
47
|
+
if (isEdit && vehicleId) {
|
|
48
|
+
loadVehicle();
|
|
49
|
+
}
|
|
50
|
+
}, [vehicleId]);
|
|
51
|
+
|
|
52
|
+
const loadPersons = async () => {
|
|
53
|
+
try {
|
|
54
|
+
const result: any = await personApi.list({ pageNo: 1, pageSize: 100 });
|
|
55
|
+
setPersons((result?.data?.list || result?.list || []).map((p: any) => ({
|
|
56
|
+
personId: p.personId,
|
|
57
|
+
personName: p.personName,
|
|
58
|
+
orgName: p.orgName,
|
|
59
|
+
jobNo: p.jobNo,
|
|
60
|
+
})));
|
|
61
|
+
} catch (error: any) {
|
|
62
|
+
console.error('加载人员失败:', error);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const loadVehicleGroups = async () => {
|
|
67
|
+
try {
|
|
68
|
+
const res: any = await vehicleGroupApi.list();
|
|
69
|
+
const list = res?.data || res || [];
|
|
70
|
+
// 添加 type 标识
|
|
71
|
+
const groupsWithType = (Array.isArray(list) ? list : []).map((g: any) => ({
|
|
72
|
+
...g,
|
|
73
|
+
type: SYSTEM_CATEGORY_ID_ARRAY.includes(g.groupId) ? 'system' as const : 'custom' as const,
|
|
74
|
+
}));
|
|
75
|
+
setVehicleGroups(groupsWithType);
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.error('加载车辆群组失败:', error);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const loadVehicle = async () => {
|
|
82
|
+
if (!vehicleId) return;
|
|
83
|
+
setLoading(true);
|
|
84
|
+
try {
|
|
85
|
+
const data = await vehicleApi.getById(vehicleId);
|
|
86
|
+
const d = data?.data || data;
|
|
87
|
+
form.setFieldsValue({
|
|
88
|
+
plateNo: d.plateNo,
|
|
89
|
+
vehicleType: d.vehicleType,
|
|
90
|
+
vehicleColor: d.vehicleColor,
|
|
91
|
+
vehicleModel: d.vehicleModel,
|
|
92
|
+
personId: d.personId,
|
|
93
|
+
groupId: d.vehicleGroup || d.groupId,
|
|
94
|
+
});
|
|
95
|
+
} catch (error: any) {
|
|
96
|
+
message.error(`加载失败: ${error.message || error}`);
|
|
97
|
+
} finally {
|
|
98
|
+
setLoading(false);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const handleSubmit = async (values: any) => {
|
|
103
|
+
setSubmitting(true);
|
|
104
|
+
try {
|
|
105
|
+
const dto = {
|
|
106
|
+
plateNo: values.plateNo,
|
|
107
|
+
vehicleType: values.vehicleType as '0' | '1' | '2' | '3',
|
|
108
|
+
vehicleColor: values.vehicleColor,
|
|
109
|
+
vehicleModel: values.vehicleModel,
|
|
110
|
+
personId: values.personId,
|
|
111
|
+
groupId: values.groupId,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
if (isEdit && vehicleId) {
|
|
115
|
+
await vehicleApi.update(vehicleId, dto);
|
|
116
|
+
message.success('更新成功');
|
|
117
|
+
navigate('/vehicle');
|
|
118
|
+
} else {
|
|
119
|
+
await vehicleApi.save(dto);
|
|
120
|
+
message.success('添加成功');
|
|
121
|
+
navigate('/vehicle');
|
|
122
|
+
}
|
|
123
|
+
} catch (error: any) {
|
|
124
|
+
message.error(`操作失败: ${error.message || error}`);
|
|
125
|
+
} finally {
|
|
126
|
+
setSubmitting(false);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// 渲染群组选项
|
|
131
|
+
const renderGroupOption = (g: CarGroup) => {
|
|
132
|
+
const label = g.type === 'system'
|
|
133
|
+
? `${g.groupName} (${getCategoryName(g.groupId)})`
|
|
134
|
+
: g.groupName;
|
|
135
|
+
return (
|
|
136
|
+
<Option key={g.groupId} value={g.groupId}>
|
|
137
|
+
{label}
|
|
138
|
+
</Option>
|
|
139
|
+
);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// 按类型分组显示
|
|
143
|
+
const systemGroups = vehicleGroups.filter(g => g.type === 'system');
|
|
144
|
+
const customGroups = vehicleGroups.filter(g => g.type === 'custom');
|
|
145
|
+
|
|
146
|
+
if (loading) {
|
|
147
|
+
return <div style={{ padding: 40, textAlign: 'center' }}><Spin size="large" /></div>;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<div>
|
|
152
|
+
<div style={{ marginBottom: 16, display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
153
|
+
<Button icon={<ArrowLeftOutlined />} onClick={() => navigate('/vehicle')}>返回</Button>
|
|
154
|
+
<h2>{isEdit ? '编辑车辆' : '添加车辆'}</h2>
|
|
155
|
+
</div>
|
|
156
|
+
|
|
157
|
+
<Card>
|
|
158
|
+
<Form
|
|
159
|
+
form={form}
|
|
160
|
+
layout="vertical"
|
|
161
|
+
onFinish={handleSubmit}
|
|
162
|
+
initialValues={{ vehicleType: '0' }}
|
|
163
|
+
>
|
|
164
|
+
<Form.Item
|
|
165
|
+
name="plateNo"
|
|
166
|
+
label="车牌号"
|
|
167
|
+
rules={[{ required: true, message: '请输入车牌号' }]}
|
|
168
|
+
>
|
|
169
|
+
<Input placeholder="如:京A12345" />
|
|
170
|
+
</Form.Item>
|
|
171
|
+
|
|
172
|
+
<Form.Item
|
|
173
|
+
name="vehicleType"
|
|
174
|
+
label="车辆类型"
|
|
175
|
+
rules={[{ required: true, message: '请选择车辆类型' }]}
|
|
176
|
+
>
|
|
177
|
+
<Select placeholder="请选择车辆类型">
|
|
178
|
+
<Option value="0">燃油车</Option>
|
|
179
|
+
<Option value="1">新能源车</Option>
|
|
180
|
+
<Option value="2">电动车</Option>
|
|
181
|
+
<Option value="3">其他</Option>
|
|
182
|
+
</Select>
|
|
183
|
+
</Form.Item>
|
|
184
|
+
|
|
185
|
+
<Form.Item name="vehicleColor" label="颜色">
|
|
186
|
+
<Input placeholder="如:白色、黑色" />
|
|
187
|
+
</Form.Item>
|
|
188
|
+
|
|
189
|
+
<Form.Item name="vehicleModel" label="品牌型号">
|
|
190
|
+
<Input placeholder="如:特斯拉 Model 3" />
|
|
191
|
+
</Form.Item>
|
|
192
|
+
|
|
193
|
+
<Form.Item name="personId" label="所属人员">
|
|
194
|
+
<Select placeholder="选择所属人员" showSearch allowClear>
|
|
195
|
+
{persons.map((p) => (
|
|
196
|
+
<Option key={p.personId} value={p.personId}>
|
|
197
|
+
{p.personName} - {p.orgName || '-'} {p.jobNo ? `(${p.jobNo})` : ''}
|
|
198
|
+
</Option>
|
|
199
|
+
))}
|
|
200
|
+
</Select>
|
|
201
|
+
</Form.Item>
|
|
202
|
+
|
|
203
|
+
<Form.Item name="groupId" label="车辆群组/分类">
|
|
204
|
+
<Select placeholder="选择车辆群组或分类" allowClear>
|
|
205
|
+
{systemGroups.length > 0 && (
|
|
206
|
+
<Select.OptGroup label="系统分类">
|
|
207
|
+
{systemGroups.map(renderGroupOption)}
|
|
208
|
+
</Select.OptGroup>
|
|
209
|
+
)}
|
|
210
|
+
{customGroups.length > 0 && (
|
|
211
|
+
<Select.OptGroup label="自定义群组">
|
|
212
|
+
{customGroups.map(renderGroupOption)}
|
|
213
|
+
</Select.OptGroup>
|
|
214
|
+
)}
|
|
215
|
+
</Select>
|
|
216
|
+
</Form.Item>
|
|
217
|
+
|
|
218
|
+
<Form.Item style={{ marginTop: 24 }}>
|
|
219
|
+
<Space>
|
|
220
|
+
<Button type="primary" htmlType="submit" icon={<SaveOutlined />} loading={submitting}>
|
|
221
|
+
{isEdit ? '保存修改' : '添加车辆'}
|
|
222
|
+
</Button>
|
|
223
|
+
<Button icon={<ReloadOutlined />} onClick={() => form.resetFields()}>重置</Button>
|
|
224
|
+
<Button onClick={() => navigate('/vehicle')}>取消</Button>
|
|
225
|
+
</Space>
|
|
226
|
+
</Form.Item>
|
|
227
|
+
</Form>
|
|
228
|
+
</Card>
|
|
229
|
+
</div>
|
|
230
|
+
);
|
|
231
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 车辆列表页面
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useEffect, useState } from 'react';
|
|
6
|
+
import { Table, Button, Input, Space, Modal, Select, Tag, message } from 'antd';
|
|
7
|
+
import { SearchOutlined, PlusOutlined, EditOutlined, DeleteOutlined, ReloadOutlined } from '@ant-design/icons';
|
|
8
|
+
import { useAppStore } from '../store/appStore';
|
|
9
|
+
import { vehicleApi } from '../services/api';
|
|
10
|
+
import { Link } from 'react-router-dom';
|
|
11
|
+
import { SYSTEM_CATEGORY_ID_ARRAY, getCategoryName } from '../utils/constants';
|
|
12
|
+
|
|
13
|
+
const { Search } = Input;
|
|
14
|
+
|
|
15
|
+
export default function VehicleList() {
|
|
16
|
+
const { vehicles, isVehicleLoading, loadVehicles } = useAppStore();
|
|
17
|
+
|
|
18
|
+
const [searchPlate, setSearchPlate] = useState('');
|
|
19
|
+
const [searchPerson, setSearchPerson] = useState('');
|
|
20
|
+
const [page, setPage] = useState(1);
|
|
21
|
+
const [pageSize, setPageSize] = useState(20);
|
|
22
|
+
const [deleteModal, setDeleteModal] = useState<{ visible: boolean; vehicleId?: string; plateNo?: string }>({ visible: false });
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
const timer = setTimeout(() => {
|
|
26
|
+
loadVehicles({ pageNo: page, pageSize, plateNo: searchPlate, personName: searchPerson });
|
|
27
|
+
}, 300);
|
|
28
|
+
return () => clearTimeout(timer);
|
|
29
|
+
}, [page, pageSize, searchPlate, searchPerson]);
|
|
30
|
+
|
|
31
|
+
const handleSearch = () => {
|
|
32
|
+
setPage(1);
|
|
33
|
+
loadVehicles({ pageNo: 1, pageSize, plateNo: searchPlate, personName: searchPerson });
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const handleDelete = async () => {
|
|
37
|
+
if (!deleteModal.vehicleId) return;
|
|
38
|
+
try {
|
|
39
|
+
await vehicleApi.delete(deleteModal.vehicleId);
|
|
40
|
+
message.success('删除成功');
|
|
41
|
+
setDeleteModal({ visible: false });
|
|
42
|
+
loadVehicles({ pageNo: page, pageSize });
|
|
43
|
+
} catch (error: any) {
|
|
44
|
+
message.error(`删除失败: ${error.message || error}`);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const handleRefresh = () => {
|
|
49
|
+
loadVehicles({ pageNo: page, pageSize, plateNo: searchPlate, personName: searchPerson });
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// 渲染群组名称
|
|
53
|
+
const renderGroupName = (record: any) => {
|
|
54
|
+
const groupId = record.vehicleGroup || record.categoryCode || record.groupId;
|
|
55
|
+
if (!groupId) return '-';
|
|
56
|
+
const isSystem = SYSTEM_CATEGORY_ID_ARRAY.includes(groupId);
|
|
57
|
+
if (isSystem) {
|
|
58
|
+
return <Tag color="blue">{getCategoryName(groupId)}</Tag>;
|
|
59
|
+
}
|
|
60
|
+
const label = record.vehicleGroupName || record.categoryName || record.groupName || groupId;
|
|
61
|
+
return <Tag color="green">{label}</Tag>;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const columns = [
|
|
65
|
+
{
|
|
66
|
+
title: '车辆 ID',
|
|
67
|
+
dataIndex: 'vehicleId',
|
|
68
|
+
key: 'vehicleId',
|
|
69
|
+
width: 200,
|
|
70
|
+
render: (text: string) => <code style={{ fontSize: 12 }}>{text}</code>,
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
title: '车牌号',
|
|
74
|
+
dataIndex: 'plateNo',
|
|
75
|
+
key: 'plateNo',
|
|
76
|
+
width: 120,
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
title: '车辆类型',
|
|
80
|
+
dataIndex: 'vehicleType',
|
|
81
|
+
key: 'vehicleType',
|
|
82
|
+
width: 80,
|
|
83
|
+
render: (t: string) => {
|
|
84
|
+
const map: Record<string, string> = { '0': '燃油车', '1': '新能源车', '2': '电动车', '3': '其他' };
|
|
85
|
+
return map[t] || t;
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
title: '颜色',
|
|
90
|
+
dataIndex: 'vehicleColor',
|
|
91
|
+
key: 'vehicleColor',
|
|
92
|
+
width: 80,
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
title: '品牌型号',
|
|
96
|
+
dataIndex: 'vehicleModel',
|
|
97
|
+
key: 'vehicleModel',
|
|
98
|
+
width: 120,
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
title: '所属人员',
|
|
102
|
+
dataIndex: 'personName',
|
|
103
|
+
key: 'personName',
|
|
104
|
+
width: 100,
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
title: '群组/分类',
|
|
108
|
+
dataIndex: 'groupId',
|
|
109
|
+
key: 'groupId',
|
|
110
|
+
width: 120,
|
|
111
|
+
render: ( _: any, record: any) => renderGroupName(record),
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
title: '操作',
|
|
115
|
+
key: 'actions',
|
|
116
|
+
width: 150,
|
|
117
|
+
render: (_: any, record: any) => (
|
|
118
|
+
<Space size="small">
|
|
119
|
+
<Link to={`/vehicle/edit/${record.vehicleId}`}>
|
|
120
|
+
<Button type="link" icon={<EditOutlined />} size="small">编辑</Button>
|
|
121
|
+
</Link>
|
|
122
|
+
<Button
|
|
123
|
+
type="link"
|
|
124
|
+
danger
|
|
125
|
+
icon={<DeleteOutlined />}
|
|
126
|
+
size="small"
|
|
127
|
+
onClick={() => setDeleteModal({ visible: true, vehicleId: record.vehicleId, plateNo: record.plateNo })}
|
|
128
|
+
>
|
|
129
|
+
删除
|
|
130
|
+
</Button>
|
|
131
|
+
</Space>
|
|
132
|
+
),
|
|
133
|
+
},
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<div>
|
|
138
|
+
<div style={{ marginBottom: 16, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
139
|
+
<h2 className="page-title" style={{ marginBottom: 0 }}>车辆管理</h2>
|
|
140
|
+
<Space>
|
|
141
|
+
<Link to="/vehicle/add">
|
|
142
|
+
<Button type="primary" icon={<PlusOutlined />}>添加车辆</Button>
|
|
143
|
+
</Link>
|
|
144
|
+
</Space>
|
|
145
|
+
</div>
|
|
146
|
+
|
|
147
|
+
<div style={{ marginBottom: 16, display: 'flex', gap: 8, alignItems: 'center' }}>
|
|
148
|
+
<Search
|
|
149
|
+
placeholder="搜索车牌号"
|
|
150
|
+
value={searchPlate}
|
|
151
|
+
onChange={(e) => setSearchPlate(e.target.value)}
|
|
152
|
+
onPressEnter={handleSearch}
|
|
153
|
+
style={{ width: 160 }}
|
|
154
|
+
/>
|
|
155
|
+
<Input
|
|
156
|
+
placeholder="搜索人员"
|
|
157
|
+
value={searchPerson}
|
|
158
|
+
onChange={(e) => setSearchPerson(e.target.value)}
|
|
159
|
+
onPressEnter={handleSearch}
|
|
160
|
+
style={{ width: 160 }}
|
|
161
|
+
/>
|
|
162
|
+
<Button icon={<SearchOutlined />} onClick={handleSearch}>搜索</Button>
|
|
163
|
+
<Button icon={<ReloadOutlined />} onClick={handleRefresh}>刷新</Button>
|
|
164
|
+
<Select
|
|
165
|
+
value={pageSize}
|
|
166
|
+
onChange={(v) => { setPageSize(v); setPage(1); }}
|
|
167
|
+
style={{ width: 100 }}
|
|
168
|
+
options={[{ value: 10, label: '10/页' }, { value: 20, label: '20/页' }, { value: 50, label: '50/页' }]}
|
|
169
|
+
/>
|
|
170
|
+
</div>
|
|
171
|
+
|
|
172
|
+
<Table
|
|
173
|
+
columns={columns}
|
|
174
|
+
dataSource={vehicles}
|
|
175
|
+
rowKey="vehicleId"
|
|
176
|
+
loading={isVehicleLoading}
|
|
177
|
+
pagination={{
|
|
178
|
+
current: page,
|
|
179
|
+
pageSize,
|
|
180
|
+
showSizeChanger: true,
|
|
181
|
+
showTotal: (t) => `共 ${t} 条`,
|
|
182
|
+
onChange: (p, ps) => { setPage(p); setPageSize(ps); },
|
|
183
|
+
}}
|
|
184
|
+
/>
|
|
185
|
+
|
|
186
|
+
<Modal
|
|
187
|
+
title="确认删除"
|
|
188
|
+
open={deleteModal.visible}
|
|
189
|
+
onOk={handleDelete}
|
|
190
|
+
onCancel={() => setDeleteModal({ visible: false })}
|
|
191
|
+
okText="确认删除"
|
|
192
|
+
okButtonProps={{ danger: true }}
|
|
193
|
+
>
|
|
194
|
+
<p>确定要删除车辆 <strong>{deleteModal.plateNo}</strong> 吗?</p>
|
|
195
|
+
<p style={{ color: '#ff4d4f' }}>此操作不可撤销!</p>
|
|
196
|
+
</Modal>
|
|
197
|
+
</div>
|
|
198
|
+
);
|
|
199
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API 服务层 - 浏览器端调用 Express API Server
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import axios from 'axios';
|
|
6
|
+
|
|
7
|
+
const API_BASE = '/api';
|
|
8
|
+
const API_KEY_STORAGE = 'hikvision_api_key';
|
|
9
|
+
|
|
10
|
+
const apiClient = axios.create({
|
|
11
|
+
baseURL: API_BASE,
|
|
12
|
+
timeout: 30000,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
apiClient.interceptors.response.use(
|
|
16
|
+
(response) => response.data,
|
|
17
|
+
(error) => Promise.reject(error)
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
// 请求拦截器:自动添加 API Key
|
|
21
|
+
apiClient.interceptors.request.use(async (config) => {
|
|
22
|
+
const apiKey = localStorage.getItem(API_KEY_STORAGE);
|
|
23
|
+
if (apiKey) {
|
|
24
|
+
config.headers['x-api-key'] = apiKey;
|
|
25
|
+
}
|
|
26
|
+
return config;
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
// 保存 API Key 到本地存储
|
|
31
|
+
export const saveApiKey = (key: string) => {
|
|
32
|
+
localStorage.setItem(API_KEY_STORAGE, key);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// 获取 API Key
|
|
36
|
+
export const getApiKey = () => localStorage.getItem(API_KEY_STORAGE) || '';
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
// 清除 API Key
|
|
40
|
+
export const clearApiKey = () => localStorage.removeItem(API_KEY_STORAGE);
|
|
41
|
+
|
|
42
|
+
// ========== 人员 API ==========
|
|
43
|
+
export const personApi = {
|
|
44
|
+
list: (params: { pageNo: number; pageSize: number; personName?: string; orgIndexCode?: string }) =>
|
|
45
|
+
apiClient.get('/persons', { params }),
|
|
46
|
+
search: (params: { pageNo: number; pageSize: number; personName?: string }) =>
|
|
47
|
+
apiClient.get('/persons/search', { params: { pageNo: params.pageNo, pageSize: params.pageSize, name: params.personName } }),
|
|
48
|
+
getById: (personId: string) =>
|
|
49
|
+
apiClient.get(`/persons/${encodeURIComponent(personId)}`),
|
|
50
|
+
create: (data: any) => apiClient.post('/persons', data),
|
|
51
|
+
update: (personId: string, data: any) => apiClient.put(`/persons/${encodeURIComponent(personId)}`, data),
|
|
52
|
+
delete: (personId: string) => apiClient.delete(`/persons/${encodeURIComponent(personId)}`),
|
|
53
|
+
batchDelete: (personIds: string[]) => apiClient.post('/persons/batch-delete', { personIds }),
|
|
54
|
+
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// ========== 组织 API ==========
|
|
58
|
+
export const orgApi = {
|
|
59
|
+
list: () => apiClient.get('/orgs'),
|
|
60
|
+
getById: (orgIndexCode: string) =>
|
|
61
|
+
apiClient.get(`/orgs/${encodeURIComponent(orgIndexCode)}`),
|
|
62
|
+
getRoot: () => apiClient.get('/orgs/root'),
|
|
63
|
+
getChildren: (orgIndexCode: string) =>
|
|
64
|
+
apiClient.get(`/orgs/${encodeURIComponent(orgIndexCode)}/children`),
|
|
65
|
+
create: (data: any) => apiClient.post('/orgs', data),
|
|
66
|
+
update: (orgIndexCode: string, data: any) =>
|
|
67
|
+
apiClient.put(`/orgs/${encodeURIComponent(orgIndexCode)}`, data),
|
|
68
|
+
delete: (orgIndexCodes: string[]) =>
|
|
69
|
+
apiClient.delete('/orgs', { data: { orgIndexCodes } }),
|
|
70
|
+
sync: (params: { startTime: string; endTime: string }) =>
|
|
71
|
+
apiClient.post('/orgs/sync', params),
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// ========== 卡片 API ==========
|
|
75
|
+
export const cardApi = {
|
|
76
|
+
list: (params: { pageNo: number; pageSize: number; plateNo?: string; personName?: string }) =>
|
|
77
|
+
apiClient.get('/cards', { params }),
|
|
78
|
+
getByCardNo: (cardNo: string) =>
|
|
79
|
+
apiClient.get(`/cards/${encodeURIComponent(cardNo)}`),
|
|
80
|
+
batchIssue: (cards: any[]) => apiClient.post('/cards/issue', { cards }),
|
|
81
|
+
unbind: (personId: string, cardNo: string) => apiClient.post('/cards/unbind', { personId, cardNo }),
|
|
82
|
+
unloss: (cardNo: string) => apiClient.post('/cards/unloss', { cardNo }),
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// ========== 车辆 API ==========
|
|
86
|
+
export const vehicleApi = {
|
|
87
|
+
list: (params: { pageNo: number; pageSize: number; plateNo?: string; personName?: string }) =>
|
|
88
|
+
apiClient.get('/vehicles', { params }),
|
|
89
|
+
getById: (vehicleId: string) =>
|
|
90
|
+
apiClient.get(`/vehicles/${encodeURIComponent(vehicleId)}`),
|
|
91
|
+
save: (data: any) => apiClient.post('/vehicles', data),
|
|
92
|
+
saveOrUpdate: (data: any) => apiClient.post('/vehicles', data),
|
|
93
|
+
update: (vehicleId: string, data: any) => apiClient.put(`/vehicles/${encodeURIComponent(vehicleId)}`, data),
|
|
94
|
+
delete: (vehicleId: string) => apiClient.delete(`/vehicles/${encodeURIComponent(vehicleId)}`),
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// ========== 分组 API ==========
|
|
98
|
+
export const vehicleGroupApi = {
|
|
99
|
+
list: () => apiClient.get('/vehicle-groups'),
|
|
100
|
+
create: (data: any) => apiClient.post('/vehicle-groups', data),
|
|
101
|
+
update: (groupId: string, data: any) => apiClient.put(`/vehicle-groups/${encodeURIComponent(groupId)}`, data),
|
|
102
|
+
delete: (groupId: string) => apiClient.delete(`/vehicle-groups/${encodeURIComponent(groupId)}`),
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// ========== 绑定关系 API ==========
|
|
106
|
+
export const bindingApi = {
|
|
107
|
+
listPersonCardBindings: () => apiClient.get('/bindings/person-card'),
|
|
108
|
+
listPersonVehicleBindings: () => apiClient.get('/bindings/person-vehicle'),
|
|
109
|
+
getPersonBindings: (personId: string) => apiClient.get('/persons/' + personId + '/bindings'),
|
|
110
|
+
bindPersonCard: (personId: string, cardNo: string) => apiClient.post('/bindings/person-card', { personId, cardNo }),
|
|
111
|
+
unbindPersonCard: (personId: string, cardNo: string) => apiClient.delete(`/bindings/person-card?personId=${personId}&cardNo=${encodeURIComponent(cardNo)}`),
|
|
112
|
+
bindPersonVehicle: (personId: string, vehicleId: string) => apiClient.post('/bindings/person-vehicle', { personId, vehicleId }),
|
|
113
|
+
unbindPersonVehicle: (personId: string, vehicleId: string) => apiClient.delete(`/bindings/person-vehicle?personId=${personId}&vehicleId=${encodeURIComponent(vehicleId)}`),
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// ========== 同步 API ==========
|
|
117
|
+
export const syncApi = {
|
|
118
|
+
getStatus: () => apiClient.get('/sync/status'),
|
|
119
|
+
getRecords: (params: { pageNo: number; pageSize: number }) => apiClient.get('/sync/records', { params }),
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// ========== 统计 API ==========
|
|
123
|
+
export const statApi = {
|
|
124
|
+
getStats: () => apiClient.get('/stats'),
|
|
125
|
+
getPersonsByOrg: () => apiClient.get('/stats/persons-by-org'),
|
|
126
|
+
getVehiclesByStatus: () => apiClient.get('/stats/vehicles-by-status'),
|
|
127
|
+
getCardsByStatus: () => apiClient.get('/stats/cards-by-status'),
|
|
128
|
+
};
|