| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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, |
| | | }); |
| | | } |
| | |
| | | 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)"> |
| | |
| | | /> |
| | | </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> |
| | |
| | | licenseExpireDate: "2026-03-10", |
| | | status: "å¨ç¨", |
| | | archived: false, |
| | | currentMileage: 12580.5, |
| | | lastRecordMileage: 12000.0, |
| | | mileagePhoto: "", |
| | | usageStatus: "idle", |
| | | }, |
| | | { |
| | | id: 2, |
| | |
| | | licenseExpireDate: "2025-07-28", |
| | | status: "ç»´ä¿®", |
| | | archived: false, |
| | | currentMileage: 8920.0, |
| | | lastRecordMileage: 8920.0, |
| | | mileagePhoto: "", |
| | | usageStatus: "repair", |
| | | }, |
| | | { |
| | | id: 3, |
| | |
| | | licenseExpireDate: "2024-05-18", |
| | | status: "é²ç½®", |
| | | archived: false, |
| | | currentMileage: 45600.0, |
| | | lastRecordMileage: 45600.0, |
| | | mileagePhoto: "", |
| | | usageStatus: "idle", |
| | | }, |
| | | { |
| | | id: 4, |
| | |
| | | licenseExpireDate: "2023-11-08", |
| | | status: "å¨ç¨", |
| | | archived: true, |
| | | currentMileage: 67890.5, |
| | | lastRecordMileage: 67890.5, |
| | | mileagePhoto: "", |
| | | usageStatus: "archived", |
| | | }, |
| | | ]); |
| | | |
| | |
| | | { label: "å¨ç¨", value: "å¨ç¨" }, |
| | | { label: "é²ç½®", value: "é²ç½®" }, |
| | | { label: "ç»´ä¿®", value: "ç»´ä¿®" }, |
| | | { label: "使ç¨ä¸", value: "使ç¨ä¸" }, |
| | | ]; |
| | | |
| | | // æ¥è¯¢è¡¨å |
| | |
| | | licenseExpireDate: "", |
| | | status: "å¨ç¨", |
| | | archived: false, |
| | | currentMileage: 0, |
| | | lastRecordMileage: 0, |
| | | mileagePhoto: "", |
| | | usageStatus: "idle", |
| | | }); |
| | | |
| | | const rules = { |
| | |
| | | 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"> |
| | |
| | | { 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 æä¸¾ä¸è´ï¼ */ |
| | |
| | | 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 "â"; |
| | |
| | | flowNodes, |
| | | templateAttachments: tpl?.storageBlobDTOs ? JSON.parse(JSON.stringify(tpl.storageBlobDTOs)) : [], |
| | | storageBlobDTOs: [], |
| | | formConfig: null, |
| | | }; |
| | | } |
| | | |
| | |
| | | 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> |
| | |
| | | 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" |
| | |
| | | <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> |
| | |
| | | :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)"> |
| | |
| | | submitApprove, |
| | | openDetail, |
| | | openApprove, |
| | | selectedRows, |
| | | batchDelete, |
| | | } = al; |
| | | |
| | | const { flowUserOptions, loadFlowUsers } = useFlowUserOptions(); |
| | |
| | | const submitFormRef = ref(); |
| | | const submitSaving = ref(false); |
| | | |
| | | // æ¹éå é¤ç¸å
³ |
| | | const selectedRows = ref([]); |
| | | |
| | | const isSubmitEdit = computed(() => submitDialog.mode === "edit"); |
| | | const submitDialogTitle = computed(() => { |
| | | if (submitDialog.mode === "edit") { |
| | |
| | | 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, |
| | |
| | | openDetail, |
| | | openApprove, |
| | | fetchApprovalList, |
| | | selectedRows, |
| | | batchDelete, |
| | | }; |
| | | } |
| | |
| | | * ä¸å¡ç³è¯·ä¸»è¡¨åï¼åºå®å + 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", |
| | |
| | | width: actionWidth, |
| | | operation: buildTableActions(), |
| | | }, |
| | | ]; |
| | | ); |
| | | return cols; |
| | | }); |
| | | } |
| | |
| | | TRAVEL_REIMBURSE: "travel_reimburse", |
| | | COST_REIMBURSE: "cost_reimburse", |
| | | ENTERPRISE_NEWS: "enterprise_news", |
| | | VEHICLE: "vehicle", |
| | | VEHICLE_DELAY: "vehicle_delay", |
| | | }; |
| | | |
| | | /** 审æ¹å®ä¾ listPage / ä¿å 使ç¨ç businessType æä¸¾ */ |
| | |
| | | [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>} */ |
| | |
| | | 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: ["车è¾å»¶æ", "å»¶æç³è¯·", "车è¾å»¶æå®¡æ¹"], |
| | | }, |
| | | }; |
| | | |
| | | /** |
| | |
| | | flowNodes: base.flowNodes, |
| | | templateAttachments: JSON.parse(JSON.stringify(templateAttachments)), |
| | | storageBlobDTOs: [], |
| | | formConfig: mapped.formConfig, // åå§ formConfig JSON å符串 |
| | | }; |
| | | } |
| | | |
| | |
| | | flowNodes: "flowNodes", |
| | | templateAttachments: "templateAttachments", |
| | | storageBlobDTOs: "storageBlobDTOs", |
| | | formConfig: "formConfig", |
| | | ...fieldMap, |
| | | }; |
| | | Object.entries(map).forEach(([srcKey, destKey]) => { |
| | |
| | | @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" |
| | |
| | | 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 = ""; |
| | | } |
| | | |
| | |
| | | { value: "textarea", label: "å¤è¡ææ¬" }, |
| | | { value: "number", label: "æ°å" }, |
| | | { value: "date", label: "æ¥æ" }, |
| | | { value: "datetime", label: "æ¥ææ¶é´" }, |
| | | { value: "datetimerange", label: "æ¥ææ¶é´èå´" }, |
| | | { value: "select", label: "䏿鿩" }, |
| | | ]; |
| | |
| | | { 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() { |
| | |
| | | if (dv === undefined || dv === null || dv === "") { |
| | | if (type === "number") return undefined; |
| | | if (type === "datetimerange") return []; |
| | | if (type === "datetime") return ""; |
| | | return ""; |
| | | } |
| | | if (type === "number") { |
| | |
| | | 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() !== ""; |
| | | } |
| | |
| | | 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)) { |
| | |
| | | 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) { |
| | |
| | | } |
| | | |
| | | 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) { |
| | |
| | | }); |
| | | } |
| | | |
| | | /** è½¦è¾ â 䏿 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; |
| | |
| | | } |
| | | 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); |
| | | } |
| | |
| | | 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) { |
| | |
| | | }) |
| | | ); |
| | | } |
| | | 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; |
| | |
| | | resolveSelectDisplayLabel, |
| | | } from "./selectOptionSource.js"; |
| | | |
| | | /** 䏿卿é项ï¼äººå / é¨é¨ç¼åä¸è§£æ */ |
| | | /** 䏿卿é项ï¼äººå / é¨é¨ / 车è¾ç¼åä¸è§£æ */ |
| | | export function useSelectOptionSources() { |
| | | const loading = ref(false); |
| | | const caches = reactive({ |
| | | users: [], |
| | | deptOptions: [], |
| | | vehicles: [], |
| | | }); |
| | | |
| | | async function ensureForFields(fields) { |
| | |
| | | const next = await fetchSelectOptionCaches(sources); |
| | | caches.users = next.users; |
| | | caches.deptOptions = next.deptOptions; |
| | | caches.vehicles = next.vehicles; |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <!--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 |
| | | ); |
| | | } |
| | | |
| | | // è§£ææ¶é´èå´ |
| | | 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䏿 ¹æ®è½¦çå·æ¥æ¾å¯¹åºç车è¾ID |
| | | // ä¼å
ä» 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 æ¯æ¨¡æ¿è¯¦æ
æ¥å£è¿åçåå§ 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 æ¯æ¨¡æ¿è¯¦æ
æ¥å£è¿åçåå§ JSON å符串 |
| | | const payload = { |
| | | ...extendSubmitForm.formPayload, |
| | | originalInstanceNo: extendSourceRow.instanceNo, |
| | | vehiclePlateNumber: extendSourceRow.vehiclePlateNumber, |
| | | originalEndDate: extendSourceRow.originalEndDate, |
| | | formConfig: extendSubmitForm.formConfig, |
| | | }; |
| | | |
| | | // è¿éè°ç¨å建延æå®¡æ¹å®ä¾çAPI |
| | | // 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 { |
| | | // è¿éè°ç¨å½è¿è½¦è¾çAPI |
| | | // 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> |
| | |
| | | <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"> |
| | |
| | | {{ 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;" |
| | |
| | | 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> |
| | |
| | | prop: "workHours", |
| | | minWidth: 100, |
| | | }, |
| | | { |
| | | label: "å·¥èµ", |
| | | prop: "wages", |
| | | minWidth: 100, |
| | | }, |
| | | // { |
| | | // label: "å·¥èµ", |
| | | // prop: "wages", |
| | | // minWidth: 100, |
| | | // }, |
| | | ]); |
| | | |
| | | // å·¦ä¾§æ±æ»å°è´¦åï¼ç产人ã产éãå·¥èµãåæ ¼çï¼ |
| | |
| | | prop: "finishedNum", |
| | | minWidth: 100, |
| | | }, |
| | | { |
| | | label: "å·¥èµ", |
| | | prop: "wages", |
| | | minWidth: 100, |
| | | }, |
| | | // { |
| | | // label: "å·¥èµ", |
| | | // prop: "wages", |
| | | // minWidth: 100, |
| | | // }, |
| | | { |
| | | label: "åæ ¼ç", |
| | | prop: "outputRate", |
| | |
| | | <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> |
| | |
| | | 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, |
| | |
| | | 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, |
| | |
| | | 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, |
| | |
| | | <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> |
| | |
| | | // ååºå¼æ°æ®ï¼æ¿ä»£é项å¼ç dataï¼ |
| | | const formState = ref({ |
| | | name: '', |
| | | type: undefined, |
| | | remark: '', |
| | | salaryQuota: '', |
| | | isQuality: false, |
| | | inbound: false, |
| | | reportWork: false, |
| | |
| | | :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> |
| | |
| | | <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" /> |
| | |
| | | <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"> |
| | |
| | | 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" }], |
| | | }; |
| | | |
| | | // åæ°å¯¹è¯æ¡ |
| | |
| | | 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; |
| | | }; |
| | | |
| | |
| | | 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 || ""; |
| | |
| | | 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; |
| | | }; |
| | | |