From 4762afb6cb043a3e539ed7ec8da5bde997bfaf65 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期五, 29 五月 2026 11:51:41 +0800
Subject: [PATCH] 英泽防锈 1.车辆管理页面开发联调
---
src/views/officeProcessAutomation/ApproveManage/approve-template/components/FormConfigEditor.vue | 13
src/views/productionManagement/productionProcess/index.vue | 41 -
src/views/officeProcessAutomation/ApproveManage/approve-list/index.vue | 6
src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalTemplateBindingUtils.js | 2
src/views/officeProcessAutomation/ApproveManage/approve-list/components/FormPayloadFields.vue | 11
src/views/productionManagement/processRoute/processRouteItem/index.vue | 14
src/views/productionManagement/productionCosting/index.vue | 20
src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalInstanceFormConfigTable.js | 13
src/api/officeProcessAutomation/vehicle.js | 179 ++++++
src/views/officeProcessAutomation/ApproveManage/approve-template/selectOptionSource.js | 36 +
src/views/officeProcessAutomation/ApproveManage/approve-template/formConfigUtils.js | 54 +
src/views/productionManagement/productionProcess/Edit.vue | 25
src/views/officeProcessAutomation/ApproveManage/approve-list/useApproveList.js | 48 +
src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalModuleRegistry.js | 16
src/views/officeProcessAutomation/ApproveManage/vehicle-apply/index.vue | 1066 ++++++++++++++++++++++++++++++++++++++
src/views/officeProcessAutomation/ApproveManage/approve-template/useSelectOptionSources.js | 4
src/views/officeProcessAutomation/ApproveManage/approve-list/approveListConstants.js | 10
src/views/inventoryManagement/vehicleManagement/index.vue | 78 ++
src/views/productionManagement/productionProcess/New.vue | 23
19 files changed, 1,537 insertions(+), 122 deletions(-)
diff --git a/src/api/officeProcessAutomation/vehicle.js b/src/api/officeProcessAutomation/vehicle.js
new file mode 100644
index 0000000..d76dc8d
--- /dev/null
+++ b/src/api/officeProcessAutomation/vehicle.js
@@ -0,0 +1,179 @@
+import request from "@/utils/request";
+
+/**
+ * 杞﹁締绠$悊API
+ */
+
+// ==================== 杞﹁締涓绘。鎺ュ彛 ====================
+
+/**
+ * 鍒嗛〉鏌ヨ杞﹁締鍒楄〃
+ * @param {Object} params - 鏌ヨ鍙傛暟
+ * @param {number} params.current - 褰撳墠椤电爜
+ * @param {number} params.size - 姣忛〉鏉℃暟
+ * @param {string} params.plateNumber - 杞︾墝鍙凤紙妯$硦鏌ヨ锛�
+ * @param {string} params.status - 浣跨敤鐘舵��
+ */
+export function listVehiclePage(params) {
+ return request({
+ url: "/approve/vehicle/listPage",
+ method: "get",
+ params,
+ });
+}
+
+/**
+ * 鏂板杞﹁締
+ * @param {Object} data - 杞﹁締鏁版嵁
+ * @param {string} data.plateNumber - 杞︾墝鍙�
+ * @param {number} data.mileage - 杞﹁締鍏噷鏁�
+ * @param {string} data.status - 浣跨敤鐘舵�侊細IDLE / IN_USE / MAINTENANCE / SCRAPPED
+ */
+export function saveVehicle(data) {
+ return request({
+ url: "/approve/vehicle/save",
+ method: "post",
+ data,
+ });
+}
+
+/**
+ * 杞﹁締璇︽儏
+ * @param {number} id - 杞﹁締ID
+ */
+export function getVehicleDetail(id) {
+ return request({
+ url: "/approve/vehicle/detail",
+ method: "get",
+ params: { id },
+ });
+}
+
+/**
+ * 淇敼杞﹁締
+ * @param {Object} data - 杞﹁締鏁版嵁
+ */
+export function updateVehicle(data) {
+ return request({
+ url: "/approve/vehicle/update",
+ method: "post",
+ data,
+ });
+}
+
+/**
+ * 鍒犻櫎杞﹁締
+ * @param {Array<number>} ids - 杞﹁締ID鏁扮粍
+ */
+export function deleteVehicle(ids) {
+ return request({
+ url: "/approve/vehicle/delete",
+ method: "delete",
+ data: ids,
+ });
+}
+
+// ==================== 鍊熷嚭璁板綍鎺ュ彛 ====================
+
+/**
+ * 鍒嗛〉鏌ヨ鍊熷嚭璁板綍
+ * @param {Object} params - 鏌ヨ鍙傛暟
+ * @param {number} params.current - 褰撳墠椤电爜
+ * @param {number} params.size - 姣忛〉鏉℃暟
+ * @param {string} params.borrowNo - 鍊熷嚭鍗曞彿
+ * @param {string} params.vehiclePlateNumber - 杞︾墝鍙�
+ * @param {string} params.applicantName - 鐢宠浜�
+ * @param {string} params.borrowStatus - 鍊熷嚭鐘舵��
+ * @param {string} params.extendStatus - 寤舵湡鐘舵��
+ */
+export function listBorrowPage(params) {
+ return request({
+ url: "/approve/vehicle/borrow/listPage",
+ method: "get",
+ params,
+ });
+}
+
+/**
+ * 鏂板鍊熷嚭鐢宠
+ * @param {Object} data - 鍊熷嚭鏁版嵁
+ * @param {number} data.vehicleId - 杞﹁締ID
+ * @param {string} data.borrowReason - 鍊熷嚭鍘熷洜
+ * @param {string} data.borrowStartTime - 鍊熷嚭寮�濮嬫椂闂�
+ * @param {string} data.plannedReturnTime - 璁″垝褰掕繕鏃堕棿
+ * @param {string} data.borrowStatus - 鍊熷嚭鐘舵�侊細DRAFT / IN_APPROVAL
+ * @param {number} data.approvalTemplateId - 瀹℃壒妯℃澘ID
+ */
+export function saveBorrow(data) {
+ return request({
+ url: "/approve/vehicle/borrow/save",
+ method: "post",
+ data,
+ });
+}
+
+/**
+ * 鍊熷嚭璁板綍璇︽儏
+ * @param {number} id - 鍊熷嚭璁板綍ID
+ */
+export function getBorrowDetail(id) {
+ return request({
+ url: "/approve/vehicle/borrow/detail",
+ method: "get",
+ params: { id },
+ });
+}
+
+/**
+ * 淇敼鍊熷嚭鐢宠
+ * @param {Object} data - 鍊熷嚭鏁版嵁
+ */
+export function updateBorrow(data) {
+ return request({
+ url: "/approve/vehicle/borrow/update",
+ method: "post",
+ data,
+ });
+}
+
+/**
+ * 鍒犻櫎鍊熷嚭璁板綍
+ * @param {Array<number>} ids - 鍊熷嚭璁板綍ID鏁扮粍
+ */
+export function deleteBorrow(ids) {
+ return request({
+ url: "/approve/vehicle/borrow/delete",
+ method: "delete",
+ data: ids,
+ });
+}
+
+/**
+ * 褰掕繕杞﹁締
+ * @param {Object} data - 褰掕繕鏁版嵁
+ * @param {number} data.id - 鍊熷嚭璁板綍ID
+ * @param {string} data.actualReturnTime - 瀹為檯褰掕繕鏃堕棿
+ */
+export function returnVehicle(data) {
+ return request({
+ url: "/approve/vehicle/borrow/return",
+ method: "post",
+ data,
+ });
+}
+
+/**
+ * 鍙戣捣寤舵湡鐢宠
+ * @param {Object} data - 寤舵湡鏁版嵁
+ * @param {number} data.id - 鍊熷嚭璁板綍ID
+ * @param {string} data.extendTargetReturnTime - 寤舵湡鍚庣殑褰掕繕鏃堕棿
+ * @param {string} data.extendReason - 寤舵湡鍘熷洜
+ * @param {number} data.approvalTemplateId - 寤舵湡瀹℃壒妯℃澘ID
+ */
+export function delayBorrow(data) {
+ return request({
+ url: "/approve/vehicle/borrow/delay",
+ method: "post",
+ data,
+ });
+}
diff --git a/src/views/inventoryManagement/vehicleManagement/index.vue b/src/views/inventoryManagement/vehicleManagement/index.vue
index 1e383c6..bba4f19 100644
--- a/src/views/inventoryManagement/vehicleManagement/index.vue
+++ b/src/views/inventoryManagement/vehicleManagement/index.vue
@@ -136,6 +136,14 @@
width="120"
show-overflow-tooltip
/>
+ <el-table-column label="褰撳墠鍏噷鏁�" prop="currentMileage" width="120" align="center" />
+ <el-table-column label="浣跨敤鐘舵��" width="100" align="center">
+ <template #default="scope">
+ <el-tag :type="usageStatusTagType(scope.row.usageStatus)">
+ {{ usageStatusLabel(scope.row.usageStatus) }}
+ </el-tag>
+ </template>
+ </el-table-column>
<el-table-column label="鐘舵��" width="100" align="center">
<template #default="scope">
<el-tag :type="statusTagType(scope.row.status)">
@@ -303,6 +311,30 @@
/>
</el-form-item>
</el-col>
+ <el-col :span="12">
+ <el-form-item label="褰撳墠鍏噷鏁帮細" prop="currentMileage">
+ <el-input-number
+ v-model="form.currentMileage"
+ :min="0"
+ :precision="1"
+ :step="0.1"
+ style="width: 100%"
+ placeholder="璇疯緭鍏ュ綋鍓嶅叕閲屾暟"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="浣跨敤鐘舵�侊細" prop="usageStatus">
+ <el-select v-model="form.usageStatus" placeholder="璇烽�夋嫨浣跨敤鐘舵��" style="width: 100%">
+ <el-option label="闂茬疆" value="idle" />
+ <el-option label="浣跨敤涓�" value="in_use" />
+ <el-option label="缁翠慨涓�" value="repair" />
+ <el-option label="淇濆吇涓�" value="maintenance" />
+ </el-select>
+ </el-form-item>
+ </el-col>
</el-row>
</el-form>
<template #footer>
@@ -333,6 +365,10 @@
licenseExpireDate: "2026-03-10",
status: "鍦ㄧ敤",
archived: false,
+ currentMileage: 12580.5,
+ lastRecordMileage: 12000.0,
+ mileagePhoto: "",
+ usageStatus: "idle",
},
{
id: 2,
@@ -346,6 +382,10 @@
licenseExpireDate: "2025-07-28",
status: "缁翠慨",
archived: false,
+ currentMileage: 8920.0,
+ lastRecordMileage: 8920.0,
+ mileagePhoto: "",
+ usageStatus: "repair",
},
{
id: 3,
@@ -359,6 +399,10 @@
licenseExpireDate: "2024-05-18",
status: "闂茬疆",
archived: false,
+ currentMileage: 45600.0,
+ lastRecordMileage: 45600.0,
+ mileagePhoto: "",
+ usageStatus: "idle",
},
{
id: 4,
@@ -372,6 +416,10 @@
licenseExpireDate: "2023-11-08",
status: "鍦ㄧ敤",
archived: true,
+ currentMileage: 67890.5,
+ lastRecordMileage: 67890.5,
+ mileagePhoto: "",
+ usageStatus: "archived",
},
]);
@@ -394,6 +442,7 @@
{ label: "鍦ㄧ敤", value: "鍦ㄧ敤" },
{ label: "闂茬疆", value: "闂茬疆" },
{ label: "缁翠慨", value: "缁翠慨" },
+ { label: "浣跨敤涓�", value: "浣跨敤涓�" },
];
// 鏌ヨ琛ㄥ崟
@@ -425,6 +474,10 @@
licenseExpireDate: "",
status: "鍦ㄧ敤",
archived: false,
+ currentMileage: 0,
+ lastRecordMileage: 0,
+ mileagePhoto: "",
+ usageStatus: "idle",
});
const rules = {
@@ -569,8 +622,33 @@
if (status === "鍦ㄧ敤") return "success";
if (status === "闂茬疆") return "info";
if (status === "缁翠慨") return "warning";
+ if (status === "浣跨敤涓�") return "primary";
return "default";
};
+
+// 浣跨敤鐘舵�佹爣绛炬牱寮�
+const usageStatusTagType = (usageStatus) => {
+ const typeMap = {
+ idle: "info",
+ in_use: "primary",
+ repair: "warning",
+ maintenance: "warning",
+ archived: "info",
+ };
+ return typeMap[usageStatus] || "default";
+};
+
+// 浣跨敤鐘舵�佹爣绛炬枃瀛�
+const usageStatusLabel = (usageStatus) => {
+ const labelMap = {
+ idle: "闂茬疆",
+ in_use: "浣跨敤涓�",
+ repair: "缁翠慨涓�",
+ maintenance: "淇濆吇涓�",
+ archived: "宸插綊妗�",
+ };
+ return labelMap[usageStatus] || usageStatus;
+};
</script>
<style scoped lang="scss">
diff --git a/src/views/officeProcessAutomation/ApproveManage/approve-list/approveListConstants.js b/src/views/officeProcessAutomation/ApproveManage/approve-list/approveListConstants.js
index 3251f0c..12cf082 100644
--- a/src/views/officeProcessAutomation/ApproveManage/approve-list/approveListConstants.js
+++ b/src/views/officeProcessAutomation/ApproveManage/approve-list/approveListConstants.js
@@ -19,6 +19,9 @@
{ value: "quotation", label: "鎶ヤ环瀹℃壒", cellBg: "#f4ecfc", cellColor: "#9b59b6" },
{ value: "shipment", label: "鍙戣揣瀹℃壒", cellBg: "#e8faf6", cellColor: "#1abc9c" },
{ value: "enterprise_news", label: "浼佷笟鏂伴椈", cellBg: "#ecf5ff", cellColor: "#409eff" },
+ { value: "vehicle", label: "杞﹁締瀹℃壒", cellBg: "#fff7e6", cellColor: "#fa8c16" },
+ { value: "vehicle_return", label: "杞﹁締杩樿溅瀹℃壒", cellBg: "#e6fffb", cellColor: "#13c2c2" },
+ { value: "vehicle_extend", label: "杞﹁締寤舵湡瀹℃壒", cellBg: "#f6ffed", cellColor: "#52c41a" },
];
/** 鍒楄〃鏌ヨ锛氬鎵圭姸鎬侊紙涓庡悗绔� status 鏋氫妇涓�鑷达級 */
@@ -516,12 +519,12 @@
return APPROVAL_STATUS_OPTIONS.find(x => x.value === key)?.label || "鈥�";
}
-/** 涓氬姟鐢宠椤电姸鎬佹枃妗堬細PENDING鈫掕繘琛屼腑 APPROVED鈫掑凡瀹屾垚 REJECTED鈫掑凡椹冲洖 */
+/** 涓氬姟鐢宠椤电姸鎬佹枃妗堬細PENDING鈫掑鎵逛腑 APPROVED鈫掑凡閫氳繃 REJECTED鈫掑凡椹冲洖 */
export function businessApprovalStatusLabel(v) {
const key = normalizeApprovalStatusKey(v);
if (key === "draft") return "鑽夌";
- if (key === "pending") return "杩涜涓�";
- if (key === "approved") return "宸插畬鎴�";
+ if (key === "pending") return "瀹℃壒涓�";
+ if (key === "approved") return "宸查�氳繃";
if (key === "rejected") return "宸查┏鍥�";
if (key === "cancelled") return "宸叉挙閿�";
return "鈥�";
@@ -600,6 +603,7 @@
flowNodes,
templateAttachments: tpl?.storageBlobDTOs ? JSON.parse(JSON.stringify(tpl.storageBlobDTOs)) : [],
storageBlobDTOs: [],
+ formConfig: null,
};
}
diff --git a/src/views/officeProcessAutomation/ApproveManage/approve-list/components/FormPayloadFields.vue b/src/views/officeProcessAutomation/ApproveManage/approve-list/components/FormPayloadFields.vue
index 7933db5..18e5387 100644
--- a/src/views/officeProcessAutomation/ApproveManage/approve-list/components/FormPayloadFields.vue
+++ b/src/views/officeProcessAutomation/ApproveManage/approve-list/components/FormPayloadFields.vue
@@ -11,7 +11,7 @@
v-for="field in fields"
:key="field.key"
:label="field.label"
- :span="field.type === 'textarea' || field.type === 'datetimerange' ? 2 : 1"
+ :span="field.type === 'textarea' || field.type === 'datetimerange' || field.type === 'datetime' ? 2 : 1"
>
<span class="field-value">{{ displayValue(field) }}</span>
</el-descriptions-item>
@@ -62,6 +62,15 @@
style="width: 100%"
/>
<el-date-picker
+ v-else-if="field.type === 'datetime'"
+ v-model="formPayload[field.key]"
+ type="datetime"
+ :placeholder="`璇烽�夋嫨${field.label}`"
+ format="YYYY-MM-DD HH:mm:ss"
+ value-format="YYYY-MM-DD HH:mm:ss"
+ style="width: 100%"
+ />
+ <el-date-picker
v-else-if="field.type === 'datetimerange'"
v-model="formPayload[field.key]"
type="datetimerange"
diff --git a/src/views/officeProcessAutomation/ApproveManage/approve-list/index.vue b/src/views/officeProcessAutomation/ApproveManage/approve-list/index.vue
index bbfa56a..078fd45 100644
--- a/src/views/officeProcessAutomation/ApproveManage/approve-list/index.vue
+++ b/src/views/officeProcessAutomation/ApproveManage/approve-list/index.vue
@@ -48,6 +48,7 @@
<el-button :icon="RefreshRight" @click="resetSearch">閲嶇疆</el-button>
</div>
<div class="search_actions">
+ <el-button type="danger" :disabled="!selectedRows?.length" @click="batchDelete">鎵归噺鍒犻櫎</el-button>
<el-button type="primary" :icon="Plus" @click="openSubmitDialog">鎻愪氦瀹℃壒</el-button>
</div>
</div>
@@ -58,10 +59,11 @@
:column="tableColumn"
:tableData="tableData"
:page="page"
- :isSelection="false"
+ :isSelection="true"
:tableLoading="tableLoading"
:total="page.total"
@pagination="pagination"
+ @selection-change="(rows) => selectedRows = rows"
>
<template #approveType="{ row }">
<span class="approve-type-cell" :style="approvalTypeStyle(row.approvalType)">
@@ -395,6 +397,8 @@
submitApprove,
openDetail,
openApprove,
+ selectedRows,
+ batchDelete,
} = al;
const { flowUserOptions, loadFlowUsers } = useFlowUserOptions();
diff --git a/src/views/officeProcessAutomation/ApproveManage/approve-list/useApproveList.js b/src/views/officeProcessAutomation/ApproveManage/approve-list/useApproveList.js
index 67b9213..68a3a3e 100644
--- a/src/views/officeProcessAutomation/ApproveManage/approve-list/useApproveList.js
+++ b/src/views/officeProcessAutomation/ApproveManage/approve-list/useApproveList.js
@@ -98,6 +98,9 @@
const submitFormRef = ref();
const submitSaving = ref(false);
+ // 鎵归噺鍒犻櫎鐩稿叧
+ const selectedRows = ref([]);
+
const isSubmitEdit = computed(() => submitDialog.mode === "edit");
const submitDialogTitle = computed(() => {
if (submitDialog.mode === "edit") {
@@ -569,6 +572,49 @@
return "寰呭鐞�";
}
+ // 鎵归噺鍒犻櫎瀹℃壒瀹炰緥
+ async function batchDelete() {
+ if (!selectedRows.value?.length) {
+ ElMessage.warning("璇烽�夋嫨瑕佸垹闄ょ殑璁板綍");
+ return;
+ }
+ try {
+ await ElMessageBox.confirm(
+ `纭畾鍒犻櫎閫変腑鐨� ${selectedRows.value.length} 鏉″鎵硅褰曞悧锛熷垹闄ゅ悗涓嶅彲鎭㈠銆俙,
+ "鍒犻櫎纭",
+ {
+ type: "warning",
+ confirmButtonText: "纭畾鍒犻櫎",
+ cancelButtonText: "鍙栨秷",
+ distinguishCancelAndClose: true,
+ autofocus: false,
+ }
+ );
+ } catch {
+ return;
+ }
+ const ids = selectedRows.value.map((row) => row.id).filter(Boolean);
+ if (!ids.length) {
+ ElMessage.warning("鏃犳硶鍒犻櫎锛氱己灏戝鎵瑰疄渚� ID");
+ return;
+ }
+ try {
+ await deleteApprovalInstance(ids);
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ selectedRows.value = [];
+ // 鍏抽棴鍙兘鎵撳紑鐨勮鎯呭脊绐�
+ if (detailDialog.visible) {
+ detailDialog.visible = false;
+ }
+ if (approveDialog.visible) {
+ approveDialog.visible = false;
+ }
+ await fetchApprovalList();
+ } catch {
+ /* 閿欒鐢辨嫤鎴櫒鎻愮ず */
+ }
+ }
+
return {
Search,
APPROVAL_TYPE_OPTIONS,
@@ -624,5 +670,7 @@
openDetail,
openApprove,
fetchApprovalList,
+ selectedRows,
+ batchDelete,
};
}
diff --git a/src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalInstanceFormConfigTable.js b/src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalInstanceFormConfigTable.js
index d6bec18..96e5fb8 100644
--- a/src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalInstanceFormConfigTable.js
+++ b/src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalInstanceFormConfigTable.js
@@ -82,19 +82,23 @@
* 涓氬姟鐢宠涓昏〃鍒楋細鍥哄畾鍒� + formConfig 鍔ㄦ�佸垪 + 瀹℃壒鐘舵�� + 鎿嶄綔
*/
export function buildInstanceTableColumns(tableDataRef, buildTableActions, options = {}) {
- const { moduleKey, excludeKeys = DEFAULT_EXCLUDE_KEYS, beforeFormColumns = [], extraColumns = [], afterFormColumns = [], actionWidth = 260 } = options;
+ const { moduleKey, excludeKeys = DEFAULT_EXCLUDE_KEYS, beforeFormColumns = [], extraColumns = [], afterFormColumns = [], actionWidth = 260, showCreateTime = true } = options;
const leadingCols = moduleKey && INSTANCE_NO_SEARCH_MODULE_KEYS.has(moduleKey) ? [INSTANCE_NO_TABLE_COLUMN] : [];
return computed(() => {
const formCols = getFormConfigFieldColumns(tableDataRef.value?.[0], { excludeKeys });
- return [
+ const cols = [
...leadingCols,
...beforeFormColumns,
...formCols,
...extraColumns,
...afterFormColumns,
- { label: "鍒涘缓鏃堕棿", prop: "createTime", width: 170 },
+ ];
+ if (showCreateTime !== false) {
+ cols.push({ label: "鍒涘缓鏃堕棿", prop: "createTime", width: 170 });
+ }
+ cols.push(
{
label: "瀹℃壒鐘舵��",
prop: "approvalStatus",
@@ -111,6 +115,7 @@
width: actionWidth,
operation: buildTableActions(),
},
- ];
+ );
+ return cols;
});
}
diff --git a/src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalModuleRegistry.js b/src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalModuleRegistry.js
index fd56eca..b5cd8c3 100644
--- a/src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalModuleRegistry.js
+++ b/src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalModuleRegistry.js
@@ -12,6 +12,8 @@
TRAVEL_REIMBURSE: "travel_reimburse",
COST_REIMBURSE: "cost_reimburse",
ENTERPRISE_NEWS: "enterprise_news",
+ VEHICLE: "vehicle",
+ VEHICLE_DELAY: "vehicle_delay",
};
/** 瀹℃壒瀹炰緥 listPage / 淇濆瓨 浣跨敤鐨� businessType 鏋氫妇 */
@@ -24,6 +26,8 @@
[APPROVAL_MODULE_KEYS.TRAVEL_REIMBURSE]: 16,
[APPROVAL_MODULE_KEYS.COST_REIMBURSE]: 17,
[APPROVAL_MODULE_KEYS.ENTERPRISE_NEWS]: 18,
+ [APPROVAL_MODULE_KEYS.VEHICLE]: 19,
+ [APPROVAL_MODULE_KEYS.VEHICLE_DELAY]: 20,
};
/** @type {Record<string, import('./approvalModuleRegistry.js').ApprovalModuleConfig>} */
@@ -81,6 +85,18 @@
businessType: APPROVAL_BUSINESS_TYPE[APPROVAL_MODULE_KEYS.ENTERPRISE_NEWS],
typeLabels: ["浼佷笟鏂伴椈", "鏂伴椈", "鏂伴椈鍙戝竷"],
},
+ [APPROVAL_MODULE_KEYS.VEHICLE]: {
+ label: "杞﹁締瀹℃壒",
+ approvalType: "vehicle",
+ businessType: APPROVAL_BUSINESS_TYPE[APPROVAL_MODULE_KEYS.VEHICLE],
+ typeLabels: ["杞﹁締", "杞﹁締瀹℃壒", "鐢ㄨ溅鐢宠", "杞﹁締浣跨敤"],
+ },
+ [APPROVAL_MODULE_KEYS.VEHICLE_DELAY]: {
+ label: "杞﹁締寤舵湡瀹℃壒",
+ approvalType: "vehicle_delay",
+ businessType: APPROVAL_BUSINESS_TYPE[APPROVAL_MODULE_KEYS.VEHICLE_DELAY],
+ typeLabels: ["杞﹁締寤舵湡", "寤舵湡鐢宠", "杞﹁締寤舵湡瀹℃壒"],
+ },
};
/**
diff --git a/src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalTemplateBindingUtils.js b/src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalTemplateBindingUtils.js
index d68016f..c11a541 100644
--- a/src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalTemplateBindingUtils.js
+++ b/src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalTemplateBindingUtils.js
@@ -40,6 +40,7 @@
flowNodes: base.flowNodes,
templateAttachments: JSON.parse(JSON.stringify(templateAttachments)),
storageBlobDTOs: [],
+ formConfig: mapped.formConfig, // 鍘熷 formConfig JSON 瀛楃涓�
};
}
@@ -80,6 +81,7 @@
flowNodes: "flowNodes",
templateAttachments: "templateAttachments",
storageBlobDTOs: "storageBlobDTOs",
+ formConfig: "formConfig",
...fieldMap,
};
Object.entries(map).forEach(([srcKey, destKey]) => {
diff --git a/src/views/officeProcessAutomation/ApproveManage/approve-template/components/FormConfigEditor.vue b/src/views/officeProcessAutomation/ApproveManage/approve-template/components/FormConfigEditor.vue
index 6880f3f..3160c24 100644
--- a/src/views/officeProcessAutomation/ApproveManage/approve-template/components/FormConfigEditor.vue
+++ b/src/views/officeProcessAutomation/ApproveManage/approve-template/components/FormConfigEditor.vue
@@ -239,6 +239,18 @@
@change="emitOut"
/>
<el-date-picker
+ v-else-if="field.type === 'datetime'"
+ v-model="field.defaultValue"
+ type="datetime"
+ placeholder="閫夊~"
+ format="YYYY-MM-DD HH:mm:ss"
+ value-format="YYYY-MM-DD HH:mm:ss"
+ style="width: 100%"
+ :disabled="isFieldLocked(field)"
+ clearable
+ @change="emitOut"
+ />
+ <el-date-picker
v-else-if="field.type === 'datetimerange'"
v-model="field.defaultValue"
type="datetimerange"
@@ -490,6 +502,7 @@
function resetDefaultValueForType(field) {
if (field.type === "number") field.defaultValue = undefined;
else if (field.type === "datetimerange") field.defaultValue = [];
+ else if (field.type === "datetime") field.defaultValue = "";
else field.defaultValue = "";
}
diff --git a/src/views/officeProcessAutomation/ApproveManage/approve-template/formConfigUtils.js b/src/views/officeProcessAutomation/ApproveManage/approve-template/formConfigUtils.js
index c1f66bd..f18c0d2 100644
--- a/src/views/officeProcessAutomation/ApproveManage/approve-template/formConfigUtils.js
+++ b/src/views/officeProcessAutomation/ApproveManage/approve-template/formConfigUtils.js
@@ -13,6 +13,7 @@
{ value: "textarea", label: "澶氳鏂囨湰" },
{ value: "number", label: "鏁板瓧" },
{ value: "date", label: "鏃ユ湡" },
+ { value: "datetime", label: "鏃ユ湡鏃堕棿" },
{ value: "datetimerange", label: "鏃ユ湡鏃堕棿鑼冨洿" },
{ value: "select", label: "涓嬫媺閫夋嫨" },
];
@@ -59,6 +60,56 @@
{ key: "dateRange", label: "璇峰亣鏃堕棿", type: "datetimerange", required: true },
],
},
+ {
+ key: "vehicle",
+ label: "杞﹁締瀹℃壒",
+ summaryPlaceholder: "璇峰~鍐欒溅杈嗕娇鐢ㄧ敵璇蜂俊鎭�",
+ fields: [
+ { key: "vehicleNo", label: "杞︾墝鍙�", type: "select", required: true, optionSource: "vehicle_list", placeholder: "璇烽�夋嫨杞﹁締" },
+ { key: "driver", label: "椹鹃┒鍛�", type: "text", required: true },
+ { key: "purpose", label: "鐢ㄨ溅浜嬬敱", type: "textarea", required: true, rows: 2 },
+ { key: "useDateRange", label: "杞﹁締浣跨敤鏃堕棿", type: "datetimerange", required: true },
+ { key: "destination", label: "鐩殑鍦�", type: "text", required: true },
+ { key: "passengers", label: "涔樿溅浜烘暟", type: "number", required: false, min: 1, precision: 0 },
+ { key: "startMileage", label: "璧峰鍏噷鏁�", type: "number", required: true, min: 0, precision: 1 },
+ { key: "startMileagePhoto", label: "璧峰鍏噷鏁扮収鐗�", type: "image", required: false },
+ { key: "estimatedEndMileage", label: "棰勮缁撴潫鍏噷鏁�", type: "number", required: false, min: 0, precision: 1 },
+ { key: "remark", label: "澶囨敞", type: "textarea", required: false, rows: 2 },
+ ],
+ },
+ {
+ key: "vehicle_return",
+ label: "杞﹁締杩樿溅瀹℃壒",
+ summaryPlaceholder: "璇峰~鍐欒溅杈嗚繕杞︿俊鎭�",
+ fields: [
+ { key: "vehicleNo", label: "杞︾墝鍙�", type: "select", required: true, optionSource: "vehicle_list", placeholder: "璇烽�夋嫨杞﹁締" },
+ { key: "driver", label: "椹鹃┒鍛�", type: "text", required: true },
+ { key: "originalApprovalNo", label: "鍘熷鎵瑰崟鍙�", type: "text", required: true },
+ { key: "returnDate", label: "杩樿溅鏃ユ湡", type: "date", required: true },
+ { key: "endMileage", label: "缁撴潫鍏噷鏁�", type: "number", required: true, min: 0, precision: 1 },
+ { key: "endMileagePhoto", label: "缁撴潫鍏噷鏁扮収鐗�", type: "image", required: false },
+ { key: "actualMileage", label: "瀹為檯琛岄┒鍏噷鏁�", type: "number", required: false, min: 0, precision: 1 },
+ { key: "extendDays", label: "寤舵湡澶╂暟", type: "number", required: false, min: 0, precision: 0 },
+ { key: "extendReason", label: "寤舵湡鍘熷洜", type: "textarea", required: false, rows: 2 },
+ { key: "vehicleStatus", label: "杞﹁締鐘舵��", type: "select", required: true, options: [{ label: "瀹屽ソ", value: "good" }, { label: "杞诲井鎹熷潖", value: "minor_damage" }, { label: "闇�瑕佺淮淇�", value: "need_repair" }] },
+ { key: "remark", label: "澶囨敞", type: "textarea", required: false, rows: 2 },
+ ],
+ },
+ {
+ key: "vehicle_extend",
+ label: "杞﹁締寤舵湡瀹℃壒",
+ summaryPlaceholder: "杞﹁締鍒版湡鍚庣敵璇峰欢鏈熶娇鐢�",
+ fields: [
+ { key: "vehicleNo", label: "杞︾墝鍙�", type: "select", required: true, optionSource: "vehicle_list", placeholder: "璇烽�夋嫨杞﹁締" },
+ { key: "driver", label: "椹鹃┒鍛�", type: "text", required: true },
+ { key: "originalApprovalNo", label: "鍘熷鎵瑰崟鍙�", type: "text", required: true },
+ { key: "originalEndDate", label: "鍘熷埌鏈熸棩鏈�", type: "date", required: true },
+ { key: "extendDays", label: "寤舵湡澶╂暟", type: "number", required: true, min: 1, precision: 0 },
+ { key: "newEndDate", label: "鏂板埌鏈熸棩鏈�", type: "date", required: true },
+ { key: "extendReason", label: "寤舵湡鍘熷洜", type: "textarea", required: true, rows: 3 },
+ { key: "remark", label: "澶囨敞", type: "textarea", required: false, rows: 2 },
+ ],
+ },
];
function newFieldUid() {
@@ -88,6 +139,7 @@
if (dv === undefined || dv === null || dv === "") {
if (type === "number") return undefined;
if (type === "datetimerange") return [];
+ if (type === "datetime") return "";
return "";
}
if (type === "number") {
@@ -106,6 +158,7 @@
if (dv === undefined || dv === null) return false;
if (type === "number") return dv !== "" && !Number.isNaN(Number(dv));
if (type === "datetimerange") return Array.isArray(dv) && dv.length === 2;
+ if (type === "datetime") return String(dv).trim() !== "";
if (type === "select") return dv !== "";
return String(dv).trim() !== "";
}
@@ -143,6 +196,7 @@
if (f.defaultValue === undefined || f.defaultValue === null) {
if (type === "number") return undefined;
if (type === "datetimerange") return [];
+ if (type === "datetime") return "";
return "";
}
if (type === "datetimerange" && Array.isArray(f.defaultValue)) {
diff --git a/src/views/officeProcessAutomation/ApproveManage/approve-template/selectOptionSource.js b/src/views/officeProcessAutomation/ApproveManage/approve-template/selectOptionSource.js
index 99706b4..1e19bb6 100644
--- a/src/views/officeProcessAutomation/ApproveManage/approve-template/selectOptionSource.js
+++ b/src/views/officeProcessAutomation/ApproveManage/approve-template/selectOptionSource.js
@@ -1,16 +1,19 @@
import { deptTreeSelect, userListNoPageByTenantId } from "@/api/system/user.js";
+import { listVehiclePage } from "@/api/officeProcessAutomation/vehicle.js";
/** 涓嬫媺閫夐」鏉ユ簮锛堝啓鍏� formConfig锛屾彁浜ら〉鎸夋潵婧愭媺鍙栨暟鎹級 */
export const SELECT_OPTION_SOURCE = {
STATIC: "static",
USER: "user",
DEPT: "dept",
+ VEHICLE: "vehicle",
};
export const SELECT_OPTION_SOURCE_OPTIONS = [
{ value: SELECT_OPTION_SOURCE.STATIC, label: "鎵嬪姩閰嶇疆", desc: "鍦ㄦā鏉夸腑鑷畾涔夐�夐」鏂囨湰涓庡��" },
{ value: SELECT_OPTION_SOURCE.USER, label: "浜哄憳鍒楄〃", desc: "浠庣郴缁熺敤鎴蜂腑閫夋嫨锛屽�间负鐢ㄦ埛 ID" },
{ value: SELECT_OPTION_SOURCE.DEPT, label: "閮ㄩ棬鍒楄〃", desc: "浠庣粍缁囨灦鏋勪腑閫夋嫨锛屽�间负閮ㄩ棬 ID" },
+ { value: SELECT_OPTION_SOURCE.VEHICLE, label: "杞﹁締鍒楄〃", desc: "浠庤溅杈嗙鐞嗕腑閫夋嫨锛屽�间负杞︾墝鍙�" },
];
export function selectOptionSourceLabel(source) {
@@ -18,7 +21,7 @@
}
export function isDynamicOptionSource(source) {
- return source === SELECT_OPTION_SOURCE.USER || source === SELECT_OPTION_SOURCE.DEPT;
+ return source === SELECT_OPTION_SOURCE.USER || source === SELECT_OPTION_SOURCE.DEPT || source === SELECT_OPTION_SOURCE.VEHICLE;
}
function unwrapArray(payload) {
@@ -69,6 +72,14 @@
});
}
+/** 杞﹁締 鈫� 涓嬫媺 option */
+export function mapVehicleToSelectOption(v) {
+ return {
+ label: v.plateNumber || `杞﹁締${v.id}`,
+ value: v.plateNumber || String(v.id),
+ };
+}
+
/** 鎸夊瓧娈甸厤缃В鏋愪笅鎷� options锛堥渶浼犲叆宸插姞杞界殑缂撳瓨锛� */
export function resolveFieldSelectOptions(field, caches = {}) {
const source = field?.optionSource || SELECT_OPTION_SOURCE.STATIC;
@@ -77,6 +88,9 @@
}
if (source === SELECT_OPTION_SOURCE.DEPT) {
return caches.deptOptions || [];
+ }
+ if (source === SELECT_OPTION_SOURCE.VEHICLE) {
+ return (caches.vehicles || []).map(mapVehicleToSelectOption);
}
return (field?.options || []).filter((o) => o.value !== "" && o.value != null);
}
@@ -89,13 +103,14 @@
return hit?.label || String(val);
}
-/** 鍔犺浇浜哄憳 / 閮ㄩ棬缂撳瓨锛堝澶勫鐢級 */
+/** 鍔犺浇浜哄憳 / 閮ㄩ棬 / 杞﹁締缂撳瓨锛堝澶勫鐢級 */
export async function fetchSelectOptionCaches(sources = []) {
const needUser = sources.includes(SELECT_OPTION_SOURCE.USER);
const needDept = sources.includes(SELECT_OPTION_SOURCE.DEPT);
- const caches = { users: [], deptOptions: [] };
+ const needVehicle = sources.includes(SELECT_OPTION_SOURCE.VEHICLE);
+ const caches = { users: [], deptOptions: [], vehicles: [] };
- if (!needUser && !needDept) return caches;
+ if (!needUser && !needDept && !needVehicle) return caches;
const tasks = [];
if (needUser) {
@@ -123,6 +138,19 @@
})
);
}
+ if (needVehicle) {
+ tasks.push(
+ listVehiclePage({ current: 1, size: 1000 })
+ .then((res) => {
+ // 杞﹁締鎺ュ彛杩斿洖鏍煎紡: { data: { records: [...], total: ... } }
+ const records = res?.data?.records;
+ caches.vehicles = Array.isArray(records) ? records : [];
+ })
+ .catch(() => {
+ caches.vehicles = [];
+ })
+ );
+ }
await Promise.all(tasks);
return caches;
diff --git a/src/views/officeProcessAutomation/ApproveManage/approve-template/useSelectOptionSources.js b/src/views/officeProcessAutomation/ApproveManage/approve-template/useSelectOptionSources.js
index 8397288..98da303 100644
--- a/src/views/officeProcessAutomation/ApproveManage/approve-template/useSelectOptionSources.js
+++ b/src/views/officeProcessAutomation/ApproveManage/approve-template/useSelectOptionSources.js
@@ -6,12 +6,13 @@
resolveSelectDisplayLabel,
} from "./selectOptionSource.js";
-/** 涓嬫媺鍔ㄦ�侀�夐」锛氫汉鍛� / 閮ㄩ棬缂撳瓨涓庤В鏋� */
+/** 涓嬫媺鍔ㄦ�侀�夐」锛氫汉鍛� / 閮ㄩ棬 / 杞﹁締缂撳瓨涓庤В鏋� */
export function useSelectOptionSources() {
const loading = ref(false);
const caches = reactive({
users: [],
deptOptions: [],
+ vehicles: [],
});
async function ensureForFields(fields) {
@@ -22,6 +23,7 @@
const next = await fetchSelectOptionCaches(sources);
caches.users = next.users;
caches.deptOptions = next.deptOptions;
+ caches.vehicles = next.vehicles;
} finally {
loading.value = false;
}
diff --git a/src/views/officeProcessAutomation/ApproveManage/vehicle-apply/index.vue b/src/views/officeProcessAutomation/ApproveManage/vehicle-apply/index.vue
new file mode 100644
index 0000000..3d18260
--- /dev/null
+++ b/src/views/officeProcessAutomation/ApproveManage/vehicle-apply/index.vue
@@ -0,0 +1,1066 @@
+<!--OA妯″潡锛氳溅杈嗗鎵圭鐞�-->
+<template>
+ <div class="app-container">
+ <!-- 杞﹁締绠$悊鍖哄煙 -->
+ <el-card class="vehicle-manage-card mb20">
+ <template #header>
+ <div class="card-header">
+ <span>杞﹁締绠$悊</span>
+ <el-button type="primary" size="small" @click="openVehicleDialog()">鏂板杞﹁締</el-button>
+ </div>
+ </template>
+ <el-table :data="vehicleList" border size="small" style="width: 100%" v-loading="vehicleLoading">
+ <el-table-column type="index" label="搴忓彿" width="60" align="center" />
+ <el-table-column prop="plateNumber" label="杞︾墝鍙�" min-width="120" align="center" />
+ <el-table-column label="浣跨敤鐘舵��" width="100" align="center">
+ <template #default="scope">
+ <el-tag :type="vehicleStatusTagType(scope.row.status)" size="small">
+ {{ vehicleStatusLabel(scope.row.status) }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="currentMileage" label="褰撳墠鍏噷鏁�" width="120" align="center" />
+ <el-table-column label="鎿嶄綔" width="150" align="center" fixed="right">
+ <template #default="scope">
+ <el-button link type="primary" size="small" @click="openVehicleDialog(scope.row)">缂栬緫</el-button>
+ <el-button link type="danger" size="small" @click="handleDeleteVehicle(scope.row)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ <div class="pagination-container" style="margin-top: 10px;">
+ <el-pagination
+ v-model:current-page="vehiclePage.current"
+ v-model:page-size="vehiclePage.size"
+ :page-sizes="[10, 20, 50]"
+ layout="total, sizes, prev, pager, next"
+ :total="vehiclePage.total"
+ @size-change="fetchVehicleList"
+ @current-change="fetchVehicleList"
+ size="small"
+ />
+ </div>
+ </el-card>
+
+ <!-- 鍊熷嚭璁板綍鍒楄〃 -->
+ <div class="search_form mb20">
+ <div>
+ <span class="search_title">瀹℃壒鍗曞彿锛�</span>
+ <el-input
+ v-model="searchForm.instanceNo"
+ style="width: 220px"
+ placeholder="璇疯緭鍏ュ鎵瑰崟鍙�"
+ clearable
+ @keyup.enter="onSearch"
+ />
+ <span class="search_title" style="margin-left: 12px">鐢宠浜猴細</span>
+ <el-input
+ v-model="searchForm.applicantKeyword"
+ style="width: 220px"
+ placeholder="濮撳悕鎴栫紪鍙�"
+ clearable
+ :prefix-icon="Search"
+ @keyup.enter="onSearch"
+ />
+ <span class="search_title" style="margin-left: 12px">杞︾墝鍙凤細</span>
+ <el-select
+ v-model="searchForm.vehiclePlateNumber"
+ style="width: 180px"
+ placeholder="璇烽�夋嫨杞︾墝鍙�"
+ clearable
+ filterable
+ >
+ <el-option
+ v-for="item in vehicleList"
+ :key="item.id"
+ :label="item.plateNumber"
+ :value="item.plateNumber"
+ />
+ </el-select>
+ <el-button type="primary" style="margin-left: 10px" @click="onSearch">鎼滅储</el-button>
+ <el-button @click="resetSearch">閲嶇疆</el-button>
+ </div>
+ <div>
+ <el-button type="danger" @click="batchDeleteBorrow" :disabled="!selectedBorrowRows?.length">鎵归噺鍒犻櫎</el-button>
+ <el-button type="primary" @click="openAddWithTemplate">鏂板鍊熷嚭鐢宠</el-button>
+ </div>
+ </div>
+ <div class="table_list">
+ <PIMTable
+ rowKey="id"
+ :column="borrowTableColumn"
+ :tableData="borrowTableData"
+ :page="borrowPage"
+ :isSelection="true"
+ :tableLoading="borrowTableLoading"
+ @pagination="onBorrowPagination"
+ @selection-change="(rows) => selectedBorrowRows = rows"
+ :total="borrowPage.total"
+ />
+ </div>
+
+ <!-- 鍊熷嚭鐢宠鎻愪氦瀵硅瘽妗� -->
+ <ApprovalInstanceSubmitDialog
+ v-model="submitDialog.visible"
+ :title="submitDialogTitle"
+ :form="submitForm"
+ :rules="submitFormRules"
+ :fields="submitFormFields"
+ :active-template="activeTemplate"
+ :user-options="flowUserOptions"
+ :is-edit="isSubmitEdit"
+ :saving="submitSaving"
+ :form-ref="submitFormRef"
+ flow-attachments-only
+ @submit="onSubmit"
+ >
+ <template #before="{ form, fields }">
+ <FormPayloadFields :fields="displayTemplateFields(fields)" :form-payload="form.formPayload" />
+ <!-- 杞﹁締浣跨敤淇℃伅灞曠ず -->
+ <el-row :gutter="24" v-if="vehicleUseInfoDisplay(form)">
+ <el-col :span="24">
+ <el-form-item label="杞﹁締鐘舵��">
+ <el-tag :type="vehicleStatusType(form.formPayload?.vehicleNo)">
+ {{ vehicleStatusText(form.formPayload?.vehicleNo) }}
+ </el-tag>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </template>
+ </ApprovalInstanceSubmitDialog>
+
+ <ApprovalTemplateBindDialog
+ v-model:visible="templateBindVisible"
+ :module-key="APPROVAL_MODULE_KEYS.VEHICLE"
+ skip-form-confirm
+ @confirm="onTemplateBound"
+ @closed="onTemplateBindClosed"
+ />
+
+ <ApprovalInstanceDetailDialog
+ v-model="detailDialog.visible"
+ title="杞﹁締瀹℃壒璇︽儏"
+ :row="detailRow"
+ @edit="openEditFromDetail"
+ />
+
+ <!-- 杞﹁締绠$悊瀵硅瘽妗� -->
+ <el-dialog
+ v-model="vehicleDialog.visible"
+ :title="vehicleDialog.isEdit ? '缂栬緫杞﹁締' : '鏂板杞﹁締'"
+ width="500px"
+ :close-on-click-modal="false"
+ >
+ <el-form :model="vehicleForm" :rules="vehicleRules" ref="vehicleFormRef" label-width="100px">
+ <el-form-item label="杞︾墝鍙�" prop="plateNumber">
+ <el-input v-model="vehicleForm.plateNumber" placeholder="璇疯緭鍏ヨ溅鐗屽彿" />
+ </el-form-item>
+ <el-form-item label="浣跨敤鐘舵��" prop="status">
+ <el-select v-model="vehicleForm.status" placeholder="璇烽�夋嫨浣跨敤鐘舵��" style="width: 100%">
+ <el-option label="闂茬疆" value="idle" />
+ <el-option label="浣跨敤涓�" value="in_use" />
+ <el-option label="缁翠慨涓�" value="repair" />
+ <el-option label="淇濆吇涓�" value="maintenance" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="褰撳墠鍏噷鏁�" prop="currentMileage">
+ <el-input-number
+ v-model="vehicleForm.currentMileage"
+ :min="0"
+ :precision="1"
+ :step="0.1"
+ controls-position="right"
+ style="width: 100%"
+ placeholder="璇疯緭鍏ュ綋鍓嶅叕閲屾暟"
+ />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button @click="vehicleDialog.visible = false">鍙栨秷</el-button>
+ <el-button type="primary" :loading="vehicleSaveLoading" @click="saveVehicle">淇濆瓨</el-button>
+ </template>
+ </el-dialog>
+
+ <!-- 寤舵湡鐢宠瀵硅瘽妗嗭紙浣跨敤瀹℃壒妯℃澘锛� -->
+ <ApprovalInstanceSubmitDialog
+ v-model="extendSubmitDialog.visible"
+ :title="extendSubmitDialogTitle"
+ :form="extendSubmitForm"
+ :rules="extendSubmitFormRules"
+ :fields="extendSubmitFormFields"
+ :active-template="extendActiveTemplate"
+ :user-options="flowUserOptions"
+ :is-edit="false"
+ :saving="extendSubmitSaving"
+ :form-ref="extendSubmitFormRef"
+ flow-attachments-only
+ @submit="onExtendSubmit"
+ >
+ <template #before="{ form, fields }">
+ <FormPayloadFields :fields="fields" :form-payload="form.formPayload" />
+ <!-- 寤舵湡淇℃伅灞曠ず -->
+ <el-row :gutter="24">
+ <el-col :span="12">
+ <el-form-item label="鍘熷鎵瑰崟鍙�">
+ <el-input v-model="extendSourceRow.instanceNo" disabled />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="杞︾墝鍙�">
+ <el-input v-model="extendSourceRow.vehiclePlateNumber" disabled />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </template>
+ </ApprovalInstanceSubmitDialog>
+
+ <ApprovalTemplateBindDialog
+ v-model:visible="extendTemplateBindVisible"
+ :module-key="APPROVAL_MODULE_KEYS.VEHICLE_DELAY"
+ skip-form-confirm
+ @confirm="onExtendTemplateBound"
+ @closed="onExtendTemplateBindClosed"
+ />
+
+ <!-- 褰掕繕杞﹁締瀵硅瘽妗� -->
+ <el-dialog
+ v-model="returnDialog.visible"
+ title="褰掕繕杞﹁締"
+ width="600px"
+ :close-on-click-modal="false"
+ >
+ <el-form :model="returnForm" :rules="returnRules" ref="returnFormRef" label-width="120px">
+ <el-form-item label="鍊熷嚭鍗曞彿">
+ <el-input v-model="returnForm.instanceNo" disabled />
+ </el-form-item>
+ <el-form-item label="杞︾墝鍙�">
+ <el-input v-model="returnForm.vehiclePlateNumber" disabled />
+ </el-form-item>
+ <el-form-item label="瀹為檯褰掕繕鏃堕棿" prop="actualReturnTime" required>
+ <el-date-picker
+ v-model="returnForm.actualReturnTime"
+ type="datetime"
+ placeholder="璇烽�夋嫨瀹為檯褰掕繕鏃堕棿"
+ format="YYYY-MM-DD HH:mm:ss"
+ value-format="YYYY-MM-DD HH:mm:ss"
+ style="width: 100%"
+ />
+ </el-form-item>
+ <el-form-item label="闄勪欢">
+ <AttachmentUploadImage
+ v-model:fileList="returnForm.returnStorageBlobDTOs"
+ :limit="5"
+ button-text="涓婁紶褰掕繕闄勪欢"
+ />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button type="primary" :loading="returnLoading" @click="submitReturn">纭褰掕繕</el-button>
+ <el-button @click="returnDialog.visible = false">鍙栨秷</el-button>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { Search } from "@element-plus/icons-vue";
+import dayjs from "dayjs";
+import { ElMessage, ElMessageBox } from "element-plus";
+import { computed, onMounted, reactive, ref } from "vue";
+import AttachmentUploadImage from "@/components/AttachmentUpload/image/index.vue";
+import {
+ deleteVehicle,
+ listVehiclePage,
+ saveVehicle as saveVehicleApi,
+ updateVehicle,
+ saveBorrow,
+ listBorrowPage,
+ deleteBorrow,
+} from "@/api/officeProcessAutomation/vehicle.js";
+import FormPayloadFields from "../approve-list/components/FormPayloadFields.vue";
+import ApprovalInstanceDetailDialog from "../approve-shared/components/ApprovalInstanceDetailDialog.vue";
+import ApprovalInstanceSubmitDialog from "../approve-shared/components/ApprovalInstanceSubmitDialog.vue";
+import ApprovalTemplateBindDialog from "../approve-shared/components/ApprovalTemplateBindDialog.vue";
+import { buildInstanceTableColumns } from "../approve-shared/approvalInstanceFormConfigTable.js";
+import { APPROVAL_MODULE_KEYS } from "../approve-shared/approvalModuleRegistry.js";
+import { useApprovalInstanceModule } from "../approve-shared/useApprovalInstanceModule.js";
+import { useFlowUserOptions } from "../approve-shared/useFlowUserOptions.js";
+
+// ==================== 杞﹁締绠$悊 ====================
+const vehicleList = ref([]);
+const vehicleLoading = ref(false);
+const vehicleSaveLoading = ref(false);
+const vehiclePage = reactive({
+ current: 1,
+ size: 10,
+ total: 0,
+});
+
+const vehicleDialog = reactive({
+ visible: false,
+ isEdit: false,
+});
+const vehicleFormRef = ref(null);
+const vehicleForm = reactive({
+ id: null,
+ plateNumber: "",
+ status: "idle",
+ currentMileage: 0,
+});
+const vehicleRules = {
+ plateNumber: [{ required: true, message: "璇疯緭鍏ヨ溅鐗屽彿", trigger: "blur" }],
+ status: [{ required: true, message: "璇烽�夋嫨浣跨敤鐘舵��", trigger: "change" }],
+};
+
+// 鑾峰彇杞﹁締鍒楄〃
+async function fetchVehicleList() {
+ vehicleLoading.value = true;
+ try {
+ const res = await listVehiclePage({
+ current: vehiclePage.current,
+ size: vehiclePage.size,
+ });
+ if (res.code === 200) {
+ vehicleList.value = (res.data?.records || []).map((item) => ({
+ id: item.id,
+ plateNumber: item.plateNumber,
+ status: mapVehicleStatusFromApi(item.status),
+ currentMileage: item.mileage || 0,
+ }));
+ vehiclePage.total = res.data?.total || 0;
+ }
+ } catch (error) {
+ ElMessage.error("鑾峰彇杞﹁締鍒楄〃澶辫触");
+ } finally {
+ vehicleLoading.value = false;
+ }
+}
+
+// 鐘舵�佹槧灏�
+function mapVehicleStatusFromApi(apiStatus) {
+ const statusMap = {
+ IDLE: "idle",
+ IN_USE: "in_use",
+ MAINTENANCE: "repair",
+ SCRAPPED: "archived",
+ };
+ return statusMap[apiStatus] || "idle";
+}
+
+function mapVehicleStatusToApi(frontendStatus) {
+ const statusMap = {
+ idle: "IDLE",
+ in_use: "IN_USE",
+ repair: "MAINTENANCE",
+ archived: "SCRAPPED",
+ };
+ return statusMap[frontendStatus] || "IDLE";
+}
+
+function vehicleStatusTagType(status) {
+ const typeMap = {
+ idle: "success",
+ in_use: "warning",
+ repair: "danger",
+ maintenance: "info",
+ };
+ return typeMap[status] || "info";
+}
+
+function vehicleStatusLabel(status) {
+ const labelMap = {
+ idle: "闂茬疆",
+ in_use: "浣跨敤涓�",
+ repair: "缁翠慨涓�",
+ maintenance: "淇濆吇涓�",
+ };
+ return labelMap[status] || status;
+}
+
+// 杞﹁締绠$悊瀵硅瘽妗�
+function openVehicleDialog(row = null) {
+ vehicleDialog.isEdit = !!row;
+ if (row) {
+ vehicleForm.id = row.id;
+ vehicleForm.plateNumber = row.plateNumber;
+ vehicleForm.status = row.status;
+ vehicleForm.currentMileage = row.currentMileage;
+ } else {
+ vehicleForm.id = null;
+ vehicleForm.plateNumber = "";
+ vehicleForm.status = "idle";
+ vehicleForm.currentMileage = 0;
+ }
+ vehicleDialog.visible = true;
+}
+
+// 淇濆瓨杞﹁締
+async function saveVehicle() {
+ if (!vehicleFormRef.value) return;
+ await vehicleFormRef.value.validate(async (valid) => {
+ if (!valid) return;
+ vehicleSaveLoading.value = true;
+ try {
+ const apiData = {
+ plateNumber: vehicleForm.plateNumber,
+ mileage: vehicleForm.currentMileage,
+ status: mapVehicleStatusToApi(vehicleForm.status),
+ };
+
+ if (vehicleDialog.isEdit) {
+ await updateVehicle({ ...apiData, id: vehicleForm.id });
+ ElMessage.success("杞﹁締淇℃伅宸叉洿鏂�");
+ } else {
+ await saveVehicleApi(apiData);
+ ElMessage.success("杞﹁締宸叉坊鍔�");
+ }
+ vehicleDialog.visible = false;
+ fetchVehicleList();
+ } catch (error) {
+ ElMessage.error(error?.message || "淇濆瓨澶辫触");
+ } finally {
+ vehicleSaveLoading.value = false;
+ }
+ });
+}
+
+// 鍒犻櫎杞﹁締
+async function handleDeleteVehicle(row) {
+ try {
+ await ElMessageBox.confirm(`纭畾瑕佸垹闄よ溅杈� ${row.plateNumber} 鍚楋紵`, "鎻愮ず", {
+ type: "warning",
+ });
+ await deleteVehicle([row.id]);
+ ElMessage.success("杞﹁締宸插垹闄�");
+ fetchVehicleList();
+ } catch {
+ // 鐢ㄦ埛鍙栨秷
+ }
+}
+
+// ==================== 鍊熷嚭鐢宠锛堜娇鐢ㄥ鎵规ā鏉匡級 ====================
+
+// 鏌ユ壘杞﹁締浣跨敤鏃堕棿瀛楁锛堟敮鎸� datetimerange 鍜� datetime 绫诲瀷锛�
+function findVehicleUseTimeField(fields = []) {
+ return (
+ fields.find((f) => (f?.type === "datetimerange" || f?.type === "datetime") && String(f?.label || "").includes("杞﹁締浣跨敤鏃堕棿")) ||
+ fields.find((f) => f?.type === "datetimerange" && f?.key === "useDateRange") ||
+ fields.find((f) => f?.type === "datetimerange") ||
+ null
+ );
+}
+
+// 鏌ユ壘杞︾墝鍙峰瓧娈�
+function findVehicleNoField(fields = []) {
+ return (
+ fields.find((f) => String(f?.label || "").includes("杞︾墝鍙�")) ||
+ fields.find((f) => f?.key === "vehicleNo") ||
+ null
+ );
+}
+
+// 瑙f瀽鏃堕棿鑼冨洿
+function resolveTimeRange(payload, timeField) {
+ if (!timeField?.key) return { start: "", end: "" };
+ const val = payload?.[timeField.key];
+ if (!Array.isArray(val) || val.length < 2) return { start: "", end: "" };
+ return { start: val[0] || "", end: val[1] || "" };
+}
+
+// 璁$畻澶╂暟
+function computeDays(startStr, endStr) {
+ if (!startStr || !endStr) return null;
+ const t0 = dayjs(startStr);
+ const t1 = dayjs(endStr);
+ if (!t0.isValid() || !t1.isValid() || !t1.isAfter(t0)) return null;
+ const days = t1.diff(t0, "millisecond") / (24 * 60 * 60 * 1000);
+ return Math.round(days * 100) / 100;
+}
+
+// 鏄剧ず妯℃澘瀛楁锛堣繃婊ゆ帀宸插湪鑷畾涔夊尯鍩熸樉绀虹殑瀛楁锛�
+function displayTemplateFields(fields = []) {
+ return (fields || []).filter((f) => {
+ const label = String(f?.label || "");
+ return !label.includes("棰勮浣跨敤澶╂暟") && !label.includes("杞﹁締鐘舵��");
+ });
+}
+
+// 杞﹁締浣跨敤淇℃伅鏄剧ず
+function vehicleUseInfoDisplay(form) {
+ const vehicleNoField = findVehicleNoField(form.formFieldDefs);
+ return !!vehicleNoField?.key && !!form.formPayload?.[vehicleNoField?.key];
+}
+
+// 杞﹁締浣跨敤鏃堕暱鏄剧ず
+function vehicleDurationDisplay(form) {
+ const useTimeField = findVehicleUseTimeField(form.formFieldDefs);
+ const { start, end } = resolveTimeRange(form.formPayload, useTimeField);
+ const d = computeDays(start, end);
+ return d == null ? "" : String(d);
+}
+
+// 杞﹁締鐘舵�佹爣绛剧被鍨�
+function vehicleStatusType(vehicleNo) {
+ const vehicle = vehicleList.value.find((v) => v.plateNumber === vehicleNo);
+ if (!vehicle) return "info";
+ const typeMap = {
+ idle: "success",
+ in_use: "warning",
+ repair: "danger",
+ maintenance: "info",
+ };
+ return typeMap[vehicle.status] || "info";
+}
+
+// 杞﹁締鐘舵�佹爣绛炬枃瀛�
+function vehicleStatusText(vehicleNo) {
+ const vehicle = vehicleList.value.find((v) => v.plateNumber === vehicleNo);
+ if (!vehicle) return "鏈煡";
+ const labelMap = {
+ idle: "闂茬疆",
+ in_use: "浣跨敤涓�",
+ repair: "缁翠慨涓�",
+ maintenance: "淇濆吇涓�",
+ };
+ return labelMap[vehicle.status] || vehicle.status;
+}
+
+// 楠岃瘉杞﹁締浣跨敤鏃堕棿锛堢洰鍓嶄笉鍋氭椂闂村厛鍚庢牎楠岋紝鐢卞悗绔鐞嗭級
+function validateVehicleUseTime() {
+ // 鏃堕棿鏍¢獙宸茬Щ闄�
+}
+
+const searchForm = reactive({
+ instanceNo: "",
+ applicantKeyword: "",
+ vehiclePlateNumber: "",
+});
+
+// ==================== 鍊熷嚭璁板綍鍒楄〃鏌ヨ ====================
+const borrowTableData = ref([]);
+const borrowTableLoading = ref(false);
+const borrowPage = reactive({
+ current: 1,
+ size: 10,
+ total: 0,
+});
+const selectedBorrowRows = ref([]);
+
+// 鏌ヨ鍊熷嚭璁板綍鍒楄〃
+async function fetchBorrowList() {
+ borrowTableLoading.value = true;
+ try {
+ const res = await listBorrowPage({
+ current: borrowPage.current,
+ size: borrowPage.size,
+ borrowNo: searchForm.instanceNo,
+ applicantName: searchForm.applicantKeyword,
+ vehiclePlateNumber: searchForm.vehiclePlateNumber,
+ });
+ if (res.code === 200) {
+ borrowTableData.value = res.data?.records || [];
+ borrowPage.total = res.data?.total || 0;
+ }
+ } catch (error) {
+ ElMessage.error(error?.message || "鏌ヨ澶辫触");
+ } finally {
+ borrowTableLoading.value = false;
+ }
+}
+
+// 鍊熷嚭璁板綍鍒嗛〉
+function onBorrowPagination(obj) {
+ borrowPage.current = obj.page;
+ borrowPage.size = obj.limit;
+ fetchBorrowList();
+}
+
+// 鎵归噺鍒犻櫎鍊熷嚭璁板綍
+async function batchDeleteBorrow() {
+ if (!selectedBorrowRows.value?.length) {
+ ElMessage.warning("璇烽�夋嫨瑕佸垹闄ょ殑璁板綍");
+ return;
+ }
+ try {
+ await ElMessageBox.confirm("纭畾鍒犻櫎閫変腑鐨勫�熷嚭璁板綍鍚楋紵", "鎻愮ず", {
+ type: "warning",
+ });
+ const ids = selectedBorrowRows.value.map(row => row.id);
+ const res = await deleteBorrow(ids);
+ if (res.code === 200) {
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ selectedBorrowRows.value = [];
+ fetchBorrowList();
+ } else {
+ ElMessage.error(res.msg || "鍒犻櫎澶辫触");
+ }
+ } catch (error) {
+ if (error !== "cancel") {
+ ElMessage.error(error?.message || "鍒犻櫎澶辫触");
+ }
+ }
+}
+
+const mod = useApprovalInstanceModule({
+ moduleKey: APPROVAL_MODULE_KEYS.VEHICLE,
+ beforeSave: validateVehicleUseTime,
+});
+
+const {
+ tableData,
+ tableLoading,
+ page,
+ detailDialog,
+ detailRow,
+ submitDialog,
+ submitForm,
+ submitFormRef,
+ submitSaving,
+ isSubmitEdit,
+ activeTemplate,
+ submitFormFields,
+ submitFormRules,
+ submitDialogTitle,
+ templateBindVisible,
+ handleQuery,
+ initModuleList,
+ pagination,
+ openAddWithTemplate,
+ onTemplateBound,
+ onTemplateBindClosed,
+ openEditFromDetail,
+ submitInstanceForm,
+ buildTableActions,
+} = mod;
+
+const { flowUserOptions } = useFlowUserOptions();
+
+// 鍒ゆ柇鏄惁杩囦簡璁″垝褰掕繕鏃堕棿锛堟槸鍚﹀彲浠ュ欢鏈燂級
+function canExtend(row) {
+ if (!["pending", "approved"].includes(row?.approvalStatus) || row?.businessType !== 19) {
+ return false;
+ }
+ // 浠庤溅杈嗕娇鐢ㄦ椂闂村瓧娈佃幏鍙栬鍒掑綊杩樻椂闂�
+ const useTimeField = findVehicleUseTimeField(row.formFieldDefs || []);
+ if (useTimeField?.key && row.formPayload?.[useTimeField.key]) {
+ const timeRange = row.formPayload[useTimeField.key];
+ if (Array.isArray(timeRange) && timeRange.length >= 2) {
+ const plannedReturnTime = dayjs(timeRange[1]);
+ // 杩囦簡璁″垝褰掕繕鏃堕棿鎵嶈兘寤舵湡
+ return dayjs().isAfter(plannedReturnTime);
+ }
+ }
+ return false;
+}
+
+// 鍊熷嚭鐘舵�佹爣绛剧被鍨�
+function borrowStatusTagType(status) {
+ const typeMap = {
+ DRAFT: "info",
+ IN_APPROVAL: "warning",
+ BORROWING: "success",
+ RETURNED: "",
+ REJECTED: "danger",
+ };
+ return typeMap[status] || "info";
+}
+
+// 鍊熷嚭鐘舵�佹枃鏈�
+function borrowStatusLabel(status) {
+ const labelMap = {
+ DRAFT: "鑽夌",
+ IN_APPROVAL: "瀹℃壒涓�",
+ BORROWING: "鍊熷嚭涓�",
+ RETURNED: "宸插綊杩�",
+ REJECTED: "宸查┏鍥�",
+ };
+ return labelMap[status] || status;
+}
+
+// 寤舵湡鐘舵�佹爣绛剧被鍨�
+function extendStatusTagType(status) {
+ const typeMap = {
+ NONE: "info",
+ PENDING: "warning",
+ APPROVED: "success",
+ REJECTED: "danger",
+ };
+ return typeMap[status] || "info";
+}
+
+// 寤舵湡鐘舵�佹枃鏈�
+function extendStatusLabel(status) {
+ const labelMap = {
+ NONE: "鏈敵璇�",
+ PENDING: "瀹℃壒涓�",
+ APPROVED: "宸查�氳繃",
+ REJECTED: "宸查┏鍥�",
+ };
+ return labelMap[status] || status;
+}
+
+// 鍒ゆ柇鏄惁鍙互寤舵湡锛堝�熷嚭涓笖鏈敵璇峰欢鏈熸垨寤舵湡宸查┏鍥烇級
+function canExtendBorrow(row) {
+ if (row.borrowStatus !== "BORROWING") return false;
+ if (row.extendStatus === "PENDING" || row.extendStatus === "APPROVED") return false;
+ // 杩囦簡璁″垝褰掕繕鏃堕棿鎵嶈兘寤舵湡
+ if (row.plannedReturnTime) {
+ return dayjs().isAfter(dayjs(row.plannedReturnTime));
+ }
+ return false;
+}
+
+// 鍒ゆ柇鏄惁鍙互褰掕繕锛堝�熷嚭涓級
+function canReturnBorrow(row) {
+ return row.borrowStatus === "BORROWING";
+}
+
+// 鏋勫缓鍊熷嚭璁板綍琛ㄦ牸鍒�
+const borrowTableColumn = computed(() => [
+ { label: "鍊熷嚭鍗曞彿", prop: "borrowNo", minWidth: 160, align: "center" },
+ { label: "杞︾墝鍙�", prop: "vehiclePlateNumber", minWidth: 120, align: "center" },
+ { label: "鐢宠浜�", prop: "applicantName", minWidth: 100, align: "center" },
+ { label: "鍊熷嚭鍘熷洜", prop: "borrowReason", minWidth: 150, align: "center", showOverflowTooltip: true },
+ { label: "鍊熷嚭鏃堕棿", prop: "borrowStartTime", width: 160, align: "center" },
+ { label: "璁″垝褰掕繕", prop: "plannedReturnTime", width: 160, align: "center" },
+ { label: "瀹為檯褰掕繕", prop: "actualReturnTime", width: 160, align: "center" },
+ {
+ label: "鍊熷嚭鐘舵��",
+ prop: "borrowStatus",
+ width: 100,
+ align: "center",
+ dataType: "tag",
+ formatData: borrowStatusLabel,
+ formatType: borrowStatusTagType,
+ },
+ {
+ label: "寤舵湡鐘舵��",
+ prop: "extendStatus",
+ width: 100,
+ align: "center",
+ dataType: "tag",
+ formatData: extendStatusLabel,
+ formatType: extendStatusTagType,
+ },
+ { label: "鍒涘缓鏃堕棿", prop: "createTime", width: 160, align: "center" },
+ {
+ dataType: "action",
+ label: "鎿嶄綔",
+ align: "center",
+ fixed: "right",
+ width: 150,
+ operation: [
+ {
+ name: "寤舵湡",
+ type: "text",
+ disabled: (row) => !canExtendBorrow(row),
+ clickFun: (row) => openExtendDialog(row),
+ },
+ {
+ name: "褰掕繕",
+ type: "text",
+ disabled: (row) => !canReturnBorrow(row),
+ clickFun: (row) => openReturnDialog(row),
+ },
+ ],
+ },
+]);
+
+async function onSearch() {
+ // 鍚屾椂鍒锋柊杞﹁締鍒楄〃锛堢敤浜庤溅鐗屽彿涓嬫媺閫夋嫨锛�
+ await fetchVehicleList();
+ // 浣跨敤鍊熷嚭璁板綍鏌ヨ鎺ュ彛
+ fetchBorrowList();
+}
+
+function resetSearch() {
+ searchForm.instanceNo = "";
+ searchForm.applicantKeyword = "";
+ searchForm.vehiclePlateNumber = "";
+ onSearch();
+}
+
+function onPagination(obj) {
+ pagination(obj, searchForm);
+}
+
+async function onSubmit() {
+ // 鏂板鍊熷嚭鐢宠锛屽彧璋冪敤 saveBorrow 鎺ュ彛
+ if (!isSubmitEdit.value) {
+ try {
+ // 浠庤〃鍗曟暟鎹腑鑾峰彇鍊熷嚭淇℃伅
+ const formPayload = submitForm.formPayload || {};
+ const fields = submitForm.formFieldDefs || [];
+
+ // 鏌ユ壘杞﹁締閫夋嫨瀛楁锛堝厛灏濊瘯浣跨敤椤圭洰涓凡鏈夌殑鏌ユ壘鏂瑰紡锛屽啀鍏煎鐩存帴鍙栧�硷級
+ const vehicleField = findVehicleNoField(fields);
+
+ // 杞﹁締閫夋嫨瀛楁鐨勫�兼槸杞︾墝鍙凤紝闇�瑕佷粠vehicleList涓牴鎹溅鐗屽彿鏌ユ壘瀵瑰簲鐨勮溅杈咺D
+ // 浼樺厛浠� formPayload.vehiclePlateNumber 鑾峰彇锛屽鏋滀笉瀛樺湪鍒欓�氳繃瀛楁key鑾峰彇
+ const selectedPlateNumber = formPayload.vehiclePlateNumber ||
+ (vehicleField?.key ? formPayload[vehicleField.key] : null);
+
+ let vehicleId = null;
+ if (selectedPlateNumber) {
+ const selectedVehicle = vehicleList.value.find(v => v.plateNumber === selectedPlateNumber);
+ vehicleId = selectedVehicle?.id;
+ }
+
+ // 鐩存帴浣跨敤琛ㄥ崟涓殑鏃堕棿瀛楁
+ const borrowStartTime = formPayload.borrowStartTime;
+ const plannedReturnTime = formPayload.plannedReturnTime;
+
+ if (!vehicleId) {
+ ElMessage.warning("璇烽�夋嫨杞﹁締");
+ return;
+ }
+ if (!borrowStartTime || !plannedReturnTime) {
+ ElMessage.warning("璇烽�夋嫨杞﹁締浣跨敤鏃堕棿");
+ return;
+ }
+
+ // 鍙皟鐢� saveBorrow 鎺ュ彛锛屼笉鍐嶈皟鐢ㄥ鎵瑰疄渚嬫彁浜�
+ // formConfig 鏄ā鏉胯鎯呮帴鍙h繑鍥炵殑鍘熷 JSON 瀛楃涓�
+ console.log('=== submitForm debug ===', {
+ templateId: submitForm.templateId,
+ templateKey: submitForm.templateKey,
+ templateSnapshotTemplateId: submitForm.templateSnapshot?.templateId,
+ formConfig: submitForm.formConfig || submitForm.templateSnapshot?.formConfig || activeTemplate.value?.formConfig,
+ 'formConfig type': typeof submitForm.formConfig,
+ });
+ const submitData = {
+ vehicleId: vehicleId,
+ borrowReason: formPayload.borrowReason || formPayload.reason || '',
+ borrowStartTime: borrowStartTime,
+ plannedReturnTime: plannedReturnTime,
+ borrowStatus: 'IN_APPROVAL',
+ approvalTemplateId: submitForm.templateId || submitForm.templateSnapshot?.templateId || submitForm.templateKey,
+ borrowStorageBlobDTOs: submitForm.storageBlobDTOs || [],
+ formConfig: submitForm.formConfig,
+ };
+ console.log('=== saveBorrow submitData ===', JSON.parse(JSON.stringify(submitData)));
+ const borrowRes = await saveBorrow(submitData);
+
+ if (borrowRes.code !== 200) {
+ ElMessage.error(borrowRes.msg || "淇濆瓨鍊熷嚭璁板綍澶辫触");
+ return;
+ }
+
+ ElMessage.success("鎻愪氦鎴愬姛");
+ submitDialog.visible = false;
+ fetchBorrowList();
+ } catch (error) {
+ ElMessage.error(error?.message || "淇濆瓨鍊熷嚭璁板綍澶辫触");
+ }
+ } else {
+ // 缂栬緫妯″紡锛岃皟鐢ㄥ鎵瑰疄渚嬫彁浜�
+ const ok = await submitInstanceForm({ skipValidate: true });
+ if (ok) ElMessage.success("淇敼鎴愬姛");
+ }
+}
+
+// ==================== 寤舵湡鐢宠锛堜娇鐢ㄥ鎵规ā鏉匡級 ====================
+const extendTemplateBindVisible = ref(false);
+const extendSubmitDialog = reactive({
+ visible: false,
+});
+const extendSubmitFormRef = ref(null);
+const extendSubmitSaving = ref(false);
+const extendSubmitForm = reactive({
+ templateId: null,
+ templateName: "",
+ formPayload: {},
+ flowNodes: [],
+ storageBlobDTOs: [],
+ formFieldDefs: [],
+});
+const extendSubmitFormRules = reactive({});
+const extendSubmitFormFields = ref([]);
+const extendActiveTemplate = ref({});
+const extendSubmitDialogTitle = computed(() => "杞﹁締寤舵湡鐢宠");
+const extendSourceRow = reactive({
+ id: null,
+ instanceNo: "",
+ vehiclePlateNumber: "",
+ originalEndDate: "",
+});
+
+// 鎵撳紑寤舵湡鐢宠瀵硅瘽妗嗭紙鍏堥�夋嫨妯℃澘锛�
+function openExtendDialog(row) {
+ // 淇濆瓨鍘熷鎵逛俊鎭�
+ extendSourceRow.id = row.id;
+ extendSourceRow.instanceNo = row.instanceNo || "";
+ // 灏濊瘯浠庡涓彲鑳界殑瀛楁鍚嶄腑鑾峰彇杞︾墝鍙�
+ const payload = row.formPayload || {};
+ extendSourceRow.vehiclePlateNumber = payload.vehicleNo || payload.plateNumber || payload.vehiclePlateNumber || payload.carNo || "";
+ // 浠庤溅杈嗕娇鐢ㄦ椂闂磋绠楀師鍒版湡鏃ユ湡
+ const useTimeField = findVehicleUseTimeField(row.formFieldDefs || []);
+ if (useTimeField?.key && row.formPayload?.[useTimeField.key]) {
+ const timeRange = row.formPayload[useTimeField.key];
+ if (Array.isArray(timeRange) && timeRange.length >= 2) {
+ extendSourceRow.originalEndDate = timeRange[1];
+ }
+ }
+ // 鎵撳紑妯℃澘閫夋嫨瀵硅瘽妗�
+ extendTemplateBindVisible.value = true;
+}
+
+// 寤舵湡妯℃澘缁戝畾纭
+function onExtendTemplateBound(payload) {
+ const { templateId, templateName, formFieldDefs, formPayload, flowNodes, templateAttachments, storageBlobDTOs } = payload;
+ extendActiveTemplate.value = { id: templateId, name: templateName, fields: formFieldDefs };
+ extendSubmitForm.templateId = templateId;
+ extendSubmitForm.templateName = templateName;
+ extendSubmitForm.formFieldDefs = formFieldDefs || [];
+ extendSubmitFormFields.value = formFieldDefs || [];
+ // 鍒濆鍖栬〃鍗曟暟鎹�
+ extendSubmitForm.formPayload = formPayload || {};
+ extendSubmitForm.flowNodes = flowNodes || [];
+ extendSubmitForm.templateAttachments = templateAttachments || [];
+ extendSubmitForm.storageBlobDTOs = storageBlobDTOs || [];
+ // 鎵撳紑鎻愪氦瀵硅瘽妗�
+ extendSubmitDialog.visible = true;
+}
+
+// 寤舵湡妯℃澘缁戝畾鍏抽棴
+function onExtendTemplateBindClosed() {
+ // 娓呯悊鐘舵��
+}
+
+// 鎻愪氦寤舵湡鐢宠
+async function onExtendSubmit() {
+ if (!extendSubmitFormRef.value) return;
+
+ extendSubmitSaving.value = true;
+ try {
+ // 鏋勫缓鎻愪氦鏁版嵁
+ // formConfig 鏄ā鏉胯鎯呮帴鍙h繑鍥炵殑鍘熷 JSON 瀛楃涓�
+ const payload = {
+ ...extendSubmitForm.formPayload,
+ originalInstanceNo: extendSourceRow.instanceNo,
+ vehiclePlateNumber: extendSourceRow.vehiclePlateNumber,
+ originalEndDate: extendSourceRow.originalEndDate,
+ formConfig: extendSubmitForm.formConfig,
+ };
+
+ // 杩欓噷璋冪敤鍒涘缓寤舵湡瀹℃壒瀹炰緥鐨凙PI
+ // await createVehicleDelayApproval({
+ // templateId: extendSubmitForm.templateId,
+ // formPayload: payload,
+ // flowNodes: extendSubmitForm.flowNodes,
+ // storageBlobDTOs: extendSubmitForm.storageBlobDTOs,
+ // formConfig: extendSubmitForm.formFieldDefs || [],
+ // });
+
+ ElMessage.success("寤舵湡鐢宠宸叉彁浜�");
+ extendSubmitDialog.visible = false;
+ onSearch();
+ } catch (error) {
+ ElMessage.error(error?.message || "鎻愪氦澶辫触");
+ } finally {
+ extendSubmitSaving.value = false;
+ }
+}
+
+// ==================== 褰掕繕杞﹁締 ====================
+const returnDialog = reactive({
+ visible: false,
+});
+const returnLoading = ref(false);
+const returnFormRef = ref(null);
+const returnForm = reactive({
+ id: null,
+ instanceNo: "",
+ vehiclePlateNumber: "",
+ actualReturnTime: "",
+ returnStorageBlobDTOs: [],
+});
+const returnRules = {
+ actualReturnTime: [{ required: true, message: "璇烽�夋嫨瀹為檯褰掕繕鏃堕棿", trigger: "change" }],
+};
+
+// 鎵撳紑褰掕繕杞﹁締瀵硅瘽妗�
+function openReturnDialog(row) {
+ returnForm.id = row.id;
+ returnForm.instanceNo = row.instanceNo || "";
+ // 灏濊瘯浠庡涓彲鑳界殑瀛楁鍚嶄腑鑾峰彇杞︾墝鍙�
+ const payload = row.formPayload || {};
+ returnForm.vehiclePlateNumber = payload.vehicleNo || payload.plateNumber || payload.vehiclePlateNumber || payload.carNo || "";
+ returnForm.actualReturnTime = dayjs().format("YYYY-MM-DD HH:mm:ss");
+ returnForm.returnStorageBlobDTOs = [];
+ returnDialog.visible = true;
+}
+
+// 鎻愪氦褰掕繕
+async function submitReturn() {
+ if (!returnFormRef.value) return;
+ await returnFormRef.value.validate(async (valid) => {
+ if (!valid) return;
+
+ returnLoading.value = true;
+ try {
+ // 杩欓噷璋冪敤褰掕繕杞﹁締鐨凙PI
+ // 1. 鏇存柊杞﹁締鍏噷鏁�
+ const vehicle = vehicleList.value.find((v) => v.plateNumber === returnForm.vehiclePlateNumber);
+ if (vehicle) {
+ vehicle.currentMileage += returnForm.mileage;
+ vehicle.status = returnForm.vehicleStatus === "good" ? "idle" : "repair";
+ }
+
+ // 2. 鍒涘缓褰掕繕瀹℃壒璁板綍锛堝疄闄呴」鐩腑璋冪敤API锛�
+ // await createVehicleReturnApproval({...});
+
+ ElMessage.success("杞﹁締褰掕繕鎴愬姛");
+ returnDialog.visible = false;
+ onSearch();
+ } catch (error) {
+ ElMessage.error(error?.message || "褰掕繕澶辫触");
+ } finally {
+ returnLoading.value = false;
+ }
+ });
+}
+
+onMounted(() => {
+ fetchVehicleList();
+ // 浣跨敤鍊熷嚭璁板綍鏌ヨ鎺ュ彛
+ fetchBorrowList();
+});
+</script>
+
+<style scoped>
+.search_form {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 12px;
+}
+
+.search_title {
+ font-size: 14px;
+ color: #606266;
+}
+
+.mb20 {
+ margin-bottom: 20px;
+}
+
+.table_list {
+ margin-top: 10px;
+}
+
+.vehicle-manage-card {
+ margin-bottom: 20px;
+}
+
+.card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.pagination-container {
+ display: flex;
+ justify-content: flex-end;
+}
+</style>
diff --git a/src/views/productionManagement/processRoute/processRouteItem/index.vue b/src/views/productionManagement/processRoute/processRouteItem/index.vue
index 3c52410..141dc9a 100644
--- a/src/views/productionManagement/processRoute/processRouteItem/index.vue
+++ b/src/views/productionManagement/processRoute/processRouteItem/index.vue
@@ -140,13 +140,7 @@
<el-table-column label="鍗曚綅"
prop="unit"
width="100" />
- <el-table-column label="璁¤垂绫诲瀷"
- prop="type"
- width="100">
- <template #default="scope">
- {{scope.row.type==0 ? "璁℃椂" : "璁′欢"}}
- </template>
- </el-table-column>
+
<el-table-column label="鏄惁璐ㄦ"
prop="isQuality"
width="100">
@@ -217,9 +211,7 @@
{{ item.model }}
<!-- <span v-if="item.unit" class="product-unit">{{ item.unit }}</span> -->
</div>
- <el-tag class="product-tag"
- :type="item.type == 1 ? 'primary' : 'success'"
- style="margin-left: 8px;">{{ item.type==0?'璁℃椂':'璁′欢' }}</el-tag>
+
<el-tag type="primary"
class="product-tag"
style="margin-left: 8px;"
@@ -476,7 +468,7 @@
v-else>
<span>{{ form.unit }}</span>
</el-form-item>
- <el-form-item label="璁¤垂绫诲瀷"
+ <el-form-item label=""
prop="type">
<el-radio-group v-model="form.type">
<el-radio :label="0">璁℃椂</el-radio>
diff --git a/src/views/productionManagement/productionCosting/index.vue b/src/views/productionManagement/productionCosting/index.vue
index 3e79b93..4de0dea 100644
--- a/src/views/productionManagement/productionCosting/index.vue
+++ b/src/views/productionManagement/productionCosting/index.vue
@@ -139,11 +139,11 @@
prop: "workHours",
minWidth: 100,
},
- {
- label: "宸ヨ祫",
- prop: "wages",
- minWidth: 100,
- },
+ // {
+ // label: "宸ヨ祫",
+ // prop: "wages",
+ // minWidth: 100,
+ // },
]);
// 宸︿晶姹囨�诲彴璐﹀垪锛堢敓浜т汉銆佷骇閲忋�佸伐璧勩�佸悎鏍肩巼锛�
@@ -158,11 +158,11 @@
prop: "finishedNum",
minWidth: 100,
},
- {
- label: "宸ヨ祫",
- prop: "wages",
- minWidth: 100,
- },
+ // {
+ // label: "宸ヨ祫",
+ // prop: "wages",
+ // minWidth: 100,
+ // },
{
label: "鍚堟牸鐜�",
prop: "outputRate",
diff --git a/src/views/productionManagement/productionProcess/Edit.vue b/src/views/productionManagement/productionProcess/Edit.vue
index 28077b6..a32c5a9 100644
--- a/src/views/productionManagement/productionProcess/Edit.vue
+++ b/src/views/productionManagement/productionProcess/Edit.vue
@@ -25,24 +25,7 @@
<el-form-item label="宸ュ簭缂栧彿" prop="no">
<el-input v-model="formState.no" />
</el-form-item>
- <el-form-item
- label="宸ュ簭绫诲瀷"
- prop="type"
- :rules="[
- {
- required: true,
- message: '璇烽�夋嫨宸ュ簭绫诲瀷',
- }
- ]"
- >
- <el-select v-model="formState.type" placeholder="璇烽�夋嫨宸ュ簭绫诲瀷">
- <el-option label="璁℃椂" :value="0" />
- <el-option label="璁′欢" :value="1" />
- </el-select>
- </el-form-item>
- <el-form-item label="宸ヨ祫瀹氶" prop="salaryQuota">
- <el-input v-model="formState.salaryQuota" type="number" :step="0.001" />
- </el-form-item>
+
<el-form-item label="鏄惁璐ㄦ" prop="isQuality">
<el-switch v-model="formState.isQuality" :active-value="true" inactive-value="false"/>
</el-form-item>
@@ -88,10 +71,8 @@
const formState = ref({
id: props.record.id,
name: props.record.name,
- type: props.record.type,
no: props.record.no,
remark: props.record.remark,
- salaryQuota: props.record.salaryQuota,
isQuality: props.record.isQuality,
inbound: props.record.inbound,
reportWork: props.record.reportWork,
@@ -113,9 +94,7 @@
id: newRecord.id,
name: newRecord.name || '',
no: newRecord.no || '',
- type: newRecord.type,
remark: newRecord.remark || '',
- salaryQuota: newRecord.salaryQuota || '',
isQuality: props.record.isQuality,
inbound: newRecord.inbound,
reportWork: newRecord.reportWork,
@@ -130,9 +109,7 @@
id: props.record.id,
name: props.record.name || '',
no: props.record.no || '',
- type: props.record.type,
remark: props.record.remark || '',
- salaryQuota: props.record.salaryQuota || '',
isQuality: props.record.isQuality,
inbound: props.record.inbound,
reportWork: props.record.reportWork,
diff --git a/src/views/productionManagement/productionProcess/New.vue b/src/views/productionManagement/productionProcess/New.vue
index 0b3fd47..3e9a823 100644
--- a/src/views/productionManagement/productionProcess/New.vue
+++ b/src/views/productionManagement/productionProcess/New.vue
@@ -25,26 +25,7 @@
<el-form-item label="宸ュ簭缂栧彿" prop="no">
<el-input v-model="formState.no" />
</el-form-item>
- <el-form-item
- label="宸ュ簭绫诲瀷"
- prop="type"
- :rules="[
- {
- required: true,
- message: '璇烽�夋嫨宸ュ簭绫诲瀷',
- }
- ]"
- >
- <el-select v-model="formState.type" placeholder="璇烽�夋嫨宸ュ簭绫诲瀷">
- <el-option label="璁℃椂" :value="0" />
- <el-option label="璁′欢" :value="1" />
- </el-select>
- </el-form-item>
- <el-form-item label="宸ヨ祫瀹氶" prop="salaryQuota">
- <el-input v-model="formState.salaryQuota" type="number" :step="0.001">
- <template #append>鍏�</template>
- </el-input>
- </el-form-item>
+
<el-form-item label="鏄惁璐ㄦ" prop="isQuality">
<el-switch v-model="formState.isQuality" :active-value="true" inactive-value="false"/>
</el-form-item>
@@ -84,9 +65,7 @@
// 鍝嶅簲寮忔暟鎹紙鏇夸唬閫夐」寮忕殑 data锛�
const formState = ref({
name: '',
- type: undefined,
remark: '',
- salaryQuota: '',
isQuality: false,
inbound: false,
reportWork: false,
diff --git a/src/views/productionManagement/productionProcess/index.vue b/src/views/productionManagement/productionProcess/index.vue
index ee49657..6c960ab 100644
--- a/src/views/productionManagement/productionProcess/index.vue
+++ b/src/views/productionManagement/productionProcess/index.vue
@@ -57,14 +57,7 @@
:type="process.isProduction ? 'warning' : 'info'">
{{ process.isProduction ? '鐢熶骇' : '涓嶇敓浜�' }}
</el-tag>
- <el-tag v-if="process.type !== null && process.type !== undefined"
- size="small"
- :type="process.type == 1 ? 'primary' : 'success'"
- style="margin-left: 8px">
- {{ process.type == 0 ? '璁℃椂' : '璁′欢' }}
- </el-tag>
</div>
- <span class="param-count">宸ヨ祫瀹氶: 楼{{ process.salaryQuota || 0 }}</span>
</div>
</div>
</div>
@@ -118,12 +111,6 @@
<el-input v-model="processForm.name"
placeholder="璇疯緭鍏ュ伐搴忓悕绉�" />
</el-form-item>
- <el-form-item label="宸ヨ祫瀹氶"
- prop="salaryQuota">
- <el-input v-model="processForm.salaryQuota"
- type="number"
- :step="0.001" />
- </el-form-item>
<el-form-item label="鏄惁璐ㄦ"
prop="isQuality">
<el-switch v-model="processForm.isQuality" />
@@ -131,13 +118,6 @@
<el-form-item label="鏄惁鐢熶骇"
prop="isProduction">
<el-switch v-model="processForm.isProduction" />
- </el-form-item>
- <el-form-item label="璁¤垂绫诲瀷"
- prop="type">
- <el-radio-group v-model="processForm.type">
- <el-radio :label="0">璁℃椂</el-radio>
- <el-radio :label="1">璁′欢</el-radio>
- </el-radio-group>
</el-form-item>
<el-form-item label="鍏宠仈璁惧"
prop="deviceLedgerId">
@@ -341,34 +321,17 @@
id: null,
no: "",
name: "",
- salaryQuota: null,
isQuality: false,
isProduction: false,
remark: "",
deviceLedgerId: null,
- type: 0,
});
const processRules = {
no: [{ required: true, message: "璇疯緭鍏ュ伐搴忕紪鐮�", trigger: "blur" }],
name: [{ required: true, message: "璇疯緭鍏ュ伐搴忓悕绉�", trigger: "blur" }],
- salaryQuota: [
- {
- required: false,
- message: "璇疯緭鍏ュ伐璧勫畾棰�",
- trigger: "blur",
- validator: (rule, value, callback) => {
- if (isNaN(value) || value < 0) {
- callback(new Error("宸ヨ祫瀹氶蹇呴』鏄潪璐熸暟瀛�"));
- } else {
- callback();
- }
- },
- },
- ],
deviceLedgerId: [
{ required: false, message: "璇烽�夋嫨璁惧", trigger: "change" },
],
- type: [{ required: false, message: "璇烽�夋嫨璁¤垂绫诲瀷", trigger: "change" }],
};
// 鍙傛暟瀵硅瘽妗�
@@ -552,12 +515,10 @@
processForm.id = null;
processForm.no = "";
processForm.name = "";
- processForm.salaryQuota = null;
processForm.isQuality = false;
processForm.isProduction = false;
processForm.remark = "";
processForm.deviceLedgerId = null;
- processForm.type = 0;
processDialogVisible.value = true;
};
@@ -566,7 +527,6 @@
processForm.id = process.id;
processForm.no = process.no;
processForm.name = process.name;
- processForm.salaryQuota = process.salaryQuota;
processForm.isQuality = !!process.isQuality;
processForm.isProduction = !!process.isProduction;
processForm.remark = process.remark || "";
@@ -574,7 +534,6 @@
const deviceId = Number(process.deviceLedgerId);
const hasDevice = deviceOptions.value.some(item => item.id === deviceId);
processForm.deviceLedgerId = deviceId && hasDevice ? deviceId : null;
- processForm.type = process.type;
processDialogVisible.value = true;
};
--
Gitblit v1.9.3