فهرست منبع

Merge branch 'feature/bpm' of https://github.com/yudaocode/yudao-ui-admin-vue3 into feature/bpm

YunaiV 7 ماه پیش
والد
کامیت
6b1c0e7369

+ 3 - 12
src/components/SimpleProcessDesignerV2/src/SimpleProcessModel.vue

@@ -51,6 +51,7 @@ import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from './consts'
 import { useWatchNode } from './node'
 import { ZoomOut, ZoomIn, ScaleToOriginal } from '@element-plus/icons-vue'
 import { isString } from '@/utils/is'
+import download from "@/utils/download";
 
 defineOptions({
   name: 'SimpleProcessModel'
@@ -174,18 +175,7 @@ defineExpose({
 /** 导出 JSON */
 // TODO @zws:增加一个 download 里面搞个 json 更好
 const exportJson = () => {
-  const blob = new Blob([JSON.stringify(processNodeTree.value)])
-  const tempLink = document.createElement('a') // 创建a标签
-  const href = window.URL.createObjectURL(blob) // 创建下载的链接
-  // filename
-  const fileName = `model.json`
-  tempLink.href = href
-  tempLink.target = '_blank'
-  tempLink.download = fileName
-  document.body.appendChild(tempLink)
-  tempLink.click() // 点击下载
-  document.body.removeChild(tempLink) // 下载完成移除元素
-  window.URL.revokeObjectURL(href) // 释放掉 blob 对象
+  download.json(new Blob([JSON.stringify(processNodeTree.value)]), 'model.json')
 }
 
 /** 导入 JSON */
@@ -200,6 +190,7 @@ const importLocalFile = () => {
   reader.onload = function () {
     if (isString(this.result)) {
       processNodeTree.value = JSON.parse(this.result)
+      emits('save', processNodeTree.value)
     }
   }
 }

+ 1 - 0
src/components/bpmnProcessDesigner/package/designer/ProcessDesigner.vue

@@ -547,6 +547,7 @@ const importLocalFile = () => {
   reader.onload = function () {
     let xmlStr = this.result
     createNewDiagram(xmlStr)
+    emit('save', xmlStr)
   }
 }
 /* ------------------------------------------------ refs methods ------------------------------------------------------ */

+ 3 - 0
src/components/bpmnProcessDesigner/package/penal/base/ElementBaseInfo.vue

@@ -152,6 +152,9 @@ watch(
       handleKeyUpdate(props.model.key)
       handleNameUpdate(props.model.name)
     }
+  },
+  {
+    immediate: true
   }
 )
 

+ 56 - 43
src/layout/components/TagsView/src/TagsView.vue

@@ -12,6 +12,11 @@ import { useDesign } from '@/hooks/web/useDesign'
 import { useTemplateRefsList } from '@vueuse/core'
 import { ElScrollbar } from 'element-plus'
 import { useScrollTo } from '@/hooks/event/useScrollTo'
+import { useTagsView } from '@/hooks/web/useTagsView'
+import { cloneDeep } from 'lodash-es'
+
+
+defineOptions({ name: 'TagsView' })
 
 const { getPrefixCls } = useDesign()
 
@@ -19,7 +24,9 @@ const prefixCls = getPrefixCls('tags-view')
 
 const { t } = useI18n()
 
-const { currentRoute, push, replace } = useRouter()
+const { currentRoute, push } = useRouter()
+
+const { closeAll, closeLeft, closeRight, closeOther, closeCurrent, refreshPage } = useTagsView()
 
 const permissionStore = usePermissionStore()
 
@@ -31,6 +38,10 @@ const visitedViews = computed(() => tagsViewStore.getVisitedViews)
 
 const affixTagArr = ref<RouteLocationNormalizedLoaded[]>([])
 
+const selectedTag = computed(() => tagsViewStore.getSelectedTag)
+
+const setSelectTag = tagsViewStore.setSelectedTag
+
 const appStore = useAppStore()
 
 const tagsViewImmerse = computed(() => appStore.getTagsViewImmerse)
@@ -45,82 +56,73 @@ const initTags = () => {
   for (const tag of unref(affixTagArr)) {
     // Must have tag name
     if (tag.name) {
-      tagsViewStore.addVisitedView(tag)
+      tagsViewStore.addVisitedView(cloneDeep(tag))
     }
   }
 }
 
-const selectedTag = ref<RouteLocationNormalizedLoaded>()
-
 // 新增tag
 const addTags = () => {
   const { name } = unref(currentRoute)
   if (name) {
-    selectedTag.value = unref(currentRoute)
+    setSelectTag(unref(currentRoute))
     tagsViewStore.addView(unref(currentRoute))
   }
-  return false
 }
 
 // 关闭选中的tag
 const closeSelectedTag = (view: RouteLocationNormalizedLoaded) => {
-  if (view?.meta?.affix) return
-  tagsViewStore.delView(view)
-  if (isActive(view)) {
-    toLastView()
+  closeCurrent(view, () => {
+    if (isActive(view)) {
+      toLastView()
+    }
+  })
+}
+
+// 去最后一个
+const toLastView = () => {
+  const visitedViews = tagsViewStore.getVisitedViews
+  const latestView = visitedViews.slice(-1)[0]
+  if (latestView) {
+    push(latestView)
+  } else {
+    if (
+      unref(currentRoute).path === permissionStore.getAddRouters[0].path ||
+      unref(currentRoute).path === permissionStore.getAddRouters[0].redirect
+    ) {
+      addTags()
+      return
+    }
+    // You can set another route
+    push(permissionStore.getAddRouters[0].path)
   }
 }
 
 // 关闭全部
 const closeAllTags = () => {
-  tagsViewStore.delAllViews()
-  toLastView()
+  closeAll(() => {
+    toLastView()
+  })
 }
 
 // 关闭其它
 const closeOthersTags = () => {
-  tagsViewStore.delOthersViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
+  closeOther()
 }
 
 // 重新加载
 const refreshSelectedTag = async (view?: RouteLocationNormalizedLoaded) => {
-  if (!view) return
-  tagsViewStore.delCachedView()
-  const { path, query } = view
-  await nextTick()
-  replace({
-    path: '/redirect' + path,
-    query: query
-  })
+  refreshPage(view)
 }
 
 // 关闭左侧
 const closeLeftTags = () => {
-  tagsViewStore.delLeftViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
+  closeLeft()
 }
 
 // 关闭右侧
 const closeRightTags = () => {
-  tagsViewStore.delRightViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
-}
-
-// 跳转到最后一个
-const toLastView = () => {
-  const visitedViews = tagsViewStore.getVisitedViews
-  const latestView = visitedViews.slice(-1)[0]
-  if (latestView) {
-    push(latestView)
-  } else {
-    if (
-      unref(currentRoute).path === permissionStore.getAddRouters[0].path ||
-      unref(currentRoute).path === permissionStore.getAddRouters[0].redirect
-    ) {
-      addTags()
-      return
-    }
-    // TODO: You can set another route
-    push('/')
-  }
+  closeRight()
 }
 
 // 滚动到选中的tag
@@ -209,13 +211,14 @@ const isActive = (route: RouteLocationNormalizedLoaded): boolean => {
 // 所有右键菜单组件的元素
 const itemRefs = useTemplateRefsList<ComponentRef<typeof ContextMenu & ContextMenuExpose>>()
 
-// 右键菜单装填改变的时候
+// 右键菜单状态改变的时候
 const visibleChange = (visible: boolean, tagItem: RouteLocationNormalizedLoaded) => {
   if (visible) {
     for (const v of unref(itemRefs)) {
       const elDropdownMenuRef = v.elDropdownMenuRef
       if (tagItem.fullPath !== v.tagItem.fullPath) {
         elDropdownMenuRef?.handleClose()
+        setSelectTag(tagItem)
       }
     }
   }
@@ -243,6 +246,16 @@ const move = (to: number) => {
   start()
 }
 
+const canShowIcon = (item: RouteLocationNormalizedLoaded) => {
+  if (
+    (item?.matched?.[1]?.meta?.icon && unref(tagsViewIcon)) ||
+    (item?.meta?.affix && unref(tagsViewIcon) && item?.meta?.icon)
+  ) {
+    return true
+  }
+  return false
+}
+
 onBeforeMount(() => {
   initTags()
   addTags()

+ 24 - 2
src/store/modules/tagsView.ts

@@ -4,16 +4,19 @@ import { getRawRoute } from '@/utils/routerHelper'
 import { defineStore } from 'pinia'
 import { store } from '../index'
 import { findIndex } from '@/utils'
+import { useUserStoreWithOut } from './user'
 
 export interface TagsViewState {
   visitedViews: RouteLocationNormalizedLoaded[]
   cachedViews: Set<string>
+  selectedTag?: RouteLocationNormalizedLoaded
 }
 
 export const useTagsViewStore = defineStore('tagsView', {
   state: (): TagsViewState => ({
     visitedViews: [],
-    cachedViews: new Set()
+    cachedViews: new Set(),
+    selectedTag: undefined
   }),
   getters: {
     getVisitedViews(): RouteLocationNormalizedLoaded[] {
@@ -21,6 +24,9 @@ export const useTagsViewStore = defineStore('tagsView', {
     },
     getCachedViews(): string[] {
       return Array.from(this.cachedViews)
+    },
+    getSelectedTag(): RouteLocationNormalizedLoaded | undefined {
+      return this.selectedTag
     }
   },
   actions: {
@@ -98,8 +104,12 @@ export const useTagsViewStore = defineStore('tagsView', {
     },
     // 删除所有tag
     delAllVisitedViews() {
+      const userStore = useUserStoreWithOut()
+
       // const affixTags = this.visitedViews.filter((tag) => tag.meta.affix)
-      this.visitedViews = []
+      this.visitedViews = userStore.getUser
+        ? this.visitedViews.filter((tag) => tag?.meta?.affix)
+        : []
     },
     // 删除其他
     delOthersViews(view: RouteLocationNormalizedLoaded) {
@@ -145,6 +155,18 @@ export const useTagsViewStore = defineStore('tagsView', {
           break
         }
       }
+    },
+    // 设置当前选中的tag
+    setSelectedTag(tag: RouteLocationNormalizedLoaded) {
+      this.selectedTag = tag
+    },
+    setTitle(title: string, path?: string) {
+      for (const v of this.visitedViews) {
+        if (v.path === (path ?? this.selectedTag?.path)) {
+          v.meta.title = title
+          break
+        }
+      }
     }
   },
   persist: false

+ 4 - 0
src/utils/download.ts

@@ -33,6 +33,10 @@ const download = {
   markdown: (data: Blob, fileName: string) => {
     download0(data, fileName, 'text/markdown')
   },
+  // 下载 Json 方法
+  json: (data: Blob, fileName: string) => {
+    download0(data, fileName, 'application/json')
+  },
   // 下载图片(允许跨域)
   image: ({
     url,

+ 6 - 0
src/views/bpm/model/CategoryDraggableModel.vue

@@ -262,6 +262,7 @@ import { checkPermi } from '@/utils/permission'
 import { useUserStoreWithOut } from '@/store/modules/user'
 import { useAppStore } from '@/store/modules/app'
 import { cloneDeep } from 'lodash-es'
+import {useTagsView} from "@/hooks/web/useTagsView";
 
 defineOptions({ name: 'BpmModel' })
 
@@ -498,6 +499,7 @@ const handleDeleteCategory = async () => {
   } catch {}
 }
 
+const tagsView = useTagsView();
 /** 添加流程模型弹窗 */
 const modelFormRef = ref()
 const openModelForm = (type: string, id?: number) => {
@@ -507,6 +509,10 @@ const openModelForm = (type: string, id?: number) => {
     push({
       name: 'BpmModelUpdate',
       params: { id, type }
+    }).then((_) => {
+      if (type === 'copy') {
+        tagsView.setTitle('复制流程')
+      }
     })
   }
 }

+ 6 - 0
src/views/bpm/model/editor/index.vue

@@ -12,6 +12,8 @@
       :additionalModel="controlForm.additionalModel"
       :model="model"
       @save="save"
+      :process-id="modelKey"
+      :process-name="modelName"
     />
     <!-- 流程属性器,负责编辑每个流程节点的属性 -->
     <MyProcessPenal
@@ -53,6 +55,8 @@ provide('formType', formType)
 
 // 注入流程数据
 const xmlString = inject('processData') as Ref
+// 注入模型数据
+const modelData = inject('modelData') as Ref
 
 const modeler = shallowRef() // BPMN Modeler
 const processDesigner = ref()
@@ -69,6 +73,8 @@ const model = ref<ModelApi.ModelVO>() // 流程模型的信息
 /** 初始化 modeler */
 // TODO @zws:需要初始化,不然首次创建后,无法发布!相当于说,key、name 要去赋值下
 const initModeler = async (item) => {
+  //先初始化模型数据
+  model.value = modelData.value
   modeler.value = item
 }
 

+ 3 - 0
src/views/bpm/model/form/index.vue

@@ -145,6 +145,7 @@ const formData: any = ref({
 const processData = ref<any>()
 
 provide('processData', processData)
+provide('modelData', formData)
 
 // 数据列表
 const formList = ref([])
@@ -160,6 +161,8 @@ const initData = async () => {
     // 复制场景
     if (route.params.type === 'copy') {
       delete formData.value.id
+      formData.value.name += '副本'
+      formData.value.key += '_copy'
     }
   } else {
     // 新增场景