liu
4 天以前 91eff4781fa4142d1f62d30a0fa1de5cae424eaf
refactor: 单据审核改为审批配置状态机(前端统一审核弹窗)
已删除5个文件
已添加19个文件
已修改43个文件
5253 ■■■■ 文件已修改
src/api/crm/cancellation/index.ts 40 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/crm/contract/index.ts 51 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/crm/invoice/index.ts 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/crm/refund/index.ts 29 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/erp/finance/payment/index.ts 41 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/erp/purchase/request/index.ts 46 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/hrm/attendance/exception/index.ts 50 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/hrm/leave/index.ts 29 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/hrm/performance-appraise/index.ts 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/hrm/resignation/index.ts 37 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/hrm/transfer/index.ts 29 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/mes/pd/project/index.ts 50 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/mes/pd/tech-confirm/index.ts 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/mes/wm/equipmentreceipt/index.ts 48 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/mes/wm/itemreceipt/index.ts 32 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/mes/wm/miscissue/index.ts 48 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/mes/wm/miscreceipt/index.ts 48 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/mes/wm/returnvendor/index.ts 48 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/mes/wm/stocktaking/task/index.ts 48 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/packages/constants/src/biz-mes-enum.ts 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/crm/cancellation/modules/audit-modal.vue 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/crm/contract/index.vue 134 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/crm/contract/modules/audit-modal.vue 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/crm/invoice/index.vue 87 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/crm/invoice/modules/audit-modal.vue 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/crm/refund/modules/audit-modal.vue 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/finance/payment/index.vue 113 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/finance/payment/modules/audit-modal.vue 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/finance/payment/modules/process-select-modal.vue 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/request/index.vue 108 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/request/modules/audit-modal.vue 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/request/modules/process-select-modal.vue 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/hrm/attendance/exception/index.vue 134 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/hrm/attendance/exception/modules/audit-modal.vue 102 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/hrm/leave/index.vue 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/hrm/leave/modules/audit-modal.vue 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/hrm/performance-appraise/index.vue 101 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/hrm/performance-appraise/modules/audit-modal.vue 93 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/hrm/performance-appraise/modules/process-select-modal.vue 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/hrm/resignation/index.vue 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/hrm/resignation/modules/audit-modal.vue 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/hrm/transfer/index.vue 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/hrm/transfer/modules/audit-modal.vue 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/pd/archive/modules/detail.vue 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/pd/project/detail/index.vue 102 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/pd/project/index.vue 100 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/pd/project/modules/audit-modal.vue 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/pd/project/modules/process-select-modal.vue 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/pd/tech-confirm/data.ts 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/pd/tech-confirm/index.vue 124 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/pd/tech-confirm/modules/audit-modal.vue 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/pd/tech-confirm/modules/customer-confirm-modal.vue 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/pd/tech-confirm/modules/form.vue 29 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/pro/task/components/gantt-chart.vue 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/components/process-select-modal.vue 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/equipmentreceipt/index.vue 111 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/equipmentreceipt/modules/audit-modal.vue 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/itemreceipt/index.vue 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/itemreceipt/modules/audit-modal.vue 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/miscissue/index.vue 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/miscissue/modules/audit-modal.vue 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/miscreceipt/index.vue 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/miscreceipt/modules/audit-modal.vue 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/returnvendor/index.vue 112 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/returnvendor/modules/audit-modal.vue 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/stocktaking/task/index.vue 130 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/stocktaking/task/modules/audit-modal.vue 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/crm/cancellation/index.ts
@@ -19,7 +19,11 @@
    applyUserName?: string;
    applyTime?: string;
    auditStatus?: number;
    processInstanceId?: string;
    // ä»¥ä¸‹ä¸ºå®¡æ ¸æ´¾ç”Ÿå­—段,由后端在审核时写入,前端只读展示
    reviewerId?: number; // å®¡æ ¸äººID(实际执行审核的用户)
    reviewerName?: string; // å®¡æ ¸äººå§“名
    reviewTime?: string; // å®¡æ ¸æ—¶é—´
    reviewRemark?: string; // å®¡æ ¸æ„è§ï¼ˆå®¡æ ¸é€šè¿‡æ—¶çš„æ„è§ / å®¡æ ¸ä¸é€šè¿‡æ—¶çš„原因)
    remark?: string;
    createTime?: string;
  }
@@ -58,12 +62,34 @@
  return requestClient.put('/crm/cancellation/update', data);
}
/** å®¡æ‰¹é”€æˆ·ç”³è¯· */
export function auditCancellation(data: {
  id: number;
  auditStatus: number;
}) {
  return requestClient.put('/crm/cancellation/audit', data);
/**
 * æäº¤å®¡æ‰¹ï¼ˆè‰ç¨¿ / å®¡æ ¸ä¸é€šè¿‡ â†’ å®¡æ‰¹ä¸­ï¼‰
 * å®¡æ‰¹äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€ç»Ÿä¸€é…ç½®ï¼Œæäº¤æ—¶æ— éœ€æŒ‡å®šã€‚
 */
export function submitCancellation(id: number) {
  return requestClient.put('/crm/cancellation/submit', null, { params: { id } });
}
/** èŽ·å¾—é”€æˆ·ç”³è¯·çš„å®¡æ‰¹äººç¼–å·é›†åˆï¼Œä¾›åˆ—è¡¨é¡µåˆ¤æ–­æ˜¯å¦å±•ç¤ºå®¡æ ¸æŒ‰é’® */
export function getCancellationApproverIds() {
  return requestClient.get<number[]>('/crm/cancellation/approver-ids');
}
/**
 * å®¡æ ¸é”€æˆ·ç”³è¯·ï¼ˆå®¡æ‰¹ä¸­ â†’ å®¡æ ¸é€šè¿‡ / å®¡æ ¸ä¸é€šè¿‡ï¼‰
 *
 * ä»…审批配置中的审批人可调用(多人时或签);pass=false æ—¶ reviewRemark(不通过原因)必填。
 */
export function auditCancellation(
  id: number,
  pass: boolean,
  reviewRemark?: string,
) {
  return requestClient.put('/crm/cancellation/audit', {
    id,
    pass,
    reviewRemark,
  });
}
/** åˆ é™¤é”€æˆ·ç”³è¯· */
src/api/crm/contract/index.ts
@@ -18,8 +18,12 @@
    ownerUserId: number;
    ownerUserName?: string;
    ownerUserDeptName?: string;
    processInstanceId: number;
    auditStatus: number;
    // ä»¥ä¸‹ä¸ºå®¡æ ¸æ´¾ç”Ÿå­—段,由后端在审核时写入,前端只读展示
    reviewerId?: number; // å®¡æ ¸äººID(实际执行审核的用户)
    reviewerName?: string; // å®¡æ ¸äººå§“名
    reviewTime?: Date; // å®¡æ ¸æ—¶é—´
    reviewRemark?: string; // å®¡æ ¸æ„è§ï¼ˆå®¡æ ¸é€šè¿‡æ—¶çš„æ„è§ / å®¡æ ¸ä¸é€šè¿‡æ—¶çš„原因)
    orderDate: Date;
    startTime: Date;
    endTime: Date;
@@ -70,11 +74,6 @@
    totalPrice: number;
  }
  /** å®¡æ‰¹æµç¨‹ */
  export interface ApproveProcess {
    key: string;
    name: string;
  }
}
/** æŸ¥è¯¢åˆåŒåˆ—表 */
@@ -135,18 +134,42 @@
  return requestClient.download('/crm/contract/export-excel', { params });
}
/** æäº¤å®¡æ ¸ */
export function submitContract(id: number, processDefinitionKey: string) {
/**
 * æäº¤åˆåŒå®¡æ‰¹ï¼ˆæœªæäº¤ / å®¡æ ¸ä¸é€šè¿‡ â†’ å®¡æ‰¹ä¸­ï¼‰
 *
 * å®¡æ‰¹äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€é…ç½®ï¼Œæäº¤æ—¶æ— éœ€æŒ‡å®šï¼›
 * æœªå¯ç”¨å®¡æ‰¹æˆ–未配置有效审批人时后端会抛出业务异常。
 */
export function submitContract(id: number) {
  return requestClient.put('/crm/contract/submit', null, {
    params: { id, processDefinitionKey },
    params: { id },
  });
}
/** èŽ·å–åˆåŒå®¡æ‰¹æµç¨‹åˆ—è¡¨ */
export function getContractApproveProcessList() {
  return requestClient.get<CrmContractApi.ApproveProcess[]>(
    '/crm/contract/approve-process-list',
  );
/**
 * èŽ·å¾—åˆåŒçš„å®¡æ‰¹äººç¼–å·é›†åˆ
 *
 * ä¾›åˆ—表页判断当前登录用户是否应展示「审核 / å®¡æ ¸ä¸é€šè¿‡ã€æŒ‰é’®ï¼›æœªé…ç½®æ—¶è¿”回空数组。
 */
export function getContractApproverIds() {
  return requestClient.get<number[]>('/crm/contract/approver-ids');
}
/**
 * å®¡æ ¸åˆåŒï¼ˆå®¡æ‰¹ä¸­ â†’ å®¡æ ¸é€šè¿‡ / å®¡æ ¸ä¸é€šè¿‡ï¼‰
 *
 * ä»…审批配置中的审批人可调用(多人时或签);pass=false æ—¶ reviewRemark(不通过原因)必填。
 */
export function auditContract(
  id: number,
  pass: boolean,
  reviewRemark?: string,
) {
  return requestClient.put('/crm/contract/audit', {
    id,
    pass,
    reviewRemark,
  });
}
/** åˆåŒè½¬ç§» */
src/api/crm/invoice/index.ts
@@ -31,11 +31,15 @@
    price?: number;
    invoiceTime?: string;
    auditStatus?: number;
    // ä»¥ä¸‹ä¸ºå®¡æ ¸æ´¾ç”Ÿå­—段,由后端在审核时写入,前端只读展示
    reviewerId?: number; // å®¡æ ¸äººID(实际执行审核的用户)
    reviewerName?: string; // å®¡æ ¸äººå§“名
    reviewTime?: string; // å®¡æ ¸æ—¶é—´
    reviewRemark?: string; // å®¡æ ¸æ„è§ï¼ˆå®¡æ ¸é€šè¿‡æ—¶çš„æ„è§ / å®¡æ ¸ä¸é€šè¿‡æ—¶çš„原因)
    remark?: string;
    ownerUserId?: number;
    ownerUserName?: string;
    ownerUserDeptName?: string;
    processInstanceId?: string;
    attachmentList?: Attachment[];
    blobIds?: number[];
    hasAttachment?: boolean;
@@ -104,9 +108,30 @@
  return requestClient.download('/crm/invoice/export-excel', { params });
}
/** æäº¤å®¡æ ¸ */
/**
 * æäº¤å®¡æ‰¹ï¼ˆè‰ç¨¿ / å®¡æ ¸ä¸é€šè¿‡ â†’ å®¡æ‰¹ä¸­ï¼‰
 * å®¡æ‰¹äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€ç»Ÿä¸€é…ç½®ï¼Œæäº¤æ—¶æ— éœ€æŒ‡å®šã€‚
 */
export function submitInvoice(id: number) {
  return requestClient.put(`/crm/invoice/submit?id=${id}`);
  return requestClient.put('/crm/invoice/submit', null, { params: { id } });
}
/** èŽ·å¾—å¼€ç¥¨çš„å®¡æ‰¹äººç¼–å·é›†åˆï¼Œä¾›åˆ—è¡¨é¡µåˆ¤æ–­æ˜¯å¦å±•ç¤ºå®¡æ ¸æŒ‰é’® */
export function getInvoiceApproverIds() {
  return requestClient.get<number[]>('/crm/invoice/approver-ids');
}
/**
 * å®¡æ ¸å¼€ç¥¨ï¼ˆå®¡æ‰¹ä¸­ â†’ å®¡æ ¸é€šè¿‡ / å®¡æ ¸ä¸é€šè¿‡ï¼‰
 *
 * ä»…审批配置中的审批人可调用(多人时或签);pass=false æ—¶ reviewRemark(不通过原因)必填。
 */
export function auditInvoice(
  id: number,
  pass: boolean,
  reviewRemark?: string,
) {
  return requestClient.put('/crm/invoice/audit', { id, pass, reviewRemark });
}
/** èŽ·å¾—å¾…å®¡æ ¸å¼€ç¥¨æ•°é‡ */
src/api/crm/refund/index.ts
@@ -17,7 +17,11 @@
    applyUserName?: string;
    applyTime?: string;
    auditStatus?: number;
    processInstanceId?: string;
    // ä»¥ä¸‹ä¸ºå®¡æ ¸æ´¾ç”Ÿå­—段,由后端在审核时写入,前端只读展示
    reviewerId?: number; // å®¡æ ¸äººID(实际执行审核的用户)
    reviewerName?: string; // å®¡æ ¸äººå§“名
    reviewTime?: string; // å®¡æ ¸æ—¶é—´
    reviewRemark?: string; // å®¡æ ¸æ„è§ï¼ˆå®¡æ ¸é€šè¿‡æ—¶çš„æ„è§ / å®¡æ ¸ä¸é€šè¿‡æ—¶çš„原因)
    remark?: string;
    createTime?: string;
  }
@@ -52,9 +56,26 @@
  return requestClient.put('/crm/refund/update', data);
}
/** å®¡æ‰¹é€€è´¹å• */
export function auditRefund(data: { id: number; auditStatus: number }) {
  return requestClient.put('/crm/refund/audit', data);
/**
 * æäº¤å®¡æ‰¹ï¼ˆè‰ç¨¿ / å®¡æ ¸ä¸é€šè¿‡ â†’ å®¡æ‰¹ä¸­ï¼‰
 * å®¡æ‰¹äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€ç»Ÿä¸€é…ç½®ï¼Œæäº¤æ—¶æ— éœ€æŒ‡å®šã€‚
 */
export function submitRefund(id: number) {
  return requestClient.put('/crm/refund/submit', null, { params: { id } });
}
/** èŽ·å¾—é€€è´¹å•çš„å®¡æ‰¹äººç¼–å·é›†åˆï¼Œä¾›åˆ—è¡¨é¡µåˆ¤æ–­æ˜¯å¦å±•ç¤ºå®¡æ ¸æŒ‰é’® */
export function getRefundApproverIds() {
  return requestClient.get<number[]>('/crm/refund/approver-ids');
}
/**
 * å®¡æ ¸é€€è´¹å•(审批中 â†’ å®¡æ ¸é€šè¿‡ / å®¡æ ¸ä¸é€šè¿‡ï¼‰
 *
 * ä»…审批配置中的审批人可调用(多人时或签);pass=false æ—¶ reviewRemark(不通过原因)必填。
 */
export function auditRefund(id: number, pass: boolean, reviewRemark?: string) {
  return requestClient.put('/crm/refund/audit', { id, pass, reviewRemark });
}
/** åˆ é™¤é€€è´¹å• */
src/api/erp/finance/payment/index.ts
@@ -18,7 +18,11 @@
    discountPrice: number; // ä¼˜æƒ é‡‘额
    paymentPrice: number; // å®žé™…付款金额
    status: number; // çŠ¶æ€
    processInstanceId?: string; // å·¥ä½œæµç¼–号
    // ========== å®¡æ‰¹ä¿¡æ¯ï¼ˆè½»é‡çº§çŠ¶æ€æœºå®¡æ‰¹ï¼‰ ==========
    reviewerId?: number; // å®¡æ ¸äººç¼–号
    reviewerName?: string; // å®¡æ ¸äººå§“名
    reviewTime?: Date | string; // å®¡æ ¸æ—¶é—´
    reviewRemark?: string; // å®¡æ ¸æ„è§ï¼ˆä¸é€šè¿‡æ—¶ä¸ºé©³å›žåŽŸå› ï¼‰
    remark: string; // å¤‡æ³¨
    attachmentList?: { id: number; name?: string; url?: string }[]; // é™„件列表
    accountId?: number; // ä»˜æ¬¾è´¦æˆ·
@@ -76,16 +80,37 @@
  return requestClient.put('/erp/finance-payment/update', data);
}
/** æäº¤ä»˜æ¬¾å•审批 */
export function submitFinancePayment(id: number, processDefinitionKey: string) {
  return requestClient.post('/erp/finance-payment/submit', null, {
    params: { id, processDefinitionKey },
/**
 * æäº¤å®¡æ‰¹ï¼ˆæœªå®¡æ ¸ â†’ å®¡æ‰¹ä¸­ï¼‰
 *
 * å®¡æ‰¹äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€ç»Ÿä¸€é…ç½®ï¼Œæäº¤æ—¶æ— éœ€æŒ‡å®šã€‚
 */
export function submitFinancePayment(id: number) {
  return requestClient.put('/erp/finance-payment/submit', null, {
    params: { id },
  });
}
/** èŽ·å–ä»˜æ¬¾å•å®¡æ‰¹æµç¨‹åˆ—è¡¨ */
export function getFinancePaymentApproveProcessList() {
  return requestClient.get('/erp/finance-payment/approve-process-list');
/** èŽ·å¾—å®¡æ‰¹äººç¼–å·é›†åˆï¼Œä¾›åˆ—è¡¨é¡µåˆ¤æ–­æ˜¯å¦å±•ç¤ºå®¡æ ¸æŒ‰é’® */
export function getFinancePaymentApproverUserIds() {
  return requestClient.get<number[]>('/erp/finance-payment/approver-ids');
}
/**
 * å®¡æ ¸ï¼ˆå®¡æ‰¹ä¸­ â†’ å®¡æ ¸é€šè¿‡/审核不通过)
 *
 * ä»…审批配置中指定的审批人本人可调用;pass=false æ—¶ reviewRemark(不通过原因)必填。
 */
export function auditFinancePayment(
  id: number,
  pass: boolean,
  reviewRemark?: string,
) {
  return requestClient.put('/erp/finance-payment/audit', {
    id,
    pass,
    reviewRemark,
  });
}
/** åˆ é™¤ä»˜æ¬¾å• */
src/api/erp/purchase/request/index.ts
@@ -8,7 +8,6 @@
    id?: number; // ç¼–号
    no?: string; // ç”³è¯·ç¼–号
    status?: number; // çŠ¶æ€ï¼š0未提交 10审批中 20审核通过 30审核不通过 40已取消
    processInstanceId?: string; // å·¥ä½œæµç¼–号
    requestUserId?: number; // ç”³è¯·äººç¼–号
    requestUserName?: string; // ç”³è¯·äººåç§°
    requestDeptId?: number; // ç”³è¯·éƒ¨é—¨ç¼–号
@@ -25,7 +24,11 @@
    productNames?: string; // äº§å“åç§°åˆ—表
    orderId?: number; // ç”Ÿæˆçš„采购订单编号
    orderNo?: string; // ç”Ÿæˆçš„采购订单号
    processInstanceId?: number; // å®¡æ‰¹æµç¨‹å®žä¾‹ç¼–号
    // ========== å®¡æ‰¹ä¿¡æ¯ï¼ˆè½»é‡çº§çŠ¶æ€æœºå®¡æ‰¹ï¼‰ ==========
    reviewerId?: number; // å®¡æ ¸äººç¼–号
    reviewerName?: string; // å®¡æ ¸äººå§“名
    reviewTime?: Date | string; // å®¡æ ¸æ—¶é—´
    reviewRemark?: string; // å®¡æ ¸æ„è§ï¼ˆä¸é€šè¿‡æ—¶ä¸ºé©³å›žåŽŸå› ï¼‰
    items?: PurchaseRequestItem[]; // ç”³è¯·æ˜Žç»†
    createTime?: string; // åˆ›å»ºæ—¶é—´
  }
@@ -56,9 +59,37 @@
  return requestClient.put('/purchase/request/update', data);
}
/** æäº¤é‡‡è´­ç”³è¯·å®¡æ‰¹ */
export function submitPurchaseRequest(id: number, processDefinitionKey: string) {
  return requestClient.put('/purchase/request/submit', null, { params: { id, processDefinitionKey } });
/**
 * æäº¤å®¡æ‰¹ï¼ˆæœªæäº¤/审核不通过 â†’ å®¡æ‰¹ä¸­ï¼‰
 *
 * å®¡æ‰¹äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€ç»Ÿä¸€é…ç½®ï¼Œæäº¤æ—¶æ— éœ€æŒ‡å®šã€‚
 */
export function submitPurchaseRequest(id: number) {
  return requestClient.put('/purchase/request/submit', null, {
    params: { id },
  });
}
/** èŽ·å¾—å®¡æ‰¹äººç¼–å·é›†åˆï¼Œä¾›åˆ—è¡¨é¡µåˆ¤æ–­æ˜¯å¦å±•ç¤ºå®¡æ ¸æŒ‰é’® */
export function getPurchaseRequestApproverUserIds() {
  return requestClient.get<number[]>('/purchase/request/approver-ids');
}
/**
 * å®¡æ ¸ï¼ˆå®¡æ‰¹ä¸­ â†’ å®¡æ ¸é€šè¿‡/审核不通过)
 *
 * ä»…审批配置中指定的审批人本人可调用;pass=false æ—¶ reviewRemark(不通过原因)必填。
 */
export function auditPurchaseRequest(
  id: number,
  pass: boolean,
  reviewRemark?: string,
) {
  return requestClient.put('/purchase/request/audit', {
    id,
    pass,
    reviewRemark,
  });
}
/** ç”Ÿæˆé‡‡è´­è®¢å• */
@@ -89,9 +120,4 @@
/** å¯¼å‡ºé‡‡è´­ç”³è¯· Excel */
export function exportPurchaseRequest(params: any) {
  return requestClient.download('/purchase/request/export-excel', { params });
}
/** èŽ·å–å®¡æ‰¹æµç¨‹åˆ—è¡¨ */
export function getApproveProcessList(id: number) {
  return requestClient.get(`/purchase/request/approve-process-list`, { params: { id } });
}
src/api/hrm/attendance/exception/index.ts
@@ -17,15 +17,14 @@
    blobIds?: number[];
    attachmentList?: SystemStorageApi.StorageBlob[];
    status?: number;
    processInstanceId?: string;
    exceptionDate?: string;
    // ä»¥ä¸‹ä¸ºå®¡æ ¸æ´¾ç”Ÿå­—段,由后端在审核时写入,前端只读展示
    reviewerId?: number; // å®¡æ ¸äººID(实际执行审核的用户)
    reviewerName?: string; // å®¡æ ¸äººå§“名
    reviewTime?: string; // å®¡æ ¸æ—¶é—´
    reviewRemark?: string; // å®¡æ ¸æ„è§ï¼ˆå®¡æ ¸é€šè¿‡æ—¶çš„æ„è§ / å®¡æ ¸ä¸é€šè¿‡æ—¶çš„原因)
    remark?: string;
    createTime?: string;
  }
  /** å®¡æ‰¹æµç¨‹ */
  export interface ApproveProcess {
    key: string;
    name: string;
  }
}
@@ -51,16 +50,37 @@
  return requestClient.post('/hrm/attendance/exception/create', data);
}
/** æäº¤è€ƒå‹¤å¼‚常申请 */
export function submitAttendanceException(id: number, processDefinitionKey: string) {
  return requestClient.post('/hrm/attendance/exception/submit', null, {
    params: { id, processDefinitionKey },
/**
 * æäº¤è€ƒå‹¤å¼‚常申请审批(草稿 / å®¡æ ¸ä¸é€šè¿‡ â†’ å®¡æ‰¹ä¸­ï¼‰
 *
 * å®¡æ‰¹äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ï¼ˆhrm_attendance_approve)统一配置,提交时无需指定。
 */
export function submitAttendanceException(id: number) {
  return requestClient.put('/hrm/attendance/exception/submit', null, {
    params: { id },
  });
}
/** èŽ·å–è€ƒå‹¤å¼‚å¸¸å®¡æ‰¹æµç¨‹åˆ—è¡¨ */
export function getAttendanceExceptionApproveProcessList() {
  return requestClient.get<HrmAttendanceExceptionApi.ApproveProcess[]>(
    '/hrm/attendance/exception/approve-process-list',
/** èŽ·å¾—è€ƒå‹¤å¼‚å¸¸ç”³è¯·çš„å®¡æ‰¹äººç¼–å·é›†åˆï¼Œä¾›åˆ—è¡¨é¡µåˆ¤æ–­æ˜¯å¦å±•ç¤ºå®¡æ ¸æŒ‰é’® */
export function getAttendanceExceptionApproverIds() {
  return requestClient.get<number[]>(
    '/hrm/attendance/exception/approver-ids',
  );
}
/**
 * å®¡æ ¸è€ƒå‹¤å¼‚常申请(审批中 â†’ å®¡æ ¸é€šè¿‡ / å®¡æ ¸ä¸é€šè¿‡ï¼‰
 *
 * ä»…审批配置中的审批人可调用(多人时或签);pass=false æ—¶ reviewRemark(不通过原因)必填。
 */
export function auditAttendanceException(
  id: number,
  pass: boolean,
  reviewRemark?: string,
) {
  return requestClient.put('/hrm/attendance/exception/audit', {
    id,
    pass,
    reviewRemark,
  });
}
src/api/hrm/leave/index.ts
@@ -18,7 +18,11 @@
    blobIds?: number[];
    attachmentList?: SystemStorageApi.StorageBlob[];
    status?: number;
    processInstanceId?: string;
    // ä»¥ä¸‹ä¸ºå®¡æ ¸æ´¾ç”Ÿå­—段,由后端在审核时写入,前端只读展示
    reviewerId?: number; // å®¡æ ¸äººID(实际执行审核的用户)
    reviewerName?: string; // å®¡æ ¸äººå§“名
    reviewTime?: string; // å®¡æ ¸æ—¶é—´
    reviewRemark?: string; // å®¡æ ¸æ„è§ï¼ˆå®¡æ ¸é€šè¿‡æ—¶çš„æ„è§ / å®¡æ ¸ä¸é€šè¿‡æ—¶çš„原因)
    remark?: string;
    createTime?: string;
  }
@@ -52,9 +56,13 @@
  return requestClient.delete(`/hrm/leave/delete?id=${id}`);
}
/** æäº¤è¯·å‡ç”³è¯·ï¼ˆå‘起审批) */
/**
 * æäº¤è¯·å‡ç”³è¯·å®¡æ‰¹ï¼ˆè‰ç¨¿ / å®¡æ ¸ä¸é€šè¿‡ â†’ å®¡æ‰¹ä¸­ï¼‰
 *
 * å®¡æ‰¹äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ï¼ˆhrm_leave_approve)统一配置,提交时无需指定。
 */
export function submitLeave(id: number) {
  return requestClient.post(`/hrm/leave/submit?id=${id}`);
  return requestClient.put('/hrm/leave/submit', null, { params: { id } });
}
/** æ’¤é”€è¯·å‡ç”³è¯· */
@@ -62,7 +70,16 @@
  return requestClient.post(`/hrm/leave/cancel?id=${id}`);
}
/** èŽ·å–å¯ç”¨æµç¨‹å®šä¹‰åˆ—è¡¨ */
export function getLeaveApproveProcessDefinitionList() {
  return requestClient.get<any[]>('/hrm/leave/approve-process-definition-list');
/** èŽ·å¾—è¯·å‡ç”³è¯·çš„å®¡æ‰¹äººç¼–å·é›†åˆï¼Œä¾›åˆ—è¡¨é¡µåˆ¤æ–­æ˜¯å¦å±•ç¤ºå®¡æ ¸æŒ‰é’® */
export function getLeaveApproverIds() {
  return requestClient.get<number[]>('/hrm/leave/approver-ids');
}
/**
 * å®¡æ ¸è¯·å‡ç”³è¯·ï¼ˆå®¡æ‰¹ä¸­ â†’ å®¡æ ¸é€šè¿‡ / å®¡æ ¸ä¸é€šè¿‡ï¼‰
 *
 * ä»…审批配置中的审批人可调用(多人时或签);pass=false æ—¶ reviewRemark(不通过原因)必填。
 */
export function auditLeave(id: number, pass: boolean, reviewRemark?: string) {
  return requestClient.put('/hrm/leave/audit', { id, pass, reviewRemark });
}
src/api/hrm/performance-appraise/index.ts
@@ -39,10 +39,11 @@
    statusName?: string;
    auditStatus?: number; // å®¡æ‰¹çŠ¶æ€ï¼š0未提交 10审批中 20通过 30驳回 40已取消
    auditStatusName?: string;
    processInstanceId?: string;
    auditUserId?: number;
    auditTime?: string;
    auditRemark?: string;
    // ä»¥ä¸‹ä¸ºå®¡æ ¸æ´¾ç”Ÿå­—段,由后端在审核时写入,前端只读展示
    reviewerId?: number; // å®¡æ ¸äººID(实际执行审核的用户)
    reviewerName?: string; // å®¡æ ¸äººå§“名
    reviewTime?: string; // å®¡æ ¸æ—¶é—´
    reviewRemark?: string; // å®¡æ ¸æ„è§ï¼ˆå®¡æ ¸é€šè¿‡æ—¶çš„æ„è§ / å®¡æ ¸ä¸é€šè¿‡æ—¶çš„原因)
    remark?: string;
    itemCount?: number; // è€ƒæ ¸æŒ‡æ ‡æ•°
    resultCount?: number; // å·²è¯„分人数
@@ -111,23 +112,37 @@
  return requestClient.post<boolean>('/hrm/performance-appraise/item-save', data);
}
/** æäº¤ç»©æ•ˆè€ƒæ ¸æ–¹æ¡ˆå®¡æ‰¹ */
export function submitAppraise(id: number, processDefinitionKey: string) {
  return requestClient.post<boolean>('/hrm/performance-appraise/submit', null, {
    params: { id, processDefinitionKey },
/**
 * æäº¤ç»©æ•ˆè€ƒæ ¸æ–¹æ¡ˆå®¡æ‰¹ï¼ˆè‰ç¨¿ / å®¡æ ¸ä¸é€šè¿‡ â†’ å®¡æ‰¹ä¸­ï¼‰
 *
 * å®¡æ‰¹äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ï¼ˆperformance_appraise_approve)统一配置,提交时无需指定。
 */
export function submitAppraise(id: number) {
  return requestClient.put<boolean>('/hrm/performance-appraise/submit', null, {
    params: { id },
  });
}
/** å®¡æ‰¹ç»©æ•ˆè€ƒæ ¸æ–¹æ¡ˆ */
export function auditAppraise(data: { id: number; auditStatus: number; remark?: string }) {
  return requestClient.put<boolean>('/hrm/performance-appraise/audit', data);
/** èŽ·å¾—ç»©æ•ˆè€ƒæ ¸æ–¹æ¡ˆçš„å®¡æ‰¹äººç¼–å·é›†åˆï¼Œä¾›åˆ—è¡¨é¡µåˆ¤æ–­æ˜¯å¦å±•ç¤ºå®¡æ ¸æŒ‰é’® */
export function getAppraiseApproverIds() {
  return requestClient.get<number[]>('/hrm/performance-appraise/approver-ids');
}
/** èŽ·å–ç»©æ•ˆè€ƒæ ¸æ–¹æ¡ˆå®¡æ‰¹æµç¨‹å®šä¹‰åˆ—è¡¨ */
export function getAppraiseProcessList() {
  return requestClient.get<{ id: string; name: string; version: number; key: string }[]>(
    '/hrm/performance-appraise/approve-process-list',
  );
/**
 * å®¡æ ¸ç»©æ•ˆè€ƒæ ¸æ–¹æ¡ˆï¼ˆå®¡æ‰¹ä¸­ â†’ å®¡æ ¸é€šè¿‡ / å®¡æ ¸ä¸é€šè¿‡ï¼‰
 *
 * ä»…审批配置中的审批人可调用(多人时或签);pass=false æ—¶ reviewRemark(不通过原因)必填。
 */
export function auditAppraise(
  id: number,
  pass: boolean,
  reviewRemark?: string,
) {
  return requestClient.put<boolean>('/hrm/performance-appraise/audit', {
    id,
    pass,
    reviewRemark,
  });
}
/** èŽ·å–è€ƒæ ¸ç»“æžœåˆ†é¡µ */
src/api/hrm/resignation/index.ts
@@ -20,7 +20,11 @@
    blobIds?: number[];
    attachmentList?: SystemStorageApi.StorageBlob[];
    status?: number;
    processInstanceId?: string;
    // ä»¥ä¸‹ä¸ºå®¡æ ¸æ´¾ç”Ÿå­—段,由后端在审核时写入,前端只读展示
    reviewerId?: number; // å®¡æ ¸äººID(实际执行审核的用户)
    reviewerName?: string; // å®¡æ ¸äººå§“名
    reviewTime?: string; // å®¡æ ¸æ—¶é—´
    reviewRemark?: string; // å®¡æ ¸æ„è§ï¼ˆå®¡æ ¸é€šè¿‡æ—¶çš„æ„è§ / å®¡æ ¸ä¸é€šè¿‡æ—¶çš„原因)
    remark?: string;
    createTime?: string;
  }
@@ -56,9 +60,13 @@
  return requestClient.delete(`/hrm/resignation/delete?id=${id}`);
}
/** æäº¤ç¦»èŒç”³è¯·ï¼ˆå‘起审批) */
/**
 * æäº¤ç¦»èŒç”³è¯·å®¡æ‰¹ï¼ˆè‰ç¨¿ / å®¡æ ¸ä¸é€šè¿‡ â†’ å®¡æ‰¹ä¸­ï¼‰
 *
 * å®¡æ‰¹äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ï¼ˆhrm_resignation_approve)统一配置,提交时无需指定。
 */
export function submitResignation(id: number) {
  return requestClient.post(`/hrm/resignation/submit?id=${id}`);
  return requestClient.put('/hrm/resignation/submit', null, { params: { id } });
}
/** æ’¤é”€ç¦»èŒç”³è¯· */
@@ -71,7 +79,24 @@
  return requestClient.put('/hrm/resignation/handover', { id, handoverStatus });
}
/** èŽ·å–å¯ç”¨æµç¨‹å®šä¹‰åˆ—è¡¨ */
export function getResignationApproveProcessDefinitionList() {
  return requestClient.get<any[]>('/hrm/resignation/approve-process-definition-list');
/** èŽ·å¾—ç¦»èŒç”³è¯·çš„å®¡æ‰¹äººç¼–å·é›†åˆï¼Œä¾›åˆ—è¡¨é¡µåˆ¤æ–­æ˜¯å¦å±•ç¤ºå®¡æ ¸æŒ‰é’® */
export function getResignationApproverIds() {
  return requestClient.get<number[]>('/hrm/resignation/approver-ids');
}
/**
 * å®¡æ ¸ç¦»èŒç”³è¯·ï¼ˆå®¡æ‰¹ä¸­ â†’ å®¡æ ¸é€šè¿‡ / å®¡æ ¸ä¸é€šè¿‡ï¼‰
 *
 * ä»…审批配置中的审批人可调用(多人时或签);pass=false æ—¶ reviewRemark(不通过原因)必填。
 */
export function auditResignation(
  id: number,
  pass: boolean,
  reviewRemark?: string,
) {
  return requestClient.put('/hrm/resignation/audit', {
    id,
    pass,
    reviewRemark,
  });
}
src/api/hrm/transfer/index.ts
@@ -23,7 +23,11 @@
    blobIds?: number[];
    attachmentList?: SystemStorageApi.StorageBlob[];
    status?: number;
    processInstanceId?: string;
    // ä»¥ä¸‹ä¸ºå®¡æ ¸æ´¾ç”Ÿå­—段,由后端在审核时写入,前端只读展示
    reviewerId?: number; // å®¡æ ¸äººID(实际执行审核的用户)
    reviewerName?: string; // å®¡æ ¸äººå§“名
    reviewTime?: string; // å®¡æ ¸æ—¶é—´
    reviewRemark?: string; // å®¡æ ¸æ„è§ï¼ˆå®¡æ ¸é€šè¿‡æ—¶çš„æ„è§ / å®¡æ ¸ä¸é€šè¿‡æ—¶çš„原因)
    remark?: string;
    createTime?: string;
  }
@@ -57,9 +61,13 @@
  return requestClient.delete(`/hrm/transfer/delete?id=${id}`);
}
/** æäº¤è°ƒå²—申请(发起审批) */
/**
 * æäº¤è°ƒå²—申请审批(草稿 / å®¡æ ¸ä¸é€šè¿‡ â†’ å®¡æ‰¹ä¸­ï¼‰
 *
 * å®¡æ‰¹äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ï¼ˆhrm_transfer_approve)统一配置,提交时无需指定。
 */
export function submitTransfer(id: number) {
  return requestClient.post(`/hrm/transfer/submit?id=${id}`);
  return requestClient.put('/hrm/transfer/submit', null, { params: { id } });
}
/** æ’¤é”€è°ƒå²—申请 */
@@ -67,7 +75,16 @@
  return requestClient.post(`/hrm/transfer/cancel?id=${id}`);
}
/** èŽ·å–å¯ç”¨æµç¨‹å®šä¹‰åˆ—è¡¨ */
export function getTransferApproveProcessDefinitionList() {
  return requestClient.get<any[]>('/hrm/transfer/approve-process-definition-list');
/** èŽ·å¾—è°ƒå²—ç”³è¯·çš„å®¡æ‰¹äººç¼–å·é›†åˆï¼Œä¾›åˆ—è¡¨é¡µåˆ¤æ–­æ˜¯å¦å±•ç¤ºå®¡æ ¸æŒ‰é’® */
export function getTransferApproverIds() {
  return requestClient.get<number[]>('/hrm/transfer/approver-ids');
}
/**
 * å®¡æ ¸è°ƒå²—申请(审批中 â†’ å®¡æ ¸é€šè¿‡ / å®¡æ ¸ä¸é€šè¿‡ï¼‰
 *
 * ä»…审批配置中的审批人可调用(多人时或签);pass=false æ—¶ reviewRemark(不通过原因)必填。
 */
export function auditTransfer(id: number, pass: boolean, reviewRemark?: string) {
  return requestClient.put('/hrm/transfer/audit', { id, pass, reviewRemark });
}
src/api/mes/pd/project/index.ts
@@ -32,8 +32,14 @@
    remark?: string;
    /** åˆ›å»ºæ—¶é—´ */
    createTime?: number;
    /** BPM æµç¨‹å®žä¾‹ç¼–号 */
    processInstanceId?: string;
    /** å®¡æ ¸äººç”¨æˆ·ç¼–号(审核通过后写入实际审核人) */
    reviewerId?: number;
    /** å®¡æ ¸äººå§“名 */
    reviewerName?: string;
    /** å®¡æ ¸æ—¶é—´ */
    reviewTime?: number;
    /** å®¡æ ¸æ„è§ï¼ˆé€šè¿‡æ—¶çš„æ„è§ / ä¸é€šè¿‡æ—¶çš„原因) */
    reviewRemark?: string;
  }
  export interface PageParams extends PageParam {
@@ -89,18 +95,42 @@
  });
}
/** æäº¤å®¡æ‰¹ï¼ˆå¾…审核→审批中) */
export function submitPdProjectApproval(id: number, processDefinitionKey: string) {
/**
 * æäº¤å®¡æ‰¹ï¼ˆå¾…审核→审批中)
 *
 * å®¡æ ¸äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€é…ç½®ï¼Œæäº¤æ—¶æ— éœ€æŒ‡å®šï¼›
 * æœªå¯ç”¨å®¡æ‰¹æˆ–未配置有效审核人时后端会抛出业务异常。
 */
export function submitPdProjectApproval(id: number) {
  return requestClient.put('/mes/pd/project/submit-approval', undefined, {
    params: { id, processDefinitionKey },
    params: { id },
  });
}
/** èŽ·å¾—å®¡æ‰¹æµç¨‹åˆ—è¡¨ */
export function getPdProjectApproveProcessList() {
  return requestClient.get<Array<{ id: string; name: string; key: string }>>(
    '/mes/pd/project/approve-process-list',
  );
/**
 * èŽ·å¾—è®¾è®¡é¡¹ç›®çš„å®¡æ ¸äººç¼–å·é›†åˆ
 *
 * ä¾›åˆ—表页判断当前登录用户是否应展示「审核 / å®¡æ ¸ä¸é€šè¿‡ã€æŒ‰é’®ï¼›æœªé…ç½®æ—¶è¿”回空数组。
 */
export function getPdProjectApproverIds() {
  return requestClient.get<number[]>('/mes/pd/project/approver-ids');
}
/**
 * å®¡æ ¸è®¾è®¡é¡¹ç›®ï¼ˆå®¡æ‰¹ä¸­â†’å¾…å½’æ¡£/待审核)
 *
 * ä»…审批配置中的审核人可调用(多人时或签);pass=false æ—¶ reviewRemark(不通过原因)必填。
 */
export function auditPdProject(
  id: number,
  pass: boolean,
  reviewRemark?: string,
) {
  return requestClient.put('/mes/pd/project/audit', {
    id,
    pass,
    reviewRemark,
  });
}
/** å½’档(待归档→已归档),后端自动创建归档台账记录 */
src/api/mes/pd/tech-confirm/index.ts
@@ -13,15 +13,17 @@
    specification?: string; // è§„格型号
    techParams?: string; // æŠ€æœ¯å‚æ•° JSON
    templateType?: string; // æ¨¡æ¿ç±»åž‹
    // ä»¥ä¸‹ä¸ºæµç¨‹æ´¾ç”Ÿå­—段,由后端自动记录,前端只读展示
    drafterId?: number; // ç¼–制人ID
    drafterName?: string; // ç¼–制人姓名
    draftTime?: Date; // ç¼–制时间
    reviewerId?: number; // å®¡æ ¸äººID
    reviewerName?: string; // å®¡æ ¸äººå§“名
    reviewerId?: number; // å®¡æ ¸äººID(实际执行审核的用户,由后端在审核时写入)
    reviewerName?: string; // å®¡æ ¸äººå§“名(审核时冗余记录)
    reviewTime?: Date; // å®¡æ ¸æ—¶é—´
    reviewRemark?: string; // å®¡æ ¸æ„è§ï¼ˆå®¡æ ¸é€šè¿‡æ—¶çš„æ„è§ / å®¡æ ¸ä¸é€šè¿‡æ—¶çš„原因)
    customerConfirmUser?: string; // å®¢æˆ·ç¡®è®¤äºº
    customerConfirmTime?: Date; // å®¢æˆ·ç¡®è®¤æ—¶é—´
    status?: number; // çŠ¶æ€
    status?: number; // çŠ¶æ€ï¼ˆ0=草稿,1=待审核,2=已审核,3=客户已确认,4=审核不通过)
    remark?: string; // å¤‡æ³¨
    createTime?: Date; // åˆ›å»ºæ—¶é—´
  }
@@ -42,21 +44,77 @@
  );
}
/** æ–°å¢žäº§å“æŠ€æœ¯ç¡®è®¤å‡½ */
/**
 * æ–°å¢žäº§å“æŠ€æœ¯ç¡®è®¤å‡½
 *
 * çŠ¶æ€ç”±åŽç«¯å›ºå®šä¸ºè‰ç¨¿ï¼Œç¼–åˆ¶äººä¸Žç¼–åˆ¶æ—¶é—´ç”±åŽç«¯è‡ªåŠ¨è®°å½•ï¼Œå‡æ— éœ€å‰ç«¯ä¼ å‚ã€‚
 */
export function createTechConfirm(data: MesPdTechConfirmApi.TechConfirm) {
  return requestClient.post('/mes/pd/tech-confirm/create', data);
}
/** ä¿®æ”¹äº§å“æŠ€æœ¯ç¡®è®¤å‡½ */
/**
 * ä¿®æ”¹äº§å“æŠ€æœ¯ç¡®è®¤å‡½
 *
 * ä»…草稿状态可修改,且状态与流程派生字段不参与提交。
 */
export function updateTechConfirm(data: MesPdTechConfirmApi.TechConfirm) {
  return requestClient.put('/mes/pd/tech-confirm/update', data);
}
/** åˆ é™¤äº§å“æŠ€æœ¯ç¡®è®¤å‡½ */
/** åˆ é™¤äº§å“æŠ€æœ¯ç¡®è®¤å‡½ï¼ˆä»…草稿 / å®¡æ ¸ä¸é€šè¿‡çŠ¶æ€å¯åˆ é™¤ï¼‰ */
export function deleteTechConfirm(id: number) {
  return requestClient.delete(`/mes/pd/tech-confirm/delete?id=${id}`);
}
/**
 * æäº¤æŠ€æœ¯ç¡®è®¤å‡½å®¡æ ¸ï¼ˆè‰ç¨¿/审核不通过→待审核)
 *
 * å®¡æ ¸äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€é…ç½®ï¼Œæäº¤æ—¶æ— éœ€æŒ‡å®šï¼›
 * æœªå¯ç”¨å®¡æ‰¹æˆ–未配置有效审核人时后端会抛出业务异常。
 */
export function submitTechConfirmAudit(id: number) {
  return requestClient.put('/mes/pd/tech-confirm/submit-approval', undefined, {
    params: { id },
  });
}
/**
 * èŽ·å¾—æŠ€æœ¯ç¡®è®¤å‡½çš„å®¡æ ¸äººç¼–å·é›†åˆ
 *
 * ä¾›åˆ—表页判断当前登录用户是否应展示「审核 / å®¡æ ¸ä¸é€šè¿‡ã€æŒ‰é’®ï¼›æœªé…ç½®æ—¶è¿”回空数组。
 */
export function getTechConfirmApproverIds() {
  return requestClient.get<number[]>('/mes/pd/tech-confirm/approver-ids');
}
/**
 * å®¡æ ¸æŠ€æœ¯ç¡®è®¤å‡½ï¼ˆå¾…审核→已审核/审核不通过)
 *
 * ä»…审批配置中的审核人可调用(多人时或签);pass=false æ—¶ reviewRemark(不通过原因)必填。
 */
export function auditTechConfirm(
  id: number,
  pass: boolean,
  reviewRemark?: string,
) {
  return requestClient.put('/mes/pd/tech-confirm/audit', {
    id,
    pass,
    reviewRemark,
  });
}
/** å®¢æˆ·ç¡®è®¤ï¼ˆå·²å®¡æ ¸â†’客户已确认) */
export function confirmTechConfirmByCustomer(
  id: number,
  customerConfirmUser: string,
) {
  return requestClient.put('/mes/pd/tech-confirm/customer-confirm', undefined, {
    params: { id, customerConfirmUser },
  });
}
/** å¯¼å‡ºäº§å“æŠ€æœ¯ç¡®è®¤å‡½ */
export function exportTechConfirm(params: any) {
  return requestClient.download('/mes/pd/tech-confirm/export-excel', { params });
src/api/mes/wm/equipmentreceipt/index.ts
@@ -13,7 +13,10 @@
    receiptDate?: number; // å…¥åº“日期
    source?: string; // å…¥åº“来源
    status?: number; // å•据状态
    processInstanceId?: string; // å®¡æ‰¹æµç¨‹å®žä¾‹ç¼–号
    reviewerId?: number; // å®¡æ ¸äººç”¨æˆ·ç¼–号
    reviewerName?: string; // å®¡æ ¸äººå§“名
    reviewTime?: number; // å®¡æ ¸æ—¶é—´
    reviewRemark?: string; // å®¡æ ¸æ„è§ï¼ˆé€šè¿‡æ—¶çš„æ„è§ / ä¸é€šè¿‡æ—¶çš„原因)
    remark?: string; // å¤‡æ³¨
    createTime?: Date; // åˆ›å»ºæ—¶é—´
    lines?: MesWmEquipmentReceiptLineApi.EquipmentReceiptLine[]; // æ˜Žç»†è¡Œ
@@ -53,21 +56,42 @@
  return requestClient.delete(`/mes/wm/equipment-receipt/delete?id=${id}`);
}
/** æŸ¥è¯¢å¯ç”¨çš„设备入库单审批流程定义 */
export function getEquipmentReceiptApproveProcessList() {
  return requestClient.get<{ id: string; name: string; key: string }[]>(
    '/mes/wm/equipment-receipt/approval-process-list',
  );
/**
 * æäº¤è®¾å¤‡å…¥åº“单审批(草稿→审批中)
 *
 * å®¡æ ¸äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€é…ç½®ï¼Œæäº¤æ—¶æ— éœ€æŒ‡å®šï¼›
 * æœªå¯ç”¨å®¡æ‰¹æˆ–未配置有效审核人时后端会抛出业务异常。
 */
export function submitEquipmentReceiptApproval(id: number) {
  return requestClient.put('/mes/wm/equipment-receipt/submit-approval', undefined, {
    params: { id },
  });
}
/** æäº¤è®¾å¤‡å…¥åº“单审批 */
export function submitEquipmentReceiptApproval(
/**
 * èŽ·å¾—è®¾å¤‡å…¥åº“å•çš„å®¡æ ¸äººç¼–å·é›†åˆ
 *
 * ä¾›åˆ—表页判断当前登录用户是否应展示「审核 / å®¡æ ¸ä¸é€šè¿‡ã€æŒ‰é’®ï¼›æœªé…ç½®æ—¶è¿”回空数组。
 */
export function getEquipmentReceiptApproverIds() {
  return requestClient.get<number[]>('/mes/wm/equipment-receipt/approver-ids');
}
/**
 * å®¡æ ¸è®¾å¤‡å…¥åº“单(审批中→已审批/草稿)
 *
 * ä»…审批配置中的审核人可调用(多人时或签);pass=false æ—¶ reviewRemark(不通过原因)必填。
 */
export function auditEquipmentReceipt(
  id: number,
  processDefinitionKey: string,
  pass: boolean,
  reviewRemark?: string,
) {
  return requestClient.put(
    `/mes/wm/equipment-receipt/submit-approval?id=${id}&processDefinitionKey=${processDefinitionKey}`,
  );
  return requestClient.put('/mes/wm/equipment-receipt/audit', {
    id,
    pass,
    reviewRemark,
  });
}
/** æ‰§è¡Œè®¾å¤‡å…¥åº“ */
src/api/mes/wm/itemreceipt/index.ts
@@ -23,6 +23,10 @@
    areaName?: string; // åº“位名称
    receiptDate?: number; // å…¥åº“日期
    status?: number; // çŠ¶æ€
    reviewerId?: number; // å®¡æ ¸äººç”¨æˆ·ç¼–号
    reviewerName?: string; // å®¡æ ¸äººå§“名
    reviewTime?: number; // å®¡æ ¸æ—¶é—´
    reviewRemark?: string; // å®¡æ ¸æ„è§ï¼ˆé€šè¿‡æ—¶çš„æ„è§ / ä¸é€šè¿‡æ—¶çš„原因)
    remark?: string; // å¤‡æ³¨
    createTime?: Date; // åˆ›å»ºæ—¶é—´
  }
@@ -92,12 +96,28 @@
  );
}
/** æäº¤é‡‡è´­å…¥åº“单审批 */
export function submitItemReceiptApproval(
/**
 * èŽ·å¾—é‡‡è´­å…¥åº“å•çš„å®¡æ ¸äººç¼–å·é›†åˆ
 *
 * ä¾›åˆ—表页判断当前登录用户是否应展示「审核 / å®¡æ ¸ä¸é€šè¿‡ã€æŒ‰é’®ï¼›æœªé…ç½®æ—¶è¿”回空数组。
 */
export function getItemReceiptApproverIds() {
  return requestClient.get<number[]>('/mes/wm/item-receipt/approver-ids');
}
/**
 * å®¡æ ¸é‡‡è´­å…¥åº“单(审批中→已审批/草稿)
 *
 * ä»…审批配置中的审核人可调用(多人时或签);pass=false æ—¶ reviewRemark(不通过原因)必填。
 */
export function auditItemReceipt(
  id: number,
  processDefinitionKey: string,
  pass: boolean,
  reviewRemark?: string,
) {
  return requestClient.put(
    `/mes/wm/item-receipt/submit-approval?id=${id}&processDefinitionKey=${processDefinitionKey}`,
  );
  return requestClient.put('/mes/wm/item-receipt/audit', {
    id,
    pass,
    reviewRemark,
  });
}
src/api/mes/wm/miscissue/index.ts
@@ -14,7 +14,10 @@
    sourceDocCode?: string; // æ¥æºå•据编码
    issueDate?: number; // å‡ºåº“日期
    status?: number; // å•据状态
    processInstanceId?: string; // å®¡æ‰¹æµç¨‹å®žä¾‹ç¼–号
    reviewerId?: number; // å®¡æ ¸äººç”¨æˆ·ç¼–号
    reviewerName?: string; // å®¡æ ¸äººå§“名
    reviewTime?: number; // å®¡æ ¸æ—¶é—´
    reviewRemark?: string; // å®¡æ ¸æ„è§ï¼ˆé€šè¿‡æ—¶çš„æ„è§ / ä¸é€šè¿‡æ—¶çš„原因)
    remark?: string; // å¤‡æ³¨
    createTime?: Date; // åˆ›å»ºæ—¶é—´
  }
