gaoluyang
2 天以前 4762afb6cb043a3e539ed7ec8da5bde997bfaf65
英泽防锈
1.车辆管理页面开发联调
已添加2个文件
已修改17个文件
1659 ■■■■■ 文件已修改
src/api/officeProcessAutomation/vehicle.js 179 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/vehicleManagement/index.vue 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/ApproveManage/approve-list/approveListConstants.js 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/ApproveManage/approve-list/components/FormPayloadFields.vue 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/ApproveManage/approve-list/index.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/ApproveManage/approve-list/useApproveList.js 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalInstanceFormConfigTable.js 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalModuleRegistry.js 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalTemplateBindingUtils.js 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/ApproveManage/approve-template/components/FormConfigEditor.vue 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/ApproveManage/approve-template/formConfigUtils.js 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/ApproveManage/approve-template/selectOptionSource.js 36 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/ApproveManage/approve-template/useSelectOptionSources.js 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/ApproveManage/vehicle-apply/index.vue 1066 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/processRouteItem/index.vue 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionCosting/index.vue 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionProcess/Edit.vue 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionProcess/New.vue 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionProcess/index.vue 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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,
  });
}
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">
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,
  };
}
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"
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();
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,
  };
}
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;
  });
}
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: ["车辆延期", "延期申请", "车辆延期审批"],
  },
};
/**
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]) => {
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 = "";
}
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)) {
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;
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;
    }
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
  );
}
// è§£æžæ—¶é—´èŒƒå›´
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>
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>
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",
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,
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,
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;
  };