From c9a6f7cbb5209415e626df29c7572cf40fe92b66 Mon Sep 17 00:00:00 2001
From: houzhongjian <houzhongyi@126.com>
Date: 星期一, 06 一月 2025 11:44:50 +0800
Subject: [PATCH] 1、工作流程功能优化,解决流程图模拟功能simulation不生效的bug 2、system menu等页面修改 3、Footer copyright年份修改为动态

---
 src/components/UserSelectForm/index.vue                                          |   39 
 src/components/bpmnProcessDesigner/package/designer/ProcessDesigner.vue          |   85 -
 src/directives/permission/hasPermi.ts                                            |   21 
 src/views/bpm/model/ModelForm.vue                                                |  189 +++
 src/views/bpm/model/index.vue                                                    |   10 
 src/utils/routerHelper.ts                                                        |    2 
 src/components/bpmnProcessDesigner/package/penal/PropertiesPanel.vue             |  119 +
 src/views/bpm/processInstance/detail/index.vue                                   |    4 
 src/components/SimpleProcessDesignerV2/src/SimpleProcessDesigner.vue             |  213 +++
 src/views/bpm/model/form/FormDesign.vue                                          |  137 ++
 src/components/SimpleProcessDesignerV2/src/nodes-config/ConditionNodeConfig.vue  |    2 
 src/views/bpm/model/form/ProcessDesign.vue                                       |  235 ++++
 src/components/SimpleProcessDesignerV2/src/NodeHandler.vue                       |   17 
 src/views/system/menu/index.vue                                                  |  200 ++-
 src/components/SimpleProcessDesignerV2/src/SimpleProcessModel.vue                |   46 
 src/components/SimpleProcessDesignerV2/theme/simple-process-designer.scss        |    7 
 src/views/bpm/model/form/BasicInfo.vue                                           |  301 +++++
 src/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue                 |    4 
 src/views/bpm/simple/SimpleModelDesign.vue                                       |  148 ++
 src/components/RouterSearch/index.vue                                            |    7 
 src/views/bpm/model/form/index.vue                                               |  439 ++++++++
 src/components/Echart/src/Echart.vue                                             |    8 
 src/components/SimpleProcessDesignerV2/src/nodes-config/DelayTimerNodeConfig.vue |  189 +++
 src/layout/components/Footer/src/Footer.vue                                      |    6 
 src/router/modules/remaining.ts                                                  |   24 
 src/components/SimpleProcessDesignerV2/src/consts.ts                             |   36 
 src/components/SimpleProcessDesignerV2/src/nodes-config/StartUserNodeConfig.vue  |   36 
 src/views/bpm/model/CategoryDraggableModel.vue                                   |   50 
 src/views/infra/file/index.vue                                                   |   10 
 src/components/SimpleProcessDesignerV2/src/nodes/DelayTimerNode.vue              |   98 +
 src/components/SimpleProcessDesignerV2/src/ProcessNodeTree.vue                   |    7 
 src/views/bpm/model/editor/index.vue                                             |  258 +++-
 src/views/bpm/processInstance/detail/ProcessInstanceOperationButton.vue          |   20 
 src/views/bpm/processInstance/create/ProcessDefinitionDetail.vue                 |   47 
 package.json                                                                     |    4 
 src/views/system/area/index.vue                                                  |   22 
 src/components/SimpleProcessDesignerV2/src/node.ts                               |   31 
 src/assets/svgs/bpm/delay.svg                                                    |    1 
 src/views/bpm/task/done/index.vue                                                |    2 
 src/components/bpmnProcessDesigner/package/theme/process-designer.scss           |   18 
 40 files changed, 2,664 insertions(+), 428 deletions(-)

diff --git a/package.json b/package.json
index a7c562a..f904721 100644
--- a/package.json
+++ b/package.json
@@ -38,7 +38,7 @@
     "animate.css": "^4.1.1",
     "axios": "^1.6.8",
     "benz-amr-recorder": "^1.1.5",
-    "bpmn-js-token-simulation": "^0.10.0",
+    "bpmn-js-token-simulation": "^0.36.0",
     "camunda-bpmn-moddle": "^7.0.1",
     "cropperjs": "^1.6.1",
     "crypto-js": "^4.2.0",
@@ -47,7 +47,7 @@
     "driver.js": "^1.3.1",
     "echarts": "^5.5.0",
     "echarts-wordcloud": "^2.1.0",
-    "element-plus": "2.8.4",
+    "element-plus": "2.9.1",
     "fast-xml-parser": "^4.3.2",
     "highlight.js": "^11.9.0",
     "jsencrypt": "^3.3.2",
diff --git a/src/assets/svgs/bpm/delay.svg b/src/assets/svgs/bpm/delay.svg
new file mode 100644
index 0000000..cbc31df
--- /dev/null
+++ b/src/assets/svgs/bpm/delay.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1735905505218" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4277" width="200" height="200" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M561.778 454.929h198.117c0.549 0 0.994 0.444 0.994 1.001v97.553a0.998 0.998 0 0 1-0.994 1.001H463.224a1.005 1.005 0 0 1-1.002-1V207.04c0-0.552 0.444-1 1.002-1h97.552c0.553 0 1.002 0.455 1.002 1v247.89zM512 952.706c-247.424 0-448-200.576-448-448 0-247.423 200.576-448 448-448s448 200.577 448 448c0 247.424-200.576 448-448 448z m0-99.555c192.44 0 348.444-156.004 348.444-348.445 0-192.44-156.003-348.444-348.444-348.444-192.44 0-348.444 156.004-348.444 348.444 0 192.441 156.003 348.445 348.444 348.445z" fill="#3296FA" p-id="4278"></path></svg>
\ No newline at end of file
diff --git a/src/components/Echart/src/Echart.vue b/src/components/Echart/src/Echart.vue
index fd3342d..02738ca 100644
--- a/src/components/Echart/src/Echart.vue
+++ b/src/components/Echart/src/Echart.vue
@@ -9,6 +9,10 @@
 import { isString } from '@/utils/is'
 import { useDesign } from '@/hooks/web/useDesign'
 
+import 'echarts/lib/component/markPoint'
+import 'echarts/lib/component/markLine'
+import 'echarts/lib/component/markArea'
+
 defineOptions({ name: 'EChart' })
 
 const { getPrefixCls, variables } = useDesign()
@@ -94,13 +98,13 @@
 
   contentEl.value = document.getElementsByClassName(`${variables.namespace}-layout-content`)[0]
   unref(contentEl) &&