@@ -50,21 +53,42 @@
  return requestClient.delete(`/mes/wm/misc-issue/delete?id=${id}`);
}
/** æŸ¥è¯¢å¯ç”¨çš„æ‚项出库单审批流程定义 */
export function getMiscIssueApproveProcessList() {
  return requestClient.get<{ id: string; name: string; key: string }[]>(
    '/mes/wm/misc-issue/approval-process-list',
  );
/**
 * æäº¤æ‚项出库单审批(草稿 â†’ å®¡æ‰¹ä¸­ï¼‰
 *
 * å®¡æ ¸äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€é…ç½®ï¼Œæäº¤æ—¶æ— éœ€æŒ‡å®šï¼›
 * æœªå¯ç”¨å®¡æ‰¹æˆ–未配置有效审核人时后端会抛出业务异常。
 */
export function submitMiscIssueApproval(id: number) {
  return requestClient.put('/mes/wm/misc-issue/submit', null, {
    params: { id },
  });
}
/** æäº¤æ‚项出库单审批 */
export function submitMiscIssueApproval(
/**
 * èŽ·å¾—æ‚é¡¹å‡ºåº“å•çš„å®¡æ ¸äººç¼–å·é›†åˆ
 *
 * ä¾›åˆ—表页判断当前登录用户是否应展示「审核 / å®¡æ ¸ä¸é€šè¿‡ã€æŒ‰é’®ï¼›æœªé…ç½®æ—¶è¿”回空数组。
 */
export function getMiscIssueApproverIds() {
  return requestClient.get<number[]>('/mes/wm/misc-issue/approver-ids');
}
/**
 * å®¡æ ¸æ‚项出库单(审批中 â†’ å¾…执行出库 / è‰ç¨¿ï¼‰
 *
 * ä»…审批配置中的审核人可调用(多人时或签);pass=false æ—¶ reviewRemark(不通过原因)必填。
 */
export function auditMiscIssue(
  id: number,
  processDefinitionKey: string,
  pass: boolean,
  reviewRemark?: string,
) {
  return requestClient.put(
    `/mes/wm/misc-issue/submit?id=${id}&processDefinitionKey=${processDefinitionKey}`,
  );
  return requestClient.put('/mes/wm/misc-issue/audit', {
    id,
    pass,
    reviewRemark,
  });
}
/** æ‰§è¡Œå‡ºåº“ */
src/api/mes/wm/miscreceipt/index.ts
@@ -14,7 +14,10 @@
    sourceDocCode?: string; // æ¥æºå•据编码
    receiptDate?: number; // å…¥åº“日期
    status?: number; // å•据状态
    processInstanceId?: string; // å®¡æ‰¹æµç¨‹å®žä¾‹ç¼–号
    reviewerId?: number; // å®¡æ ¸äººç”¨æˆ·ç¼–号
    reviewerName?: string; // å®¡æ ¸äººå§“名
    reviewTime?: number; // å®¡æ ¸æ—¶é—´
    reviewRemark?: string; // å®¡æ ¸æ„è§ï¼ˆé€šè¿‡æ—¶çš„æ„è§ / ä¸é€šè¿‡æ—¶çš„原因)
    remark?: string; // å¤‡æ³¨
    createTime?: Date; // åˆ›å»ºæ—¶é—´
  }
@@ -50,21 +53,42 @@
  return requestClient.delete(`/mes/wm/misc-receipt/delete?id=${id}`);
}
/** æŸ¥è¯¢å¯ç”¨çš„æ‚项入库单审批流程定义 */
export function getMiscReceiptApproveProcessList() {
  return requestClient.get<{ id: string; name: string; key: string }[]>(
    '/mes/wm/misc-receipt/approval-process-list',
  );
/**
 * æäº¤æ‚项入库单审批(草稿 â†’ å®¡æ‰¹ä¸­ï¼‰
 *
 * å®¡æ ¸äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€é…ç½®ï¼Œæäº¤æ—¶æ— éœ€æŒ‡å®šï¼›
 * æœªå¯ç”¨å®¡æ‰¹æˆ–未配置有效审核人时后端会抛出业务异常。
 */
export function submitMiscReceiptApproval(id: number) {
  return requestClient.put('/mes/wm/misc-receipt/submit', null, {
    params: { id },
  });
}
/** æäº¤æ‚项入库单审批 */
export function submitMiscReceiptApproval(
/**
 * èŽ·å¾—æ‚é¡¹å…¥åº“å•çš„å®¡æ ¸äººç¼–å·é›†åˆ
 *
 * ä¾›åˆ—表页判断当前登录用户是否应展示「审核 / å®¡æ ¸ä¸é€šè¿‡ã€æŒ‰é’®ï¼›æœªé…ç½®æ—¶è¿”回空数组。
 */
export function getMiscReceiptApproverIds() {
  return requestClient.get<number[]>('/mes/wm/misc-receipt/approver-ids');
}
/**
 * å®¡æ ¸æ‚项入库单(审批中 â†’ å·²å®¡æ‰¹ / è‰ç¨¿ï¼‰
 *
 * ä»…审批配置中的审核人可调用(多人时或签);pass=false æ—¶ reviewRemark(不通过原因)必填。
 */
export function auditMiscReceipt(
  id: number,
  processDefinitionKey: string,
  pass: boolean,
  reviewRemark?: string,
) {
  return requestClient.put(
    `/mes/wm/misc-receipt/submit?id=${id}&processDefinitionKey=${processDefinitionKey}`,
  );
  return requestClient.put('/mes/wm/misc-receipt/audit', {
    id,
    pass,
    reviewRemark,
  });
}
/** æ‰§è¡Œå…¥åº“ */
src/api/mes/wm/returnvendor/index.ts
@@ -19,7 +19,10 @@
    transportCode?: string; // è¿å•号
    transportTelephone?: string; // è”系电话
    status?: number; // å•据状态
    processInstanceId?: string; // å®¡æ‰¹æµç¨‹å®žä¾‹ç¼–号
    reviewerId?: number; // å®¡æ ¸äººç”¨æˆ·ç¼–号
    reviewerName?: string; // å®¡æ ¸äººå§“名
    reviewTime?: number; // å®¡æ ¸æ—¶é—´
    reviewRemark?: string; // å®¡æ ¸æ„è§ï¼ˆé€šè¿‡æ—¶çš„æ„è§ / ä¸é€šè¿‡æ—¶çš„原因)
    remark?: string; // å¤‡æ³¨
    createTime?: Date; // åˆ›å»ºæ—¶é—´
  }
@@ -55,21 +58,42 @@
  return requestClient.delete(`/mes/wm/return-vendor/delete?id=${id}`);
}
/** æŸ¥è¯¢å¯ç”¨çš„供应商退货单审批流程定义 */
export function getReturnVendorApproveProcessList() {
  return requestClient.get<{ id: string; name: string; key: string }[]>(
    '/mes/wm/return-vendor/approval-process-list',
  );
/**
 * æäº¤ä¾›åº”商退货单审批(草稿 â†’ å¾…拣货)
 *
 * å®¡æ ¸äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€é…ç½®ï¼Œæäº¤æ—¶æ— éœ€æŒ‡å®šï¼›
 * æœªå¯ç”¨å®¡æ‰¹æˆ–未配置有效审核人时后端会抛出业务异常。
 */
export function submitReturnVendorApproval(id: number) {
  return requestClient.put('/mes/wm/return-vendor/submit', null, {
    params: { id },
  });
}
/** æäº¤ä¾›åº”商退货单审批 */
export function submitReturnVendorApproval(
/**
 * èŽ·å¾—ä¾›åº”å•†é€€è´§å•çš„å®¡æ ¸äººç¼–å·é›†åˆ
 *
 * ä¾›åˆ—表页判断当前登录用户是否应展示「审核 / å®¡æ ¸ä¸é€šè¿‡ã€æŒ‰é’®ï¼›æœªé…ç½®æ—¶è¿”回空数组。
 */
export function getReturnVendorApproverIds() {
  return requestClient.get<number[]>('/mes/wm/return-vendor/approver-ids');
}
/**
 * å®¡æ ¸ä¾›åº”商退货单(待拣货 â†’ å¾…执行退货 / è‰ç¨¿ï¼‰
 *
 * ä»…审批配置中的审核人可调用(多人时或签);pass=false æ—¶ reviewRemark(不通过原因)必填。
 */
export function auditReturnVendor(
  id: number,
  processDefinitionKey: string,
  pass: boolean,
  reviewRemark?: string,
) {
  return requestClient.put(
    `/mes/wm/return-vendor/submit?id=${id}&processDefinitionKey=${processDefinitionKey}`,
  );
  return requestClient.put('/mes/wm/return-vendor/audit', {
    id,
    pass,
    reviewRemark,
  });
}
/** æ‰§è¡Œæ‹£è´§ */
src/api/mes/wm/stocktaking/task/index.ts
@@ -20,7 +20,10 @@
    startTime?: number; // å¼€å§‹æ—¶é—´
    endTime?: number; // ç»“束时间
    status?: number; // å•据状态
    processInstanceId?: string; // å®¡æ‰¹æµç¨‹å®žä¾‹ç¼–号
    reviewerId?: number; // å®¡æ ¸äººç”¨æˆ·ç¼–号
    reviewerName?: string; // å®¡æ ¸äººå§“名
    reviewTime?: number; // ç»“果审批提交时间 / å®¡æ ¸æ—¶é—´
    reviewRemark?: string; // å®¡æ ¸æ„è§ï¼ˆé€šè¿‡æ—¶çš„æ„è§ / ä¸é€šè¿‡æ—¶çš„原因)
    remark?: string; // å¤‡æ³¨
    createTime?: string; // åˆ›å»ºæ—¶é—´
  }
@@ -74,21 +77,42 @@
  });
}
/** æŸ¥è¯¢å¯ç”¨çš„盘点结果审批流程定义 */
export function getStockTakingApproveProcessList() {
  return requestClient.get<{ id: string; name: string; key: string }[]>(
    '/mes/wm/stocktaking-task/approval-process-list',
  );
/**
 * æäº¤ç›˜ç‚¹ç»“果审批(盘点中 â†’ ç›˜ç‚¹ä¸­Â·ç»“果审批中)
 *
 * å®¡æ ¸äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€é…ç½®ï¼Œæäº¤æ—¶æ— éœ€æŒ‡å®šï¼›
 * æœªå¯ç”¨å®¡æ‰¹æˆ–未配置有效审核人时后端会抛出业务异常。
 */
export function submitStockTakingApproval(id: number) {
  return requestClient.put('/mes/wm/stocktaking-task/submit-approval', null, {
    params: { id },
  });
}
/** æäº¤ç›˜ç‚¹ç»“果审批 */
export function submitStockTakingApproval(
/**
 * èŽ·å¾—ç›˜ç‚¹ç»“æžœå®¡æ‰¹çš„å®¡æ ¸äººç¼–å·é›†åˆ
 *
 * ä¾›åˆ—表页判断当前登录用户是否应展示「审核 / å®¡æ ¸ä¸é€šè¿‡ã€æŒ‰é’®ï¼›æœªé…ç½®æ—¶è¿”回空数组。
 */
export function getStockTakingApproverIds() {
  return requestClient.get<number[]>('/mes/wm/stocktaking-task/approver-ids');
}
/**
 * å®¡æ ¸ç›˜ç‚¹ç»“果(结果审批中 â†’ å·²å®Œæˆ / ç›˜ç‚¹ä¸­ï¼‰
 *
 * ä»…审批配置中的审核人可调用(多人时或签);pass=false æ—¶ reviewRemark(不通过原因)必填。
 */
export function auditStockTakingResult(
  id: number,
  processDefinitionKey: string,
  pass: boolean,
  reviewRemark?: string,
) {
  return requestClient.put(
    `/mes/wm/stocktaking-task/submit-approval?id=${id}&processDefinitionKey=${processDefinitionKey}`,
  );
  return requestClient.put('/mes/wm/stocktaking-task/audit', {
    id,
    pass,
    reviewRemark,
  });
}
/** å¯¼å‡ºç›˜ç‚¹ä»»åŠ¡ */
src/packages/constants/src/biz-mes-enum.ts
@@ -649,3 +649,18 @@
  FINISHED: MesOrderStatusConstants.FINISHED,
  CANCELED: MesOrderStatusConstants.CANCELLED,
} as const;
/**
 * MES äº§å“æŠ€æœ¯ç¡®è®¤å‡½çŠ¶æ€æžšä¸¾
 *
 * çŠ¶æ€ç”±ä¸šåŠ¡æµç¨‹é©±åŠ¨ï¼Œå‰ç«¯ä¸æä¾›æ‰‹å·¥é€‰æ‹©ï¼š
 * è‰ç¨¿ --提交审核(指定审核人)--> å¾…审核 --审核通过--> å·²å®¡æ ¸ --客户确认--> å®¢æˆ·å·²ç¡®è®¤
 * å¾…审核 --审核不通过--> å®¡æ ¸ä¸é€šè¿‡ï¼Œå¯ä¿®æ”¹åŽé‡æ–°æäº¤ã€‚
 */
export const MesPdTechConfirmStatusEnum = {
  DRAFT: 0, // è‰ç¨¿ï¼Œå¯ä¿®æ”¹ã€åˆ é™¤ã€æäº¤å®¡æ ¸
  PENDING_AUDIT: 1, // å¾…审核,单据锁定,仅指定审核人可操作
  AUDITED: 2, // å·²å®¡æ ¸ï¼Œå¯å®¢æˆ·ç¡®è®¤
  CUSTOMER_CONFIRMED: 3, // å®¢æˆ·å·²ç¡®è®¤ï¼Œç»ˆæ€
  REJECTED: 4, // å®¡æ ¸ä¸é€šè¿‡ï¼Œå¯ä¿®æ”¹åŽé‡æ–°æäº¤
} as const;
src/views/crm/cancellation/modules/audit-modal.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,99 @@
<script lang="ts" setup>
  import type { VbenFormSchema } from '#/adapter/form';
  import type { CrmCancellationApi } from '#/api/crm/cancellation';
  import { computed, ref } from 'vue';
  import { useVbenModal } from '@vben/common-ui';
  import { message } from 'ant-design-vue';
  import { useVbenForm } from '#/adapter/form';
  import { auditCancellation } from '#/api/crm/cancellation';
  const emit = defineEmits(['success']);
  const pass = ref(true); // true=审核通过 / false=审核不通过
  const rowData = ref<CrmCancellationApi.Cancellation>();
  const getTitle = computed(() => (pass.value ? '审核通过' : '审核不通过'));
  const [Form, formApi] = useVbenForm({
    commonConfig: {
      componentProps: {
        class: 'w-full',
      },
      formItemClass: 'col-span-1',
      labelWidth: 90,
    },
    wrapperClass: 'grid-cols-1',
    layout: 'horizontal',
    schema: [],
    showDefaultActions: false,
  });
  /** å®¡æ ¸è¡¨å•字段:不通过时必须填写原因 */
  function useAuditSchema(): VbenFormSchema[] {
    return [
      {
        fieldName: 'reviewRemark',
        label: '审核意见',
        component: 'Textarea',
        formItemClass: 'col-span-1',
        componentProps: {
          rows: 3,
          placeholder: pass.value
            ? '请输入审核意见(可选)'
            : '请输入不通过原因(必填)',
        },
        rules: pass.value ? '' : 'required',
      },
    ];
  }
  const [Modal, modalApi] = useVbenModal({
    async onConfirm() {
      const { valid } = await formApi.validate();
      if (!valid) {
        return;
      }
      modalApi.lock();
      const values = await formApi.getValues();
      try {
        await auditCancellation(
          rowData.value!.id!,
          pass.value,
          values.reviewRemark,
        );
        await modalApi.close();
        emit('success');
        message.success(pass.value ? '审核通过' : '已退回,可修改后重新提交');
      } finally {
        modalApi.unlock();
      }
    },
    async onOpenChange(isOpen: boolean) {
      if (!isOpen) {
        rowData.value = undefined;
        return;
      }
      const data = modalApi.getData<{
        pass: boolean;
        row: CrmCancellationApi.Cancellation;
      }>();
      rowData.value = data.row;
      pass.value = data.pass;
      formApi.setState({ schema: useAuditSchema() });
      formApi.resetForm();
    },
  });
</script>
<template>
  <Modal :title="getTitle" class="w-2/5">
    <div v-if="rowData" class="mx-4 mb-4 text-sm text-gray-600">
      <div>销户编号:{{ rowData.cancellationNo }}</div>
      <div>客户名称:{{ rowData.customerName }}</div>
    </div>
    <Form class="mx-4" />
  </Modal>
</template>
src/views/crm/contract/index.vue
@@ -6,35 +6,66 @@
import { useRouter } from 'vue-router';
import { Page, useVbenModal } from '@vben/common-ui';
import { useUserStore } from '@vben/stores';
import { downloadFileFromBlobPart } from '@vben/utils';
import { Button, message, Modal, Select, Tabs, Tag, Tooltip } from 'ant-design-vue';
import { Button, message, Tabs, Tag, Tooltip } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
  deleteContract,
  exportContract,
  generateSaleOrder,
  getContractApproveProcessList,
  getContractApproverIds,
  getContractPage,
  submitContract,
} from '#/api/crm/contract';
import { $t } from '#/locales';
import { useGridColumns, useGridFormSchema } from './data';
import AuditModal from './modules/audit-modal.vue';
import Form from './modules/form.vue';
const { push } = useRouter();
const sceneType = ref('1');
const userStore = useUserStore();
const currentUserId = userStore.userInfo?.id; // å½“前登录用户 ID,用于审批人权限判断
/** å®¡æ‰¹é…ç½®ä¸­è¯¥ä¸šåŠ¡çš„å®¡æ‰¹äººç¼–å·é›†åˆï¼Œå†³å®šã€Œå®¡æ ¸ / å®¡æ ¸ä¸é€šè¿‡ã€æŒ‰é’®æ˜¯å¦å±•示 */
const approverIds = ref<number[]>([]);
/** æ‹‰å–审批人集合;失败时保持为空(按钮不展示),不阻断列表加载 */
async function loadApproverIds() {
  try {
    approverIds.value = (await getContractApproverIds()) ?? [];
  } catch {
    approverIds.value = [];
  }
}
/** å½“前登录用户是否为该业务的审批人,且合同处于审批中(10) */
function canAudit(row: CrmContractApi.Contract) {
  return (
    row.auditStatus === 10 &&
    !!currentUserId &&
    approverIds.value.includes(currentUserId)
  );
}
const [FormModal, formModalApi] = useVbenModal({
  connectedComponent: Form,
  destroyOnClose: true,
});
/** åˆ·æ–°è¡¨æ ¼ */
const [AuditModalComp, auditModalApi] = useVbenModal({
  connectedComponent: AuditModal,
  destroyOnClose: true,
});
/** åˆ·æ–°è¡¨æ ¼ä¸Žå®¡æ‰¹äººé›†åˆ */
function handleRefresh() {
  gridApi.query();
  loadApproverIds();
}
/** å¤„理场景类型的切换 */
@@ -78,35 +109,24 @@
  }
}
/** æäº¤å®¡æ‰¹å¼¹çª— */
const submitModalVisible = ref(false);
const currentRow = ref<CrmContractApi.Contract>();
const processList = ref<CrmContractApi.ApproveProcess[]>([]);
const selectedProcessKey = ref<string>();
const submitLoading = ref(false);
/** æäº¤å®¡æ‰¹ - å®¡æ‰¹äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€ç»Ÿä¸€æŒ‡å®šï¼Œæ­¤å¤„无需选择 */
async function handleSubmit(row: CrmContractApi.Contract) {
  const res = await getContractApproveProcessList();
  processList.value = res;
  currentRow.value = row;
  selectedProcessKey.value = undefined;
  submitModalVisible.value = true;
}
async function confirmSubmit() {
  if (!selectedProcessKey.value) {
    message.warning('请选择审批流程');
    return;
  }
  submitLoading.value = true;
  const hideLoading = message.loading({
    content: '正在提交审批...',
    duration: 0,
  });
  try {
    await submitContract(currentRow.value!.id!, selectedProcessKey.value);
    await submitContract(row.id!);
    message.success('提交审批成功');
    submitModalVisible.value = false;
    handleRefresh();
  } finally {
    submitLoading.value = false;
    hideLoading();
  }
}
/** å®¡æ ¸ - é€šè¿‡ / ä¸é€šè¿‡ */
function handleAudit(row: CrmContractApi.Contract, pass: boolean) {
  auditModalApi.setData({ pass, row }).open();
}
/** æŸ¥çœ‹åˆåŒè¯¦æƒ… */
@@ -127,14 +147,6 @@
/** æŸ¥çœ‹å•†æœºè¯¦æƒ… */
function handleBusinessDetail(row: CrmContractApi.Contract) {
  push({ name: 'CrmBusinessDetail', params: { id: row.businessId } });
}
/** æŸ¥çœ‹å®¡æ‰¹è¯¦æƒ… */
function handleProcessDetail(row: CrmContractApi.Contract) {
  push({
    name: 'BpmProcessInstanceDetail',
    query: { id: row.processInstanceId },
  });
}
/** ç”Ÿæˆé”€å”®è®¢å• */
@@ -200,11 +212,15 @@
    },
  } as VxeTableGridOptions<CrmContractApi.Contract>,
});
// è¿›å…¥é¡µé¢å³æ‹‰å–审批人集合,用于控制审核按钮显隐
loadApproverIds();
</script>
<template>
  <Page auto-content-height>
    <FormModal @success="handleRefresh" />
    <AuditModalComp @success="handleRefresh" />
    <Grid>
      <template #toolbar-actions>
        <Tabs class="w-full" @change="handleChangeSceneType">
@@ -292,15 +308,31 @@
              label: '提交审批',
              type: 'link',
              auth: ['crm:contract:update'],
              onClick: handleSubmit.bind(null, row),
              ifShow: row.auditStatus === 0,
              // å®¡æ‰¹äººç”±å®¡æ‰¹é…ç½®ç»Ÿä¸€æŒ‡å®šï¼Œæäº¤å‰ä»…二次确认,不再弹选流程框
              popConfirm: {
                title: '确认提交审批?提交后由审批配置中指定的审批人处理。',
                confirm: handleSubmit.bind(null, row),
              },
              // æœªæäº¤(0) ä¸Ž å®¡æ ¸ä¸é€šè¿‡(30) å‡å¯æäº¤ï¼ŒåŽè€…需修改后重新提交
              ifShow: row.auditStatus === 0 || row.auditStatus === 30,
            },
            {
              label: '查看审批',
              label: '审核',
              type: 'link',
              auth: ['crm:contract:update'],
              onClick: handleProcessDetail.bind(null, row),
              ifShow: row.auditStatus !== 0,
              icon: ACTION_ICON.AUDIT,
              auth: ['crm:contract:audit'],
              // ä»…「审批中 + å½“前登录用户在审批配置的审批人名单内」才显示
              ifShow: canAudit(row),
              onClick: handleAudit.bind(null, row, true),
            },
            {
              label: '审核不通过',
              type: 'link',
              danger: true,
              auth: ['crm:contract:audit'],
              // åŒä¸Šï¼Œä¸Žã€Œå®¡æ ¸ã€æˆå¯¹å‡ºçް
              ifShow: canAudit(row),
              onClick: handleAudit.bind(null, row, false),
            },
            {
              label: '生成销售订单',
@@ -332,27 +364,5 @@
      </template>
    </Grid>
    <!-- æäº¤å®¡æ‰¹å¼¹çª— -->
    <Modal
      v-model:open="submitModalVisible"
      title="选择审批流程"
      width="400px"
      @ok="confirmSubmit"
      :confirmLoading="submitLoading"
    >
      <Select
        v-model:value="selectedProcessKey"
        placeholder="请选择审批流程"
        class="w-full"
      >
        <Select.Option
          v-for="item in processList"
          :key="item.key"
          :value="item.key"
        >
          {{ item.name }}
        </Select.Option>
      </Select>
    </Modal>
  </Page>
</template>
src/views/crm/contract/modules/audit-modal.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,95 @@
<script lang="ts" setup>
  import type { VbenFormSchema } from '#/adapter/form';
  import type { CrmContractApi } from '#/api/crm/contract';
  import { computed, ref } from 'vue';
  import { useVbenModal } from '@vben/common-ui';
  import { message } from 'ant-design-vue';
  import { useVbenForm } from '#/adapter/form';
  import { auditContract } from '#/api/crm/contract';
  const emit = defineEmits(['success']);
  const pass = ref(true); // true=审核通过 / false=审核不通过
  const rowData = ref<CrmContractApi.Contract>();
  const getTitle = computed(() => (pass.value ? '审核通过' : '审核不通过'));
  const [Form, formApi] = useVbenForm({
    commonConfig: {
      componentProps: {
        class: 'w-full',
      },
      formItemClass: 'col-span-1',
      labelWidth: 90,
    },
    wrapperClass: 'grid-cols-1',
    layout: 'horizontal',
    schema: [],
    showDefaultActions: false,
  });
  /** å®¡æ ¸è¡¨å•字段:不通过时必须填写原因 */
  function useAuditSchema(): VbenFormSchema[] {
    return [
      {
        fieldName: 'reviewRemark',
        label: '审核意见',
        component: 'Textarea',
        formItemClass: 'col-span-1',
        componentProps: {
          rows: 3,
          placeholder: pass.value
            ? '请输入审核意见(可选)'
            : '请输入不通过原因(必填)',
        },
        rules: pass.value ? '' : 'required',
      },
    ];
  }
  const [Modal, modalApi] = useVbenModal({
    async onConfirm() {
      const { valid } = await formApi.validate();
      if (!valid) {
        return;
      }
      modalApi.lock();
      const values = await formApi.getValues();
      try {
        await auditContract(rowData.value!.id!, pass.value, values.reviewRemark);
        await modalApi.close();
        emit('success');
        message.success(pass.value ? '审核通过' : '已退回,可修改后重新提交');
      } finally {
        modalApi.unlock();
      }
    },
    async onOpenChange(isOpen: boolean) {
      if (!isOpen) {
        rowData.value = undefined;
        return;
      }
      const data = modalApi.getData<{
        pass: boolean;
        row: CrmContractApi.Contract;
      }>();
      rowData.value = data.row;
      pass.value = data.pass;
      formApi.setState({ schema: useAuditSchema() });
      formApi.resetForm();
    },
  });
</script>
<template>
  <Modal :title="getTitle" class="w-2/5">
    <div v-if="rowData" class="mx-4 mb-4 text-sm text-gray-600">
      <div>合同名称:{{ rowData.name }}</div>
      <div>合同编号:{{ rowData.no }}</div>
    </div>
    <Form class="mx-4" />
  </Modal>
</template>
src/views/crm/invoice/index.vue
@@ -6,6 +6,7 @@
import { useRouter } from 'vue-router';
import { Page, useVbenModal } from '@vben/common-ui';
import { useUserStore } from '@vben/stores';
import { downloadFileFromBlobPart } from '@vben/utils';
import { Button, message, Tag, Tabs } from 'ant-design-vue';
@@ -14,25 +15,56 @@
import {
  deleteInvoice,
  exportInvoice,
  getInvoiceApproverIds,
  getInvoicePage,
  submitInvoice,
} from '#/api/crm/invoice';
import { $t } from '#/locales';
import { useGridColumns, useGridFormSchema } from './data';
import AuditModal from './modules/audit-modal.vue';
import Form from './modules/form.vue';
const { push } = useRouter();
const sceneType = ref('1');
const userStore = useUserStore();
const currentUserId = userStore.userInfo?.id; // å½“前登录用户 ID,用于审批人权限判断
/** å®¡æ‰¹é…ç½®ä¸­è¯¥ä¸šåŠ¡çš„å®¡æ‰¹äººç¼–å·é›†åˆï¼Œå†³å®šã€Œå®¡æ ¸ / å®¡æ ¸ä¸é€šè¿‡ã€æŒ‰é’®æ˜¯å¦å±•示 */
const approverIds = ref<number[]>([]);
/** æ‹‰å–审批人集合;失败时保持为空(按钮不展示),不阻断列表加载 */
async function loadApproverIds() {
  try {
    approverIds.value = (await getInvoiceApproverIds()) ?? [];
  } catch {
    approverIds.value = [];
  }
}
/** å½“前登录用户是否为该业务的审批人,且开票处于审批中(10) */
function canAudit(row: CrmInvoiceApi.Invoice) {
  return (
    row.auditStatus === 10 &&
    !!currentUserId &&
    approverIds.value.includes(currentUserId)
  );
}
const [FormModal, formModalApi] = useVbenModal({
  connectedComponent: Form,
  destroyOnClose: true,
});
/** åˆ·æ–°è¡¨æ ¼ */
const [AuditModalComp, auditModalApi] = useVbenModal({
  connectedComponent: AuditModal,
  destroyOnClose: true,
});
/** åˆ·æ–°è¡¨æ ¼ä¸Žå®¡æ‰¹äººé›†åˆ */
function handleRefresh() {
  gridApi.query();
  loadApproverIds();
}
/** å¤„理场景类型的切换 */
@@ -76,19 +108,24 @@
  }
}
/** æäº¤å®¡æ ¸ */
/** æäº¤å®¡æ‰¹ - å®¡æ‰¹äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€ç»Ÿä¸€æŒ‡å®šï¼Œæ­¤å¤„无需选择 */
async function handleSubmit(row: CrmInvoiceApi.Invoice) {
  const hideLoading = message.loading({
    content: '提交审核中...',
    content: '正在提交审批...',
    duration: 0,
  });
  try {
    await submitInvoice(row.id!);
    message.success('提交审核成功');
    message.success('提交审批成功');
    handleRefresh();
  } finally {
    hideLoading();
  }
}
/** å®¡æ ¸ - é€šè¿‡ / ä¸é€šè¿‡ */
function handleAudit(row: CrmInvoiceApi.Invoice, pass: boolean) {
  auditModalApi.setData({ pass, row }).open();
}
/** æŸ¥çœ‹å¼€ç¥¨è¯¦æƒ… */
@@ -104,14 +141,6 @@
/** æŸ¥çœ‹åˆåŒè¯¦æƒ… */
function handleContractDetail(row: CrmInvoiceApi.Invoice) {
  push({ name: 'CrmContractDetail', params: { id: row.contractId } });
}
/** æŸ¥çœ‹å®¡æ‰¹è¯¦æƒ… */
function handleProcessDetail(row: CrmInvoiceApi.Invoice) {
  push({
    name: 'BpmProcessInstanceDetail',
    query: { id: row.processInstanceId },
  });
}
const [Grid, gridApi] = useVbenVxeGrid({
@@ -144,11 +173,15 @@
    },
  } as VxeTableGridOptions<CrmInvoiceApi.Invoice>,
});
// è¿›å…¥é¡µé¢å³æ‹‰å–审批人集合,用于控制审核按钮显隐
loadApproverIds();
</script>
<template>
  <Page auto-content-height>
    <FormModal @success="handleRefresh" />
    <AuditModalComp @success="handleRefresh" />
    <Grid>
      <template #toolbar-actions>
        <Tabs class="w-full" @change="handleChangeSceneType">
@@ -227,18 +260,34 @@
              onClick: handleEdit.bind(null, row),
            },
            {
              label: '提交审核',
              label: '提交审批',
              type: 'link',
              auth: ['crm:invoice:update'],
              onClick: handleSubmit.bind(null, row),
              ifShow: row.auditStatus === 0,
              // å®¡æ‰¹äººç”±å®¡æ‰¹é…ç½®ç»Ÿä¸€æŒ‡å®šï¼Œæäº¤å‰ä»…二次确认,不再弹选流程框
              popConfirm: {
                title: '确认提交审批?提交后由审批配置中指定的审批人处理。',
                confirm: handleSubmit.bind(null, row),
              },
              // æœªæäº¤(0) ä¸Ž å®¡æ ¸ä¸é€šè¿‡(30) å‡å¯æäº¤ï¼ŒåŽè€…需修改后重新提交
              ifShow: row.auditStatus === 0 || row.auditStatus === 30,
            },
            {
              label: '查看审批',
              label: '审核',
              type: 'link',
              auth: ['crm:invoice:update'],
              onClick: handleProcessDetail.bind(null, row),
              ifShow: row.auditStatus !== 0 && !!row.processInstanceId,
              icon: ACTION_ICON.AUDIT,
              auth: ['crm:invoice:audit'],
              // ä»…「审批中 + å½“前登录用户在审批配置的审批人名单内」才显示
              ifShow: canAudit(row),
              onClick: handleAudit.bind(null, row, true),
            },
            {
              label: '审核不通过',
              type: 'link',
              danger: true,
              auth: ['crm:invoice:audit'],
              // åŒä¸Šï¼Œä¸Žã€Œå®¡æ ¸ã€æˆå¯¹å‡ºçް
              ifShow: canAudit(row),
              onClick: handleAudit.bind(null, row, false),
            },
            {
              label: $t('common.delete'),
src/views/crm/invoice/modules/audit-modal.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,95 @@
<script lang="ts" setup>
  import type { VbenFormSchema } from '#/adapter/form';
  import type { CrmInvoiceApi } from '#/api/crm/invoice';
  import { computed, ref } from 'vue';
  import { useVbenModal } from '@vben/common-ui';
  import { message } from 'ant-design-vue';
  import { useVbenForm } from '#/adapter/form';
  import { auditInvoice } from '#/api/crm/invoice';
  const emit = defineEmits(['success']);
  const pass = ref(true); // true=审核通过 / false=审核不通过
  const rowData = ref<CrmInvoiceApi.Invoice>();
  const getTitle = computed(() => (pass.value ? '审核通过' : '审核不通过'));
  const [Form, formApi] = useVbenForm({
    commonConfig: {
      componentProps: {
        class: 'w-full',
      },
      formItemClass: 'col-span-1',
      labelWidth: 90,
    },
    wrapperClass: 'grid-cols-1',
    layout: 'horizontal',
    schema: [],
    showDefaultActions: false,
  });
  /** å®¡æ ¸è¡¨å•字段:不通过时必须填写原因 */
  function useAuditSchema(): VbenFormSchema[] {
    return [
      {
        fieldName: 'reviewRemark',
        label: '审核意见',
        component: 'Textarea',
        formItemClass: 'col-span-1',
        componentProps: {
          rows: 3,
          placeholder: pass.value
            ? '请输入审核意见(可选)'
            : '请输入不通过原因(必填)',
        },
        rules: pass.value ? '' : 'required',
      },
    ];
  }
  const [Modal, modalApi] = useVbenModal({
    async onConfirm() {
      const { valid } = await formApi.validate();
      if (!valid) {
        return;
      }
      modalApi.lock();
      const values = await formApi.getValues();
      try {
        await auditInvoice(rowData.value!.id!, pass.value, values.reviewRemark);
        await modalApi.close();
        emit('success');
        message.success(pass.value ? '审核通过' : '已退回,可修改后重新提交');
      } finally {
        modalApi.unlock();
      }
    },
    async onOpenChange(isOpen: boolean) {
      if (!isOpen) {
        rowData.value = undefined;
        return;
      }
      const data = modalApi.getData<{
        pass: boolean;
        row: CrmInvoiceApi.Invoice;
      }>();
      rowData.value = data.row;
      pass.value = data.pass;
      formApi.setState({ schema: useAuditSchema() });
      formApi.resetForm();
    },
  });
</script>
<template>
  <Modal :title="getTitle" class="w-2/5">
    <div v-if="rowData" class="mx-4 mb-4 text-sm text-gray-600">
      <div>开票编号:{{ rowData.no }}</div>
      <div>客户名称:{{ rowData.customerName }}</div>
    </div>
    <Form class="mx-4" />
  </Modal>
</template>
src/views/crm/refund/modules/audit-modal.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,95 @@
<script lang="ts" setup>
  import type { VbenFormSchema } from '#/adapter/form';
  import type { CrmRefundApi } from '#/api/crm/refund';
  import { computed, ref } from 'vue';
  import { useVbenModal } from '@vben/common-ui';
  import { message } from 'ant-design-vue';
  import { useVbenForm } from '#/adapter/form';
  import { auditRefund } from '#/api/crm/refund';
  const emit = defineEmits(['success']);
  const pass = ref(true); // true=审核通过 / false=审核不通过
  const rowData = ref<CrmRefundApi.Refund>();
  const getTitle = computed(() => (pass.value ? '审核通过' : '审核不通过'));
  const [Form, formApi] = useVbenForm({
    commonConfig: {
      componentProps: {
        class: 'w-full',
      },
      formItemClass: 'col-span-1',
      labelWidth: 90,
    },
    wrapperClass: 'grid-cols-1',
    layout: 'horizontal',
    schema: [],
    showDefaultActions: false,
  });
  /** å®¡æ ¸è¡¨å•字段:不通过时必须填写原因 */
  function useAuditSchema(): VbenFormSchema[] {
    return [
      {
        fieldName: 'reviewRemark',
        label: '审核意见',
        component: 'Textarea',
        formItemClass: 'col-span-1',
        componentProps: {
          rows: 3,
          placeholder: pass.value
            ? '请输入审核意见(可选)'
            : '请输入不通过原因(必填)',
        },
        rules: pass.value ? '' : 'required',
      },
    ];
  }
  const [Modal, modalApi] = useVbenModal({
    async onConfirm() {
      const { valid } = await formApi.validate();
      if (!valid) {
        return;
      }
      modalApi.lock();
      const values = await formApi.getValues();
      try {
        await auditRefund(rowData.value!.id!, pass.value, values.reviewRemark);
        await modalApi.close();
        emit('success');
        message.success(pass.value ? '审核通过' : '已退回,可修改后重新提交');
      } finally {
        modalApi.unlock();
      }
    },
    async onOpenChange(isOpen: boolean) {
      if (!isOpen) {
        rowData.value = undefined;
        return;
      }
      const data = modalApi.getData<{
        pass: boolean;
        row: CrmRefundApi.Refund;
      }>();
      rowData.value = data.row;
      pass.value = data.pass;
      formApi.setState({ schema: useAuditSchema() });
      formApi.resetForm();
    },
  });
</script>
<template>
  <Modal :title="getTitle" class="w-2/5">
    <div v-if="rowData" class="mx-4 mb-4 text-sm text-gray-600">
      <div>退费单号:{{ rowData.refundNo }}</div>
      <div>客户名称:{{ rowData.customerName }}</div>
    </div>
    <Form class="mx-4" />
  </Modal>
</template>
src/views/erp/finance/payment/index.vue
@@ -2,10 +2,10 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { ErpFinancePaymentApi } from '#/api/erp/finance/payment';
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import { onMounted, ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { useUserStore } from '@vben/stores';
import { downloadFileFromBlobPart, isEmpty } from '@vben/utils';
import { message } from 'ant-design-vue';
@@ -14,33 +14,56 @@
import {
  deleteFinancePayment,
  exportFinancePayment,
  getFinancePaymentApproveProcessList,
  getFinancePaymentApproverUserIds,
  getFinancePaymentPage,
  submitFinancePayment,
} from '#/api/erp/finance/payment';
import { $t } from '#/locales';
import { useGridColumns, useGridFormSchema } from './data';
import AuditModal from './modules/audit-modal.vue';
import Form from './modules/form.vue';
import ProcessSelectModal from './modules/process-select-modal.vue';
/** ERP ä»˜æ¬¾å•列表 */
defineOptions({ name: 'ErpFinancePayment' });
const router = useRouter();
const userStore = useUserStore();
/** å½“前业务类型的审批人编号集合,用于判断是否展示审核按钮 */
const approverIds = ref<number[]>([]);
/** å½“前登录用户编号 */
const currentUserId = userStore.userInfo?.id;
const [FormModal, formModalApi] = useVbenModal({
  connectedComponent: Form,
  destroyOnClose: true,
});
const [ProcessModal, processModalApi] = useVbenModal({
  connectedComponent: ProcessSelectModal,
const [AuditModalComp, auditModalApi] = useVbenModal({
  connectedComponent: AuditModal,
  destroyOnClose: true,
});
/** åˆ·æ–°è¡¨æ ¼ */
/** åˆ·æ–°è¡¨æ ¼ã€å®¡æ‰¹äººé›†åˆ */
function handleRefresh() {
  gridApi.query();
  loadApproverIds();
}
/** åŠ è½½å½“å‰ä¸šåŠ¡ç±»åž‹çš„å®¡æ‰¹äººç¼–å·é›†åˆ */
async function loadApproverIds() {
  try {
    approverIds.value = (await getFinancePaymentApproverUserIds()) ?? [];
  } catch {
    approverIds.value = [];
  }
}
/** å½“前用户是否为该业务类型的审批人,且单据处于审批中 */
function canAudit(row: ErpFinancePaymentApi.FinancePayment) {
  return (
    row.status === 15 && approverIds.value.includes(currentUserId as number)
  );
}
/** å¯¼å‡ºè¡¨æ ¼ */
@@ -74,36 +97,43 @@
  }
}
/** æäº¤å®¡æ‰¹ - å…ˆèŽ·å–æµç¨‹åˆ—è¡¨ */
/** æäº¤å®¡æ ¸ - åŽç«¯æŒ‰å®¡æ‰¹é…ç½®æ ¡éªŒå®¡æ‰¹äººï¼Œæ— éœ€å‰ç«¯é€‰æ‹©æµç¨‹ */
async function handleSubmit(row: ErpFinancePaymentApi.FinancePayment) {
  const hideLoading = message.loading({
    content: '获取审批流程...',
    content: '正在提交审核...',
    duration: 0,
  });
  try {
    const processList = await getFinancePaymentApproveProcessList();
    hideLoading();
    if (!processList || processList.length === 0) {
      message.warning('暂无可用审批流程,请先配置 BPM æµç¨‹');
      return;
    }
    // æ‰“开流程选择弹窗
    processModalApi.setData({ id: row.id, processList }).open();
  } catch {
    await submitFinancePayment(row.id!);
    message.success('提交成功,等待审批人审核');
    handleRefresh();
  } finally {
    hideLoading();
  }
}
/** æŸ¥çœ‹å®¡æ‰¹è¯¦æƒ… */
function handleProcessDetail(row: ErpFinancePaymentApi.FinancePayment) {
  if (!row.processInstanceId) {
    message.warning('该付款单尚未提交审批');
/** å®¡æ ¸ - é€šè¿‡ / ä¸é€šè¿‡ */
function handleAudit(row: ErpFinancePaymentApi.FinancePayment, pass: boolean) {
  auditModalApi.setData({ pass, row }).open();
}
/** æŸ¥çœ‹å®¡æ‰¹è®°å½• - è½»é‡çº§å®¡æ‰¹ä¸å†è·³è½¬ BPM æµç¨‹è¯¦æƒ… */
function handleViewProcess(row: ErpFinancePaymentApi.FinancePayment) {
  const parts: string[] = [];
  if (row.reviewerName) {
    parts.push(`审核人:${row.reviewerName}`);
  }
  if (row.reviewTime) {
    parts.push(`审核时间:${row.reviewTime}`);
  }
  if (row.reviewRemark) {
    parts.push(`审核意见:${row.reviewRemark}`);
  }
  if (parts.length === 0) {
    message.info('该付款单暂无审核记录');
    return;
  }
  router.push({
    name: 'BpmProcessInstanceDetail',
    query: { id: row.processInstanceId },
  });
  message.info(parts.join(' '));
}
const checkedIds = ref<number[]>([]);
@@ -153,11 +183,15 @@
    checkboxChange: handleRowCheckboxChange,
  },
});
onMounted(() => {
  loadApproverIds();
});
</script>
<template>
  <Page auto-content-height><FormModal @success="handleRefresh" />
    <ProcessModal @success="handleRefresh" />
    <AuditModalComp @success="handleRefresh" />
    <Grid table-title="付款单列表">
      <template #toolbar-tools>
        <TableAction
@@ -221,16 +255,35 @@
              type: 'link',
              icon: ACTION_ICON.AUDIT,
              auth: ['erp:finance-payment:update'],
              // æœªå®¡æ ¸ï¼ˆå«å®¡æ ¸ä¸é€šè¿‡é€€å›žï¼‰å‡å¯æäº¤ï¼Œé€€å›žåŽéœ€ä¿®æ”¹å†é‡æ–°æäº¤
              ifShow: () => row.status === 10,
              onClick: handleSubmit.bind(null, row),
            },
            {
              label: '审核',
              type: 'link',
              icon: ACTION_ICON.AUDIT,
              auth: ['erp:finance-payment:audit'],
              // ä»…「审批中 + å½“前登录用户是审批人」才显示,其他人看不到该按钮
              ifShow: canAudit(row),
              onClick: handleAudit.bind(null, row, true),
            },
            {
              label: '审核不通过',
              type: 'link',
              danger: true,
              icon: ACTION_ICON.AUDIT,
              auth: ['erp:finance-payment:audit'],
              ifShow: canAudit(row),
              onClick: handleAudit.bind(null, row, false),
            },
            {
              label: '查看审批',
              type: 'link',
              icon: ACTION_ICON.VIEW,
              auth: ['erp:finance-payment:query'],
              ifShow: () => row.status !== 10 && !!row.processInstanceId,
              onClick: handleProcessDetail.bind(null, row),
              ifShow: () => row.status === 20 || !!row.reviewRemark,
              onClick: handleViewProcess.bind(null, row),
            },
            {
              label: $t('common.delete'),
src/views/erp/finance/payment/modules/audit-modal.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,99 @@
<script lang="ts" setup>
  import type { VbenFormSchema } from '#/adapter/form';
  import type { ErpFinancePaymentApi } from '#/api/erp/finance/payment';
  import { computed, ref } from 'vue';
  import { useVbenModal } from '@vben/common-ui';
  import { message } from 'ant-design-vue';
  import { useVbenForm } from '#/adapter/form';
  import { auditFinancePayment } from '#/api/erp/finance/payment';
  const emit = defineEmits(['success']);
  const pass = ref(true); // true=审核通过 / false=审核不通过
  const rowData = ref<ErpFinancePaymentApi.FinancePayment>();
  const getTitle = computed(() => (pass.value ? '审核通过' : '审核不通过'));
  const [Form, formApi] = useVbenForm({
    commonConfig: {
      componentProps: {
        class: 'w-full',
      },
      formItemClass: 'col-span-1',
      labelWidth: 130,
    },
    wrapperClass: 'grid-cols-1',
    layout: 'horizontal',
    schema: [],
    showDefaultActions: false,
  });
  /** å®¡æ ¸è¡¨å•字段:不通过时必须填写原因 */
  function useAuditSchema(): VbenFormSchema[] {
    return [
      {
        fieldName: 'reviewRemark',
        label: '审核意见',
        component: 'Textarea',
        formItemClass: 'col-span-1',
        componentProps: {
          rows: 3,
          placeholder: pass.value
            ? '请输入审核意见(可选)'
            : '请输入不通过原因(必填)',
        },
        rules: pass.value ? '' : 'required',
      },
    ];
  }
  const [Modal, modalApi] = useVbenModal({
    async onConfirm() {
      const { valid } = await formApi.validate();
      if (!valid) {
        return;
      }
      modalApi.lock();
      const values = await formApi.getValues();
      try {
        await auditFinancePayment(
          rowData.value!.id!,
          pass.value,
          values.reviewRemark,
        );
        await modalApi.close();
        emit('success');
        message.success(pass.value ? '审核通过' : '已退回,可修改后重新提交');
      } finally {
        modalApi.unlock();
      }
    },
    async onOpenChange(isOpen: boolean) {
      if (!isOpen) {
        rowData.value = undefined;
        return;
      }
      const data = modalApi.getData<{
        pass: boolean;
        row: ErpFinancePaymentApi.FinancePayment;
      }>();
      rowData.value = data.row;
      pass.value = data.pass;
      formApi.setState({ schema: useAuditSchema() });
      formApi.resetForm();
    },
  });
</script>
<template>
  <Modal :title="getTitle" class="w-2/5">
    <div v-if="rowData" class="mx-4 mb-4 text-sm text-gray-600">
      <div>付款单号:{{ rowData.no }}</div>
      <div>供应商:{{ rowData.supplierName || '-' }}</div>
    </div>
    <Form class="mx-4" />
  </Modal>
</template>
src/views/erp/finance/payment/modules/process-select-modal.vue
ÎļþÒÑɾ³ý
src/views/erp/purchase/request/index.vue
@@ -2,10 +2,11 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { ErpPurchaseRequestApi } from '#/api/erp/purchase/request';
import { ref } from 'vue';
import { onMounted, ref } from 'vue';
import { useRouter } from 'vue-router';
import { confirm, Page, useVbenModal } from '@vben/common-ui';
import { Page, useVbenModal } from '@vben/common-ui';
import { useUserStore } from '@vben/stores';
import { downloadFileFromBlobPart, isEmpty } from '@vben/utils';
import { message, Tag } from 'ant-design-vue';
@@ -14,21 +15,27 @@
import {
  deletePurchaseRequest,
  exportPurchaseRequest,
  getApproveProcessList,
  getPurchaseRequestApproverUserIds,
  getPurchaseRequestPage,
  submitPurchaseRequest,
} from '#/api/erp/purchase/request';
import { $t } from '#/locales';
import { useGridColumns, useGridFormSchema } from './data';
import AuditModal from './modules/audit-modal.vue';
import Form from './modules/form.vue';
import OrderForm from './modules/order-form.vue';
import ProcessSelectModal from './modules/process-select-modal.vue';
/** ERP é‡‡è´­ç”³è¯·åˆ—表 */
defineOptions({ name: 'ErpPurchaseRequest' });
const router = useRouter();
const userStore = useUserStore();
/** å½“前业务类型的审批人编号集合,用于判断是否展示审核按钮 */
const approverIds = ref<number[]>([]);
/** å½“前登录用户编号 */
const currentUserId = userStore.userInfo?.id;
const [FormModal, formModalApi] = useVbenModal({
  connectedComponent: Form,
@@ -40,14 +47,31 @@
  destroyOnClose: true,
});
const [ProcessModal, processModalApi] = useVbenModal({
  connectedComponent: ProcessSelectModal,
const [AuditModalComp, auditModalApi] = useVbenModal({
  connectedComponent: AuditModal,
  destroyOnClose: true,
});
/** åˆ·æ–°è¡¨æ ¼ */
/** åˆ·æ–°è¡¨æ ¼ã€å®¡æ‰¹äººé›†åˆ */
function handleRefresh() {
  gridApi.query();
  loadApproverIds();
}
/** åŠ è½½å½“å‰ä¸šåŠ¡ç±»åž‹çš„å®¡æ‰¹äººç¼–å·é›†åˆ */
async function loadApproverIds() {
  try {
    approverIds.value = (await getPurchaseRequestApproverUserIds()) ?? [];
  } catch {
    approverIds.value = [];
  }
}
/** å½“前用户是否为该业务类型的审批人,且单据处于审批中 */
function canAudit(row: ErpPurchaseRequestApi.PurchaseRequest) {
  return (
    row.status === 10 && approverIds.value.includes(currentUserId as number)
  );
}
/** å¯¼å‡ºè¡¨æ ¼ */
@@ -81,24 +105,24 @@
  }
}
/** æäº¤å®¡æ‰¹ - å…ˆèŽ·å–æµç¨‹åˆ—è¡¨ */
/** æäº¤å®¡æ ¸ - åŽç«¯æŒ‰å®¡æ‰¹é…ç½®æ ¡éªŒå®¡æ‰¹äººï¼Œæ— éœ€å‰ç«¯é€‰æ‹©æµç¨‹ */
async function handleSubmit(row: ErpPurchaseRequestApi.PurchaseRequest) {
  const hideLoading = message.loading({
    content: '获取审批流程...',
    content: '正在提交审核...',
    duration: 0,
  });
  try {
    const processList = await getApproveProcessList(row.id!);
    hideLoading();
    if (!processList || processList.length === 0) {
      message.warning('暂无可用审批流程,请先配置 BPM æµç¨‹');
      return;
    }
    // æ‰“开流程选择弹窗
    processModalApi.setData({ id: row.id, processList }).open();
  } catch {
    await submitPurchaseRequest(row.id!);
    message.success('提交成功,等待审批人审核');
    handleRefresh();
  } finally {
    hideLoading();
  }
}
/** å®¡æ ¸ - é€šè¿‡ / ä¸é€šè¿‡ */
function handleAudit(row: ErpPurchaseRequestApi.PurchaseRequest, pass: boolean) {
  auditModalApi.setData({ pass, row }).open();
}
/** ç”Ÿæˆé‡‡è´­è®¢å• */
@@ -120,13 +144,25 @@
  formModalApi.setData({ formType: 'detail', id: row.id }).open();
}
/** æŸ¥çœ‹å®¡æ‰¹è¯¦æƒ… */
/** æŸ¥çœ‹å®¡æ‰¹è®°å½• - è½»é‡çº§å®¡æ‰¹ä¸å†è·³è½¬ BPM æµç¨‹è¯¦æƒ… */
function handleViewProcess(row: ErpPurchaseRequestApi.PurchaseRequest) {
  if (!row.processInstanceId) {
    message.warning('该申请尚未提交审批');
  const parts: string[] = [];
  if (row.reviewerName) {
    parts.push(`审核人:${row.reviewerName}`);
  }
  if (row.reviewTime) {
    parts.push(`审核时间:${row.reviewTime}`);
  }
  if (row.reviewRemark) {
    parts.push(
      `${row.status === 30 ? '不通过原因' : '审核意见'}:${row.reviewRemark}`,
    );
  }
  if (parts.length === 0) {
    message.info('该申请暂无审核记录');
    return;
  }
  router.push({ name: 'BpmProcessInstanceDetail', query: { id: row.processInstanceId } });
  message.info(parts.join(' '));
}
/** æŸ¥çœ‹é‡‡è´­è®¢å• */
@@ -167,13 +203,17 @@
    checkboxChange: handleRowCheckboxChange,
  },
});
onMounted(() => {
  loadApproverIds();
});
</script>
<template>
  <Page auto-content-height>
    <FormModal @success="handleRefresh" />
    <OrderFormModal @success="handleRefresh" />
    <ProcessModal @success="handleRefresh" />
    <AuditModalComp @success="handleRefresh" />
    <Grid table-title="采购申请列表">
      <template #toolbar-tools>
        <TableAction
@@ -240,8 +280,28 @@
            {
              label: '提交',
              type: 'link',
              auth: ['erp:purchase-request:submit'],
              // æœªæäº¤ä¸Žå®¡æ ¸ä¸é€šè¿‡å‡å¯æäº¤ï¼Œå®¡æ ¸ä¸é€šè¿‡éœ€ä¿®æ”¹åŽé‡æ–°æäº¤
              ifShow: () => row.status === 0 || row.status === 30,
              onClick: handleSubmit.bind(null, row),
            },
            {
              label: '审核',
              type: 'link',
              icon: ACTION_ICON.AUDIT,
              auth: ['erp:purchase-request:audit'],
              // ä»…「审批中 + å½“前登录用户是审批人」才显示,其他人看不到该按钮
              ifShow: canAudit(row),
              onClick: handleAudit.bind(null, row, true),
            },
            {
              label: '审核不通过',
              type: 'link',
              danger: true,
              icon: ACTION_ICON.AUDIT,
              auth: ['erp:purchase-request:audit'],
              ifShow: canAudit(row),
              onClick: handleAudit.bind(null, row, false),
            },
            {
              label: '生成订单',
@@ -253,7 +313,7 @@
            {
              label: '查看审批',
              type: 'link',
              ifShow: () => row.status !== 0,
              ifShow: () => row.status === 20 || row.status === 30,
              onClick: handleViewProcess.bind(null, row),
            },
            {
src/views/erp/purchase/request/modules/audit-modal.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,99 @@
<script lang="ts" setup>
  import type { VbenFormSchema } from '#/adapter/form';
  import type { ErpPurchaseRequestApi } from '#/api/erp/purchase/request';
  import { computed, ref } from 'vue';
  import { useVbenModal } from '@vben/common-ui';
  import { message } from 'ant-design-vue';
  import { useVbenForm } from '#/adapter/form';
  import { auditPurchaseRequest } from '#/api/erp/purchase/request';
  const emit = defineEmits(['success']);
  const pass = ref(true); // true=审核通过 / false=审核不通过
  const rowData = ref<ErpPurchaseRequestApi.PurchaseRequest>();
  const getTitle = computed(() => (pass.value ? '审核通过' : '审核不通过'));
  const [Form, formApi] = useVbenForm({
    commonConfig: {
      componentProps: {
        class: 'w-full',
      },
      formItemClass: 'col-span-1',
      labelWidth: 130,
    },
    wrapperClass: 'grid-cols-1',
    layout: 'horizontal',
    schema: [],
    showDefaultActions: false,
  });
  /** å®¡æ ¸è¡¨å•字段:不通过时必须填写原因 */
  function useAuditSchema(): VbenFormSchema[] {
    return [
      {
        fieldName: 'reviewRemark',
        label: '审核意见',
        component: 'Textarea',
        formItemClass: 'col-span-1',
        componentProps: {
          rows: 3,
          placeholder: pass.value
            ? '请输入审核意见(可选)'
            : '请输入不通过原因(必填)',
        },
        rules: pass.value ? '' : 'required',
      },
    ];
  }
  const [Modal, modalApi] = useVbenModal({
    async onConfirm() {
      const { valid } = await formApi.validate();
      if (!valid) {
        return;
      }
      modalApi.lock();
      const values = await formApi.getValues();
      try {
        await auditPurchaseRequest(
          rowData.value!.id!,
          pass.value,
          values.reviewRemark,
        );
        await modalApi.close();
        emit('success');
        message.success(pass.value ? '审核通过' : '已退回,可修改后重新提交');
      } finally {
        modalApi.unlock();
      }
    },
    async onOpenChange(isOpen: boolean) {
      if (!isOpen) {
        rowData.value = undefined;
        return;
      }
      const data = modalApi.getData<{
        pass: boolean;
        row: ErpPurchaseRequestApi.PurchaseRequest;
      }>();
      rowData.value = data.row;
      pass.value = data.pass;
      formApi.setState({ schema: useAuditSchema() });
      formApi.resetForm();
    },
  });
</script>
<template>
  <Modal :title="getTitle" class="w-2/5">
    <div v-if="rowData" class="mx-4 mb-4 text-sm text-gray-600">
      <div>申请单号:{{ rowData.no }}</div>
      <div>申请人:{{ rowData.requestUserName || '-' }}</div>
    </div>
    <Form class="mx-4" />
  </Modal>
</template>
src/views/erp/purchase/request/modules/process-select-modal.vue
ÎļþÒÑɾ³ý
src/views/hrm/attendance/exception/index.vue
@@ -5,28 +5,60 @@
import { ref } from 'vue';
import { Page, useVbenModal } from '#/packages/effects/common-ui/src';
import { useUserStore } from '@vben/stores';
import { message, Modal, Select } from 'ant-design-vue';
import { message } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
  getAttendanceExceptionApproveProcessList,
  getAttendanceExceptionApproverIds,
  getAttendanceExceptionPage,
  submitAttendanceException,
} from '#/api/hrm/attendance/exception';
import { $t } from '#/locales';
import { useGridColumns, useGridFormSchema } from './data';
import AuditModal from './modules/audit-modal.vue';
import Form from './modules/form.vue';
const userStore = useUserStore();
const currentUserId = userStore.userInfo?.id; // å½“前登录用户 ID,用于审批人权限判断
/** å®¡æ‰¹é…ç½®ä¸­è¯¥ä¸šåŠ¡çš„å®¡æ‰¹äººç¼–å·é›†åˆï¼Œå†³å®šã€Œå®¡æ ¸ / å®¡æ ¸ä¸é€šè¿‡ã€æŒ‰é’®æ˜¯å¦å±•示 */
const approverIds = ref<number[]>([]);
/** æ‹‰å–审批人集合;失败时保持为空(按钮不展示),不阻断列表加载 */
async function loadApproverIds() {
  try {
    approverIds.value = (await getAttendanceExceptionApproverIds()) ?? [];
  } catch {
    approverIds.value = [];
  }
}
/** å½“前登录用户是否为该业务的审批人,且申请处于审批中(10) */
function canAudit(row: HrmAttendanceExceptionApi.AttendanceException) {
  return (
    row.status === 10 &&
    !!currentUserId &&
    approverIds.value.includes(currentUserId)
  );
}
const [FormModal, formModalApi] = useVbenModal({
  connectedComponent: Form,
  destroyOnClose: true,
});
/** åˆ·æ–°è¡¨æ ¼ */
const [AuditModalComp, auditModalApi] = useVbenModal({
  connectedComponent: AuditModal,
  destroyOnClose: true,
});
/** åˆ·æ–°è¡¨æ ¼ä¸Žå®¡æ‰¹äººé›†åˆ */
function handleRefresh() {
  gridApi.query();
  loadApproverIds();
}
/** åˆ›å»ºè€ƒå‹¤å¼‚常申请 */
@@ -39,37 +71,27 @@
  formModalApi.setData({ id: row.id, formType: 'detail' }).open();
}
/** æäº¤å®¡æ‰¹å¼¹çª— */
const submitModalVisible = ref(false);
const currentRow = ref<HrmAttendanceExceptionApi.AttendanceException>();
const processList = ref<HrmAttendanceExceptionApi.ApproveProcess[]>([]);
const selectedProcessKey = ref<string>();
const submitLoading = ref(false);
/** æäº¤è€ƒå‹¤å¼‚常申请 */
/** æäº¤å®¡æ‰¹ - å®¡æ‰¹äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€ç»Ÿä¸€æŒ‡å®šï¼Œæ­¤å¤„无需选择 */
async function handleSubmit(row: HrmAttendanceExceptionApi.AttendanceException) {
  const res = await getAttendanceExceptionApproveProcessList();
  processList.value = res;
  currentRow.value = row;
  selectedProcessKey.value = undefined;
  submitModalVisible.value = true;
}
/** ç¡®è®¤æäº¤ */
async function confirmSubmit() {
  if (!selectedProcessKey.value) {
    message.warning('请选择审批流程');
    return;
  }
  submitLoading.value = true;
  const hideLoading = message.loading({
    content: '正在提交审批...',
    duration: 0,
  });
  try {
    await submitAttendanceException(currentRow.value!.id!, selectedProcessKey.value);
    await submitAttendanceException(row.id!);
    message.success('提交审批成功');
    submitModalVisible.value = false;
    handleRefresh();
  } finally {
    submitLoading.value = false;
    hideLoading();
  }
}
/** å®¡æ ¸ - é€šè¿‡ / ä¸é€šè¿‡ */
function handleAudit(
  row: HrmAttendanceExceptionApi.AttendanceException,
  pass: boolean,
) {
  auditModalApi.setData({ pass, row }).open();
}
const [Grid, gridApi] = useVbenVxeGrid({
@@ -100,11 +122,15 @@
    },
  } as VxeTableGridOptions<HrmAttendanceExceptionApi.AttendanceException>,
});
// è¿›å…¥é¡µé¢å³æ‹‰å–审批人集合,用于控制审核按钮显隐
loadApproverIds();
</script>
<template>
  <Page auto-content-height>
    <FormModal @success="handleRefresh" />
    <AuditModalComp @success="handleRefresh" />
    <Grid table-title="考勤异常申请列表">
      <template #toolbar-tools>
        <TableAction
@@ -128,38 +154,38 @@
              onClick: handleDetail.bind(null, row),
            },
            {
              label: '提交',
              label: '提交审批',
              type: 'link',
              auth: ['hrm:attendance-exception:submit'],
              ifShow: row.status === 0,
              onClick: handleSubmit.bind(null, row),
              // å®¡æ‰¹äººç”±å®¡æ‰¹é…ç½®ç»Ÿä¸€æŒ‡å®šï¼Œæäº¤å‰ä»…二次确认,不再弹选流程框
              popConfirm: {
                title: '确认提交审批?提交后由审批配置中指定的审批人处理。',
                confirm: handleSubmit.bind(null, row),
              },
              // è‰ç¨¿(0) ä¸Ž å®¡æ ¸ä¸é€šè¿‡(30) å‡å¯æäº¤ï¼ŒåŽè€…需修改后重新提交
              ifShow: row.status === 0 || row.status === 30,
            },
            {
              label: '审核',
              type: 'link',
              icon: ACTION_ICON.AUDIT,
              auth: ['hrm:attendance-exception:audit'],
              // ä»…「审批中 + å½“前登录用户在审批配置的审批人名单内」才显示
              ifShow: canAudit(row),
              onClick: handleAudit.bind(null, row, true),
            },
            {
              label: '审核不通过',
              type: 'link',
              danger: true,
              auth: ['hrm:attendance-exception:audit'],
              // åŒä¸Šï¼Œä¸Žã€Œå®¡æ ¸ã€æˆå¯¹å‡ºçް
              ifShow: canAudit(row),
              onClick: handleAudit.bind(null, row, false),
            },
          ]"
        />
      </template>
    </Grid>
    <!-- æäº¤å®¡æ‰¹å¼¹çª— -->
    <Modal
      v-model:open="submitModalVisible"
      title="选择审批流程"
      width="400px"
      @ok="confirmSubmit"
      :confirmLoading="submitLoading"
    >
      <Select
        v-model:value="selectedProcessKey"
        placeholder="请选择审批流程"
        class="w-full"
      >
        <Select.Option
          v-for="item in processList"
          :key="item.key"
          :value="item.key"
        >
          {{ item.name }}
        </Select.Option>
      </Select>
    </Modal>
  </Page>
</template>
src/views/hrm/attendance/exception/modules/audit-modal.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,102 @@
<script lang="ts" setup>
import type { VbenFormSchema } from '#/adapter/form';
import type { HrmAttendanceExceptionApi } from '#/api/hrm/attendance/exception';
import { computed, ref } from 'vue';
import { useVbenModal } from '#/packages/effects/common-ui/src';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { auditAttendanceException } from '#/api/hrm/attendance/exception';
const emit = defineEmits(['success']);
const pass = ref(true); // true=审核通过 / false=审核不通过
const rowData = ref<HrmAttendanceExceptionApi.AttendanceException>();
const getTitle = computed(() => (pass.value ? '审核通过' : '审核不通过'));
const [Form, formApi] = useVbenForm({
  commonConfig: {
    componentProps: {
      class: 'w-full',
    },
    formItemClass: 'col-span-1',
    labelWidth: 90,
  },
  wrapperClass: 'grid-cols-1',
  layout: 'horizontal',
  schema: [],
  showDefaultActions: false,
});
/** å®¡æ ¸è¡¨å•字段:不通过时必须填写原因 */
function useAuditSchema(): VbenFormSchema[] {
  return [
    {
      fieldName: 'reviewRemark',
      label: '审核意见',
      component: 'Textarea',
      formItemClass: 'col-span-1',
      componentProps: {
        rows: 3,
        placeholder: pass.value
          ? '请输入审核意见(可选)'
          : '请输入不通过原因(必填)',
      },
      rules: pass.value ? '' : 'required',
    },
  ];
}
const [Modal, modalApi] = useVbenModal({
  async onConfirm() {
    const { valid } = await formApi.validate();
    if (!valid) {
      return;
    }
    modalApi.lock();
    const values = await formApi.getValues();
    try {
      await auditAttendanceException(
        rowData.value!.id!,
        pass.value,
        values.reviewRemark,
      );
      await modalApi.close();
      emit('success');
      message.success(pass.value ? '审核通过' : '已退回,可修改后重新提交');
    } finally {
      modalApi.unlock();
    }
  },
  async onOpenChange(isOpen: boolean) {
    if (!isOpen) {
      rowData.value = undefined;
      return;
    }
    const data = modalApi.getData<{
      pass: boolean;
      row: HrmAttendanceExceptionApi.AttendanceException;
    }>();
    rowData.value = data.row;
    pass.value = data.pass;
    formApi.setState({ schema: useAuditSchema() });
    formApi.resetForm();
  },
});
</script>
<template>
  <Modal :title="getTitle" class="w-2/5">
    <div v-if="rowData" class="mx-4 mb-4 text-sm text-gray-600">
      <div>申请人:{{ rowData.userName }}</div>
      <div>所属部门:{{ rowData.deptName }}</div>
      <div>异常日期:{{ rowData.exceptionDate }}</div>
      <div>异常类型:{{ rowData.exceptionTypeName }}</div>
      <div>申请原因:{{ rowData.reason }}</div>
    </div>
    <Form class="mx-4" />
  </Modal>
</template>
src/views/hrm/leave/index.vue
@@ -2,7 +2,10 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { HrmLeaveApi } from '#/api/hrm/leave';
import { ref } from 'vue';
import { Page, useVbenModal } from '#/packages/effects/common-ui/src';
import { useUserStore } from '@vben/stores';
import { message, Tag } from 'ant-design-vue';
@@ -13,22 +16,54 @@
import {
  cancelLeave,
  deleteLeave,
  getLeaveApproverIds,
  getLeavePage,
  submitLeave,
} from '#/api/hrm/leave';
import { $t } from '#/locales';
import { useGridColumns, useGridFormSchema } from './data';
import AuditModal from './modules/audit-modal.vue';
import Form from './modules/form.vue';
const userStore = useUserStore();
const currentUserId = userStore.userInfo?.id; // å½“前登录用户 ID,用于审批人权限判断
/** å®¡æ‰¹é…ç½®ä¸­è¯¥ä¸šåŠ¡çš„å®¡æ‰¹äººç¼–å·é›†åˆï¼Œå†³å®šã€Œå®¡æ ¸ / å®¡æ ¸ä¸é€šè¿‡ã€æŒ‰é’®æ˜¯å¦å±•示 */
const approverIds = ref<number[]>([]);
/** æ‹‰å–审批人集合;失败时保持为空(按钮不展示),不阻断列表加载 */
async function loadApproverIds() {
  try {
    approverIds.value = (await getLeaveApproverIds()) ?? [];
  } catch {
    approverIds.value = [];
  }
}
/** å½“前登录用户是否为该业务的审批人,且申请处于审批中(10) */
function canAudit(row: HrmLeaveApi.Leave) {
  return (
    row.status === 10 &&
    !!currentUserId &&
    approverIds.value.includes(currentUserId)
  );
}
const [FormModal, formModalApi] = useVbenModal({
  connectedComponent: Form,
  destroyOnClose: true,
});
/** åˆ·æ–°è¡¨æ ¼ */
const [AuditModalComp, auditModalApi] = useVbenModal({
  connectedComponent: AuditModal,
  destroyOnClose: true,
});
/** åˆ·æ–°è¡¨æ ¼ä¸Žå®¡æ‰¹äººé›†åˆ */
function handleRefresh() {
  gridApi.query();
  loadApproverIds();
}
/** åˆ›å»ºè¯·å‡ç”³è¯· */
@@ -61,11 +96,24 @@
  }
}
/** æäº¤è¯·å‡ç”³è¯· */
/** æäº¤å®¡æ‰¹ - å®¡æ‰¹äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€ç»Ÿä¸€æŒ‡å®šï¼Œæ­¤å¤„无需选择 */
async function handleSubmit(row: HrmLeaveApi.Leave) {
  await submitLeave(row.id!);
  message.success('提交成功');
  handleRefresh();
  const hideLoading = message.loading({
    content: '正在提交审批...',
    duration: 0,
  });
  try {
    await submitLeave(row.id!);
    message.success('提交审批成功');
    handleRefresh();
  } finally {
    hideLoading();
  }
}
/** å®¡æ ¸ - é€šè¿‡ / ä¸é€šè¿‡ */
function handleAudit(row: HrmLeaveApi.Leave, pass: boolean) {
  auditModalApi.setData({ pass, row }).open();
}
/** æ’¤é”€è¯·å‡ç”³è¯· */
@@ -117,11 +165,15 @@
    },
  } as VxeTableGridOptions<HrmLeaveApi.Leave>,
});
// è¿›å…¥é¡µé¢å³æ‹‰å–审批人集合,用于控制审核按钮显隐
loadApproverIds();
</script>
<template>
  <Page auto-content-height>
    <FormModal @success="handleRefresh" />
    <AuditModalComp @success="handleRefresh" />
    <Grid table-title="请假申请列表">
      <template #toolbar-tools>
        <TableAction
@@ -158,11 +210,34 @@
              onClick: handleEdit.bind(null, row),
            },
            {
              label: '提交',
              label: '提交审批',
              type: 'link',
              auth: ['hrm:leave:submit'],
              ifShow: row.status === 0,
              onClick: handleSubmit.bind(null, row),
              auth: ['hrm:leave:update'],
              // å®¡æ‰¹äººç”±å®¡æ‰¹é…ç½®ç»Ÿä¸€æŒ‡å®šï¼Œæäº¤å‰ä»…二次确认,不再弹选流程框
              popConfirm: {
                title: '确认提交审批?提交后由审批配置中指定的审批人处理。',
                confirm: handleSubmit.bind(null, row),
              },
              // è‰ç¨¿(0) ä¸Ž å®¡æ ¸ä¸é€šè¿‡(30) å‡å¯æäº¤ï¼ŒåŽè€…需修改后重新提交
              ifShow: row.status === 0 || row.status === 30,
            },
            {
              label: '审核',
              type: 'link',
              icon: ACTION_ICON.AUDIT,
              auth: ['hrm:leave:audit'],
              // ä»…「审批中 + å½“前登录用户在审批配置的审批人名单内」才显示
              ifShow: canAudit(row),
              onClick: handleAudit.bind(null, row, true),
            },
            {
              label: '审核不通过',
              type: 'link',
              danger: true,
              auth: ['hrm:leave:audit'],
              // åŒä¸Šï¼Œä¸Žã€Œå®¡æ ¸ã€æˆå¯¹å‡ºçް
              ifShow: canAudit(row),
              onClick: handleAudit.bind(null, row, false),
            },
            {
              label: '撤销',
src/views/hrm/leave/modules/audit-modal.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,98 @@
<script lang="ts" setup>
import type { VbenFormSchema } from '#/adapter/form';
import type { HrmLeaveApi } from '#/api/hrm/leave';
import { computed, ref } from 'vue';
import { useVbenModal } from '#/packages/effects/common-ui/src';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { auditLeave } from '#/api/hrm/leave';
const emit = defineEmits(['success']);
const pass = ref(true); // true=审核通过 / false=审核不通过
const rowData = ref<HrmLeaveApi.Leave>();
const getTitle = computed(() => (pass.value ? '审核通过' : '审核不通过'));
const [Form, formApi] = useVbenForm({
  commonConfig: {
    componentProps: {
      class: 'w-full',
    },
    formItemClass: 'col-span-1',
    labelWidth: 90,
  },
  wrapperClass: 'grid-cols-1',
  layout: 'horizontal',
  schema: [],
  showDefaultActions: false,
});
/** å®¡æ ¸è¡¨å•字段:不通过时必须填写原因 */
function useAuditSchema(): VbenFormSchema[] {
  return [
    {
      fieldName: 'reviewRemark',
      label: '审核意见',
      component: 'Textarea',
      formItemClass: 'col-span-1',
      componentProps: {
        rows: 3,
        placeholder: pass.value
          ? '请输入审核意见(可选)'
          : '请输入不通过原因(必填)',
      },
      rules: pass.value ? '' : 'required',
    },
  ];
}
const [Modal, modalApi] = useVbenModal({
  async onConfirm() {
    const { valid } = await formApi.validate();
    if (!valid) {
      return;
    }
    modalApi.lock();
    const values = await formApi.getValues();
    try {
      await auditLeave(rowData.value!.id!, pass.value, values.reviewRemark);
      await modalApi.close();
      emit('success');
      message.success(pass.value ? '审核通过' : '已退回,可修改后重新提交');
    } finally {
      modalApi.unlock();
    }
  },
  async onOpenChange(isOpen: boolean) {
    if (!isOpen) {
      rowData.value = undefined;
      return;
    }
    const data = modalApi.getData<{
      pass: boolean;
      row: HrmLeaveApi.Leave;
    }>();
    rowData.value = data.row;
    pass.value = data.pass;
    formApi.setState({ schema: useAuditSchema() });
    formApi.resetForm();
  },
});
</script>
<template>
  <Modal :title="getTitle" class="w-2/5">
    <div v-if="rowData" class="mx-4 mb-4 text-sm text-gray-600">
      <div>申请人:{{ rowData.employeeName }}</div>
      <div>所属部门:{{ rowData.deptName }}</div>
      <div>开始时间:{{ rowData.startTime }}</div>
      <div>结束时间:{{ rowData.endTime }}</div>
      <div>请假时长:{{ rowData.duration }} å¤©</div>
    </div>
    <Form class="mx-4" />
  </Modal>
</template>
src/views/hrm/performance-appraise/index.vue
@@ -3,9 +3,9 @@
import type { HrmPerformanceAppraiseApi } from '#/api/hrm/performance-appraise';
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import { Page, useVbenModal } from '@vben/common-ui';
import { useUserStore } from '@vben/stores';
import { isEmpty } from '@vben/utils';
import { message, Tag } from 'ant-design-vue';
@@ -13,8 +13,9 @@
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
  deleteAppraise,
  getAppraiseApproverIds,
  getAppraisePage,
  getAppraiseProcessList,
  submitAppraise,
} from '#/api/hrm/performance-appraise';
import { $t } from '#/locales';
@@ -22,14 +23,35 @@
import AuditModal from './modules/audit-modal.vue';
import Form from './modules/form.vue';
import ItemModal from './modules/item.vue';
import ProcessSelectModal from './modules/process-select-modal.vue';
import ResultModal from './modules/result.vue';
import ScoreModal from './modules/score.vue';
/** HRM ç»©æ•ˆè€ƒæ ¸æ–¹æ¡ˆåˆ—表 */
defineOptions({ name: 'HrmPerformanceAppraise' });
const router = useRouter();
const userStore = useUserStore();
const currentUserId = userStore.userInfo?.id; // å½“前登录用户 ID,用于审批人权限判断
/** å®¡æ‰¹é…ç½®ä¸­è¯¥ä¸šåŠ¡çš„å®¡æ‰¹äººç¼–å·é›†åˆï¼Œå†³å®šã€Œå®¡æ ¸ / å®¡æ ¸ä¸é€šè¿‡ã€æŒ‰é’®æ˜¯å¦å±•示 */
const approverIds = ref<number[]>([]);
/** æ‹‰å–审批人集合;失败时保持为空(按钮不展示),不阻断列表加载 */
async function loadApproverIds() {
  try {
    approverIds.value = (await getAppraiseApproverIds()) ?? [];
  } catch {
    approverIds.value = [];
  }
}
/** å½“前登录用户是否为该业务的审批人,且方案处于审批中(10) */
function canAudit(row: HrmPerformanceAppraiseApi.Appraise) {
  return (
    row.auditStatus === AUDIT_STATUS.PROCESS &&
    !!currentUserId &&
    approverIds.value.includes(currentUserId)
  );
}
const [FormModal, formModalApi] = useVbenModal({
  connectedComponent: Form,
@@ -51,18 +73,15 @@
  destroyOnClose: true,
});
const [ProcessModal, processModalApi] = useVbenModal({
  connectedComponent: ProcessSelectModal,
  destroyOnClose: true,
});
const [AuditFormModal, auditModalApi] = useVbenModal({
  connectedComponent: AuditModal,
  destroyOnClose: true,
});
/** åˆ·æ–°è¡¨æ ¼ä¸Žå®¡æ‰¹äººé›†åˆ */
function handleRefresh() {
  gridApi.query();
  loadApproverIds();
}
function handleCreate() {
@@ -103,40 +122,24 @@
  }
}
/** æäº¤å®¡æ‰¹ - å…ˆèŽ·å–æµç¨‹åˆ—è¡¨ */
/** æäº¤å®¡æ‰¹ - å®¡æ‰¹äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€ç»Ÿä¸€æŒ‡å®šï¼Œæ­¤å¤„无需选择 */
async function handleSubmit(row: HrmPerformanceAppraiseApi.Appraise) {
  const hideLoading = message.loading({
    content: '获取审批流程...',
    content: '正在提交审批...',
    duration: 0,
  });
  try {
    const processList = await getAppraiseProcessList();
    hideLoading();
    if (!processList || processList.length === 0) {
      message.warning('暂无可用审批流程,请先配置 BPM æµç¨‹');
      return;
    }
    processModalApi.setData({ id: row.id, processList }).open();
  } catch {
    await submitAppraise(row.id!);
    message.success('提交审批成功');
    handleRefresh();
  } finally {
    hideLoading();
  }
}
/** å®¡æ‰¹ï¼ˆé€šè¿‡/驳回) */
function handleAudit(row: HrmPerformanceAppraiseApi.Appraise) {
  auditModalApi.setData({ id: row.id }).open();
}
/** æŸ¥çœ‹å®¡æ‰¹è¯¦æƒ… */
function handleViewProcess(row: HrmPerformanceAppraiseApi.Appraise) {
  if (!row.processInstanceId) {
    message.warning('该方案尚未提交审批');
    return;
  }
  router.push({
    name: 'BpmProcessInstanceDetail',
    query: { id: row.processInstanceId },
  });
/** å®¡æ ¸ - é€šè¿‡ / ä¸é€šè¿‡ */
function handleAudit(row: HrmPerformanceAppraiseApi.Appraise, pass: boolean) {
  auditModalApi.setData({ pass, row }).open();
}
const checkedIds = ref<number[]>([]);
@@ -181,6 +184,9 @@
    checkboxChange: handleRowCheckboxChange,
  },
});
// è¿›å…¥é¡µé¢å³æ‹‰å–审批人集合,用于控制审核按钮显隐
loadApproverIds();
</script>
<template>
@@ -189,7 +195,6 @@
    <ItemFormModal @success="handleRefresh" />
    <ScoreFormModal @success="handleRefresh" />
    <ResultFormModal @success="handleRefresh" />
    <ProcessModal @success="handleRefresh" />
    <AuditFormModal @success="handleRefresh" />
    <Grid table-title="绩效考核方案列表">
      <template #toolbar-tools>
@@ -267,22 +272,32 @@
            {
              label: '提交审批',
              type: 'link',
              auth: ['hrm:performance-appraise:submit'],
              auth: ['hrm:performance-appraise:update'],
              // å®¡æ‰¹äººç”±å®¡æ‰¹é…ç½®ç»Ÿä¸€æŒ‡å®šï¼Œæäº¤å‰ä»…二次确认,不再弹选流程框
              popConfirm: {
                title: '确认提交审批?提交后由审批配置中指定的审批人处理。',
                confirm: handleSubmit.bind(null, row),
              },
              // è‰ç¨¿(0) ä¸Ž å®¡æ ¸ä¸é€šè¿‡(30) å‡å¯æäº¤ï¼ŒåŽè€…需修改后重新提交
              ifShow: row.auditStatus === AUDIT_STATUS.DRAFT || row.auditStatus === AUDIT_STATUS.REJECT,
              onClick: handleSubmit.bind(null, row),
            },
            {
              label: '审批',
              label: '审核',
              type: 'link',
              icon: ACTION_ICON.AUDIT,
              auth: ['hrm:performance-appraise:audit'],
              ifShow: row.auditStatus === AUDIT_STATUS.PROCESS,
              onClick: handleAudit.bind(null, row),
              // ä»…「审批中 + å½“前登录用户在审批配置的审批人名单内」才显示
              ifShow: canAudit(row),
              onClick: handleAudit.bind(null, row, true),
            },
            {
              label: '查看审批',
              label: '审核不通过',
              type: 'link',
              ifShow: row.auditStatus !== AUDIT_STATUS.DRAFT && !!row.processInstanceId,
              onClick: handleViewProcess.bind(null, row),
              danger: true,
              auth: ['hrm:performance-appraise:audit'],
              // åŒä¸Šï¼Œä¸Žã€Œå®¡æ ¸ã€æˆå¯¹å‡ºçް
              ifShow: canAudit(row),
              onClick: handleAudit.bind(null, row, false),
            },
            {
              label: $t('common.delete'),
src/views/hrm/performance-appraise/modules/audit-modal.vue
@@ -1,5 +1,8 @@
<script lang="ts" setup>
import { ref } from 'vue';
import type { VbenFormSchema } from '#/adapter/form';
import type { HrmPerformanceAppraiseApi } from '#/api/hrm/performance-appraise';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
@@ -10,42 +13,42 @@
const emit = defineEmits(['success']);
const appraiseId = ref<number>();
const pass = ref(true); // true=审核通过 / false=审核不通过
const rowData = ref<HrmPerformanceAppraiseApi.Appraise>();
const getTitle = computed(() => (pass.value ? '审核通过' : '审核不通过'));
const [Form, formApi] = useVbenForm({
  commonConfig: {
    componentProps: {
      class: 'w-full',
    },
    labelWidth: 100,
    formItemClass: 'col-span-1',
    labelWidth: 90,
  },
  wrapperClass: 'grid-cols-1',
  layout: 'vertical',
  schema: [
    {
      fieldName: 'auditStatus',
      label: '审批结果',
      component: 'RadioGroup',
      componentProps: {
        options: [
          { label: '通过', value: 20 },
          { label: '驳回', value: 30 },
        ],
      },
      rules: 'selectRequired',
    },
    {
      fieldName: 'remark',
      label: '审批备注',
      component: 'Textarea',
      componentProps: {
        placeholder: '请输入审批备注',
        rows: 3,
      },
    },
  ],
  layout: 'horizontal',
  schema: [],
  showDefaultActions: false,
});
/** å®¡æ ¸è¡¨å•字段:不通过时必须填写原因 */
function useAuditSchema(): VbenFormSchema[] {
  return [
    {
      fieldName: 'reviewRemark',
      label: '审核意见',
      component: 'Textarea',
      formItemClass: 'col-span-1',
      componentProps: {
        rows: 3,
        placeholder: pass.value
          ? '请输入审核意见(可选)'
          : '请输入不通过原因(必填)',
      },
      rules: pass.value ? '' : 'required',
    },
  ];
}
const [Modal, modalApi] = useVbenModal({
  async onConfirm() {
@@ -54,38 +57,40 @@
      return;
    }
    modalApi.lock();
    const values = await formApi.getValues();
    try {
      const values = await formApi.getValues<{
        auditStatus: number;
        remark?: string;
      }>();
      await auditAppraise({
        id: appraiseId.value!,
        auditStatus: values.auditStatus,
        remark: values.remark,
      });
      await auditAppraise(rowData.value!.id!, pass.value, values.reviewRemark);
      await modalApi.close();
      emit('success');
      message.success('审批完成');
      message.success(pass.value ? '审核通过' : '已退回,可修改后重新提交');
    } finally {
      modalApi.unlock();
    }
  },
  async onOpenChange(isOpen: boolean) {
    if (!isOpen) {
      appraiseId.value = undefined;
      await formApi.resetForm();
      rowData.value = undefined;
      return;
    }
    const data = modalApi.getData<{ id: number }>();
    appraiseId.value = data?.id;
    await formApi.setValues({ auditStatus: 20 });
    const data = modalApi.getData<{
      pass: boolean;
      row: HrmPerformanceAppraiseApi.Appraise;
    }>();
    rowData.value = data.row;
    pass.value = data.pass;
    formApi.setState({ schema: useAuditSchema() });
    formApi.resetForm();
  },
});
</script>
<template>
  <Modal title="审批绩效考核方案" class="w-1/3">
    <Form class="mx-3" />
  <Modal :title="getTitle" class="w-2/5">
    <div v-if="rowData" class="mx-4 mb-4 text-sm text-gray-600">
      <div>方案名称:{{ rowData.name }}</div>
      <div>考核周期:{{ rowData.period }}</div>
      <div>考核指标数:{{ rowData.itemCount }}</div>
    </div>
    <Form class="mx-4" />
  </Modal>
</template>
src/views/hrm/performance-appraise/modules/process-select-modal.vue
ÎļþÒÑɾ³ý
src/views/hrm/resignation/index.vue
@@ -2,7 +2,10 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { HrmResignationApi } from '#/api/hrm/resignation';
import { ref } from 'vue';
import { Page, useVbenModal } from '#/packages/effects/common-ui/src';
import { useUserStore } from '@vben/stores';
import { message } from 'ant-design-vue';
@@ -10,6 +13,7 @@
import {
  cancelResignation,
  deleteResignation,
  getResignationApproverIds,
  getResignationPage,
  submitResignation,
  updateHandoverStatus,
@@ -17,16 +21,47 @@
import { $t } from '#/locales';
import { useGridColumns, useGridFormSchema } from './data';
import AuditModal from './modules/audit-modal.vue';
import Form from './modules/form.vue';
const userStore = useUserStore();
const currentUserId = userStore.userInfo?.id; // å½“前登录用户 ID,用于审批人权限判断
/** å®¡æ‰¹é…ç½®ä¸­è¯¥ä¸šåŠ¡çš„å®¡æ‰¹äººç¼–å·é›†åˆï¼Œå†³å®šã€Œå®¡æ ¸ / å®¡æ ¸ä¸é€šè¿‡ã€æŒ‰é’®æ˜¯å¦å±•示 */
const approverIds = ref<number[]>([]);
/** æ‹‰å–审批人集合;失败时保持为空(按钮不展示),不阻断列表加载 */
async function loadApproverIds() {
  try {
    approverIds.value = (await getResignationApproverIds()) ?? [];
  } catch {
    approverIds.value = [];
  }
}
/** å½“前登录用户是否为该业务的审批人,且申请处于审批中(10) */
function canAudit(row: HrmResignationApi.Resignation) {
  return (
    row.status === 10 &&
    !!currentUserId &&
    approverIds.value.includes(currentUserId)
  );
}
const [FormModal, formModalApi] = useVbenModal({
  connectedComponent: Form,
  destroyOnClose: true,
});
/** åˆ·æ–°è¡¨æ ¼ */
const [AuditModalComp, auditModalApi] = useVbenModal({
  connectedComponent: AuditModal,
  destroyOnClose: true,
});
/** åˆ·æ–°è¡¨æ ¼ä¸Žå®¡æ‰¹äººé›†åˆ */
function handleRefresh() {
  gridApi.query();
  loadApproverIds();
}
/** åˆ›å»ºç¦»èŒç”³è¯· */
@@ -59,11 +94,24 @@
  }
}
/** æäº¤ç¦»èŒç”³è¯· */
/** æäº¤å®¡æ‰¹ - å®¡æ‰¹äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€ç»Ÿä¸€æŒ‡å®šï¼Œæ­¤å¤„无需选择 */
async function handleSubmit(row: HrmResignationApi.Resignation) {
  await submitResignation(row.id!);
  message.success('提交成功');
  handleRefresh();
  const hideLoading = message.loading({
    content: '正在提交审批...',
    duration: 0,
  });
  try {
    await submitResignation(row.id!);
    message.success('提交审批成功');
    handleRefresh();
  } finally {
    hideLoading();
  }
}
/** å®¡æ ¸ - é€šè¿‡ / ä¸é€šè¿‡ */
function handleAudit(row: HrmResignationApi.Resignation, pass: boolean) {
  auditModalApi.setData({ pass, row }).open();
}
/** æ’¤é”€ç¦»èŒç”³è¯· */
@@ -111,11 +159,15 @@
    },
  } as VxeTableGridOptions<HrmResignationApi.Resignation>,
});
// è¿›å…¥é¡µé¢å³æ‹‰å–审批人集合,用于控制审核按钮显隐
loadApproverIds();
</script>
<template>
  <Page auto-content-height>
    <FormModal @success="handleRefresh" />
    <AuditModalComp @success="handleRefresh" />
    <Grid table-title="离职申请列表">
      <template #toolbar-tools>
        <TableAction
@@ -147,11 +199,34 @@
              onClick: handleEdit.bind(null, row),
            },
            {
              label: '提交',
              label: '提交审批',
              type: 'link',
              auth: ['hrm:resignation:submit'],
              ifShow: row.status === 0,
              onClick: handleSubmit.bind(null, row),
              auth: ['hrm:resignation:update'],
              // å®¡æ‰¹äººç”±å®¡æ‰¹é…ç½®ç»Ÿä¸€æŒ‡å®šï¼Œæäº¤å‰ä»…二次确认,不再弹选流程框
              popConfirm: {
                title: '确认提交审批?提交后由审批配置中指定的审批人处理。',
                confirm: handleSubmit.bind(null, row),
              },
              // è‰ç¨¿(0) ä¸Ž å®¡æ ¸ä¸é€šè¿‡(30) å‡å¯æäº¤ï¼ŒåŽè€…需修改后重新提交
              ifShow: row.status === 0 || row.status === 30,
            },
            {
              label: '审核',
              type: 'link',
              icon: ACTION_ICON.AUDIT,
              auth: ['hrm:resignation:audit'],
              // ä»…「审批中 + å½“前登录用户在审批配置的审批人名单内」才显示
              ifShow: canAudit(row),
              onClick: handleAudit.bind(null, row, true),
            },
            {
              label: '审核不通过',
              type: 'link',
              danger: true,
              auth: ['hrm:resignation:audit'],
              // åŒä¸Šï¼Œä¸Žã€Œå®¡æ ¸ã€æˆå¯¹å‡ºçް
              ifShow: canAudit(row),
              onClick: handleAudit.bind(null, row, false),
            },
            {
              label: '撤销',
src/views/hrm/resignation/modules/audit-modal.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,98 @@
<script lang="ts" setup>
import type { VbenFormSchema } from '#/adapter/form';
import type { HrmResignationApi } from '#/api/hrm/resignation';
import { computed, ref } from 'vue';
import { useVbenModal } from '#/packages/effects/common-ui/src';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { auditResignation } from '#/api/hrm/resignation';
const emit = defineEmits(['success']);
const pass = ref(true); // true=审核通过 / false=审核不通过
const rowData = ref<HrmResignationApi.Resignation>();
const getTitle = computed(() => (pass.value ? '审核通过' : '审核不通过'));
const [Form, formApi] = useVbenForm({
  commonConfig: {
    componentProps: {
      class: 'w-full',
    },
    formItemClass: 'col-span-1',
    labelWidth: 90,
  },
  wrapperClass: 'grid-cols-1',
  layout: 'horizontal',
  schema: [],
  showDefaultActions: false,
});
/** å®¡æ ¸è¡¨å•字段:不通过时必须填写原因 */
function useAuditSchema(): VbenFormSchema[] {
  return [
    {
      fieldName: 'reviewRemark',
      label: '审核意见',
      component: 'Textarea',
      formItemClass: 'col-span-1',
      componentProps: {
        rows: 3,
        placeholder: pass.value
          ? '请输入审核意见(可选)'
          : '请输入不通过原因(必填)',
      },
      rules: pass.value ? '' : 'required',
    },
  ];
}
const [Modal, modalApi] = useVbenModal({
  async onConfirm() {
    const { valid } = await formApi.validate();
    if (!valid) {
      return;
    }
    modalApi.lock();
    const values = await formApi.getValues();
    try {
      await auditResignation(rowData.value!.id!, pass.value, values.reviewRemark);
      await modalApi.close();
      emit('success');
      message.success(pass.value ? '审核通过' : '已退回,可修改后重新提交');
    } finally {
      modalApi.unlock();
    }
  },
  async onOpenChange(isOpen: boolean) {
    if (!isOpen) {
      rowData.value = undefined;
      return;
    }
    const data = modalApi.getData<{
      pass: boolean;
      row: HrmResignationApi.Resignation;
    }>();
    rowData.value = data.row;
    pass.value = data.pass;
    formApi.setState({ schema: useAuditSchema() });
    formApi.resetForm();
  },
});
</script>
<template>
  <Modal :title="getTitle" class="w-2/5">
    <div v-if="rowData" class="mx-4 mb-4 text-sm text-gray-600">
      <div>申请人:{{ rowData.employeeName }}</div>
      <div>所属部门:{{ rowData.deptName }}</div>
      <div>预计离职日期:{{ rowData.expectedDate }}</div>
      <div>交接人:{{ rowData.handoverUserName }}</div>
      <div>离职原因:{{ rowData.reason }}</div>
    </div>
    <Form class="mx-4" />
  </Modal>
</template>
src/views/hrm/transfer/index.vue
@@ -2,7 +2,10 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { HrmTransferApi } from '#/api/hrm/transfer';
import { ref } from 'vue';
import { Page, useVbenModal } from '#/packages/effects/common-ui/src';
import { useUserStore } from '@vben/stores';
import { message } from 'ant-design-vue';
@@ -10,22 +13,54 @@
import {
  cancelTransfer,
  deleteTransfer,
  getTransferApproverIds,
  getTransferPage,
  submitTransfer,
} from '#/api/hrm/transfer';
import { $t } from '#/locales';
import { useGridColumns, useGridFormSchema } from './data';
import AuditModal from './modules/audit-modal.vue';
import Form from './modules/form.vue';
const userStore = useUserStore();
const currentUserId = userStore.userInfo?.id; // å½“前登录用户 ID,用于审批人权限判断
/** å®¡æ‰¹é…ç½®ä¸­è¯¥ä¸šåŠ¡çš„å®¡æ‰¹äººç¼–å·é›†åˆï¼Œå†³å®šã€Œå®¡æ ¸ / å®¡æ ¸ä¸é€šè¿‡ã€æŒ‰é’®æ˜¯å¦å±•示 */
const approverIds = ref<number[]>([]);
/** æ‹‰å–审批人集合;失败时保持为空(按钮不展示),不阻断列表加载 */
async function loadApproverIds() {
  try {
    approverIds.value = (await getTransferApproverIds()) ?? [];
  } catch {
    approverIds.value = [];
  }
}
/** å½“前登录用户是否为该业务的审批人,且申请处于审批中(10) */
function canAudit(row: HrmTransferApi.Transfer) {
  return (
    row.status === 10 &&
    !!currentUserId &&
    approverIds.value.includes(currentUserId)
  );
}
const [FormModal, formModalApi] = useVbenModal({
  connectedComponent: Form,
  destroyOnClose: true,
});
/** åˆ·æ–°è¡¨æ ¼ */
const [AuditModalComp, auditModalApi] = useVbenModal({
  connectedComponent: AuditModal,
  destroyOnClose: true,
});
/** åˆ·æ–°è¡¨æ ¼ä¸Žå®¡æ‰¹äººé›†åˆ */
function handleRefresh() {
  gridApi.query();
  loadApproverIds();
}
/** åˆ›å»ºè°ƒå²—申请 */
@@ -58,11 +93,24 @@
  }
}
/** æäº¤è°ƒå²—申请 */
/** æäº¤å®¡æ‰¹ - å®¡æ‰¹äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€ç»Ÿä¸€æŒ‡å®šï¼Œæ­¤å¤„无需选择 */
async function handleSubmit(row: HrmTransferApi.Transfer) {
  await submitTransfer(row.id!);
  message.success('提交成功');
  handleRefresh();
  const hideLoading = message.loading({
    content: '正在提交审批...',
    duration: 0,
  });
  try {
    await submitTransfer(row.id!);
    message.success('提交审批成功');
    handleRefresh();
  } finally {
    hideLoading();
  }
}
/** å®¡æ ¸ - é€šè¿‡ / ä¸é€šè¿‡ */
function handleAudit(row: HrmTransferApi.Transfer, pass: boolean) {
  auditModalApi.setData({ pass, row }).open();
}
/** æ’¤é”€è°ƒå²—申请 */
@@ -100,11 +148,15 @@
    },
  } as VxeTableGridOptions<HrmTransferApi.Transfer>,
});
// è¿›å…¥é¡µé¢å³æ‹‰å–审批人集合,用于控制审核按钮显隐
loadApproverIds();
</script>
<template>
  <Page auto-content-height>
    <FormModal @success="handleRefresh" />
    <AuditModalComp @success="handleRefresh" />
    <Grid table-title="调岗申请列表">
      <template #toolbar-tools>
        <TableAction
