feat(frontend): add inspection/replacement APIs and shared InspectionForm component
- Create inspection.ts with template CRUD and record APIs - Create vehicle-replacement.ts with full CRUD + BPM approval APIs - Update return-order.ts with new fields and 5 new endpoints - Create shared InspectionForm.vue component with category grouping, multi-input-type rendering, auto-save, and image upload - Update barrel exports in index.ts Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
13
apps/web-antd/src/api/asset/index.ts
Normal file
13
apps/web-antd/src/api/asset/index.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
export * from './parking';
|
||||||
|
export * from './customer';
|
||||||
|
export * from './supplier';
|
||||||
|
export * from './vehicle-model';
|
||||||
|
export * from './vehicle-registration';
|
||||||
|
export * from './contract';
|
||||||
|
export * from './vehicle-prepare';
|
||||||
|
export * from './delivery-task';
|
||||||
|
export * from './delivery-order';
|
||||||
|
export * from './return-order';
|
||||||
|
export * from './vehicle';
|
||||||
|
export * from './inspection';
|
||||||
|
export * from './vehicle-replacement';
|
||||||
100
apps/web-antd/src/api/asset/inspection.ts
Normal file
100
apps/web-antd/src/api/asset/inspection.ts
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import type { PageParam, PageResult } from '@vben/request';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export namespace InspectionApi {
|
||||||
|
export interface Template {
|
||||||
|
id?: number;
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
bizType: number;
|
||||||
|
vehicleType?: string;
|
||||||
|
status: number;
|
||||||
|
remark?: string;
|
||||||
|
items?: TemplateItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TemplateItem {
|
||||||
|
id?: number;
|
||||||
|
category: string;
|
||||||
|
itemName: string;
|
||||||
|
itemCode: string;
|
||||||
|
inputType: string;
|
||||||
|
sort: number;
|
||||||
|
required: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RecordDetail {
|
||||||
|
id: number;
|
||||||
|
recordCode: string;
|
||||||
|
templateId: number;
|
||||||
|
sourceType: number;
|
||||||
|
sourceId: number;
|
||||||
|
vehicleId: number;
|
||||||
|
inspectorName?: string;
|
||||||
|
inspectionTime?: string;
|
||||||
|
status: number;
|
||||||
|
overallResult?: number;
|
||||||
|
remark?: string;
|
||||||
|
items: RecordItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RecordItem {
|
||||||
|
id: number;
|
||||||
|
itemCode: string;
|
||||||
|
category: string;
|
||||||
|
itemName: string;
|
||||||
|
inputType: string;
|
||||||
|
result?: number;
|
||||||
|
value?: string;
|
||||||
|
remark?: string;
|
||||||
|
imageUrls?: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getInspectionTemplatePage(params: PageParam) {
|
||||||
|
return requestClient.get<PageResult<InspectionApi.Template>>(
|
||||||
|
'/asset/inspection-template/page',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getInspectionTemplate(id: number) {
|
||||||
|
return requestClient.get<InspectionApi.Template>(
|
||||||
|
`/asset/inspection-template/get?id=${id}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createInspectionTemplate(data: InspectionApi.Template) {
|
||||||
|
return requestClient.post('/asset/inspection-template/create', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateInspectionTemplate(data: InspectionApi.Template) {
|
||||||
|
return requestClient.put('/asset/inspection-template/update', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteInspectionTemplate(id: number) {
|
||||||
|
return requestClient.delete(`/asset/inspection-template/delete?id=${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getInspectionRecord(id: number) {
|
||||||
|
return requestClient.get<InspectionApi.RecordDetail>(
|
||||||
|
`/asset/inspection-record/get?id=${id}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateInspectionRecordItem(data: {
|
||||||
|
id: number;
|
||||||
|
result?: number;
|
||||||
|
value?: string;
|
||||||
|
remark?: string;
|
||||||
|
imageUrls?: string;
|
||||||
|
}) {
|
||||||
|
return requestClient.put('/asset/inspection-record/update-item', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function completeInspection(id: number, inspectorName: string) {
|
||||||
|
return requestClient.post(
|
||||||
|
`/asset/inspection-record/complete?id=${id}&inspectorName=${inspectorName}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
121
apps/web-antd/src/api/asset/return-order.ts
Normal file
121
apps/web-antd/src/api/asset/return-order.ts
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
import type { PageParam, PageResult } from '@vben/request';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export namespace AssetReturnOrderApi {
|
||||||
|
export interface ReturnOrderVehicle {
|
||||||
|
id?: number;
|
||||||
|
vehicleId?: number;
|
||||||
|
plateNo?: string;
|
||||||
|
vin?: string;
|
||||||
|
brand?: string;
|
||||||
|
model?: string;
|
||||||
|
returnMileage?: number;
|
||||||
|
returnHydrogenLevel?: number;
|
||||||
|
deliveryHydrogenLevel?: number;
|
||||||
|
hydrogenDiff?: number;
|
||||||
|
hydrogenUnitPrice?: number;
|
||||||
|
hydrogenRefundAmount?: number;
|
||||||
|
checkList?: string;
|
||||||
|
defectPhotos?: string;
|
||||||
|
vehicleDamageFee?: number;
|
||||||
|
toolDamageFee?: number;
|
||||||
|
unpaidMaintenanceFee?: number;
|
||||||
|
unpaidRepairFee?: number;
|
||||||
|
otherFee?: number;
|
||||||
|
inspectionRecordId?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ReturnOrder {
|
||||||
|
id?: number;
|
||||||
|
orderCode?: string;
|
||||||
|
contractId: number;
|
||||||
|
contractCode?: string;
|
||||||
|
projectName?: string;
|
||||||
|
customerId?: number;
|
||||||
|
customerName?: string;
|
||||||
|
returnDate: string;
|
||||||
|
returnPerson: string;
|
||||||
|
returnLocation?: string;
|
||||||
|
returnReason?: string;
|
||||||
|
returnReasonDesc?: string;
|
||||||
|
totalRefundAmount?: number;
|
||||||
|
depositRefund?: number;
|
||||||
|
hydrogenRefund?: number;
|
||||||
|
otherCharges?: number;
|
||||||
|
returnPhotos?: string;
|
||||||
|
sourceType?: number;
|
||||||
|
sourceId?: number;
|
||||||
|
deliveryOrderId?: number;
|
||||||
|
status?: number;
|
||||||
|
approvalStatus?: number;
|
||||||
|
vehicles?: ReturnOrderVehicle[];
|
||||||
|
createTime?: Date;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getReturnOrderPage(params: PageParam) {
|
||||||
|
return requestClient.get<PageResult<AssetReturnOrderApi.ReturnOrder>>(
|
||||||
|
'/asset/return-order/page',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getReturnOrder(id: number) {
|
||||||
|
return requestClient.get<AssetReturnOrderApi.ReturnOrder>(
|
||||||
|
`/asset/return-order/get?id=${id}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createReturnOrder(data: AssetReturnOrderApi.ReturnOrder) {
|
||||||
|
return requestClient.post('/asset/return-order/create', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateReturnOrder(data: AssetReturnOrderApi.ReturnOrder) {
|
||||||
|
return requestClient.put('/asset/return-order/update', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteReturnOrder(id: number) {
|
||||||
|
return requestClient.delete(`/asset/return-order/delete?id=${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function completeReturnOrderInspection(id: number) {
|
||||||
|
return requestClient.put(`/asset/return-order/complete-inspection?id=${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function settleReturnOrder(id: number) {
|
||||||
|
return requestClient.put(`/asset/return-order/settle?id=${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createReturnOrderFromDelivery(
|
||||||
|
deliveryOrderId: number,
|
||||||
|
vehicleIds: number[],
|
||||||
|
) {
|
||||||
|
return requestClient.post('/asset/return-order/create-from-delivery', {
|
||||||
|
deliveryOrderId,
|
||||||
|
vehicleIds,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function startVehicleInspection(returnOrderVehicleId: number) {
|
||||||
|
return requestClient.post(
|
||||||
|
`/asset/return-order/start-vehicle-inspection?returnOrderVehicleId=${returnOrderVehicleId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function completeVehicleInspection(returnOrderVehicleId: number) {
|
||||||
|
return requestClient.post(
|
||||||
|
`/asset/return-order/complete-vehicle-inspection?returnOrderVehicleId=${returnOrderVehicleId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function submitReturnOrderApproval(id: number) {
|
||||||
|
return requestClient.post(`/asset/return-order/submit-approval?id=${id}`, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function withdrawReturnOrderApproval(id: number) {
|
||||||
|
return requestClient.post(
|
||||||
|
`/asset/return-order/withdraw-approval?id=${id}`,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
}
|
||||||
82
apps/web-antd/src/api/asset/vehicle-replacement.ts
Normal file
82
apps/web-antd/src/api/asset/vehicle-replacement.ts
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import type { PageParam, PageResult } from '@vben/request';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export namespace AssetVehicleReplacementApi {
|
||||||
|
export interface VehicleReplacement {
|
||||||
|
id?: number;
|
||||||
|
replacementCode?: string;
|
||||||
|
replacementType: number;
|
||||||
|
contractId?: number;
|
||||||
|
contractCode?: string;
|
||||||
|
projectName?: string;
|
||||||
|
customerId?: number;
|
||||||
|
customerName?: string;
|
||||||
|
originalVehicleId?: number;
|
||||||
|
originalPlateNo?: string;
|
||||||
|
originalVin?: string;
|
||||||
|
newVehicleId?: number;
|
||||||
|
newPlateNo?: string;
|
||||||
|
newVin?: string;
|
||||||
|
deliveryOrderId?: number;
|
||||||
|
replacementReason?: string;
|
||||||
|
expectedDate?: string;
|
||||||
|
actualDate?: string;
|
||||||
|
returnDate?: string;
|
||||||
|
status?: number;
|
||||||
|
approvalStatus?: number;
|
||||||
|
bpmInstanceId?: string;
|
||||||
|
remark?: string;
|
||||||
|
creator?: string;
|
||||||
|
createTime?: Date;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getVehicleReplacementPage(params: PageParam) {
|
||||||
|
return requestClient.get<
|
||||||
|
PageResult<AssetVehicleReplacementApi.VehicleReplacement>
|
||||||
|
>('/asset/vehicle-replacement/page', { params });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getVehicleReplacement(id: number) {
|
||||||
|
return requestClient.get<AssetVehicleReplacementApi.VehicleReplacement>(
|
||||||
|
`/asset/vehicle-replacement/get?id=${id}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createVehicleReplacement(
|
||||||
|
data: AssetVehicleReplacementApi.VehicleReplacement,
|
||||||
|
) {
|
||||||
|
return requestClient.post('/asset/vehicle-replacement/create', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateVehicleReplacement(
|
||||||
|
data: AssetVehicleReplacementApi.VehicleReplacement,
|
||||||
|
) {
|
||||||
|
return requestClient.put('/asset/vehicle-replacement/update', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteVehicleReplacement(id: number) {
|
||||||
|
return requestClient.delete(`/asset/vehicle-replacement/delete?id=${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function submitVehicleReplacementApproval(id: number) {
|
||||||
|
return requestClient.post(
|
||||||
|
`/asset/vehicle-replacement/submit-approval?id=${id}`,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function withdrawVehicleReplacementApproval(id: number) {
|
||||||
|
return requestClient.post(
|
||||||
|
`/asset/vehicle-replacement/withdraw-approval?id=${id}`,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function confirmVehicleReplacementReturn(id: number) {
|
||||||
|
return requestClient.post(
|
||||||
|
`/asset/vehicle-replacement/confirm-return?id=${id}`,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
}
|
||||||
290
apps/web-antd/src/views/asset/components/InspectionForm.vue
Normal file
290
apps/web-antd/src/views/asset/components/InspectionForm.vue
Normal file
@@ -0,0 +1,290 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { InspectionApi } from '#/api/asset/inspection';
|
||||||
|
|
||||||
|
import { computed, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Collapse,
|
||||||
|
CollapsePanel,
|
||||||
|
Image,
|
||||||
|
Input,
|
||||||
|
InputNumber,
|
||||||
|
message,
|
||||||
|
RadioButton,
|
||||||
|
RadioGroup,
|
||||||
|
Space,
|
||||||
|
Spin,
|
||||||
|
Upload,
|
||||||
|
} from 'ant-design-vue';
|
||||||
|
|
||||||
|
import {
|
||||||
|
completeInspection,
|
||||||
|
getInspectionRecord,
|
||||||
|
updateInspectionRecordItem,
|
||||||
|
} from '#/api/asset/inspection';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
recordId: number;
|
||||||
|
readonly?: boolean;
|
||||||
|
onComplete?: () => void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
const submitting = ref(false);
|
||||||
|
const record = ref<InspectionApi.RecordDetail>();
|
||||||
|
const inspectorName = ref('');
|
||||||
|
|
||||||
|
// Group items by category
|
||||||
|
const groupedItems = computed(() => {
|
||||||
|
if (!record.value?.items) return [];
|
||||||
|
const map = new Map<string, InspectionApi.RecordItem[]>();
|
||||||
|
for (const item of record.value.items) {
|
||||||
|
const list = map.get(item.category) || [];
|
||||||
|
list.push(item);
|
||||||
|
map.set(item.category, list);
|
||||||
|
}
|
||||||
|
return [...map.entries()].map(([category, items]) => ({
|
||||||
|
category,
|
||||||
|
items,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Active collapse keys (all open by default)
|
||||||
|
const activeKeys = computed(() =>
|
||||||
|
groupedItems.value.map((g) => g.category),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Whether inspection is already completed
|
||||||
|
const isCompleted = computed(() => record.value?.status === 2);
|
||||||
|
|
||||||
|
// Load inspection record
|
||||||
|
async function loadRecord() {
|
||||||
|
if (!props.recordId) return;
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
record.value = await getInspectionRecord(props.recordId);
|
||||||
|
inspectorName.value = record.value.inspectorName || '';
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.recordId,
|
||||||
|
() => loadRecord(),
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
// Save single item on change
|
||||||
|
async function handleItemChange(item: InspectionApi.RecordItem) {
|
||||||
|
if (props.readonly || isCompleted.value) return;
|
||||||
|
try {
|
||||||
|
await updateInspectionRecordItem({
|
||||||
|
id: item.id,
|
||||||
|
result: item.result,
|
||||||
|
value: item.value,
|
||||||
|
remark: item.remark,
|
||||||
|
imageUrls: item.imageUrls,
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
message.error('保存检查项失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complete inspection
|
||||||
|
async function handleComplete() {
|
||||||
|
if (!inspectorName.value) {
|
||||||
|
message.warning('请输入验车人姓名');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
submitting.value = true;
|
||||||
|
try {
|
||||||
|
await completeInspection(props.recordId, inspectorName.value);
|
||||||
|
message.success('验车完成');
|
||||||
|
await loadRecord();
|
||||||
|
props.onComplete?.();
|
||||||
|
} catch {
|
||||||
|
message.error('完成验车失败');
|
||||||
|
} finally {
|
||||||
|
submitting.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Result options for checkbox type
|
||||||
|
const resultOptions = [
|
||||||
|
{ label: '合格', value: 1 },
|
||||||
|
{ label: '不合格', value: 2 },
|
||||||
|
{ label: '不适用', value: 3 },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Parse image URLs
|
||||||
|
function parseImageUrls(urls?: string): string[] {
|
||||||
|
if (!urls) return [];
|
||||||
|
return urls.split(',').filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle image upload (simplified - assumes backend returns URL)
|
||||||
|
function handleImageUpload(item: InspectionApi.RecordItem, info: any) {
|
||||||
|
if (info.file.status === 'done' && info.file.response) {
|
||||||
|
const url = info.file.response.data;
|
||||||
|
const urls = parseImageUrls(item.imageUrls);
|
||||||
|
urls.push(url);
|
||||||
|
item.imageUrls = urls.join(',');
|
||||||
|
handleItemChange(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine if inputs should be disabled
|
||||||
|
const isDisabled = computed(() => props.readonly || isCompleted.value);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Spin :spinning="loading">
|
||||||
|
<div class="inspection-form">
|
||||||
|
<!-- Inspector name -->
|
||||||
|
<div class="mb-4 flex items-center gap-4">
|
||||||
|
<label class="font-medium">验车人:</label>
|
||||||
|
<Input
|
||||||
|
v-model:value="inspectorName"
|
||||||
|
:disabled="isDisabled"
|
||||||
|
placeholder="请输入验车人姓名"
|
||||||
|
style="width: 200px"
|
||||||
|
/>
|
||||||
|
<span v-if="record?.inspectionTime" class="text-gray-500">
|
||||||
|
验车时间:{{ record.inspectionTime }}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-if="isCompleted"
|
||||||
|
class="rounded bg-green-100 px-2 py-1 text-sm text-green-700"
|
||||||
|
>
|
||||||
|
已完成
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Inspection items grouped by category -->
|
||||||
|
<Collapse :active-key="activeKeys" :bordered="true">
|
||||||
|
<CollapsePanel
|
||||||
|
v-for="group in groupedItems"
|
||||||
|
:key="group.category"
|
||||||
|
:header="group.category"
|
||||||
|
>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div
|
||||||
|
v-for="item in group.items"
|
||||||
|
:key="item.id"
|
||||||
|
class="rounded border border-gray-200 p-3"
|
||||||
|
>
|
||||||
|
<div class="mb-2 flex items-start justify-between">
|
||||||
|
<span class="font-medium">{{ item.itemName }}</span>
|
||||||
|
<span class="text-xs text-gray-400">{{ item.itemCode }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap items-start gap-4">
|
||||||
|
<!-- Checkbox type: radio group -->
|
||||||
|
<div v-if="item.inputType === 'checkbox'" class="flex-1">
|
||||||
|
<RadioGroup
|
||||||
|
v-model:value="item.result"
|
||||||
|
:disabled="isDisabled"
|
||||||
|
button-style="solid"
|
||||||
|
size="small"
|
||||||
|
@change="handleItemChange(item)"
|
||||||
|
>
|
||||||
|
<RadioButton
|
||||||
|
v-for="opt in resultOptions"
|
||||||
|
:key="opt.value"
|
||||||
|
:value="opt.value"
|
||||||
|
>
|
||||||
|
{{ opt.label }}
|
||||||
|
</RadioButton>
|
||||||
|
</RadioGroup>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Number type: input number -->
|
||||||
|
<div v-else-if="item.inputType === 'number'" class="flex-1">
|
||||||
|
<InputNumber
|
||||||
|
v-model:value="item.value"
|
||||||
|
:disabled="isDisabled"
|
||||||
|
placeholder="请输入数值"
|
||||||
|
size="small"
|
||||||
|
style="width: 200px"
|
||||||
|
@change="handleItemChange(item)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Text type: input -->
|
||||||
|
<div v-else class="flex-1">
|
||||||
|
<Input
|
||||||
|
v-model:value="item.value"
|
||||||
|
:disabled="isDisabled"
|
||||||
|
placeholder="请输入"
|
||||||
|
size="small"
|
||||||
|
style="width: 300px"
|
||||||
|
@change="handleItemChange(item)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Remark -->
|
||||||
|
<div class="flex-1">
|
||||||
|
<Input
|
||||||
|
v-model:value="item.remark"
|
||||||
|
:disabled="isDisabled"
|
||||||
|
placeholder="备注"
|
||||||
|
size="small"
|
||||||
|
@change="handleItemChange(item)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Images -->
|
||||||
|
<div class="mt-2">
|
||||||
|
<Space :size="8" wrap>
|
||||||
|
<Image
|
||||||
|
v-for="(url, idx) in parseImageUrls(item.imageUrls)"
|
||||||
|
:key="idx"
|
||||||
|
:src="url"
|
||||||
|
:width="60"
|
||||||
|
:height="60"
|
||||||
|
style="object-fit: cover; border-radius: 4px"
|
||||||
|
/>
|
||||||
|
<Upload
|
||||||
|
v-if="!isDisabled"
|
||||||
|
:show-upload-list="false"
|
||||||
|
action="/admin-api/infra/file/upload"
|
||||||
|
name="file"
|
||||||
|
accept="image/*"
|
||||||
|
@change="(info: any) => handleImageUpload(item, info)"
|
||||||
|
>
|
||||||
|
<Button size="small" type="dashed">上传图片</Button>
|
||||||
|
</Upload>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CollapsePanel>
|
||||||
|
</Collapse>
|
||||||
|
|
||||||
|
<!-- Overall remark -->
|
||||||
|
<div v-if="record" class="mt-4">
|
||||||
|
<label class="mb-1 block font-medium">整体备注:</label>
|
||||||
|
<Input.TextArea
|
||||||
|
v-model:value="record.remark"
|
||||||
|
:disabled="isDisabled"
|
||||||
|
:rows="3"
|
||||||
|
placeholder="请输入整体备注"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Complete button -->
|
||||||
|
<div v-if="!isDisabled" class="mt-4 text-right">
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
:loading="submitting"
|
||||||
|
@click="handleComplete"
|
||||||
|
>
|
||||||
|
完成验车
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Spin>
|
||||||
|
</template>
|
||||||
183
apps/web-antd/src/views/asset/return-order/index.vue
Normal file
183
apps/web-antd/src/views/asset/return-order/index.vue
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
|
import type { AssetReturnOrderApi } from '#/api/asset/return-order';
|
||||||
|
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import { confirm, Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import {
|
||||||
|
completeReturnOrderInspection,
|
||||||
|
deleteReturnOrder,
|
||||||
|
getReturnOrderPage,
|
||||||
|
settleReturnOrder,
|
||||||
|
} from '#/api/asset/return-order';
|
||||||
|
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 handleEdit(row: AssetReturnOrderApi.ReturnOrder) {
|
||||||
|
formModalApi.setData(row).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleDelete(row: AssetReturnOrderApi.ReturnOrder) {
|
||||||
|
const hideLoading = message.loading({
|
||||||
|
content: $t('ui.actionMessage.deleting', [row.orderCode]),
|
||||||
|
duration: 0,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await deleteReturnOrder(row.id!);
|
||||||
|
message.success($t('ui.actionMessage.deleteSuccess', [row.orderCode]));
|
||||||
|
handleRefresh();
|
||||||
|
} finally {
|
||||||
|
hideLoading();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleCompleteInspection(row: AssetReturnOrderApi.ReturnOrder) {
|
||||||
|
await confirm('确认验车完成?完成后状态将变更为验车完成。');
|
||||||
|
await completeReturnOrderInspection(row.id!);
|
||||||
|
message.success('验车已完成');
|
||||||
|
handleRefresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSettle(row: AssetReturnOrderApi.ReturnOrder) {
|
||||||
|
await confirm('确认结算还车单?结算后不可修改。');
|
||||||
|
await settleReturnOrder(row.id!);
|
||||||
|
message.success('还车单已结算');
|
||||||
|
handleRefresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 根据状态动态生成操作菜单 */
|
||||||
|
function getRowActions(row: AssetReturnOrderApi.ReturnOrder) {
|
||||||
|
const actions: any[] = [];
|
||||||
|
|
||||||
|
// 查看 - 始终可见
|
||||||
|
actions.push({
|
||||||
|
auth: ['asset:return-order:query'],
|
||||||
|
icon: ACTION_ICON.VIEW,
|
||||||
|
label: '查看',
|
||||||
|
onClick: () => message.info('查看功能开发中'),
|
||||||
|
type: 'link',
|
||||||
|
});
|
||||||
|
|
||||||
|
// 待验车 → 可编辑
|
||||||
|
if (row.status === 0) {
|
||||||
|
actions.push({
|
||||||
|
auth: ['asset:return-order:update'],
|
||||||
|
icon: ACTION_ICON.EDIT,
|
||||||
|
label: $t('common.edit'),
|
||||||
|
onClick: () => handleEdit(row),
|
||||||
|
type: 'link',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 待验车 → 完成验车
|
||||||
|
if (row.status === 0) {
|
||||||
|
actions.push({
|
||||||
|
auth: ['asset:return-order:update'],
|
||||||
|
label: '完成验车',
|
||||||
|
onClick: () => handleCompleteInspection(row),
|
||||||
|
type: 'link',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验车完成 → 结算
|
||||||
|
if (row.status === 1) {
|
||||||
|
actions.push({
|
||||||
|
auth: ['asset:return-order:update'],
|
||||||
|
label: '结算',
|
||||||
|
onClick: () => handleSettle(row),
|
||||||
|
type: 'link',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 待验车 → 可删除
|
||||||
|
if (row.status === 0) {
|
||||||
|
actions.push({
|
||||||
|
auth: ['asset:return-order:delete'],
|
||||||
|
danger: true,
|
||||||
|
icon: ACTION_ICON.DELETE,
|
||||||
|
label: $t('common.delete'),
|
||||||
|
popConfirm: {
|
||||||
|
confirm: () => handleDelete(row),
|
||||||
|
title: $t('ui.actionMessage.deleteConfirm', [row.orderCode]),
|
||||||
|
},
|
||||||
|
type: 'link',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
|
formOptions: {
|
||||||
|
schema: useGridFormSchema(),
|
||||||
|
},
|
||||||
|
gridOptions: {
|
||||||
|
columns: useGridColumns(),
|
||||||
|
height: 'auto',
|
||||||
|
keepSource: true,
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async ({ page }, formValues) => {
|
||||||
|
return await getReturnOrderPage({
|
||||||
|
pageNo: page.currentPage,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
...formValues,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rowConfig: {
|
||||||
|
keyField: 'id',
|
||||||
|
isHover: true,
|
||||||
|
},
|
||||||
|
toolbarConfig: {
|
||||||
|
refresh: true,
|
||||||
|
search: true,
|
||||||
|
},
|
||||||
|
} as VxeTableGridOptions<AssetReturnOrderApi.ReturnOrder>,
|
||||||
|
});
|
||||||
|
</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:return-order:create'],
|
||||||
|
onClick: handleCreate,
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<TableAction :actions="getRowActions(row)" />
|
||||||
|
</template>
|
||||||
|
</Grid>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
Reference in New Issue
Block a user