-    (unref(contentEl) as Element).addEventListener('transitionend', contentResizeHandler)
+  (unref(contentEl) as Element).addEventListener('transitionend', contentResizeHandler)
 })
 
 onBeforeUnmount(() => {
   window.removeEventListener('resize', resizeHandler)
   unref(contentEl) &&
-    (unref(contentEl) as Element).removeEventListener('transitionend', contentResizeHandler)
+  (unref(contentEl) as Element).removeEventListener('transitionend', contentResizeHandler)
 })
 
 onActivated(() => {
diff --git a/src/components/RouterSearch/index.vue b/src/components/RouterSearch/index.vue
index 3fa35f6..42a4174 100644
--- a/src/components/RouterSearch/index.vue
+++ b/src/components/RouterSearch/index.vue
@@ -79,7 +79,12 @@
 
 function handleChange(path) {
   router.push({ path })
+  hiddenSearch()
   hiddenTopSearch()
+}
+
+function hiddenSearch() {
+  showSearch.value = false
 }
 
 function hiddenTopSearch() {
@@ -99,6 +104,8 @@
 // 监听 ctrl + k
 function listenKey(event) {
   if ((event.ctrlKey || event.metaKey) && event.key === 'k') {
+    // 阻止触发浏览器默认事件
+    event.preventDefault()
     showSearch.value = !showSearch.value
     // 这里可以执行相应的操作(例如打开搜索框等)
   }
diff --git a/src/components/SimpleProcessDesignerV2/src/NodeHandler.vue b/src/components/SimpleProcessDesignerV2/src/NodeHandler.vue
index 853a0aa..4dfd51a 100644
--- a/src/components/SimpleProcessDesignerV2/src/NodeHandler.vue
+++ b/src/components/SimpleProcessDesignerV2/src/NodeHandler.vue
@@ -39,6 +39,13 @@
             </div>
             <div class="handler-item-text">包容分支</div>
           </div>
+          <div class="handler-item" @click="addNode(NodeType.DELAY_TIMER_NODE)">
+            <!-- TODO @芋艿 需要更换一下iconfont的图标 -->
+            <div class="handler-item-icon copy">
+              <span class="iconfont icon-size icon-copy"></span>
+            </div>
+            <div class="handler-item-text">延迟器</div>
+          </div>
         </div>
         <template #reference>
           <div class="add-icon"><Icon icon="ep:plus" /></div>
@@ -208,6 +215,16 @@
     }
     emits('update:childNode', data)
   }
+  if (type === NodeType.DELAY_TIMER_NODE) {
+    const data: SimpleFlowNode = {
+      id: 'Activity_' + generateUUID(),
+      name: NODE_DEFAULT_NAME.get(NodeType.DELAY_TIMER_NODE) as string,
+      showText: '',
+      type: NodeType.DELAY_TIMER_NODE,
+      childNode: props.childNode
+    }
+    emits('update:childNode', data)
+  }
 }
 </script>
 
diff --git a/src/components/SimpleProcessDesignerV2/src/ProcessNodeTree.vue b/src/components/SimpleProcessDesignerV2/src/ProcessNodeTree.vue
index 9b1d65a..419501a 100644
--- a/src/components/SimpleProcessDesignerV2/src/ProcessNodeTree.vue
+++ b/src/components/SimpleProcessDesignerV2/src/ProcessNodeTree.vue
@@ -38,6 +38,12 @@
     @update:model-value="handleModelValueUpdate"
     @find:parent-node="findFromParentNode"
   />
+  <!-- 延迟器节点 -->
+  <DelayTimerNode
+    v-if="currentNode && currentNode.type === NodeType.DELAY_TIMER_NODE"
+    :flow-node="currentNode"
+    @update:flow-node="handleModelValueUpdate"
+  />
   <!-- 递归显示孩子节点  -->
   <ProcessNodeTree
     v-if="currentNode && currentNode.childNode"
@@ -60,6 +66,7 @@
 import ExclusiveNode from './nodes/ExclusiveNode.vue'
 import ParallelNode from './nodes/ParallelNode.vue'
 import InclusiveNode from './nodes/InclusiveNode.vue'
+import DelayTimerNode from './nodes/DelayTimerNode.vue'
 import { SimpleFlowNode, NodeType } from './consts'
 import { useWatchNode } from './node'
 defineOptions({
diff --git a/src/components/SimpleProcessDesignerV2/src/SimpleProcessDesigner.vue b/src/components/SimpleProcessDesignerV2/src/SimpleProcessDesigner.vue
index 6b5ff99..22e6073 100644
--- a/src/components/SimpleProcessDesignerV2/src/SimpleProcessDesigner.vue
+++ b/src/components/SimpleProcessDesignerV2/src/SimpleProcessDesigner.vue
@@ -1,6 +1,7 @@
 <template>
   <div v-loading="loading" class="overflow-auto">
     <SimpleProcessModel
+      ref="simpleProcessModelRef"
       v-if="processNodeTree"
       :flow-node="processNodeTree"
       :readonly="false"
@@ -38,12 +39,30 @@
 defineOptions({
   name: 'SimpleProcessDesigner'
 })
-const emits = defineEmits(['success']) // 保存成功事件
+
+const emits = defineEmits(['success', 'init-finished']) // 保存成功事件
 
 const props = defineProps({
   modelId: {
     type: String,
-    required: true
+    required: false
+  },
+  modelKey: {
+    type: String,
+    required: false
+  },
+  modelName: {
+    type: String,
+    required: false
+  },
+  // 可发起流程的人员编号
+  startUserIds : {
+    type: Array,
+    required: false
+  },
+  value: {
+    type: [String, Object],
+    required: false
   }
 })
 
@@ -56,6 +75,10 @@
 const deptOptions = ref<DeptApi.DeptVO[]>([]) // 部门列表
 const deptTreeOptions = ref()
 const userGroupOptions = ref<UserGroupApi.UserGroupVO[]>([]) // 用户组列表
+
+// 添加当前值的引用
+const currentValue = ref<SimpleFlowNode | undefined>()
+
 provide('formFields', formFields)
 provide('formType', formType)
 provide('roleList', roleOptions)
@@ -64,33 +87,101 @@
 provide('deptList', deptOptions)
 provide('userGroupList', userGroupOptions)
 provide('deptTree', deptTreeOptions)
+provide('startUserIds', props.startUserIds)
 
 const message = useMessage() // 国际化
 const processNodeTree = ref<SimpleFlowNode | undefined>()
 const errorDialogVisible = ref(false)
 let errorNodes: SimpleFlowNode[] = []
-const saveSimpleFlowModel = async (simpleModelNode: SimpleFlowNode) => {
-  if (!simpleModelNode) {
-    message.error('模型数据为空')
-    return
-  }
-  try {
-    loading.value = true
-    const data = {
-      id: props.modelId,
-      simpleModel: simpleModelNode
+
+// 添加更新模型的方法
+const updateModel = () => {
+  if (!processNodeTree.value) {
+    processNodeTree.value = {
+      name: '发起人',
+      type: NodeType.START_USER_NODE,
+      id: NodeId.START_USER_NODE_ID,
+      childNode: {
+        id: NodeId.END_EVENT_NODE_ID,
+        name: '结束',
+        type: NodeType.END_EVENT_NODE
+      }
     }
-    const result = await updateBpmSimpleModel(data)
-    if (result) {
-      message.success('修改成功')
-      emits('success')
-    } else {
-      message.alert('修改失败')
-    }
-  } finally {
-    loading.value = false
+    // 初始化时也触发一次保存
+    saveSimpleFlowModel(processNodeTree.value)
   }
 }
+
+// 加载流程数据
+const loadProcessData = async (data: any) => {
+  try {
+    if (data) {
+      const parsedData = typeof data === 'string' ? JSON.parse(data) : data
+      processNodeTree.value = parsedData
+      currentValue.value = parsedData
+      // 确保数据加载后刷新视图
+      await nextTick()
+      if (simpleProcessModelRef.value?.refresh) {
+        await simpleProcessModelRef.value.refresh()
+      }
+    }
+  } catch (error) {
+    console.error('加载流程数据失败:', error)
+  }
+}
+
+// 监听属性变化
+watch(
+  () => props.value,
+  async (newValue, oldValue) => {
+    if (newValue && newValue !== oldValue) {
+      await loadProcessData(newValue)
+    }
+  },
+  { immediate: true, deep: true }
+)
+
+// 监听流程节点树变化,自动保存
+watch(
+  () => processNodeTree.value,
+  async (newValue, oldValue) => {
+    if (newValue && oldValue && JSON.stringify(newValue) !== JSON.stringify(oldValue)) {
+      await saveSimpleFlowModel(newValue)
+    }
+  },
+  { deep: true }
+)
+
+const saveSimpleFlowModel = async (simpleModelNode: SimpleFlowNode) => {
+  if (!simpleModelNode) {
+    return
+  }
+
+  // 校验节点
+  errorNodes = []
+  validateNode(simpleModelNode, errorNodes)
+  if (errorNodes.length > 0) {
+    errorDialogVisible.value = true
+    return
+  }
+
+  try {
+    if (props.modelId) {
+      // 编辑模式
+      const data = {
+        id: props.modelId,
+        simpleModel: simpleModelNode
+      }
+      await updateBpmSimpleModel(data)
+    }
+    // 无论是编辑还是新建模式,都更新当前值并触发事件
+    currentValue.value = simpleModelNode
+    emits('success', simpleModelNode)
+  } catch (error) {
+    console.error('保存失败:', error)
+  }
+}
+
 // 校验节点设置。 暂时以 showText 为空 未节点错误配置
 const validateNode = (node: SimpleFlowNode | undefined, errorNodes: SimpleFlowNode[]) => {
   if (node) {
@@ -134,12 +225,14 @@
   try {
     loading.value = true
     // 获取表单字段
-    const bpmnModel = await getModel(props.modelId)
-    if (bpmnModel) {
-      formType.value = bpmnModel.formType
-      if (formType.value === 10) {
-        const bpmnForm = (await getForm(bpmnModel.formId)) as unknown as FormVO
-        formFields.value = bpmnForm?.fields
+    if (props.modelId) {
+      const bpmnModel = await getModel(props.modelId)
+      if (bpmnModel) {
+        formType.value = bpmnModel.formType
+        if (formType.value === 10) {
+          const bpmnForm = (await getForm(bpmnModel.formId)) as unknown as FormVO
+          formFields.value = bpmnForm?.fields
+        }
       }
     }
     // 获得角色列表
@@ -150,30 +243,64 @@
     userOptions.value = await UserApi.getSimpleUserList()
     // 获得部门列表
     deptOptions.value = await DeptApi.getSimpleDeptList()
-
     deptTreeOptions.value = handleTree(deptOptions.value as DeptApi.DeptVO[], 'id')
     // 获取用户组列表
     userGroupOptions.value = await UserGroupApi.getUserGroupSimpleList()
 
-    //获取 SIMPLE 设计器模型
-    const result = await getBpmSimpleModel(props.modelId)
-    if (result) {
-      processNodeTree.value = result
-    } else {
-      // 初始值
-      processNodeTree.value = {
-        name: '发起人',
-        type: NodeType.START_USER_NODE,
-        id: NodeId.START_USER_NODE_ID,
-        childNode: {
-          id: NodeId.END_EVENT_NODE_ID,
-          name: '结束',
-          type: NodeType.END_EVENT_NODE
-        }
+    // 加载流程数据
+    if (props.modelId) {
+      // 获取 SIMPLE 设计器模型
+      const result = await getBpmSimpleModel(props.modelId)
+      if (result) {
+        await loadProcessData(result)
+      } else {
+        updateModel()
       }
+    } else if (props.value) {
+      await loadProcessData(props.value)
+    } else {
+      updateModel()
     }
   } finally {
     loading.value = false
+    emits('init-finished')
   }
 })
+
+const simpleProcessModelRef = ref()
+
+/** 获取当前流程数据 */
+const getCurrentFlowData = async () => {
+  try {
+    if (simpleProcessModelRef.value) {
+      const data = await simpleProcessModelRef.value.getCurrentFlowData()
+      if (data) {
+        currentValue.value = data
+        return data
+      }
+    }
+    return currentValue.value
+  } catch (error) {
+    console.error('获取流程数据失败:', error)
+    return currentValue.value
+  }
+}
+
+// 刷新方法
+const refresh = async () => {
+  try {
+    if (currentValue.value) {
+      await loadProcessData(currentValue.value)
+    }
+  } catch (error) {
+    console.error('刷新失败:', error)
+  }
+}
+
+defineExpose({
+  getCurrentFlowData,
+  updateModel,
+  loadProcessData,
+  refresh
+})
 </script>
diff --git a/src/components/SimpleProcessDesignerV2/src/SimpleProcessModel.vue b/src/components/SimpleProcessDesignerV2/src/SimpleProcessModel.vue
index 3a18227..ccd1f10 100644
--- a/src/components/SimpleProcessDesignerV2/src/SimpleProcessModel.vue
+++ b/src/components/SimpleProcessDesignerV2/src/SimpleProcessModel.vue
@@ -8,15 +8,6 @@
           <el-button size="default" class="w-80px"> {{ scaleValue }}% </el-button>
           <el-button size="default" :plain="true" :icon="ZoomIn" @click="zoomIn()" />
         </el-button-group>
-        <el-button
-          v-if="!readonly"
-          size="default"
-          class="ml-4px"
-          type="primary"
-          :icon="Select"
-          @click="saveSimpleFlowModel"
-          >保存模型</el-button
-        >
       </el-row>
     </div>
     <div class="simple-process-model" :style="`transform: scale(${scaleValue / 100});`">
@@ -42,7 +33,8 @@
 import ProcessNodeTree from './ProcessNodeTree.vue'
 import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from './consts'
 import { useWatchNode } from './node'
-import { Select, ZoomOut, ZoomIn, ScaleToOriginal } from '@element-plus/icons-vue'
+import { ZoomOut, ZoomIn, ScaleToOriginal } from '@element-plus/icons-vue'
+
 defineOptions({
   name: 'SimpleProcessModel'
 })
@@ -58,6 +50,7 @@
     default: true
   }
 })
+
 const emits = defineEmits<{
   'save': [node: SimpleFlowNode | undefined]
 }>()
@@ -68,6 +61,7 @@
 let scaleValue = ref(100)
 const MAX_SCALE_VALUE = 200
 const MIN_SCALE_VALUE = 50
+
 // 放大
 const zoomIn = () => {
   if (scaleValue.value == MAX_SCALE_VALUE) {
@@ -75,6 +69,7 @@
   }
   scaleValue.value += 10
 }
+
 // 缩小
 const zoomOut = () => {
   if (scaleValue.value == MIN_SCALE_VALUE) {
@@ -82,21 +77,14 @@
   }
   scaleValue.value -= 10
 }
+
 const processReZoom = () => {
   scaleValue.value = 100
 }
 
 const errorDialogVisible = ref(false)
 let errorNodes: SimpleFlowNode[] = []
-const saveSimpleFlowModel = async () => {
-  errorNodes = []
-  validateNode(processNodeTree.value, errorNodes)
-  if (errorNodes.length > 0) {
-    errorDialogVisible.value = true
-    return
-  }
-  emits('save', processNodeTree.value)
-}
+
 // 校验节点设置。 暂时以 showText 为空 未节点错误配置
 const validateNode = (node: SimpleFlowNode | undefined, errorNodes: SimpleFlowNode[]) => {
   if (node) {
@@ -135,6 +123,26 @@
     }
   }
 }
+
+/** 获取当前流程数据 */
+const getCurrentFlowData = async () => {
+  try {
+    errorNodes = []
+    validateNode(processNodeTree.value, errorNodes)
+    if (errorNodes.length > 0) {
+      errorDialogVisible.value = true
+      return undefined
+    }
+    return processNodeTree.value
+  } catch (error) {
+    console.error('获取流程数据失败:', error)
+    return undefined
+  }
+}
+
+defineExpose({
+  getCurrentFlowData
+})
 </script>
 
 <style lang="scss" scoped></style>
diff --git a/src/components/SimpleProcessDesignerV2/src/consts.ts b/src/components/SimpleProcessDesignerV2/src/consts.ts
index b81aa94..10d8a21 100644
--- a/src/components/SimpleProcessDesignerV2/src/consts.ts
+++ b/src/components/SimpleProcessDesignerV2/src/consts.ts
@@ -24,6 +24,11 @@
   COPY_TASK_NODE = 12,
 
   /**
+   * 延迟器节点
+   */
+  DELAY_TIMER_NODE = 14,
+
+  /**
    * 条件节点
    */
   CONDITION_NODE = 50,
@@ -98,6 +103,8 @@
   defaultFlow?: boolean
   // 活动的状态,用于前端节点状态展示
   activityStatus?: TaskStatusEnum
+  // 延迟设置
+  delaySetting?: DelaySetting
 }
 // 候选人策略枚举 ( 用于审批节点。抄送节点 )
 export enum CandidateStrategy {
@@ -413,12 +420,14 @@
 NODE_DEFAULT_TEXT.set(NodeType.COPY_TASK_NODE, '请配置抄送人')
 NODE_DEFAULT_TEXT.set(NodeType.CONDITION_NODE, '请设置条件')
 NODE_DEFAULT_TEXT.set(NodeType.START_USER_NODE, '请设置发起人')
+NODE_DEFAULT_TEXT.set(NodeType.DELAY_TIMER_NODE, '请设置延迟器')
 
 export const NODE_DEFAULT_NAME = new Map<number, string>()
 NODE_DEFAULT_NAME.set(NodeType.USER_TASK_NODE, '审批人')
 NODE_DEFAULT_NAME.set(NodeType.COPY_TASK_NODE, '抄送人')
 NODE_DEFAULT_NAME.set(NodeType.CONDITION_NODE, '条件')
 NODE_DEFAULT_NAME.set(NodeType.START_USER_NODE, '发起人')
+NODE_DEFAULT_NAME.set(NodeType.DELAY_TIMER_NODE, '延迟器')
 
 // 候选人策略。暂时不从字典中取。 后续可能调整。控制显示顺序
 export const CANDIDATE_STRATEGY: DictDataVO[] = [
@@ -568,3 +577,30 @@
    */
   START_USER_ID = 'PROCESS_START_USER_ID'
 }
+
+/**
+ * 延迟设置
+ */
+export type DelaySetting = {
+  // 延迟类型
+  delayType: number
+  // 延迟时间表达式
+  delayTime: string
+}
+/**
+ * 延迟类型
+ */
+export enum DelayTypeEnum {
+  /**
+   * 固定时长
+   */
+  FIXED_TIME_DURATION = 1,
+  /**
+   * 固定日期时间
+   */
+  FIXED_DATE_TIME = 2
+}
+export const DELAY_TYPE = [
+  { label: '固定时长', value: DelayTypeEnum.FIXED_TIME_DURATION },
+  { label: '固定日期', value: DelayTypeEnum.FIXED_DATE_TIME }
+]
diff --git a/src/components/SimpleProcessDesignerV2/src/node.ts b/src/components/SimpleProcessDesignerV2/src/node.ts
index 4cbac6e..282e81b 100644
--- a/src/components/SimpleProcessDesignerV2/src/node.ts
+++ b/src/components/SimpleProcessDesignerV2/src/node.ts
@@ -1,4 +1,3 @@
-import { cloneDeep } from 'lodash-es'
 import { TaskStatusEnum } from '@/api/bpm/task'
 import * as RoleApi from '@/api/system/role'
 import * as DeptApi from '@/api/system/dept'
@@ -14,7 +13,7 @@
   NODE_DEFAULT_NAME,
   AssignStartUserHandlerType,
   AssignEmptyHandlerType,
-  FieldPermissionType,
+  FieldPermissionType
 } from './consts'
 import { parseFormFields } from '@/components/FormCreate/src/utils/index'
 export function useWatchNode(props: { flowNode: SimpleFlowNode }): Ref<SimpleFlowNode> {
@@ -52,9 +51,33 @@
 
   const getNodeConfigFormFields = (nodeFormFields?: Array<Record<string, string>>) => {
     nodeFormFields = toRaw(nodeFormFields)
-    fieldsPermissionConfig.value =
-      cloneDeep(nodeFormFields) || getDefaultFieldsPermission(unref(formFields))
+    if (!nodeFormFields || nodeFormFields.length === 0) {
+      fieldsPermissionConfig.value = getDefaultFieldsPermission(unref(formFields))
+    } else {
+      fieldsPermissionConfig.value = mergeFieldsPermission(nodeFormFields, unref(formFields))
+    }
   }
+  // 合并已经设置的表单字段权限,当前流程表单字段 (可能新增,或删除了字段)
+  const mergeFieldsPermission = (
+    formFieldsPermisson: Array<Record<string, string>>,
+    formFields?: string[]
+  ) => {
+    let mergedFieldsPermission: Array<Record<string, any>> = []
+    if (formFields) {
+      mergedFieldsPermission = parseFormCreateFields(formFields).map((item) => {
+        const found = formFieldsPermisson.find(
+          (fieldPermission) => fieldPermission.field == item.field
+        )
+        return {
+          field: item.field,
+          title: item.title,
+          permission: found ? found.permission : defaultPermission
+        }
+      })
+    }
+    return mergedFieldsPermission
+  }
+
   // 默认的表单权限: 获取表单的所有字段,设置字段默认权限为只读
   const getDefaultFieldsPermission = (formFields?: string[]) => {
     let defaultFieldsPermission: Array<Record<string, any>> = []
diff --git a/src/components/SimpleProcessDesignerV2/src/nodes-config/ConditionNodeConfig.vue b/src/components/SimpleProcessDesignerV2/src/nodes-config/ConditionNodeConfig.vue
index 49e5d9f..ae93172 100644
--- a/src/components/SimpleProcessDesignerV2/src/nodes-config/ConditionNodeConfig.vue
+++ b/src/components/SimpleProcessDesignerV2/src/nodes-config/ConditionNodeConfig.vue
@@ -381,7 +381,7 @@
 
 /** 获取字段名称 */
 const getFieldTitle = (field: string) => {
-  const item = fieldsInfo.find((item) => item.field === field)
+  const item = fieldOptions.value.find((item) => item.field === field)
   return item?.title
 }
 
diff --git a/src/components/SimpleProcessDesignerV2/src/nodes-config/DelayTimerNodeConfig.vue b/src/components/SimpleProcessDesignerV2/src/nodes-config/DelayTimerNodeConfig.vue
new file mode 100644
index 0000000..27a351b
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/src/nodes-config/DelayTimerNodeConfig.vue
@@ -0,0 +1,189 @@
+<template>
+  <el-drawer
+    :append-to-body="true"
+    v-model="settingVisible"
+    :show-close="false"
+    :size="550"
+    :before-close="saveConfig"
+  >
+    <template #header>
+      <div class="config-header">
+        <input
+          v-if="showInput"
+          type="text"
+          class="config-editable-input"
+          @blur="blurEvent()"
+          v-mountedFocus
+          v-model="nodeName"
+          :placeholder="nodeName"
+        />
+        <div v-else class="node-name">
+          {{ nodeName }} <Icon class="ml-1" icon="ep:edit-pen" :size="16" @click="clickIcon()" />
+        </div>
+        <div class="divide-line"></div>
+      </div>
+    </template>
+    <div>
+      <el-form ref="formRef" :model="configForm" label-position="top" :rules="formRules">
+        <el-form-item label="延迟时间" prop="delayType">
+          <el-radio-group v-model="configForm.delayType">
+            <el-radio-button
+              v-for="item in DELAY_TYPE"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item v-if="configForm.delayType === DelayTypeEnum.FIXED_TIME_DURATION">
+          <el-form-item prop="timeDuration">
+            <el-input-number
+              class="mr-2"
+              :style="{ width: '100px' }"
+              v-model="configForm.timeDuration"
+              :min="1"
+              controls-position="right"
+            />
+          </el-form-item>
+          <el-select v-model="configForm.timeUnit" class="mr-2" :style="{ width: '100px' }">
+            <el-option
+              v-for="item in TIME_UNIT_TYPES"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+          <el-text>后进入下一节点</el-text>
+        </el-form-item>
+        <el-form-item v-if="configForm.delayType === DelayTypeEnum.FIXED_DATE_TIME" prop="dateTime">
+          <el-date-picker
+            class="mr-2"
+            v-model="configForm.dateTime"
+            type="datetime"
+            placeholder="请选择日期和时间"
+            value-format="YYYY-MM-DDTHH:mm:ss"
+          />
+          <el-text>后进入下一节点</el-text>
+        </el-form-item>
+      </el-form>
+    </div>
+    <template #footer>
+      <el-divider />
+      <div>
+        <el-button type="primary" @click="saveConfig">确 定</el-button>
+        <el-button @click="closeDrawer">取 消</el-button>
+      </div>
+    </template>
+  </el-drawer>
+</template>
+<script setup lang="ts">
+import {
+  SimpleFlowNode,
+  NodeType,
+  TIME_UNIT_TYPES,
+  TimeUnitType,
+  DelayTypeEnum,
+  DELAY_TYPE
+} from '../consts'
+import { useWatchNode, useDrawer, useNodeName } from '../node'
+import { convertTimeUnit } from '../utils'
+defineOptions({
+  name: 'DelayTimerNodeConfig'
+})
+const props = defineProps({
+  flowNode: {
+    type: Object as () => SimpleFlowNode,
+    required: true
+  }
+})
+// 抽屉配置
+const { settingVisible, closeDrawer, openDrawer } = useDrawer()
+// 当前节点
+const currentNode = useWatchNode(props)
+// 节点名称
+const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(NodeType.DELAY_TIMER_NODE)
+// 抄送人表单配置
+const formRef = ref() // 表单 Ref
+// 表单校验规则
+const formRules = reactive({
+  delayType: [{ required: true, message: '延迟时间不能为空', trigger: 'change' }],
+  timeDuration: [{ required: true, message: '延迟时间不能为空', trigger: 'change' }],
+  dateTime: [{ required: true, message: '延迟时间不能为空', trigger: 'change' }]
+})
+// 配置表单数据
+const configForm = ref({
+  delayType: DelayTypeEnum.FIXED_TIME_DURATION,
+  timeDuration: 1,
+  timeUnit: TimeUnitType.HOUR,
+  dateTime: ''
+})
+// 保存配置
+const saveConfig = async () => {
+  if (!formRef) return false
+  const valid = await formRef.value.validate()
+  if (!valid) return false
+  const showText = getShowText()
+  if (!showText) return false
+  currentNode.value.showText = showText
+  if (configForm.value.delayType === DelayTypeEnum.FIXED_TIME_DURATION) {
+    currentNode.value.delaySetting = {
+      delayType: configForm.value.delayType,
+      delayTime: getIsoTimeDuration()
+    }
+  }
+  if (configForm.value.delayType === DelayTypeEnum.FIXED_DATE_TIME) {
+    currentNode.value.delaySetting = {
+      delayType: configForm.value.delayType,
+      delayTime: configForm.value.dateTime
+    }
+  }
+  settingVisible.value = false
+  return true
+}
+const getShowText = (): string => {
+  let showText = ''
+  if (configForm.value.delayType === DelayTypeEnum.FIXED_TIME_DURATION) {
+    showText = `延迟${configForm.value.timeDuration}${TIME_UNIT_TYPES.find((item) => item.value === configForm.value.timeUnit).label}`
+  }
+  if (configForm.value.delayType === DelayTypeEnum.FIXED_DATE_TIME) {
+    showText = `延迟至${configForm.value.dateTime.replace('T', ' ')}`
+  }
+  return showText
+}
+const getIsoTimeDuration = () => {
+  let strTimeDuration = 'PT'
+  if (configForm.value.timeUnit === TimeUnitType.MINUTE) {
+    strTimeDuration += configForm.value.timeDuration + 'M'
+  }
+  if (configForm.value.timeUnit === TimeUnitType.HOUR) {
+    strTimeDuration += configForm.value.timeDuration + 'H'
+  }
+  if (configForm.value.timeUnit === TimeUnitType.DAY) {
+    strTimeDuration += configForm.value.timeDuration + 'D'
+  }
+  return strTimeDuration
+}
+// 显示延迟器节点配置, 由父组件传过来
+const showDelayTimerNodeConfig = (node: SimpleFlowNode) => {
+  nodeName.value = node.name
+  if (node.delaySetting) {
+    configForm.value.delayType = node.delaySetting.delayType
+    // 固定时长
+    if (configForm.value.delayType === DelayTypeEnum.FIXED_TIME_DURATION) {
+      const strTimeDuration = node.delaySetting.delayTime
+      let parseTime = strTimeDuration.slice(2, strTimeDuration.length - 1)
+      let parseTimeUnit = strTimeDuration.slice(strTimeDuration.length - 1)
+      configForm.value.timeDuration = parseInt(parseTime)
+      configForm.value.timeUnit = convertTimeUnit(parseTimeUnit)
+    }
+    // 固定日期时间
+    if (configForm.value.delayType === DelayTypeEnum.FIXED_DATE_TIME) {
+      configForm.value.dateTime = node.delaySetting.delayTime
+    }
+  }
+}
+
+defineExpose({ openDrawer, showDelayTimerNodeConfig }) // 暴露方法给父组件
+</script>
+
+<style lang="scss" scoped></style>
diff --git a/src/components/SimpleProcessDesignerV2/src/nodes-config/StartUserNodeConfig.vue b/src/components/SimpleProcessDesignerV2/src/nodes-config/StartUserNodeConfig.vue
index e43a351..26c8e13 100644
--- a/src/components/SimpleProcessDesignerV2/src/nodes-config/StartUserNodeConfig.vue
+++ b/src/components/SimpleProcessDesignerV2/src/nodes-config/StartUserNodeConfig.vue
@@ -25,7 +25,20 @@
     </template>
     <el-tabs type="border-card" v-model="activeTabName">
       <el-tab-pane label="权限" name="user">
-        <div> 待实现 </div>
+        <el-text v-if="!startUserIds || startUserIds.length === 0"> 全部成员可以发起流程 </el-text>
+        <el-text v-else-if="startUserIds.length == 1">
+          {{ getUserNicknames(startUserIds) }} 可发起流程
+        </el-text>
+        <el-text v-else>
+          <el-tooltip
+            class="box-item"
+            effect="dark"
+            placement="top"
+            :content="getUserNicknames(startUserIds)"
+          >
+            {{ getUserNicknames(startUserIds.slice(0,2)) }} 等 {{ startUserIds.length }} 人可发起流程
+          </el-tooltip>
+        </el-text>
       </el-tab-pane>
       <el-tab-pane label="表单字段权限" name="fields" v-if="formType === 10">
         <div class="field-setting-pane">
@@ -86,7 +99,7 @@
 <script setup lang="ts">
 import { SimpleFlowNode, NodeType, FieldPermissionType, START_USER_BUTTON_SETTING } from '../consts'
 import { useWatchNode, useDrawer, useNodeName, useFormFieldsPermission } from '../node'
-
+import * as UserApi from '@/api/system/user'
 defineOptions({
   name: 'StartUserNodeConfig'
 })
@@ -96,6 +109,10 @@
     required: true
   }
 })
+// 可发起流程的用户编号
+const startUserIds = inject<Ref<any[]>>('startUserIds')
+// 用户列表
+const userOptions = inject<Ref<UserApi.UserVO[]>>('userList')
 // 抽屉配置
 const { settingVisible, closeDrawer, openDrawer } = useDrawer()
 // 当前节点
@@ -108,12 +125,23 @@
 const { formType, fieldsPermissionConfig, getNodeConfigFormFields } = useFormFieldsPermission(
   FieldPermissionType.WRITE
 )
-
+const getUserNicknames = (userIds: number[]): string => {
+  if (!userIds || userIds.length === 0) {
+    return ''
+  }
+  const nicknames: string[] = []
+  userIds.forEach((userId) => {
+    const found = userOptions?.value.find((item) => item.id === userId)
+    if (found && found.nickname) {
+      nicknames.push(found.nickname)
+    }
+  })
+  return nicknames.join(',')
+}
 // 保存配置
 const saveConfig = async () => {
   activeTabName.value = 'user'
   currentNode.value.name = nodeName.value!
-  // TODO 暂时写死。后续可以显示谁有权限可以发起
   currentNode.value.showText = '已设置'
   // 设置表单权限
   currentNode.value.fieldsPermission = fieldsPermissionConfig.value
diff --git a/src/components/SimpleProcessDesignerV2/src/nodes/DelayTimerNode.vue b/src/components/SimpleProcessDesignerV2/src/nodes/DelayTimerNode.vue
new file mode 100644
index 0000000..94f9c41
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/src/nodes/DelayTimerNode.vue
@@ -0,0 +1,98 @@
+<template>
+  <div class="node-wrapper">
+    <div class="node-container">
+      <div
+        class="node-box"
+        :class="[
+          { 'node-config-error': !currentNode.showText },
+          `${useTaskStatusClass(currentNode?.activityStatus)}`
+        ]"
+      >
+        <div class="node-title-container">
+          <!-- TODO @芋艿 需要更换图标 -->
+          <div class="node-title-icon copy-task"><span class="iconfont icon-copy"></span></div>
+          <input
+            v-if="!readonly && showInput"
+            type="text"
+            class="editable-title-input"
+            @blur="blurEvent()"
+            v-mountedFocus
+            v-model="currentNode.name"
+            :placeholder="currentNode.name"
+          />
+          <div v-else class="node-title" @click="clickTitle">
+            {{ currentNode.name }}
+          </div>
+        </div>
+        <div class="node-content" @click="openNodeConfig">
+          <div class="node-text" :title="currentNode.showText" v-if="currentNode.showText">
+            {{ currentNode.showText }}
+          </div>
+          <div class="node-text" v-else>
+            {{ NODE_DEFAULT_TEXT.get(NodeType.DELAY_TIMER_NODE) }}
+          </div>
+          <Icon v-if="!readonly" icon="ep:arrow-right-bold" />
+        </div>
+        <div v-if="!readonly" class="node-toolbar">
+          <div class="toolbar-icon"
+            ><Icon color="#0089ff" icon="ep:circle-close-filled" :size="18" @click="deleteNode"
+          /></div>
+        </div>
+      </div>
+
+      <!-- 传递子节点给添加节点组件。会在子节点前面添加节点 -->
+      <NodeHandler
+        v-if="currentNode"
+        v-model:child-node="currentNode.childNode"
+        :current-node="currentNode"
+      />
+    </div>
+    <DelayTimerNodeConfig
+      v-if="!readonly && currentNode"
+      ref="nodeSetting"
+      :flow-node="currentNode"
+    />
+  </div>
+</template>
+<script setup lang="ts">
+import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
+import NodeHandler from '../NodeHandler.vue'
+import { useNodeName2, useWatchNode, useTaskStatusClass } from '../node'
+import DelayTimerNodeConfig from '../nodes-config/DelayTimerNodeConfig.vue'
+defineOptions({
+  name: 'DelayTimerNode'
+})
+const props = defineProps({
+  flowNode: {
+    type: Object as () => SimpleFlowNode,
+    required: true
+  }
+})
+// 定义事件,更新父组件。
+const emits = defineEmits<{
+  'update:flowNode': [node: SimpleFlowNode | undefined]
+}>()
+// 是否只读
+const readonly = inject<Boolean>('readonly')
+// 监控节点的变化
+const currentNode = useWatchNode(props)
+// 节点名称编辑
+const { showInput, blurEvent, clickTitle } = useNodeName2(currentNode, NodeType.DELAY_TIMER_NODE)
+
+const nodeSetting = ref()
+// 打开节点配置
+const openNodeConfig = () => {
+  if (readonly) {
+    return
+  }
+  nodeSetting.value.showDelayTimerNodeConfig(currentNode.value)
+  nodeSetting.value.openDrawer()
+}
+
+// 删除节点。更新当前节点为孩子节点
+const deleteNode = () => {
+  emits('update:flowNode', currentNode.value.childNode)
+}
+</script>
+
+<style lang="scss" scoped></style>
diff --git a/src/components/SimpleProcessDesignerV2/theme/simple-process-designer.scss b/src/components/SimpleProcessDesignerV2/theme/simple-process-designer.scss
index 516756e..8cf2681 100644
--- a/src/components/SimpleProcessDesignerV2/theme/simple-process-designer.scss
+++ b/src/components/SimpleProcessDesignerV2/theme/simple-process-designer.scss
@@ -173,13 +173,16 @@
   height: 100%;
   padding-top: 32px;
   background-color: #fafafa;
+  overflow-x: auto;
+  width: 100%;
+
   .simple-process-model {
     display: flex;
     flex-direction: column;
     justify-content: center;
     align-items: center;
     transform-origin: 50% 0 0;
-    overflow: auto;
+    min-width: fit-content;
     transform: scale(1);
     transition: transform 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
     background: url(@/assets/svgs/bpm/simple-process-bg.svg) 0 0 repeat;
@@ -473,6 +476,7 @@
       .branch-node-container {
         position: relative;
         display: flex;
+        min-width: fit-content;
 
         &::before {
           position: absolute;
@@ -548,6 +552,7 @@
           background: transparent;
           border-top: 2px solid #dedede;
           border-bottom: 2px solid #dedede;
+          flex-shrink: 0;
 
           &::before {
             position: absolute;
diff --git a/src/components/UserSelectForm/index.vue b/src/components/UserSelectForm/index.vue
index 801489b..5ed99f8 100644
--- a/src/components/UserSelectForm/index.vue
+++ b/src/components/UserSelectForm/index.vue
@@ -39,7 +39,7 @@
   </Dialog>
 </template>
 <script lang="ts" setup>
-import { defaultProps, findTreeNode, handleTree } from '@/utils/tree'
+import { defaultProps, handleTree } from '@/utils/tree'
 import * as DeptApi from '@/api/system/dept'
 import * as UserApi from '@/api/system/user'
 
@@ -50,6 +50,7 @@
 const { t } = useI18n() // 国际
 const message = useMessage() // 消息弹窗
 const deptTree = ref<Tree[]>([]) // 部门树形结构化
+const deptList = ref<any[]>([]) // 保存扁平化的部门列表数据
 const userList = ref<UserApi.UserVO[]>([]) // 所有用户列表
 const filteredUserList = ref<UserApi.UserVO[]>([]) // 当前部门过滤后的用户列表
 const selectedUserIdList: any = ref([]) // 选中的用户列表
@@ -79,7 +80,9 @@
   resetForm()
 
   // 加载部门、用户列表
-  deptTree.value = handleTree(await DeptApi.getSimpleDeptList())
+  const deptData = await DeptApi.getSimpleDeptList()
+  deptList.value = deptData // 保存扁平结构的部门数据
+  deptTree.value = handleTree(deptData) // 转换成树形结构
   userList.value = await UserApi.getSimpleUserList()
 
   // 初始状态下,过滤列表等于所有用户列表
@@ -88,16 +91,31 @@
   dialogVisible.value = true
 }
 
+/** 获取指定部门及其所有子部门的ID列表 */
+const getChildDeptIds = (deptId: number, deptList: any[]): number[] => {
+  const ids = [deptId]
+  const children = deptList.filter((dept) => dept.parentId === deptId)
+  children.forEach((child) => {
+    ids.push(...getChildDeptIds(child.id, deptList))
+  })
+  return ids
+}
+
 /** 获取部门过滤后的用户列表 */
-const getUserList = async (deptId?: number) => {
+const filterUserList = async (deptId?: number) => {
   formLoading.value = true
   try {
-    // @ts-ignore
-    // TODO @芋艿:替换到 simple List 暂不支持 deptId 过滤
-    // TODO @Zqqq:这个,可以使用前端过滤么?通过 deptList 获取到 deptId 子节点,然后去 userList
-    const data = await UserApi.getUserPage({ pageSize: 100, pageNo: 1, deptId })
-    // 更新过滤后的用户列表
-    filteredUserList.value = data.list
+    if (!deptId) {
+      // 如果没有选择部门,显示所有用户
+      filteredUserList.value = [...userList.value]
+      return
+    }
+
+    // 直接使用已保存的部门列表数据进行过滤
+    const deptIds = getChildDeptIds(deptId, deptList.value)
+
+    // 过滤出这些部门下的用户
+    filteredUserList.value = userList.value.filter((user) => deptIds.includes(user.deptId))
   } finally {
     formLoading.value = false
   }
@@ -121,6 +139,7 @@
 /** 重置表单 */
 const resetForm = () => {
   deptTree.value = []
+  deptList.value = []
   userList.value = []
   filteredUserList.value = []
   selectedUserIdList.value = []
@@ -128,7 +147,7 @@
 
 /** 处理部门被点击 */
 const handleNodeClick = (row: { [key: string]: any }) => {
-  getUserList(row.id)
+  filterUserList(row.id)
 }
 
 defineExpose({ open }) // 提供 open 方法,用于打开弹窗
diff --git a/src/components/bpmnProcessDesigner/package/designer/ProcessDesigner.vue b/src/components/bpmnProcessDesigner/package/designer/ProcessDesigner.vue
index 6cbe11f..9d2fa5b 100644
--- a/src/components/bpmnProcessDesigner/package/designer/ProcessDesigner.vue
+++ b/src/components/bpmnProcessDesigner/package/designer/ProcessDesigner.vue
@@ -160,13 +160,6 @@
             <XButton preIcon="ep:refresh" @click="processRestart()" />
           </el-tooltip>
         </ElButtonGroup>
-        <XButton
-          preIcon="ep:plus"
-          title="保存模型"
-          @click="processSave"
-          :type="props.headerButtonType"
-          :disabled="simulationStatus"
-        />
       </template>
       <!-- 用于打开本地文件-->
       <input
@@ -314,6 +307,28 @@
       ['default', 'primary', 'success', 'warning', 'danger', 'info'].indexOf(value) !== -1
   }
 })
+
+// 监听value变化,重新加载流程图
+watch(
+  () => props.value,
+  (newValue) => {
+    if (newValue && bpmnModeler) {
+      createNewDiagram(newValue)
+    }
+  },
+  { immediate: true }
+)
+
+// 监听processId和processName变化
+watch(
+  [() => props.processId, () => props.processName],
+  ([newId, newName]) => {
+    if (newId && newName && !props.value) {
+      createNewDiagram(null)
+    }
+  },
+  { immediate: true }
+)
 
 provide('configGlobal', props)
 let bpmnModeler: any = null
@@ -592,16 +607,6 @@
   defaultZoom.value = newZoom
   bpmnModeler.get('canvas').zoom(defaultZoom.value)
 }
-// const processZoomTo = (newZoom = 1) => {
-//   if (newZoom < 0.2) {
-//     throw new Error('[Process Designer Warn ]: The zoom ratio cannot be less than 0.2')
-//   }
-//   if (newZoom > 4) {
-//     throw new Error('[Process Designer Warn ]: The zoom ratio cannot be greater than 4')
-//   }
-//   defaultZoom = newZoom
-//   bpmnModeler.get('canvas').zoom(newZoom)
-// }
 const processReZoom = () => {
   defaultZoom.value = 1
   bpmnModeler.get('canvas').zoom('fit-viewport', 'auto')
@@ -640,63 +645,19 @@
 }
 const previewProcessJson = () => {
   bpmnModeler.saveXML({ format: true }).then(({ xml }) => {
-    // console.log(xml, 'xml')
-
-    // const rootNode = parseXmlString(xml)
-    // console.log(rootNode, 'rootNoderootNode')
     const rootNodes = new XmlNode(XmlNodeType.Root, parseXmlString(xml))
-    // console.log(rootNodes, 'rootNodesrootNodesrootNodes')
-    // console.log(rootNodes.parent.toJsObject(), 'rootNodes.toJSON()')
-    // console.log(JSON.stringify(rootNodes.parent.toJsObject()), 'rootNodes.toJSON()')
-    // console.log(JSON.stringify(rootNodes.parent.toJSON()), 'rootNodes.toJSON()')
-
-    // const parser = new xml2js.XMLParser()
-    // let jObj = parser.parse(xml)
-    // console.log(jObj, 'jObjjObjjObjjObjjObj')
-    // const builder = new xml2js.XMLBuilder(xml)
-    // const xmlContent = builder
-    // console.log(xmlContent, 'xmlContent')
-    // console.log(xml2js, 'convertconvertconvert')
     previewResult.value = rootNodes.parent?.toJSON() as unknown as string
-    // previewResult.value = jObj
-    // previewResult.value = convert.xml2json(xml,  {explicitArray : false},{ spaces: 2 })
     previewType.value = 'json'
     previewModelVisible.value = true
   })
 }
+
 /* ------------------------------------------------ 芋道源码 methods ------------------------------------------------------ */
-const processSave = async () => {
-  // console.log(bpmnModeler, 'bpmnModelerbpmnModelerbpmnModelerbpmnModeler')
-  const { err, xml } = await bpmnModeler.saveXML()
-  // console.log(err, 'errerrerrerrerr')
-  // console.log(xml, 'xmlxmlxmlxmlxml')
-  // 读取异常时抛出异常
-  if (err) {
-    // this.$modal.msgError('保存模型失败,请重试!')
-    alert('保存模型失败,请重试!')
-    return
-  }
-  // 触发 save 事件
-  emit('save', xml)
-}
-/** 高亮显示 */
-// const highlightedCode = (previewType, previewResult) => {
-//   console.log(previewType, 'previewType, previewResult')
-//   console.log(previewResult, 'previewType, previewResult')
-//   console.log(hljs.highlight, 'hljs.highlight')
-//   const result = hljs.highlight(previewType, previewResult.value || '', true)
-//   return result.value || '&nbsp;'
-// }
-onBeforeMount(() => {
-  console.log(props, 'propspropspropsprops')
-})
 onMounted(() => {
   initBpmnModeler()
   createNewDiagram(props.value)
 })
 onBeforeUnmount(() => {
-  // this.$once('hook:beforeDestroy', () => {
-  // })
   if (bpmnModeler) bpmnModeler.destroy()
   emit('destroy', bpmnModeler)
   bpmnModeler = null
diff --git a/src/components/bpmnProcessDesigner/package/penal/PropertiesPanel.vue b/src/components/bpmnProcessDesigner/package/penal/PropertiesPanel.vue
index d2409ee..e426eb6 100644
--- a/src/components/bpmnProcessDesigner/package/penal/PropertiesPanel.vue
+++ b/src/components/bpmnProcessDesigner/package/penal/PropertiesPanel.vue
@@ -1,6 +1,6 @@
 <template>
-  <div class="process-panel__container" :style="{ width: `${width}px`, maxHeight: '700px' }">
-    <el-collapse v-model="activeTab">
+  <div class="process-panel__container" :style="{ width: `${width}px` }">
+    <el-collapse v-model="activeTab" v-if="isReady">
       <el-collapse-item name="base">
         <!-- class="panel-tab__title" -->
         <template #title>
@@ -28,7 +28,7 @@
       </el-collapse-item>
       <el-collapse-item name="task" v-if="isTaskCollapseItemShow(elementType)" key="task">
         <template #title
-          ><Icon icon="ep:checked" />{{ getTaskCollapseItemName(elementType) }}</template
+        ><Icon icon="ep:checked" />{{ getTaskCollapseItemName(elementType) }}</template
         >
         <element-task :id="elementId" :type="elementType" />
       </el-collapse-item>
@@ -119,24 +119,16 @@
 const conditionFormVisible = ref(false) // 流转条件设置
 const formVisible = ref(false) // 表单配置
 const bpmnElement = ref()
+const isReady = ref(false)
 
 provide('prefix', props.prefix)
 provide('width', props.width)
-const bpmnInstances = () => (window as any)?.bpmnInstances
 
-// 监听 props.bpmnModeler 然后 initModels
-const unwatchBpmn = watch(
-  () => props.bpmnModeler,
-  () => {
-    // 避免加载时 流程图 并未加载完成
-    if (!props.bpmnModeler) {
-      console.log('缺少props.bpmnModeler')
-      return
-    }
-
-    console.log('props.bpmnModeler 有值了!!!')
-    const w = window as any
-    w.bpmnInstances = {
+// 初始化 bpmnInstances
+const initBpmnInstances = () => {
+  if (!props.bpmnModeler) return false
+  try {
+    const instances = {
       modeler: props.bpmnModeler,
       modeling: props.bpmnModeler.get('modeling'),
       moddle: props.bpmnModeler.get('moddle'),
@@ -148,9 +140,45 @@
       selection: props.bpmnModeler.get('selection')
     }
 
-    console.log(bpmnInstances(), 'window.bpmnInstances')
-    getActiveElement()
-    unwatchBpmn()
+    // 检查所有实例是否都存在
+    const allInstancesExist = Object.values(instances).every(instance => instance)
+    if (allInstancesExist) {
+      const w = window as any
+      w.bpmnInstances = instances
+      return true
+    }
+    return false
+  } catch (error) {
+    console.error('初始化 bpmnInstances 失败:', error)
+    return false
+  }
+}
+
+const bpmnInstances = () => (window as any)?.bpmnInstances
+
+// 监听 props.bpmnModeler 然后 initModels
+const unwatchBpmn = watch(
+  () => props.bpmnModeler,
+  async () => {
+    // 避免加载时 流程图 并未加载完成
+    if (!props.bpmnModeler) {
+      console.log('缺少props.bpmnModeler')
+      return
+    }
+
+    try {
+      // 等待 modeler 初始化完成
+      await nextTick()
+      if (initBpmnInstances()) {
+        isReady.value = true
+        await nextTick()
+        getActiveElement()
+      } else {
+        console.error('modeler 实例未完全初始化')
+      }
+    } catch (error) {
+      console.error('初始化失败:', error)
+    }
   },
   {
     immediate: true
@@ -158,6 +186,8 @@
 )
 
 const getActiveElement = () => {
+  if (!isReady.value || !props.bpmnModeler) return
+
   // 初始第一个选中元素 bpmn:Process
   initFormOnChanged(null)
   props.bpmnModeler.on('import.done', (e) => {
@@ -175,8 +205,11 @@
     }
   })
 }
+
 // 初始化数据
 const initFormOnChanged = (element) => {
+  if (!isReady.value || !bpmnInstances()) return
+
   let activatedElement = element
   if (!activatedElement) {
     activatedElement =
@@ -184,32 +217,36 @@
       bpmnInstances().elementRegistry.find((el) => el.type === 'bpmn:Collaboration')
   }
   if (!activatedElement) return
-  console.log(`
-              ----------
-      select element changed:
-                id:  ${activatedElement.id}
-              type:  ${activatedElement.businessObject.$type}
-              ----------
-              `)
-  console.log('businessObject: ', activatedElement.businessObject)
-  bpmnInstances().bpmnElement = activatedElement
-  bpmnElement.value = activatedElement
-  elementId.value = activatedElement.id
-  elementType.value = activatedElement.type.split(':')[1] || ''
-  elementBusinessObject.value = JSON.parse(JSON.stringify(activatedElement.businessObject))
-  conditionFormVisible.value = !!(
-    elementType.value === 'SequenceFlow' &&
-    activatedElement.source &&
-    activatedElement.source.type.indexOf('StartEvent') === -1
-  )
-  formVisible.value = elementType.value === 'UserTask' || elementType.value === 'StartEvent'
+
+  try {
+    console.log(`
+                ----------
+        select element changed:
+                  id:  ${activatedElement.id}
+                type:  ${activatedElement.businessObject.$type}
+                ----------
+                `)
+    console.log('businessObject: ', activatedElement.businessObject)
+    bpmnInstances().bpmnElement = activatedElement
+    bpmnElement.value = activatedElement
+    elementId.value = activatedElement.id
+    elementType.value = activatedElement.type.split(':')[1] || ''
+    elementBusinessObject.value = JSON.parse(JSON.stringify(activatedElement.businessObject))
+    conditionFormVisible.value = !!(
+      elementType.value === 'SequenceFlow' &&
+      activatedElement.source &&
+      activatedElement.source.type.indexOf('StartEvent') === -1
+    )
+    formVisible.value = elementType.value === 'UserTask' || elementType.value === 'StartEvent'
+  } catch (error) {
+    console.error('初始化表单数据失败:', error)
+  }
 }
 
 onBeforeUnmount(() => {
   const w = window as any
   w.bpmnInstances = null
-  console.log(props, 'props1')
-  console.log(props.bpmnModeler, 'props.bpmnModeler1')
+  isReady.value = false
 })
 
 watch(
diff --git a/src/components/bpmnProcessDesigner/package/theme/process-designer.scss b/src/components/bpmnProcessDesigner/package/theme/process-designer.scss
index 6af945d..ac2976b 100644
--- a/src/components/bpmnProcessDesigner/package/theme/process-designer.scss
+++ b/src/components/bpmnProcessDesigner/package/theme/process-designer.scss
@@ -1,6 +1,4 @@
-@import 'bpmn-js-token-simulation/assets/css/bpmn-js-token-simulation.css';
-@import 'bpmn-js-token-simulation/assets/css/font-awesome.min.css';
-@import 'bpmn-js-token-simulation/assets/css/normalize.css';
+@use 'bpmn-js-token-simulation/assets/css/bpmn-js-token-simulation.css';
 
 // 边框被 token-simulation 样式覆盖了
 .djs-palette {
@@ -83,7 +81,7 @@
       height: 100%;
       position: relative;
       background: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdHRlcm4gaWQ9ImEiIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHBhdGggZD0iTTAgMTBoNDBNMTAgMHY0ME0wIDIwaDQwTTIwIDB2NDBNMCAzMGg0ME0zMCAwdjQwIiBmaWxsPSJub25lIiBzdHJva2U9IiNlMGUwZTAiIG9wYWNpdHk9Ii4yIi8+PHBhdGggZD0iTTQwIDBIMHY0MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZTBlMGUwIi8+PC9wYXR0ZXJuPjwvZGVmcz48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2EpIi8+PC9zdmc+')
-        repeat !important;
+      repeat !important;
       div.toggle-mode {
         display: none;
       }
@@ -97,12 +95,12 @@
         box-sizing: border-box;
       }
     }
-    svg {
-      width: 100%;
-      height: 100%;
-      min-height: 100%;
-      overflow: hidden;
-    }
+    // svg {
+    //   width: 100%;
+    //   height: 100%;
+    //   min-height: 100%;
+    //   overflow: hidden;
+    // }
   }
 }
 
diff --git a/src/directives/permission/hasPermi.ts b/src/directives/permission/hasPermi.ts
index 931f44b..0ef3c50 100644
--- a/src/directives/permission/hasPermi.ts
+++ b/src/directives/permission/hasPermi.ts
@@ -5,18 +5,10 @@
 
 export function hasPermi(app: App<Element>) {
   app.directive('hasPermi', (el, binding) => {
-    const { wsCache } = useCache()
     const { value } = binding
-    const all_permission = '*:*:*'
-    const userInfo = wsCache.get(CACHE_KEY.USER)
-    const permissions = userInfo?.permissions || []
 
     if (value && value instanceof Array && value.length > 0) {
-      const permissionFlag = value
-
-      const hasPermissions = permissions.some((permission: string) => {
-        return all_permission === permission || permissionFlag.includes(permission)
-      })
+      const hasPermissions = hasPermission(value)
 
       if (!hasPermissions) {
         el.parentNode && el.parentNode.removeChild(el)
@@ -26,3 +18,14 @@
     }
   })
 }
+
+export const hasPermission = (permission: string[]) => {
+  const { wsCache } = useCache()
+  const all_permission = '*:*:*'
+  const userInfo = wsCache.get(CACHE_KEY.USER)
+  const permissions = userInfo?.permissions || []
+
+  return permissions.some((p: string) => {
+    return all_permission === p || permission.includes(p)
+  })
+}
diff --git a/src/layout/components/Footer/src/Footer.vue b/src/layout/components/Footer/src/Footer.vue
index 62302fc..c5a1d1a 100644
--- a/src/layout/components/Footer/src/Footer.vue
+++ b/src/layout/components/Footer/src/Footer.vue
@@ -12,6 +12,10 @@
 const appStore = useAppStore()
 
 const title = computed(() => appStore.getTitle)
+
+
+// 添加当前年份计算属性
+const currentYear = computed(() => new Date().getFullYear())
 </script>
 
 <template>
@@ -19,6 +23,6 @@
     :class="prefixCls"
     class="h-[var(--app-footer-height)] bg-[var(--app-content-bg-color)] text-center leading-[var(--app-footer-height)] text-[var(--el-text-color-placeholder)] dark:bg-[var(--el-bg-color)] overflow-hidden"
   >
-    <span class="text-14px">Copyright ©2022-{{ title }}</span>
+    <span class="text-14px">Copyright ©{{ currentYear }} {{ title }}</span>
   </div>
 </template>
diff --git a/src/router/modules/remaining.ts b/src/router/modules/remaining.ts
index 6b64a95..f603ca5 100644
--- a/src/router/modules/remaining.ts
+++ b/src/router/modules/remaining.ts
@@ -351,6 +351,30 @@
           title: '查看 OA 请假',
           activeMenu: '/bpm/oa/leave'
         }
+      },
+      {
+        path: 'manager/model/create',
+        component: () => import('@/views/bpm/model/form/index.vue'),
+        name: 'BpmModelCreate',
+        meta: {
+          noCache: true,
+          hidden: true,
+          canTo: true,
+          title: '创建流程',
+          activeMenu: '/bpm/manager/model'
+        }
+      },
+      {
+        path: 'manager/model/update/:id',
+        component: () => import('@/views/bpm/model/form/index.vue'),
+        name: 'BpmModelUpdate',
+        meta: {
+          noCache: true,
+          hidden: true,
+          canTo: true,
+          title: '修改流程',
+          activeMenu: '/bpm/manager/model'
+        }
       }
     ]
   },
diff --git a/src/utils/routerHelper.ts b/src/utils/routerHelper.ts
index b65f93a..a4b2295 100644
--- a/src/utils/routerHelper.ts
+++ b/src/utils/routerHelper.ts
@@ -73,7 +73,7 @@
       noCache: !route.keepAlive,
       alwaysShow:
         route.children &&
-        route.children.length === 1 &&
+        route.children.length > 0 &&
         (route.alwaysShow !== undefined ? route.alwaysShow : true)
     } as any
     // 特殊逻辑:如果后端配置的 MenuDO.component 包含 ?,则表示需要传递参数
diff --git a/src/views/bpm/model/CategoryDraggableModel.vue b/src/views/bpm/model/CategoryDraggableModel.vue
index 7bd58d7..f3b5a42 100644
--- a/src/views/bpm/model/CategoryDraggableModel.vue
+++ b/src/views/bpm/model/CategoryDraggableModel.vue
@@ -64,6 +64,7 @@
       </div>
     </div>
   </div>
+
   <!-- 模型列表 -->
   <el-collapse-transition>
     <div v-show="isExpand">
@@ -90,7 +91,7 @@
             </div>
           </template>
         </el-table-column>
-        <el-table-column label="可见范围" prop="startUserIds" min-width="100">
+        <el-table-column label="可见范围" prop="startUserIds" min-width="150">
           <template #default="scope">
             <el-text v-if="!scope.row.startUsers || scope.row.startUsers.length === 0">
               全部可见
@@ -110,7 +111,7 @@
             </el-text>
           </template>
         </el-table-column>
-        <el-table-column label="表单信息" prop="formType" min-width="200">
+        <el-table-column label="表单信息" prop="formType" min-width="150">
           <template #default="scope">
             <el-button
               v-if="scope.row.formType === BpmModelFormType.NORMAL"
@@ -161,16 +162,6 @@
               :disabled="!isManagerUser(scope.row)"
             >
               修改
-            </el-button>
-            <el-button
-              link
-              class="!ml-5px"
-              type="primary"
-              @click="handleDesign(scope.row)"
-              v-hasPermi="['bpm:model:update']"
-              :disabled="!isManagerUser(scope.row)"
-            >
-              设计
             </el-button>
             <el-button
               link
@@ -236,11 +227,6 @@
     </template>
   </Dialog>
 
-  <!-- 弹窗:表单详情 -->
-  <Dialog title="表单详情" v-model="formDetailVisible" width="800">
-    <form-create :rule="formDetailPreview.rule" :option="formDetailPreview.option" />
-  </Dialog>
-
   <!-- 表单弹窗:添加流程模型 -->
   <ModelForm :categoryId="categoryInfo.code" ref="modelFormRef" @success="emit('success')" />
 </template>
@@ -254,7 +240,7 @@
 import * as ModelApi from '@/api/bpm/model'
 import * as FormApi from '@/api/bpm/form'
 import { setConfAndFields2 } from '@/utils/formCreate'
-import { BpmModelFormType, BpmModelType } from '@/utils/constants'
+import { BpmModelFormType } from '@/utils/constants'
 import { checkPermi } from '@/utils/permission'
 import { useUserStoreWithOut } from '@/store/modules/user'
 import { useAppStore } from '@/store/modules/app'
@@ -340,25 +326,6 @@
     // 刷新列表
     emit('success')
   } catch {}
-}
-
-/** 设计流程 */
-const handleDesign = (row: any) => {
-  if (row.type == BpmModelType.BPMN) {
-    push({
-      name: 'BpmModelEditor',
-      query: {
-        modelId: row.id
-      }
-    })
-  } else {
-    push({
-      name: 'SimpleModelDesign',
-      query: {
-        modelId: row.id
-      }
-    })
-  }
 }
 
 /** 发布流程 */
@@ -501,7 +468,14 @@
 /** 添加流程模型弹窗 */
 const modelFormRef = ref()
 const openModelForm = (type: string, id?: number) => {
-  modelFormRef.value.open(type, id)
+  if (type === 'create') {
+    push({ name: 'BpmModelCreate' })
+  } else {
+    push({
+      name: 'BpmModelUpdate',
+      params: { id }
+    })
+  }
 }
 
 watch(() => props.categoryInfo.modelList, updateModeList, { immediate: true })
diff --git a/src/views/bpm/model/ModelForm.vue b/src/views/bpm/model/ModelForm.vue
index 16d3c1d..095b0ac 100644
--- a/src/views/bpm/model/ModelForm.vue
+++ b/src/views/bpm/model/ModelForm.vue
@@ -123,29 +123,69 @@
           </el-radio>
         </el-radio-group>
       </el-form-item>
-      <el-form-item label="谁可以发起" prop="startUserIds">
+      <el-form-item label="谁可以发起" prop="startUserType">
         <el-select
-          v-model="formData.startUserIds"
-          multiple
-          placeholder="请选择可发起人,默认(不选择)则所有人都可以发起"
+          v-model="formData.startUserType"
+          placeholder="请选择谁可以发起"
+          @change="handleStartUserTypeChange"
         >
-          <el-option
-            v-for="user in userList"
-            :key="user.id"
-            :label="user.nickname"
-            :value="user.id"
-          />
+          <el-option label="全员" :value="0" />
+          <el-option label="指定人员" :value="1" />
+          <el-option label="均不可提交" :value="2" />
         </el-select>
+        <div v-if="formData.startUserType === 1" class="mt-2 flex flex-wrap gap-2">
+          <div
+            v-for="user in selectedStartUsers"
+            :key="user.id"
+            class="bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
+          >
+            <el-avatar class="!m-5px" :size="28" v-if="user.avatar" :src="user.avatar" />
+            <el-avatar class="!m-5px" :size="28" v-else>
+              {{ user.nickname.substring(0, 1) }}
+            </el-avatar>
+            {{ user.nickname }}
+            <Icon
+              icon="ep:close"
+              class="ml-2 cursor-pointer hover:text-red-500"
+              @click="handleRemoveStartUser(user)"
+            />
+          </div>
+          <el-button type="primary" link @click="openStartUserSelect">
+            <Icon icon="ep:plus" />选择人员
+          </el-button>
+        </div>
       </el-form-item>
-      <el-form-item label="流程管理员" prop="managerUserIds">
-        <el-select v-model="formData.managerUserIds" multiple placeholder="请选择流程管理员">
-          <el-option
-            v-for="user in userList"
-            :key="user.id"
-            :label="user.nickname"
-            :value="user.id"
-          />
+      <el-form-item label="流程管理员" prop="managerUserType">
+        <el-select
+          v-model="formData.managerUserType"
+          placeholder="请选择流程管理员"
+          @change="handleManagerUserTypeChange"
+        >
+          <el-option label="全员" :value="0" />
+          <el-option label="指定人员" :value="1" />
+          <el-option label="均不可提交" :value="2" />
         </el-select>
+        <div v-if="formData.managerUserType === 1" class="mt-2 flex flex-wrap gap-2">
+          <div
+            v-for="user in selectedManagerUsers"
+            :key="user.id"
+            class="bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
+          >
+            <el-avatar class="!m-5px" :size="28" v-if="user.avatar" :src="user.avatar" />
+            <el-avatar class="!m-5px" :size="28" v-else>
+              {{ user.nickname.substring(0, 1) }}
+            </el-avatar>
+            {{ user.nickname }}
+            <Icon
+              icon="ep:close"
+              class="ml-2 cursor-pointer hover:text-red-500"
+              @click="handleRemoveManagerUser(user)"
+            />
+          </div>
+          <el-button type="primary" link @click="openManagerUserSelect">
+            <Icon icon="ep:plus" />选择人员
+          </el-button>
+        </div>
       </el-form-item>
     </el-form>
     <template #footer>
@@ -153,6 +193,7 @@
       <el-button @click="dialogVisible = false">取 消</el-button>
     </template>
   </Dialog>
+  <UserSelectForm ref="userSelectFormRef" @confirm="handleUserSelectConfirm" />
 </template>
 <script lang="ts" setup>
 import { propTypes } from '@/utils/propTypes'
@@ -160,11 +201,12 @@
 import { ElMessageBox } from 'element-plus'
 import * as ModelApi from '@/api/bpm/model'
 import * as FormApi from '@/api/bpm/form'
-import { CategoryApi } from '@/api/bpm/category'
+import { CategoryApi, CategoryVO } from '@/api/bpm/category'
 import { BpmModelFormType, BpmModelType } from '@/utils/constants'
 import { UserVO } from '@/api/system/user'
 import * as UserApi from '@/api/system/user'
 import { useUserStoreWithOut } from '@/store/modules/user'
+import { FormVO } from '@/api/bpm/form'
 
 defineOptions({ name: 'ModelForm' })
 
@@ -178,7 +220,7 @@
 const dialogTitle = ref('') // 弹窗的标题
 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
 const formType = ref('') // 表单的类型:create - 新增;update - 修改
-const formData = ref({
+const formData: any = ref({
   id: undefined,
   name: '',
   key: '',
@@ -191,6 +233,8 @@
   formCustomCreatePath: '',
   formCustomViewPath: '',
   visible: true,
+  startUserType: undefined,
+  managerUserType: undefined,
   startUserIds: [],
   managerUserIds: []
 })
@@ -208,9 +252,13 @@
   managerUserIds: [{ required: true, message: '流程管理员不能为空', trigger: 'blur' }]
 })
 const formRef = ref() // 表单 Ref
-const formList = ref([]) // 流程表单的下拉框的数据
-const categoryList = ref([]) // 流程分类列表
+const formList = ref<FormVO[]>([]) // 流程表单的下拉框的数据
+const categoryList = ref<CategoryVO[]>([]) // 流程分类列表
 const userList = ref<UserVO[]>([]) // 用户列表
+const selectedStartUsers = ref<UserVO[]>([]) // 已选择的发起人列表
+const selectedManagerUsers = ref<UserVO[]>([]) // 已选择的管理员列表
+const userSelectFormRef = ref() // 用户选择弹窗 ref
+const currentSelectType = ref<'start' | 'manager'>('start') // 当前选择的是发起人还是管理员
 
 /** 打开弹窗 */
 const open = async (type: string, id?: string) => {
@@ -225,6 +273,19 @@
       formData.value = await ModelApi.getModel(id)
     } finally {
       formLoading.value = false
+    }
+    // 加载数据时,根据已有的用户ID列表初始化已选用户
+    if (formData.value.startUserIds?.length) {
+      formData.value.startUserType = 1
+      selectedStartUsers.value = userList.value.filter((user) =>
+        formData.value.startUserIds.includes(user.id)
+      )
+    }
+    if (formData.value.managerUserIds?.length) {
+      formData.value.managerUserType = 1
+      selectedManagerUsers.value = userList.value.filter((user) =>
+        formData.value.managerUserIds.includes(user.id)
+      )
     }
   } else {
     formData.value.managerUserIds.push(userStore.getUser.id)
@@ -257,9 +318,9 @@
       // 提示,引导用户做后续的操作
       await ElMessageBox.alert(
         '<strong>新建模型成功!</strong>后续需要执行如下 2 个步骤:' +
-          '<div>1. 点击【设计流程】按钮,绘制流程图</div>' +
-          '<div>2. 点击【发布流程】按钮,完成流程的最终发布</div>' +
-          '另外,每次流程修改后,都需要点击【发布流程】按钮,才能正式生效!!!',
+        '<div>1. 点击【设计流程】按钮,绘制流程图</div>' +
+        '<div>2. 点击【发布流程】按钮,完成流程的最终发布</div>' +
+        '另外,每次流程修改后,都需要点击【发布流程】按钮,才能正式生效!!!',
         '重要提示',
         {
           dangerouslyUseHTMLString: true,
@@ -293,9 +354,87 @@
     formCustomCreatePath: '',
     formCustomViewPath: '',
     visible: true,
+    startUserType: undefined,
+    managerUserType: undefined,
     startUserIds: [],
     managerUserIds: []
   }
   formRef.value?.resetFields()
+  selectedStartUsers.value = []
+  selectedManagerUsers.value = []
+}
+
+/** 处理发起人类型变化 */
+const handleStartUserTypeChange = (value: number) => {
+  if (value !== 1) {
+    selectedStartUsers.value = []
+    formData.value.startUserIds = []
+  }
+}
+
+/** 处理管理员类型变化 */
+const handleManagerUserTypeChange = (value: number) => {
+  if (value !== 1) {
+    selectedManagerUsers.value = []
+    formData.value.managerUserIds = []
+  }
+}
+
+/** 打开发起人选择 */
+const openStartUserSelect = () => {
+  currentSelectType.value = 'start'
+  userSelectFormRef.value.open(0, selectedStartUsers.value)
+}
+
+/** 打开管理员选择 */
+const openManagerUserSelect = () => {
+  currentSelectType.value = 'manager'
+  userSelectFormRef.value.open(0, selectedManagerUsers.value)
+}
+
+/** 处理用户选择确认 */
+const handleUserSelectConfirm = (_, users: UserVO[]) => {
+  if (currentSelectType.value === 'start') {
+    selectedStartUsers.value = users
+    formData.value.startUserIds = users.map((u) => u.id)
+  } else {
+    selectedManagerUsers.value = users
+    formData.value.managerUserIds = users.map((u) => u.id)
+  }
+}
+
+/** 移除发起人 */
+const handleRemoveStartUser = (user: UserVO) => {
+  selectedStartUsers.value = selectedStartUsers.value.filter((u) => u.id !== user.id)
+  formData.value.startUserIds = formData.value.startUserIds.filter((id: number) => id !== user.id)
+}
+
+/** 移除管理员 */
+const handleRemoveManagerUser = (user: UserVO) => {
+  selectedManagerUsers.value = selectedManagerUsers.value.filter((u) => u.id !== user.id)
+  formData.value.managerUserIds = formData.value.managerUserIds.filter(
+    (id: number) => id !== user.id
+  )
 }
 </script>
+
+<style lang="scss" scoped>
+.bg-gray-100 {
+  background-color: #f5f7fa;
+  transition: all 0.3s;
+
+  &:hover {
+    background-color: #e6e8eb;
+  }
+
+  .ep-close {
+    font-size: 14px;
+    color: #909399;
+    transition: color 0.3s;
+
+    &:hover {
+      color: #f56c6c;
+    }
+  }
+}
+</style>
diff --git a/src/views/bpm/model/editor/index.vue b/src/views/bpm/model/editor/index.vue
index 1a41a50..37eff73 100644
--- a/src/views/bpm/model/editor/index.vue
+++ b/src/views/bpm/model/editor/index.vue
@@ -3,7 +3,6 @@
     <!-- 流程设计器,负责绘制流程等 -->
     <MyProcessDesigner
       key="designer"
-      v-if="xmlString !== undefined"
       v-model="xmlString"
       :value="xmlString"
       v-bind="controlForm"
@@ -11,12 +10,14 @@
       ref="processDesigner"
       @init-finished="initModeler"
       :additionalModel="controlForm.additionalModel"
+      :model="model"
       @save="save"
     />
     <!-- 流程属性器,负责编辑每个流程节点的属性 -->
     <MyProcessPenal
+      v-if="isModelerReady && modeler"
       key="penal"
-      :bpmnModeler="modeler as any"
+      :bpmnModeler="modeler"
       :prefix="controlForm.prefix"
       class="process-panel"
       :model="model"
@@ -31,12 +32,17 @@
 // 自定义左侧菜单(修改 默认任务 为 用户任务)
 import CustomPaletteProvider from '@/components/bpmnProcessDesigner/package/designer/plugins/palette'
 import * as ModelApi from '@/api/bpm/model'
-import { getForm, FormVO } from '@/api/bpm/form'
 
 defineOptions({ name: 'BpmModelEditor' })
 
-const router = useRouter() // 路由
-const { query } = useRoute() // 路由的查询
+const props = defineProps<{
+  modelId?: string
+  modelKey?: string
+  modelName?: string
+  value?: string
+}>()
+
+const emit = defineEmits(['success', 'init-finished'])
 const message = useMessage() // 国际化
 
 // 表单信息
@@ -45,8 +51,10 @@
 provide('formFields', formFields)
 provide('formType', formType)
 
-const xmlString = ref(undefined) // BPMN XML
-const modeler = ref(null) // BPMN Modeler
+const xmlString = ref<string>('') // BPMN XML
+const modeler = shallowRef() // BPMN Modeler
+const processDesigner = ref()
+const isModelerReady = ref(false)
 const controlForm = ref({
   simulation: true,
   labelEditing: false,
@@ -57,73 +65,215 @@
 })
 const model = ref<ModelApi.ModelVO>() // 流程模型的信息
 
+// 初始化 bpmnInstances
+const initBpmnInstances = () => {
+  if (!modeler.value) return false
+  try {
+    const instances = {
+      modeler: modeler.value,
+      modeling: modeler.value.get('modeling'),
+      moddle: modeler.value.get('moddle'),
+      eventBus: modeler.value.get('eventBus'),
+      bpmnFactory: modeler.value.get('bpmnFactory'),
+      elementFactory: modeler.value.get('elementFactory'),
+      elementRegistry: modeler.value.get('elementRegistry'),
+      replace: modeler.value.get('replace'),
+      selection: modeler.value.get('selection')
+    }
+
+    // 检查所有实例是否都存在
+    return Object.values(instances).every((instance) => instance)
+  } catch (error) {
+    console.error('初始化 bpmnInstances 失败:', error)
+    return false
+  }
+}
+
 /** 初始化 modeler */
-const initModeler = (item) => {
-  setTimeout(() => {
+const initModeler = async (item) => {
+  try {
     modeler.value = item
-  }, 10)
+    // 等待 modeler 初始化完成
+    await nextTick()
+
+    // 确保 modeler 的所有实例都已经准备好
+    if (initBpmnInstances()) {
+      isModelerReady.value = true
+      emit('init-finished')
+
+      // 初始化完成后,设置初始值
+      if (props.modelId) {
+        // 编辑模式
+        const data = await ModelApi.getModel(props.modelId)
+        model.value = {
+          ...data,
+          bpmnXml: undefined // 清空 bpmnXml 属性
+        }
+        xmlString.value = data.bpmnXml || getDefaultBpmnXml(data.key, data.name)
+      } else if (props.modelKey && props.modelName) {
+        // 新建模式
+        xmlString.value = props.value || getDefaultBpmnXml(props.modelKey, props.modelName)
+        model.value = {
+          key: props.modelKey,
+          name: props.modelName
+        } as ModelApi.ModelVO
+      }
+
+      // 导入XML并刷新视图
+      await nextTick()
+      try {
+        await modeler.value.importXML(xmlString.value)
+        if (processDesigner.value?.refresh) {
+          processDesigner.value.refresh()
+        }
+      } catch (error) {
+        console.error('导入XML失败:', error)
+      }
+    } else {
+      console.error('modeler 实例未完全初始化')
+    }
+  } catch (error) {
+    console.error('初始化 modeler 失败:', error)
+  }
+}
+
+/** 获取默认的BPMN XML */
+const getDefaultBpmnXml = (key: string, name: string) => {
+  return `<?xml version="1.0" encoding="UTF-8"?>
+<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.activiti.org/processdef">
+  <process id="${key}" name="${name}" isExecutable="true" />
+  <bpmndi:BPMNDiagram id="BPMNDiagram">
+    <bpmndi:BPMNPlane id="${key}_di" bpmnElement="${key}" />
+  </bpmndi:BPMNDiagram>
+</definitions>`
 }
 
 /** 添加/修改模型 */
 const save = async (bpmnXml: string) => {
-  const data = {
-    ...model.value,
-    bpmnXml: bpmnXml // bpmnXml 只是初始化流程图,后续修改无法通过它获得
-  } as unknown as ModelApi.ModelVO
-  // 提交
-  if (data.id) {
-    await ModelApi.updateModelBpmn(data)
-    message.success('修改成功')
-  } else {
-    await ModelApi.updateModelBpmn(data)
-    message.success('新增成功')
+  try {
+    xmlString.value = bpmnXml
+    if (props.modelId) {
+      // 编辑模式
+      const data = {
+        ...model.value,
+        bpmnXml: bpmnXml
+      } as unknown as ModelApi.ModelVO
+      await ModelApi.updateModelBpmn(data)
+      emit('success')
+    } else {
+      // 新建模式,直接返回XML
+      emit('success', bpmnXml)
+    }
+  } catch (error) {
+    console.error('保存失败:', error)
+    message.error('保存失败')
   }
-  // 跳转回去
-  close()
 }
 
-/** 关闭按钮 */
-const close = () => {
-  router.push({ path: '/bpm/manager/model' })
+// 监听 key、name 和 value 的变化
+watch(
+  [() => props.modelKey, () => props.modelName, () => props.value],
+  async ([newKey, newName, newValue]) => {
+    if (!props.modelId && isModelerReady.value) {
+      let shouldRefresh = false
+
+      if (newKey && newName) {
+        const newXml = newValue || getDefaultBpmnXml(newKey, newName)
+        if (newXml !== xmlString.value) {
+          xmlString.value = newXml
+          shouldRefresh = true
+        }
+        model.value = {
+          ...model.value,
+          key: newKey,
+          name: newName
+        } as ModelApi.ModelVO
+      } else if (newValue && newValue !== xmlString.value) {
+        xmlString.value = newValue
+        shouldRefresh = true
+      }
+
+      if (shouldRefresh) {
+        // 确保更新后重新渲染
+        await nextTick()
+        if (processDesigner.value?.refresh) {
+          try {
+            await modeler.value?.importXML(xmlString.value)
+            processDesigner.value.refresh()
+          } catch (error) {
+            console.error('导入XML失败:', error)
+          }
+        }
+      }
+    }
+  },
+  { deep: true }
+)
+
+// 在组件卸载时清理
+onBeforeUnmount(() => {
+  isModelerReady.value = false
+  modeler.value = null
+  // 清理全局实例
+  const w = window as any
+  if (w.bpmnInstances) {
+    w.bpmnInstances = null
+  }
+})
+
+/** 获取 XML 字符串 */
+const saveXML = async () => {
+  if (!modeler.value) {
+    return { xml: xmlString.value }
+  }
+  try {
+    const result = await modeler.value.saveXML({ format: true })
+    xmlString.value = result.xml
+    return result
+  } catch (error) {
+    console.error('获取XML失败:', error)
+    return { xml: xmlString.value }
+  }
 }
 
-/** 初始化 */
-onMounted(async () => {
-  const modelId = query.modelId as unknown as number
-  if (!modelId) {
-    message.error('缺少模型 modelId 编号')
-    return
+/** 获取SVG字符串 */
+const saveSVG = async () => {
+  if (!modeler.value) {
+    return { svg: undefined }
   }
-  // 查询模型
-  const data = await ModelApi.getModel(modelId)
-  if (!data.bpmnXml) {
-    // 首次创建的 Model 模型,它是没有 bpmnXml,此时需要给它一个默认的
-    data.bpmnXml = ` <?xml version="1.0" encoding="UTF-8"?>
-<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.activiti.org/processdef">
-  <process id="${data.key}" name="${data.name}" isExecutable="true" />
-  <bpmndi:BPMNDiagram id="BPMNDiagram">
-    <bpmndi:BPMNPlane id="${data.key}_di" bpmnElement="${data.key}" />
-  </bpmndi:BPMNDiagram>
-</definitions>`
+  try {
+    return await modeler.value.saveSVG()
+  } catch (error) {
+    console.error('获取SVG失败:', error)
+    return { svg: undefined }
   }
+}
 
-  formType.value = data.formType
-  if (data.formType === 10) {
-    const bpmnForm = (await getForm(data.formId)) as unknown as FormVO
-    formFields.value = bpmnForm?.fields
+/** 刷新视图 */
+const refresh = async () => {
+  if (processDesigner.value?.refresh && modeler.value) {
+    try {
+      await modeler.value.importXML(xmlString.value)
+      processDesigner.value.refresh()
+    } catch (error) {
+      console.error('刷新视图失败:', error)
+    }
   }
+}
 
-  model.value = {
-    ...data,
-    bpmnXml: undefined // 清空 bpmnXml 属性
-  }
-  xmlString.value = data.bpmnXml
+// 暴露必要的属性和方法给父组件
+defineExpose({
+  modeler,
+  isModelerReady,
+  saveXML,
+  saveSVG,
+  refresh
 })
 </script>
 <style lang="scss">
 .process-panel__container {
   position: absolute;
-  top: 90px;
-  right: 60px;
+  top: 172px;
+  right: 70px;
 }
 </style>
diff --git a/src/views/bpm/model/form/BasicInfo.vue b/src/views/bpm/model/form/BasicInfo.vue
new file mode 100644
index 0000000..0359ea8
--- /dev/null
+++ b/src/views/bpm/model/form/BasicInfo.vue
@@ -0,0 +1,301 @@
+<template>
+  <el-form ref="formRef" :model="modelData" :rules="rules" label-width="120px" class="mt-20px">
+    <el-form-item label="流程标识" prop="key" class="mb-20px">
+      <div class="flex items-center">
+        <el-input
+          class="!w-440px"
+          v-model="modelData.key"
+          :disabled="!!modelData.id"
+          placeholder="请输入流标标识"
+        />
+        <el-tooltip
+          class="item"
+          :content="modelData.id ? '流程标识不可修改!' : '新建后,流程标识不可修改!'"
+          effect="light"
+          placement="top"
+        >
+          <Icon icon="ep:question-filled" class="ml-5px" />
+        </el-tooltip>
+      </div>
+    </el-form-item>
+    <el-form-item label="流程名称" prop="name" class="mb-20px">
+      <el-input
+        v-model="modelData.name"
+        :disabled="!!modelData.id"
+        clearable
+        placeholder="请输入流程名称"
+      />
+    </el-form-item>
+    <el-form-item label="流程分类" prop="category" class="mb-20px">
+      <el-select
+        class="!w-full"
+        v-model="modelData.category"
+        clearable
+        placeholder="请选择流程分类"
+      >
+        <el-option
+          v-for="category in categoryList"
+          :key="category.code"
+          :label="category.name"
+          :value="category.code"
+        />
+      </el-select>
+    </el-form-item>
+    <el-form-item label="流程图标" prop="icon" class="mb-20px">
+      <UploadImg v-model="modelData.icon" :limit="1" height="64px" width="64px" />
+    </el-form-item>
+    <el-form-item label="流程描述" prop="description" class="mb-20px">
+      <el-input v-model="modelData.description" clearable type="textarea" />
+    </el-form-item>
+    <el-form-item label="流程类型" prop="type" class="mb-20px">
+      <el-radio-group v-model="modelData.type">
+        <el-radio
+          v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_TYPE)"
+          :key="dict.value"
+          :value="dict.value"
+        >
+          {{ dict.label }}
+        </el-radio>
+      </el-radio-group>
+    </el-form-item>
+    <el-form-item label="是否可见" prop="visible" class="mb-20px">
+      <el-radio-group v-model="modelData.visible">
+        <el-radio
+          v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
+          :key="dict.value"
+          :value="dict.value"
+        >
+          {{ dict.label }}
+        </el-radio>
+      </el-radio-group>
+    </el-form-item>
+    <el-form-item label="谁可以发起" prop="startUserType" class="mb-20px">
+      <el-select
+        v-model="modelData.startUserType"
+        placeholder="请选择谁可以发起"
+        @change="handleStartUserTypeChange"
+      >
+        <el-option label="全员" :value="0" />
+        <el-option label="指定人员" :value="1" />
+        <el-option label="均不可提交" :value="2" />
+      </el-select>
+      <div v-if="modelData.startUserType === 1" class="mt-2 flex flex-wrap gap-2">
+        <div
+          v-for="user in selectedStartUsers"
+          :key="user.id"
+          class="bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
+        >
+          <el-avatar class="!m-5px" :size="28" v-if="user.avatar" :src="user.avatar" />
+          <el-avatar class="!m-5px" :size="28" v-else>
+            {{ user.nickname.substring(0, 1) }}
+          </el-avatar>
+          {{ user.nickname }}
+          <Icon
+            icon="ep:close"
+            class="ml-2 cursor-pointer hover:text-red-500"
+            @click="handleRemoveStartUser(user)"
+          />
+        </div>
+        <el-button type="primary" link @click="openStartUserSelect">
+          <Icon icon="ep:plus" />选择人员
+        </el-button>
+      </div>
+    </el-form-item>
+    <el-form-item label="流程管理员" prop="managerUserType" class="mb-20px">
+      <el-select
+        v-model="modelData.managerUserType"
+        placeholder="请选择流程管理员"
+        @change="handleManagerUserTypeChange"
+      >
+        <el-option label="全员" :value="0" />
+        <el-option label="指定人员" :value="1" />
+        <el-option label="均不可提交" :value="2" />
+      </el-select>
+      <div v-if="modelData.managerUserType === 1" class="mt-2 flex flex-wrap gap-2">
+        <div
+          v-for="user in selectedManagerUsers"
+          :key="user.id"
+          class="bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
+        >
+          <el-avatar class="!m-5px" :size="28" v-if="user.avatar" :src="user.avatar" />
+          <el-avatar class="!m-5px" :size="28" v-else>
+            {{ user.nickname.substring(0, 1) }}
+          </el-avatar>
+          {{ user.nickname }}
+          <Icon
+            icon="ep:close"
+            class="ml-2 cursor-pointer hover:text-red-500"
+            @click="handleRemoveManagerUser(user)"
+          />
+        </div>
+        <el-button type="primary" link @click="openManagerUserSelect">
+          <Icon icon="ep:plus" />选择人员
+        </el-button>
+      </div>
+    </el-form-item>
+  </el-form>
+
+  <!-- 用户选择弹窗 -->
+  <UserSelectForm ref="userSelectFormRef" @confirm="handleUserSelectConfirm" />
+</template>
+
+<script lang="ts" setup>
+import { DICT_TYPE, getBoolDictOptions, getIntDictOptions } from '@/utils/dict'
+import { UserVO } from '@/api/system/user'
+
+const props = defineProps({
+  modelValue: {
+    type: Object,
+    required: true
+  },
+  categoryList: {
+    type: Array,
+    required: true
+  },
+  userList: {
+    type: Array,
+    required: true
+  }
+})
+
+const emit = defineEmits(['update:modelValue'])
+
+const formRef = ref()
+const selectedStartUsers = ref<UserVO[]>([])
+const selectedManagerUsers = ref<UserVO[]>([])
+const userSelectFormRef = ref()
+const currentSelectType = ref<'start' | 'manager'>('start')
+
+const rules = {
+  name: [{ required: true, message: '流程名称不能为空', trigger: 'blur' }],
+  key: [{ required: true, message: '流程标识不能为空', trigger: 'blur' }],
+  category: [{ required: true, message: '流程分类不能为空', trigger: 'blur' }],
+  icon: [{ required: true, message: '流程图标不能为空', trigger: 'blur' }],
+  type: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }],
+  visible: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }],
+  managerUserIds: [{ required: true, message: '流程管理员不能为空', trigger: 'blur' }]
+}
+
+// 创建本地数据副本
+const modelData = computed({
+  get: () => props.modelValue,
+  set: (val) => emit('update:modelValue', val)
+})
+
+// 初始化选中的用户
+watch(
+  () => props.modelValue,
+  (newVal) => {
+    if (newVal.startUserIds?.length) {
+      selectedStartUsers.value = props.userList.filter((user: UserVO) =>
+        newVal.startUserIds.includes(user.id)
+      ) as UserVO[]
+    }
+    if (newVal.managerUserIds?.length) {
+      selectedManagerUsers.value = props.userList.filter((user: UserVO) =>
+        newVal.managerUserIds.includes(user.id)
+      ) as UserVO[]
+    }
+  },
+  { immediate: true }
+)
+
+/** 打开发起人选择 */
+const openStartUserSelect = () => {
+  currentSelectType.value = 'start'
+  userSelectFormRef.value.open(0, selectedStartUsers.value)
+}
+
+/** 打开管理员选择 */
+const openManagerUserSelect = () => {
+  currentSelectType.value = 'manager'
+  userSelectFormRef.value.open(0, selectedManagerUsers.value)
+}
+
+/** 处理用户选择确认 */
+const handleUserSelectConfirm = (_, users: UserVO[]) => {
+  if (currentSelectType.value === 'start') {
+    selectedStartUsers.value = users
+    emit('update:modelValue', {
+      ...modelData.value,
+      startUserIds: users.map((u) => u.id)
+    })
+  } else {
+    selectedManagerUsers.value = users
+    emit('update:modelValue', {
+      ...modelData.value,
+      managerUserIds: users.map((u) => u.id)
+    })
+  }
+}
+
+/** 处理发起人类型变化 */
+const handleStartUserTypeChange = (value: number) => {
+  if (value !== 1) {
+    selectedStartUsers.value = []
+    emit('update:modelValue', {
+      ...modelData.value,
+      startUserIds: []
+    })
+  }
+}
+
+/** 处理管理员类型变化 */
+const handleManagerUserTypeChange = (value: number) => {
+  if (value !== 1) {
+    selectedManagerUsers.value = []
+    emit('update:modelValue', {
+      ...modelData.value,
+      managerUserIds: []
+    })
+  }
+}
+
+/** 移除发起人 */
+const handleRemoveStartUser = (user: UserVO) => {
+  selectedStartUsers.value = selectedStartUsers.value.filter((u) => u.id !== user.id)
+  emit('update:modelValue', {
+    ...modelData.value,
+    startUserIds: modelData.value.startUserIds.filter((id: number) => id !== user.id)
+  })
+}
+
+/** 移除管理员 */
+const handleRemoveManagerUser = (user: UserVO) => {
+  selectedManagerUsers.value = selectedManagerUsers.value.filter((u) => u.id !== user.id)
+  emit('update:modelValue', {
+    ...modelData.value,
+    managerUserIds: modelData.value.managerUserIds.filter((id: number) => id !== user.id)
+  })
+}
+
+/** 表单校验 */
+const validate = async () => {
+  await formRef.value?.validate()
+}
+
+defineExpose({
+  validate
+})
+</script>
+
+<style lang="scss" scoped>
+.bg-gray-100 {
+  background-color: #f5f7fa;
+  transition: all 0.3s;
+
+  &:hover {
+    background-color: #e6e8eb;
+  }
+
+  .ep-close {
+    font-size: 14px;
+    color: #909399;
+    transition: color 0.3s;
+
+    &:hover {
+      color: #f56c6c;
+    }
+  }
+}
+</style>
diff --git a/src/views/bpm/model/form/FormDesign.vue b/src/views/bpm/model/form/FormDesign.vue
new file mode 100644
index 0000000..98aee6d
--- /dev/null
+++ b/src/views/bpm/model/form/FormDesign.vue
@@ -0,0 +1,137 @@
+<template>
+  <el-form ref="formRef" :model="modelData" :rules="rules" label-width="120px" class="mt-20px">
+    <el-form-item label="表单类型" prop="formType" class="mb-20px">
+      <el-radio-group v-model="modelData.formType">
+        <el-radio
+          v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_FORM_TYPE)"
+          :key="dict.value"
+          :value="dict.value"
+        >
+          {{ dict.label }}
+        </el-radio>
+      </el-radio-group>
+    </el-form-item>
+    <el-form-item v-if="modelData.formType === 10" label="流程表单" prop="formId">
+      <el-select v-model="modelData.formId" clearable style="width: 100%">
+        <el-option v-for="form in formList" :key="form.id" :label="form.name" :value="form.id" />
+      </el-select>
+    </el-form-item>
+    <el-form-item v-if="modelData.formType === 20" label="表单提交路由" prop="formCustomCreatePath">
+      <el-input
+        v-model="modelData.formCustomCreatePath"
+        placeholder="请输入表单提交路由"
+        style="width: 330px"
+      />
+      <el-tooltip
+        class="item"
+        content="自定义表单的提交路径,使用 Vue 的路由地址,例如说:bpm/oa/leave/create.vue"
+        effect="light"
+        placement="top"
+      >
+        <Icon icon="ep:question" class="ml-5px" />
+      </el-tooltip>
+    </el-form-item>
+    <el-form-item v-if="modelData.formType === 20" label="表单查看地址" prop="formCustomViewPath">
+      <el-input
+        v-model="modelData.formCustomViewPath"
+        placeholder="请输入表单查看的组件地址"
+        style="width: 330px"
+      />
+      <el-tooltip
+        class="item"
+        content="自定义表单的查看组件地址,使用 Vue 的组件地址,例如说:bpm/oa/leave/detail.vue"
+        effect="light"
+        placement="top"
+      >
+        <Icon icon="ep:question" class="ml-5px" />
+      </el-tooltip>
+    </el-form-item>
+    <!-- 表单预览 -->
+    <div
+      v-if="modelData.formType === 10 && modelData.formId && formPreview.rule.length > 0"
+      class="mt-20px"
+    >
+      <div class="flex items-center mb-15px">
+        <div class="h-15px w-4px bg-[#1890ff] mr-10px"></div>
+        <span class="text-15px font-bold">表单预览</span>
+      </div>
+      <form-create
+        v-model="formPreview.formData"
+        :rule="formPreview.rule"
+        :option="formPreview.option"
+      />
+    </div>
+  </el-form>
+</template>
+
+<script lang="ts" setup>
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import * as FormApi from '@/api/bpm/form'
+import { setConfAndFields2 } from '@/utils/formCreate'
+
+const props = defineProps({
+  modelValue: {
+    type: Object,
+    required: true
+  },
+  formList: {
+    type: Array,
+    required: true
+  }
+})
+
+const emit = defineEmits(['update:modelValue'])
+
+const formRef = ref()
+
+// 创建本地数据副本
+const modelData = computed({
+  get: () => props.modelValue,
+  set: (val) => emit('update:modelValue', val)
+})
+
+// 表单预览数据
+const formPreview = ref({
+  formData: {},
+  rule: [],
+  option: {
+    submitBtn: false,
+    resetBtn: false,
+    formData: {}
+  }
+})
+
+// 监听表单ID变化,加载表单数据
+watch(
+  () => modelData.value.formId,
+  async (newFormId) => {
+    if (newFormId && modelData.value.formType === 10) {
+      const data = await FormApi.getForm(newFormId)
+      setConfAndFields2(formPreview.value, data.conf, data.fields)
+      // 设置只读
+      formPreview.value.rule.forEach((item: any) => {
+        item.props = { ...item.props, disabled: true }
+      })
+    } else {
+      formPreview.value.rule = []
+    }
+  },
+  { immediate: true }
+)
+
+const rules = {
+  formType: [{ required: true, message: '表单类型不能为空', trigger: 'blur' }],
+  formId: [{ required: true, message: '流程表单不能为空', trigger: 'blur' }],
+  formCustomCreatePath: [{ required: true, message: '表单提交路由不能为空', trigger: 'blur' }],
+  formCustomViewPath: [{ required: true, message: '表单查看地址不能为空', trigger: 'blur' }]
+}
+
+/** 表单校验 */
+const validate = async () => {
+  await formRef.value?.validate()
+}
+
+defineExpose({
+  validate
+})
+</script>
diff --git a/src/views/bpm/model/form/ProcessDesign.vue b/src/views/bpm/model/form/ProcessDesign.vue
new file mode 100644
index 0000000..40d35ab
--- /dev/null
+++ b/src/views/bpm/model/form/ProcessDesign.vue
@@ -0,0 +1,235 @@
+<template>
+  <!-- BPMN设计器 -->
+  <template v-if="modelData.type === BpmModelType.BPMN">
+    <BpmModelEditor
+      v-if="showDesigner"
+      :model-id="modelData.id"
+      :model-key="modelData.key"
+      :model-name="modelData.name"
+      :value="currentBpmnXml"
+      ref="bpmnEditorRef"
+      @success="handleDesignSuccess"
+      @init-finished="handleEditorInit"
+    />
+  </template>
+
+  <!-- Simple设计器 -->
+  <template v-else>
+    <SimpleModelDesign
+      v-if="showDesigner"
+      :model-id="modelData.id"
+      :model-key="modelData.key"
+      :model-name="modelData.name"
+      :start-user-ids="modelData.startUserIds"
+      :value="currentSimpleModel"
+      ref="simpleEditorRef"
+      @success="handleDesignSuccess"
+      @init-finished="handleEditorInit"
+    />
+  </template>
+</template>
+
+<script lang="ts" setup>
+import { BpmModelType } from '@/utils/constants'
+import BpmModelEditor from '../editor/index.vue'
+import SimpleModelDesign from '../../simple/SimpleModelDesign.vue'
+
+const props = defineProps({
+  modelValue: {
+    type: Object,
+    required: true
+  }
+})
+
+const emit = defineEmits(['update:modelValue', 'success'])
+
+const bpmnEditorRef = ref()
+const simpleEditorRef = ref()
+const isEditorInitialized = ref(false)
+
+// 创建本地数据副本
+const modelData = computed({
+  get: () => props.modelValue,
+  set: (val) => emit('update:modelValue', val)
+})
+
+// 保存当前的流程XML或数据
+const currentBpmnXml = ref('')
+const currentSimpleModel = ref('')
+
+// 初始化或更新当前的XML数据
+const initOrUpdateXmlData = () => {
+  if (modelData.value) {
+    if (modelData.value.type === BpmModelType.BPMN) {
+      currentBpmnXml.value = modelData.value.bpmnXml || ''
+    } else {
+      currentSimpleModel.value = modelData.value.simpleModel || ''
+    }
+  }
+}
+
+// 监听modelValue的变化,更新数据
+watch(
+  () => props.modelValue,
+  (newVal) => {
+    if (newVal) {
+      if (newVal.type === BpmModelType.BPMN) {
+        if (newVal.bpmnXml && newVal.bpmnXml !== currentBpmnXml.value) {
+          currentBpmnXml.value = newVal.bpmnXml
+          // 如果编辑器已经初始化,刷新视图
+          if (isEditorInitialized.value && bpmnEditorRef.value?.refresh) {
+            nextTick(() => {
+              bpmnEditorRef.value.refresh()
+            })
+          }
+        }
+      } else {
+        if (newVal.simpleModel && newVal.simpleModel !== currentSimpleModel.value) {
+          currentSimpleModel.value = newVal.simpleModel
+          // 如果编辑器已经初始化,刷新视图
+          if (isEditorInitialized.value && simpleEditorRef.value?.refresh) {
+            nextTick(() => {
+              simpleEditorRef.value.refresh()
+            })
+          }
+        }
+      }
+    }
+  },
+  { immediate: true, deep: true }
+)
+
+/** 编辑器初始化完成的回调 */
+const handleEditorInit = async () => {
+  isEditorInitialized.value = true
+
+  // 等待下一个tick,确保编辑器已经准备好
+  await nextTick()
+
+  // 初始化完成后,设置初始值
+  if (modelData.value.type === BpmModelType.BPMN) {
+    if (modelData.value.bpmnXml) {
+      currentBpmnXml.value = modelData.value.bpmnXml
+      if (bpmnEditorRef.value?.refresh) {
+        await nextTick()
+        bpmnEditorRef.value.refresh()
+      }
+    }
+  } else {
+    if (modelData.value.simpleModel) {
+      currentSimpleModel.value = modelData.value.simpleModel
+      if (simpleEditorRef.value?.refresh) {
+        await nextTick()
+        simpleEditorRef.value.refresh()
+      }
+    }
+  }
+}
+
+/** 获取当前流程数据 */
+const getProcessData = async () => {
+  try {
+    if (modelData.value.type === BpmModelType.BPMN) {
+      if (!bpmnEditorRef.value || !isEditorInitialized.value) {
+        return currentBpmnXml.value || undefined
+      }
+      const { xml } = await bpmnEditorRef.value.saveXML()
+      if (xml) {
+        currentBpmnXml.value = xml
+        return xml
+      }
+    } else {
+      if (!simpleEditorRef.value || !isEditorInitialized.value) {
+        return currentSimpleModel.value || undefined
+      }
+      const flowData = await simpleEditorRef.value.getCurrentFlowData()
+      if (flowData) {
+        currentSimpleModel.value = flowData
+        return flowData
+      }
+    }
+    return modelData.value.type === BpmModelType.BPMN
+      ? currentBpmnXml.value
+      : currentSimpleModel.value
+  } catch (error) {
+    console.error('获取流程数据失败:', error)
+    return modelData.value.type === BpmModelType.BPMN
+      ? currentBpmnXml.value
+      : currentSimpleModel.value
+  }
+}
+
+/** 表单校验 */
+const validate = async () => {
+  try {
+    // 获取最新的流程数据
+    const processData = await getProcessData()
+    if (!processData) {
+      throw new Error('请设计流程')
+    }
+    return true
+  } catch (error) {
+    throw error
+  }
+}
+
+/** 处理设计器保存成功 */
+const handleDesignSuccess = async (data?: any) => {
+  if (data) {
+    if (modelData.value.type === BpmModelType.BPMN) {
+      currentBpmnXml.value = data
+    } else {
+      currentSimpleModel.value = data
+    }
+
+    // 创建新的对象以触发响应式更新
+    const newModelData = {
+      ...modelData.value,
+      bpmnXml: modelData.value.type === BpmModelType.BPMN ? data : null,
+      simpleModel: modelData.value.type === BpmModelType.BPMN ? null : data
+    }
+
+    // 使用emit更新父组件的数据
+    await nextTick()
+    emit('update:modelValue', newModelData)
+    emit('success', data)
+  }
+}
+
+/** 是否显示设计器 */
+const showDesigner = computed(() => {
+  return Boolean(modelData.value?.key && modelData.value?.name)
+})
+
+// 组件创建时初始化数据
+onMounted(() => {
+  initOrUpdateXmlData()
+})
+
+// 组件卸载前保存数据
+onBeforeUnmount(async () => {
+  try {
+    // 获取并保存最新的流程数据
+    const data = await getProcessData()
+    if (data) {
+      // 创建新的对象以触发响应式更新
+      const newModelData = {
+        ...modelData.value,
+        bpmnXml: modelData.value.type === BpmModelType.BPMN ? data : null,
+        simpleModel: modelData.value.type === BpmModelType.BPMN ? null : data
+      }
+
+      // 使用emit更新父组件的数据
+      await nextTick()
+      emit('update:modelValue', newModelData)
+    }
+  } catch (error) {
+    console.error('保存数据失败:', error)
+  }
+})
+
+defineExpose({
+  validate,
+  getProcessData
+})
+</script>
diff --git a/src/views/bpm/model/form/index.vue b/src/views/bpm/model/form/index.vue
new file mode 100644
index 0000000..4585fc6
--- /dev/null
+++ b/src/views/bpm/model/form/index.vue
@@ -0,0 +1,439 @@
+<template>
+  <ContentWrap>
+    <div class="mx-auto">
+      <!-- 头部导航栏 -->
+      <div
+        class="absolute top-0 left-0 right-0 h-50px bg-white border-bottom z-10 flex items-center px-20px"
+      >
+        <!-- 左侧标题 -->
+        <div class="w-200px flex items-center overflow-hidden">
+          <Icon icon="ep:arrow-left" class="cursor-pointer flex-shrink-0" @click="handleBack" />
+          <span class="ml-10px text-16px truncate" :title="formData.name || '创建流程'">
+            {{ formData.name || '创建流程' }}
+          </span>
+        </div>
+
+        <!-- 步骤条 -->
+        <div class="flex-1 flex items-center justify-center h-full">
+          <div class="w-400px flex items-center justify-between h-full">
+            <div
+              v-for="(step, index) in steps"
+              :key="index"
+              class="flex items-center cursor-pointer mx-15px relative h-full"
+              :class="[
+                currentStep === index
+                  ? 'text-[#3473ff] border-[#3473ff] border-b-2 border-b-solid'
+                  : 'text-gray-500'
+              ]"
+              @click="handleStepClick(index)"
+            >
+              <div
+                class="w-28px h-28px rounded-full flex items-center justify-center mr-8px border-2 border-solid text-15px"
+                :class="[
+                  currentStep === index
+                    ? 'bg-[#3473ff] text-white border-[#3473ff]'
+                    : 'border-gray-300 bg-white text-gray-500'
+                ]"
+              >
+                {{ index + 1 }}
+              </div>
+              <span class="text-16px font-bold whitespace-nowrap">{{ step.title }}</span>
+            </div>
+          </div>
+        </div>
+
+        <!-- 右侧按钮 -->
+        <div class="w-200px flex items-center justify-end gap-2">
+          <el-button v-if="route.params.id" type="success" @click="handleDeploy">发 布</el-button>
+          <el-button type="primary" @click="handleSave">保 存</el-button>
+        </div>
+      </div>
+
+      <!-- 主体内容 -->
+      <div class="mt-50px">
+        <!-- 第一步:基本信息 -->
+        <div v-if="currentStep === 0" class="mx-auto w-560px">
+          <BasicInfo
+            v-model="formData"
+            :categoryList="categoryList"
+            :userList="userList"
+            ref="basicInfoRef"
+          />
+        </div>
+
+        <!-- 第二步:表单设计 -->
+        <div v-if="currentStep === 1" class="mx-auto w-560px">
+          <FormDesign v-model="formData" :formList="formList" ref="formDesignRef" />
+        </div>
+
+        <!-- 第三步:流程设计 -->
+        <ProcessDesign
+          v-if="currentStep === 2"
+          v-model="formData"
+          ref="processDesignRef"
+          @success="handleDesignSuccess"
+        />
+      </div>
+    </div>
+  </ContentWrap>
+</template>
+
+<script lang="ts" setup>
+import { useRoute, useRouter } from 'vue-router'
+import { useMessage } from '@/hooks/web/useMessage'
+import * as ModelApi from '@/api/bpm/model'
+import * as FormApi from '@/api/bpm/form'
+import { CategoryApi } from '@/api/bpm/category'
+import * as UserApi from '@/api/system/user'
+import { useUserStoreWithOut } from '@/store/modules/user'
+import { BpmModelFormType, BpmModelType } from '@/utils/constants'
+import BasicInfo from './BasicInfo.vue'
+import FormDesign from './FormDesign.vue'
+import ProcessDesign from './ProcessDesign.vue'
+import { useTagsViewStore } from '@/store/modules/tagsView'
+
+const router = useRouter()
+const { delView } = useTagsViewStore() // 视图操作
+const route = useRoute()
+const message = useMessage()
+const userStore = useUserStoreWithOut()
+
+// 组件引用
+const basicInfoRef = ref()
+const formDesignRef = ref()
+const processDesignRef = ref()
+
+/** 步骤校验函数 */
+const validateBasic = async () => {
+  await basicInfoRef.value?.validate()
+}
+
+/** 表单设计校验 */
+const validateForm = async () => {
+  await formDesignRef.value?.validate()
+}
+
+/** 流程设计校验 */
+const validateProcess = async () => {
+  await processDesignRef.value?.validate()
+}
+
+const currentStep = ref(0) // 步骤控制
+const steps = [
+  { title: '基本信息', validator: validateBasic },
+  { title: '表单设计', validator: validateForm },
+  { title: '流程设计', validator: validateProcess }
+]
+
+// 表单数据
+const formData: any = ref({
+  id: undefined,
+  name: '',
+  key: '',
+  category: undefined,
+  icon: undefined,
+  description: '',
+  type: BpmModelType.BPMN,
+  formType: BpmModelFormType.NORMAL,
+  formId: '',
+  formCustomCreatePath: '',
+  formCustomViewPath: '',
+  visible: true,
+  startUserType: undefined,
+  managerUserType: undefined,
+  startUserIds: [],
+  managerUserIds: []
+})
+
+// 数据列表
+const formList = ref([])
+const categoryList = ref([])
+const userList = ref<UserApi.UserVO[]>([])
+
+/** 初始化数据 */
+const initData = async () => {
+  const modelId = route.params.id as string
+  if (modelId) {
+    // 修改场景
+    formData.value = await ModelApi.getModel(modelId)
+  } else {
+    // 新增场景
+    formData.value.managerUserIds.push(userStore.getUser.id)
+  }
+
+  // 获取表单列表
+  formList.value = await FormApi.getFormSimpleList()
+  // 获取分类列表
+  categoryList.value = await CategoryApi.getCategorySimpleList()
+  // 获取用户列表
+  userList.value = await UserApi.getSimpleUserList()
+}
+
+/** 校验所有步骤数据是否完整 */
+const validateAllSteps = async () => {
+  try {
+    // 基本信息校验
+    await basicInfoRef.value?.validate()
+    if (!formData.value.key || !formData.value.name || !formData.value.category) {
+      currentStep.value = 0
+      throw new Error('请完善基本信息')
+    }
+
+    // 表单设计校验
+    await formDesignRef.value?.validate()
+    if (formData.value.formType === 10 && !formData.value.formId) {
+      currentStep.value = 1
+      throw new Error('请选择流程表单')
+    }
+    if (
+      formData.value.formType === 20 &&
+      (!formData.value.formCustomCreatePath || !formData.value.formCustomViewPath)
+    ) {
+      currentStep.value = 1
+      throw new Error('请完善自定义表单信息')
+    }
+
+    // 流程设计校验
+    // 如果已经有流程数据,则不需要重新校验
+    if (!formData.value.bpmnXml && !formData.value.simpleModel) {
+      // 如果当前不在第三步,需要先保存当前步骤数据
+      if (currentStep.value !== 2) {
+        await steps[currentStep.value].validator()
+        // 切换到第三步
+        currentStep.value = 2
+        // 等待组件渲染完成
+        await nextTick()
+      }
+
+      // 校验流程设计
+      await processDesignRef.value?.validate()
+      const processData = await processDesignRef.value?.getProcessData()
+      if (!processData) {
+        throw new Error('请设计流程')
+      }
+
+      // 保存流程数据
+      if (formData.value.type === BpmModelType.BPMN) {
+        formData.value.bpmnXml = processData
+        formData.value.simpleModel = null
+      } else {
+        formData.value.bpmnXml = null
+        formData.value.simpleModel = processData
+      }
+    }
+
+    return true
+  } catch (error) {
+    throw error
+  }
+}
+
+/** 保存操作 */
+const handleSave = async () => {
+  try {
+    // 保存前校验所有步骤的数据
+    await validateAllSteps()
+
+    // 更新表单数据
+    const modelData = {
+      ...formData.value
+    }
+
+    // 如果当前在第三步,获取最新的流程设计数据
+    if (currentStep.value === 2) {
+      const processData = await processDesignRef.value?.getProcessData()
+      if (processData) {
+        if (formData.value.type === BpmModelType.BPMN) {
+          modelData.bpmnXml = processData
+          modelData.simpleModel = null
+        } else {
+          modelData.bpmnXml = null
+          modelData.simpleModel = processData
+        }
+      }
+    }
+
+    if (formData.value.id) {
+      // 修改场景
+      await ModelApi.updateModel(modelData)
+      // 询问是否发布流程
+      try {
+        await message.confirm('修改流程成功,是否发布流程?')
+        // 用户点击确认,执行发布
+        await handleDeploy()
+      } catch {
+        // 用户点击取消,停留在当前页面
+      }
+    } else {
+      // 新增场景
+      formData.value.id = await ModelApi.createModel(modelData)
+      message.success('新增成功')
+      try {
+        await message.confirm('创建流程成功,是否继续编辑?')
+        // 用户点击继续编辑,跳转到编辑页面
+        await nextTick()
+        // 先删除当前页签
+        delView(unref(router.currentRoute))
+        // 跳转到编辑页面
+        await router.push({
+          name: 'BpmModelUpdate',
+          params: { id: formData.value.id }
+        })
+      } catch {
+        // 先删除当前页签
+        delView(unref(router.currentRoute))
+        // 用户点击返回列表
+        await router.push({ name: 'BpmModel' })
+      }
+    }
+  } catch (error: any) {
+    console.error('保存失败:', error)
+    message.warning(error.message || '请完善所有步骤的必填信息')
+  }
+}
+
+/** 发布操作 */
+const handleDeploy = async () => {
+  try {
+    // 修改场景下直接发布,新增场景下需要先确认
+    if (!formData.value.id) {
+      await message.confirm('是否确认发布该流程?')
+    }
+
+    // 校验所有步骤
+    await validateAllSteps()
+
+    // 更新表单数据
+    const modelData = {
+      ...formData.value
+    }
+
+    // 如果当前在第三步,获取最新的流程设计数据
+    if (currentStep.value === 2) {
+      const processData = await processDesignRef.value?.getProcessData()
+      if (processData) {
+        if (formData.value.type === BpmModelType.BPMN) {
+          modelData.bpmnXml = processData
+          modelData.simpleModel = null
+        } else {
+          modelData.bpmnXml = null
+          modelData.simpleModel = processData
+        }
+      }
+    }
+
+    // 先保存所有数据
+    if (formData.value.id) {
+      await ModelApi.updateModel(modelData)
+    } else {
+      const result = await ModelApi.createModel(modelData)
+      formData.value.id = result.id
+    }
+
+    // 发布
+    await ModelApi.deployModel(formData.value.id)
+    message.success('发布成功')
+    // 返回列表页
+    await router.push({ name: 'BpmModel' })
+  } catch (error: any) {
+    console.error('发布失败:', error)
+    message.warning(error.message || '发布失败')
+  }
+}
+
+/** 步骤切换处理 */
+const handleStepClick = async (index: number) => {
+  try {
+    // 如果是切换到第三步(流程设计),需要校验key和name
+    if (index === 2) {
+      if (!formData.value.key || !formData.value.name) {
+        message.warning('请先填写流程标识和流程名称')
+        return
+      }
+    }
+
+    // 保存当前步骤的数据
+    if (currentStep.value === 2) {
+      const processData = await processDesignRef.value?.getProcessData()
+      if (processData) {
+        if (formData.value.type === BpmModelType.BPMN) {
+          formData.value.bpmnXml = processData
+          formData.value.simpleModel = null
+        } else {
+          formData.value.bpmnXml = null
+          formData.value.simpleModel = processData
+        }
+      }
+    } else {
+      // 只有在向后切换时才进行校验
+      if (index > currentStep.value) {
+        if (typeof steps[currentStep.value].validator === 'function') {
+          await steps[currentStep.value].validator()
+        }
+      }
+    }
+
+    // 切换步骤
+    currentStep.value = index
+
+    // 如果切换到流程设计步骤,等待组件渲染完成后刷新设计器
+    if (index === 2) {
+      await nextTick()
+      // 等待更长时间确保组件完全初始化
+      await new Promise(resolve => setTimeout(resolve, 200))
+      if (processDesignRef.value?.refresh) {
+        await processDesignRef.value.refresh()
+      }
+    }
+  } catch (error) {
+    console.error('步骤切换失败:', error)
+    message.warning('请先完善当前步骤必填信息')
+  }
+}
+
+/** 处理设计器保存成功 */
+const handleDesignSuccess = (bpmnXml?: string) => {
+  if (bpmnXml) {
+    formData.value.bpmnXml = bpmnXml
+  }
+}
+
+/** 返回列表页 */
+const handleBack = () => {
+  // 先删除当前页签
+  delView(unref(router.currentRoute))
+  // 跳转到列表页
+  router.push({ name: 'BpmModel' })
+}
+
+/** 初始化 */
+onMounted(async () => {
+  await initData()
+})
+
+// 添加组件卸载前的清理代码
+onBeforeUnmount(() => {
+  // 清理所有的引用
+  basicInfoRef.value = null
+  formDesignRef.value = null
+  processDesignRef.value = null
+})
+</script>
+
+<style lang="scss" scoped>
+.border-bottom {
+  border-bottom: 1px solid #dcdfe6;
+}
+
+.text-primary {
+  color: #3473ff;
+}
+
+.bg-primary {
+  background-color: #3473ff;
+}
+
+.border-primary {
+  border-color: #3473ff;
+}
+</style>
diff --git a/src/views/bpm/model/index.vue b/src/views/bpm/model/index.vue
index bf43d29..c7d9417 100644
--- a/src/views/bpm/model/index.vue
+++ b/src/views/bpm/model/index.vue
@@ -106,6 +106,7 @@
 
 defineOptions({ name: 'BpmModel' })
 
+const { push } = useRouter()
 const message = useMessage() // 消息弹窗
 const loading = ref(true) // 列表的加载中
 const isCategorySorting = ref(false) // 是否 category 正处于排序状态
@@ -124,7 +125,14 @@
 /** 添加/修改操作 */
 const formRef = ref()
 const openForm = (type: string, id?: number) => {
-  formRef.value.open(type, id)
+  if (type === 'create') {
+    push({ name: 'BpmModelCreate' })
+  } else {
+    push({
+      name: 'BpmModelUpdate',
+      params: { id }
+    })
+  }
 }
 
 /** 流程表单的详情按钮操作 */
diff --git a/src/views/bpm/processInstance/create/ProcessDefinitionDetail.vue b/src/views/bpm/processInstance/create/ProcessDefinitionDetail.vue
index 3800f19..7eaf0f4 100644
--- a/src/views/bpm/processInstance/create/ProcessDefinitionDetail.vue
+++ b/src/views/bpm/processInstance/create/ProcessDefinitionDetail.vue
@@ -8,7 +8,7 @@
         <!-- 中间主要内容 tab 栏 -->
         <el-tabs v-model="activeTab">
           <!-- 表单信息 -->
-          <el-tab-pane label="表单填写" name="form" >
+          <el-tab-pane label="表单填写" name="form">
             <div class="form-scroll-area" v-loading="processInstanceStartLoading">
               <el-scrollbar>
                 <el-row>
@@ -75,7 +75,11 @@
 <script lang="ts" setup>
 import { decodeFields, setConfAndFields2 } from '@/utils/formCreate'
 import { BpmModelType } from '@/utils/constants'
-import { CandidateStrategy } from '@/components/SimpleProcessDesignerV2/src/consts'
+import {
+  CandidateStrategy,
+  NodeId,
+  FieldPermissionType
+} from '@/components/SimpleProcessDesignerV2/src/consts'
 import ProcessInstanceBpmnViewer from '../detail/ProcessInstanceBpmnViewer.vue'
 import ProcessInstanceSimpleViewer from '../detail/ProcessInstanceSimpleViewer.vue'
 import ProcessInstanceTimeline from '../detail/ProcessInstanceTimeline.vue'
@@ -129,8 +133,10 @@
       }
     }
     setConfAndFields2(detailForm, row.formConf, row.formFields, formVariables)
+
     await nextTick()
     fApi.value?.btn.show(false) // 隐藏提交按钮
+
     // 获取流程审批信息
     await getApprovalDetail(row)
 
@@ -152,7 +158,12 @@
 /** 获取审批详情 */
 const getApprovalDetail = async (row: any) => {
   try {
-    const data = await ProcessInstanceApi.getApprovalDetail({ processDefinitionId: row.id })
+    // TODO 获取审批详情,设置 activityId 为发起人节点(为了获取字段权限。暂时只对 Simple 设计器有效)
+    const data = await ProcessInstanceApi.getApprovalDetail({
+      processDefinitionId: row.id,
+      activityId: NodeId.START_USER_NODE_ID
+    })
+
     if (!data) {
       message.error('查询不到审批详情信息!')
       return
@@ -170,7 +181,33 @@
 
     // 获取审批节点,显示 Timeline 的数据
     activityNodes.value = data.activityNodes
+    // 获取表单字段权限
+    const formFieldsPermission = data.formFieldsPermission
+    // 设置表单字段权限
+    if (formFieldsPermission) {
+      Object.keys(formFieldsPermission).forEach((item) => {
+        setFieldPermission(item, formFieldsPermission[item])
+      })
+    }
   } finally {
+  }
+}
+
+/**
+ * 设置表单权限
+ */
+const setFieldPermission = (field: string, permission: string) => {
+  if (permission === FieldPermissionType.READ) {
+    //@ts-ignore
+    fApi.value?.disabled(true, field)
+  }
+  if (permission === FieldPermissionType.WRITE) {
+    //@ts-ignore
+    fApi.value?.disabled(false, field)
+  }
+  if (permission === FieldPermissionType.NONE) {
+    //@ts-ignore
+    fApi.value?.hidden(true, field)
   }
 }
 
@@ -243,11 +280,11 @@
   .form-scroll-area {
     height: calc(
       100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px -
-        $process-header-height - 40px
+      $process-header-height - 40px
     );
     max-height: calc(
       100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px -
-        $process-header-height - 40px
+      $process-header-height - 40px
     );
     overflow: auto;
   }
diff --git a/src/views/bpm/processInstance/detail/ProcessInstanceOperationButton.vue b/src/views/bpm/processInstance/detail/ProcessInstanceOperationButton.vue
index 894a5d4..1b3ebc5 100644
--- a/src/views/bpm/processInstance/detail/ProcessInstanceOperationButton.vue
+++ b/src/views/bpm/processInstance/detail/ProcessInstanceOperationButton.vue
@@ -588,7 +588,7 @@
 })
 const deleteSignFormRule = reactive<FormRules<typeof deleteSignForm>>({
   deleteSignTaskId: [{ required: true, message: '减签人员不能为空', trigger: 'change' }],
- reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
+  reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
 })
 
 // 退回表单
@@ -627,11 +627,11 @@
 const openPopover = async (type: string) => {
   if (type === 'approve') {
     // 校验流程表单
-     const valid = await validateNormalForm();
-     if (!valid) {
+    const valid = await validateNormalForm();
+    if (!valid) {
       message.warning('表单校验不通过,请先完善表单!!')
       return;
-     }
+    }
   }
   if (type === 'return') {
     // 获取退回节点
@@ -652,7 +652,7 @@
 const closePropover = (type: string, formRef: FormInstance | undefined) => {
   if (formRef) {
     formRef.resetFields()
-  } 
+  }
   popOverVisible.value[type] = false
 }
 
@@ -664,8 +664,8 @@
     if (!formRef) return
     await formRef.validate()
     if (pass) {
-       // 获取修改的流程变量, 暂时只支持流程表单
-       const variables = getUpdatedProcessInstanceVaiables();
+      // 获取修改的流程变量, 暂时只支持流程表单
+      const variables = getUpdatedProcessInstanceVaiables();
       // 审批通过数据
       const data = {
         id: runningTask.value.id,
@@ -684,8 +684,8 @@
       popOverVisible.value.approve = false
       message.success('审批通过成功')
     } else {
-       // 审批不通过数据
-       const data = {
+      // 审批不通过数据
+      const data = {
         id: runningTask.value.id,
         reason: rejectReasonForm.reason,
       }
@@ -752,7 +752,7 @@
 const handleDelegate = async () => {
   formLoading.value = true
   try {
- 
+
     // 1.1 校验表单
     if (!delegateFormRef.value) return
     await delegateFormRef.value.validate()
diff --git a/src/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue b/src/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue
index 56b466a..e24316c 100644
--- a/src/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue
+++ b/src/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue
@@ -25,7 +25,7 @@
           </div>
         </div>
       </template>
-      <div class="flex flex-col items-start gap2" :id="`activity-task-${activity.id}`">
+      <div class="flex flex-col items-start gap2" :id="`activity-task-${activity.id}-${index}`">
         <!-- 第一行:节点名称、时间 -->
         <div class="flex w-full">
           <div class="font-bold"> {{ activity.name }}</div>
@@ -113,7 +113,7 @@
                 </div>
               </div>
             </div>
-            <teleport defer :to="`#activity-task-${activity.id}`">
+            <teleport defer :to="`#activity-task-${activity.id}-${index}`">
               <div
                 v-if="
                   task.reason &&
diff --git a/src/views/bpm/processInstance/detail/index.vue b/src/views/bpm/processInstance/detail/index.vue
index 9809f7a..a6ed3b5 100644
--- a/src/views/bpm/processInstance/detail/index.vue
+++ b/src/views/bpm/processInstance/detail/index.vue
@@ -325,11 +325,11 @@
     display: flex;
     height: calc(
       100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px -
-        $process-header-height - 40px
+      $process-header-height - 40px
     );
     max-height: calc(
       100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px -
-        $process-header-height - 40px
+      $process-header-height - 40px
     );
     overflow: auto;
     flex-direction: column;
diff --git a/src/views/bpm/simple/SimpleModelDesign.vue b/src/views/bpm/simple/SimpleModelDesign.vue
index 1740e03..eed0099 100644
--- a/src/views/bpm/simple/SimpleModelDesign.vue
+++ b/src/views/bpm/simple/SimpleModelDesign.vue
@@ -1,6 +1,15 @@
 <template>
   <ContentWrap :bodyStyle="{ padding: '20px 16px' }">
-    <SimpleProcessDesigner :model-id="modelId" @success="close" />
+    <SimpleProcessDesigner
+      :model-id="modelId"
+      :model-key="modelKey"
+      :model-name="modelName"
+      :value="currentValue"
+      @success="handleSuccess"
+      @init-finished="handleInit"
+      :start-user-ids="startUserIds"
+      ref="designerRef"
+    />
   </ContentWrap>
 </template>
 <script setup lang="ts">
@@ -9,11 +18,138 @@
 defineOptions({
   name: 'SimpleModelDesign'
 })
-const router = useRouter() // 路由
-const { query } = useRoute() // 路由的查询
-const modelId = query.modelId as string
-const close = () => {
-  router.push({ path: '/bpm/manager/model' })
+
+const props = defineProps<{
+  modelId?: string
+  modelKey?: string
+  modelName?: string
+  value?: string
+  startUserIds?: number[]
+}>()
+
+const emit = defineEmits(['success', 'init-finished'])
+const designerRef = ref()
+const isInitialized = ref(false)
+const currentValue = ref('')
+
+// 初始化或更新当前值
+const initOrUpdateValue = async () => {
+  console.log('initOrUpdateValue', props.value)
+  if (props.value) {
+    currentValue.value = props.value
+    // 如果设计器已经初始化,立即加载数据
+    if (isInitialized.value && designerRef.value) {
+      try {
+        await designerRef.value.loadProcessData(props.value)
+        await nextTick()
+        if (designerRef.value.refresh) {
+          await designerRef.value.refresh()
+        }
+      } catch (error) {
+        console.error('加载流程数据失败:', error)
+      }
+    }
+  }
 }
+
+// 监听属性变化
+watch(
+  [() => props.modelKey, () => props.modelName, () => props.value],
+  async ([newKey, newName, newValue], [oldKey, oldName, oldValue]) => {
+    if (designerRef.value && isInitialized.value) {
+      try {
+        if (newKey && newName && (newKey !== oldKey || newName !== oldName)) {
+          await designerRef.value.updateModel(newKey, newName)
+        }
+        if (newValue && newValue !== oldValue) {
+          currentValue.value = newValue
+          await designerRef.value.loadProcessData(newValue)
+          await nextTick()
+          if (designerRef.value.refresh) {
+            await designerRef.value.refresh()
+          }
+        }
+      } catch (error) {
+        console.error('更新流程数据失败:', error)
+      }
+    }
+  },
+  { deep: true, immediate: true }
+)
+
+// 初始化完成回调
+const handleInit = async () => {
+  try {
+    isInitialized.value = true
+    emit('init-finished')
+
+    // 等待下一个tick,确保设计器已经准备好
+    await nextTick()
+
+    // 初始化完成后,设置初始值
+    if (props.modelKey && props.modelName) {
+      await designerRef.value.updateModel(props.modelKey, props.modelName)
+    }
+    if (props.value) {
+      currentValue.value = props.value
+      await designerRef.value.loadProcessData(props.value)
+      // 再次刷新确保数据正确加载
+      await nextTick()
+      if (designerRef.value.refresh) {
+        await designerRef.value.refresh()
+      }
+    }
+  } catch (error) {
+    console.error('初始化流程数据失败:', error)
+  }
+}
+
+// 修改成功回调
+const handleSuccess = (data?: any) => {
+  console.warn('handleSuccess', data)
+  if (data && data !== currentValue.value) {
+    currentValue.value = data
+    emit('success', data)
+  }
+}
+
+/** 获取当前流程数据 */
+const getCurrentFlowData = async () => {
+  try {
+    if (designerRef.value) {
+      const data = await designerRef.value.getCurrentFlowData()
+      if (data) {
+        currentValue.value = data
+      }
+      return data
+    }
+    return currentValue.value || undefined
+  } catch (error) {
+    console.error('获取流程数据失败:', error)
+    return currentValue.value || undefined
+  }
+}
+
+// 组件创建时初始化数据
+onMounted(() => {
+  initOrUpdateValue()
+})
+
+// 组件卸载前保存数据
+onBeforeUnmount(async () => {
+  try {
+    const data = await getCurrentFlowData()
+    if (data) {
+      emit('success', data)
+    }
+  } catch (error) {
+    console.error('保存数据失败:', error)
+  }
+})
+
+defineExpose({
+  getCurrentFlowData,
+  refresh: () => designerRef.value?.refresh?.()
+})
 </script>
 <style lang="scss" scoped></style>
diff --git a/src/views/bpm/task/done/index.vue b/src/views/bpm/task/done/index.vue
index 1365104..e83e9ed 100644
--- a/src/views/bpm/task/done/index.vue
+++ b/src/views/bpm/task/done/index.vue
@@ -103,7 +103,7 @@
             <el-button @click="handleQuery"> 确认</el-button>
             <el-button @click="showPopover = false"> 取消</el-button>
             <el-button @click="resetQuery"> 清空</el-button>
-        </el-form-item>
+          </el-form-item>
         </el-popover>
       </el-form-item>
 
diff --git a/src/views/infra/file/index.vue b/src/views/infra/file/index.vue
index e1d2ffd..3598bfa 100644
--- a/src/views/infra/file/index.vue
+++ b/src/views/infra/file/index.vue
@@ -91,6 +91,9 @@
       />
       <el-table-column label="操作" align="center">
         <template #default="scope">
+          <el-button link type="primary" @click="copyToClipboard(scope.row.url)">
+            复制链接
+          </el-button>
           <el-button
             link
             type="danger"
@@ -168,6 +171,13 @@
   formRef.value.open()
 }
 
+/** 复制到剪贴板方法 */
+const copyToClipboard = (text: string) => {
+  navigator.clipboard.writeText(text).then(() => {
+    message.success('复制成功')
+  })
+}
+
 /** 删除按钮操作 */
 const handleDelete = async (id: number) => {
   try {
diff --git a/src/views/system/area/index.vue b/src/views/system/area/index.vue
index f3289a0..339ecef 100644
--- a/src/views/system/area/index.vue
+++ b/src/views/system/area/index.vue
@@ -1,4 +1,6 @@
 <template>
+  <doc-alert title="地区 & IP" url="https://doc.iocoder.cn/area-and-ip/" />
+
   <!-- 操作栏 -->
   <ContentWrap>
     <el-button type="primary" plain @click="openForm()">
@@ -14,6 +16,7 @@
         <template #default="{ height, width }">
           <!-- Virtualized Table 虚拟化表格:高性能,解决表格在大数据量下的卡顿问题 -->
           <el-table-v2
+            v-loading="loading"
             :columns="columns"
             :data="list"
             :width="width"
@@ -29,7 +32,7 @@
   <AreaForm ref="formRef" />
 </template>
 <script setup lang="tsx">
-import type { Column } from 'element-plus'
+import { Column } from 'element-plus'
 import AreaForm from './AreaForm.vue'
 import * as AreaApi from '@/api/system/area'
 
@@ -38,7 +41,7 @@
 // 表格的 column 字段
 const columns: Column[] = [
   {
-    dataKey: 'id', // 需要渲染当前列的数据字段。例如说:{id:9527, name:'Mike'},则填 id
+    dataKey: 'id', // 需要渲染当前列的数据字段
     title: '编号', // 显示在单元格表头的文本
     width: 400, // 当前列的宽度,必须设置
     fixed: true, // 是否固定列
@@ -50,14 +53,17 @@
     width: 200
   }
 ]
-// 表格的数据
-const list = ref([])
+const loading = ref(true) // 列表的加载中
+const list = ref([]) // 表格的数据
 
-/**
- * 获得数据列表
- */
+/** 获得数据列表 */
 const getList = async () => {
-  list.value = await AreaApi.getAreaTree()
+  loading.value = true
+  try {
+    list.value = await AreaApi.getAreaTree()
+  } finally {
+    loading.value = false
+  }
 }
 
 /** 添加/修改操作 */
diff --git a/src/views/system/menu/index.vue b/src/views/system/menu/index.vue
index a8fcc0e..03d352f 100644
--- a/src/views/system/menu/index.vue
+++ b/src/views/system/menu/index.vue
@@ -50,10 +50,6 @@
           <Icon class="mr-5px" icon="ep:plus" />
           新增
         </el-button>
-        <el-button plain type="danger" @click="toggleExpandAll">
-          <Icon class="mr-5px" icon="ep:sort" />
-          展开/折叠
-        </el-button>
         <el-button plain @click="refreshMenu">
           <Icon class="mr-5px" icon="ep:refresh" />
           刷新菜单缓存
@@ -64,57 +60,22 @@
 
   <!-- 列表 -->
   <ContentWrap>
-    <el-table
-      v-if="refreshTable"
-      v-loading="loading"
-      :data="list"
-      :default-expand-all="isExpandAll"
-      row-key="id"
-    >
-      <el-table-column :show-overflow-tooltip="true" label="菜单名称" prop="name" width="250" />
-      <el-table-column align="center" label="图标" prop="icon" width="100">
-        <template #default="scope">
-          <Icon :icon="scope.row.icon" />
+    <div style="height: 700px">
+      <!-- AutoResizer 自动调节大小 -->
+      <el-auto-resizer>
+        <template #default="{ height, width }">
+          <!-- Virtualized Table 虚拟化表格:高性能,解决表格在大数据量下的卡顿问题 -->
+          <el-table-v2
+            v-loading="loading"
+            :columns="columns"
+            :data="list"
+            :width="width"
+            :height="height"
+            expand-column-key="name"
+          />
         </template>
-      </el-table-column>
-      <el-table-column label="排序" prop="sort" width="60" />
-      <el-table-column :show-overflow-tooltip="true" label="权限标识" prop="permission" />
-      <el-table-column :show-overflow-tooltip="true" label="组件路径" prop="component" />
-      <el-table-column :show-overflow-tooltip="true" label="组件名称" prop="componentName" />
-      <el-table-column label="状态" prop="status" width="80">
-        <template #default="scope">
-          <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
-        </template>
-      </el-table-column>
-      <el-table-column align="center" label="操作">
-        <template #default="scope">
-          <el-button
-            v-hasPermi="['system:menu:update']"
-            link
-            type="primary"
-            @click="openForm('update', scope.row.id)"
-          >
-            修改
-          </el-button>
-          <el-button
-            v-hasPermi="['system:menu:create']"
-            link
-            type="primary"
-            @click="openForm('create', undefined, scope.row.id)"
-          >
-            新增
-          </el-button>
-          <el-button
-            v-hasPermi="['system:menu:delete']"
-            link
-            type="danger"
-            @click="handleDelete(scope.row.id)"
-          >
-            删除
-          </el-button>
-        </template>
-      </el-table-column>
-    </el-table>
+      </el-auto-resizer>
+    </div>
   </ContentWrap>
 
   <!-- 表单弹窗:添加/修改 -->
@@ -124,8 +85,14 @@
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { handleTree } from '@/utils/tree'
 import * as MenuApi from '@/api/system/menu'
+import { MenuVO } from '@/api/system/menu'
 import MenuForm from './MenuForm.vue'
-import {CACHE_KEY, useCache, useSessionCache} from '@/hooks/web/useCache'
+import { CACHE_KEY, useCache, useSessionCache } from '@/hooks/web/useCache'
+import { h } from 'vue'
+import { Column, ElButton } from 'element-plus'
+import { Icon } from '@/components/Icon'
+import { hasPermission } from '@/directives/permission/hasPermi'
+import { CommonStatusEnum } from '@/utils/constants'
 
 defineOptions({ name: 'SystemMenu' })
 
@@ -134,6 +101,101 @@
 const { t } = useI18n() // 国际化
 const message = useMessage() // 消息弹窗
 
+// 表格的 column 字段
+const columns: Column[] = [
+  {
+    dataKey: 'name',
+    title: '菜单名称',
+    width: 250
+  },
+  {
+    dataKey: 'icon',
+    title: '图标',
+    width: 150,
+    cellRenderer: ({ rowData }) => {
+      return h(Icon, {
+        icon: rowData.icon
+      })
+    }
+  },
+  {
+    dataKey: 'sort',
+    title: '排序',
+    width: 100
+  },
+  {
+    dataKey: 'permission',
+    title: '权限标识',
+    width: 240
+  },
+  {
+    dataKey: 'component',
+    title: '组件路径',
+    width: 240
+  },
+  {
+    dataKey: 'componentName',
+    title: '组件名称',
+    width: 240
+  },
+  {
+    dataKey: 'status',
+    title: '状态',
+    width: 160,
+    cellRenderer: ({ rowData }) => {
+      return h(ElSwitch, {
+        modelValue: rowData.status,
+        activeValue: CommonStatusEnum.ENABLE,
+        inactiveValue: CommonStatusEnum.DISABLE,
+        loading: menuStatusUpdating.value[rowData.id],
+        disabled: !hasPermission(['system:menu:update']),
+        onChange: (val) => handleStatusChanged(rowData, val as number)
+      })
+    }
+  },
+  {
+    dataKey: 'operation',
+    title: '操作',
+    width: 200,
+    cellRenderer: ({ rowData }) => {
+      return h(
+        'div',
+        [
+          hasPermission(['system:menu:update']) &&
+          h(
+            ElButton,
+            {
+              link: true,
+              type: 'primary',
+              onClick: () => openForm('update', rowData.id)
+            },
+            '修改'
+          ),
+          hasPermission(['system:menu:create']) &&
+          h(
+            ElButton,
+            {
+              link: true,
+              type: 'primary',
+              onClick: () => openForm('create', undefined, rowData.id)
+            },
+            '新增'
+          ),
+          hasPermission(['system:menu:delete']) &&
+          h(
+            ElButton,
+            {
+              link: true,
+              type: 'danger',
+              onClick: () => handleDelete(rowData.id)
+            },
+            '删除'
+          )
+        ].filter(Boolean)
+      )
+    }
+  }
+]
 const loading = ref(true) // 列表的加载中
 const list = ref<any>([]) // 列表的数据
 const queryParams = reactive({
@@ -141,8 +203,6 @@
   status: undefined
 })
 const queryFormRef = ref() // 搜索的表单
-const isExpandAll = ref(false) // 是否展开,默认全部折叠
-const refreshTable = ref(true) // 重新渲染表格状态
 
 /** 查询列表 */
 const getList = async () => {
@@ -172,21 +232,14 @@
   formRef.value.open(type, id, parentId)
 }
 
-/** 展开/折叠操作 */
-const toggleExpandAll = () => {
-  refreshTable.value = false
-  isExpandAll.value = !isExpandAll.value
-  nextTick(() => {
-    refreshTable.value = true
-  })
-}
-
 /** 刷新菜单缓存按钮操作 */
 const refreshMenu = async () => {
   try {
     await message.confirm('即将更新缓存刷新浏览器!', '刷新菜单缓存')
     // 清空,从而触发刷新
     wsCache.delete(CACHE_KEY.USER)
+    wsCache.delete(CACHE_KEY.ROLE_ROUTERS)
+    wsSessionCache.delete(CACHE_KEY.USER)
     wsSessionCache.delete(CACHE_KEY.ROLE_ROUTERS)
     // 刷新浏览器
     location.reload()
@@ -206,6 +259,21 @@
   } catch {}
 }
 
+/** 开启/关闭菜单的状态 */
+const menuStatusUpdating = ref({}) // 菜单状态更新中的 menu 映射。key:菜单编号,value:是否更新中
+const handleStatusChanged = async (menu: MenuVO, val: number) => {
+  // 1. 标记 menu.id 更新中
+  menuStatusUpdating.value[menu.id] = true
+  try {
+    // 2. 发起更新状态
+    menu.status = val
+    await MenuApi.updateMenu(menu)
+  } finally {
+    // 3. 标记 menu.id 更新完成
+    menuStatusUpdating.value[menu.id] = false
+  }
+}
+
 /** 初始化 **/
 onMounted(() => {
   getList()

--
Gitblit v1.9.3