@@ -136,11 +188,34 @@
              onClick: handleEdit.bind(null, row),
            },
            {
              label: '提交',
              label: '提交审批',
              type: 'link',
              auth: ['hrm:transfer:submit'],
              ifShow: row.status === 0,
              onClick: handleSubmit.bind(null, row),
              auth: ['hrm:transfer:update'],
              // å®¡æ‰¹äººç”±å®¡æ‰¹é…ç½®ç»Ÿä¸€æŒ‡å®šï¼Œæäº¤å‰ä»…二次确认,不再弹选流程框
              popConfirm: {
                title: '确认提交审批?提交后由审批配置中指定的审批人处理。',
                confirm: handleSubmit.bind(null, row),
              },
              // è‰ç¨¿(0) ä¸Ž å®¡æ ¸ä¸é€šè¿‡(30) å‡å¯æäº¤ï¼ŒåŽè€…需修改后重新提交
              ifShow: row.status === 0 || row.status === 30,
            },
            {
              label: '审核',
              type: 'link',
              icon: ACTION_ICON.AUDIT,
              auth: ['hrm:transfer:audit'],
              // ä»…「审批中 + å½“前登录用户在审批配置的审批人名单内」才显示
              ifShow: canAudit(row),
              onClick: handleAudit.bind(null, row, true),
            },
            {
              label: '审核不通过',
              type: 'link',
              danger: true,
              auth: ['hrm:transfer:audit'],
              // åŒä¸Šï¼Œä¸Žã€Œå®¡æ ¸ã€æˆå¯¹å‡ºçް
              ifShow: canAudit(row),
              onClick: handleAudit.bind(null, row, false),
            },
            {
              label: '撤销',
src/views/hrm/transfer/modules/audit-modal.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,99 @@
<script lang="ts" setup>
import type { VbenFormSchema } from '#/adapter/form';
import type { HrmTransferApi } from '#/api/hrm/transfer';
import { computed, ref } from 'vue';
import { useVbenModal } from '#/packages/effects/common-ui/src';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { auditTransfer } from '#/api/hrm/transfer';
const emit = defineEmits(['success']);
const pass = ref(true); // true=审核通过 / false=审核不通过
const rowData = ref<HrmTransferApi.Transfer>();
const getTitle = computed(() => (pass.value ? '审核通过' : '审核不通过'));
const [Form, formApi] = useVbenForm({
  commonConfig: {
    componentProps: {
      class: 'w-full',
    },
    formItemClass: 'col-span-1',
    labelWidth: 90,
  },
  wrapperClass: 'grid-cols-1',
  layout: 'horizontal',
  schema: [],
  showDefaultActions: false,
});
/** å®¡æ ¸è¡¨å•字段:不通过时必须填写原因 */
function useAuditSchema(): VbenFormSchema[] {
  return [
    {
      fieldName: 'reviewRemark',
      label: '审核意见',
      component: 'Textarea',
      formItemClass: 'col-span-1',
      componentProps: {
        rows: 3,
        placeholder: pass.value
          ? '请输入审核意见(可选)'
          : '请输入不通过原因(必填)',
      },
      rules: pass.value ? '' : 'required',
    },
  ];
}
const [Modal, modalApi] = useVbenModal({
  async onConfirm() {
    const { valid } = await formApi.validate();
    if (!valid) {
      return;
    }
    modalApi.lock();
    const values = await formApi.getValues();
    try {
      await auditTransfer(rowData.value!.id!, pass.value, values.reviewRemark);
      await modalApi.close();
      emit('success');
      message.success(pass.value ? '审核通过' : '已退回,可修改后重新提交');
    } finally {
      modalApi.unlock();
    }
  },
  async onOpenChange(isOpen: boolean) {
    if (!isOpen) {
      rowData.value = undefined;
      return;
    }
    const data = modalApi.getData<{
      pass: boolean;
      row: HrmTransferApi.Transfer;
    }>();
    rowData.value = data.row;
    pass.value = data.pass;
    formApi.setState({ schema: useAuditSchema() });
    formApi.resetForm();
  },
});
</script>
<template>
  <Modal :title="getTitle" class="w-2/5">
    <div v-if="rowData" class="mx-4 mb-4 text-sm text-gray-600">
      <div>申请人:{{ rowData.employeeName }}</div>
      <div>原部门:{{ rowData.currentDeptName }}</div>
      <div>目标部门:{{ rowData.targetDeptName }}</div>
      <div>原岗位:{{ rowData.currentPostName }}</div>
      <div>目标岗位:{{ rowData.targetPostName }}</div>
      <div>生效日期:{{ rowData.effectiveDate }}</div>
    </div>
    <Form class="mx-4" />
  </Modal>
</template>
src/views/mes/pd/archive/modules/detail.vue
@@ -9,11 +9,12 @@
import { FilePreviewModal, useFilePreview } from '#/components/file-preview';
interface Props {
  visible: boolean;
  visible?: boolean;
  productCode?: string;
}
const props = withDefaults(defineProps<Props>(), {
  visible: false,
  productCode: '',
});
@@ -27,6 +28,8 @@
const loading = ref(false);
const detailList = ref<MesPdArchiveApi.DetailItem[]>([]);
const activeProject = ref('0');
/** å½“前查看的产品编码(命令式 open(code) ä¼ å…¥æˆ–取自 prop) */
const currentCode = ref('');
const { openPreview, previewState } = useFilePreview();
@@ -52,28 +55,50 @@
  },
];
watch(() => props.visible, async (newVal) => {
  if (newVal && props.productCode) {
    open.value = true;
    loading.value = true;
    detailList.value = [];
    activeProject.value = '0';
    try {
      detailList.value = await getPdArchiveDetailByProduct(props.productCode);
    } catch {
      message.error('获取归档详情失败');
    } finally {
      loading.value = false;
    }
  } else if (!newVal) {
    open.value = false;
    detailList.value = [];
/** æ‰“开弹窗并加载指定产品编码的归档详情;编码为空时直接展示空态 */
async function showDetail(code: string) {
  currentCode.value = code || '';
  open.value = true;
  detailList.value = [];
  activeProject.value = '0';
  if (!currentCode.value) {
    return;
  }
});
  loading.value = true;
  try {
    detailList.value = await getPdArchiveDetailByProduct(currentCode.value);
  } catch {
    message.error('获取归档详情失败');
  } finally {
    loading.value = false;
  }
}
watch(
  () => props.visible,
  (newVal) => {
    if (newVal) {
      // å³ä½¿äº§å“ç¼–码为空也打开弹窗(展示空态),避免父组件 visible å¡åœ¨ true åŽå†ä¹Ÿæ— æ³•触发
      showDetail(props.productCode || '');
    } else {
      open.value = false;
      detailList.value = [];
    }
  },
);
function handleClose() {
  open.value = false;
  detailList.value = [];
  emit('update:visible', false);
}
/** å…¼å®¹å‘½ä»¤å¼è°ƒç”¨ï¼šarchiveDetailRef.value?.open(productCode) */
function handleOpen(code?: string) {
  showDetail(code ?? props.productCode ?? '');
}
defineExpose({ open: handleOpen });
</script>
<template>
@@ -143,7 +168,7 @@
        </Tabs.TabPane>
      </Tabs>
      <div v-else class="py-8 text-center text-gray-400">
        æš‚无归档记录
        {{ currentCode ? '暂无归档记录' : '该工单未设置产品编码,暂无归档记录' }}
      </div>
    </template>
  </Modal>
src/views/mes/pd/project/detail/index.vue
@@ -5,6 +5,8 @@
import { computed, onMounted, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useUserStore } from '@vben/stores';
import { Page, useVbenModal } from '#/packages/effects/common-ui/src';
import { useTabs } from '#/packages/effects/hooks/src';
import { MesPdProjectStatusEnum } from '#/packages/constants/src';
@@ -14,8 +16,9 @@
import {
  archivePdProject,
  getPdProject,
  getPdProjectApproveProcessList,
  getPdProjectApproverIds,
  publishPdProject,
  submitPdProjectApproval,
  uploadPdProject,
} from '#/api/mes/pd/project';
import { useDescription } from '#/components/description';
@@ -23,9 +26,9 @@
import { $t } from '#/locales';
import Form from '../modules/form.vue';
import AuditModal from '../modules/audit-modal.vue';
import DocumentAuditList from '../modules/document-audit-list.vue';
import DocumentList from '../modules/document-list.vue';
import ProcessSelectModal from '../modules/process-select-modal.vue';
import WorkflowTimeline from '../modules/workflow-timeline.vue';
import TaskDraftBanner from '../modules/task-draft-banner.vue';
import { useDetailSchema } from './data';
@@ -77,10 +80,33 @@
  destroyOnClose: true,
});
const [ProcessModal, processModalApi] = useVbenModal({
  connectedComponent: ProcessSelectModal,
const [AuditModalComp, auditModalApi] = useVbenModal({
  connectedComponent: AuditModal,
  destroyOnClose: true,
});
const userStore = useUserStore();
const currentUserId = userStore.userInfo?.id; // å½“前登录用户 ID,用于审核人权限判断
/** å®¡æ‰¹é…ç½®ä¸­è¯¥ä¸šåŠ¡çš„å®¡æ ¸äººç¼–å·é›†åˆï¼Œå†³å®šã€Œå®¡æ ¸ / å®¡æ ¸ä¸é€šè¿‡ã€æŒ‰é’®æ˜¯å¦å±•示 */
const approverIds = ref<number[]>([]);
/** æ‹‰å–审核人集合;失败时保持为空(按钮不展示),不阻断详情加载 */
async function loadApproverIds() {
  try {
    approverIds.value = (await getPdProjectApproverIds()) ?? [];
  } catch {
    approverIds.value = [];
  }
}
/** å½“前登录用户是否为该业务的审核人,且单据处于审批中状态 */
const canAudit = computed(
  () =>
    isApproving.value &&
    !!currentUserId &&
    approverIds.value.includes(currentUserId),
);
async function loadProjectDetail() {
  loading.value = true;
@@ -128,38 +154,24 @@
  }
}
/** æäº¤å®¡æ‰¹ - å…ˆèŽ·å–æµç¨‹åˆ—è¡¨ */
/** æäº¤å®¡æ‰¹ - å®¡æ ¸äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€ç»Ÿä¸€æŒ‡å®šï¼Œæ­¤å¤„无需选择 */
async function handleSubmit() {
  const hideLoading = message.loading({ content: '获取审批流程...', duration: 0 });
  try {
    const processList = await getPdProjectApproveProcessList();
    hideLoading();
    if (!processList || processList.length === 0) {
      message.warning('暂无可用审批流程,请先配置 BPM æµç¨‹');
      return;
    }
    // æ‰“开流程选择弹窗
    processModalApi.setData({ id: projectId.value, processList }).open();
  } catch {
    hideLoading();
  }
}
/** æŸ¥çœ‹å®¡æ‰¹è¯¦æƒ… */
function handleProcessDetail() {
  if (!project.value.processInstanceId) {
    message.warning('该项目尚未提交审批');
    return;
  }
  router.push({
    name: 'BpmProcessInstanceDetail',
    query: { id: project.value.processInstanceId },
  const hideLoading = message.loading({
    content: '正在提交审批...',
    duration: 0,
  });
  try {
    await submitPdProjectApproval(projectId.value);
    message.success('提交审批成功');
    loadProjectDetail();
  } finally {
    hideLoading();
  }
}
/** æäº¤å®¡æ‰¹æˆåŠŸåŽåˆ·æ–°è¯¦æƒ… */
function handleSubmitSuccess() {
  loadProjectDetail();
/** å®¡æ ¸ - é€šè¿‡ / ä¸é€šè¿‡ */
function handleAudit(pass: boolean) {
  auditModalApi.setData({ pass, row: project.value }).open();
}
async function handleArchive() {
@@ -176,6 +188,8 @@
onMounted(() => {
  projectId.value = Number(route.params.id);
  loadProjectDetail();
  // è¿›å…¥é¡µé¢å³æ‹‰å–审核人集合,用于控制审核按钮显隐
  loadApproverIds();
});
</script>
@@ -186,7 +200,7 @@
    :title="project.taskName || '设计任务详情'"
  >
    <FormModal @success="loadProjectDetail" />
    <ProcessModal @success="handleSubmitSuccess" />
    <AuditModalComp @success="loadProjectDetail" />
    <template #extra>
      <TableAction
@@ -220,13 +234,27 @@
            label: '提交审批',
            type: 'primary',
            ifShow: isPendingAudit,
            onClick: handleSubmit,
            // å®¡æ ¸äººç”±å®¡æ‰¹é…ç½®ç»Ÿä¸€æŒ‡å®šï¼Œæäº¤å‰ä»…二次确认,不再弹选流程框
            popConfirm: {
              title: '确认提交审批?提交后由审批配置中指定的审核人处理。',
              confirm: handleSubmit,
            },
          },
          {
            label: '查看审批',
            label: '审核',
            type: 'primary',
            ifShow: isApproving,
            onClick: handleProcessDetail,
            icon: ACTION_ICON.AUDIT,
            // ä»…「审批中 + å½“前登录用户在审批配置的审核人名单内」才显示
            ifShow: canAudit,
            onClick: () => handleAudit(true),
          },
          {
            label: '审核不通过',
            type: 'default',
            danger: true,
            // åŒä¸Šï¼Œä¸Žã€Œå®¡æ ¸ã€æˆå¯¹å‡ºçް
            ifShow: canAudit,
            onClick: () => handleAudit(false),
          },
          {
            label: '归档',
src/views/mes/pd/project/index.vue
@@ -2,8 +2,10 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesPdProjectApi } from '#/api/mes/pd/project';
import { onActivated } from 'vue';
import { onActivated, ref } from 'vue';
import { useRouter } from 'vue-router';
import { useUserStore } from '@vben/stores';
import { Page, useVbenModal } from '#/packages/effects/common-ui/src';
import { MesPdProjectStatusEnum } from '#/packages/constants/src';
@@ -17,19 +19,44 @@
  deletePdProject,
  exportPdProject,
  getPdProjectApproveProcessList,
  getPdProjectApproverIds,
  getPdProjectPage,
  publishPdProject,
  submitPdProjectApproval,
} from '#/api/mes/pd/project';
import { $t } from '#/locales';
import { useGridColumns, useGridFormSchema } from './data';
import AuditModal from './modules/audit-modal.vue';
import CreateForm from './modules/create-form.vue';
import Form from './modules/form.vue';
import ProcessSelectModal from './modules/process-select-modal.vue';
const router = useRouter();
const userStore = useUserStore();
const currentUserId = userStore.userInfo?.id; // å½“前登录用户 ID,用于审核人权限判断
/** å®¡æ‰¹é…ç½®ä¸­è¯¥ä¸šåŠ¡çš„å®¡æ ¸äººç¼–å·é›†åˆï¼Œå†³å®šã€Œå®¡æ ¸ / å®¡æ ¸ä¸é€šè¿‡ã€æŒ‰é’®æ˜¯å¦å±•示 */
const approverIds = ref<number[]>([]);
/** æ‹‰å–审核人集合;失败时保持为空(按钮不展示),不阻断列表加载 */
async function loadApproverIds() {
  try {
    approverIds.value = (await getPdProjectApproverIds()) ?? [];
  } catch {
    approverIds.value = [];
  }
}
/** å½“前登录用户是否为该业务的审核人,且单据处于审批中状态 */
function canAudit(row: MesPdProjectApi.Project) {
  return (
    row.status === MesPdProjectStatusEnum.APPROVING &&
    !!currentUserId &&
    approverIds.value.includes(currentUserId)
  );
}
const [CreateFormModal, createFormModalApi] = useVbenModal({
  connectedComponent: CreateForm,
@@ -41,13 +68,15 @@
  destroyOnClose: true,
});
const [ProcessModal, processModalApi] = useVbenModal({
  connectedComponent: ProcessSelectModal,
const [AuditModalComp, auditModalApi] = useVbenModal({
  connectedComponent: AuditModal,
  destroyOnClose: true,
});
/** åˆ·æ–°è¡¨æ ¼ä¸Žå®¡æ ¸äººé›†åˆ */
function handleRefresh() {
  gridApi.query();
  loadApproverIds();
}
function handleCreateSuccess() {
@@ -80,36 +109,24 @@
  }
}
/** æäº¤å®¡æ‰¹ - å…ˆèŽ·å–æµç¨‹åˆ—è¡¨ */
/** æäº¤å®¡æ‰¹ - å®¡æ ¸äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€ç»Ÿä¸€æŒ‡å®šï¼Œæ­¤å¤„无需选择 */
async function handleSubmit(row: MesPdProjectApi.Project) {
  const hideLoading = message.loading({
    content: '获取审批流程...',
    content: '正在提交审批...',
    duration: 0,
  });
  try {
    const processList = await getPdProjectApproveProcessList();
    hideLoading();
    if (!processList || processList.length === 0) {
      message.warning('暂无可用审批流程,请先配置 BPM æµç¨‹');
      return;
    }
    // æ‰“开流程选择弹窗
    processModalApi.setData({ id: row.id, processList }).open();
  } catch {
    await submitPdProjectApproval(row.id!);
    message.success('提交审批成功');
    handleRefresh();
  } finally {
    hideLoading();
  }
}
/** æŸ¥çœ‹å®¡æ‰¹è¯¦æƒ… */
function handleProcessDetail(row: MesPdProjectApi.Project) {
  if (!row.processInstanceId) {
    message.warning('该项目尚未提交审批');
    return;
  }
  router.push({
    name: 'BpmProcessInstanceDetail',
    query: { id: row.processInstanceId },
  });
/** å®¡æ ¸ - é€šè¿‡ / ä¸é€šè¿‡ */
function handleAudit(row: MesPdProjectApi.Project, pass: boolean) {
  auditModalApi.setData({ pass, row }).open();
}
async function handleArchive(row: MesPdProjectApi.Project) {
@@ -170,13 +187,16 @@
onActivated(() => {
  handleRefresh();
});
// è¿›å…¥é¡µé¢å³æ‹‰å–审核人集合,用于控制审核按钮显隐
loadApproverIds();
</script>
<template>
  <Page auto-content-height>
    <CreateFormModal @success="handleCreateSuccess" />
    <FormModal @success="handleRefresh" />
    <ProcessModal @success="handleRefresh" />
    <AuditModalComp @success="handleRefresh" />
    <Grid table-title="设计任务">
      <template #toolbar-tools>
@@ -226,15 +246,31 @@
              label: '提交审批',
              type: 'link',
              auth: ['mes:pd-project:update'],
              // å¾…审核可提交;审核不通过会退回待审核,可修改后重新提交
              ifShow: row.status === MesPdProjectStatusEnum.PENDING_AUDIT,
              onClick: handleSubmit.bind(null, row),
              // å®¡æ ¸äººç”±å®¡æ‰¹é…ç½®ç»Ÿä¸€æŒ‡å®šï¼Œæäº¤å‰ä»…二次确认,不再弹选流程框
              popConfirm: {
                title: '确认提交审批?提交后由审批配置中指定的审核人处理。',
                confirm: handleSubmit.bind(null, row),
              },
            },
            {
              label: '查看审批',
              label: '审核',
              type: 'link',
              auth: ['mes:pd-project:update'],
              ifShow: row.status === MesPdProjectStatusEnum.APPROVING,
              onClick: handleProcessDetail.bind(null, row),
              icon: ACTION_ICON.AUDIT,
              auth: ['mes:pd-project:audit'],
              // ä»…「审批中 + å½“前登录用户在审批配置的审核人名单内」才显示
              ifShow: canAudit(row),
              onClick: handleAudit.bind(null, row, true),
            },
            {
              label: '审核不通过',
              type: 'link',
              danger: true,
              auth: ['mes:pd-project:audit'],
              // åŒä¸Šï¼Œä¸Žã€Œå®¡æ ¸ã€æˆå¯¹å‡ºçް
              ifShow: canAudit(row),
              onClick: handleAudit.bind(null, row, false),
            },
            {
              label: '归档',
src/views/mes/pd/project/modules/audit-modal.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,96 @@
<script lang="ts" setup>
import type { VbenFormSchema } from '#/adapter/form';
import type { MesPdProjectApi } from '#/api/mes/pd/project';
import { computed, ref } from 'vue';
import { useVbenModal } from '#/packages/effects/common-ui/src';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { auditPdProject } from '#/api/mes/pd/project';
const emit = defineEmits(['success']);
const pass = ref(true); // true=审核通过 / false=审核不通过
const rowData = ref<MesPdProjectApi.Project>();
const getTitle = computed(() => (pass.value ? '审核通过' : '审核不通过'));
const [Form, formApi] = useVbenForm({
  commonConfig: {
    componentProps: {
      class: 'w-full',
    },
    formItemClass: 'col-span-1',
    labelWidth: 90,
  },
  wrapperClass: 'grid-cols-1',
  layout: 'horizontal',
  schema: [],
  showDefaultActions: false,
});
/** å®¡æ ¸è¡¨å•字段:不通过时必须填写原因 */
function useAuditSchema(): VbenFormSchema[] {
  return [
    {
      fieldName: 'reviewRemark',
      label: '审核意见',
      component: 'Textarea',
      formItemClass: 'col-span-1',
      componentProps: {
        rows: 3,
        placeholder: pass.value
          ? '请输入审核意见(可选)'
          : '请输入不通过原因(必填)',
      },
      rules: pass.value ? '' : 'required',
    },
  ];
}
const [Modal, modalApi] = useVbenModal({
  async onConfirm() {
    const { valid } = await formApi.validate();
    if (!valid) {
      return;
    }
    modalApi.lock();
    const values = await formApi.getValues();
    try {
      await auditPdProject(rowData.value!.id!, pass.value, values.reviewRemark);
      await modalApi.close();
      emit('success');
      message.success(pass.value ? '审核通过' : '已退回,可修改后重新提交');
    } finally {
      modalApi.unlock();
    }
  },
  async onOpenChange(isOpen: boolean) {
    if (!isOpen) {
      rowData.value = undefined;
      return;
    }
    const data = modalApi.getData<{
      pass: boolean;
      row: MesPdProjectApi.Project;
    }>();
    rowData.value = data.row;
    pass.value = data.pass;
    formApi.setState({ schema: useAuditSchema() });
    formApi.resetForm();
  },
});
</script>
<template>
  <Modal :title="getTitle" class="w-2/5">
    <div v-if="rowData" class="mx-4 mb-4 text-sm text-gray-600">
      <div>任务编码:{{ rowData.taskCode }}</div>
      <div>任务名称:{{ rowData.taskName }}</div>
      <div>产品名称:{{ rowData.productName }}</div>
    </div>
    <Form class="mx-4" />
  </Modal>
</template>
src/views/mes/pd/project/modules/process-select-modal.vue
ÎļþÒÑɾ³ý
src/views/mes/pd/tech-confirm/data.ts
@@ -13,8 +13,18 @@
/** è¡¨å•类型 */
export type FormType = 'create' | 'detail' | 'update';
/** æ–°å¢ž/修改产品技术确认函 */
/**
 * æ–°å¢ž/修改/查看产品技术确认函
 *
 * çŠ¶æ€åŠç¼–åˆ¶äººã€å®¡æ ¸äººã€å®¢æˆ·ç¡®è®¤äººç­‰æµç¨‹æ´¾ç”Ÿå­—æ®µä¸€å¾‹åªè¯»ï¼š
 * çŠ¶æ€ç”±åŽç«¯æŒ‰ä¸šåŠ¡æµç¨‹æŽ¨è¿›ï¼ˆæäº¤å®¡æ ¸ â†’ å®¡æ ¸é€šè¿‡/不通过 â†’ å®¢æˆ·ç¡®è®¤ï¼‰ï¼Œ
 * äººå‘˜ä¸Žæ—¶é—´ç”±åŽç«¯è‡ªåŠ¨è®°å½•ï¼Œå‡ä¸å…è®¸æ‰‹å·¥å¡«å†™ã€‚
 *
 * @param formType è¡¨å•类型,create æ—¶ä¸å±•示状态字段(新建单据固定为草稿)
 * @param formApi è¡¨å• API
 */
export function useFormSchema(
  formType: FormType,
  formApi?: VbenFormApi,
): VbenFormSchema[] {
  return [
@@ -97,9 +107,24 @@
      label: '状态',
      component: 'Select',
      componentProps: {
        allowClear: true,
        // çŠ¶æ€ç”±ä¸šåŠ¡æµç¨‹é©±åŠ¨ï¼Œä»…ä½œå±•ç¤ºï¼Œä¸å¯æ‰‹å·¥é€‰æ‹©
        disabled: true,
        options: getDictOptions(DICT_TYPE.PRODUCT_TECH_CONFIRM_STATUS, 'number'),
        placeholder: '请选择状态',
      },
      dependencies: {
        triggerFields: [''],
        // æ–°å»ºå•据固定为草稿,不展示状态
        show: () => formType !== 'create',
      },
    },
    {
      fieldName: 'flowDivider',
      label: '流程信息(系统自动记录)',
      component: 'Divider',
      formItemClass: 'col-span-3',
      dependencies: {
        triggerFields: [''],
        show: () => formType !== 'create',
      },
    },
    {
@@ -107,7 +132,12 @@
      label: '编制人',
      component: 'Input',
      componentProps: {
        placeholder: '请输入编制人姓名',
        disabled: true,
        placeholder: '由系统自动记录',
      },
      dependencies: {
        triggerFields: [''],
        show: () => formType !== 'create',
      },
    },
    {
@@ -116,9 +146,14 @@
      component: 'DatePicker',
      componentProps: {
        class: '!w-full',
        disabled: true,
        format: 'YYYY-MM-DD HH:mm:ss',
        showTime: true,
        valueFormat: 'YYYY-MM-DD HH:mm:ss',
      },
      dependencies: {
        triggerFields: [''],
        show: () => formType !== 'create',
      },
    },
    {
@@ -126,7 +161,12 @@
      label: '审核人',
      component: 'Input',
      componentProps: {
        placeholder: '请输入审核人姓名',
        disabled: true,
        placeholder: '提交审核时选择',
      },
      dependencies: {
        triggerFields: [''],
        show: () => formType !== 'create',
      },
    },
    {
@@ -135,9 +175,29 @@
      component: 'DatePicker',
      componentProps: {
        class: '!w-full',
        disabled: true,
        format: 'YYYY-MM-DD HH:mm:ss',
        showTime: true,
        valueFormat: 'YYYY-MM-DD HH:mm:ss',
      },
      dependencies: {
        triggerFields: [''],
        show: () => formType !== 'create',
      },
    },
    {
      fieldName: 'reviewRemark',
      label: '审核意见',
      component: 'Textarea',
      formItemClass: 'col-span-3',
      componentProps: {
        disabled: true,
        placeholder: '审核时填写',
        rows: 2,
      },
      dependencies: {
        triggerFields: [''],
        show: () => formType !== 'create',
      },
    },
    {
@@ -145,7 +205,12 @@
      label: '客户确认人',
      component: 'Input',
      componentProps: {
        placeholder: '请输入客户确认人',
        disabled: true,
        placeholder: '由系统自动记录',
      },
      dependencies: {
        triggerFields: [''],
        show: () => formType !== 'create',
      },
    },
    {
@@ -154,9 +219,14 @@
      component: 'DatePicker',
      componentProps: {
        class: '!w-full',
        disabled: true,
        format: 'YYYY-MM-DD HH:mm:ss',
        showTime: true,
        valueFormat: 'YYYY-MM-DD HH:mm:ss',
      },
      dependencies: {
        triggerFields: [''],
        show: () => formType !== 'create',
      },
    },
    {
@@ -233,6 +303,7 @@
      },
    },
    { field: 'drafterName', title: '编制人', width: 90 },
    { field: 'reviewerName', title: '审核人', width: 90 },
    {
      field: 'status',
      title: '状态',
src/views/mes/pd/tech-confirm/index.vue
@@ -2,11 +2,13 @@
  import type { VxeTableGridOptions } from '#/adapter/vxe-table';
  import type { MesPdTechConfirmApi } from '#/api/mes/pd/tech-confirm';
  import { ref } from 'vue';
  import { Page, useVbenModal } from '@vben/common-ui';
  import { $t } from '@vben/locales';
  import { downloadFileFromBlobPart } from '@vben/utils';
  import { ref } from 'vue';
  import { MesPdTechConfirmStatusEnum } from '#/packages/constants/src';
  import { Button, message } from 'ant-design-vue';
@@ -14,21 +16,60 @@
  import {
    deleteTechConfirm,
    exportTechConfirm,
    getTechConfirmApproverIds,
    getTechConfirmPage,
    updateTechConfirm,
    submitTechConfirmAudit,
  } from '#/api/mes/pd/tech-confirm';
  import { useUserStore } from '@vben/stores';
  import { useGridColumns, useGridFormSchema } from './data';
  import AuditModal from './modules/audit-modal.vue';
  import CustomerConfirmModal from './modules/customer-confirm-modal.vue';
  import Form from './modules/form.vue';
  const userStore = useUserStore();
  const currentUserId = userStore.userInfo?.id; // å½“前登录用户 ID,用于审核人权限判断
  /** å®¡æ‰¹é…ç½®ä¸­è¯¥ä¸šåŠ¡çš„å®¡æ ¸äººç¼–å·é›†åˆï¼Œå†³å®šã€Œå®¡æ ¸ / å®¡æ ¸ä¸é€šè¿‡ã€æŒ‰é’®æ˜¯å¦å±•示 */
  const approverIds = ref<number[]>([]);
  /** æ‹‰å–审核人集合;失败时保持为空(按钮不展示),不阻断列表加载 */
  async function loadApproverIds() {
    try {
      approverIds.value = (await getTechConfirmApproverIds()) ?? [];
    } catch {
      approverIds.value = [];
    }
  }
  /** å½“前登录用户是否为该业务的审核人,且单据处于待审核状态 */
  function canAudit(row: MesPdTechConfirmApi.TechConfirm) {
    return (
      row.status === MesPdTechConfirmStatusEnum.PENDING_AUDIT &&
      !!currentUserId &&
      approverIds.value.includes(currentUserId)
    );
  }
  const [FormModal, formModalApi] = useVbenModal({
    connectedComponent: Form,
    destroyOnClose: true,
  });
  /** åˆ·æ–°è¡¨æ ¼ */
  const [AuditModalComp, auditModalApi] = useVbenModal({
    connectedComponent: AuditModal,
    destroyOnClose: true,
  });
  const [CustomerConfirm, customerConfirmApi] = useVbenModal({
    connectedComponent: CustomerConfirmModal,
    destroyOnClose: true,
  });
  /** åˆ·æ–°è¡¨æ ¼ä¸Žå®¡æ ¸äººé›†åˆ */
  function handleRefresh() {
    gridApi.query();
    loadApproverIds();
  }
  /** æ–°å¢ž */
@@ -61,16 +102,35 @@
    }
  }
  /** çŠ¶æ€æµè½¬ */
  async function handleStatus(row: MesPdTechConfirmApi.TechConfirm, next: number) {
    const hideLoading = message.loading({ content: '状态更新中...', duration: 0 });
  /** æäº¤å®¡æ ¸ - å®¡æ ¸äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€ç»Ÿä¸€æŒ‡å®šï¼Œæ­¤å¤„无需选择 */
  async function handleSubmitAudit(row: MesPdTechConfirmApi.TechConfirm) {
    const hideLoading = message.loading({
      content: '正在提交审核...',
      duration: 0,
    });
    try {
      await updateTechConfirm({ ...row, status: next });
      message.success($t('ui.actionMessage.operationSuccess'));
      await submitTechConfirmAudit(row.id!);
      message.success('提交审核成功');
      handleRefresh();
    } finally {
      hideLoading();
    }
  }
  /** å®¡æ ¸ - é€šè¿‡ / ä¸é€šè¿‡ */
  function handleAudit(row: MesPdTechConfirmApi.TechConfirm, pass: boolean) {
    auditModalApi.setData({ pass, row }).open();
  }
  /** å®¢æˆ·ç¡®è®¤ - è®°å½•客户确认人,由后端写入确认时间 */
  function handleCustomerConfirm(row: MesPdTechConfirmApi.TechConfirm) {
    customerConfirmApi
      .setData({
        id: row.id,
        confirmNo: row.confirmNo,
        customerName: row.customerName,
      })
      .open();
  }
  /** å¯¼å‡º */
@@ -107,11 +167,16 @@
      },
    } as VxeTableGridOptions<MesPdTechConfirmApi.TechConfirm>,
  });
  // è¿›å…¥é¡µé¢å³æ‹‰å–审核人集合,用于控制审核按钮显隐
  loadApproverIds();
</script>
<template>
  <Page auto-content-height>
    <FormModal @success="handleRefresh" />
    <AuditModalComp @success="handleRefresh" />
    <CustomerConfirm @success="handleRefresh" />
    <Grid table-title="产品技术确认函列表">
      <template #toolbar-tools>
        <TableAction
@@ -146,30 +211,51 @@
              type: 'link',
              icon: ACTION_ICON.AUDIT,
              auth: ['mes:pd-tech-confirm:update'],
              ifShow: row.status === 0,
              onClick: handleStatus.bind(null, row, 1),
              // è‰ç¨¿ä¸Žå®¡æ ¸ä¸é€šè¿‡å‡å¯æäº¤ï¼Œå®¡æ ¸ä¸é€šè¿‡éœ€ä¿®æ”¹åŽé‡æ–°æäº¤
              ifShow:
                row.status === MesPdTechConfirmStatusEnum.DRAFT ||
                row.status === MesPdTechConfirmStatusEnum.REJECTED,
              // å®¡æ ¸äººç”±å®¡æ‰¹é…ç½®ç»Ÿä¸€æŒ‡å®šï¼Œæäº¤å‰ä»…二次确认,不再弹选人框
              popConfirm: {
                title: '确认提交审核?提交后由审批配置中指定的审核人处理。',
                confirm: handleSubmitAudit.bind(null, row),
              },
            },
            {
              label: '审核通过',
              label: '审核',
              type: 'link',
              icon: ACTION_ICON.AUDIT,
              auth: ['mes:pd-tech-confirm:update'],
              ifShow: row.status === 1,
              onClick: handleStatus.bind(null, row, 2),
              auth: ['mes:pd-tech-confirm:audit'],
              // ä»…「待审核 + å½“前登录用户在审批配置的审核人名单内」才显示
              ifShow: canAudit(row),
              onClick: handleAudit.bind(null, row, true),
            },
            {
              label: '审核不通过',
              type: 'link',
              danger: true,
              auth: ['mes:pd-tech-confirm:audit'],
              // åŒä¸Šï¼Œä¸Žã€Œå®¡æ ¸ã€æˆå¯¹å‡ºçް
              ifShow: canAudit(row),
              onClick: handleAudit.bind(null, row, false),
            },
            {
              label: '客户确认',
              type: 'link',
              icon: ACTION_ICON.AUDIT,
              auth: ['mes:pd-tech-confirm:update'],
              ifShow: row.status === 2,
              onClick: handleStatus.bind(null, row, 3),
              ifShow: row.status === MesPdTechConfirmStatusEnum.AUDITED,
              onClick: handleCustomerConfirm.bind(null, row),
            },
            {
              label: $t('common.edit'),
              type: 'link',
              icon: ACTION_ICON.EDIT,
              auth: ['mes:pd-tech-confirm:update'],
              // ä»…草稿与审核不通过可修改,其余状态单据已锁定
              ifShow:
                row.status === MesPdTechConfirmStatusEnum.DRAFT ||
                row.status === MesPdTechConfirmStatusEnum.REJECTED,
              onClick: handleEdit.bind(null, row),
            },
            {
@@ -178,6 +264,10 @@
              danger: true,
              icon: ACTION_ICON.DELETE,
              auth: ['mes:pd-tech-confirm:delete'],
              // ä»…草稿与审核不通过可删除
              ifShow:
                row.status === MesPdTechConfirmStatusEnum.DRAFT ||
                row.status === MesPdTechConfirmStatusEnum.REJECTED,
              popConfirm: {
                title: $t('ui.actionMessage.deleteConfirm', [row.confirmNo ?? '']),
                confirm: handleDelete.bind(null, row),
src/views/mes/pd/tech-confirm/modules/audit-modal.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,96 @@
<script lang="ts" setup>
  import type { VbenFormSchema } from '#/adapter/form';
  import type { MesPdTechConfirmApi } from '#/api/mes/pd/tech-confirm';
  import { computed, ref } from 'vue';
  import { useVbenModal } from '@vben/common-ui';
  import { $t } from '@vben/locales';
  import { message } from 'ant-design-vue';
  import { useVbenForm } from '#/adapter/form';
  import { auditTechConfirm } from '#/api/mes/pd/tech-confirm';
  const emit = defineEmits(['success']);
  const pass = ref(true); // true=审核通过 / false=审核不通过
  const rowData = ref<MesPdTechConfirmApi.TechConfirm>();
  const getTitle = computed(() => (pass.value ? '审核通过' : '审核不通过'));
  const [Form, formApi] = useVbenForm({
    commonConfig: {
      componentProps: {
        class: 'w-full',
      },
      formItemClass: 'col-span-1',
      labelWidth: 130,
    },
    wrapperClass: 'grid-cols-1',
    layout: 'horizontal',
    schema: [],
    showDefaultActions: false,
  });
  /** å®¡æ ¸è¡¨å•字段:不通过时必须填写原因 */
  function useAuditSchema(): VbenFormSchema[] {
    return [
      {
        fieldName: 'reviewRemark',
        label: '审核意见',
        component: 'Textarea',
        formItemClass: 'col-span-1',
        componentProps: {
          rows: 3,
          placeholder: pass.value
            ? '请输入审核意见(可选)'
            : '请输入不通过原因(必填)',
        },
        rules: pass.value ? '' : 'required',
      },
    ];
  }
  const [Modal, modalApi] = useVbenModal({
    async onConfirm() {
      const { valid } = await formApi.validate();
      if (!valid) {
        return;
      }
      modalApi.lock();
      const values = await formApi.getValues();
      try {
        await auditTechConfirm(rowData.value!.id!, pass.value, values.reviewRemark);
        await modalApi.close();
        emit('success');
        message.success(pass.value ? '审核通过' : '已退回,可修改后重新提交');
      } finally {
        modalApi.unlock();
      }
    },
    async onOpenChange(isOpen: boolean) {
      if (!isOpen) {
        rowData.value = undefined;
        return;
      }
      const data = modalApi.getData<{
        pass: boolean;
        row: MesPdTechConfirmApi.TechConfirm;
      }>();
      rowData.value = data.row;
      pass.value = data.pass;
      formApi.setState({ schema: useAuditSchema() });
      formApi.resetForm();
    },
  });
</script>
<template>
  <Modal :title="getTitle" class="w-2/5">
    <div v-if="rowData" class="mx-4 mb-4 text-sm text-gray-600">
      <div>确认函编号:{{ rowData.confirmNo }}</div>
      <div>产品名称:{{ rowData.productName }}</div>
    </div>
    <Form class="mx-4" />
  </Modal>
</template>
src/views/mes/pd/tech-confirm/modules/customer-confirm-modal.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,74 @@
<script lang="ts" setup>
import { reactive } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { Form, FormItem, Input, message } from 'ant-design-vue';
import { confirmTechConfirmByCustomer } from '#/api/mes/pd/tech-confirm';
const emit = defineEmits(['success']);
const formData = reactive({
  id: 0,
  confirmNo: '',
  customerName: '',
  customerConfirmUser: '',
});
const [Modal, modalApi] = useVbenModal({
  async onConfirm() {
    if (!formData.customerConfirmUser.trim()) {
      message.warning('请输入客户确认人');
      return;
    }
    modalApi.lock();
    try {
      await confirmTechConfirmByCustomer(
        formData.id,
        formData.customerConfirmUser.trim(),
      );
      await modalApi.close();
      emit('success');
      message.success('客户确认成功');
    } finally {
      modalApi.unlock();
    }
  },
  onOpenChange(isOpen: boolean) {
    if (!isOpen) {
      return;
    }
    const data = modalApi.getData<{
      customerName?: string;
      confirmNo?: string;
      id: number;
    }>();
    formData.id = data.id;
    formData.confirmNo = data.confirmNo ?? '';
    formData.customerName = data.customerName ?? '';
    // é»˜è®¤å¸¦å…¥å®¢æˆ·åç§°ä½œä¸ºç¡®è®¤äººï¼Œå®¢æˆ·å¯¹æŽ¥äººå§“名可由业务员修正
    formData.customerConfirmUser = data.customerName ?? '';
  },
});
</script>
<template>
  <Modal class="w-1/3" title="客户确认">
    <Form :label-col="{ style: { width: '100px' } }">
      <FormItem label="确认函编号">
        {{ formData.confirmNo }}
      </FormItem>
      <FormItem label="客户名称">
        {{ formData.customerName }}
      </FormItem>
      <FormItem label="客户确认人" required>
        <Input
          v-model:value="formData.customerConfirmUser"
          :maxlength="64"
          placeholder="请输入客户确认人姓名"
        />
      </FormItem>
    </Form>
  </Modal>
</template>
src/views/mes/pd/tech-confirm/modules/form.vue
@@ -8,6 +8,8 @@
  import { useVbenModal } from '@vben/common-ui';
  import { $t } from '@vben/locales';
  import { MesPdTechConfirmStatusEnum } from '#/packages/constants/src';
  import { message } from 'ant-design-vue';
  import { useVbenForm } from '#/adapter/form';
@@ -55,8 +57,21 @@
        return;
      }
      modalApi.lock();
      // æäº¤è¡¨å•
      const data = (await formApi.getValues()) as MesPdTechConfirmApi.TechConfirm;
      // æäº¤è¡¨å•:只提交用户可填写的业务字段,状态与流程派生字段由后端按流程推进,不回传
      const {
        status,
        drafterId,
        drafterName,
        draftTime,
        reviewerId,
        reviewerName,
        reviewTime,
        reviewRemark,
        customerConfirmUser,
        customerConfirmTime,
        ...values
      } = await formApi.getValues();
      const data = values as MesPdTechConfirmApi.TechConfirm;
      try {
        await (data.id ? updateTechConfirm(data) : createTechConfirm(data));
        // å…³é—­å¹¶æç¤º
@@ -75,7 +90,7 @@
      // åŠ è½½æ•°æ®
      const data = modalApi.getData<{ formType: FormType; id?: number }>();
      formType.value = data.formType;
      formApi.setState({ schema: useFormSchema(formApi) });
      formApi.setState({ schema: useFormSchema(formType.value, formApi) });
      formApi.setDisabled(formType.value === 'detail');
      modalApi.setState({ showConfirmButton: formType.value !== 'detail' });
      if (!data?.id) {
@@ -86,6 +101,14 @@
        formData.value = await getTechConfirm(data.id);
        // è®¾ç½®åˆ° values
        await formApi.setValues(formData.value);
        // ä»…草稿与审核不通过可修改;待审核 / å·²å®¡æ ¸ / å®¢æˆ·å·²ç¡®è®¤ä¸ºé”å®šæˆ–终态
        const editable =
          formData.value?.status === MesPdTechConfirmStatusEnum.DRAFT ||
          formData.value?.status === MesPdTechConfirmStatusEnum.REJECTED;
        if (!editable) {
          formApi.setDisabled(true);
          modalApi.setState({ showConfirmButton: false });
        }
      } finally {
        modalApi.unlock();
      }
src/views/mes/pro/task/components/gantt-chart.vue
@@ -168,15 +168,29 @@
    [BarcodeBizTypeEnum.WORKORDER]: 'project',
    [BarcodeBizTypeEnum.TASK]: 'task',
  };
  gantt.parse({
    data: tasks.map((item: any) => ({
  const data = tasks
    .map((item: any) => ({
      ...item,
      type: typeMap[item.type] || item.type,
      start_date: item.startDate ? new Date(item.startDate) : undefined,
      end_date: item.endDate ? new Date(item.endDate) : undefined,
    })),
    links: [],
  });
    }))
    // è¿‡æ»¤ç¼ºå¤±/非法日期的任务,避免 dhtmlx-gantt è®¡ç®—时间轴时抛 valueOf å¼‚常
    .filter(
      (task: any) =>
        task.start_date instanceof Date &&
        task.end_date instanceof Date &&
        !Number.isNaN(task.start_date.getTime()) &&
        !Number.isNaN(task.end_date.getTime()),
    );
  if (data.length === 0) {
    return;
  }
  try {
    gantt.parse({ data, links: [] });
  } catch (error) {
    console.error('甘特图渲染失败:', error);
  }
}
watch(
src/views/wls/components/process-select-modal.vue
ÎļþÒÑɾ³ý
src/views/wls/equipmentreceipt/index.vue
@@ -2,10 +2,11 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesWmEquipmentReceiptApi } from '#/api/mes/wm/equipmentreceipt';
import { useRouter } from 'vue-router';
import { ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { MesWmEquipmentReceiptStatusEnum } from '@vben/constants';
import { useUserStore } from '@vben/stores';
import { Button, message, Tag } from 'ant-design-vue';
@@ -13,31 +14,54 @@
import {
  cancelEquipmentReceipt,
  deleteEquipmentReceipt,
  getEquipmentReceiptApproveProcessList,
  getEquipmentReceiptApproverIds,
  getEquipmentReceiptPage,
  submitEquipmentReceiptApproval,
} from '#/api/mes/wm/equipmentreceipt';
import { $t } from '#/locales';
import ProcessSelectModal from '#/views/wls/components/process-select-modal.vue';
import { EQUIPMENT_RECEIPT_STATUS_MAP, useGridColumns, useGridFormSchema } from './data';
import AuditModal from './modules/audit-modal.vue';
import Form from './modules/form.vue';
const router = useRouter();
const userStore = useUserStore();
const currentUserId = userStore.userInfo?.id; // å½“前登录用户 ID,用于审核人权限判断
/** å®¡æ‰¹é…ç½®ä¸­è¯¥ä¸šåŠ¡çš„å®¡æ ¸äººç¼–å·é›†åˆï¼Œå†³å®šã€Œå®¡æ ¸ / å®¡æ ¸ä¸é€šè¿‡ã€æŒ‰é’®æ˜¯å¦å±•示 */
const approverIds = ref<number[]>([]);
/** æ‹‰å–审核人集合;失败时保持为空(按钮不展示),不阻断列表加载 */
async function loadApproverIds() {
  try {
    approverIds.value = (await getEquipmentReceiptApproverIds()) ?? [];
  } catch {
    approverIds.value = [];
  }
}
/** å½“前登录用户是否为该业务的审核人,且单据处于审批中状态 */
function canAudit(row: MesWmEquipmentReceiptApi.EquipmentReceipt) {
  return (
    row.status === MesWmEquipmentReceiptStatusEnum.APPROVING &&
    !!currentUserId &&
    approverIds.value.includes(currentUserId)
  );
}
const [FormModal, formModalApi] = useVbenModal({
  connectedComponent: Form,
  destroyOnClose: true,
});
const [ProcessModal, processModalApi] = useVbenModal({
  connectedComponent: ProcessSelectModal,
const [AuditModalComp, auditModalApi] = useVbenModal({
  connectedComponent: AuditModal,
  destroyOnClose: true,
});
/** åˆ·æ–°è¡¨æ ¼ */
/** åˆ·æ–°è¡¨æ ¼ä¸Žå®¡æ ¸äººé›†åˆ */
function handleRefresh() {
  gridApi.query();
  loadApproverIds();
}
/** åˆ›å»ºè®¾å¤‡å…¥åº“单 */
@@ -60,41 +84,29 @@
  formModalApi.setData({ formType: 'finish', id: row.id }).open();
}
/** æäº¤å®¡æ‰¹ - å…ˆèŽ·å–æµç¨‹åˆ—è¡¨ */
async function handleSubmitApproval(row: MesWmEquipmentReceiptApi.EquipmentReceipt) {
/** æäº¤å®¡æ‰¹ - å®¡æ ¸äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€ç»Ÿä¸€æŒ‡å®šï¼Œæ­¤å¤„无需选择 */
async function handleSubmitApproval(
  row: MesWmEquipmentReceiptApi.EquipmentReceipt,
) {
  const hideLoading = message.loading({
    content: '获取审批流程...',
    content: '正在提交审批...',
    duration: 0,
  });
  try {
    const processList = await getEquipmentReceiptApproveProcessList();
    hideLoading();
    if (!processList || processList.length === 0) {
      message.warning('暂无可用审批流程,请先配置 BPM æµç¨‹');
      return;
    }
    processModalApi
      .setData({
        id: row.id,
        processList,
        submitFn: submitEquipmentReceiptApproval,
      })
      .open();
  } catch {
    await submitEquipmentReceiptApproval(row.id!);
    message.success('提交审批成功');
    handleRefresh();
  } finally {
    hideLoading();
  }
}
/** æŸ¥çœ‹å®¡æ‰¹è¯¦æƒ… */
function handleProcessDetail(row: MesWmEquipmentReceiptApi.EquipmentReceipt) {
  if (!row.processInstanceId) {
    message.warning('该单据尚未提交审批');
    return;
  }
  router.push({
    name: 'BpmProcessInstanceDetail',
    query: { id: row.processInstanceId },
  });
/** å®¡æ ¸ - é€šè¿‡ / ä¸é€šè¿‡ */
function handleAudit(
  row: MesWmEquipmentReceiptApi.EquipmentReceipt,
  pass: boolean,
) {
  auditModalApi.setData({ pass, row }).open();
}
/** åˆ é™¤è®¾å¤‡å…¥åº“单 */
@@ -150,11 +162,14 @@
});
const StatusEnum = MesWmEquipmentReceiptStatusEnum;
// è¿›å…¥é¡µé¢å³æ‹‰å–审核人集合,用于控制审核按钮显隐
loadApproverIds();
</script>
<template>
  <Page auto-content-height><FormModal @success="handleRefresh" />
    <ProcessModal @success="handleRefresh" />
    <AuditModalComp @success="handleRefresh" />
    <Grid table-title="设备入库单列表">
      <template #toolbar-tools>
@@ -207,15 +222,31 @@
              label: '提交审批',
              type: 'link',
              auth: ['mes:wm:equipment-receipt:submit'],
              // è‰ç¨¿å¯æäº¤ï¼›å®¡æ ¸ä¸é€šè¿‡ä¼šé€€å›žè‰ç¨¿ï¼Œå¯ä¿®æ”¹åŽé‡æ–°æäº¤
              ifShow: row.status === StatusEnum.PREPARE,
              onClick: handleSubmitApproval.bind(null, row),
              // å®¡æ ¸äººç”±å®¡æ‰¹é…ç½®ç»Ÿä¸€æŒ‡å®šï¼Œæäº¤å‰ä»…二次确认,不再弹选流程框
              popConfirm: {
                title: '确认提交审批?提交后由审批配置中指定的审核人处理。',
                confirm: handleSubmitApproval.bind(null, row),
              },
            },
            {
              label: '查看审批',
              label: '审核',
              type: 'link',
              auth: ['mes:wm:equipment-receipt:query'],
              ifShow: row.status === StatusEnum.APPROVING,
              onClick: handleProcessDetail.bind(null, row),
              icon: ACTION_ICON.AUDIT,
              auth: ['mes:wm:equipment-receipt:audit'],
              // ä»…「审批中 + å½“前登录用户在审批配置的审核人名单内」才显示
              ifShow: canAudit(row),
              onClick: handleAudit.bind(null, row, true),
            },
            {
              label: '审核不通过',
              type: 'link',
              danger: true,
              auth: ['mes:wm:equipment-receipt:audit'],
              // åŒä¸Šï¼Œä¸Žã€Œå®¡æ ¸ã€æˆå¯¹å‡ºçް
              ifShow: canAudit(row),
              onClick: handleAudit.bind(null, row, false),
            },
            {
              label: '执行入库',
src/views/wls/equipmentreceipt/modules/audit-modal.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,99 @@
<script lang="ts" setup>
import type { VbenFormSchema } from '#/adapter/form';
import type { MesWmEquipmentReceiptApi } from '#/api/mes/wm/equipmentreceipt';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { auditEquipmentReceipt } from '#/api/mes/wm/equipmentreceipt';
const emit = defineEmits(['success']);
const pass = ref(true); // true=审核通过 / false=审核不通过
const rowData = ref<MesWmEquipmentReceiptApi.EquipmentReceipt>();
const getTitle = computed(() => (pass.value ? '审核通过' : '审核不通过'));
const [Form, formApi] = useVbenForm({
  commonConfig: {
    componentProps: {
      class: 'w-full',
    },
    formItemClass: 'col-span-1',
    labelWidth: 90,
  },
  wrapperClass: 'grid-cols-1',
  layout: 'horizontal',
  schema: [],
  showDefaultActions: false,
});
/** å®¡æ ¸è¡¨å•字段:不通过时必须填写原因 */
function useAuditSchema(): VbenFormSchema[] {
  return [
    {
      fieldName: 'reviewRemark',
      label: '审核意见',
      component: 'Textarea',
      formItemClass: 'col-span-1',
      componentProps: {
        rows: 3,
        placeholder: pass.value
          ? '请输入审核意见(可选)'
          : '请输入不通过原因(必填)',
      },
      rules: pass.value ? '' : 'required',
    },
  ];
}
const [Modal, modalApi] = useVbenModal({
  async onConfirm() {
    const { valid } = await formApi.validate();
    if (!valid) {
      return;
    }
    modalApi.lock();
    const values = await formApi.getValues();
    try {
      await auditEquipmentReceipt(
        rowData.value!.id!,
        pass.value,
        values.reviewRemark,
      );
      await modalApi.close();
      emit('success');
      message.success(pass.value ? '审核通过' : '已退回,可修改后重新提交');
    } finally {
      modalApi.unlock();
    }
  },
  async onOpenChange(isOpen: boolean) {
    if (!isOpen) {
      rowData.value = undefined;
      return;
    }
    const data = modalApi.getData<{
      pass: boolean;
      row: MesWmEquipmentReceiptApi.EquipmentReceipt;
    }>();
    rowData.value = data.row;
    pass.value = data.pass;
    formApi.setState({ schema: useAuditSchema() });
    formApi.resetForm();
  },
});
</script>
<template>
  <Modal :title="getTitle" class="w-2/5">
    <div v-if="rowData" class="mx-4 mb-4 text-sm text-gray-600">
      <div>单据编码:{{ rowData.code }}</div>
      <div>单据名称:{{ rowData.name }}</div>
    </div>
    <Form class="mx-4" />
  </Modal>
</template>
src/views/wls/itemreceipt/index.vue
@@ -2,8 +2,11 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesWmItemReceiptApi } from '#/api/mes/wm/itemreceipt';
import { ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { MesWmItemReceiptStatusEnum } from '@vben/constants';
import { useUserStore } from '@vben/stores';
import { downloadFileFromBlobPart } from '@vben/utils';
import { Button, message } from 'ant-design-vue';
@@ -14,16 +17,47 @@
  deleteItemReceipt,
  exportItemReceipt,
  generateItemReceiptFromArrivalNotice,
  getItemReceiptApproverIds,
  getItemReceiptPage,
} from '#/api/mes/wm/itemreceipt';
import { $t } from '#/locales';
import { useGridColumns, useGridFormSchema } from './data';
import AuditModal from './modules/audit-modal.vue';
import Form from './modules/form.vue';
import AnSelectDialog from './modules/an-select-dialog.vue';
const userStore = useUserStore();
const currentUserId = userStore.userInfo?.id; // å½“前登录用户 ID,用于审核人权限判断
/** å®¡æ‰¹é…ç½®ä¸­è¯¥ä¸šåŠ¡çš„å®¡æ ¸äººç¼–å·é›†åˆï¼Œå†³å®šã€Œå®¡æ ¸ / å®¡æ ¸ä¸é€šè¿‡ã€æŒ‰é’®æ˜¯å¦å±•示 */
const approverIds = ref<number[]>([]);
/** æ‹‰å–审核人集合;失败时保持为空(按钮不展示),不阻断列表加载 */
async function loadApproverIds() {
  try {
    approverIds.value = (await getItemReceiptApproverIds()) ?? [];
  } catch {
    approverIds.value = [];
  }
}
/** å½“前登录用户是否为该业务的审核人,且单据处于审批中状态 */
function canAudit(row: MesWmItemReceiptApi.ItemReceipt) {
  return (
    row.status === MesWmItemReceiptStatusEnum.APPROVING &&
    !!currentUserId &&
    approverIds.value.includes(currentUserId)
  );
}
const [FormModal, formModalApi] = useVbenModal({
  connectedComponent: Form,
  destroyOnClose: true,
});
const [AuditModalComp, auditModalApi] = useVbenModal({
  connectedComponent: AuditModal,
  destroyOnClose: true,
});
@@ -32,9 +66,10 @@
  destroyOnClose: true,
});
/** åˆ·æ–°è¡¨æ ¼ */
/** åˆ·æ–°è¡¨æ ¼ä¸Žå®¡æ ¸äººé›†åˆ */
function handleRefresh() {
  gridApi.query();
  loadApproverIds();
}
/** åˆ›å»ºé‡‡è´­å…¥åº“单 */
@@ -106,6 +141,11 @@
  }
}
/** å®¡æ ¸ - é€šè¿‡ / ä¸é€šè¿‡ */
function handleAudit(row: MesWmItemReceiptApi.ItemReceipt, pass: boolean) {
  auditModalApi.setData({ pass, row }).open();
}
/** å¯¼å‡ºè¡¨æ ¼ */
async function handleExport() {
  const data = await exportItemReceipt(await gridApi.formApi.getValues());
@@ -141,11 +181,15 @@
    },
  } as VxeTableGridOptions<MesWmItemReceiptApi.ItemReceipt>,
});
// è¿›å…¥é¡µé¢å³æ‹‰å–审核人集合,用于控制审核按钮显隐
loadApproverIds();
</script>
<template>
  <Page auto-content-height>
    <FormModal @success="handleRefresh" />
    <AuditModalComp @success="handleRefresh" />
    <AnSelectModal @selected="handleArrivalNoticeSelected" />
    <Grid table-title="采购入库单列表">
