feat: 【BPM 工作流】完善操作按钮、流程签名组件
							parent
							
								
									66ac3de5c1
								
							
						
					
					
						commit
						01f929e10f
					
				| 
						 | 
					@ -53,7 +53,8 @@
 | 
				
			||||||
    "pinia": "catalog:",
 | 
					    "pinia": "catalog:",
 | 
				
			||||||
    "vue": "catalog:",
 | 
					    "vue": "catalog:",
 | 
				
			||||||
    "vue-dompurify-html": "catalog:",
 | 
					    "vue-dompurify-html": "catalog:",
 | 
				
			||||||
    "vue-router": "catalog:"
 | 
					    "vue-router": "catalog:",
 | 
				
			||||||
 | 
					    "vue3-signature": "catalog:"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
    "@types/crypto-js": "catalog:"
 | 
					    "@types/crypto-js": "catalog:"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -37,11 +37,12 @@ export async function getProcessDefinitionPage(params: PageParam) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** 查询流程定义列表 */
 | 
					/** 查询流程定义列表 */
 | 
				
			||||||
export async function getProcessDefinitionList(params: any) {
 | 
					export async function getProcessDefinitionList(params: any) {
 | 
				
			||||||
  return requestClient.get<
 | 
					  return requestClient.get<BpmProcessDefinitionApi.ProcessDefinitionVO[]>(
 | 
				
			||||||
    PageResult<BpmProcessDefinitionApi.ProcessDefinitionVO>
 | 
					    '/bpm/process-definition/list',
 | 
				
			||||||
  >('/bpm/process-definition/list', {
 | 
					    {
 | 
				
			||||||
      params,
 | 
					      params,
 | 
				
			||||||
  });
 | 
					    },
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** 查询流程定义列表(简单列表) */
 | 
					/** 查询流程定义列表(简单列表) */
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,6 +14,7 @@ export namespace BpmModelApi {
 | 
				
			||||||
  /** 流程定义 VO */
 | 
					  /** 流程定义 VO */
 | 
				
			||||||
  export interface ProcessDefinitionVO {
 | 
					  export interface ProcessDefinitionVO {
 | 
				
			||||||
    id: string;
 | 
					    id: string;
 | 
				
			||||||
 | 
					    key?: string;
 | 
				
			||||||
    version: number;
 | 
					    version: number;
 | 
				
			||||||
    deploymentTime: number;
 | 
					    deploymentTime: number;
 | 
				
			||||||
    suspensionState: number;
 | 
					    suspensionState: number;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,7 @@
 | 
				
			||||||
import type { PageParam, PageResult } from '@vben/request';
 | 
					import type { PageParam, PageResult } from '@vben/request';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import type { BpmTaskApi } from '../task';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import type { BpmModelApi } from '#/api/bpm/model';
 | 
					import type { BpmModelApi } from '#/api/bpm/model';
 | 
				
			||||||
import type { BpmCandidateStrategyEnum, BpmNodeTypeEnum } from '#/utils';
 | 
					import type { BpmCandidateStrategyEnum, BpmNodeTypeEnum } from '#/utils';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -67,25 +69,26 @@ export namespace BpmProcessInstanceApi {
 | 
				
			||||||
    processDefinition: BpmModelApi.ProcessDefinitionVO;
 | 
					    processDefinition: BpmModelApi.ProcessDefinitionVO;
 | 
				
			||||||
    processInstance: BpmProcessInstanceApi.ProcessInstanceVO;
 | 
					    processInstance: BpmProcessInstanceApi.ProcessInstanceVO;
 | 
				
			||||||
    status: number;
 | 
					    status: number;
 | 
				
			||||||
 | 
					    todoTask: BpmTaskApi.TaskVO;
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // 抄送流程实例 VO
 | 
					  // 抄送流程实例 VO
 | 
				
			||||||
  export type CopyVO = {
 | 
					  export type CopyVO = {
 | 
				
			||||||
 | 
					    activityId: string;
 | 
				
			||||||
 | 
					    activityName: string;
 | 
				
			||||||
 | 
					    createTime: number;
 | 
				
			||||||
 | 
					    createUser: User;
 | 
				
			||||||
    id: number;
 | 
					    id: number;
 | 
				
			||||||
    startUser: User;
 | 
					 | 
				
			||||||
    processInstanceId: string;
 | 
					    processInstanceId: string;
 | 
				
			||||||
    processInstanceName: string;
 | 
					    processInstanceName: string;
 | 
				
			||||||
    processInstanceStartTime: number;
 | 
					    processInstanceStartTime: number;
 | 
				
			||||||
    activityId: string;
 | 
					 | 
				
			||||||
    activityName: string;
 | 
					 | 
				
			||||||
    taskId: string;
 | 
					 | 
				
			||||||
    reason: string;
 | 
					    reason: string;
 | 
				
			||||||
    createUser: User;
 | 
					    startUser: User;
 | 
				
			||||||
    createTime: number;
 | 
					 | 
				
			||||||
    summary: {
 | 
					    summary: {
 | 
				
			||||||
      key: string;
 | 
					      key: string;
 | 
				
			||||||
      value: string;
 | 
					      value: string;
 | 
				
			||||||
    }[];
 | 
					    }[];
 | 
				
			||||||
 | 
					    taskId: string;
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -173,7 +176,7 @@ export async function getApprovalDetail(params: any) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** 获取下一个执行的流程节点 */
 | 
					/** 获取下一个执行的流程节点 */
 | 
				
			||||||
export async function getNextApprovalNodes(params: any) {
 | 
					export async function getNextApprovalNodes(params: any) {
 | 
				
			||||||
  return requestClient.get<BpmProcessInstanceApi.ProcessInstanceVO>(
 | 
					  return requestClient.get<BpmProcessInstanceApi.ApprovalNodeInfo[]>(
 | 
				
			||||||
    `/bpm/process-instance/get-next-approval-nodes`,
 | 
					    `/bpm/process-instance/get-next-approval-nodes`,
 | 
				
			||||||
    { params },
 | 
					    { params },
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -89,8 +89,8 @@ export const getTaskListByProcessInstanceId = async (id: string) => {
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** 获取所有可退回的节点 */
 | 
					/** 获取所有可退回的节点 */
 | 
				
			||||||
export const getTaskListByReturn = async (data: any) => {
 | 
					export const getTaskListByReturn = async (id: string) => {
 | 
				
			||||||
  return await requestClient.get('/bpm/task/list-by-return', data);
 | 
					  return await requestClient.get(`/bpm/task/list-by-return?id=${id}`);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** 退回 */
 | 
					/** 退回 */
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -441,30 +441,6 @@ export const ErpBizType = {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ========== BPM 模块 ==========
 | 
					// ========== BPM 模块 ==========
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const BpmModelType = {
 | 
					 | 
				
			||||||
  BPMN: 10, // BPMN 设计器
 | 
					 | 
				
			||||||
  SIMPLE: 20, // 简易设计器
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const BpmModelFormType = {
 | 
					 | 
				
			||||||
  NORMAL: 10, // 流程表单
 | 
					 | 
				
			||||||
  CUSTOM: 20, // 业务表单
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const BpmProcessInstanceStatus = {
 | 
					 | 
				
			||||||
  NOT_START: -1, // 未开始
 | 
					 | 
				
			||||||
  RUNNING: 1, // 审批中
 | 
					 | 
				
			||||||
  APPROVE: 2, // 审批通过
 | 
					 | 
				
			||||||
  REJECT: 3, // 审批不通过
 | 
					 | 
				
			||||||
  CANCEL: 4, // 已取消
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const BpmAutoApproveType = {
 | 
					 | 
				
			||||||
  NONE: 0, // 不自动通过
 | 
					 | 
				
			||||||
  APPROVE_ALL: 1, // 仅审批一次,后续重复的审批节点均自动通过
 | 
					 | 
				
			||||||
  APPROVE_SEQUENT: 2, // 仅针对连续审批的节点自动通过
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 候选人策略枚举 ( 用于审批节点。抄送节点 )
 | 
					// 候选人策略枚举 ( 用于审批节点。抄送节点 )
 | 
				
			||||||
export enum BpmCandidateStrategyEnum {
 | 
					export enum BpmCandidateStrategyEnum {
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
| 
						 | 
					@ -594,6 +570,40 @@ export enum BpmNodeTypeEnum {
 | 
				
			||||||
  USER_TASK_NODE = 11,
 | 
					  USER_TASK_NODE = 11,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 *  流程任务操作按钮
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export enum BpmTaskOperationButtonTypeEnum {
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * 加签
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  ADD_SIGN = 5,
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * 通过
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  APPROVE = 1,
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * 抄送
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  COPY = 7,
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * 委派
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  DELEGATE = 4,
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * 拒绝
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  REJECT = 2,
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * 退回
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  RETURN = 6,
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * 转办
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  TRANSFER = 3,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 任务状态枚举
 | 
					 * 任务状态枚举
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
| 
						 | 
					@ -667,3 +677,51 @@ export enum BpmFieldPermissionType {
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  WRITE = '2',
 | 
					  WRITE = '2',
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 流程模型类型
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const BpmModelType = {
 | 
				
			||||||
 | 
					  BPMN: 10, // BPMN 设计器
 | 
				
			||||||
 | 
					  SIMPLE: 20, // 简易设计器
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 流程模型表单类型
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const BpmModelFormType = {
 | 
				
			||||||
 | 
					  NORMAL: 10, // 流程表单
 | 
				
			||||||
 | 
					  CUSTOM: 20, // 业务表单
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 流程实例状态
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const BpmProcessInstanceStatus = {
 | 
				
			||||||
 | 
					  NOT_START: -1, // 未开始
 | 
				
			||||||
 | 
					  RUNNING: 1, // 审批中
 | 
				
			||||||
 | 
					  APPROVE: 2, // 审批通过
 | 
				
			||||||
 | 
					  REJECT: 3, // 审批不通过
 | 
				
			||||||
 | 
					  CANCEL: 4, // 已取消
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 自动审批类型
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const BpmAutoApproveType = {
 | 
				
			||||||
 | 
					  NONE: 0, // 不自动通过
 | 
				
			||||||
 | 
					  APPROVE_ALL: 1, // 仅审批一次,后续重复的审批节点均自动通过
 | 
				
			||||||
 | 
					  APPROVE_SEQUENT: 2, // 仅针对连续审批的节点自动通过
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 审批操作按钮名称
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const OPERATION_BUTTON_NAME = new Map<number, string>();
 | 
				
			||||||
 | 
					OPERATION_BUTTON_NAME.set(BpmTaskOperationButtonTypeEnum.APPROVE, '通过');
 | 
				
			||||||
 | 
					OPERATION_BUTTON_NAME.set(BpmTaskOperationButtonTypeEnum.REJECT, '拒绝');
 | 
				
			||||||
 | 
					OPERATION_BUTTON_NAME.set(BpmTaskOperationButtonTypeEnum.TRANSFER, '转办');
 | 
				
			||||||
 | 
					OPERATION_BUTTON_NAME.set(BpmTaskOperationButtonTypeEnum.DELEGATE, '委派');
 | 
				
			||||||
 | 
					OPERATION_BUTTON_NAME.set(BpmTaskOperationButtonTypeEnum.ADD_SIGN, '加签');
 | 
				
			||||||
 | 
					OPERATION_BUTTON_NAME.set(BpmTaskOperationButtonTypeEnum.RETURN, '退回');
 | 
				
			||||||
 | 
					OPERATION_BUTTON_NAME.set(BpmTaskOperationButtonTypeEnum.COPY, '抄送');
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,214 @@
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 下载工具模块
 | 
				
			||||||
 | 
					 * 提供多种文件格式的下载功能
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 图片下载配置接口
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					interface ImageDownloadOptions {
 | 
				
			||||||
 | 
					  /** 图片 URL */
 | 
				
			||||||
 | 
					  url: string;
 | 
				
			||||||
 | 
					  /** 指定画布宽度 */
 | 
				
			||||||
 | 
					  canvasWidth?: number;
 | 
				
			||||||
 | 
					  /** 指定画布高度 */
 | 
				
			||||||
 | 
					  canvasHeight?: number;
 | 
				
			||||||
 | 
					  /** 将图片绘制在画布上时带上图片的宽高值,默认为 true */
 | 
				
			||||||
 | 
					  drawWithImageSize?: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 基础文件下载函数
 | 
				
			||||||
 | 
					 * @param data - 文件数据 Blob
 | 
				
			||||||
 | 
					 * @param fileName - 文件名
 | 
				
			||||||
 | 
					 * @param mimeType - MIME 类型
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const download0 = (data: Blob, fileName: string, mimeType: string) => {
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    // 创建 blob
 | 
				
			||||||
 | 
					    const blob = new Blob([data], { type: mimeType });
 | 
				
			||||||
 | 
					    // 创建 href 超链接,点击进行下载
 | 
				
			||||||
 | 
					    window.URL = window.URL || window.webkitURL;
 | 
				
			||||||
 | 
					    const href = URL.createObjectURL(blob);
 | 
				
			||||||
 | 
					    const downA = document.createElement('a');
 | 
				
			||||||
 | 
					    downA.href = href;
 | 
				
			||||||
 | 
					    downA.download = fileName;
 | 
				
			||||||
 | 
					    downA.click();
 | 
				
			||||||
 | 
					    // 销毁超链接
 | 
				
			||||||
 | 
					    window.URL.revokeObjectURL(href);
 | 
				
			||||||
 | 
					  } catch (error) {
 | 
				
			||||||
 | 
					    console.error('文件下载失败:', error);
 | 
				
			||||||
 | 
					    throw new Error(
 | 
				
			||||||
 | 
					      `文件下载失败: ${error instanceof Error ? error.message : '未知错误'}`,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 触发文件下载的通用方法
 | 
				
			||||||
 | 
					 * @param url - 下载链接
 | 
				
			||||||
 | 
					 * @param fileName - 文件名
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					const triggerDownload = (url: string, fileName: string) => {
 | 
				
			||||||
 | 
					  const a = document.createElement('a');
 | 
				
			||||||
 | 
					  a.href = url;
 | 
				
			||||||
 | 
					  a.download = fileName;
 | 
				
			||||||
 | 
					  a.click();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const download = {
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * 下载 Excel 文件
 | 
				
			||||||
 | 
					   * @param data - 文件数据 Blob
 | 
				
			||||||
 | 
					   * @param fileName - 文件名
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  excel: (data: Blob, fileName: string) => {
 | 
				
			||||||
 | 
					    download0(data, fileName, 'application/vnd.ms-excel');
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * 下载 Word 文件
 | 
				
			||||||
 | 
					   * @param data - 文件数据 Blob
 | 
				
			||||||
 | 
					   * @param fileName - 文件名
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  word: (data: Blob, fileName: string) => {
 | 
				
			||||||
 | 
					    download0(data, fileName, 'application/msword');
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * 下载 Zip 文件
 | 
				
			||||||
 | 
					   * @param data - 文件数据 Blob
 | 
				
			||||||
 | 
					   * @param fileName - 文件名
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  zip: (data: Blob, fileName: string) => {
 | 
				
			||||||
 | 
					    download0(data, fileName, 'application/zip');
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * 下载 HTML 文件
 | 
				
			||||||
 | 
					   * @param data - 文件数据 Blob
 | 
				
			||||||
 | 
					   * @param fileName - 文件名
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  html: (data: Blob, fileName: string) => {
 | 
				
			||||||
 | 
					    download0(data, fileName, 'text/html');
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * 下载 Markdown 文件
 | 
				
			||||||
 | 
					   * @param data - 文件数据 Blob
 | 
				
			||||||
 | 
					   * @param fileName - 文件名
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  markdown: (data: Blob, fileName: string) => {
 | 
				
			||||||
 | 
					    download0(data, fileName, 'text/markdown');
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * 下载 JSON 文件
 | 
				
			||||||
 | 
					   * @param data - 文件数据 Blob
 | 
				
			||||||
 | 
					   * @param fileName - 文件名
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  json: (data: Blob, fileName: string) => {
 | 
				
			||||||
 | 
					    download0(data, fileName, 'application/json');
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * 下载图片(允许跨域)
 | 
				
			||||||
 | 
					   * @param options - 图片下载配置
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  image: (options: ImageDownloadOptions) => {
 | 
				
			||||||
 | 
					    const {
 | 
				
			||||||
 | 
					      url,
 | 
				
			||||||
 | 
					      canvasWidth,
 | 
				
			||||||
 | 
					      canvasHeight,
 | 
				
			||||||
 | 
					      drawWithImageSize = true,
 | 
				
			||||||
 | 
					    } = options;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const image = new Image();
 | 
				
			||||||
 | 
					    // image.setAttribute('crossOrigin', 'anonymous')
 | 
				
			||||||
 | 
					    image.src = url;
 | 
				
			||||||
 | 
					    image.addEventListener('load', () => {
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        const canvas = document.createElement('canvas');
 | 
				
			||||||
 | 
					        canvas.width = canvasWidth || image.width;
 | 
				
			||||||
 | 
					        canvas.height = canvasHeight || image.height;
 | 
				
			||||||
 | 
					        const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
 | 
				
			||||||
 | 
					        ctx?.clearRect(0, 0, canvas.width, canvas.height);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (drawWithImageSize) {
 | 
				
			||||||
 | 
					          ctx.drawImage(image, 0, 0, image.width, image.height);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          ctx.drawImage(image, 0, 0);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const dataUrl = canvas.toDataURL('image/png');
 | 
				
			||||||
 | 
					        triggerDownload(dataUrl, 'image.png');
 | 
				
			||||||
 | 
					      } catch (error) {
 | 
				
			||||||
 | 
					        console.error('图片下载失败:', error);
 | 
				
			||||||
 | 
					        throw new Error(
 | 
				
			||||||
 | 
					          `图片下载失败: ${error instanceof Error ? error.message : '未知错误'}`,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    image.addEventListener('error', () => {
 | 
				
			||||||
 | 
					      throw new Error('图片加载失败');
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * 将 Base64 字符串转换为文件对象
 | 
				
			||||||
 | 
					   * @param base64 - Base64 字符串
 | 
				
			||||||
 | 
					   * @param fileName - 文件名
 | 
				
			||||||
 | 
					   * @returns File 对象
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  base64ToFile: (base64: string, fileName: string): File => {
 | 
				
			||||||
 | 
					    // 输入验证
 | 
				
			||||||
 | 
					    if (!base64 || typeof base64 !== 'string') {
 | 
				
			||||||
 | 
					      throw new Error('base64 参数必须是非空字符串');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 将 base64 按照逗号进行分割,将前缀与后续内容分隔开
 | 
				
			||||||
 | 
					    const data = base64.split(',');
 | 
				
			||||||
 | 
					    if (data.length !== 2 || !data[0] || !data[1]) {
 | 
				
			||||||
 | 
					      throw new Error('无效的 base64 格式');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 利用正则表达式从前缀中获取类型信息(image/png、image/jpeg、image/webp等)
 | 
				
			||||||
 | 
					    const typeMatch = data[0].match(/:(.*?);/);
 | 
				
			||||||
 | 
					    if (!typeMatch || !typeMatch[1]) {
 | 
				
			||||||
 | 
					      throw new Error('无法解析 base64 类型信息');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const type = typeMatch[1];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 从类型信息中获取具体的文件格式后缀(png、jpeg、webp)
 | 
				
			||||||
 | 
					    const typeParts = type.split('/');
 | 
				
			||||||
 | 
					    if (typeParts.length !== 2 || !typeParts[1]) {
 | 
				
			||||||
 | 
					      throw new Error('无效的 MIME 类型格式');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const suffix = typeParts[1];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      // 使用 atob() 对 base64 数据进行解码,结果是一个文件数据流以字符串的格式输出
 | 
				
			||||||
 | 
					      const bstr = window.atob(data[1]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // 获取解码结果字符串的长度
 | 
				
			||||||
 | 
					      const n = bstr.length;
 | 
				
			||||||
 | 
					      // 根据解码结果字符串的长度创建一个等长的整型数字数组
 | 
				
			||||||
 | 
					      const u8arr = new Uint8Array(n);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // 优化的 Uint8Array 填充逻辑
 | 
				
			||||||
 | 
					      for (let i = 0; i < n; i++) {
 | 
				
			||||||
 | 
					        // 使用 charCodeAt() 获取字符对应的字节值(Base64 解码后的字符串是字节级别的)
 | 
				
			||||||
 | 
					        // eslint-disable-next-line unicorn/prefer-code-point
 | 
				
			||||||
 | 
					        u8arr[i] = bstr.charCodeAt(i);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // 返回 File 文件对象
 | 
				
			||||||
 | 
					      return new File([u8arr], `${fileName}.${suffix}`, { type });
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      throw new Error(
 | 
				
			||||||
 | 
					        `Base64 解码失败: ${error instanceof Error ? error.message : '未知错误'}`,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,6 @@
 | 
				
			||||||
export * from './constants';
 | 
					export * from './constants';
 | 
				
			||||||
export * from './dict';
 | 
					export * from './dict';
 | 
				
			||||||
 | 
					export * from './download';
 | 
				
			||||||
export * from './formatTime';
 | 
					export * from './formatTime';
 | 
				
			||||||
export * from './formCreate';
 | 
					export * from './formCreate';
 | 
				
			||||||
export * from './rangePickerProps';
 | 
					export * from './rangePickerProps';
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,5 @@
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import type { BpmCategoryApi } from '#/api/bpm/category';
 | 
				
			||||||
import type { BpmProcessDefinitionApi } from '#/api/bpm/definition';
 | 
					import type { BpmProcessDefinitionApi } from '#/api/bpm/definition';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { computed, nextTick, onMounted, ref } from 'vue';
 | 
					import { computed, nextTick, onMounted, ref } from 'vue';
 | 
				
			||||||
| 
						 | 
					@ -33,7 +34,9 @@ const processInstanceId: any = route.query.processInstanceId; // 流程实例编
 | 
				
			||||||
const loading = ref(true); // 加载中
 | 
					const loading = ref(true); // 加载中
 | 
				
			||||||
const categoryList: any = ref([]); // 分类的列表
 | 
					const categoryList: any = ref([]); // 分类的列表
 | 
				
			||||||
const activeCategory = ref(''); // 当前选中的分类
 | 
					const activeCategory = ref(''); // 当前选中的分类
 | 
				
			||||||
const processDefinitionList = ref([]); // 流程定义的列表
 | 
					const processDefinitionList = ref<
 | 
				
			||||||
 | 
					  BpmProcessDefinitionApi.ProcessDefinitionVO[]
 | 
				
			||||||
 | 
					>([]); // 流程定义的列表
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 实现 groupBy 功能
 | 
					// 实现 groupBy 功能
 | 
				
			||||||
const groupBy = (array: any[], key: string) => {
 | 
					const groupBy = (array: any[], key: string) => {
 | 
				
			||||||
| 
						 | 
					@ -107,8 +110,12 @@ const handleGetProcessDefinitionList = async () => {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** 用于存储搜索过滤后的流程定义 */
 | 
				
			||||||
 | 
					const filteredProcessDefinitionList = ref<
 | 
				
			||||||
 | 
					  BpmProcessDefinitionApi.ProcessDefinitionVO[]
 | 
				
			||||||
 | 
					>([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** 搜索流程 */
 | 
					/** 搜索流程 */
 | 
				
			||||||
const filteredProcessDefinitionList = ref([]); // 用于存储搜索过滤后的流程定义
 | 
					 | 
				
			||||||
const handleQuery = () => {
 | 
					const handleQuery = () => {
 | 
				
			||||||
  if (searchName.value.trim()) {
 | 
					  if (searchName.value.trim()) {
 | 
				
			||||||
    // 如果有搜索关键字,进行过滤
 | 
					    // 如果有搜索关键字,进行过滤
 | 
				
			||||||
| 
						 | 
					@ -150,10 +157,15 @@ const processDefinitionGroup: any = computed(() => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const grouped = groupBy(filteredProcessDefinitionList.value, 'category');
 | 
					  const grouped = groupBy(filteredProcessDefinitionList.value, 'category');
 | 
				
			||||||
  // 按照 categoryList 的顺序重新组织数据
 | 
					  // 按照 categoryList 的顺序重新组织数据
 | 
				
			||||||
  const orderedGroup = {};
 | 
					  const orderedGroup: Record<
 | 
				
			||||||
  categoryList.value.forEach((category: any) => {
 | 
					    string,
 | 
				
			||||||
 | 
					    BpmProcessDefinitionApi.ProcessDefinitionVO[]
 | 
				
			||||||
 | 
					  > = {};
 | 
				
			||||||
 | 
					  categoryList.value.forEach((category: BpmCategoryApi.CategoryVO) => {
 | 
				
			||||||
    if (grouped[category.code]) {
 | 
					    if (grouped[category.code]) {
 | 
				
			||||||
      orderedGroup[category.code] = grouped[category.code];
 | 
					      orderedGroup[category.code] = grouped[
 | 
				
			||||||
 | 
					        category.code
 | 
				
			||||||
 | 
					      ] as BpmProcessDefinitionApi.ProcessDefinitionVO[];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
  return orderedGroup;
 | 
					  return orderedGroup;
 | 
				
			||||||
| 
						 | 
					@ -191,7 +203,7 @@ const availableCategories = computed(() => {
 | 
				
			||||||
  const availableCategoryCodes = Object.keys(processDefinitionGroup.value);
 | 
					  const availableCategoryCodes = Object.keys(processDefinitionGroup.value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // 过滤出有流程的分类
 | 
					  // 过滤出有流程的分类
 | 
				
			||||||
  return categoryList.value.filter((category: CategoryVO) =>
 | 
					  return categoryList.value.filter((category: BpmCategoryApi.CategoryVO) =>
 | 
				
			||||||
    availableCategoryCodes.includes(category.code),
 | 
					    availableCategoryCodes.includes(category.code),
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -229,11 +241,7 @@ onMounted(() => {
 | 
				
			||||||
              allow-clear
 | 
					              allow-clear
 | 
				
			||||||
              @input="handleQuery"
 | 
					              @input="handleQuery"
 | 
				
			||||||
              @clear="handleQuery"
 | 
					              @clear="handleQuery"
 | 
				
			||||||
            >
 | 
					            />
 | 
				
			||||||
              <template #prefix>
 | 
					 | 
				
			||||||
                <IconifyIcon icon="mdi:search-web" />
 | 
					 | 
				
			||||||
              </template>
 | 
					 | 
				
			||||||
            </InputSearch>
 | 
					 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </template>
 | 
					        </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -289,15 +297,6 @@ onMounted(() => {
 | 
				
			||||||
                        </Tooltip>
 | 
					                        </Tooltip>
 | 
				
			||||||
                      </span>
 | 
					                      </span>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
 | 
					 | 
				
			||||||
                    <!-- TODO: 发起流程按钮 -->
 | 
					 | 
				
			||||||
                    <!-- <template #actions>
 | 
					 | 
				
			||||||
                      <div class="flex justify-end px-4">
 | 
					 | 
				
			||||||
                        <Button type="link" @click="handleSelect(definition)">
 | 
					 | 
				
			||||||
                          发起流程
 | 
					 | 
				
			||||||
                        </Button>
 | 
					 | 
				
			||||||
                      </div>
 | 
					 | 
				
			||||||
                    </template> -->
 | 
					 | 
				
			||||||
                  </Card>
 | 
					                  </Card>
 | 
				
			||||||
                </Col>
 | 
					                </Col>
 | 
				
			||||||
              </Row>
 | 
					              </Row>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,16 +7,7 @@ import { nextTick, onMounted, ref, shallowRef, watch } from 'vue';
 | 
				
			||||||
import { Page } from '@vben/common-ui';
 | 
					import { Page } from '@vben/common-ui';
 | 
				
			||||||
import { formatDateTime } from '@vben/utils';
 | 
					import { formatDateTime } from '@vben/utils';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import {
 | 
					import { Avatar, Card, Col, message, Row, TabPane, Tabs } from 'ant-design-vue';
 | 
				
			||||||
  Avatar,
 | 
					 | 
				
			||||||
  Button,
 | 
					 | 
				
			||||||
  Card,
 | 
					 | 
				
			||||||
  Col,
 | 
					 | 
				
			||||||
  message,
 | 
					 | 
				
			||||||
  Row,
 | 
					 | 
				
			||||||
  TabPane,
 | 
					 | 
				
			||||||
  Tabs,
 | 
					 | 
				
			||||||
} from 'ant-design-vue';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  getApprovalDetail as getApprovalDetailApi,
 | 
					  getApprovalDetail as getApprovalDetailApi,
 | 
				
			||||||
| 
						 | 
					@ -39,6 +30,9 @@ import {
 | 
				
			||||||
  SvgBpmRunningIcon,
 | 
					  SvgBpmRunningIcon,
 | 
				
			||||||
} from '#/views/bpm/processInstance/detail/modules/icons';
 | 
					} from '#/views/bpm/processInstance/detail/modules/icons';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import ProcessInstanceBpmnViewer from './modules/bpm-viewer.vue';
 | 
				
			||||||
 | 
					import ProcessInstanceOperationButton from './modules/operation-button.vue';
 | 
				
			||||||
 | 
					import ProcessInstanceSimpleViewer from './modules/simple-bpm-viewer.vue';
 | 
				
			||||||
import BpmProcessInstanceTaskList from './modules/task-list.vue';
 | 
					import BpmProcessInstanceTaskList from './modules/task-list.vue';
 | 
				
			||||||
import ProcessInstanceTimeline from './modules/time-line.vue';
 | 
					import ProcessInstanceTimeline from './modules/time-line.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -72,7 +66,7 @@ const processInstanceLoading = ref(false); // 流程实例的加载中
 | 
				
			||||||
const processInstance = ref<BpmProcessInstanceApi.ProcessInstanceVO>(); // 流程实例
 | 
					const processInstance = ref<BpmProcessInstanceApi.ProcessInstanceVO>(); // 流程实例
 | 
				
			||||||
const processDefinition = ref<any>({}); // 流程定义
 | 
					const processDefinition = ref<any>({}); // 流程定义
 | 
				
			||||||
const processModelView = ref<any>({}); // 流程模型视图
 | 
					const processModelView = ref<any>({}); // 流程模型视图
 | 
				
			||||||
// const operationButtonRef = ref(); // 操作按钮组件 ref
 | 
					const operationButtonRef = ref(); // 操作按钮组件 ref
 | 
				
			||||||
const auditIconsMap: {
 | 
					const auditIconsMap: {
 | 
				
			||||||
  [key: string]:
 | 
					  [key: string]:
 | 
				
			||||||
    | typeof SvgBpmApproveIcon
 | 
					    | typeof SvgBpmApproveIcon
 | 
				
			||||||
| 
						 | 
					@ -162,6 +156,7 @@ async function getApprovalDetail() {
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      // 注意:data.processDefinition.formCustomViewPath 是组件的全路径,例如说:/crm/contract/detail/index.vue
 | 
					      // 注意:data.processDefinition.formCustomViewPath 是组件的全路径,例如说:/crm/contract/detail/index.vue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      BusinessFormComponent.value = registerComponent(
 | 
					      BusinessFormComponent.value = registerComponent(
 | 
				
			||||||
        data?.processDefinition?.formCustomViewPath || '',
 | 
					        data?.processDefinition?.formCustomViewPath || '',
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
| 
						 | 
					@ -169,6 +164,9 @@ async function getApprovalDetail() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 获取审批节点,显示 Timeline 的数据
 | 
					    // 获取审批节点,显示 Timeline 的数据
 | 
				
			||||||
    activityNodes.value = data.activityNodes;
 | 
					    activityNodes.value = data.activityNodes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 获取待办任务显示操作按钮
 | 
				
			||||||
 | 
					    operationButtonRef.value?.loadTodoTask(data.todoTask);
 | 
				
			||||||
  } catch {
 | 
					  } catch {
 | 
				
			||||||
    message.error('获取审批详情失败!');
 | 
					    message.error('获取审批详情失败!');
 | 
				
			||||||
  } finally {
 | 
					  } finally {
 | 
				
			||||||
| 
						 | 
					@ -249,9 +247,7 @@ onMounted(async () => {
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <Page auto-content-height>
 | 
					  <Page auto-content-height>
 | 
				
			||||||
    <Card
 | 
					    <Card
 | 
				
			||||||
      class="h-full"
 | 
					 | 
				
			||||||
      :body-style="{
 | 
					      :body-style="{
 | 
				
			||||||
        height: 'calc(100% - 140px)',
 | 
					 | 
				
			||||||
        overflowY: 'auto',
 | 
					        overflowY: 'auto',
 | 
				
			||||||
        paddingTop: '12px',
 | 
					        paddingTop: '12px',
 | 
				
			||||||
      }"
 | 
					      }"
 | 
				
			||||||
| 
						 | 
					@ -306,18 +302,25 @@ onMounted(async () => {
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <!-- 流程操作 -->
 | 
					        <!-- 流程操作 -->
 | 
				
			||||||
        <div class="flex-1">
 | 
					        <div class="process-tabs-container flex flex-1 flex-col">
 | 
				
			||||||
          <Tabs v-model:active-key="activeTab" class="mt-0">
 | 
					          <Tabs v-model:active-key="activeTab" class="mt-0 h-full">
 | 
				
			||||||
            <TabPane tab="审批详情" key="form">
 | 
					            <TabPane tab="审批详情" key="form" class="tab-pane-content">
 | 
				
			||||||
              <Row :gutter="[48, 24]">
 | 
					              <Row :gutter="[48, 24]" class="h-full">
 | 
				
			||||||
                <Col :xs="24" :sm="24" :md="18" :lg="18" :xl="16">
 | 
					                <Col
 | 
				
			||||||
 | 
					                  :xs="24"
 | 
				
			||||||
 | 
					                  :sm="24"
 | 
				
			||||||
 | 
					                  :md="18"
 | 
				
			||||||
 | 
					                  :lg="18"
 | 
				
			||||||
 | 
					                  :xl="16"
 | 
				
			||||||
 | 
					                  class="h-full"
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
                  <!-- 流程表单 -->
 | 
					                  <!-- 流程表单 -->
 | 
				
			||||||
                  <div
 | 
					                  <div
 | 
				
			||||||
                    v-if="
 | 
					                    v-if="
 | 
				
			||||||
                      processDefinition?.formType === BpmModelFormType.NORMAL
 | 
					                      processDefinition?.formType === BpmModelFormType.NORMAL
 | 
				
			||||||
                    "
 | 
					                    "
 | 
				
			||||||
 | 
					                    class="h-full"
 | 
				
			||||||
                  >
 | 
					                  >
 | 
				
			||||||
                    <!-- v-model="detailForm.value" -->
 | 
					 | 
				
			||||||
                    <form-create
 | 
					                    <form-create
 | 
				
			||||||
                      v-model="detailForm.value"
 | 
					                      v-model="detailForm.value"
 | 
				
			||||||
                      v-model:api="fApi"
 | 
					                      v-model:api="fApi"
 | 
				
			||||||
| 
						 | 
					@ -330,23 +333,41 @@ onMounted(async () => {
 | 
				
			||||||
                    v-if="
 | 
					                    v-if="
 | 
				
			||||||
                      processDefinition?.formType === BpmModelFormType.CUSTOM
 | 
					                      processDefinition?.formType === BpmModelFormType.CUSTOM
 | 
				
			||||||
                    "
 | 
					                    "
 | 
				
			||||||
 | 
					                    class="h-full"
 | 
				
			||||||
                  >
 | 
					                  >
 | 
				
			||||||
                    <BusinessFormComponent :id="processInstance?.businessKey" />
 | 
					                    <BusinessFormComponent :id="processInstance?.businessKey" />
 | 
				
			||||||
                  </div>
 | 
					                  </div>
 | 
				
			||||||
                </Col>
 | 
					                </Col>
 | 
				
			||||||
                <Col :xs="24" :sm="24" :md="6" :lg="6" :xl="8">
 | 
					                <Col :xs="24" :sm="24" :md="6" :lg="6" :xl="8" class="h-full">
 | 
				
			||||||
                  <div class="mt-2">
 | 
					                  <div class="mt-4 h-full">
 | 
				
			||||||
                    <ProcessInstanceTimeline :activity-nodes="activityNodes" />
 | 
					                    <ProcessInstanceTimeline :activity-nodes="activityNodes" />
 | 
				
			||||||
                  </div>
 | 
					                  </div>
 | 
				
			||||||
                </Col>
 | 
					                </Col>
 | 
				
			||||||
              </Row>
 | 
					              </Row>
 | 
				
			||||||
            </TabPane>
 | 
					            </TabPane>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <TabPane tab="流程图" key="diagram">
 | 
					            <TabPane tab="流程图" key="diagram" class="tab-pane-content">
 | 
				
			||||||
              <div>待开发</div>
 | 
					              <div class="h-full">
 | 
				
			||||||
 | 
					                <ProcessInstanceSimpleViewer
 | 
				
			||||||
 | 
					                  v-show="
 | 
				
			||||||
 | 
					                    processDefinition.modelType &&
 | 
				
			||||||
 | 
					                    processDefinition.modelType === BpmModelType.SIMPLE
 | 
				
			||||||
 | 
					                  "
 | 
				
			||||||
 | 
					                  :loading="processInstanceLoading"
 | 
				
			||||||
 | 
					                  :model-view="processModelView"
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					                <ProcessInstanceBpmnViewer
 | 
				
			||||||
 | 
					                  v-show="
 | 
				
			||||||
 | 
					                    processDefinition.modelType &&
 | 
				
			||||||
 | 
					                    processDefinition.modelType === BpmModelType.BPMN
 | 
				
			||||||
 | 
					                  "
 | 
				
			||||||
 | 
					                  :loading="processInstanceLoading"
 | 
				
			||||||
 | 
					                  :model-view="processModelView"
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
            </TabPane>
 | 
					            </TabPane>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <TabPane tab="流转记录" key="record">
 | 
					            <TabPane tab="流转记录" key="record" class="tab-pane-content">
 | 
				
			||||||
              <div class="h-full">
 | 
					              <div class="h-full">
 | 
				
			||||||
                <BpmProcessInstanceTaskList
 | 
					                <BpmProcessInstanceTaskList
 | 
				
			||||||
                  ref="taskListRef"
 | 
					                  ref="taskListRef"
 | 
				
			||||||
| 
						 | 
					@ -357,19 +378,65 @@ onMounted(async () => {
 | 
				
			||||||
            </TabPane>
 | 
					            </TabPane>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <!-- TODO 待开发 -->
 | 
					            <!-- TODO 待开发 -->
 | 
				
			||||||
            <TabPane tab="流转评论" key="comment" v-if="false">
 | 
					            <TabPane
 | 
				
			||||||
              <div>待开发</div>
 | 
					              tab="流转评论"
 | 
				
			||||||
 | 
					              key="comment"
 | 
				
			||||||
 | 
					              v-if="false"
 | 
				
			||||||
 | 
					              class="tab-pane-content"
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					              <div class="h-full">待开发</div>
 | 
				
			||||||
            </TabPane>
 | 
					            </TabPane>
 | 
				
			||||||
          </Tabs>
 | 
					          </Tabs>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      <template #actions>
 | 
					      <template #actions>
 | 
				
			||||||
        <div class="flex justify-start gap-x-2 p-4">
 | 
					        <div class="px-4">
 | 
				
			||||||
          <Button type="primary">驳回</Button>
 | 
					          <ProcessInstanceOperationButton
 | 
				
			||||||
          <Button type="primary">同意</Button>
 | 
					            ref="operationButtonRef"
 | 
				
			||||||
 | 
					            :process-instance="processInstance"
 | 
				
			||||||
 | 
					            :process-definition="processDefinition"
 | 
				
			||||||
 | 
					            :user-options="userOptions"
 | 
				
			||||||
 | 
					            :normal-form="detailForm"
 | 
				
			||||||
 | 
					            :normal-form-api="fApi"
 | 
				
			||||||
 | 
					            :writable-fields="writableFields"
 | 
				
			||||||
 | 
					            @success="getDetail"
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </template>
 | 
					      </template>
 | 
				
			||||||
    </Card>
 | 
					    </Card>
 | 
				
			||||||
  </Page>
 | 
					  </Page>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="scss" scoped>
 | 
				
			||||||
 | 
					.ant-tabs-content {
 | 
				
			||||||
 | 
					  height: 100%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.process-tabs-container {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  flex-direction: column;
 | 
				
			||||||
 | 
					  height: 100%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					:deep(.ant-tabs) {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  flex-direction: column;
 | 
				
			||||||
 | 
					  height: 100%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					:deep(.ant-tabs-content) {
 | 
				
			||||||
 | 
					  flex: 1;
 | 
				
			||||||
 | 
					  overflow-y: auto;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					:deep(.ant-tabs-tabpane) {
 | 
				
			||||||
 | 
					  height: 100%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.tab-pane-content {
 | 
				
			||||||
 | 
					  height: calc(100vh - 420px);
 | 
				
			||||||
 | 
					  padding-right: 12px;
 | 
				
			||||||
 | 
					  overflow: hidden auto;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,9 @@
 | 
				
			||||||
 | 
					<script setup lang="ts">
 | 
				
			||||||
 | 
					defineOptions({ name: 'ProcessInstanceBpmnViewer' });
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <div>
 | 
				
			||||||
 | 
					    <h1>BPMN Viewer</h1>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
					@ -0,0 +1,88 @@
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { ref } from 'vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { useVbenModal } from '@vben/common-ui';
 | 
				
			||||||
 | 
					import { IconifyIcon } from '@vben/icons';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Button, message, Space, Tooltip } from 'ant-design-vue';
 | 
				
			||||||
 | 
					import Vue3Signature from 'vue3-signature';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { uploadFile } from '#/api/infra/file';
 | 
				
			||||||
 | 
					import { download } from '#/utils';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					defineOptions({
 | 
				
			||||||
 | 
					  name: 'BpmProcessInstanceSignature',
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const emits = defineEmits(['success']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const [Modal, modalApi] = useVbenModal({
 | 
				
			||||||
 | 
					  title: '流程签名',
 | 
				
			||||||
 | 
					  onOpenChange(visible) {
 | 
				
			||||||
 | 
					    if (!visible) {
 | 
				
			||||||
 | 
					      modalApi.close();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  onConfirm: () => {
 | 
				
			||||||
 | 
					    submit();
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const signature = ref<InstanceType<typeof Vue3Signature>>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const open = async () => {
 | 
				
			||||||
 | 
					  modalApi.open();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					defineExpose({ open });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const submit = async () => {
 | 
				
			||||||
 | 
					  message.success({
 | 
				
			||||||
 | 
					    content: '签名上传中请稍等。。。',
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  const signFileUrl = await uploadFile({
 | 
				
			||||||
 | 
					    file: download.base64ToFile(
 | 
				
			||||||
 | 
					      signature?.value?.save('image/jpeg') || '',
 | 
				
			||||||
 | 
					      '签名',
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  emits('success', signFileUrl);
 | 
				
			||||||
 | 
					  modalApi.close();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <Modal class="h-[500px] w-[900px]">
 | 
				
			||||||
 | 
					    <div class="mb-2 flex justify-end">
 | 
				
			||||||
 | 
					      <Space>
 | 
				
			||||||
 | 
					        <Tooltip title="撤销上一步操作">
 | 
				
			||||||
 | 
					          <Button @click="signature?.undo()">
 | 
				
			||||||
 | 
					            <template #icon>
 | 
				
			||||||
 | 
					              <IconifyIcon icon="mi:undo" class="mb-[4px] size-[16px]" />
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					            撤销
 | 
				
			||||||
 | 
					          </Button>
 | 
				
			||||||
 | 
					        </Tooltip>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <Tooltip title="清空画布">
 | 
				
			||||||
 | 
					          <Button @click="signature?.clear()">
 | 
				
			||||||
 | 
					            <template #icon>
 | 
				
			||||||
 | 
					              <IconifyIcon
 | 
				
			||||||
 | 
					                icon="mdi:delete-outline"
 | 
				
			||||||
 | 
					                class="mb-[4px] size-[16px]"
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					            <span>清除</span>
 | 
				
			||||||
 | 
					          </Button>
 | 
				
			||||||
 | 
					        </Tooltip>
 | 
				
			||||||
 | 
					      </Space>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <Vue3Signature
 | 
				
			||||||
 | 
					      class="mx-auto border-[1px] border-solid border-gray-300"
 | 
				
			||||||
 | 
					      ref="signature"
 | 
				
			||||||
 | 
					      w="874px"
 | 
				
			||||||
 | 
					      h="324px"
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  </Modal>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,9 @@
 | 
				
			||||||
 | 
					<script setup lang="ts">
 | 
				
			||||||
 | 
					defineOptions({ name: 'ProcessInstanceSimpleViewer' });
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <div>
 | 
				
			||||||
 | 
					    <h1>Simple BPM Viewer</h1>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
| 
						 | 
					@ -43,7 +43,7 @@ const statusIconMap: Record<
 | 
				
			||||||
  // 审批未开始
 | 
					  // 审批未开始
 | 
				
			||||||
  '-1': { color: '#909398', icon: 'mdi:clock-outline' },
 | 
					  '-1': { color: '#909398', icon: 'mdi:clock-outline' },
 | 
				
			||||||
  // 待审批
 | 
					  // 待审批
 | 
				
			||||||
  '0': { color: '#00b32a', icon: 'mdi:loading' },
 | 
					  '0': { color: '#ff943e', icon: 'mdi:loading', animation: 'animate-spin' },
 | 
				
			||||||
  // 审批中
 | 
					  // 审批中
 | 
				
			||||||
  '1': { color: '#448ef7', icon: 'mdi:loading', animation: 'animate-spin' },
 | 
					  '1': { color: '#448ef7', icon: 'mdi:loading', animation: 'animate-spin' },
 | 
				
			||||||
  // 审批通过
 | 
					  // 审批通过
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -45,14 +45,14 @@ const [Grid, gridApi] = useVbenVxeGrid({
 | 
				
			||||||
    cellConfig: {
 | 
					    cellConfig: {
 | 
				
			||||||
      height: 64,
 | 
					      height: 64,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  } as VxeTableGridOptions<BpmProcessInstanceApi.ProcessInstanceVO>,
 | 
					  } as VxeTableGridOptions<BpmProcessInstanceApi.CopyVO>,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** 表格操作按钮的回调函数 */
 | 
					/** 表格操作按钮的回调函数 */
 | 
				
			||||||
function onActionClick({
 | 
					function onActionClick({
 | 
				
			||||||
  code,
 | 
					  code,
 | 
				
			||||||
  row,
 | 
					  row,
 | 
				
			||||||
}: OnActionClickParams<BpmProcessInstanceApi.ProcessInstanceVO>) {
 | 
					}: OnActionClickParams<BpmProcessInstanceApi.CopyVO>) {
 | 
				
			||||||
  switch (code) {
 | 
					  switch (code) {
 | 
				
			||||||
    case 'detail': {
 | 
					    case 'detail': {
 | 
				
			||||||
      onDetail(row);
 | 
					      onDetail(row);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -528,6 +528,9 @@ catalogs:
 | 
				
			||||||
    vue-tsc:
 | 
					    vue-tsc:
 | 
				
			||||||
      specifier: 2.2.10
 | 
					      specifier: 2.2.10
 | 
				
			||||||
      version: 2.2.10
 | 
					      version: 2.2.10
 | 
				
			||||||
 | 
					    vue3-signature:
 | 
				
			||||||
 | 
					      specifier: ^0.2.4
 | 
				
			||||||
 | 
					      version: 0.2.4
 | 
				
			||||||
    vxe-pc-ui:
 | 
					    vxe-pc-ui:
 | 
				
			||||||
      specifier: ^4.5.35
 | 
					      specifier: ^4.5.35
 | 
				
			||||||
      version: 4.6.8
 | 
					      version: 4.6.8
 | 
				
			||||||
| 
						 | 
					@ -758,6 +761,9 @@ importers:
 | 
				
			||||||
      vue-router:
 | 
					      vue-router:
 | 
				
			||||||
        specifier: 'catalog:'
 | 
					        specifier: 'catalog:'
 | 
				
			||||||
        version: 4.5.1(vue@3.5.13(typescript@5.8.3))
 | 
					        version: 4.5.1(vue@3.5.13(typescript@5.8.3))
 | 
				
			||||||
 | 
					      vue3-signature:
 | 
				
			||||||
 | 
					        specifier: 'catalog:'
 | 
				
			||||||
 | 
					        version: 0.2.4(vue@3.5.13(typescript@5.8.3))
 | 
				
			||||||
    devDependencies:
 | 
					    devDependencies:
 | 
				
			||||||
      '@types/crypto-js':
 | 
					      '@types/crypto-js':
 | 
				
			||||||
        specifier: 'catalog:'
 | 
					        specifier: 'catalog:'
 | 
				
			||||||
| 
						 | 
					@ -6220,6 +6226,9 @@ packages:
 | 
				
			||||||
    resolution: {integrity: sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==}
 | 
					    resolution: {integrity: sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==}
 | 
				
			||||||
    engines: {node: '>=18'}
 | 
					    engines: {node: '>=18'}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  default-passive-events@2.0.0:
 | 
				
			||||||
 | 
					    resolution: {integrity: sha512-eMtt76GpDVngZQ3ocgvRcNCklUMwID1PaNbCNxfpDXuiOXttSh0HzBbda1HU9SIUsDc02vb7g9+3I5tlqe/qMQ==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  define-data-property@1.1.4:
 | 
					  define-data-property@1.1.4:
 | 
				
			||||||
    resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
 | 
					    resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
 | 
				
			||||||
    engines: {node: '>= 0.4'}
 | 
					    engines: {node: '>= 0.4'}
 | 
				
			||||||
| 
						 | 
					@ -10018,6 +10027,9 @@ packages:
 | 
				
			||||||
    resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
 | 
					    resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
 | 
				
			||||||
    engines: {node: '>=14'}
 | 
					    engines: {node: '>=14'}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  signature_pad@3.0.0-beta.4:
 | 
				
			||||||
 | 
					    resolution: {integrity: sha512-cOf2NhVuTiuNqe2X/ycEmizvCDXk0DoemhsEpnkcGnA4kS5iJYTCqZ9As7tFBbsch45Q1EdX61833+6sjJ8rrw==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  simple-swizzle@0.2.2:
 | 
					  simple-swizzle@0.2.2:
 | 
				
			||||||
    resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
 | 
					    resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11163,6 +11175,11 @@ packages:
 | 
				
			||||||
    peerDependencies:
 | 
					    peerDependencies:
 | 
				
			||||||
      vue: ^3.5.13
 | 
					      vue: ^3.5.13
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  vue3-signature@0.2.4:
 | 
				
			||||||
 | 
					    resolution: {integrity: sha512-XFwwFVK9OG3F085pKIq2SlNVqx32WdFH+TXbGEWc5FfEKpx8oMmZuAwZZ50K/pH2FgmJSE8IRwU9DDhrLpd6iA==}
 | 
				
			||||||
 | 
					    peerDependencies:
 | 
				
			||||||
 | 
					      vue: ^3.5.13
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  vue@3.5.13:
 | 
					  vue@3.5.13:
 | 
				
			||||||
    resolution: {integrity: sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==}
 | 
					    resolution: {integrity: sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==}
 | 
				
			||||||
    peerDependencies:
 | 
					    peerDependencies:
 | 
				
			||||||
| 
						 | 
					@ -16351,6 +16368,8 @@ snapshots:
 | 
				
			||||||
      bundle-name: 4.1.0
 | 
					      bundle-name: 4.1.0
 | 
				
			||||||
      default-browser-id: 5.0.0
 | 
					      default-browser-id: 5.0.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  default-passive-events@2.0.0: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  define-data-property@1.1.4:
 | 
					  define-data-property@1.1.4:
 | 
				
			||||||
    dependencies:
 | 
					    dependencies:
 | 
				
			||||||
      es-define-property: 1.0.1
 | 
					      es-define-property: 1.0.1
 | 
				
			||||||
| 
						 | 
					@ -20470,6 +20489,8 @@ snapshots:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  signal-exit@4.1.0: {}
 | 
					  signal-exit@4.1.0: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  signature_pad@3.0.0-beta.4: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  simple-swizzle@0.2.2:
 | 
					  simple-swizzle@0.2.2:
 | 
				
			||||||
    dependencies:
 | 
					    dependencies:
 | 
				
			||||||
      is-arrayish: 0.3.2
 | 
					      is-arrayish: 0.3.2
 | 
				
			||||||
| 
						 | 
					@ -21801,6 +21822,12 @@ snapshots:
 | 
				
			||||||
      is-plain-object: 3.0.1
 | 
					      is-plain-object: 3.0.1
 | 
				
			||||||
      vue: 3.5.13(typescript@5.8.3)
 | 
					      vue: 3.5.13(typescript@5.8.3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  vue3-signature@0.2.4(vue@3.5.13(typescript@5.8.3)):
 | 
				
			||||||
 | 
					    dependencies:
 | 
				
			||||||
 | 
					      default-passive-events: 2.0.0
 | 
				
			||||||
 | 
					      signature_pad: 3.0.0-beta.4
 | 
				
			||||||
 | 
					      vue: 3.5.13(typescript@5.8.3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  vue@3.5.13(typescript@5.8.3):
 | 
					  vue@3.5.13(typescript@5.8.3):
 | 
				
			||||||
    dependencies:
 | 
					    dependencies:
 | 
				
			||||||
      '@vue/compiler-dom': 3.5.13
 | 
					      '@vue/compiler-dom': 3.5.13
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -198,3 +198,4 @@ catalog:
 | 
				
			||||||
  zod: ^3.24.3
 | 
					  zod: ^3.24.3
 | 
				
			||||||
  zod-defaults: ^0.1.3
 | 
					  zod-defaults: ^0.1.3
 | 
				
			||||||
  highlight.js: ^11.11.1
 | 
					  highlight.js: ^11.11.1
 | 
				
			||||||
 | 
					  vue3-signature: ^0.2.4
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue