feat(frontend): add vehicle replacement pages and enhance delivery/return/prepare with inspection
- Create vehicle-replacement module (data.ts, index.vue, form.vue) with full CRUD, BPM approval actions, and conditional row actions - Enhance vehicle-prepare form with InspectionForm component (backwards compatible with old hardcoded checklist) - Enhance delivery-order with "还车" and "替换车" action buttons on completed orders, plus InspectionForm integration - Enhance return-order with BPM approval submit/withdraw actions and per-vehicle inspection start/view capability - Add inspectionRecordId to vehicle-prepare and delivery-order API types Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
71
apps/web-antd/src/api/asset/delivery-order.ts
Normal file
71
apps/web-antd/src/api/asset/delivery-order.ts
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import type { PageParam, PageResult } from '@vben/request';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export namespace AssetDeliveryOrderApi {
|
||||||
|
export interface DeliveryOrderVehicle {
|
||||||
|
id?: number;
|
||||||
|
taskVehicleId?: number;
|
||||||
|
vehicleId?: number;
|
||||||
|
plateNo?: string;
|
||||||
|
vin?: string;
|
||||||
|
brand?: string;
|
||||||
|
model?: string;
|
||||||
|
mileage?: number;
|
||||||
|
hydrogenLevel?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DeliveryOrder {
|
||||||
|
id?: number;
|
||||||
|
orderCode?: string;
|
||||||
|
taskId: number;
|
||||||
|
taskCode?: string;
|
||||||
|
contractId?: number;
|
||||||
|
contractCode?: string;
|
||||||
|
projectName?: string;
|
||||||
|
customerId?: number;
|
||||||
|
customerName?: string;
|
||||||
|
deliveryDate: string;
|
||||||
|
deliveryPerson: string;
|
||||||
|
deliveryLocation?: string;
|
||||||
|
authorizedPersonId?: number;
|
||||||
|
authorizedPersonName?: string;
|
||||||
|
authorizedPersonPhone?: string;
|
||||||
|
authorizedPersonIdCard?: string;
|
||||||
|
esignStatus?: number;
|
||||||
|
deliveryPhotos?: string;
|
||||||
|
inspectionRecordId?: number;
|
||||||
|
status?: number;
|
||||||
|
vehicles?: DeliveryOrderVehicle[];
|
||||||
|
createTime?: Date;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDeliveryOrderPage(params: PageParam) {
|
||||||
|
return requestClient.get<PageResult<AssetDeliveryOrderApi.DeliveryOrder>>(
|
||||||
|
'/asset/delivery-order/page',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDeliveryOrder(id: number) {
|
||||||
|
return requestClient.get<AssetDeliveryOrderApi.DeliveryOrder>(
|
||||||
|
`/asset/delivery-order/get?id=${id}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createDeliveryOrder(data: AssetDeliveryOrderApi.DeliveryOrder) {
|
||||||
|
return requestClient.post('/asset/delivery-order/create', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateDeliveryOrder(data: AssetDeliveryOrderApi.DeliveryOrder) {
|
||||||
|
return requestClient.put('/asset/delivery-order/update', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteDeliveryOrder(id: number) {
|
||||||
|
return requestClient.delete(`/asset/delivery-order/delete?id=${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function completeDeliveryOrder(id: number) {
|
||||||
|
return requestClient.put(`/asset/delivery-order/complete?id=${id}`);
|
||||||
|
}
|
||||||
62
apps/web-antd/src/api/asset/vehicle-prepare.ts
Normal file
62
apps/web-antd/src/api/asset/vehicle-prepare.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import type { PageParam, PageResult } from '@vben/request';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export namespace AssetVehiclePrepareApi {
|
||||||
|
export interface VehiclePrepare {
|
||||||
|
id?: number;
|
||||||
|
vehicleId: number;
|
||||||
|
plateNo?: string;
|
||||||
|
vin?: string;
|
||||||
|
vehicleType?: string;
|
||||||
|
vehicleModelId?: number;
|
||||||
|
brand?: string;
|
||||||
|
model?: string;
|
||||||
|
contractId?: number;
|
||||||
|
contractCode?: string;
|
||||||
|
parkingLot?: string;
|
||||||
|
hasBodyAd?: boolean;
|
||||||
|
bodyAdPhoto?: string;
|
||||||
|
enlargedTextPhoto?: string;
|
||||||
|
hasTailLift?: boolean;
|
||||||
|
spareTireDepth?: number;
|
||||||
|
spareTirePhoto?: string;
|
||||||
|
defectPhotos?: string;
|
||||||
|
trailerPlateNo?: string;
|
||||||
|
checkList?: string;
|
||||||
|
inspectionRecordId?: number;
|
||||||
|
status?: number;
|
||||||
|
completeTime?: Date;
|
||||||
|
creator?: string;
|
||||||
|
createTime?: Date;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getVehiclePreparePage(params: PageParam) {
|
||||||
|
return requestClient.get<PageResult<AssetVehiclePrepareApi.VehiclePrepare>>(
|
||||||
|
'/asset/vehicle-prepare/page',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getVehiclePrepare(id: number) {
|
||||||
|
return requestClient.get<AssetVehiclePrepareApi.VehiclePrepare>(
|
||||||
|
`/asset/vehicle-prepare/get?id=${id}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createVehiclePrepare(data: AssetVehiclePrepareApi.VehiclePrepare) {
|
||||||
|
return requestClient.post('/asset/vehicle-prepare/create', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateVehiclePrepare(data: AssetVehiclePrepareApi.VehiclePrepare) {
|
||||||
|
return requestClient.put('/asset/vehicle-prepare/update', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteVehiclePrepare(id: number) {
|
||||||
|
return requestClient.delete(`/asset/vehicle-prepare/delete?id=${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function completeVehiclePrepare(id: number) {
|
||||||
|
return requestClient.put(`/asset/vehicle-prepare/complete?id=${id}`);
|
||||||
|
}
|
||||||
174
apps/web-antd/src/views/asset/delivery-order/data.ts
Normal file
174
apps/web-antd/src/views/asset/delivery-order/data.ts
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
|
|
||||||
|
import { z } from '#/adapter/form';
|
||||||
|
import { getRangePickerDefaultProps } from '#/utils';
|
||||||
|
|
||||||
|
// ========== 新增/编辑表单 ==========
|
||||||
|
export function useFormSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'id',
|
||||||
|
dependencies: { triggerFields: [''], show: () => false },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'taskId',
|
||||||
|
label: '交车任务',
|
||||||
|
component: 'ApiSelect',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请选择交车任务',
|
||||||
|
api: () =>
|
||||||
|
import('#/api/asset/delivery-task').then((m) =>
|
||||||
|
m.getSimpleDeliveryTaskList(),
|
||||||
|
),
|
||||||
|
labelField: 'taskCode',
|
||||||
|
valueField: 'id',
|
||||||
|
showSearch: true,
|
||||||
|
filterOption: (input: string, option: any) =>
|
||||||
|
(option?.label ?? '').toLowerCase().includes(input.toLowerCase()),
|
||||||
|
},
|
||||||
|
rules: z.number().min(1, { message: '请选择交车任务' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'deliveryDate',
|
||||||
|
label: '交车日期',
|
||||||
|
component: 'DatePicker',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请选择交车日期',
|
||||||
|
showTime: true,
|
||||||
|
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||||
|
style: { width: '100%' },
|
||||||
|
},
|
||||||
|
rules: z.string().min(1, { message: '请选择交车日期' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'deliveryPerson',
|
||||||
|
label: '交车人',
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: { placeholder: '请输入交车人' },
|
||||||
|
rules: z.string().min(1, { message: '请输入交车人' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'deliveryLocation',
|
||||||
|
label: '交车地点',
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: { placeholder: '请输入交车地点' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'authorizedPersonName',
|
||||||
|
label: '被授权人姓名',
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: { placeholder: '请输入被授权人姓名' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'authorizedPersonPhone',
|
||||||
|
label: '被授权人电话',
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: { placeholder: '请输入被授权人电话' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'authorizedPersonIdCard',
|
||||||
|
label: '被授权人身份证',
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: { placeholder: '请输入被授权人身份证' },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 搜索表单 ==========
|
||||||
|
export function useGridFormSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
fieldName: 'contractCode',
|
||||||
|
label: '合同编码',
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: { placeholder: '请输入合同编码' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'projectName',
|
||||||
|
label: '项目名称',
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: { placeholder: '请输入项目名称' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'customerName',
|
||||||
|
label: '客户名称',
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: { placeholder: '请输入客户名称' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'deliveryRegion',
|
||||||
|
label: '交车区域',
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: { placeholder: '请输入交车区域' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'deliveryDate',
|
||||||
|
label: '交车时间',
|
||||||
|
component: 'RangePicker',
|
||||||
|
componentProps: getRangePickerDefaultProps(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'deliveryPerson',
|
||||||
|
label: '交车人',
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: { placeholder: '请输入交车人' },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 待处理列表列 ==========
|
||||||
|
export function usePendingColumns(): VxeTableGridOptions['columns'] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
field: 'expectedDeliveryDate',
|
||||||
|
title: '预计交车时间',
|
||||||
|
minWidth: 200,
|
||||||
|
formatter: ({ row }: { row: any }) => {
|
||||||
|
const start = row.expectedDeliveryDateStart || '';
|
||||||
|
const end = row.expectedDeliveryDateEnd || '';
|
||||||
|
return end ? `${start} 至 ${end}` : start;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ field: 'createTime', title: '任务发布时间', minWidth: 160, formatter: 'formatDateTime' },
|
||||||
|
{ field: 'contractCode', title: '合同编码', minWidth: 220 },
|
||||||
|
{ field: 'projectName', title: '项目名称', minWidth: 150 },
|
||||||
|
{ field: 'customerName', title: '客户名称', minWidth: 150 },
|
||||||
|
{ field: 'vehicleCount', title: '交车数量', minWidth: 100, align: 'center' },
|
||||||
|
{ field: 'deliveryRegion', title: '交车区域', minWidth: 120 },
|
||||||
|
{ field: 'deliveryLocation', title: '交车地点', minWidth: 150 },
|
||||||
|
{ title: '操作', width: 160, fixed: 'right', slots: { default: 'actions' } },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 历史记录列表列 ==========
|
||||||
|
export function useHistoryColumns(): VxeTableGridOptions['columns'] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
field: 'expectedDeliveryDate',
|
||||||
|
title: '预计交车时间',
|
||||||
|
minWidth: 200,
|
||||||
|
formatter: ({ row }: { row: any }) => {
|
||||||
|
const start = row.expectedDeliveryDateStart || '';
|
||||||
|
const end = row.expectedDeliveryDateEnd || '';
|
||||||
|
return end ? `${start} 至 ${end}` : start;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ field: 'createTime', title: '任务发布时间', minWidth: 160, formatter: 'formatDateTime' },
|
||||||
|
{ field: 'completeTime', title: '交车完成时间', minWidth: 160, formatter: 'formatDateTime' },
|
||||||
|
{ field: 'deliveryPerson', title: '交车人', minWidth: 100 },
|
||||||
|
{ field: 'contractCode', title: '合同编码', minWidth: 220 },
|
||||||
|
{ field: 'projectName', title: '项目名称', minWidth: 150 },
|
||||||
|
{ field: 'customerName', title: '客户名称', minWidth: 150 },
|
||||||
|
{ field: 'vehicleCount', title: '交车数量', minWidth: 100, align: 'center' },
|
||||||
|
{ field: 'deliveryRegion', title: '交车区域', minWidth: 120 },
|
||||||
|
{ field: 'deliveryLocation', title: '交车地点', minWidth: 150 },
|
||||||
|
{ title: '操作', width: 100, fixed: 'right', slots: { default: 'actions' } },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 兼容旧引用 ==========
|
||||||
|
export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||||
|
return usePendingColumns();
|
||||||
|
}
|
||||||
201
apps/web-antd/src/views/asset/delivery-order/index.vue
Normal file
201
apps/web-antd/src/views/asset/delivery-order/index.vue
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
|
import type { AssetDeliveryOrderApi } from '#/api/asset/delivery-order';
|
||||||
|
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import { confirm, Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { message, Tabs } from 'ant-design-vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import {
|
||||||
|
completeDeliveryOrder,
|
||||||
|
deleteDeliveryOrder,
|
||||||
|
getDeliveryOrderPage,
|
||||||
|
} from '#/api/asset/delivery-order';
|
||||||
|
import { createReturnOrderFromDelivery } from '#/api/asset/return-order';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
|
import {
|
||||||
|
useGridFormSchema,
|
||||||
|
useHistoryColumns,
|
||||||
|
usePendingColumns,
|
||||||
|
} from './data';
|
||||||
|
import Form from './modules/form.vue';
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const [FormModal, formModalApi] = useVbenModal({
|
||||||
|
connectedComponent: Form,
|
||||||
|
destroyOnClose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const activeTab = ref('pending');
|
||||||
|
|
||||||
|
function handleRefresh() {
|
||||||
|
gridApi.query();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCreate() {
|
||||||
|
formModalApi.setData(null).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleEdit(row: AssetDeliveryOrderApi.DeliveryOrder) {
|
||||||
|
formModalApi.setData(row).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleDelete(row: AssetDeliveryOrderApi.DeliveryOrder) {
|
||||||
|
const hideLoading = message.loading({
|
||||||
|
content: $t('ui.actionMessage.deleting', [row.orderCode]),
|
||||||
|
duration: 0,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await deleteDeliveryOrder(row.id!);
|
||||||
|
message.success($t('ui.actionMessage.deleteSuccess', [row.orderCode]));
|
||||||
|
handleRefresh();
|
||||||
|
} finally {
|
||||||
|
hideLoading();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleComplete(row: AssetDeliveryOrderApi.DeliveryOrder) {
|
||||||
|
await confirm('确认完成交车?完成后将更新任务车辆状态。');
|
||||||
|
await completeDeliveryOrder(row.id!);
|
||||||
|
message.success('交车已完成');
|
||||||
|
handleRefresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 还车:从交车单创建还车单
|
||||||
|
async function handleReturn(row: AssetDeliveryOrderApi.DeliveryOrder) {
|
||||||
|
await confirm('确认为该交车单创建还车单?');
|
||||||
|
const vehicleIds = (row.vehicles || []).map((v: any) => v.vehicleId).filter(Boolean);
|
||||||
|
if (vehicleIds.length === 0) {
|
||||||
|
message.warning('该交车单没有关联车辆');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await createReturnOrderFromDelivery(row.id!, vehicleIds);
|
||||||
|
message.success('还车单已创建');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 替换车:跳转到替换车新增页面,带上参数
|
||||||
|
function handleReplacement(row: AssetDeliveryOrderApi.DeliveryOrder) {
|
||||||
|
router.push({
|
||||||
|
path: '/asset/vehicle-replacement',
|
||||||
|
query: {
|
||||||
|
contractId: String(row.contractId || ''),
|
||||||
|
deliveryOrderId: String(row.id || ''),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
|
formOptions: {
|
||||||
|
schema: useGridFormSchema(),
|
||||||
|
},
|
||||||
|
gridOptions: {
|
||||||
|
columns: usePendingColumns(),
|
||||||
|
height: 'auto',
|
||||||
|
keepSource: true,
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async ({ page }, formValues) => {
|
||||||
|
return await getDeliveryOrderPage({
|
||||||
|
pageNo: page.currentPage,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
status: activeTab.value === 'history' ? 1 : 0,
|
||||||
|
...formValues,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rowConfig: {
|
||||||
|
keyField: 'id',
|
||||||
|
isHover: true,
|
||||||
|
},
|
||||||
|
toolbarConfig: {
|
||||||
|
refresh: true,
|
||||||
|
search: true,
|
||||||
|
},
|
||||||
|
} as VxeTableGridOptions<AssetDeliveryOrderApi.DeliveryOrder>,
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleTabChange(key: string) {
|
||||||
|
activeTab.value = key;
|
||||||
|
const columns =
|
||||||
|
key === 'history' ? useHistoryColumns() : usePendingColumns();
|
||||||
|
gridApi.grid?.reloadColumn(columns as any);
|
||||||
|
gridApi.query();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height>
|
||||||
|
<FormModal @success="handleRefresh" />
|
||||||
|
<Tabs :active-key="activeTab" @update:active-key="handleTabChange">
|
||||||
|
<Tabs.TabPane key="pending" tab="待处理" />
|
||||||
|
<Tabs.TabPane key="history" tab="历史记录" />
|
||||||
|
</Tabs>
|
||||||
|
<Grid :table-title="activeTab === 'history' ? '历史记录' : '待处理'">
|
||||||
|
<template #toolbar-tools>
|
||||||
|
<TableAction
|
||||||
|
v-if="activeTab === 'pending'"
|
||||||
|
:actions="[
|
||||||
|
{
|
||||||
|
label: '交车',
|
||||||
|
type: 'primary',
|
||||||
|
icon: ACTION_ICON.ADD,
|
||||||
|
auth: ['asset:delivery-order:create'],
|
||||||
|
onClick: handleCreate,
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<TableAction
|
||||||
|
v-if="activeTab === 'pending'"
|
||||||
|
:actions="[
|
||||||
|
{
|
||||||
|
label: '查看',
|
||||||
|
type: 'link',
|
||||||
|
icon: ACTION_ICON.VIEW,
|
||||||
|
auth: ['asset:delivery-order:query'],
|
||||||
|
onClick: () => message.info('查看功能开发中'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '交车',
|
||||||
|
type: 'link',
|
||||||
|
auth: ['asset:delivery-order:create'],
|
||||||
|
onClick: () => handleCreate(),
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
<TableAction
|
||||||
|
v-else
|
||||||
|
:actions="[
|
||||||
|
{
|
||||||
|
label: '查看',
|
||||||
|
type: 'link',
|
||||||
|
icon: ACTION_ICON.VIEW,
|
||||||
|
auth: ['asset:delivery-order:query'],
|
||||||
|
onClick: () => handleEdit(row),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '还车',
|
||||||
|
type: 'link',
|
||||||
|
auth: ['asset:return-order:create'],
|
||||||
|
onClick: () => handleReturn(row),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '替换车',
|
||||||
|
type: 'link',
|
||||||
|
auth: ['asset:vehicle-replacement:create'],
|
||||||
|
onClick: () => handleReplacement(row),
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</Grid>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
428
apps/web-antd/src/views/asset/delivery-order/modules/form.vue
Normal file
428
apps/web-antd/src/views/asset/delivery-order/modules/form.vue
Normal file
@@ -0,0 +1,428 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { AssetDeliveryOrderApi } from '#/api/asset/delivery-order';
|
||||||
|
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import InspectionForm from '../../components/InspectionForm.vue';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Col,
|
||||||
|
Input,
|
||||||
|
InputNumber,
|
||||||
|
message,
|
||||||
|
Row,
|
||||||
|
Space,
|
||||||
|
Switch,
|
||||||
|
Table,
|
||||||
|
} from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
import {
|
||||||
|
createDeliveryOrder,
|
||||||
|
getDeliveryOrder,
|
||||||
|
updateDeliveryOrder,
|
||||||
|
} from '#/api/asset/delivery-order';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
|
import { useFormSchema } from '../data';
|
||||||
|
|
||||||
|
const emit = defineEmits(['success']);
|
||||||
|
const formData = ref<AssetDeliveryOrderApi.DeliveryOrder>();
|
||||||
|
const vehicles = ref<AssetDeliveryOrderApi.DeliveryOrderVehicle[]>([]);
|
||||||
|
|
||||||
|
// 司机信息
|
||||||
|
const driverInfo = ref({
|
||||||
|
driverName: '',
|
||||||
|
driverIdCard: '',
|
||||||
|
driverPhone: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
// 费用信息
|
||||||
|
const costList = ref<{ costName: string; amount: number; remark: string }[]>(
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
// 交检清单数据
|
||||||
|
const inspectionData = ref<
|
||||||
|
Record<string, { checked: boolean; remark: string }>
|
||||||
|
>({});
|
||||||
|
|
||||||
|
const INSPECTION_ITEMS = [
|
||||||
|
{
|
||||||
|
category: '车辆附件',
|
||||||
|
items: [
|
||||||
|
'行驶证',
|
||||||
|
'营运证',
|
||||||
|
'通行证',
|
||||||
|
'加氢证',
|
||||||
|
'登记证',
|
||||||
|
'保险卡',
|
||||||
|
'车钥匙',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: '外观检查',
|
||||||
|
items: ['车身完好', '挡风玻璃', '后视镜', '车灯完好', '轮胎正常'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: '车内检查',
|
||||||
|
items: [
|
||||||
|
'仪表盘正常',
|
||||||
|
'空调功能',
|
||||||
|
'安全带',
|
||||||
|
'喇叭功能',
|
||||||
|
'雨刮器',
|
||||||
|
'车窗升降',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: '动力系统',
|
||||||
|
items: ['发动机正常', '燃料电池状态', '制动功能', '转向正常'],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function initInspectionData() {
|
||||||
|
const data: Record<string, { checked: boolean; remark: string }> = {};
|
||||||
|
for (const cat of INSPECTION_ITEMS) {
|
||||||
|
for (const item of cat.items) {
|
||||||
|
data[`${cat.category}-${item}`] = { checked: true, remark: '' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inspectionData.value = data;
|
||||||
|
}
|
||||||
|
initInspectionData();
|
||||||
|
|
||||||
|
const inspectionTableData = computed(() => {
|
||||||
|
const rows: any[] = [];
|
||||||
|
for (const cat of INSPECTION_ITEMS) {
|
||||||
|
for (let i = 0; i < cat.items.length; i++) {
|
||||||
|
rows.push({
|
||||||
|
key: `${cat.category}-${cat.items[i]}`,
|
||||||
|
category: cat.category,
|
||||||
|
item: cat.items[i]!,
|
||||||
|
rowSpan: i === 0 ? cat.items.length : 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rows;
|
||||||
|
});
|
||||||
|
|
||||||
|
const inspectionColumns = [
|
||||||
|
{
|
||||||
|
title: '类别',
|
||||||
|
dataIndex: 'category',
|
||||||
|
key: 'category',
|
||||||
|
width: 120,
|
||||||
|
customCell: (_: any, index: number) => {
|
||||||
|
const row = inspectionTableData.value[index];
|
||||||
|
return { rowSpan: row?.rowSpan ?? 1 };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ title: '检查项目', dataIndex: 'item', key: 'item', width: 160 },
|
||||||
|
{ title: '交车方', key: 'deliver', width: 120 },
|
||||||
|
{ title: '接车方', key: 'receive', width: 120 },
|
||||||
|
{ title: '备注', key: 'remark', width: 180 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const getTitle = computed(() => {
|
||||||
|
return formData.value?.id
|
||||||
|
? $t('ui.actionTitle.edit', ['交车单'])
|
||||||
|
: $t('ui.actionTitle.create', ['交车单']);
|
||||||
|
});
|
||||||
|
|
||||||
|
const [BasicForm, basicFormApi] = useVbenForm({
|
||||||
|
commonConfig: {
|
||||||
|
componentProps: { class: 'w-full' },
|
||||||
|
formItemClass: 'col-span-2',
|
||||||
|
labelWidth: 150,
|
||||||
|
},
|
||||||
|
layout: 'horizontal',
|
||||||
|
schema: useFormSchema(),
|
||||||
|
showDefaultActions: false,
|
||||||
|
wrapperClass: 'grid-cols-6',
|
||||||
|
});
|
||||||
|
|
||||||
|
// 车辆明细列定义
|
||||||
|
const vehicleColumns = [
|
||||||
|
{ title: '序号', key: 'index', width: 60 },
|
||||||
|
{ title: '品牌', dataIndex: 'brand', key: 'brand', width: 100 },
|
||||||
|
{ title: '型号', dataIndex: 'model', key: 'model', width: 100 },
|
||||||
|
{ title: '车牌号', dataIndex: 'plateNo', key: 'plateNo', width: 120 },
|
||||||
|
{ title: 'VIN码', dataIndex: 'vin', key: 'vin', width: 180 },
|
||||||
|
{ title: '停车场', dataIndex: 'parkingLot', key: 'parkingLot', width: 120 },
|
||||||
|
{ title: '交车里程(km)', key: 'mileage', width: 130 },
|
||||||
|
{ title: '交车氢量(%)', key: 'hydrogenLevel', width: 120 },
|
||||||
|
{ title: '交车电量(%)', key: 'batteryLevel', width: 120 },
|
||||||
|
];
|
||||||
|
|
||||||
|
// 费用列定义
|
||||||
|
const costColumns = [
|
||||||
|
{ title: '序号', key: 'index', width: 60 },
|
||||||
|
{ title: '费用名称', key: 'costName', width: 200 },
|
||||||
|
{ title: '金额(元)', key: 'amount', width: 150 },
|
||||||
|
{ title: '备注', key: 'costRemark', width: 200 },
|
||||||
|
{ title: '操作', key: 'costAction', width: 80 },
|
||||||
|
];
|
||||||
|
|
||||||
|
function handleAddCost() {
|
||||||
|
costList.value.push({ costName: '', amount: 0, remark: '' });
|
||||||
|
}
|
||||||
|
function handleDeleteCost(index: number) {
|
||||||
|
costList.value.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const costTotal = computed(() => {
|
||||||
|
return costList.value.reduce((sum, c) => sum + (c.amount || 0), 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
async onConfirm() {
|
||||||
|
const { valid } = await basicFormApi.validate();
|
||||||
|
if (!valid) return;
|
||||||
|
|
||||||
|
modalApi.lock();
|
||||||
|
const data =
|
||||||
|
(await basicFormApi.getValues()) as AssetDeliveryOrderApi.DeliveryOrder;
|
||||||
|
data.vehicles = vehicles.value;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await (formData.value?.id
|
||||||
|
? updateDeliveryOrder(data)
|
||||||
|
: createDeliveryOrder(data));
|
||||||
|
await modalApi.close();
|
||||||
|
emit('success');
|
||||||
|
message.success($t('ui.actionMessage.operationSuccess'));
|
||||||
|
} finally {
|
||||||
|
modalApi.unlock();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async onOpenChange(isOpen: boolean) {
|
||||||
|
if (!isOpen) {
|
||||||
|
formData.value = undefined;
|
||||||
|
vehicles.value = [];
|
||||||
|
costList.value = [];
|
||||||
|
driverInfo.value = { driverName: '', driverIdCard: '', driverPhone: '' };
|
||||||
|
initInspectionData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = modalApi.getData<AssetDeliveryOrderApi.DeliveryOrder>();
|
||||||
|
if (!data || !data.id) return;
|
||||||
|
|
||||||
|
modalApi.lock();
|
||||||
|
try {
|
||||||
|
formData.value = await getDeliveryOrder(data.id);
|
||||||
|
await basicFormApi.setValues(formData.value);
|
||||||
|
vehicles.value = formData.value.vehicles || [];
|
||||||
|
} finally {
|
||||||
|
modalApi.unlock();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Modal :title="getTitle" class="w-4/5" style="max-width: 1400px">
|
||||||
|
<div class="space-y-4" style="max-height: 75vh; overflow-y: auto">
|
||||||
|
<!-- Card 1: 交车基本信息 -->
|
||||||
|
<Card title="交车基本信息" size="small">
|
||||||
|
<BasicForm />
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<!-- Card 2: 司机信息 -->
|
||||||
|
<Card title="司机信息" size="small">
|
||||||
|
<Row :gutter="24">
|
||||||
|
<Col :span="8">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="mb-1 block font-medium">司机姓名</label>
|
||||||
|
<Input
|
||||||
|
v-model:value="driverInfo.driverName"
|
||||||
|
placeholder="请输入司机姓名"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="mb-1 block font-medium">司机身份证</label>
|
||||||
|
<Input
|
||||||
|
v-model:value="driverInfo.driverIdCard"
|
||||||
|
placeholder="请输入司机身份证号"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="mb-1 block font-medium">司机手机号</label>
|
||||||
|
<Input
|
||||||
|
v-model:value="driverInfo.driverPhone"
|
||||||
|
placeholder="请输入司机手机号"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<!-- Card 3: 交车车辆明细 -->
|
||||||
|
<Card title="交车车辆明细" size="small">
|
||||||
|
<Table
|
||||||
|
:columns="vehicleColumns"
|
||||||
|
:data-source="vehicles"
|
||||||
|
:pagination="false"
|
||||||
|
:scroll="{ x: 1200 }"
|
||||||
|
size="small"
|
||||||
|
bordered
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record, index }">
|
||||||
|
<template v-if="column.key === 'index'">
|
||||||
|
{{ index + 1 }}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'mileage'">
|
||||||
|
<InputNumber
|
||||||
|
v-model:value="record.mileage"
|
||||||
|
:min="0"
|
||||||
|
:precision="0"
|
||||||
|
placeholder="里程"
|
||||||
|
size="small"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'hydrogenLevel'">
|
||||||
|
<InputNumber
|
||||||
|
v-model:value="record.hydrogenLevel"
|
||||||
|
:min="0"
|
||||||
|
:max="100"
|
||||||
|
:precision="0"
|
||||||
|
placeholder="氢量%"
|
||||||
|
size="small"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'batteryLevel'">
|
||||||
|
<InputNumber
|
||||||
|
v-model:value="record.batteryLevel"
|
||||||
|
:min="0"
|
||||||
|
:max="100"
|
||||||
|
:precision="0"
|
||||||
|
placeholder="电量%"
|
||||||
|
size="small"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<!-- Card 4: 交检清单 -->
|
||||||
|
<Card title="交检清单" size="small">
|
||||||
|
<!-- 新版:使用验车记录组件 -->
|
||||||
|
<template v-if="formData?.inspectionRecordId">
|
||||||
|
<InspectionForm :record-id="formData.inspectionRecordId" :readonly="false" />
|
||||||
|
</template>
|
||||||
|
<!-- 旧版:硬编码检查单(兼容旧数据) -->
|
||||||
|
<template v-else>
|
||||||
|
<Table
|
||||||
|
:columns="inspectionColumns"
|
||||||
|
:data-source="inspectionTableData"
|
||||||
|
:pagination="false"
|
||||||
|
size="small"
|
||||||
|
bordered
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'deliver'">
|
||||||
|
<Switch
|
||||||
|
v-model:checked="inspectionData[record.key]!.checked"
|
||||||
|
checked-children="✓"
|
||||||
|
un-checked-children="×"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'receive'">
|
||||||
|
<Switch
|
||||||
|
checked-children="✓"
|
||||||
|
un-checked-children="×"
|
||||||
|
size="small"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'remark'">
|
||||||
|
<Input
|
||||||
|
v-model:value="inspectionData[record.key]!.remark"
|
||||||
|
placeholder="备注"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
|
</template>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<!-- Card 5: 费用信息 -->
|
||||||
|
<Card title="费用信息" size="small">
|
||||||
|
<Space direction="vertical" style="width: 100%" :size="12">
|
||||||
|
<div
|
||||||
|
v-if="costList.length > 0"
|
||||||
|
class="rounded bg-blue-50 px-3 py-2 text-sm"
|
||||||
|
>
|
||||||
|
费用合计:<span class="font-semibold text-red-500"
|
||||||
|
>¥{{ costTotal.toFixed(2) }}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<Table
|
||||||
|
:columns="costColumns"
|
||||||
|
:data-source="costList"
|
||||||
|
:pagination="false"
|
||||||
|
size="small"
|
||||||
|
bordered
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record, index }">
|
||||||
|
<template v-if="column.key === 'index'">
|
||||||
|
{{ index + 1 }}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'costName'">
|
||||||
|
<Input
|
||||||
|
v-model:value="record.costName"
|
||||||
|
placeholder="请输入费用名称"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'amount'">
|
||||||
|
<InputNumber
|
||||||
|
v-model:value="record.amount"
|
||||||
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
placeholder="金额"
|
||||||
|
size="small"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'costRemark'">
|
||||||
|
<Input
|
||||||
|
v-model:value="record.remark"
|
||||||
|
placeholder="备注"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'costAction'">
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
danger
|
||||||
|
size="small"
|
||||||
|
@click="handleDeleteCost(index)"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
|
<Button type="dashed" block @click="handleAddCost">
|
||||||
|
+ 添加费用项
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
@@ -14,6 +14,8 @@ import {
|
|||||||
deleteReturnOrder,
|
deleteReturnOrder,
|
||||||
getReturnOrderPage,
|
getReturnOrderPage,
|
||||||
settleReturnOrder,
|
settleReturnOrder,
|
||||||
|
submitReturnOrderApproval,
|
||||||
|
withdrawReturnOrderApproval,
|
||||||
} from '#/api/asset/return-order';
|
} from '#/api/asset/return-order';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
@@ -65,6 +67,20 @@ async function handleSettle(row: AssetReturnOrderApi.ReturnOrder) {
|
|||||||
handleRefresh();
|
handleRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleSubmitApproval(row: AssetReturnOrderApi.ReturnOrder) {
|
||||||
|
await confirm('确认提交审批?');
|
||||||
|
await submitReturnOrderApproval(row.id!);
|
||||||
|
message.success('已提交审批');
|
||||||
|
handleRefresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleWithdrawApproval(row: AssetReturnOrderApi.ReturnOrder) {
|
||||||
|
await confirm('确认撤回审批?');
|
||||||
|
await withdrawReturnOrderApproval(row.id!);
|
||||||
|
message.success('已撤回审批');
|
||||||
|
handleRefresh();
|
||||||
|
}
|
||||||
|
|
||||||
/** 根据状态动态生成操作菜单 */
|
/** 根据状态动态生成操作菜单 */
|
||||||
function getRowActions(row: AssetReturnOrderApi.ReturnOrder) {
|
function getRowActions(row: AssetReturnOrderApi.ReturnOrder) {
|
||||||
const actions: any[] = [];
|
const actions: any[] = [];
|
||||||
@@ -99,6 +115,26 @@ function getRowActions(row: AssetReturnOrderApi.ReturnOrder) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 验车完成 → 提交审批
|
||||||
|
if (row.status === 1 && row.approvalStatus !== 1) {
|
||||||
|
actions.push({
|
||||||
|
auth: ['asset:return-order:update'],
|
||||||
|
label: '提交审批',
|
||||||
|
onClick: () => handleSubmitApproval(row),
|
||||||
|
type: 'link',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 审批中 → 撤回
|
||||||
|
if (row.approvalStatus === 1) {
|
||||||
|
actions.push({
|
||||||
|
auth: ['asset:return-order:update'],
|
||||||
|
label: '撤回审批',
|
||||||
|
onClick: () => handleWithdrawApproval(row),
|
||||||
|
type: 'link',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 验车完成 → 结算
|
// 验车完成 → 结算
|
||||||
if (row.status === 1) {
|
if (row.status === 1) {
|
||||||
actions.push({
|
actions.push({
|
||||||
|
|||||||
566
apps/web-antd/src/views/asset/return-order/modules/form.vue
Normal file
566
apps/web-antd/src/views/asset/return-order/modules/form.vue
Normal file
@@ -0,0 +1,566 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { AssetReturnOrderApi } from '#/api/asset/return-order';
|
||||||
|
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import InspectionForm from '../../components/InspectionForm.vue';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Col,
|
||||||
|
Divider,
|
||||||
|
Input,
|
||||||
|
InputNumber,
|
||||||
|
message,
|
||||||
|
Row,
|
||||||
|
Space,
|
||||||
|
Switch,
|
||||||
|
Table,
|
||||||
|
} from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
import {
|
||||||
|
createReturnOrder,
|
||||||
|
getReturnOrder,
|
||||||
|
startVehicleInspection,
|
||||||
|
updateReturnOrder,
|
||||||
|
} from '#/api/asset/return-order';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
|
import { useFormSchema } from '../data';
|
||||||
|
|
||||||
|
const emit = defineEmits(['success']);
|
||||||
|
const formData = ref<AssetReturnOrderApi.ReturnOrder>();
|
||||||
|
const vehicles = ref<AssetReturnOrderApi.ReturnOrderVehicle[]>([]);
|
||||||
|
|
||||||
|
const getTitle = computed(() => {
|
||||||
|
return formData.value?.id
|
||||||
|
? $t('ui.actionTitle.edit', ['还车单'])
|
||||||
|
: $t('ui.actionTitle.create', ['还车单']);
|
||||||
|
});
|
||||||
|
|
||||||
|
const [BasicForm, basicFormApi] = useVbenForm({
|
||||||
|
commonConfig: {
|
||||||
|
componentProps: { class: 'w-full' },
|
||||||
|
formItemClass: 'col-span-2',
|
||||||
|
labelWidth: 150,
|
||||||
|
},
|
||||||
|
layout: 'horizontal',
|
||||||
|
schema: useFormSchema(),
|
||||||
|
showDefaultActions: false,
|
||||||
|
wrapperClass: 'grid-cols-6',
|
||||||
|
});
|
||||||
|
|
||||||
|
// ========== 还车车辆列表 ==========
|
||||||
|
const vehicleColumns = [
|
||||||
|
{ title: '序号', key: 'index', width: 60 },
|
||||||
|
{ title: '车牌号', dataIndex: 'plateNo', key: 'plateNo', width: 120 },
|
||||||
|
{ title: '品牌', dataIndex: 'brand', key: 'brand', width: 100 },
|
||||||
|
{ title: '型号', dataIndex: 'model', key: 'model', width: 100 },
|
||||||
|
{ title: 'VIN码', dataIndex: 'vin', key: 'vin', width: 180 },
|
||||||
|
{ title: '还车里程(km)', key: 'returnMileage', width: 130 },
|
||||||
|
{ title: '还车氢量(%)', key: 'returnHydrogenLevel', width: 120 },
|
||||||
|
{
|
||||||
|
title: '交车氢量(%)',
|
||||||
|
dataIndex: 'deliveryHydrogenLevel',
|
||||||
|
key: 'deliveryHydrogenLevel',
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{ title: '氢量差值', key: 'hydrogenDiff', width: 100 },
|
||||||
|
{ title: '氢费退还(元)', key: 'hydrogenRefundAmount', width: 120 },
|
||||||
|
{ title: '验车', key: 'inspection', width: 120 },
|
||||||
|
{ title: '操作', key: 'action', width: 80, fixed: 'right' as const },
|
||||||
|
];
|
||||||
|
|
||||||
|
function handleAddVehicle() {
|
||||||
|
vehicles.value.push({
|
||||||
|
plateNo: '',
|
||||||
|
brand: '',
|
||||||
|
model: '',
|
||||||
|
vin: '',
|
||||||
|
returnMileage: 0,
|
||||||
|
returnHydrogenLevel: 0,
|
||||||
|
deliveryHydrogenLevel: 0,
|
||||||
|
hydrogenDiff: 0,
|
||||||
|
hydrogenUnitPrice: 0,
|
||||||
|
hydrogenRefundAmount: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDeleteVehicle(index: number) {
|
||||||
|
vehicles.value.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 车辆验车 ==========
|
||||||
|
async function handleStartInspection(vehicle: AssetReturnOrderApi.ReturnOrderVehicle) {
|
||||||
|
if (!vehicle.id) return;
|
||||||
|
await startVehicleInspection(vehicle.id);
|
||||||
|
message.success('验车记录已创建');
|
||||||
|
// Reload the return order to get updated inspectionRecordId
|
||||||
|
if (formData.value?.id) {
|
||||||
|
formData.value = await getReturnOrder(formData.value.id);
|
||||||
|
vehicles.value = formData.value.vehicles || [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 费用核算 ==========
|
||||||
|
const costBreakdown = ref({
|
||||||
|
vehicleDamageFee: 0,
|
||||||
|
toolDamageFee: 0,
|
||||||
|
unpaidMaintenanceFee: 0,
|
||||||
|
unpaidRepairFee: 0,
|
||||||
|
violationFee: 0,
|
||||||
|
otherFee: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 各项费用合计
|
||||||
|
const totalDeductions = computed(() => {
|
||||||
|
const c = costBreakdown.value;
|
||||||
|
return (
|
||||||
|
(c.vehicleDamageFee || 0) +
|
||||||
|
(c.toolDamageFee || 0) +
|
||||||
|
(c.unpaidMaintenanceFee || 0) +
|
||||||
|
(c.unpaidRepairFee || 0) +
|
||||||
|
(c.violationFee || 0) +
|
||||||
|
(c.otherFee || 0)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 氢费退还合计
|
||||||
|
const totalHydrogenRefund = computed(() => {
|
||||||
|
return vehicles.value.reduce(
|
||||||
|
(sum, v) => sum + (v.hydrogenRefundAmount || 0),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 汇总
|
||||||
|
const summary = computed(() => {
|
||||||
|
const depositRefund = formData.value?.depositRefund || 0;
|
||||||
|
const totalRefund = depositRefund + totalHydrogenRefund.value;
|
||||||
|
const totalDeduct = totalDeductions.value;
|
||||||
|
const netAmount = totalRefund - totalDeduct;
|
||||||
|
return {
|
||||||
|
depositRefund,
|
||||||
|
hydrogenRefund: totalHydrogenRefund.value,
|
||||||
|
totalRefund,
|
||||||
|
totalDeductions: totalDeduct,
|
||||||
|
netAmount,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// ========== 交检清单 ==========
|
||||||
|
const inspectionData = ref<
|
||||||
|
Record<string, { deliver: boolean; receive: boolean; remark: string }>
|
||||||
|
>({});
|
||||||
|
|
||||||
|
const INSPECTION_ITEMS = [
|
||||||
|
{
|
||||||
|
category: '车辆附件',
|
||||||
|
items: ['行驶证', '营运证', '通行证', '加氢证', '登记证', '保险卡', '车钥匙'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: '外观检查',
|
||||||
|
items: ['车身完好', '挡风玻璃', '后视镜', '车灯完好', '轮胎正常'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: '车内检查',
|
||||||
|
items: ['仪表盘正常', '空调功能', '安全带', '喇叭功能', '雨刮器'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: '动力系统',
|
||||||
|
items: ['发动机正常', '燃料电池状态', '制动功能', '转向正常'],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function initInspectionData() {
|
||||||
|
const data: Record<
|
||||||
|
string,
|
||||||
|
{ deliver: boolean; receive: boolean; remark: string }
|
||||||
|
> = {};
|
||||||
|
for (const cat of INSPECTION_ITEMS) {
|
||||||
|
for (const item of cat.items) {
|
||||||
|
data[`${cat.category}-${item}`] = {
|
||||||
|
deliver: true,
|
||||||
|
receive: true,
|
||||||
|
remark: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inspectionData.value = data;
|
||||||
|
}
|
||||||
|
initInspectionData();
|
||||||
|
|
||||||
|
const inspectionTableData = computed(() => {
|
||||||
|
const rows: any[] = [];
|
||||||
|
for (const cat of INSPECTION_ITEMS) {
|
||||||
|
for (let i = 0; i < cat.items.length; i++) {
|
||||||
|
rows.push({
|
||||||
|
key: `${cat.category}-${cat.items[i]}`,
|
||||||
|
category: cat.category,
|
||||||
|
item: cat.items[i]!,
|
||||||
|
rowSpan: i === 0 ? cat.items.length : 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rows;
|
||||||
|
});
|
||||||
|
|
||||||
|
const inspectionColumns = [
|
||||||
|
{
|
||||||
|
title: '类别',
|
||||||
|
dataIndex: 'category',
|
||||||
|
key: 'category',
|
||||||
|
width: 120,
|
||||||
|
customCell: (_: any, index: number) => {
|
||||||
|
const row = inspectionTableData.value[index];
|
||||||
|
return { rowSpan: row?.rowSpan ?? 1 };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ title: '检查项目', dataIndex: 'item', key: 'item', width: 160 },
|
||||||
|
{ title: '交车方(交车时)', key: 'deliver', width: 120 },
|
||||||
|
{ title: '还车方(还车时)', key: 'receive', width: 120 },
|
||||||
|
{ title: '备注', key: 'inspRemark', width: 180 },
|
||||||
|
];
|
||||||
|
|
||||||
|
// ========== Modal ==========
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
async onConfirm() {
|
||||||
|
const { valid } = await basicFormApi.validate();
|
||||||
|
if (!valid) return;
|
||||||
|
|
||||||
|
if (vehicles.value.length === 0) {
|
||||||
|
message.warning('请至少添加一辆还车车辆');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
modalApi.lock();
|
||||||
|
const data =
|
||||||
|
(await basicFormApi.getValues()) as AssetReturnOrderApi.ReturnOrder;
|
||||||
|
data.vehicles = vehicles.value;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await (formData.value?.id
|
||||||
|
? updateReturnOrder(data)
|
||||||
|
: createReturnOrder(data));
|
||||||
|
await modalApi.close();
|
||||||
|
emit('success');
|
||||||
|
message.success($t('ui.actionMessage.operationSuccess'));
|
||||||
|
} finally {
|
||||||
|
modalApi.unlock();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async onOpenChange(isOpen: boolean) {
|
||||||
|
if (!isOpen) {
|
||||||
|
formData.value = undefined;
|
||||||
|
vehicles.value = [];
|
||||||
|
costBreakdown.value = {
|
||||||
|
vehicleDamageFee: 0,
|
||||||
|
toolDamageFee: 0,
|
||||||
|
unpaidMaintenanceFee: 0,
|
||||||
|
unpaidRepairFee: 0,
|
||||||
|
violationFee: 0,
|
||||||
|
otherFee: 0,
|
||||||
|
};
|
||||||
|
initInspectionData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = modalApi.getData<AssetReturnOrderApi.ReturnOrder>();
|
||||||
|
if (!data || !data.id) return;
|
||||||
|
|
||||||
|
modalApi.lock();
|
||||||
|
try {
|
||||||
|
formData.value = await getReturnOrder(data.id);
|
||||||
|
await basicFormApi.setValues(formData.value);
|
||||||
|
vehicles.value = formData.value.vehicles || [];
|
||||||
|
} finally {
|
||||||
|
modalApi.unlock();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Modal :title="getTitle" class="w-4/5" style="max-width: 1400px">
|
||||||
|
<div class="space-y-4" style="max-height: 75vh; overflow-y: auto">
|
||||||
|
<!-- Card 1: 还车基本信息 -->
|
||||||
|
<Card title="还车基本信息" size="small">
|
||||||
|
<BasicForm />
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<!-- Card 2: 还车车辆 -->
|
||||||
|
<Card title="还车车辆" size="small">
|
||||||
|
<Space direction="vertical" style="width: 100%" :size="12">
|
||||||
|
<Table
|
||||||
|
:columns="vehicleColumns"
|
||||||
|
:data-source="vehicles"
|
||||||
|
:pagination="false"
|
||||||
|
:scroll="{ x: 1400 }"
|
||||||
|
size="small"
|
||||||
|
bordered
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record, index }">
|
||||||
|
<template v-if="column.key === 'index'">
|
||||||
|
{{ index + 1 }}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'returnMileage'">
|
||||||
|
<InputNumber
|
||||||
|
v-model:value="record.returnMileage"
|
||||||
|
:min="0"
|
||||||
|
:precision="0"
|
||||||
|
placeholder="里程"
|
||||||
|
size="small"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'returnHydrogenLevel'">
|
||||||
|
<InputNumber
|
||||||
|
v-model:value="record.returnHydrogenLevel"
|
||||||
|
:min="0"
|
||||||
|
:max="100"
|
||||||
|
:precision="0"
|
||||||
|
placeholder="氢量%"
|
||||||
|
size="small"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'hydrogenDiff'">
|
||||||
|
<span
|
||||||
|
:class="
|
||||||
|
(record.returnHydrogenLevel || 0) -
|
||||||
|
(record.deliveryHydrogenLevel || 0) <
|
||||||
|
0
|
||||||
|
? 'text-red-500'
|
||||||
|
: 'text-green-600'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
(
|
||||||
|
(record.returnHydrogenLevel || 0) -
|
||||||
|
(record.deliveryHydrogenLevel || 0)
|
||||||
|
).toFixed(0)
|
||||||
|
}}%
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'hydrogenRefundAmount'">
|
||||||
|
<InputNumber
|
||||||
|
v-model:value="record.hydrogenRefundAmount"
|
||||||
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
placeholder="退还金额"
|
||||||
|
size="small"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'inspection'">
|
||||||
|
<Button
|
||||||
|
v-if="!record.inspectionRecordId"
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
@click="handleStartInspection(record)"
|
||||||
|
>
|
||||||
|
开始验车
|
||||||
|
</Button>
|
||||||
|
<span v-else class="text-green-600">已有验车记录</span>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'action'">
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
danger
|
||||||
|
size="small"
|
||||||
|
@click="handleDeleteVehicle(index)"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
|
<Button type="dashed" block @click="handleAddVehicle">
|
||||||
|
+ 添加车辆
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<!-- Card 3: 交检清单(旧版兼容) -->
|
||||||
|
<Card title="交检清单" size="small">
|
||||||
|
<Table
|
||||||
|
:columns="inspectionColumns"
|
||||||
|
:data-source="inspectionTableData"
|
||||||
|
:pagination="false"
|
||||||
|
size="small"
|
||||||
|
bordered
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'deliver'">
|
||||||
|
<Switch
|
||||||
|
v-model:checked="inspectionData[record.key]!.deliver"
|
||||||
|
checked-children="✓"
|
||||||
|
un-checked-children="×"
|
||||||
|
size="small"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'receive'">
|
||||||
|
<Switch
|
||||||
|
v-model:checked="inspectionData[record.key]!.receive"
|
||||||
|
checked-children="✓"
|
||||||
|
un-checked-children="×"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'inspRemark'">
|
||||||
|
<Input
|
||||||
|
v-model:value="inspectionData[record.key]!.remark"
|
||||||
|
placeholder="备注"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<!-- 车辆验车详情 -->
|
||||||
|
<template v-for="vehicle in vehicles" :key="vehicle.id">
|
||||||
|
<Card
|
||||||
|
v-if="vehicle.inspectionRecordId"
|
||||||
|
:title="`${vehicle.plateNo || '车辆'} - 验车记录`"
|
||||||
|
size="small"
|
||||||
|
class="mt-2"
|
||||||
|
>
|
||||||
|
<InspectionForm :record-id="vehicle.inspectionRecordId" :readonly="false" />
|
||||||
|
</Card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Card 4: 费用核算 -->
|
||||||
|
<Card title="费用核算" size="small">
|
||||||
|
<Row :gutter="24">
|
||||||
|
<Col :span="8">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="mb-1 block font-medium">车辆损坏费用</label>
|
||||||
|
<InputNumber
|
||||||
|
v-model:value="costBreakdown.vehicleDamageFee"
|
||||||
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
addon-after="元"
|
||||||
|
placeholder="请输入"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="mb-1 block font-medium">工具损坏费用</label>
|
||||||
|
<InputNumber
|
||||||
|
v-model:value="costBreakdown.toolDamageFee"
|
||||||
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
addon-after="元"
|
||||||
|
placeholder="请输入"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="mb-1 block font-medium">未结保养费</label>
|
||||||
|
<InputNumber
|
||||||
|
v-model:value="costBreakdown.unpaidMaintenanceFee"
|
||||||
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
addon-after="元"
|
||||||
|
placeholder="请输入"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="mb-1 block font-medium">未结维修费</label>
|
||||||
|
<InputNumber
|
||||||
|
v-model:value="costBreakdown.unpaidRepairFee"
|
||||||
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
addon-after="元"
|
||||||
|
placeholder="请输入"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="mb-1 block font-medium">违章罚款</label>
|
||||||
|
<InputNumber
|
||||||
|
v-model:value="costBreakdown.violationFee"
|
||||||
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
addon-after="元"
|
||||||
|
placeholder="请输入"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="mb-1 block font-medium">其他费用</label>
|
||||||
|
<InputNumber
|
||||||
|
v-model:value="costBreakdown.otherFee"
|
||||||
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
addon-after="元"
|
||||||
|
placeholder="请输入"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<!-- 费用汇总 -->
|
||||||
|
<div class="grid grid-cols-5 gap-4 rounded bg-gray-50 p-4">
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="text-xs text-gray-500">退还保证金</div>
|
||||||
|
<div class="mt-1 text-lg font-semibold">
|
||||||
|
¥{{ summary.depositRefund.toFixed(2) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="text-xs text-gray-500">氢费退还</div>
|
||||||
|
<div class="mt-1 text-lg font-semibold">
|
||||||
|
¥{{ summary.hydrogenRefund.toFixed(2) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="text-xs text-gray-500">应退总额</div>
|
||||||
|
<div class="mt-1 text-lg font-semibold text-green-600">
|
||||||
|
¥{{ summary.totalRefund.toFixed(2) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="text-xs text-gray-500">扣款总额</div>
|
||||||
|
<div class="mt-1 text-lg font-semibold text-red-500">
|
||||||
|
¥{{ summary.totalDeductions.toFixed(2) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="text-xs text-gray-500">
|
||||||
|
{{ summary.netAmount >= 0 ? '应退款' : '应缴纳' }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mt-1 text-xl font-bold"
|
||||||
|
:class="
|
||||||
|
summary.netAmount >= 0 ? 'text-green-600' : 'text-red-500'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
¥{{ Math.abs(summary.netAmount).toFixed(2) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
630
apps/web-antd/src/views/asset/vehicle-prepare/modules/form.vue
Normal file
630
apps/web-antd/src/views/asset/vehicle-prepare/modules/form.vue
Normal file
@@ -0,0 +1,630 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { AssetVehiclePrepareApi } from '#/api/asset/vehicle-prepare';
|
||||||
|
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import InspectionForm from '../../components/InspectionForm.vue';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Col,
|
||||||
|
Divider,
|
||||||
|
Drawer,
|
||||||
|
Input,
|
||||||
|
InputNumber,
|
||||||
|
message,
|
||||||
|
Row,
|
||||||
|
Select,
|
||||||
|
Switch,
|
||||||
|
Table,
|
||||||
|
Upload,
|
||||||
|
} from 'ant-design-vue';
|
||||||
|
import { PlusOutlined } from '@vben/icons';
|
||||||
|
|
||||||
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
import {
|
||||||
|
createVehiclePrepare,
|
||||||
|
getVehiclePrepare,
|
||||||
|
updateVehiclePrepare,
|
||||||
|
} from '#/api/asset/vehicle-prepare';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
|
import {
|
||||||
|
CHECK_CATEGORIES,
|
||||||
|
HYDROGEN_UNIT_OPTIONS,
|
||||||
|
PREP_TYPE_OPTIONS,
|
||||||
|
} from '../data';
|
||||||
|
|
||||||
|
const emit = defineEmits(['success']);
|
||||||
|
const formData = ref<AssetVehiclePrepareApi.VehiclePrepare>();
|
||||||
|
const getTitle = computed(() => {
|
||||||
|
return formData.value?.id
|
||||||
|
? $t('ui.actionTitle.edit', ['备车记录'])
|
||||||
|
: $t('ui.actionTitle.create', ['备车记录']);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ========== 车辆基本信息 ==========
|
||||||
|
const vehicleInfo = ref({
|
||||||
|
vehicleId: undefined as number | undefined,
|
||||||
|
plateNo: '',
|
||||||
|
vehicleType: '',
|
||||||
|
brand: '',
|
||||||
|
model: '',
|
||||||
|
vin: '',
|
||||||
|
parkingLot: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
// ========== 整备信息 ==========
|
||||||
|
const prepInfo = ref({
|
||||||
|
preparationType: '日常整备',
|
||||||
|
mileage: undefined as number | undefined,
|
||||||
|
hydrogenRemaining: undefined as number | undefined,
|
||||||
|
hydrogenUnit: '%',
|
||||||
|
batteryRemaining: undefined as number | undefined,
|
||||||
|
commercialInsuranceExpiry: '',
|
||||||
|
compulsoryInsuranceExpiry: '',
|
||||||
|
hasBodyAd: false,
|
||||||
|
hasTailLift: false,
|
||||||
|
trailerPlateNo: '',
|
||||||
|
spareTireDepth: undefined as number | undefined,
|
||||||
|
remark: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
// ========== 照片上传 ==========
|
||||||
|
const bodyAdPhotos = ref<any[]>([]);
|
||||||
|
const enlargedTextPhotos = ref<any[]>([]);
|
||||||
|
const spareTirePhotos = ref<any[]>([]);
|
||||||
|
const defectPhotos = ref<any[]>([]);
|
||||||
|
|
||||||
|
// ========== 备车检查单 ==========
|
||||||
|
const checkDrawerVisible = ref(false);
|
||||||
|
const checkData = ref<
|
||||||
|
Record<string, { checked: boolean; remark: string; depth?: number }>
|
||||||
|
>({});
|
||||||
|
|
||||||
|
function initCheckData() {
|
||||||
|
const data: Record<
|
||||||
|
string,
|
||||||
|
{ checked: boolean; remark: string; depth?: number }
|
||||||
|
> = {};
|
||||||
|
for (const cat of CHECK_CATEGORIES) {
|
||||||
|
for (const item of cat.items) {
|
||||||
|
const key = `${cat.category}-${item}`;
|
||||||
|
data[key] = { checked: true, remark: '', depth: undefined };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checkData.value = data;
|
||||||
|
}
|
||||||
|
initCheckData();
|
||||||
|
|
||||||
|
// 生成检查表数据源(带行合并信息)
|
||||||
|
const checkTableData = computed(() => {
|
||||||
|
const rows: any[] = [];
|
||||||
|
for (const cat of CHECK_CATEGORIES) {
|
||||||
|
for (let i = 0; i < cat.items.length; i++) {
|
||||||
|
rows.push({
|
||||||
|
key: `${cat.category}-${cat.items[i]}`,
|
||||||
|
category: cat.category,
|
||||||
|
item: cat.items[i]!,
|
||||||
|
isInput: cat.type === 'input',
|
||||||
|
rowSpan: i === 0 ? cat.items.length : 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rows;
|
||||||
|
});
|
||||||
|
|
||||||
|
const checkColumns = [
|
||||||
|
{
|
||||||
|
title: '类别',
|
||||||
|
dataIndex: 'category',
|
||||||
|
key: 'category',
|
||||||
|
width: 120,
|
||||||
|
customCell: (_: any, index: number) => {
|
||||||
|
const row = checkTableData.value[index];
|
||||||
|
return { rowSpan: row?.rowSpan ?? 1 };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ title: '检查项目', dataIndex: 'item', key: 'item', width: 160 },
|
||||||
|
{ title: '检查情况', key: 'check', width: 160 },
|
||||||
|
{ title: '备注', key: 'remark', width: 200 },
|
||||||
|
];
|
||||||
|
|
||||||
|
// 已完成的检查项统计
|
||||||
|
const checkSummary = computed(() => {
|
||||||
|
const total = Object.keys(checkData.value).length;
|
||||||
|
const normal = Object.values(checkData.value).filter(
|
||||||
|
(v) => v.checked,
|
||||||
|
).length;
|
||||||
|
return { total, normal, abnormal: total - normal };
|
||||||
|
});
|
||||||
|
|
||||||
|
// ========== 重置表单 ==========
|
||||||
|
function resetForm() {
|
||||||
|
vehicleInfo.value = {
|
||||||
|
vehicleId: undefined,
|
||||||
|
plateNo: '',
|
||||||
|
vehicleType: '',
|
||||||
|
brand: '',
|
||||||
|
model: '',
|
||||||
|
vin: '',
|
||||||
|
parkingLot: '',
|
||||||
|
};
|
||||||
|
prepInfo.value = {
|
||||||
|
preparationType: '日常整备',
|
||||||
|
mileage: undefined,
|
||||||
|
hydrogenRemaining: undefined,
|
||||||
|
hydrogenUnit: '%',
|
||||||
|
batteryRemaining: undefined,
|
||||||
|
commercialInsuranceExpiry: '',
|
||||||
|
compulsoryInsuranceExpiry: '',
|
||||||
|
hasBodyAd: false,
|
||||||
|
hasTailLift: false,
|
||||||
|
trailerPlateNo: '',
|
||||||
|
spareTireDepth: undefined,
|
||||||
|
remark: '',
|
||||||
|
};
|
||||||
|
bodyAdPhotos.value = [];
|
||||||
|
enlargedTextPhotos.value = [];
|
||||||
|
spareTirePhotos.value = [];
|
||||||
|
defectPhotos.value = [];
|
||||||
|
initCheckData();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Modal ==========
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
async onConfirm() {
|
||||||
|
// 基础校验
|
||||||
|
if (!vehicleInfo.value.vehicleId) {
|
||||||
|
message.warning('请选择车牌号');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
modalApi.lock();
|
||||||
|
const data = {
|
||||||
|
...vehicleInfo.value,
|
||||||
|
...prepInfo.value,
|
||||||
|
checkList: JSON.stringify(checkData.value),
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
if (formData.value?.id) {
|
||||||
|
data.id = formData.value.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await (formData.value?.id
|
||||||
|
? updateVehiclePrepare(data)
|
||||||
|
: createVehiclePrepare(data));
|
||||||
|
await modalApi.close();
|
||||||
|
emit('success');
|
||||||
|
message.success($t('ui.actionMessage.operationSuccess'));
|
||||||
|
} finally {
|
||||||
|
modalApi.unlock();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async onOpenChange(isOpen: boolean) {
|
||||||
|
if (!isOpen) {
|
||||||
|
formData.value = undefined;
|
||||||
|
resetForm();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = modalApi.getData<AssetVehiclePrepareApi.VehiclePrepare>();
|
||||||
|
if (!data || !data.id) return;
|
||||||
|
|
||||||
|
modalApi.lock();
|
||||||
|
try {
|
||||||
|
formData.value = await getVehiclePrepare(data.id);
|
||||||
|
// 回填表单数据
|
||||||
|
vehicleInfo.value = {
|
||||||
|
vehicleId: formData.value.vehicleId,
|
||||||
|
plateNo: formData.value.plateNo || '',
|
||||||
|
vehicleType: formData.value.vehicleType || '',
|
||||||
|
brand: formData.value.brand || '',
|
||||||
|
model: formData.value.model || '',
|
||||||
|
vin: formData.value.vin || '',
|
||||||
|
parkingLot: formData.value.parkingLot || '',
|
||||||
|
};
|
||||||
|
prepInfo.value = {
|
||||||
|
...prepInfo.value,
|
||||||
|
hasBodyAd: formData.value.hasBodyAd || false,
|
||||||
|
hasTailLift: formData.value.hasTailLift || false,
|
||||||
|
spareTireDepth: formData.value.spareTireDepth,
|
||||||
|
trailerPlateNo: formData.value.trailerPlateNo || '',
|
||||||
|
};
|
||||||
|
if (formData.value.checkList) {
|
||||||
|
try {
|
||||||
|
checkData.value = JSON.parse(formData.value.checkList);
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
modalApi.unlock();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const isView = computed(() => {
|
||||||
|
const mode = modalApi.getData<any>()?._mode;
|
||||||
|
return !!mode && mode === 'view';
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Modal :title="getTitle" class="w-4/5" style="max-width: 1400px">
|
||||||
|
<div class="space-y-4" style="max-height: 75vh; overflow-y: auto">
|
||||||
|
<!-- Card 1: 车辆基本信息 -->
|
||||||
|
<Card title="车辆基本信息" size="small">
|
||||||
|
<Row :gutter="24">
|
||||||
|
<Col :span="8">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="mb-1 block font-medium">
|
||||||
|
<span class="text-red-500">*</span> 车牌号
|
||||||
|
</label>
|
||||||
|
<Select
|
||||||
|
v-model:value="vehicleInfo.vehicleId"
|
||||||
|
placeholder="请选择或输入车牌号"
|
||||||
|
show-search
|
||||||
|
:filter-option="
|
||||||
|
(input: string, option: any) =>
|
||||||
|
(option?.label ?? '')
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(input.toLowerCase())
|
||||||
|
"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<!-- 实际应从API获取 -->
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="mb-1 block font-medium">车辆类型</label>
|
||||||
|
<Input
|
||||||
|
v-model:value="vehicleInfo.vehicleType"
|
||||||
|
placeholder="选择车牌号后自动填充"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="mb-1 block font-medium">品牌</label>
|
||||||
|
<Input
|
||||||
|
v-model:value="vehicleInfo.brand"
|
||||||
|
placeholder="选择车牌号后自动填充"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="mb-1 block font-medium">型号</label>
|
||||||
|
<Input
|
||||||
|
v-model:value="vehicleInfo.model"
|
||||||
|
placeholder="选择车牌号后自动填充"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="mb-1 block font-medium">车辆识别代码</label>
|
||||||
|
<Input
|
||||||
|
v-model:value="vehicleInfo.vin"
|
||||||
|
placeholder="选择车牌号后自动填充"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="mb-1 block font-medium">停车场</label>
|
||||||
|
<Select
|
||||||
|
v-model:value="vehicleInfo.parkingLot"
|
||||||
|
placeholder="请选择停车场"
|
||||||
|
style="width: 100%"
|
||||||
|
allow-clear
|
||||||
|
>
|
||||||
|
<!-- 从停车场API获取 -->
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<!-- Card 2: 整备信息 -->
|
||||||
|
<Card title="整备信息" size="small">
|
||||||
|
<Row :gutter="24">
|
||||||
|
<Col :span="8">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="mb-1 block font-medium">整备类型</label>
|
||||||
|
<Select
|
||||||
|
v-model:value="prepInfo.preparationType"
|
||||||
|
:options="PREP_TYPE_OPTIONS"
|
||||||
|
placeholder="请选择整备类型"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="mb-1 block font-medium">仪表盘里程</label>
|
||||||
|
<InputNumber
|
||||||
|
v-model:value="prepInfo.mileage"
|
||||||
|
:min="0"
|
||||||
|
:precision="0"
|
||||||
|
addon-after="km"
|
||||||
|
placeholder="请输入里程"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="mb-1 block font-medium">剩余氢量</label>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<InputNumber
|
||||||
|
v-model:value="prepInfo.hydrogenRemaining"
|
||||||
|
:min="0"
|
||||||
|
:precision="1"
|
||||||
|
placeholder="请输入"
|
||||||
|
class="flex-1"
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
v-model:value="prepInfo.hydrogenUnit"
|
||||||
|
:options="HYDROGEN_UNIT_OPTIONS"
|
||||||
|
style="width: 80px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="mb-1 block font-medium">剩余电量</label>
|
||||||
|
<InputNumber
|
||||||
|
v-model:value="prepInfo.batteryRemaining"
|
||||||
|
:min="0"
|
||||||
|
:max="100"
|
||||||
|
:precision="0"
|
||||||
|
addon-after="%"
|
||||||
|
placeholder="请输入电量百分比"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="mb-1 block font-medium">商业险到期时间</label>
|
||||||
|
<Input
|
||||||
|
v-model:value="prepInfo.commercialInsuranceExpiry"
|
||||||
|
placeholder="自动获取"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="mb-1 block font-medium">交强险到期时间</label>
|
||||||
|
<Input
|
||||||
|
v-model:value="prepInfo.compulsoryInsuranceExpiry"
|
||||||
|
placeholder="自动获取"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<!-- 设备开关 -->
|
||||||
|
<Row :gutter="24">
|
||||||
|
<Col :span="6">
|
||||||
|
<div class="mb-4 flex items-center gap-3">
|
||||||
|
<label class="font-medium">车身广告及放大字</label>
|
||||||
|
<Switch v-model:checked="prepInfo.hasBodyAd" />
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col :span="6">
|
||||||
|
<div class="mb-4 flex items-center gap-3">
|
||||||
|
<label class="font-medium">尾板</label>
|
||||||
|
<Switch v-model:checked="prepInfo.hasTailLift" />
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col :span="6">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="mb-1 block font-medium">备胎胎纹深度</label>
|
||||||
|
<InputNumber
|
||||||
|
v-model:value="prepInfo.spareTireDepth"
|
||||||
|
:min="0"
|
||||||
|
:precision="1"
|
||||||
|
addon-after="mm"
|
||||||
|
placeholder="请输入"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col :span="6">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="mb-1 block font-medium">挂车牌号</label>
|
||||||
|
<Input
|
||||||
|
v-model:value="prepInfo.trailerPlateNo"
|
||||||
|
placeholder="请输入挂车牌号"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<!-- Card 3: 照片信息 -->
|
||||||
|
<Card title="照片信息" size="small">
|
||||||
|
<Row :gutter="24">
|
||||||
|
<Col :span="6" v-if="prepInfo.hasBodyAd">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="mb-2 block font-medium">广告照片(最多1张)</label>
|
||||||
|
<Upload
|
||||||
|
:file-list="bodyAdPhotos"
|
||||||
|
list-type="picture-card"
|
||||||
|
:max-count="1"
|
||||||
|
:before-upload="() => false"
|
||||||
|
@change="({ fileList }: any) => (bodyAdPhotos = fileList)"
|
||||||
|
>
|
||||||
|
<div v-if="bodyAdPhotos.length < 1">
|
||||||
|
<PlusOutlined />
|
||||||
|
<div class="mt-2 text-xs">上传</div>
|
||||||
|
</div>
|
||||||
|
</Upload>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col :span="6" v-if="prepInfo.hasBodyAd">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="mb-2 block font-medium"
|
||||||
|
>放大字照片(最多1张)</label
|
||||||
|
>
|
||||||
|
<Upload
|
||||||
|
:file-list="enlargedTextPhotos"
|
||||||
|
list-type="picture-card"
|
||||||
|
:max-count="1"
|
||||||
|
:before-upload="() => false"
|
||||||
|
@change="
|
||||||
|
({ fileList }: any) => (enlargedTextPhotos = fileList)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div v-if="enlargedTextPhotos.length < 1">
|
||||||
|
<PlusOutlined />
|
||||||
|
<div class="mt-2 text-xs">上传</div>
|
||||||
|
</div>
|
||||||
|
</Upload>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col :span="6">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="mb-2 block font-medium"
|
||||||
|
>备胎照片(最多1张)</label
|
||||||
|
>
|
||||||
|
<Upload
|
||||||
|
:file-list="spareTirePhotos"
|
||||||
|
list-type="picture-card"
|
||||||
|
:max-count="1"
|
||||||
|
:before-upload="() => false"
|
||||||
|
@change="({ fileList }: any) => (spareTirePhotos = fileList)"
|
||||||
|
>
|
||||||
|
<div v-if="spareTirePhotos.length < 1">
|
||||||
|
<PlusOutlined />
|
||||||
|
<div class="mt-2 text-xs">上传</div>
|
||||||
|
</div>
|
||||||
|
</Upload>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col :span="6">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="mb-2 block font-medium"
|
||||||
|
>瑕疵照片(最多4张)</label
|
||||||
|
>
|
||||||
|
<Upload
|
||||||
|
:file-list="defectPhotos"
|
||||||
|
list-type="picture-card"
|
||||||
|
:max-count="4"
|
||||||
|
:before-upload="() => false"
|
||||||
|
@change="({ fileList }: any) => (defectPhotos = fileList)"
|
||||||
|
>
|
||||||
|
<div v-if="defectPhotos.length < 4">
|
||||||
|
<PlusOutlined />
|
||||||
|
<div class="mt-2 text-xs">上传</div>
|
||||||
|
</div>
|
||||||
|
</Upload>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<!-- Card 4: 车辆检查 -->
|
||||||
|
<Card title="车辆检查" size="small">
|
||||||
|
<!-- 新版:使用验车记录组件 -->
|
||||||
|
<template v-if="formData?.inspectionRecordId">
|
||||||
|
<InspectionForm :record-id="formData.inspectionRecordId" :readonly="isView" />
|
||||||
|
</template>
|
||||||
|
<!-- 旧版:硬编码检查单(兼容旧数据) -->
|
||||||
|
<template v-else>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<Button type="primary" @click="checkDrawerVisible = true">
|
||||||
|
打开备车检查单
|
||||||
|
</Button>
|
||||||
|
<span class="ml-4 text-gray-500">
|
||||||
|
共 {{ checkSummary.total }} 项,正常
|
||||||
|
<span class="text-green-600">{{ checkSummary.normal }}</span>
|
||||||
|
项,异常
|
||||||
|
<span class="text-red-500">{{ checkSummary.abnormal }}</span> 项
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<!-- Card 5: 备注 -->
|
||||||
|
<Card title="备注" size="small">
|
||||||
|
<Input.TextArea
|
||||||
|
v-model:value="prepInfo.remark"
|
||||||
|
placeholder="请输入备注信息"
|
||||||
|
:rows="3"
|
||||||
|
:maxlength="500"
|
||||||
|
show-count
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 检查单抽屉 -->
|
||||||
|
<Drawer
|
||||||
|
v-model:open="checkDrawerVisible"
|
||||||
|
title="备车检查单"
|
||||||
|
:width="780"
|
||||||
|
placement="right"
|
||||||
|
>
|
||||||
|
<template #extra>
|
||||||
|
<span class="text-gray-500">
|
||||||
|
正常 {{ checkSummary.normal }}/{{ checkSummary.total }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<Table
|
||||||
|
:columns="checkColumns"
|
||||||
|
:data-source="checkTableData"
|
||||||
|
:pagination="false"
|
||||||
|
size="small"
|
||||||
|
bordered
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'check'">
|
||||||
|
<template v-if="record.isInput">
|
||||||
|
<InputNumber
|
||||||
|
v-model:value="checkData[record.key]!.depth"
|
||||||
|
placeholder="胎纹深度"
|
||||||
|
addon-after="mm"
|
||||||
|
:min="0"
|
||||||
|
:precision="1"
|
||||||
|
size="small"
|
||||||
|
style="width: 130px"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<Switch
|
||||||
|
v-model:checked="checkData[record.key]!.checked"
|
||||||
|
checked-children="正常"
|
||||||
|
un-checked-children="异常"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'remark'">
|
||||||
|
<Input
|
||||||
|
v-model:value="checkData[record.key]!.remark"
|
||||||
|
placeholder="备注"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
|
</Drawer>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
247
apps/web-antd/src/views/asset/vehicle-replacement/data.ts
Normal file
247
apps/web-antd/src/views/asset/vehicle-replacement/data.ts
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
|
|
||||||
|
import { z } from '#/adapter/form';
|
||||||
|
|
||||||
|
export const REPLACEMENT_TYPE_OPTIONS = [
|
||||||
|
{ label: '临时替换', value: 1 },
|
||||||
|
{ label: '永久替换', value: 2 },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const REPLACEMENT_STATUS_OPTIONS = [
|
||||||
|
{ label: '草稿', value: 0 },
|
||||||
|
{ label: '审批中', value: 1 },
|
||||||
|
{ label: '已通过', value: 2 },
|
||||||
|
{ label: '执行中', value: 3 },
|
||||||
|
{ label: '已完成', value: 4 },
|
||||||
|
{ label: '已驳回', value: 5 },
|
||||||
|
{ label: '已撤回', value: 6 },
|
||||||
|
];
|
||||||
|
|
||||||
|
/** 搜索表单 */
|
||||||
|
export function useGridFormSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
fieldName: 'replacementCode',
|
||||||
|
label: '替换单号',
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入替换单号',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'replacementType',
|
||||||
|
label: '替换类型',
|
||||||
|
component: 'Select',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请选择替换类型',
|
||||||
|
options: REPLACEMENT_TYPE_OPTIONS,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'customerName',
|
||||||
|
label: '客户名称',
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入客户名称',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'status',
|
||||||
|
label: '状态',
|
||||||
|
component: 'Select',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请选择状态',
|
||||||
|
options: REPLACEMENT_STATUS_OPTIONS,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 表格列配置 */
|
||||||
|
export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||||
|
return [
|
||||||
|
{ type: 'checkbox', width: 60, fixed: 'left' },
|
||||||
|
{
|
||||||
|
field: 'replacementCode',
|
||||||
|
title: '替换单号',
|
||||||
|
minWidth: 160,
|
||||||
|
fixed: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'replacementType',
|
||||||
|
title: '替换类型',
|
||||||
|
minWidth: 100,
|
||||||
|
formatter({ cellValue }: { cellValue: number }) {
|
||||||
|
const option = REPLACEMENT_TYPE_OPTIONS.find(
|
||||||
|
(item) => item.value === cellValue,
|
||||||
|
);
|
||||||
|
return option?.label ?? '';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'contractCode',
|
||||||
|
title: '合同编号',
|
||||||
|
minWidth: 160,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'customerName',
|
||||||
|
title: '客户名称',
|
||||||
|
minWidth: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'originalPlateNo',
|
||||||
|
title: '原车牌号',
|
||||||
|
minWidth: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'newPlateNo',
|
||||||
|
title: '新车牌号',
|
||||||
|
minWidth: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'status',
|
||||||
|
title: '状态',
|
||||||
|
minWidth: 100,
|
||||||
|
formatter({ cellValue }: { cellValue: number }) {
|
||||||
|
const option = REPLACEMENT_STATUS_OPTIONS.find(
|
||||||
|
(item) => item.value === cellValue,
|
||||||
|
);
|
||||||
|
return option?.label ?? '';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'expectedDate',
|
||||||
|
title: '预计替换日期',
|
||||||
|
minWidth: 180,
|
||||||
|
formatter: 'formatDateTime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'createTime',
|
||||||
|
title: '创建时间',
|
||||||
|
minWidth: 180,
|
||||||
|
formatter: 'formatDateTime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
width: 280,
|
||||||
|
fixed: 'right',
|
||||||
|
slots: { default: 'actions' },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 新增/修改的表单 */
|
||||||
|
export function useFormSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'id',
|
||||||
|
dependencies: {
|
||||||
|
triggerFields: [''],
|
||||||
|
show: () => false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'replacementType',
|
||||||
|
label: '替换类型',
|
||||||
|
component: 'Select',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请选择替换类型',
|
||||||
|
options: REPLACEMENT_TYPE_OPTIONS,
|
||||||
|
},
|
||||||
|
rules: z.number({ message: '请选择替换类型' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'contractId',
|
||||||
|
label: '关联合同',
|
||||||
|
component: 'ApiSelect',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请选择关联合同',
|
||||||
|
api: () =>
|
||||||
|
import('#/api/asset/contract').then((m) =>
|
||||||
|
m.getSimpleContractList(),
|
||||||
|
),
|
||||||
|
labelField: 'contractCode',
|
||||||
|
valueField: 'id',
|
||||||
|
showSearch: true,
|
||||||
|
filterOption: (input: string, option: any) =>
|
||||||
|
(option?.label ?? '').toLowerCase().includes(input.toLowerCase()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'originalVehicleId',
|
||||||
|
label: '原车辆ID',
|
||||||
|
component: 'InputNumber',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入原车辆ID',
|
||||||
|
class: 'w-full',
|
||||||
|
},
|
||||||
|
rules: z.number({ message: '请输入原车辆ID' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'newVehicleId',
|
||||||
|
label: '新车辆ID',
|
||||||
|
component: 'InputNumber',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入新车辆ID',
|
||||||
|
class: 'w-full',
|
||||||
|
},
|
||||||
|
rules: z.number({ message: '请输入新车辆ID' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'deliveryOrderId',
|
||||||
|
label: '交车单ID',
|
||||||
|
component: 'InputNumber',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入交车单ID',
|
||||||
|
class: 'w-full',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'replacementReason',
|
||||||
|
label: '替换原因',
|
||||||
|
component: 'Textarea',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入替换原因',
|
||||||
|
rows: 3,
|
||||||
|
},
|
||||||
|
rules: z.string().min(1, { message: '请输入替换原因' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'expectedDate',
|
||||||
|
label: '预计替换日期',
|
||||||
|
component: 'DatePicker',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请选择预计替换日期',
|
||||||
|
class: 'w-full',
|
||||||
|
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'returnDate',
|
||||||
|
label: '预计归还日期',
|
||||||
|
component: 'DatePicker',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请选择预计归还日期',
|
||||||
|
class: 'w-full',
|
||||||
|
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||||
|
},
|
||||||
|
dependencies: {
|
||||||
|
triggerFields: ['replacementType'],
|
||||||
|
show(values) {
|
||||||
|
return values.replacementType === 1;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'remark',
|
||||||
|
label: '备注',
|
||||||
|
component: 'Textarea',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入备注',
|
||||||
|
rows: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
233
apps/web-antd/src/views/asset/vehicle-replacement/index.vue
Normal file
233
apps/web-antd/src/views/asset/vehicle-replacement/index.vue
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
|
import type { AssetVehicleReplacementApi } from '#/api/asset/vehicle-replacement';
|
||||||
|
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import { confirm, Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
import { isEmpty } from '@vben/utils';
|
||||||
|
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import {
|
||||||
|
confirmVehicleReplacementReturn,
|
||||||
|
deleteVehicleReplacement,
|
||||||
|
getVehicleReplacementPage,
|
||||||
|
submitVehicleReplacementApproval,
|
||||||
|
withdrawVehicleReplacementApproval,
|
||||||
|
} from '#/api/asset/vehicle-replacement';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
|
import { useGridColumns, useGridFormSchema } from './data';
|
||||||
|
import Form from './modules/form.vue';
|
||||||
|
|
||||||
|
const [FormModal, formModalApi] = useVbenModal({
|
||||||
|
connectedComponent: Form,
|
||||||
|
destroyOnClose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 刷新表格 */
|
||||||
|
function handleRefresh() {
|
||||||
|
gridApi.query();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 新增替换车 */
|
||||||
|
function handleCreate() {
|
||||||
|
formModalApi.setData(null).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查看替换车 */
|
||||||
|
function handleView(row: AssetVehicleReplacementApi.VehicleReplacement) {
|
||||||
|
formModalApi.setData({ ...row, _mode: 'view' }).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 编辑替换车 */
|
||||||
|
function handleEdit(row: AssetVehicleReplacementApi.VehicleReplacement) {
|
||||||
|
formModalApi.setData({ ...row, _mode: 'edit' }).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删除替换车 */
|
||||||
|
async function handleDelete(row: AssetVehicleReplacementApi.VehicleReplacement) {
|
||||||
|
const hideLoading = message.loading({
|
||||||
|
content: $t('ui.actionMessage.deleting', [row.replacementCode]),
|
||||||
|
duration: 0,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await deleteVehicleReplacement(row.id!);
|
||||||
|
message.success($t('ui.actionMessage.deleteSuccess', [row.replacementCode]));
|
||||||
|
handleRefresh();
|
||||||
|
} finally {
|
||||||
|
hideLoading();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 提交审批 */
|
||||||
|
async function handleSubmitApproval(row: AssetVehicleReplacementApi.VehicleReplacement) {
|
||||||
|
const hideLoading = message.loading({
|
||||||
|
content: '提交审批中...',
|
||||||
|
duration: 0,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await submitVehicleReplacementApproval(row.id!);
|
||||||
|
message.success('提交审批成功');
|
||||||
|
handleRefresh();
|
||||||
|
} finally {
|
||||||
|
hideLoading();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 撤回审批 */
|
||||||
|
async function handleWithdrawApproval(row: AssetVehicleReplacementApi.VehicleReplacement) {
|
||||||
|
await confirm('确认撤回审批吗?');
|
||||||
|
const hideLoading = message.loading({
|
||||||
|
content: '撤回审批中...',
|
||||||
|
duration: 0,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await withdrawVehicleReplacementApproval(row.id!);
|
||||||
|
message.success('撤回审批成功');
|
||||||
|
handleRefresh();
|
||||||
|
} finally {
|
||||||
|
hideLoading();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 确认换回 */
|
||||||
|
async function handleConfirmReturn(row: AssetVehicleReplacementApi.VehicleReplacement) {
|
||||||
|
await confirm('确认换回原车吗?');
|
||||||
|
const hideLoading = message.loading({
|
||||||
|
content: '确认换回中...',
|
||||||
|
duration: 0,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await confirmVehicleReplacementReturn(row.id!);
|
||||||
|
message.success('确认换回成功');
|
||||||
|
handleRefresh();
|
||||||
|
} finally {
|
||||||
|
hideLoading();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkedIds = ref<number[]>([]);
|
||||||
|
function handleRowCheckboxChange({
|
||||||
|
records,
|
||||||
|
}: {
|
||||||
|
records: AssetVehicleReplacementApi.VehicleReplacement[];
|
||||||
|
}) {
|
||||||
|
checkedIds.value = records.map((item) => item.id!);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
|
formOptions: {
|
||||||
|
schema: useGridFormSchema(),
|
||||||
|
},
|
||||||
|
gridOptions: {
|
||||||
|
columns: useGridColumns(),
|
||||||
|
height: 'auto',
|
||||||
|
keepSource: true,
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async ({ page }, formValues) => {
|
||||||
|
return await getVehicleReplacementPage({
|
||||||
|
pageNo: page.currentPage,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
...formValues,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rowConfig: {
|
||||||
|
keyField: 'id',
|
||||||
|
isHover: true,
|
||||||
|
},
|
||||||
|
toolbarConfig: {
|
||||||
|
refresh: true,
|
||||||
|
search: true,
|
||||||
|
},
|
||||||
|
} as VxeTableGridOptions<AssetVehicleReplacementApi.VehicleReplacement>,
|
||||||
|
gridEvents: {
|
||||||
|
checkboxAll: handleRowCheckboxChange,
|
||||||
|
checkboxChange: handleRowCheckboxChange,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height>
|
||||||
|
<FormModal @success="handleRefresh" />
|
||||||
|
<Grid table-title="替换车列表">
|
||||||
|
<template #toolbar-tools>
|
||||||
|
<TableAction
|
||||||
|
:actions="[
|
||||||
|
{
|
||||||
|
label: $t('ui.actionTitle.create', ['替换车']),
|
||||||
|
type: 'primary',
|
||||||
|
icon: ACTION_ICON.ADD,
|
||||||
|
auth: ['asset:vehicle-replacement:create'],
|
||||||
|
onClick: handleCreate,
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<TableAction
|
||||||
|
:actions="[
|
||||||
|
{
|
||||||
|
label: $t('common.view'),
|
||||||
|
type: 'link',
|
||||||
|
icon: ACTION_ICON.VIEW,
|
||||||
|
onClick: handleView.bind(null, row),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: $t('common.edit'),
|
||||||
|
type: 'link',
|
||||||
|
icon: ACTION_ICON.EDIT,
|
||||||
|
auth: ['asset:vehicle-replacement:update'],
|
||||||
|
onClick: handleEdit.bind(null, row),
|
||||||
|
ifShow: row.status === 0 || row.status === 5 || row.status === 6,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: $t('common.delete'),
|
||||||
|
type: 'link',
|
||||||
|
danger: true,
|
||||||
|
icon: ACTION_ICON.DELETE,
|
||||||
|
auth: ['asset:vehicle-replacement:delete'],
|
||||||
|
ifShow: row.status === 0,
|
||||||
|
popConfirm: {
|
||||||
|
title: $t('ui.actionMessage.deleteConfirm', [
|
||||||
|
row.replacementCode,
|
||||||
|
]),
|
||||||
|
confirm: handleDelete.bind(null, row),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '提交审批',
|
||||||
|
type: 'link',
|
||||||
|
icon: ACTION_ICON.CHECK,
|
||||||
|
auth: ['asset:vehicle-replacement:update'],
|
||||||
|
ifShow: row.status === 0 || row.status === 5 || row.status === 6,
|
||||||
|
onClick: handleSubmitApproval.bind(null, row),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '撤回审批',
|
||||||
|
type: 'link',
|
||||||
|
icon: ACTION_ICON.CLOSE,
|
||||||
|
auth: ['asset:vehicle-replacement:update'],
|
||||||
|
ifShow: row.status === 1,
|
||||||
|
onClick: handleWithdrawApproval.bind(null, row),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '确认换回',
|
||||||
|
type: 'link',
|
||||||
|
icon: ACTION_ICON.CHECK,
|
||||||
|
auth: ['asset:vehicle-replacement:update'],
|
||||||
|
ifShow: row.status === 3 && row.replacementType === 1,
|
||||||
|
onClick: handleConfirmReturn.bind(null, row),
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</Grid>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { AssetVehicleReplacementApi } from '#/api/asset/vehicle-replacement';
|
||||||
|
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Card, message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
import {
|
||||||
|
createVehicleReplacement,
|
||||||
|
getVehicleReplacement,
|
||||||
|
updateVehicleReplacement,
|
||||||
|
} from '#/api/asset/vehicle-replacement';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
|
import { useFormSchema } from '../data';
|
||||||
|
|
||||||
|
type FormMode = 'create' | 'edit' | 'view';
|
||||||
|
|
||||||
|
const emit = defineEmits(['success']);
|
||||||
|
const formData = ref<AssetVehicleReplacementApi.VehicleReplacement>();
|
||||||
|
const mode = ref<FormMode>('create');
|
||||||
|
|
||||||
|
const isReadOnly = computed(() => mode.value === 'view');
|
||||||
|
|
||||||
|
const getTitle = computed(() => {
|
||||||
|
if (mode.value === 'view') {
|
||||||
|
return '查看替换车';
|
||||||
|
}
|
||||||
|
return formData.value?.id
|
||||||
|
? $t('ui.actionTitle.edit', ['替换车'])
|
||||||
|
: $t('ui.actionTitle.create', ['替换车']);
|
||||||
|
});
|
||||||
|
|
||||||
|
const [Form, formApi] = useVbenForm({
|
||||||
|
commonConfig: {
|
||||||
|
componentProps: {
|
||||||
|
class: 'w-full',
|
||||||
|
},
|
||||||
|
formItemClass: 'col-span-2',
|
||||||
|
labelWidth: 150,
|
||||||
|
},
|
||||||
|
layout: 'horizontal',
|
||||||
|
schema: useFormSchema(),
|
||||||
|
showDefaultActions: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
async onConfirm() {
|
||||||
|
if (isReadOnly.value) {
|
||||||
|
await modalApi.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { valid } = await formApi.validate();
|
||||||
|
if (!valid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
modalApi.lock();
|
||||||
|
|
||||||
|
const data =
|
||||||
|
(await formApi.getValues()) as AssetVehicleReplacementApi.VehicleReplacement;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await (formData.value?.id
|
||||||
|
? updateVehicleReplacement(data)
|
||||||
|
: createVehicleReplacement(data));
|
||||||
|
await modalApi.close();
|
||||||
|
emit('success');
|
||||||
|
message.success($t('ui.actionMessage.operationSuccess'));
|
||||||
|
} finally {
|
||||||
|
modalApi.unlock();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async onOpenChange(isOpen: boolean) {
|
||||||
|
if (!isOpen) {
|
||||||
|
formData.value = undefined;
|
||||||
|
mode.value = 'create';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = modalApi.getData<
|
||||||
|
AssetVehicleReplacementApi.VehicleReplacement & { _mode?: FormMode }
|
||||||
|
>();
|
||||||
|
|
||||||
|
// Determine mode
|
||||||
|
if (data?._mode) {
|
||||||
|
mode.value = data._mode;
|
||||||
|
} else if (data?.id) {
|
||||||
|
mode.value = 'edit';
|
||||||
|
} else {
|
||||||
|
mode.value = 'create';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set readonly state on form
|
||||||
|
await formApi.setState({ commonConfig: { componentProps: { disabled: isReadOnly.value } } });
|
||||||
|
|
||||||
|
if (data?.id) {
|
||||||
|
// Edit or view mode: load data from API
|
||||||
|
modalApi.lock();
|
||||||
|
try {
|
||||||
|
formData.value = await getVehicleReplacement(data.id);
|
||||||
|
await formApi.setValues(formData.value);
|
||||||
|
} finally {
|
||||||
|
modalApi.unlock();
|
||||||
|
}
|
||||||
|
} else if (data) {
|
||||||
|
// Create mode: check for pre-fill data (contractId, vehicleId, deliveryOrderId)
|
||||||
|
const prefillData: Record<string, any> = {};
|
||||||
|
if (data.contractId) {
|
||||||
|
prefillData.contractId = data.contractId;
|
||||||
|
}
|
||||||
|
if (data.originalVehicleId) {
|
||||||
|
prefillData.originalVehicleId = data.originalVehicleId;
|
||||||
|
}
|
||||||
|
if (data.deliveryOrderId) {
|
||||||
|
prefillData.deliveryOrderId = data.deliveryOrderId;
|
||||||
|
}
|
||||||
|
if (Object.keys(prefillData).length > 0) {
|
||||||
|
await formApi.setValues(prefillData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Modal :title="getTitle" class="w-3/4">
|
||||||
|
<div class="mx-4">
|
||||||
|
<Card :bordered="false">
|
||||||
|
<Form />
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
Reference in New Issue
Block a user