@@ -212,6 +256,24 @@
              onClick: handleStock.bind(null, row),
            },
            {
              label: '审核',
              type: 'link',
              icon: ACTION_ICON.AUDIT,
              auth: ['mes:wm-item-receipt:audit'],
              // ä»…「审批中 + å½“前登录用户在审批配置的审核人名单内」才显示
              ifShow: canAudit(row),
              onClick: handleAudit.bind(null, row, true),
            },
            {
              label: '审核不通过',
              type: 'link',
              danger: true,
              auth: ['mes:wm-item-receipt:audit'],
              // åŒä¸Šï¼Œä¸Žã€Œå®¡æ ¸ã€æˆå¯¹å‡ºçް
              ifShow: canAudit(row),
              onClick: handleAudit.bind(null, row, false),
            },
            {
              label: '执行入库',
              type: 'link',
              auth: ['mes:wm-item-receipt:finish'],
src/views/wls/itemreceipt/modules/audit-modal.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,95 @@
<script lang="ts" setup>
import type { VbenFormSchema } from '#/adapter/form';
import type { MesWmItemReceiptApi } from '#/api/mes/wm/itemreceipt';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { auditItemReceipt } from '#/api/mes/wm/itemreceipt';
const emit = defineEmits(['success']);
const pass = ref(true); // true=审核通过 / false=审核不通过
const rowData = ref<MesWmItemReceiptApi.ItemReceipt>();
const getTitle = computed(() => (pass.value ? '审核通过' : '审核不通过'));
const [Form, formApi] = useVbenForm({
  commonConfig: {
    componentProps: {
      class: 'w-full',
    },
    formItemClass: 'col-span-1',
    labelWidth: 90,
  },
  wrapperClass: 'grid-cols-1',
  layout: 'horizontal',
  schema: [],
  showDefaultActions: false,
});
/** å®¡æ ¸è¡¨å•字段:不通过时必须填写原因 */
function useAuditSchema(): VbenFormSchema[] {
  return [
    {
      fieldName: 'reviewRemark',
      label: '审核意见',
      component: 'Textarea',
      formItemClass: 'col-span-1',
      componentProps: {
        rows: 3,
        placeholder: pass.value
          ? '请输入审核意见(可选)'
          : '请输入不通过原因(必填)',
      },
      rules: pass.value ? '' : 'required',
    },
  ];
}
const [Modal, modalApi] = useVbenModal({
  async onConfirm() {
    const { valid } = await formApi.validate();
    if (!valid) {
      return;
    }
    modalApi.lock();
    const values = await formApi.getValues();
    try {
      await auditItemReceipt(rowData.value!.id!, pass.value, values.reviewRemark);
      await modalApi.close();
      emit('success');
      message.success(pass.value ? '审核通过' : '已退回,可修改后重新提交');
    } finally {
      modalApi.unlock();
    }
  },
  async onOpenChange(isOpen: boolean) {
    if (!isOpen) {
      rowData.value = undefined;
      return;
    }
    const data = modalApi.getData<{
      pass: boolean;
      row: MesWmItemReceiptApi.ItemReceipt;
    }>();
    rowData.value = data.row;
    pass.value = data.pass;
    formApi.setState({ schema: useAuditSchema() });
    formApi.resetForm();
  },
});
</script>
<template>
  <Modal :title="getTitle" class="w-2/5">
    <div v-if="rowData" class="mx-4 mb-4 text-sm text-gray-600">
      <div>入库单编码:{{ rowData.code }}</div>
      <div>入库单名称:{{ rowData.name }}</div>
    </div>
    <Form class="mx-4" />
  </Modal>
</template>
src/views/wls/miscissue/index.vue
@@ -2,10 +2,11 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesWmMiscIssueApi } from '#/api/mes/wm/miscissue';
import { useRouter } from 'vue-router';
import { ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { MesWmMiscIssueStatusEnum } from '@vben/constants';
import { useUserStore } from '@vben/stores';
import { downloadFileFromBlobPart } from '@vben/utils';
import { Button, message } from 'ant-design-vue';
@@ -15,31 +16,54 @@
  cancelMiscIssue,
  deleteMiscIssue,
  exportMiscIssue,
  getMiscIssueApproveProcessList,
  getMiscIssueApproverIds,
  getMiscIssuePage,
  submitMiscIssueApproval,
} from '#/api/mes/wm/miscissue';
import { $t } from '#/locales';
import ProcessSelectModal from '#/views/wls/components/process-select-modal.vue';
import { useGridColumns, useGridFormSchema } from './data';
import AuditModal from './modules/audit-modal.vue';
import Form from './modules/form.vue';
const router = useRouter();
const userStore = useUserStore();
const currentUserId = userStore.userInfo?.id; // å½“前登录用户 ID,用于审核人权限判断
/** å®¡æ‰¹é…ç½®ä¸­è¯¥ä¸šåŠ¡çš„å®¡æ ¸äººç¼–å·é›†åˆï¼Œå†³å®šã€Œå®¡æ ¸ / å®¡æ ¸ä¸é€šè¿‡ã€æŒ‰é’®æ˜¯å¦å±•示 */
const approverIds = ref<number[]>([]);
/** æ‹‰å–审核人集合;失败时保持为空(按钮不展示),不阻断列表加载 */
async function loadApproverIds() {
  try {
    approverIds.value = (await getMiscIssueApproverIds()) ?? [];
  } catch {
    approverIds.value = [];
  }
}
/** å½“前登录用户是否为该业务的审核人,且单据处于审批中状态 */
function canAudit(row: MesWmMiscIssueApi.MiscIssue) {
  return (
    row.status === MesWmMiscIssueStatusEnum.APPROVING &&
    !!currentUserId &&
    approverIds.value.includes(currentUserId)
  );
}
const [FormModal, formModalApi] = useVbenModal({
  connectedComponent: Form,
  destroyOnClose: true,
});
const [ProcessModal, processModalApi] = useVbenModal({
  connectedComponent: ProcessSelectModal,
const [AuditModalComp, auditModalApi] = useVbenModal({
  connectedComponent: AuditModal,
  destroyOnClose: true,
});
/** åˆ·æ–°è¡¨æ ¼ */
/** åˆ·æ–°è¡¨æ ¼ä¸Žå®¡æ ¸äººé›†åˆ */
function handleRefresh() {
  gridApi.query();
  loadApproverIds();
}
/** åˆ›å»ºæ‚项出库单 */
@@ -62,41 +86,24 @@
  formModalApi.setData({ formType: 'finish', id: row.id }).open();
}
/** æäº¤å®¡æ‰¹ - å…ˆèŽ·å–æµç¨‹åˆ—è¡¨ */
/** æäº¤å®¡æ‰¹ - å®¡æ ¸äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€ç»Ÿä¸€æŒ‡å®šï¼Œæ­¤å¤„无需选择 */
async function handleSubmitApproval(row: MesWmMiscIssueApi.MiscIssue) {
  const hideLoading = message.loading({
    content: '获取审批流程...',
    content: '正在提交审批...',
    duration: 0,
  });
  try {
    const processList = await getMiscIssueApproveProcessList();
    hideLoading();
    if (!processList || processList.length === 0) {
      message.warning('暂无可用审批流程,请先配置 BPM æµç¨‹');
      return;
    }
    processModalApi
      .setData({
        id: row.id,
        processList,
        submitFn: submitMiscIssueApproval,
      })
      .open();
  } catch {
    await submitMiscIssueApproval(row.id!);
    message.success('提交审批成功');
    handleRefresh();
  } finally {
    hideLoading();
  }
}
/** æŸ¥çœ‹å®¡æ‰¹è¯¦æƒ… */
function handleProcessDetail(row: MesWmMiscIssueApi.MiscIssue) {
  if (!row.processInstanceId) {
    message.warning('该单据尚未提交审批');
    return;
  }
  router.push({
    name: 'BpmProcessInstanceDetail',
    query: { id: row.processInstanceId },
  });
/** å®¡æ ¸ - é€šè¿‡ / ä¸é€šè¿‡ */
function handleAudit(row: MesWmMiscIssueApi.MiscIssue, pass: boolean) {
  auditModalApi.setData({ pass, row }).open();
}
/** åˆ é™¤æ‚项出库单 */
@@ -156,11 +163,15 @@
    },
  } as VxeTableGridOptions<MesWmMiscIssueApi.MiscIssue>,
});
// è¿›å…¥é¡µé¢å³æ‹‰å–审核人集合,用于控制审核按钮显隐
loadApproverIds();
</script>
<template>
  <Page auto-content-height><FormModal @success="handleRefresh" />
    <ProcessModal @success="handleRefresh" />
  <Page auto-content-height>
    <FormModal @success="handleRefresh" />
    <AuditModalComp @success="handleRefresh" />
    <Grid table-title="杂项出库单列表">
      <template #toolbar-tools>
