Forráskód Böngészése

仿钉钉流程设计器-前端重构

jason 1 éve
szülő
commit
931417928f

+ 1 - 1
src/api/bpm/model/index.ts

@@ -29,7 +29,7 @@ export const getModelPage = async (params) => {
   return await request.get({ url: '/bpm/model/page', params })
 }
 
-export const getModel = async (id: number) => {
+export const getModel = async (id: string) => {
   return await request.get({ url: '/bpm/model/get?id=' + id })
 }
 

+ 70 - 0
src/components/SimpleProcessDesignerV2/src/NodeHandler.vue

@@ -0,0 +1,70 @@
+<template>
+  <div class="node-handler-wrapper">
+    <div class="node-handler" v-if="props.showAdd">
+     <el-popover  trigger="hover" v-model:visible="popoverShow"  placement="right-start"  width="auto"> 
+          <div class="handler-item-wrapper">
+            <div class="handler-item"  @click="addNode(NodeType.USER_TASK_NODE)">
+              <div class="approve handler-item-icon">
+                <span class="iconfont icon-approve icon-size"></span>
+              </div>
+              <div class="handler-item-text">审批人</div>
+            </div>
+            <!-- <div class="handler-item" @click="addNode(NodeType.CONDITION_NODE)">
+              <div class="handler-item-icon">
+                <span class="iconfont icon-size icon-exclusive"></span>
+              </div>
+              <div class="handler-item-text">条件分支</div>
+            </div> -->
+          </div>
+          <template #reference>
+            <div class="add-icon"><Icon icon="ep:plus" /></div>
+          </template>
+      </el-popover>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { SimpleFlowNode, NodeType, NODE_DEFAULT_NAME } from './consts'
+import { generateUUID } from '@/utils'
+defineOptions({
+  name: 'NodeHandler'
+})
+const popoverShow = ref(false)
+
+const props = defineProps({
+  childNode: {
+    type: Object as () => SimpleFlowNode,
+    default: null
+  },
+  showAdd: {
+    // 是否显示添加节点
+    type: Boolean,
+    default: true
+  }
+})
+
+const emits = defineEmits(['update:childNode'])
+
+const addNode = (type: number) => {
+  popoverShow.value = false
+  if (type === NodeType.USER_TASK_NODE) {
+    const data: SimpleFlowNode = {
+      id:  generateUUID(),
+      name: NODE_DEFAULT_NAME.get(NodeType.USER_TASK_NODE) as string,
+      showText: '',
+      type: NodeType.USER_TASK_NODE,
+      // 审批节点配置
+      attributes: {
+        approveMethod: 1,
+        candidateStrategy: 30,
+        candidateParam: undefined
+      },
+      childNode: props.childNode
+    }
+    emits('update:childNode', data)
+  }
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 42 - 0
src/components/SimpleProcessDesignerV2/src/ProcessNodeTree.vue

@@ -0,0 +1,42 @@
+<template>
+  <!-- 开始节点 -->
+  <StartEventNode v-if="currentNode && currentNode.type === NodeType.START_EVENT_NODE" :flow-node ="currentNode" />
+
+  <!-- 递归显示子节点  -->
+  <ProcessNodeTree v-if="currentNode && currentNode.childNode"  v-model:flow-node="currentNode.childNode"/>
+
+  <!-- 结束节点 -->
+  <EndEventNode v-if="currentNode && currentNode.type === NodeType.END_EVENT_NODE"/>
+</template>
+<script setup lang='ts'>
+import StartEventNode from './nodes/StartEventNode.vue';
+import EndEventNode from './nodes/EndEventNode.vue';
+
+import { SimpleFlowNode, NodeType }  from './consts';
+defineOptions({
+  name: 'ProcessNodeTree'
+})
+const props = defineProps({
+  flowNode : {
+    type: Object as () => SimpleFlowNode,
+    default: () => null 
+  }
+})
+const emits = defineEmits(['update:flowNode'])
+
+const currentNode = ref<SimpleFlowNode>(props.flowNode);
+
+// 重要:监控节点变化. 能动态新增、删除节点
+watch(() => props.flowNode, (newValue) => {  
+  console.log("Flow Nodes changed", newValue);
+  currentNode.value = newValue;  
+});
+
+// const handleModelValueUpdate = (updateValue) => {
+//   console.log('handleModelValueUpdate', updateValue)
+//   emits('update:flowNode', updateValue);
+// } 
+</script>
+<style lang='scss' scoped>
+
+</style>

+ 140 - 0
src/components/SimpleProcessDesignerV2/src/SimpleProcessDesigner.vue

@@ -0,0 +1,140 @@
+<template>
+  <div class="simple-flow-canvas">
+    <div class="simple-flow-container">
+      <div class="top-area-container">
+        <div class="top-actions">
+          <div class="canvas-control">
+            <span class="control-scale-group">
+              <span class="control-scale-button"> <Icon icon="ep:plus" @click="zoomOut()" /></span>
+              <span class="control-scale-label">{{ scaleValue }}%</span>
+              <span class="control-scale-button"><Icon icon="ep:minus" @click="zoomIn()" /></span>
+            </span>
+          </div>
+          <el-button type="primary" @click="saveSimpleFlowModel">保存</el-button>
+          <!-- <el-button type="primary">全局设置</el-button> -->
+        </div>
+      </div>
+      <div class="scale-container" :style="`transform: scale(${scaleValue / 100});`">
+        <ProcessNodeTree v-if="processNodeTree" v-model:flow-node="processNodeTree" />
+      </div>
+    </div>
+    <Dialog v-model="errorDialogVisible" title="保存失败" width="400" :fullscreen="false">
+      <div v-for="(item, index) in errorNodes" :key="index">
+          {{ item.name }}
+      </div>
+      <template #footer>
+        <el-button type="primary" @click="errorDialogVisible = false" >知道了</el-button>
+      </template>
+    </Dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+import ProcessNodeTree from './ProcessNodeTree.vue';
+import { saveBpmSimpleModel, getBpmSimpleModel } from '@/api/bpm/simple'
+import { SimpleFlowNode, NodeType } from './consts'
+
+defineOptions({
+  name: 'SimpleProcessDesigner'
+})
+
+const router = useRouter() // 路由
+const props = defineProps({
+  modelId: String
+})
+const message = useMessage() // 国际化
+const processNodeTree = ref<SimpleFlowNode>({
+  name: '开始',
+  type: NodeType.START_EVENT_NODE,
+  id: 'StartEvent_1',
+  childNode: {
+    id: 'EndEvent_1',
+    name: '结束',
+    type: NodeType.END_EVENT_NODE
+  }
+})
+
+const errorDialogVisible = ref(false)
+let errorNodes: SimpleFlowNode[] = []
+const saveSimpleFlowModel = async () => {
+  console.log('processNodeTree===>', processNodeTree.value)
+  if (!props.modelId) {
+    message.error('缺少模型 modelId 编号')
+    return
+  }
+  errorNodes = []
+  validateNode(processNodeTree.value, errorNodes)
+  console.log('errorNodes is ', errorNodes)
+  if (errorNodes.length > 0) {
+    errorDialogVisible.value = true
+  }
+  // const data = {
+  //   modelId: props.modelId,
+  //   simpleModelBody: simpleWorkFlowNodes.value
+  // }
+  // console.log('request json data1 is ', data)
+  // const result = await saveBpmSimpleModel(data)
+  // console.log('save the result is ', result)
+  // if (result) {
+  //   message.success('修改成功')
+  //   close()
+  // } else {
+  //   message.alert('修改失败')
+  // }
+}
+const validateNode = (node: SimpleFlowNode | undefined, errorNodes: SimpleFlowNode[]) => {
+  if (node) {
+    const { type, showText, conditionNodes } = node
+    if (type == NodeType.END_EVENT_NODE) {
+      return
+    }
+    if (type == NodeType.START_EVENT_NODE) {
+      validateNode(node.childNode, errorNodes)
+    }
+
+    if (type === NodeType.USER_TASK_NODE) {
+      if (!showText) {
+        errorNodes.push(node)
+      }
+      validateNode(node.childNode, errorNodes)
+    }
+    if (type === NodeType.CONDITION_NODE) {
+      if (!showText) {
+        errorNodes.push(node)
+      }
+      validateNode(node.childNode, errorNodes)
+    }
+
+    if (type == NodeType.EXCLUSIVE_NODE) {
+      conditionNodes?.forEach((item) => {
+        validateNode(item, errorNodes)
+      })
+      validateNode(node.childNode, errorNodes)
+    }
+  }
+}
+const close = () => {
+  router.push({ path: '/bpm/manager/model' })
+}
+let scaleValue = ref(100)
+
+const zoomOut = () => {
+  if (scaleValue.value == 300) {
+    return
+  }
+  scaleValue.value += 10
+}
+const zoomIn = () => {
+  if (scaleValue.value == 50) {
+    return
+  }
+  scaleValue.value -= 10
+}
+onMounted(async () => {
+  const result = await getBpmSimpleModel(props.modelId)
+  if (result) {
+    console.log('get the result is ', result)
+    processNodeTree.value = result
+  }
+})
+</script>

+ 72 - 0
src/components/SimpleProcessDesignerV2/src/consts.ts

@@ -0,0 +1,72 @@
+// @ts-ignore
+import { DictDataVO } from '@/api/system/dict/types'
+
+export enum NodeType {
+  /**
+   * 发起人节点
+   */
+  START_EVENT_NODE = 0,
+  /**
+   * 结束节点
+   */
+  END_EVENT_NODE = -2,
+
+  /**
+   * 审批人节点
+   */
+  USER_TASK_NODE = 1,
+ 
+  /**
+   * 条件节点
+   */
+  CONDITION_NODE = 3,
+  /**
+   * 条件分支节点
+   */
+  EXCLUSIVE_NODE = 4,
+  /**
+   * 并行分支分叉节点
+   */
+  PARALLEL_NODE_FORK = 5,
+  /**
+   * 并行分支聚合
+   */
+  PARALLEL_NODE_JOIN = 6,
+  /**
+   * 包容分支分叉节点
+   */
+  INCLUSIVE_NODE_FORK = 7,
+  /**
+   * 包容分支聚合节点
+   */
+  INCLUSIVE_NODE_JOIN = 8
+}
+
+export type SimpleFlowNode = {
+  id: string,
+  type: NodeType,
+  name: string,
+  showText?: string,
+  attributes?: any,
+  // 孩子节点
+  childNode?: SimpleFlowNode,
+  // 条件节点
+  conditionNodes?: SimpleFlowNode[]
+}
+
+export const NODE_DEFAULT_TEXT = new Map<number,string>()
+NODE_DEFAULT_TEXT.set(NodeType.USER_TASK_NODE, '请配置该节点')
+NODE_DEFAULT_TEXT.set(NodeType.CONDITION_NODE, '请设置条件')
+
+export const NODE_DEFAULT_NAME = new Map<number,string>()
+NODE_DEFAULT_NAME.set(NodeType.USER_TASK_NODE, '审批人')
+NODE_DEFAULT_NAME.set(NodeType.CONDITION_NODE, '条件')
+
+
+export const APPROVE_METHODS: DictDataVO [] = [
+  { label: '单人审批', value: 1 },
+  { label: '多人会签(需所有审批人同意)', value: 2 },
+  { label: '多人或签(一名审批人同意即可)', value: 3 },
+  { label: '依次审批(按顺序依次审批)', value: 4 }
+  // TODO 更多的类型
+]

+ 4 - 0
src/components/SimpleProcessDesignerV2/src/index.ts

@@ -0,0 +1,4 @@
+import SimpleProcessDesigner from './SimpleProcessDesigner.vue'
+import '../theme/simple-process-designer.scss'
+
+export { SimpleProcessDesigner }

+ 13 - 0
src/components/SimpleProcessDesignerV2/src/nodes/EndEventNode.vue

@@ -0,0 +1,13 @@
+<template>
+  <div class="end-node-wrapper">
+    <div class="end-node-box">
+      <span class="node-fixed-name" title="结束">结束</span>
+    </div>
+  </div>
+</template>
+<script setup lang="ts">
+defineOptions({
+  name: 'EndEventNode'
+})
+</script>
+<style lang="scss" scoped></style>

+ 35 - 0
src/components/SimpleProcessDesignerV2/src/nodes/StartEventNode.vue

@@ -0,0 +1,35 @@
+<template>
+  <div class="start-node-wrapper">
+    <div class="start-node-container">
+      <div class="start-node-box">
+        <Icon icon="ep:avatar" />
+        <span class="node-fixed-name" :title="currentNode.name">{{currentNode.name}}</span>
+        <Icon icon="ep:arrow-right-bold" />
+      </div>
+      <!-- 传递子节点给添加节点组件。会在子节点后面添加节点 -->
+      <node-handler v-if="currentNode" v-model:child-node="currentNode.childNode" />
+    </div>
+  </div>
+</template>
+<script setup lang="ts">
+import NodeHandler from '../NodeHandler.vue'
+import { SimpleFlowNode }  from '../consts';
+defineOptions({
+  name: 'StartEventNode'
+})
+const props = defineProps({
+  flowNode : {
+    type: Object as () => SimpleFlowNode,
+    default: () => null
+  }
+});
+const currentNode = ref<SimpleFlowNode>(props.flowNode);
+
+watch(() => props.flowNode, (newValue) => {  
+  console.log('start node value changed=================>', newValue);
+  currentNode.value = newValue;  
+}); 
+</script>
+<style lang="scss" scoped>
+
+</style>

BIN
src/components/SimpleProcessDesignerV2/theme/iconfont.ttf


BIN
src/components/SimpleProcessDesignerV2/theme/iconfont.woff


BIN
src/components/SimpleProcessDesignerV2/theme/iconfont.woff2


+ 521 - 0
src/components/SimpleProcessDesignerV2/theme/simple-process-designer.scss

@@ -0,0 +1,521 @@
+.simple-flow-canvas {
+  position: absolute;
+  inset: 0;
+  z-index: 1;
+  overflow: auto;
+  background-color: #fafafa;
+  user-select: none;
+
+  .simple-flow-container {
+    position: relative;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+
+    .top-area-container {
+      position: sticky;
+      inset: 0;
+      display: flex;
+      width: 100%;
+      height: 42px;
+      z-index: 1;
+      // padding: 4px 0;
+      background-color: #fff;
+      justify-content: flex-end;
+      align-items: center;
+
+      .top-actions {
+        display: flex;
+        margin: 4px;
+        margin-right: 8px;
+        align-items: center;
+
+        .canvas-control {
+          font-size: 16px;
+
+          .control-scale-group {
+            display: inline-flex;
+            align-items: center;
+            margin-right: 8px;
+
+            .control-scale-button {
+              display: inline-flex;
+              width: 28px;
+              height: 28px;
+              padding: 2px;
+              text-align: center;
+              cursor: pointer;
+              justify-content: center;
+              align-items: center;
+            }
+
+            .control-scale-label {
+              margin: 0 4px;
+              font-size: 14px;
+            }
+          }
+        }
+      }
+    }
+
+    .scale-container {
+      display: flex;
+      flex-direction: column;
+      justify-content: center;
+      align-items: center;
+      margin-top: 16px;
+      background-color: #fafafa;
+      transform-origin: 50% 0 0;
+      transform: scale(1);
+      transition: transform 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
+
+      // 节点容器 定义节点宽度
+      .node-container {
+        width: 200px;
+      }
+      // 节点
+      .node-box {
+        position: relative;
+        display: flex;
+        min-height: 70px;
+        padding: 5px 10px 8px;
+        cursor: pointer;
+        background-color: #fff;
+        flex-direction: column;
+        border: 2px solid transparent;
+        // border-color: #0089ff;
+        border-radius: 8px;
+        // border-color: #0089ff;
+        box-shadow: 0 1px 4px 0 rgba(10, 30, 65, 0.16);
+        transition: all 0.1s cubic-bezier(0.645, 0.045, 0.355, 1);
+
+        &:hover {
+          // border-color: #0089ff;
+          .node-toolbar {
+            opacity: 1;
+          }
+        }
+
+        // 普通节点标题
+        .node-title-container {
+          display: flex;
+          padding: 4px;
+          cursor: pointer;
+          border-radius: 4px 4px 0 0;
+          align-items: center;
+
+          .node-title-icon {
+            display: flex;
+            align-items: center;
+          }
+
+          .node-title {
+            margin-left: 4px;
+            font-size: 14px;
+            font-weight: 600;
+            white-space: nowrap;
+            overflow: hidden;
+            text-overflow: ellipsis;
+            color: #1f1f1f;
+            // vertical-align: middle;
+          }
+        }
+
+        // 条件节点标题
+        .branch-node-title-container {
+          display: flex;
+          padding: 4px;
+          cursor: pointer;
+          border-radius: 4px 4px 0 0;
+          align-items: center;
+          justify-content: space-between;
+
+          .branch-title {
+            max-width: 120px;
+            font-size: 13px;
+            font-weight: 600;
+            color: #f60;
+          }
+
+          .branch-priority {
+            min-width: 50px;
+            font-size: 13px;
+          }
+        }
+
+        .node-content {
+          display: flex;
+          min-height: 32px;
+          padding: 4px 8px;
+          margin-top: 4px;
+          line-height: 32px;
+          justify-content: space-between;
+          align-items: center;
+          color: #111f2c;
+          background: rgba(0, 0, 0, 0.03);
+          border-radius: 4px;
+
+          .node-text {
+            display: -webkit-box;
+            overflow: hidden;
+            font-size: 14px;
+            line-height: 24px;
+            text-overflow: ellipsis;
+            word-break: break-all;
+            -webkit-line-clamp: 2; /* 这将限制文本显示为两行 */
+            -webkit-box-orient: vertical;
+          }
+        }
+
+        .node-toolbar {
+          opacity: 0;
+          position: absolute;
+          top: -26px;
+          right: 10px;
+          display: flex;
+
+          .toolbar-icon {
+            text-align: center;
+            vertical-align: middle;
+            color: #000;
+            border-radius: 4px;
+          }
+        }
+      }
+
+      
+      .node-config-error {
+        border-color: #ff5219;
+      }
+      // 普通节点包装
+      .node-wrapper {
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+        align-items: center;
+      }
+      // 节点连线处理
+      .node-handler-wrapper {
+        position: relative;
+        display: flex;
+        height: 70px;
+        align-items: center;
+        user-select: none;
+        justify-content: center;
+        flex-direction: column;
+
+        &::before {
+          position: absolute;
+          top: 0;
+          right: 0;
+          left: 0;
+          // bottom: 5px;
+          bottom: 0px;
+          z-index: 0;
+          width: 2px;
+          height: 100%;
+          // height: calc(100% - 5px);
+          margin: auto;
+          background-color: #dedede;
+          content: '';
+        }
+
+        .node-handler {
+          .add-icon {
+            position: relative;
+            top: -5px;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            cursor: pointer;
+            width: 25px;
+            height: 25px;
+            color: #fff;
+            background-color: #0089ff;
+            border-radius: 50%;
+
+            &:hover{
+              transform: scale(1.1);
+              
+            }
+          }
+          
+        }
+
+        .node-handler-arrow {
+          position: absolute;
+          bottom: 0;
+          left: 50%;
+          display: flex;
+          transform: translateX(-50%);
+        }
+      }
+
+      
+      // 条件节点包装
+      .branch-node-wrapper {
+        position: relative;
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+        align-items: center;
+        margin-top: 16px;
+
+        .branch-node-container {
+          position: relative;
+          display: flex;
+
+          &::before {
+            position: absolute;
+            height: 100%;
+            width: 4px;
+            background-color:  #fafafa;
+            content: "";
+            left: 50%;
+            transform: translate(-50%);
+          }
+
+          .branch-node-add {
+            position: absolute;
+            top: -18px;
+            left: 50%;
+            z-index: 1;
+            height: 36px;
+            padding: 0 10px;
+            font-size: 12px;
+            line-height: 36px;
+            color: #222;
+            cursor: pointer;
+            background: #fff;
+            border: 2px solid #dedede;
+            border-radius: 18px;
+            transform: translateX(-50%);
+            transform-origin: center center;
+            transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
+          }
+
+          .branch-node-item {
+            position: relative;
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            min-width: 280px;
+            padding: 40px 40px 0;
+            background: transparent;
+            border-top: 2px solid #dedede;
+            border-bottom: 2px solid #dedede;
+
+            &::before {
+              position: absolute;
+              width: 2px;
+              height: 100%;
+              margin: auto;
+              inset: 0;
+              background-color: #dedede;
+              content: '';
+            }
+          }
+          // 覆盖条件节点第一个节点左上角的线
+          .branch-line-first-top {
+            position: absolute;
+            top: -5px;
+            left: -1px;
+            width: 50%;
+            height: 7px;
+            background-color: #fafafa;
+            content: '';
+          }
+          // 覆盖条件节点第一个节点左下角的线
+          .branch-line-first-bottom {
+            position: absolute;
+            bottom: -5px;
+            left: -1px;
+            width: 50%;
+            height: 7px;
+            background-color: #fafafa;
+            content: '';
+          }
+          // 覆盖条件节点最后一个节点右上角的线
+          .branch-line-last-top {
+            position: absolute;
+            top: -5px;
+            right: -1px;
+            width: 50%;
+            height: 7px;
+            background-color: #fafafa;
+            content: '';
+          }
+          // 覆盖条件节点最后一个节点右下角的线
+          .branch-line-last-bottom {
+            position: absolute;
+            right: -1px;
+            bottom: -5px;
+            width: 50%;
+            height: 7px;
+            background-color: #fafafa;
+            content: '';
+          }
+        }
+      }
+
+      .node-fixed-name {
+        display: inline-block;
+        width: auto;
+        padding: 0 4px;
+        overflow: hidden;
+        text-align: center;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+      }
+      // 开始节点包装
+      .start-node-wrapper {
+        position: relative;
+        margin-top: 16px;
+
+        .start-node-container {
+          display: flex;
+          flex-direction: column;
+          justify-content: center;
+          align-items: center;
+
+          .start-node-box {
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            width: 90px;
+            height: 36px;
+            padding: 3px 4px;
+            color: #212121;
+            cursor: pointer;
+            // background: #2c2c2c;
+            background: #fafafa;
+            border-radius: 30px;
+            box-shadow: 0 1px 5px 0 rgba(10, 30, 65, 0.08);
+            box-sizing: border-box;
+          }
+        }
+      }
+
+      // 结束节点包装
+      .end-node-wrapper {
+        margin-bottom: 16px;
+
+        .end-node-box {
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          width: 80px;
+          height: 36px;
+          color: #212121;
+          // background: #6e6e6e;
+          background: #fafafa;
+          border-radius: 30px;
+          box-shadow: 0 1px 5px 0 rgba(10, 30, 65, 0.08);
+          box-sizing: border-box;
+        }
+      }
+
+      // 可编辑的 title 输入框
+      .editable-title-input {
+        height: 20px;
+        line-height: 20px;
+        font-size: 12px;
+        margin-left: 4px;
+        border: 1px solid #d9d9d9;
+        border-radius: 4px;
+        transition: all 0.3s;
+
+        &:focus   {
+          border-color: #40a9ff;
+          outline: 0;
+          box-shadow: 0 0 0 2px rgba(24, 144, 255, .2)
+        }
+      }
+    }
+  }
+
+
+}
+
+// 节点连线气泡卡片样式
+.handler-item-wrapper {
+  display: flex;
+  cursor: pointer;
+
+  .handler-item {
+    margin-right: 8px;
+  }
+
+ .handler-item-icon {
+   width: 80px;
+   height: 80px;
+   background: #fff;
+   border: 1px solid #e2e2e2;
+   border-radius: 50%;
+   transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
+   user-select: none;
+   text-align: center;
+
+   &:hover {
+    background: #3296fa;
+    box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1);
+   }
+
+   .icon-size {
+     font-size: 35px;
+     line-height: 80px;
+   }
+ }
+
+ .approve {
+   color: #ff943e;
+ }
+
+ .handler-item-text {
+   margin-top: 4px;
+   width: 80px;
+   text-align: center;
+ }
+
+}
+
+// iconfont 样式
+@font-face {
+  font-family: "iconfont"; /* Project id 4495938 */
+  src: url('iconfont.woff2?t=1712392083512') format('woff2'),
+      url('iconfont.woff?t=1712392083512') format('woff'),
+      url('iconfont.ttf?t=1712392083512') format('truetype');
+}
+
+.iconfont {
+  font-family: "iconfont" !important;
+  font-size: 16px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+.icon-Inclusive:before {
+  content: "\e602";
+}
+
+.icon-copy:before {
+  content: "\e7eb";
+}
+
+.icon-handle:before {
+  content: "\e61c";
+}
+
+.icon-exclusive:before {
+  content: "\e717";
+}
+
+.icon-approve:before {
+  content: "\e715";
+}
+
+.icon-parallel:before {
+  content: "\e688";
+}

+ 14 - 153
src/views/bpm/simpleWorkflow/index.vue

@@ -1,169 +1,30 @@
 <template>
-  <div>
-    <section class="dingflow-design">
-      <div class="sticky right-0 top-0 z-10 w-full flex justify-end bg-white p-2 pr-4">
-        <el-button type="primary" size="small" @click="test">保存(测试中,待完善)</el-button>
-      </div>
-      <div class="box-scale">
-        <div class="start-event-node">
-          <div class="start-event-node-circle">开始</div>
-        </div>
-        <div class="start-event-node-line"></div>
-        <nodeWrap v-model:nodeConfig="nodeConfig"/>
-       
-        <div class="end-event">
-          <div class="end-event-circle">结束</div>
-        </div>
-      </div>
-    </section>
-  </div>
-  <approverDrawer/>
-  <copyerDrawer />
+  <SimpleProcessDesigner :model-id="modelId"/>
 </template>
-<script lang="ts" setup>
-import nodeWrap from '@/components/SimpleProcessDesigner/src/nodeWrap.vue'
-import approverDrawer from '@/components/SimpleProcessDesigner/src/drawer/approverDrawer.vue'
-import copyerDrawer from '@/components/SimpleProcessDesigner/src/drawer/copyerDrawer.vue'
-import { WorkFlowNode } from '@/components/SimpleProcessDesigner/src/consts'
-import { ref } from 'vue'
-import { saveBpmSimpleModel, getBpmSimpleModel } from '@/api/bpm/simple'
+<script setup lang='ts'>
+import { SimpleProcessDesigner } from '@/components/SimpleProcessDesignerV2/src/';
 import { getModel } from '@/api/bpm/model'
 import { getForm, FormVO } from '@/api/bpm/form'
-defineOptions({ name: 'SimpleWorkflowDesignEditor' })
-const uid = getCurrentInstance().uid
-const router = useRouter() // 路由
-const { query } = useRoute() // 路由的查询
-const modelId = query.modelId
-const message = useMessage() // 国际化
-const nodeConfig = ref<WorkFlowNode>({
-  name: '发起人',
-  type: 0,
-  id: 'StartEvent_' + uid,
-  childNode: undefined,
-  attributes: undefined,
-  conditionNodes: []
+defineOptions({
+  name: 'SimpleWorkflowDesignEditor'
 })
-// 默认的表单字段权限
-const defaultFieldsPermission: any[] = []
-const  formType = ref(20);
-provide('defaultFieldsPermission', defaultFieldsPermission)
+const { query } = useRoute() // 路由的查询
+const modelId : string | undefined = query.modelId  as string;
+const formFields = ref<string[]>([])
+const formType = ref(20);
+provide('formFields', formFields)
 provide('formType', formType)
-const test = async () => {
-  if (!modelId) {
-    message.error('缺少模型 modelId 编号')
-    return
-  }
-  const test = nodeConfig.value
-  console.log('test is ', test)
-  console.log('nodeConfig.value ', nodeConfig.value)
-  const data1 = {
-    modelId: modelId,
-    simpleModelBody: toRaw(nodeConfig.value)
-  }
-  const data = {
-    modelId: modelId,
-    simpleModelBody: nodeConfig.value
-  }
-  console.log('request json data1 is ', data1)
-  const result = await saveBpmSimpleModel(data)
-  console.log('save the result is ', result)
-  if (result) {
-    message.success('修改成功')
-    close()
-  } else {
-    message.alert('修改失败')
-  }
-}
-const close = () => {
-  router.push({ path: '/bpm/manager/model' })
-}
-onMounted(async () => {
-  const bpmnModel = await getModel(modelId)
+onMounted( async () => {
+  const bpmnModel = await getModel(modelId);
   if (bpmnModel) {
     formType.value = bpmnModel.formType
     if (formType.value === 10) {
       const bpmnForm = await getForm(bpmnModel.formId) as unknown as FormVO
-      const formFields = bpmnForm?.fields
-      if (formFields) {
-        formFields.forEach((fieldStr: string) => {
-          const { field, title } = JSON.parse(fieldStr)
-          defaultFieldsPermission.push({
-            field,
-            title,
-            permission: '2'
-          })
-        })
-      }
-      console.log('defaultFieldsPermissions', defaultFieldsPermission);
+      formFields.value = bpmnForm?.fields
     }
   }
-  console.log('the modelId is ', modelId)
-  const result = await getBpmSimpleModel(modelId)
-  if (result) {
-    console.log('get the result is ', result)
-    nodeConfig.value = result
-  }
 })
 </script>
-<style>
-@import url('@/components/SimpleProcessDesigner/theme/workflow.css');
-
-.end-event {
-  display: flex;
-  direction: columns;
-  justify-content: center;
-  align-items: center;
-}
-
-.end-event-circle {
-  display: flex;
-  width: 40px;
-  height: 40px;
-  font-size: 14px;
-  color: #f8f8fa;
-  background-image: linear-gradient(-30deg, #bbbbc4, #d5d5de), linear-gradient(#bcbcc5, #bcbcc5);
-  border-radius: 50%;
-  justify-content: center;
-  align-items: center;
-}
-
-/* .start-event-node {
-  color: #191f2566;
-  text-align: left;
-  border-radius: 50%;
-} */
-.start-event-node {
-  display: flex;
-  direction: columns;
-  justify-content: center;
-  align-items: center;
-}
-
-.start-event-node-circle {
-  display: flex;
-  width: 40px;
-  height: 40px;
-  align-items: center;
-  justify-content: center;
-  font-size: 14px;
-  color: #f8f8fa;
-  background-image: linear-gradient(90deg, #ff6a00, #f78b3e), linear-gradient(#ff6a00, #ff6a00);
-  border-radius: 50%;
-}
-
-.start-event-node-line::before {
-  position: absolute;
-  inset: 0;
-  z-index: -1;
-  width: 2px;
-  height: 100%;
-  margin: auto;
-  background-color: #cacaca;
-  content: '';
-}
+<style lang='scss' scoped>
 
-.start-event-node-line {
-  position: relative;
-  padding: 20px 0 32px;
-}
 </style>

+ 169 - 0
src/views/bpm/simpleWorkflow/index1.vue

@@ -0,0 +1,169 @@
+<template>
+  <div>
+    <section class="dingflow-design">
+      <div class="sticky right-0 top-0 z-10 w-full flex justify-end bg-white p-2 pr-4">
+        <el-button type="primary" size="small" @click="test">保存(测试中,待完善)</el-button>
+      </div>
+      <div class="box-scale">
+        <div class="start-event-node">
+          <div class="start-event-node-circle">开始</div>
+        </div>
+        <div class="start-event-node-line"></div>
+        <nodeWrap v-model:nodeConfig="nodeConfig"/>
+       
+        <div class="end-event">
+          <div class="end-event-circle">结束</div>
+        </div>
+      </div>
+    </section>
+  </div>
+  <approverDrawer/>
+  <copyerDrawer />
+</template>
+<script lang="ts" setup>
+import nodeWrap from '@/components/SimpleProcessDesigner/src/nodeWrap.vue'
+import approverDrawer from '@/components/SimpleProcessDesigner/src/drawer/approverDrawer.vue'
+import copyerDrawer from '@/components/SimpleProcessDesigner/src/drawer/copyerDrawer.vue'
+import { WorkFlowNode } from '@/components/SimpleProcessDesigner/src/consts'
+import { ref } from 'vue'
+import { saveBpmSimpleModel, getBpmSimpleModel } from '@/api/bpm/simple'
+import { getModel } from '@/api/bpm/model'
+import { getForm, FormVO } from '@/api/bpm/form'
+defineOptions({ name: 'SimpleWorkflowDesignEditor2' })
+const uid = getCurrentInstance().uid
+const router = useRouter() // 路由
+const { query } = useRoute() // 路由的查询
+const modelId = query.modelId
+const message = useMessage() // 国际化
+const nodeConfig = ref<WorkFlowNode>({
+  name: '发起人',
+  type: 0,
+  id: 'StartEvent_' + uid,
+  childNode: undefined,
+  attributes: undefined,
+  conditionNodes: []
+})
+// 默认的表单字段权限
+const defaultFieldsPermission: any[] = []
+const  formType = ref(20);
+provide('defaultFieldsPermission', defaultFieldsPermission)
+provide('formType', formType)
+const test = async () => {
+  if (!modelId) {
+    message.error('缺少模型 modelId 编号')
+    return
+  }
+  const test = nodeConfig.value
+  console.log('test is ', test)
+  console.log('nodeConfig.value ', nodeConfig.value)
+  const data1 = {
+    modelId: modelId,
+    simpleModelBody: toRaw(nodeConfig.value)
+  }
+  const data = {
+    modelId: modelId,
+    simpleModelBody: nodeConfig.value
+  }
+  console.log('request json data1 is ', data1)
+  const result = await saveBpmSimpleModel(data)
+  console.log('save the result is ', result)
+  if (result) {
+    message.success('修改成功')
+    close()
+  } else {
+    message.alert('修改失败')
+  }
+}
+const close = () => {
+  router.push({ path: '/bpm/manager/model' })
+}
+onMounted(async () => {
+  const bpmnModel = await getModel(modelId)
+  if (bpmnModel) {
+    formType.value = bpmnModel.formType
+    if (formType.value === 10) {
+      const bpmnForm = await getForm(bpmnModel.formId) as unknown as FormVO
+      const formFields = bpmnForm?.fields
+      if (formFields) {
+        formFields.forEach((fieldStr: string) => {
+          const { field, title } = JSON.parse(fieldStr)
+          defaultFieldsPermission.push({
+            field,
+            title,
+            permission: '2'
+          })
+        })
+      }
+      console.log('defaultFieldsPermissions', defaultFieldsPermission);
+    }
+  }
+  console.log('the modelId is ', modelId)
+  const result = await getBpmSimpleModel(modelId)
+  if (result) {
+    console.log('get the result is ', result)
+    nodeConfig.value = result
+  }
+})
+</script>
+<style>
+@import url('@/components/SimpleProcessDesigner/theme/workflow.css');
+
+.end-event {
+  display: flex;
+  direction: columns;
+  justify-content: center;
+  align-items: center;
+}
+
+.end-event-circle {
+  display: flex;
+  width: 40px;
+  height: 40px;
+  font-size: 14px;
+  color: #f8f8fa;
+  background-image: linear-gradient(-30deg, #bbbbc4, #d5d5de), linear-gradient(#bcbcc5, #bcbcc5);
+  border-radius: 50%;
+  justify-content: center;
+  align-items: center;
+}
+
+/* .start-event-node {
+  color: #191f2566;
+  text-align: left;
+  border-radius: 50%;
+} */
+.start-event-node {
+  display: flex;
+  direction: columns;
+  justify-content: center;
+  align-items: center;
+}
+
+.start-event-node-circle {
+  display: flex;
+  width: 40px;
+  height: 40px;
+  align-items: center;
+  justify-content: center;
+  font-size: 14px;
+  color: #f8f8fa;
+  background-image: linear-gradient(90deg, #ff6a00, #f78b3e), linear-gradient(#ff6a00, #ff6a00);
+  border-radius: 50%;
+}
+
+.start-event-node-line::before {
+  position: absolute;
+  inset: 0;
+  z-index: -1;
+  width: 2px;
+  height: 100%;
+  margin: auto;
+  background-color: #cacaca;
+  content: '';
+}
+
+.start-event-node-line {
+  position: relative;
+  padding: 20px 0 32px;
+}
+</style>