@@ -216,14 +227,29 @@
              type: 'link',
              auth: ['mes:wm-misc-issue:update'],
              ifShow: row.status === MesWmMiscIssueStatusEnum.PREPARE,
              onClick: handleSubmitApproval.bind(null, row),
              // å®¡æ ¸äººç”±å®¡æ‰¹é…ç½®ç»Ÿä¸€æŒ‡å®šï¼Œæäº¤å‰ä»…二次确认,不再弹选流程框
              popConfirm: {
                title: '确认提交审批?提交后由审批配置中指定的审核人处理。',
                confirm: handleSubmitApproval.bind(null, row),
              },
            },
            {
              label: '查看审批',
              label: '审核',
              type: 'link',
              auth: ['mes:wm-misc-issue:query'],
              ifShow: row.status === MesWmMiscIssueStatusEnum.APPROVING,
              onClick: handleProcessDetail.bind(null, row),
              icon: ACTION_ICON.AUDIT,
              auth: ['mes:wm-misc-issue:audit'],
              // ä»…「审批中 + å½“前登录用户在审批配置的审核人名单内」才显示
              ifShow: canAudit(row),
              onClick: handleAudit.bind(null, row, true),
            },
            {
              label: '审核不通过',
              type: 'link',
              danger: true,
              auth: ['mes:wm-misc-issue:audit'],
              // åŒä¸Šï¼Œä¸Žã€Œå®¡æ ¸ã€æˆå¯¹å‡ºçް
              ifShow: canAudit(row),
              onClick: handleAudit.bind(null, row, false),
            },
            {
              label: '执行出库',
src/views/wls/miscissue/modules/audit-modal.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,95 @@
<script lang="ts" setup>
import type { VbenFormSchema } from '#/adapter/form';
import type { MesWmMiscIssueApi } from '#/api/mes/wm/miscissue';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { auditMiscIssue } from '#/api/mes/wm/miscissue';
const emit = defineEmits(['success']);
const pass = ref(true); // true=审核通过 / false=审核不通过
const rowData = ref<MesWmMiscIssueApi.MiscIssue>();
const getTitle = computed(() => (pass.value ? '审核通过' : '审核不通过'));
const [Form, formApi] = useVbenForm({
  commonConfig: {
    componentProps: {
      class: 'w-full',
    },
    formItemClass: 'col-span-1',
    labelWidth: 90,
  },
  wrapperClass: 'grid-cols-1',
  layout: 'horizontal',
  schema: [],
  showDefaultActions: false,
});
/** å®¡æ ¸è¡¨å•字段:不通过时必须填写原因 */
function useAuditSchema(): VbenFormSchema[] {
  return [
    {
      fieldName: 'reviewRemark',
      label: '审核意见',
      component: 'Textarea',
      formItemClass: 'col-span-1',
      componentProps: {
        rows: 3,
        placeholder: pass.value
          ? '请输入审核意见(可选)'
          : '请输入不通过原因(必填)',
      },
      rules: pass.value ? '' : 'required',
    },
  ];
}
const [Modal, modalApi] = useVbenModal({
  async onConfirm() {
    const { valid } = await formApi.validate();
    if (!valid) {
      return;
    }
    modalApi.lock();
    const values = await formApi.getValues();
    try {
      await auditMiscIssue(rowData.value!.id!, pass.value, values.reviewRemark);
      await modalApi.close();
      emit('success');
      message.success(pass.value ? '审核通过' : '已退回,可修改后重新提交');
    } finally {
      modalApi.unlock();
    }
  },
  async onOpenChange(isOpen: boolean) {
    if (!isOpen) {
      rowData.value = undefined;
      return;
    }
    const data = modalApi.getData<{
      pass: boolean;
      row: MesWmMiscIssueApi.MiscIssue;
    }>();
    rowData.value = data.row;
    pass.value = data.pass;
    formApi.setState({ schema: useAuditSchema() });
    formApi.resetForm();
  },
});
</script>
<template>
  <Modal :title="getTitle" class="w-2/5">
    <div v-if="rowData" class="mx-4 mb-4 text-sm text-gray-600">
      <div>出库单编号:{{ rowData.code }}</div>
      <div>出库单名称:{{ rowData.name }}</div>
    </div>
    <Form class="mx-4" />
  </Modal>
</template>
src/views/wls/miscreceipt/index.vue
@@ -2,10 +2,11 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesWmMiscReceiptApi } from '#/api/mes/wm/miscreceipt';
import { useRouter } from 'vue-router';
import { ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { MesWmMiscReceiptStatusEnum } from '@vben/constants';
import { useUserStore } from '@vben/stores';
import { downloadFileFromBlobPart } from '@vben/utils';
import { Button, message } from 'ant-design-vue';
@@ -15,31 +16,54 @@
  cancelMiscReceipt,
  deleteMiscReceipt,
  exportMiscReceipt,
  getMiscReceiptApproveProcessList,
  getMiscReceiptApproverIds,
  getMiscReceiptPage,
  submitMiscReceiptApproval,
} from '#/api/mes/wm/miscreceipt';
import { $t } from '#/locales';
import ProcessSelectModal from '#/views/wls/components/process-select-modal.vue';
import { useGridColumns, useGridFormSchema } from './data';
import AuditModal from './modules/audit-modal.vue';
import Form from './modules/form.vue';
const router = useRouter();
const userStore = useUserStore();
const currentUserId = userStore.userInfo?.id; // å½“前登录用户 ID,用于审核人权限判断
/** å®¡æ‰¹é…ç½®ä¸­è¯¥ä¸šåŠ¡çš„å®¡æ ¸äººç¼–å·é›†åˆï¼Œå†³å®šã€Œå®¡æ ¸ / å®¡æ ¸ä¸é€šè¿‡ã€æŒ‰é’®æ˜¯å¦å±•示 */
const approverIds = ref<number[]>([]);
/** æ‹‰å–审核人集合;失败时保持为空(按钮不展示),不阻断列表加载 */
async function loadApproverIds() {
  try {
    approverIds.value = (await getMiscReceiptApproverIds()) ?? [];
  } catch {
    approverIds.value = [];
  }
}
/** å½“前登录用户是否为该业务的审核人,且单据处于审批中状态 */
function canAudit(row: MesWmMiscReceiptApi.MiscReceipt) {
  return (
    row.status === MesWmMiscReceiptStatusEnum.APPROVING &&
    !!currentUserId &&
    approverIds.value.includes(currentUserId)
  );
}
const [FormModal, formModalApi] = useVbenModal({
  connectedComponent: Form,
  destroyOnClose: true,
});
const [ProcessModal, processModalApi] = useVbenModal({
  connectedComponent: ProcessSelectModal,
const [AuditModalComp, auditModalApi] = useVbenModal({
  connectedComponent: AuditModal,
  destroyOnClose: true,
});
/** åˆ·æ–°è¡¨æ ¼ */
/** åˆ·æ–°è¡¨æ ¼ä¸Žå®¡æ ¸äººé›†åˆ */
function handleRefresh() {
  gridApi.query();
  loadApproverIds();
}
/** åˆ›å»ºæ‚项入库单 */
@@ -62,41 +86,24 @@
  formModalApi.setData({ formType: 'finish', id: row.id }).open();
}
/** æäº¤å®¡æ‰¹ - å…ˆèŽ·å–æµç¨‹åˆ—è¡¨ */
/** æäº¤å®¡æ‰¹ - å®¡æ ¸äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€ç»Ÿä¸€æŒ‡å®šï¼Œæ­¤å¤„无需选择 */
async function handleSubmitApproval(row: MesWmMiscReceiptApi.MiscReceipt) {
  const hideLoading = message.loading({
    content: '获取审批流程...',
    content: '正在提交审批...',
    duration: 0,
  });
  try {
    const processList = await getMiscReceiptApproveProcessList();
    hideLoading();
    if (!processList || processList.length === 0) {
      message.warning('暂无可用审批流程,请先配置 BPM æµç¨‹');
      return;
    }
    processModalApi
      .setData({
        id: row.id,
        processList,
        submitFn: submitMiscReceiptApproval,
      })
      .open();
  } catch {
    await submitMiscReceiptApproval(row.id!);
    message.success('提交审批成功');
    handleRefresh();
  } finally {
    hideLoading();
  }
}
/** æŸ¥çœ‹å®¡æ‰¹è¯¦æƒ… */
function handleProcessDetail(row: MesWmMiscReceiptApi.MiscReceipt) {
  if (!row.processInstanceId) {
    message.warning('该单据尚未提交审批');
    return;
  }
  router.push({
    name: 'BpmProcessInstanceDetail',
    query: { id: row.processInstanceId },
  });
/** å®¡æ ¸ - é€šè¿‡ / ä¸é€šè¿‡ */
function handleAudit(row: MesWmMiscReceiptApi.MiscReceipt, pass: boolean) {
  auditModalApi.setData({ pass, row }).open();
}
/** åˆ é™¤æ‚项入库单 */
@@ -156,11 +163,15 @@
    },
  } as VxeTableGridOptions<MesWmMiscReceiptApi.MiscReceipt>,
});
// è¿›å…¥é¡µé¢å³æ‹‰å–审核人集合,用于控制审核按钮显隐
loadApproverIds();
</script>
<template>
  <Page auto-content-height><FormModal @success="handleRefresh" />
    <ProcessModal @success="handleRefresh" />
  <Page auto-content-height>
    <FormModal @success="handleRefresh" />
    <AuditModalComp @success="handleRefresh" />
    <Grid table-title="杂项入库单列表">
      <template #toolbar-tools>
@@ -216,14 +227,29 @@
              type: 'link',
              auth: ['mes:wm:misc-receipt:submit'],
              ifShow: row.status === MesWmMiscReceiptStatusEnum.PREPARE,
              onClick: handleSubmitApproval.bind(null, row),
              // å®¡æ ¸äººç”±å®¡æ‰¹é…ç½®ç»Ÿä¸€æŒ‡å®šï¼Œæäº¤å‰ä»…二次确认,不再弹选流程框
              popConfirm: {
                title: '确认提交审批?提交后由审批配置中指定的审核人处理。',
                confirm: handleSubmitApproval.bind(null, row),
              },
            },
            {
              label: '查看审批',
              label: '审核',
              type: 'link',
              auth: ['mes:wm:misc-receipt:query'],
              ifShow: row.status === MesWmMiscReceiptStatusEnum.APPROVING,
              onClick: handleProcessDetail.bind(null, row),
              icon: ACTION_ICON.AUDIT,
              auth: ['mes:wm:misc-receipt:audit'],
              // ä»…「审批中 + å½“前登录用户在审批配置的审核人名单内」才显示
              ifShow: canAudit(row),
              onClick: handleAudit.bind(null, row, true),
            },
            {
              label: '审核不通过',
              type: 'link',
              danger: true,
              auth: ['mes:wm:misc-receipt:audit'],
              // åŒä¸Šï¼Œä¸Žã€Œå®¡æ ¸ã€æˆå¯¹å‡ºçް
              ifShow: canAudit(row),
              onClick: handleAudit.bind(null, row, false),
            },
            {
              label: '执行入库',
src/views/wls/miscreceipt/modules/audit-modal.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,95 @@
<script lang="ts" setup>
import type { VbenFormSchema } from '#/adapter/form';
import type { MesWmMiscReceiptApi } from '#/api/mes/wm/miscreceipt';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { auditMiscReceipt } from '#/api/mes/wm/miscreceipt';
const emit = defineEmits(['success']);
const pass = ref(true); // true=审核通过 / false=审核不通过
const rowData = ref<MesWmMiscReceiptApi.MiscReceipt>();
const getTitle = computed(() => (pass.value ? '审核通过' : '审核不通过'));
const [Form, formApi] = useVbenForm({
  commonConfig: {
    componentProps: {
      class: 'w-full',
    },
    formItemClass: 'col-span-1',
    labelWidth: 90,
  },
  wrapperClass: 'grid-cols-1',
  layout: 'horizontal',
  schema: [],
  showDefaultActions: false,
});
/** å®¡æ ¸è¡¨å•字段:不通过时必须填写原因 */
function useAuditSchema(): VbenFormSchema[] {
  return [
    {
      fieldName: 'reviewRemark',
      label: '审核意见',
      component: 'Textarea',
      formItemClass: 'col-span-1',
      componentProps: {
        rows: 3,
        placeholder: pass.value
          ? '请输入审核意见(可选)'
          : '请输入不通过原因(必填)',
      },
      rules: pass.value ? '' : 'required',
    },
  ];
}
const [Modal, modalApi] = useVbenModal({
  async onConfirm() {
    const { valid } = await formApi.validate();
    if (!valid) {
      return;
    }
    modalApi.lock();
    const values = await formApi.getValues();
    try {
      await auditMiscReceipt(rowData.value!.id!, pass.value, values.reviewRemark);
      await modalApi.close();
      emit('success');
      message.success(pass.value ? '审核通过' : '已退回,可修改后重新提交');
    } finally {
      modalApi.unlock();
    }
  },
  async onOpenChange(isOpen: boolean) {
    if (!isOpen) {
      rowData.value = undefined;
      return;
    }
    const data = modalApi.getData<{
      pass: boolean;
      row: MesWmMiscReceiptApi.MiscReceipt;
    }>();
    rowData.value = data.row;
    pass.value = data.pass;
    formApi.setState({ schema: useAuditSchema() });
    formApi.resetForm();
  },
});
</script>
<template>
  <Modal :title="getTitle" class="w-2/5">
    <div v-if="rowData" class="mx-4 mb-4 text-sm text-gray-600">
      <div>入库单编号:{{ rowData.code }}</div>
      <div>入库单名称:{{ rowData.name }}</div>
    </div>
    <Form class="mx-4" />
  </Modal>
</template>
src/views/wls/returnvendor/index.vue
@@ -2,10 +2,11 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesWmReturnVendorApi } from '#/api/mes/wm/returnvendor';
import { useRouter } from 'vue-router';
import { ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { MesWmReturnVendorStatusEnum } from '@vben/constants';
import { useUserStore } from '@vben/stores';
import { downloadFileFromBlobPart } from '@vben/utils';
import { Button, message } from 'ant-design-vue';
@@ -15,31 +16,54 @@
  cancelReturnVendor,
  deleteReturnVendor,
  exportReturnVendor,
  getReturnVendorApproveProcessList,
  getReturnVendorApproverIds,
  getReturnVendorPage,
  submitReturnVendorApproval,
} from '#/api/mes/wm/returnvendor';
import { $t } from '#/locales';
import ProcessSelectModal from '#/views/wls/components/process-select-modal.vue';
import { useGridColumns, useGridFormSchema } from './data';
import AuditModal from './modules/audit-modal.vue';
import Form from './modules/form.vue';
const router = useRouter();
const userStore = useUserStore();
const currentUserId = userStore.userInfo?.id; // å½“前登录用户 ID,用于审核人权限判断
/** å®¡æ‰¹é…ç½®ä¸­è¯¥ä¸šåŠ¡çš„å®¡æ ¸äººç¼–å·é›†åˆï¼Œå†³å®šã€Œå®¡æ ¸ / å®¡æ ¸ä¸é€šè¿‡ã€æŒ‰é’®æ˜¯å¦å±•示 */
const approverIds = ref<number[]>([]);
/** æ‹‰å–审核人集合;失败时保持为空(按钮不展示),不阻断列表加载 */
async function loadApproverIds() {
  try {
    approverIds.value = (await getReturnVendorApproverIds()) ?? [];
  } catch {
    approverIds.value = [];
  }
}
/** å½“前登录用户是否为该业务的审核人,且单据处于待拣货状态 */
function canAudit(row: MesWmReturnVendorApi.ReturnVendor) {
  return (
    row.status === MesWmReturnVendorStatusEnum.APPROVING &&
    !!currentUserId &&
    approverIds.value.includes(currentUserId)
  );
}
const [FormModal, formModalApi] = useVbenModal({
  connectedComponent: Form,
  destroyOnClose: true,
});
const [ProcessModal, processModalApi] = useVbenModal({
  connectedComponent: ProcessSelectModal,
const [AuditModalComp, auditModalApi] = useVbenModal({
  connectedComponent: AuditModal,
  destroyOnClose: true,
});
/** åˆ·æ–°è¡¨æ ¼ */
/** åˆ·æ–°è¡¨æ ¼ä¸Žå®¡æ ¸äººé›†åˆ */
function handleRefresh() {
  gridApi.query();
  loadApproverIds();
}
/** åˆ›å»ºä¾›åº”商退货单 */
@@ -62,41 +86,24 @@
  formModalApi.setData({ formType: 'stock', id: row.id }).open();
}
/** æäº¤å®¡æ‰¹ - å…ˆèŽ·å–æµç¨‹åˆ—è¡¨ */
/** æäº¤å®¡æ‰¹ - å®¡æ ¸äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€ç»Ÿä¸€æŒ‡å®šï¼Œæ­¤å¤„无需选择 */
async function handleSubmitApproval(row: MesWmReturnVendorApi.ReturnVendor) {
  const hideLoading = message.loading({
    content: '获取审批流程...',
    content: '正在提交审批...',
    duration: 0,
  });
  try {
    const processList = await getReturnVendorApproveProcessList();
    hideLoading();
    if (!processList || processList.length === 0) {
      message.warning('暂无可用审批流程,请先配置 BPM æµç¨‹');
      return;
    }
    processModalApi
      .setData({
        id: row.id,
        processList,
        submitFn: submitReturnVendorApproval,
      })
      .open();
  } catch {
    await submitReturnVendorApproval(row.id!);
    message.success('提交审批成功');
    handleRefresh();
  } finally {
    hideLoading();
  }
}
/** æŸ¥çœ‹å®¡æ‰¹è¯¦æƒ… */
function handleProcessDetail(row: MesWmReturnVendorApi.ReturnVendor) {
  if (!row.processInstanceId) {
    message.warning('该单据尚未提交审批');
    return;
  }
  router.push({
    name: 'BpmProcessInstanceDetail',
    query: { id: row.processInstanceId },
  });
/** å®¡æ ¸ - é€šè¿‡ / ä¸é€šè¿‡ */
function handleAudit(row: MesWmReturnVendorApi.ReturnVendor, pass: boolean) {
  auditModalApi.setData({ pass, row }).open();
}
/** å®Œæˆé€€è´§ */
@@ -161,11 +168,15 @@
    },
  } as VxeTableGridOptions<MesWmReturnVendorApi.ReturnVendor>,
});
// è¿›å…¥é¡µé¢å³æ‹‰å–审核人集合,用于控制审核按钮显隐
loadApproverIds();
</script>
<template>
  <Page auto-content-height><FormModal @success="handleRefresh" />
    <ProcessModal @success="handleRefresh" />
  <Page auto-content-height>
    <FormModal @success="handleRefresh" />
    <AuditModalComp @success="handleRefresh" />
    <Grid table-title="供应商退货单列表">
      <template #toolbar-tools>
@@ -201,7 +212,29 @@
              type: 'link',
              auth: ['mes:wm-return-vendor:update'],
              ifShow: row.status === MesWmReturnVendorStatusEnum.PREPARE,
              onClick: handleSubmitApproval.bind(null, row),
              // å®¡æ ¸äººç”±å®¡æ‰¹é…ç½®ç»Ÿä¸€æŒ‡å®šï¼Œæäº¤å‰ä»…二次确认,不再弹选流程框
              popConfirm: {
                title: '确认提交审批?提交后由审批配置中指定的审核人处理。',
                confirm: handleSubmitApproval.bind(null, row),
              },
            },
            {
              label: '审核',
              type: 'link',
              icon: ACTION_ICON.AUDIT,
              auth: ['mes:wm-return-vendor:audit'],
              // ä»…「待拣货 + å½“前登录用户在审批配置的审核人名单内」才显示
              ifShow: canAudit(row),
              onClick: handleAudit.bind(null, row, true),
            },
            {
              label: '审核不通过',
              type: 'link',
              danger: true,
              auth: ['mes:wm-return-vendor:audit'],
              // åŒä¸Šï¼Œä¸Žã€Œå®¡æ ¸ã€æˆå¯¹å‡ºçް
              ifShow: canAudit(row),
              onClick: handleAudit.bind(null, row, false),
            },
            {
              label: $t('common.edit'),
@@ -222,13 +255,6 @@
                title: $t('ui.actionMessage.deleteConfirm', [row.code]),
                confirm: handleDelete.bind(null, row),
              },
            },
            {
              label: '查看审批',
              type: 'link',
              auth: ['mes:wm-return-vendor:query'],
              ifShow: row.status === MesWmReturnVendorStatusEnum.APPROVING,
              onClick: handleProcessDetail.bind(null, row),
            },
            {
              label: '执行拣货',
src/views/wls/returnvendor/modules/audit-modal.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,95 @@
<script lang="ts" setup>
import type { VbenFormSchema } from '#/adapter/form';
import type { MesWmReturnVendorApi } from '#/api/mes/wm/returnvendor';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { auditReturnVendor } from '#/api/mes/wm/returnvendor';
const emit = defineEmits(['success']);
const pass = ref(true); // true=审核通过 / false=审核不通过
const rowData = ref<MesWmReturnVendorApi.ReturnVendor>();
const getTitle = computed(() => (pass.value ? '审核通过' : '审核不通过'));
const [Form, formApi] = useVbenForm({
  commonConfig: {
    componentProps: {
      class: 'w-full',
    },
    formItemClass: 'col-span-1',
    labelWidth: 90,
  },
  wrapperClass: 'grid-cols-1',
  layout: 'horizontal',
  schema: [],
  showDefaultActions: false,
});
/** å®¡æ ¸è¡¨å•字段:不通过时必须填写原因 */
function useAuditSchema(): VbenFormSchema[] {
  return [
    {
      fieldName: 'reviewRemark',
      label: '审核意见',
      component: 'Textarea',
      formItemClass: 'col-span-1',
      componentProps: {
        rows: 3,
        placeholder: pass.value
          ? '请输入审核意见(可选)'
          : '请输入不通过原因(必填)',
      },
      rules: pass.value ? '' : 'required',
    },
  ];
}
const [Modal, modalApi] = useVbenModal({
  async onConfirm() {
    const { valid } = await formApi.validate();
    if (!valid) {
      return;
    }
    modalApi.lock();
    const values = await formApi.getValues();
    try {
      await auditReturnVendor(rowData.value!.id!, pass.value, values.reviewRemark);
      await modalApi.close();
      emit('success');
      message.success(pass.value ? '审核通过' : '已退回,可修改后重新提交');
    } finally {
      modalApi.unlock();
    }
  },
  async onOpenChange(isOpen: boolean) {
    if (!isOpen) {
      rowData.value = undefined;
      return;
    }
    const data = modalApi.getData<{
      pass: boolean;
      row: MesWmReturnVendorApi.ReturnVendor;
    }>();
    rowData.value = data.row;
    pass.value = data.pass;
    formApi.setState({ schema: useAuditSchema() });
    formApi.resetForm();
  },
});
</script>
<template>
  <Modal :title="getTitle" class="w-2/5">
    <div v-if="rowData" class="mx-4 mb-4 text-sm text-gray-600">
      <div>退货单编号:{{ rowData.code }}</div>
      <div>供应商:{{ rowData.vendorName }}</div>
    </div>
    <Form class="mx-4" />
  </Modal>
</template>
src/views/wls/stocktaking/task/index.vue
@@ -3,10 +3,10 @@
import type { MesWmStockTakingTaskApi } from '#/api/mes/wm/stocktaking/task';
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import { Page, useVbenModal } from '@vben/common-ui';
import { MesWmStockTakingTaskStatusEnum } from '@vben/constants';
import { useUserStore } from '@vben/stores';
import { downloadFileFromBlobPart } from '@vben/utils';
import { Button, message } from 'ant-design-vue';
@@ -16,7 +16,7 @@
  cancelStockTaking,
  deleteStockTaking,
  exportStockTaking,
  getStockTakingApproveProcessList,
  getStockTakingApproverIds,
  getStockTakingPage,
  submitStockTakingApproval,
} from '#/api/mes/wm/stocktaking/task';
@@ -26,25 +26,70 @@
} from '#/api/wls/stocktaking/task/line';
import { $t } from '#/locales';
import ProcessSelectModal from '#/views/wls/components/process-select-modal.vue';
import { useGridColumns, useGridFormSchema } from './data';
import AuditModal from './modules/audit-modal.vue';
import Form from './modules/form.vue';
const router = useRouter();
const userStore = useUserStore();
const currentUserId = userStore.userInfo?.id; // å½“前登录用户 ID,用于审核人权限判断
/** å®¡æ‰¹é…ç½®ä¸­è¯¥ä¸šåŠ¡çš„å®¡æ ¸äººç¼–å·é›†åˆï¼Œå†³å®šã€Œå®¡æ ¸ / å®¡æ ¸ä¸é€šè¿‡ã€æŒ‰é’®æ˜¯å¦å±•示 */
const approverIds = ref<number[]>([]);
/** æ‹‰å–审核人集合;失败时保持为空(按钮不展示),不阻断列表加载 */
async function loadApproverIds() {
  try {
    approverIds.value = (await getStockTakingApproverIds()) ?? [];
  } catch {
    approverIds.value = [];
  }
}
/**
 * æœ¬è½®ç›˜ç‚¹ç»“果审批是否已提交、正在等待审核
 *
 * ä»»åŠ¡çŠ¶æ€ã€Œç›˜ç‚¹ä¸­ã€åŒæ—¶æ‰¿è½½ç›˜ç‚¹ä¸­ä¸Žç»“æžœå®¡æ‰¹ä¸­ä¸¤ä¸ªé˜¶æ®µï¼š
 * reviewTime éžç©ºä¸” reviewerId ä¸ºç©ºè¡¨ç¤ºç»“果已提交、等待审核;
 * å®¡æ ¸ä¸é€šè¿‡åŽ reviewerId éžç©ºï¼Œä»»åŠ¡å›žåˆ°å¯é‡æ–°æäº¤ç»“æžœå®¡æ‰¹çš„çŠ¶æ€ã€‚
 */
function isResultApprovalPending(row: MesWmStockTakingTaskApi.StockTakingTask) {
  return !row.reviewerId && !!row.reviewTime;
}
/** å½“前登录用户是否为该业务的审核人,且结果审批已提交等待审核 */
function canAudit(row: MesWmStockTakingTaskApi.StockTakingTask) {
  return (
    row.status === MesWmStockTakingTaskStatusEnum.APPROVING &&
    isResultApprovalPending(row) &&
    !!currentUserId &&
    approverIds.value.includes(currentUserId)
  );
}
/** æ˜¯å¦å¯æäº¤ç»“果审批:盘点中且本轮结果审批尚未提交 */
function canSubmitApproval(
  row: MesWmStockTakingTaskApi.StockTakingTask,
) {
  return (
    row.status === MesWmStockTakingTaskStatusEnum.APPROVING &&
    !isResultApprovalPending(row)
  );
}
const [FormModal, formModalApi] = useVbenModal({
  connectedComponent: Form,
  destroyOnClose: true,
});
const [ProcessModal, processModalApi] = useVbenModal({
  connectedComponent: ProcessSelectModal,
const [AuditModalComp, auditModalApi] = useVbenModal({
  connectedComponent: AuditModal,
  destroyOnClose: true,
});
/** åˆ·æ–°è¡¨æ ¼ */
/** åˆ·æ–°è¡¨æ ¼ä¸Žå®¡æ ¸äººé›†åˆ */
function handleRefresh() {
  gridApi.query();
  loadApproverIds();
}
/** åˆ›å»ºç›˜ç‚¹ä»»åŠ¡ */
@@ -67,41 +112,24 @@
  formModalApi.setData({ formType: 'submit', id: row.id }).open();
}
/** æäº¤ç»“果审批 - å…ˆèŽ·å–æµç¨‹åˆ—è¡¨ */
/** æäº¤ç»“果审批 - å®¡æ ¸äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€ç»Ÿä¸€æŒ‡å®šï¼Œæ­¤å¤„无需选择 */
async function handleSubmitApproval(row: MesWmStockTakingTaskApi.StockTakingTask) {
  const hideLoading = message.loading({
    content: '获取审批流程...',
    content: '正在提交结果审批...',
    duration: 0,
  });
  try {
    const processList = await getStockTakingApproveProcessList();
    hideLoading();
    if (!processList || processList.length === 0) {
      message.warning('暂无可用审批流程,请先配置 BPM æµç¨‹');
      return;
    }
    processModalApi
      .setData({
        id: row.id,
        processList,
        submitFn: submitStockTakingApproval,
      })
      .open();
  } catch {
    await submitStockTakingApproval(row.id!);
    message.success('提交结果审批成功');
    handleRefresh();
  } finally {
    hideLoading();
  }
}
/** æŸ¥çœ‹å®¡æ‰¹è¯¦æƒ… */
function handleProcessDetail(row: MesWmStockTakingTaskApi.StockTakingTask) {
  if (!row.processInstanceId) {
    message.warning('该任务尚未提交结果审批');
    return;
  }
  router.push({
    name: 'BpmProcessInstanceDetail',
    query: { id: row.processInstanceId },
  });
/** å®¡æ ¸ - é€šè¿‡ / ä¸é€šè¿‡ */
function handleAudit(row: MesWmStockTakingTaskApi.StockTakingTask, pass: boolean) {
  auditModalApi.setData({ pass, row }).open();
}
/** å–消盘点任务 */
@@ -198,11 +226,15 @@
});
const StatusEnum = MesWmStockTakingTaskStatusEnum;
// è¿›å…¥é¡µé¢å³æ‹‰å–审核人集合,用于控制审核按钮显隐
loadApproverIds();
</script>
<template>
  <Page auto-content-height><FormModal @success="handleRefresh" />
    <ProcessModal @success="handleRefresh" />
  <Page auto-content-height>
    <FormModal @success="handleRefresh" />
    <AuditModalComp @success="handleRefresh" />
    <Grid table-title="盘点任务列表">
      <template #toolbar-tools>
@@ -279,15 +311,31 @@
              label: '提交结果审批',
              type: 'link',
              auth: ['mes:wm-stock-taking-task:update'],
              ifShow: row.status === StatusEnum.APPROVING,
              onClick: handleSubmitApproval.bind(null, row),
              // ç›˜ç‚¹ä¸­ä¸”本轮结果审批尚未提交时才可提交
              ifShow: canSubmitApproval(row),
              // å®¡æ ¸äººç”±å®¡æ‰¹é…ç½®ç»Ÿä¸€æŒ‡å®šï¼Œæäº¤å‰ä»…二次确认,不再弹选流程框
              popConfirm: {
                title: '确认提交结果审批?提交后由审批配置中指定的审核人处理。',
                confirm: handleSubmitApproval.bind(null, row),
              },
            },
            {
              label: '查看审批',
              label: '审核',
              type: 'link',
              auth: ['mes:wm-stock-taking-task:query'],
              ifShow: row.status === StatusEnum.APPROVING,
              onClick: handleProcessDetail.bind(null, row),
              icon: ACTION_ICON.AUDIT,
              auth: ['mes:wm-stock-taking-task:audit'],
              // ä»…「结果审批中 + å½“前登录用户在审批配置的审核人名单内」才显示
              ifShow: canAudit(row),
              onClick: handleAudit.bind(null, row, true),
            },
            {
              label: '审核不通过',
              type: 'link',
              danger: true,
              auth: ['mes:wm-stock-taking-task:audit'],
              // åŒä¸Šï¼Œä¸Žã€Œå®¡æ ¸ã€æˆå¯¹å‡ºçް
              ifShow: canAudit(row),
              onClick: handleAudit.bind(null, row, false),
            },
            {
              label: '取消',
src/views/wls/stocktaking/task/modules/audit-modal.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,99 @@
<script lang="ts" setup>
import type { VbenFormSchema } from '#/adapter/form';
import type { MesWmStockTakingTaskApi } from '#/api/mes/wm/stocktaking/task';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { auditStockTakingResult } from '#/api/mes/wm/stocktaking/task';
const emit = defineEmits(['success']);
const pass = ref(true); // true=审核通过 / false=审核不通过
const rowData = ref<MesWmStockTakingTaskApi.StockTakingTask>();
const getTitle = computed(() => (pass.value ? '审核通过' : '审核不通过'));
const [Form, formApi] = useVbenForm({
  commonConfig: {
    componentProps: {
      class: 'w-full',
    },
    formItemClass: 'col-span-1',
    labelWidth: 90,
  },
  wrapperClass: 'grid-cols-1',
  layout: 'horizontal',
  schema: [],
  showDefaultActions: false,
});
/** å®¡æ ¸è¡¨å•字段:不通过时必须填写原因 */
function useAuditSchema(): VbenFormSchema[] {
  return [
    {
      fieldName: 'reviewRemark',
      label: '审核意见',
      component: 'Textarea',
      formItemClass: 'col-span-1',
      componentProps: {
        rows: 3,
        placeholder: pass.value
          ? '请输入审核意见(可选)'
          : '请输入不通过原因(必填)',
      },
      rules: pass.value ? '' : 'required',
    },
  ];
}
const [Modal, modalApi] = useVbenModal({
  async onConfirm() {
    const { valid } = await formApi.validate();
    if (!valid) {
      return;
    }
    modalApi.lock();
    const values = await formApi.getValues();
    try {
      await auditStockTakingResult(
        rowData.value!.id!,
        pass.value,
        values.reviewRemark,
      );
      await modalApi.close();
      emit('success');
      message.success(pass.value ? '审核通过' : '已退回,可修改后重新提交');
    } finally {
      modalApi.unlock();
    }
  },
  async onOpenChange(isOpen: boolean) {
    if (!isOpen) {
      rowData.value = undefined;
      return;
    }
    const data = modalApi.getData<{
      pass: boolean;
      row: MesWmStockTakingTaskApi.StockTakingTask;
    }>();
    rowData.value = data.row;
    pass.value = data.pass;
    formApi.setState({ schema: useAuditSchema() });
    formApi.resetForm();
  },
});
</script>
<template>
  <Modal :title="getTitle" class="w-2/5">
    <div v-if="rowData" class="mx-4 mb-4 text-sm text-gray-600">
      <div>任务编码:{{ rowData.code }}</div>
      <div>任务名称:{{ rowData.name }}</div>
    </div>
    <Form class="mx-4" />
  </Modal>
</template>