add 增加logicflow流程图预览
This commit is contained in:
		| @ -22,6 +22,7 @@ | |||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "@element-plus/icons-vue": "2.3.1", |     "@element-plus/icons-vue": "2.3.1", | ||||||
|     "@highlightjs/vue-plugin": "2.1.0", |     "@highlightjs/vue-plugin": "2.1.0", | ||||||
|  |     "@logicflow/core": "^2.0.13", | ||||||
|     "@vueup/vue-quill": "1.2.0", |     "@vueup/vue-quill": "1.2.0", | ||||||
|     "@vueuse/core": "13.1.0", |     "@vueuse/core": "13.1.0", | ||||||
|     "animate.css": "4.1.1", |     "animate.css": "4.1.1", | ||||||
|  | |||||||
| @ -3,21 +3,7 @@ | |||||||
|     <el-dialog v-model="visible" draggable title="审批记录" :width="props.width" :height="props.height" :close-on-click-modal="false"> |     <el-dialog v-model="visible" draggable title="审批记录" :width="props.width" :height="props.height" :close-on-click-modal="false"> | ||||||
|       <el-tabs v-model="tabActiveName" class="demo-tabs"> |       <el-tabs v-model="tabActiveName" class="demo-tabs"> | ||||||
|         <el-tab-pane v-loading="loading" label="流程图" name="image" style="height: 68vh"> |         <el-tab-pane v-loading="loading" label="流程图" name="image" style="height: 68vh"> | ||||||
|           <div |           <flowChart :defJson="defJson" v-if="defJson != ''" /> | ||||||
|             ref="imageWrapperRef" |  | ||||||
|             class="image-wrapper" |  | ||||||
|             @wheel="handleMouseWheel" |  | ||||||
|             @mousedown="handleMouseDown" |  | ||||||
|             @mousemove="handleMouseMove" |  | ||||||
|             @mouseup="handleMouseUp" |  | ||||||
|             @mouseleave="handleMouseLeave" |  | ||||||
|             @dblclick="resetTransform" |  | ||||||
|             :style="transformStyle" |  | ||||||
|           > |  | ||||||
|             <el-card class="box-card"> |  | ||||||
|               <el-image :src="imgUrl" class="scalable-image" /> |  | ||||||
|             </el-card> |  | ||||||
|           </div> |  | ||||||
|         </el-tab-pane> |         </el-tab-pane> | ||||||
|         <el-tab-pane v-loading="loading" label="审批信息" name="info"> |         <el-tab-pane v-loading="loading" label="审批信息" name="info"> | ||||||
|           <div> |           <div> | ||||||
| @ -76,7 +62,7 @@ | |||||||
| import { flowImage } from '@/api/workflow/instance'; | import { flowImage } from '@/api/workflow/instance'; | ||||||
| import { propTypes } from '@/utils/propTypes'; | import { propTypes } from '@/utils/propTypes'; | ||||||
| import { listByIds } from '@/api/system/oss'; | import { listByIds } from '@/api/system/oss'; | ||||||
|  | import FlowChart from '@/components/Process/flowChart.vue'; | ||||||
| const { proxy } = getCurrentInstance() as ComponentInternalInstance; | const { proxy } = getCurrentInstance() as ComponentInternalInstance; | ||||||
| const { wf_task_status } = toRefs<any>(proxy?.useDict('wf_task_status')); | const { wf_task_status } = toRefs<any>(proxy?.useDict('wf_task_status')); | ||||||
| const props = defineProps({ | const props = defineProps({ | ||||||
| @ -88,6 +74,7 @@ const visible = ref(false); | |||||||
| const historyList = ref<Array<any>>([]); | const historyList = ref<Array<any>>([]); | ||||||
| const tabActiveName = ref('image'); | const tabActiveName = ref('image'); | ||||||
| const imgUrl = ref(''); | const imgUrl = ref(''); | ||||||
|  | const defJson = ref<any>(''); | ||||||
|  |  | ||||||
| //初始化查询审批记录 | //初始化查询审批记录 | ||||||
| const init = async (businessId: string | number) => { | const init = async (businessId: string | number) => { | ||||||
| @ -99,6 +86,7 @@ const init = async (businessId: string | number) => { | |||||||
|     if (resp.data) { |     if (resp.data) { | ||||||
|       historyList.value = resp.data.list; |       historyList.value = resp.data.list; | ||||||
|       imgUrl.value = 'data:image/gif;base64,' + resp.data.image; |       imgUrl.value = 'data:image/gif;base64,' + resp.data.image; | ||||||
|  |       defJson.value = resp.data.defChart.defJson; | ||||||
|       if (historyList.value.length > 0) { |       if (historyList.value.length > 0) { | ||||||
|         historyList.value.forEach((item) => { |         historyList.value.forEach((item) => { | ||||||
|           if (item.ext) { |           if (item.ext) { | ||||||
| @ -124,109 +112,6 @@ const handleDownload = (ossId: string) => { | |||||||
|   proxy?.$download.oss(ossId); |   proxy?.$download.oss(ossId); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const imageWrapperRef = ref<HTMLElement | null>(null); |  | ||||||
| const scale = ref(1); // 初始缩放比例 |  | ||||||
| const maxScale = 3; // 最大缩放比例 |  | ||||||
| const minScale = 0.5; // 最小缩放比例 |  | ||||||
|  |  | ||||||
| let isDragging = false; |  | ||||||
| let startX = 0; |  | ||||||
| let startY = 0; |  | ||||||
| let currentTranslateX = 0; |  | ||||||
| let currentTranslateY = 0; |  | ||||||
|  |  | ||||||
| const handleMouseWheel = (event: WheelEvent) => { |  | ||||||
|   event.preventDefault(); |  | ||||||
|   let newScale = scale.value - event.deltaY / 1000; |  | ||||||
|   newScale = Math.max(minScale, Math.min(newScale, maxScale)); |  | ||||||
|   if (newScale !== scale.value) { |  | ||||||
|     scale.value = newScale; |  | ||||||
|     resetDragPosition(); // 重置拖拽位置,使图片居中 |  | ||||||
|   } |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| const handleMouseDown = (event: MouseEvent) => { |  | ||||||
|   if (scale.value > 1) { |  | ||||||
|     event.preventDefault(); // 阻止默认行为,防止拖拽 |  | ||||||
|     isDragging = true; |  | ||||||
|     startX = event.clientX; |  | ||||||
|     startY = event.clientY; |  | ||||||
|   } |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| const handleMouseMove = (event: MouseEvent) => { |  | ||||||
|   if (!isDragging || !imageWrapperRef.value) return; |  | ||||||
|  |  | ||||||
|   const deltaX = event.clientX - startX; |  | ||||||
|   const deltaY = event.clientY - startY; |  | ||||||
|   startX = event.clientX; |  | ||||||
|   startY = event.clientY; |  | ||||||
|  |  | ||||||
|   currentTranslateX += deltaX; |  | ||||||
|   currentTranslateY += deltaY; |  | ||||||
|  |  | ||||||
|   // 边界检测,防止图片被拖出容器 |  | ||||||
|   const bounds = getBounds(); |  | ||||||
|   if (currentTranslateX > bounds.maxTranslateX) { |  | ||||||
|     currentTranslateX = bounds.maxTranslateX; |  | ||||||
|   } else if (currentTranslateX < bounds.minTranslateX) { |  | ||||||
|     currentTranslateX = bounds.minTranslateX; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   if (currentTranslateY > bounds.maxTranslateY) { |  | ||||||
|     currentTranslateY = bounds.maxTranslateY; |  | ||||||
|   } else if (currentTranslateY < bounds.minTranslateY) { |  | ||||||
|     currentTranslateY = bounds.minTranslateY; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   applyTransform(); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| const handleMouseUp = () => { |  | ||||||
|   isDragging = false; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| const handleMouseLeave = () => { |  | ||||||
|   isDragging = false; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| const resetTransform = () => { |  | ||||||
|   scale.value = 1; |  | ||||||
|   currentTranslateX = 0; |  | ||||||
|   currentTranslateY = 0; |  | ||||||
|   applyTransform(); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| const resetDragPosition = () => { |  | ||||||
|   currentTranslateX = 0; |  | ||||||
|   currentTranslateY = 0; |  | ||||||
|   applyTransform(); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| const applyTransform = () => { |  | ||||||
|   if (imageWrapperRef.value) { |  | ||||||
|     imageWrapperRef.value.style.transform = `translate(${currentTranslateX}px, ${currentTranslateY}px) scale(${scale.value})`; |  | ||||||
|   } |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| const getBounds = () => { |  | ||||||
|   if (!imageWrapperRef.value) return { minTranslateX: 0, maxTranslateX: 0, minTranslateY: 0, maxTranslateY: 0 }; |  | ||||||
|  |  | ||||||
|   const imgRect = imageWrapperRef.value.getBoundingClientRect(); |  | ||||||
|   const containerRect = imageWrapperRef.value.parentElement?.getBoundingClientRect() ?? imgRect; |  | ||||||
|  |  | ||||||
|   const minTranslateX = (containerRect.width - imgRect.width * scale.value) / 2; |  | ||||||
|   const maxTranslateX = -(containerRect.width - imgRect.width * scale.value) / 2; |  | ||||||
|   const minTranslateY = (containerRect.height - imgRect.height * scale.value) / 2; |  | ||||||
|   const maxTranslateY = -(containerRect.height - imgRect.height * scale.value) / 2; |  | ||||||
|  |  | ||||||
|   return { minTranslateX, maxTranslateX, minTranslateY, maxTranslateY }; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| const transformStyle = computed(() => ({ |  | ||||||
|   transition: isDragging ? 'none' : 'transform 0.2s ease' |  | ||||||
| })); |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * 对外暴露子组件方法 |  * 对外暴露子组件方法 | ||||||
|  */ |  */ | ||||||
| @ -235,46 +120,10 @@ defineExpose({ | |||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
| <style lang="scss" scoped> | <style lang="scss" scoped> | ||||||
| .triangle { |  | ||||||
|   box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); |  | ||||||
|   border-radius: 6px; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .triangle::after { |  | ||||||
|   content: ' '; |  | ||||||
|   position: absolute; |  | ||||||
|   top: 8em; |  | ||||||
|   right: 215px; |  | ||||||
|   border: 15px solid; |  | ||||||
|   border-color: transparent #fff transparent transparent; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .container { | .container { | ||||||
|   :deep(.el-dialog .el-dialog__body) { |   :deep(.el-dialog .el-dialog__body) { | ||||||
|     max-height: calc(100vh - 170px) !important; |     max-height: calc(100vh - 170px) !important; | ||||||
|     min-height: calc(100vh - 170px) !important; |     min-height: calc(100vh - 170px) !important; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| .image-wrapper { |  | ||||||
|   width: 100%; |  | ||||||
|   overflow: hidden; |  | ||||||
|   position: relative; |  | ||||||
|   margin: 0 auto; |  | ||||||
|   display: flex; |  | ||||||
|   justify-content: center; |  | ||||||
|   align-items: center; |  | ||||||
|   user-select: none; /* 禁用文本选择 */ |  | ||||||
|   cursor: grab; /* 设置初始鼠标指针为可拖动 */ |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .image-wrapper:active { |  | ||||||
|   cursor: grabbing; /* 当正在拖动时改变鼠标指针 */ |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .scalable-image { |  | ||||||
|   object-fit: contain; |  | ||||||
|   width: 100%; |  | ||||||
|   padding: 15px; |  | ||||||
| } |  | ||||||
| </style> | </style> | ||||||
|  | |||||||
							
								
								
									
										84
									
								
								src/components/Process/flowChart.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								src/components/Process/flowChart.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,84 @@ | |||||||
|  | <template> | ||||||
|  |   <div> | ||||||
|  |     <el-header style="border-bottom: 1px solid rgb(218 218 218); height: auto"> | ||||||
|  |       <div style="display: flex; padding: 10px 0px; justify-content: space-between"> | ||||||
|  |         <div> | ||||||
|  |           <el-tooltip effect="dark" content="自适应屏幕" placement="bottom"> | ||||||
|  |             <el-button size="small" icon="Rank" @click="zoomViewport(1)">自适应屏幕</el-button> | ||||||
|  |           </el-tooltip> | ||||||
|  |           <el-tooltip effect="dark" content="放大" placement="bottom"> | ||||||
|  |             <el-button size="small" icon="ZoomIn" @click="zoomViewport(true)">放大</el-button> | ||||||
|  |           </el-tooltip> | ||||||
|  |           <el-tooltip effect="dark" content="缩小" placement="bottom"> | ||||||
|  |             <el-button size="small" icon="ZoomOut" @click="zoomViewport(false)">缩小</el-button> | ||||||
|  |           </el-tooltip> | ||||||
|  |         </div> | ||||||
|  |         <div> | ||||||
|  |           <el-button size="small" style="border: 1px solid #000">未完成</el-button> | ||||||
|  |           <el-button size="small" style="background-color: #fff8dc; border: 1px solid #ffcd17">进行中</el-button> | ||||||
|  |           <el-button size="small" style="background-color: #f0ffd9; border: 1px solid #9dff00">已完成</el-button> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </el-header> | ||||||
|  |     <div class="container" ref="container"></div> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script setup lang="ts"> | ||||||
|  | import LogicFlow from '@logicflow/core'; | ||||||
|  | import '@logicflow/core/lib/style/index.css'; | ||||||
|  | import Start from './js/start.js'; | ||||||
|  | import Between from './js/between.js'; | ||||||
|  | import Serial from './js/serial.js'; | ||||||
|  | import Parallel from './js/parallel.js'; | ||||||
|  | import End from './js/end.js'; | ||||||
|  | import Skip from './js/skip.js'; | ||||||
|  | import { json2LogicFlowJson } from './js/tool.js'; | ||||||
|  |  | ||||||
|  | // Props 定义方式变化 | ||||||
|  | const props = defineProps({ | ||||||
|  |   defJson: { | ||||||
|  |     type: Object, | ||||||
|  |     default: () => ({}) | ||||||
|  |   } | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | const container = ref(null); | ||||||
|  | const lf = ref(null); | ||||||
|  | const register = () => { | ||||||
|  |   lf.value.register(Start); | ||||||
|  |   lf.value.register(Between); | ||||||
|  |   lf.value.register(Serial); | ||||||
|  |   lf.value.register(Parallel); | ||||||
|  |   lf.value.register(End); | ||||||
|  |   lf.value.register(Skip); | ||||||
|  | }; | ||||||
|  | const zoomViewport = async (zoom) => { | ||||||
|  |   lf.value.zoom(zoom); | ||||||
|  |   // 将内容平移至画布中心 | ||||||
|  |   lf.value.translateCenter(); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | onMounted(async () => { | ||||||
|  |   if (props.defJson) { | ||||||
|  |     const data = json2LogicFlowJson(props.defJson); | ||||||
|  |     lf.value = new LogicFlow({ | ||||||
|  |       container: container.value, | ||||||
|  |       grid: false, | ||||||
|  |       isSilentMode: true, | ||||||
|  |       textEdit: false | ||||||
|  |     }); | ||||||
|  |     register(); | ||||||
|  |     lf.value.render(data); | ||||||
|  |     lf.value.translateCenter(); | ||||||
|  |   } | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <style scoped> | ||||||
|  | /* 样式部分保持不变 */ | ||||||
|  | .container { | ||||||
|  |   width: 100%; | ||||||
|  |   height: 500px; | ||||||
|  | } | ||||||
|  | </style> | ||||||
							
								
								
									
										154
									
								
								src/components/Process/flowChartImg.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										154
									
								
								src/components/Process/flowChartImg.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,154 @@ | |||||||
|  | <template> | ||||||
|  |   <div | ||||||
|  |     ref="imageWrapperRef" | ||||||
|  |     class="image-wrapper" | ||||||
|  |     @wheel="handleMouseWheel" | ||||||
|  |     @mousedown="handleMouseDown" | ||||||
|  |     @mousemove="handleMouseMove" | ||||||
|  |     @mouseup="handleMouseUp" | ||||||
|  |     @mouseleave="handleMouseLeave" | ||||||
|  |     @dblclick="resetTransform" | ||||||
|  |     :style="transformStyle" | ||||||
|  |   > | ||||||
|  |     <el-card class="box-card"> | ||||||
|  |       <el-image :src="props.imgUrl" class="scalable-image" /> | ||||||
|  |     </el-card> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script setup lang="ts"> | ||||||
|  | // Props 定义方式变化 | ||||||
|  | const props = defineProps({ | ||||||
|  |   imgUrl: { | ||||||
|  |     type: String, | ||||||
|  |     default: () => '' | ||||||
|  |   } | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | const imageWrapperRef = ref<HTMLElement | null>(null); | ||||||
|  | const scale = ref(1); // 初始缩放比例 | ||||||
|  | const maxScale = 3; // 最大缩放比例 | ||||||
|  | const minScale = 0.5; // 最小缩放比例 | ||||||
|  |  | ||||||
|  | let isDragging = false; | ||||||
|  | let startX = 0; | ||||||
|  | let startY = 0; | ||||||
|  | let currentTranslateX = 0; | ||||||
|  | let currentTranslateY = 0; | ||||||
|  |  | ||||||
|  | const handleMouseWheel = (event: WheelEvent) => { | ||||||
|  |   event.preventDefault(); | ||||||
|  |   let newScale = scale.value - event.deltaY / 1000; | ||||||
|  |   newScale = Math.max(minScale, Math.min(newScale, maxScale)); | ||||||
|  |   if (newScale !== scale.value) { | ||||||
|  |     scale.value = newScale; | ||||||
|  |     resetDragPosition(); // 重置拖拽位置,使图片居中 | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const handleMouseDown = (event: MouseEvent) => { | ||||||
|  |   if (scale.value > 1) { | ||||||
|  |     event.preventDefault(); // 阻止默认行为,防止拖拽 | ||||||
|  |     isDragging = true; | ||||||
|  |     startX = event.clientX; | ||||||
|  |     startY = event.clientY; | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const handleMouseMove = (event: MouseEvent) => { | ||||||
|  |   if (!isDragging || !imageWrapperRef.value) return; | ||||||
|  |  | ||||||
|  |   const deltaX = event.clientX - startX; | ||||||
|  |   const deltaY = event.clientY - startY; | ||||||
|  |   startX = event.clientX; | ||||||
|  |   startY = event.clientY; | ||||||
|  |  | ||||||
|  |   currentTranslateX += deltaX; | ||||||
|  |   currentTranslateY += deltaY; | ||||||
|  |  | ||||||
|  |   // 边界检测,防止图片被拖出容器 | ||||||
|  |   const bounds = getBounds(); | ||||||
|  |   if (currentTranslateX > bounds.maxTranslateX) { | ||||||
|  |     currentTranslateX = bounds.maxTranslateX; | ||||||
|  |   } else if (currentTranslateX < bounds.minTranslateX) { | ||||||
|  |     currentTranslateX = bounds.minTranslateX; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (currentTranslateY > bounds.maxTranslateY) { | ||||||
|  |     currentTranslateY = bounds.maxTranslateY; | ||||||
|  |   } else if (currentTranslateY < bounds.minTranslateY) { | ||||||
|  |     currentTranslateY = bounds.minTranslateY; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   applyTransform(); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const handleMouseUp = () => { | ||||||
|  |   isDragging = false; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const handleMouseLeave = () => { | ||||||
|  |   isDragging = false; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const resetTransform = () => { | ||||||
|  |   scale.value = 1; | ||||||
|  |   currentTranslateX = 0; | ||||||
|  |   currentTranslateY = 0; | ||||||
|  |   applyTransform(); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const resetDragPosition = () => { | ||||||
|  |   currentTranslateX = 0; | ||||||
|  |   currentTranslateY = 0; | ||||||
|  |   applyTransform(); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const applyTransform = () => { | ||||||
|  |   if (imageWrapperRef.value) { | ||||||
|  |     imageWrapperRef.value.style.transform = `translate(${currentTranslateX}px, ${currentTranslateY}px) scale(${scale.value})`; | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const getBounds = () => { | ||||||
|  |   if (!imageWrapperRef.value) return { minTranslateX: 0, maxTranslateX: 0, minTranslateY: 0, maxTranslateY: 0 }; | ||||||
|  |  | ||||||
|  |   const imgRect = imageWrapperRef.value.getBoundingClientRect(); | ||||||
|  |   const containerRect = imageWrapperRef.value.parentElement?.getBoundingClientRect() ?? imgRect; | ||||||
|  |  | ||||||
|  |   const minTranslateX = (containerRect.width - imgRect.width * scale.value) / 2; | ||||||
|  |   const maxTranslateX = -(containerRect.width - imgRect.width * scale.value) / 2; | ||||||
|  |   const minTranslateY = (containerRect.height - imgRect.height * scale.value) / 2; | ||||||
|  |   const maxTranslateY = -(containerRect.height - imgRect.height * scale.value) / 2; | ||||||
|  |  | ||||||
|  |   return { minTranslateX, maxTranslateX, minTranslateY, maxTranslateY }; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const transformStyle = computed(() => ({ | ||||||
|  |   transition: isDragging ? 'none' : 'transform 0.2s ease' | ||||||
|  | })); | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <style scoped> | ||||||
|  | .image-wrapper { | ||||||
|  |   width: 100%; | ||||||
|  |   overflow: hidden; | ||||||
|  |   position: relative; | ||||||
|  |   margin: 0 auto; | ||||||
|  |   display: flex; | ||||||
|  |   justify-content: center; | ||||||
|  |   align-items: center; | ||||||
|  |   user-select: none; /* 禁用文本选择 */ | ||||||
|  |   cursor: grab; /* 设置初始鼠标指针为可拖动 */ | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .image-wrapper:active { | ||||||
|  |   cursor: grabbing; /* 当正在拖动时改变鼠标指针 */ | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .scalable-image { | ||||||
|  |   object-fit: contain; | ||||||
|  |   width: 100%; | ||||||
|  |   padding: 15px; | ||||||
|  | } | ||||||
|  | </style> | ||||||
							
								
								
									
										21
									
								
								src/components/Process/js/between.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/components/Process/js/between.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | |||||||
|  | import { RectNode, RectNodeModel } from '@logicflow/core'; | ||||||
|  |  | ||||||
|  | class BetweenModel extends RectNodeModel { | ||||||
|  |   initNodeData(data) { | ||||||
|  |     super.initNodeData(data); | ||||||
|  |     this.width = 100; | ||||||
|  |     this.height = 80; | ||||||
|  |     this.radius = 5; | ||||||
|  |   } | ||||||
|  |   getNodeStyle() { | ||||||
|  |     return super.getNodeStyle(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class BetweenView extends RectNode {} | ||||||
|  |  | ||||||
|  | export default { | ||||||
|  |   type: 'between', | ||||||
|  |   model: BetweenModel, | ||||||
|  |   view: BetweenView | ||||||
|  | }; | ||||||
							
								
								
									
										16
									
								
								src/components/Process/js/end.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/components/Process/js/end.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | |||||||
|  | import { CircleNode, CircleNodeModel } from '@logicflow/core'; | ||||||
|  |  | ||||||
|  | class endModel extends CircleNodeModel { | ||||||
|  |   initNodeData(data) { | ||||||
|  |     super.initNodeData(data); | ||||||
|  |     this.r = 20; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class endView extends CircleNode {} | ||||||
|  |  | ||||||
|  | export default { | ||||||
|  |   type: 'end', | ||||||
|  |   model: endModel, | ||||||
|  |   view: endView | ||||||
|  | }; | ||||||
							
								
								
									
										55
									
								
								src/components/Process/js/parallel.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/components/Process/js/parallel.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,55 @@ | |||||||
|  | import { h, PolygonNode, PolygonNodeModel } from '@logicflow/core'; | ||||||
|  |  | ||||||
|  | class ParallelModel extends PolygonNodeModel { | ||||||
|  |   static extendKey = 'ParallelModel'; | ||||||
|  |   constructor(data, graphModel) { | ||||||
|  |     if (!data.text) { | ||||||
|  |       data.text = ''; | ||||||
|  |     } | ||||||
|  |     if (data.text && typeof data.text === 'string') { | ||||||
|  |       data.text = { | ||||||
|  |         value: data.text, | ||||||
|  |         x: data.x, | ||||||
|  |         y: data.y + 40 | ||||||
|  |       }; | ||||||
|  |     } | ||||||
|  |     super(data, graphModel); | ||||||
|  |     this.points = [ | ||||||
|  |       [25, 0], | ||||||
|  |       [50, 25], | ||||||
|  |       [25, 50], | ||||||
|  |       [0, 25] | ||||||
|  |     ]; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class ParallelView extends PolygonNode { | ||||||
|  |   static extendKey = 'ParallelNode'; | ||||||
|  |   getShape() { | ||||||
|  |     const { model } = this.props; | ||||||
|  |     const { x, y, width, height, points } = model; | ||||||
|  |     const style = model.getNodeStyle(); | ||||||
|  |     return h( | ||||||
|  |       'g', | ||||||
|  |       { | ||||||
|  |         transform: `matrix(1 0 0 1 ${x - width / 2} ${y - height / 2})` | ||||||
|  |       }, | ||||||
|  |       h('polygon', { | ||||||
|  |         ...style, | ||||||
|  |         x, | ||||||
|  |         y, | ||||||
|  |         points | ||||||
|  |       }), | ||||||
|  |       h('path', { | ||||||
|  |         d: 'm 23,10 0,12.5 -12.5,0 0,5 12.5,0 0,12.5 5,0 0,-12.5 12.5,0 0,-5 -12.5,0 0,-12.5 -5,0 z', | ||||||
|  |         ...style | ||||||
|  |       }) | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export default { | ||||||
|  |   type: 'parallel', | ||||||
|  |   view: ParallelView, | ||||||
|  |   model: ParallelModel | ||||||
|  | }; | ||||||
							
								
								
									
										55
									
								
								src/components/Process/js/serial.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/components/Process/js/serial.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,55 @@ | |||||||
|  | import { h, PolygonNode, PolygonNodeModel } from '@logicflow/core'; | ||||||
|  |  | ||||||
|  | class SerialModel extends PolygonNodeModel { | ||||||
|  |   static extendKey = 'SerialModel'; | ||||||
|  |   constructor(data, graphModel) { | ||||||
|  |     if (!data.text) { | ||||||
|  |       data.text = ''; | ||||||
|  |     } | ||||||
|  |     if (data.text && typeof data.text === 'string') { | ||||||
|  |       data.text = { | ||||||
|  |         value: data.text, | ||||||
|  |         x: data.x, | ||||||
|  |         y: data.y + 40 | ||||||
|  |       }; | ||||||
|  |     } | ||||||
|  |     super(data, graphModel); | ||||||
|  |     this.points = [ | ||||||
|  |       [25, 0], | ||||||
|  |       [50, 25], | ||||||
|  |       [25, 50], | ||||||
|  |       [0, 25] | ||||||
|  |     ]; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class SerialView extends PolygonNode { | ||||||
|  |   static extendKey = 'SerialNode'; | ||||||
|  |   getShape() { | ||||||
|  |     const { model } = this.props; | ||||||
|  |     const { x, y, width, height, points } = model; | ||||||
|  |     const style = model.getNodeStyle(); | ||||||
|  |     return h( | ||||||
|  |       'g', | ||||||
|  |       { | ||||||
|  |         transform: `matrix(1 0 0 1 ${x - width / 2} ${y - height / 2})` | ||||||
|  |       }, | ||||||
|  |       h('polygon', { | ||||||
|  |         ...style, | ||||||
|  |         x, | ||||||
|  |         y, | ||||||
|  |         points | ||||||
|  |       }), | ||||||
|  |       h('path', { | ||||||
|  |         d: 'm 16,15 7.42857142857143,9.714285714285715 -7.42857142857143,9.714285714285715 3.428571428571429,0 5.714285714285715,-7.464228571428572 5.714285714285715,7.464228571428572 3.428571428571429,0 -7.42857142857143,-9.714285714285715 7.42857142857143,-9.714285714285715 -3.428571428571429,0 -5.714285714285715,7.464228571428572 -5.714285714285715,-7.464228571428572 -3.428571428571429,0 z', | ||||||
|  |         ...style | ||||||
|  |       }) | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export default { | ||||||
|  |   type: 'serial', | ||||||
|  |   view: SerialView, | ||||||
|  |   model: SerialModel | ||||||
|  | }; | ||||||
							
								
								
									
										32
									
								
								src/components/Process/js/skip.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/components/Process/js/skip.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | |||||||
|  | import { PolylineEdge, PolylineEdgeModel } from '@logicflow/core'; | ||||||
|  |  | ||||||
|  | class SkipModel extends PolylineEdgeModel { | ||||||
|  |   setAttributes() { | ||||||
|  |     this.offset = 20; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   getEdgeStyle() { | ||||||
|  |     const style = super.getEdgeStyle(); | ||||||
|  |     const { properties } = this; | ||||||
|  |     if (properties.isActived) { | ||||||
|  |       style.strokeDasharray = '4 4'; | ||||||
|  |     } | ||||||
|  |     return style; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 重写此方法,使保存数据是能带上锚点数据。 | ||||||
|  |    */ | ||||||
|  |   getData() { | ||||||
|  |     const data = super.getData(); | ||||||
|  |     data.sourceAnchorId = this.sourceAnchorId; | ||||||
|  |     data.targetAnchorId = this.targetAnchorId; | ||||||
|  |     return data; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export default { | ||||||
|  |   type: 'skip', | ||||||
|  |   view: PolylineEdge, | ||||||
|  |   model: SkipModel | ||||||
|  | }; | ||||||
							
								
								
									
										16
									
								
								src/components/Process/js/start.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/components/Process/js/start.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | |||||||
|  | import { CircleNode, CircleNodeModel } from '@logicflow/core'; | ||||||
|  |  | ||||||
|  | class StartModel extends CircleNodeModel { | ||||||
|  |   initNodeData(data) { | ||||||
|  |     super.initNodeData(data); | ||||||
|  |     this.r = 20; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class StartView extends CircleNode {} | ||||||
|  |  | ||||||
|  | export default { | ||||||
|  |   type: 'start', | ||||||
|  |   model: StartModel, | ||||||
|  |   view: StartView | ||||||
|  | }; | ||||||
							
								
								
									
										237
									
								
								src/components/Process/js/tool.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										237
									
								
								src/components/Process/js/tool.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,237 @@ | |||||||
|  | const NODE_TYPE_MAP = { 0: 'start', 1: 'between', 2: 'end', 3: 'serial', 4: 'parallel' }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 将warm-flow的定义json数据转成LogicFlow支持的数据格式 | ||||||
|  |  * @param {*} json | ||||||
|  |  * @returns LogicFlow的数据 | ||||||
|  |  */ | ||||||
|  | export const json2LogicFlowJson = (definition) => { | ||||||
|  |   const graphData = { | ||||||
|  |     nodes: [], | ||||||
|  |     edges: [] | ||||||
|  |   }; | ||||||
|  |   // 解析definition属性 | ||||||
|  |   graphData.flowCode = definition.flowCode; | ||||||
|  |   graphData.flowName = definition.flowName; | ||||||
|  |   graphData.version = definition.version; | ||||||
|  |   graphData.fromCustom = definition.fromCustom; | ||||||
|  |   graphData.fromPath = definition.fromPath; | ||||||
|  |   // 解析节点 | ||||||
|  |   const allSkips = definition.nodeList.reduce((acc, node) => { | ||||||
|  |     if (node.skipList && Array.isArray(node.skipList)) { | ||||||
|  |       acc.push(...node.skipList); | ||||||
|  |     } | ||||||
|  |     return acc; | ||||||
|  |   }, []); | ||||||
|  |   const allNodes = definition.nodeList; | ||||||
|  |   // 解析节点 | ||||||
|  |   if (allNodes.length) { | ||||||
|  |     for (var i = 0, len = allNodes.length; i < len; i++) { | ||||||
|  |       let node = allNodes[i]; | ||||||
|  |       let lfNode = { | ||||||
|  |         text: {}, | ||||||
|  |         properties: {} | ||||||
|  |       }; | ||||||
|  |       // 处理节点 | ||||||
|  |       lfNode.type = NODE_TYPE_MAP[node.nodeType]; | ||||||
|  |       lfNode.id = node.nodeCode; | ||||||
|  |       let coordinate = node.coordinate; | ||||||
|  |       if (coordinate) { | ||||||
|  |         const attr = coordinate.split('|'); | ||||||
|  |         const nodeXy = attr[0].split(','); | ||||||
|  |         lfNode.x = parseInt(nodeXy[0]); | ||||||
|  |         lfNode.y = parseInt(nodeXy[1]); | ||||||
|  |         if (attr.length === 2) { | ||||||
|  |           const textXy = attr[1].split(','); | ||||||
|  |           lfNode.text.x = parseInt(textXy[0]); | ||||||
|  |           lfNode.text.y = parseInt(textXy[1]); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       lfNode.text.value = node.nodeName; | ||||||
|  |       lfNode.properties.nodeRatio = node.nodeRatio.toString(); | ||||||
|  |       lfNode.properties.permissionFlag = node.permissionFlag; | ||||||
|  |       lfNode.properties.anyNodeSkip = node.anyNodeSkip; | ||||||
|  |       lfNode.properties.listenerType = node.listenerType; | ||||||
|  |       lfNode.properties.listenerPath = node.listenerPath; | ||||||
|  |       lfNode.properties.formCustom = node.formCustom; | ||||||
|  |       lfNode.properties.formPath = node.formPath; | ||||||
|  |       lfNode.properties.ext = {}; | ||||||
|  |       if (node.ext && typeof node.ext === 'string') { | ||||||
|  |         try { | ||||||
|  |           node.ext = JSON.parse(node.ext); | ||||||
|  |           node.ext.forEach((e) => { | ||||||
|  |             lfNode.properties.ext[e.code] = String(e.value).includes(',') ? e.value.split(',') : String(e.value); | ||||||
|  |           }); | ||||||
|  |         } catch (error) { | ||||||
|  |           console.error('Error parsing JSON:', error); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       lfNode.properties.style = {}; | ||||||
|  |       if (node.status === 2) { | ||||||
|  |         lfNode.properties.style.fill = '#F0FFD9'; | ||||||
|  |         lfNode.properties.style.stroke = '#9DFF00'; | ||||||
|  |       } | ||||||
|  |       if (node.status === 1) { | ||||||
|  |         lfNode.properties.style.fill = '#FFF8DC'; | ||||||
|  |         lfNode.properties.style.stroke = '#FFCD17'; | ||||||
|  |       } | ||||||
|  |       graphData.nodes.push(lfNode); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   if (allSkips.length) { | ||||||
|  |     // 处理边 | ||||||
|  |     let skipEle = null; | ||||||
|  |     let edge = {}; | ||||||
|  |     for (var j = 0, lenn = allSkips.length; j < lenn; j++) { | ||||||
|  |       skipEle = allSkips[j]; | ||||||
|  |       edge = { | ||||||
|  |         text: {}, | ||||||
|  |         properties: {} | ||||||
|  |       }; | ||||||
|  |       edge.id = skipEle.id; | ||||||
|  |       edge.type = 'skip'; | ||||||
|  |       edge.sourceNodeId = skipEle.nowNodeCode; | ||||||
|  |       edge.targetNodeId = skipEle.nextNodeCode; | ||||||
|  |       edge.text = { value: skipEle.skipName }; | ||||||
|  |       edge.properties.skipCondition = skipEle.skipCondition; | ||||||
|  |       edge.properties.skipName = skipEle.skipName; | ||||||
|  |       edge.properties.skipType = skipEle.skipType; | ||||||
|  |       const expr = skipEle.expr; | ||||||
|  |       if (expr) { | ||||||
|  |         edge.properties.expr = skipEle.expr; | ||||||
|  |       } | ||||||
|  |       const coordinate = skipEle.coordinate; | ||||||
|  |       if (coordinate) { | ||||||
|  |         const coordinateXy = coordinate.split('|'); | ||||||
|  |         edge.pointsList = []; | ||||||
|  |         coordinateXy[0].split(';').forEach((item) => { | ||||||
|  |           const pointArr = item.split(','); | ||||||
|  |           edge.pointsList.push({ | ||||||
|  |             x: parseInt(pointArr[0]), | ||||||
|  |             y: parseInt(pointArr[1]) | ||||||
|  |           }); | ||||||
|  |         }); | ||||||
|  |         edge.startPoint = edge.pointsList[0]; | ||||||
|  |         edge.endPoint = edge.pointsList[edge.pointsList.length - 1]; | ||||||
|  |         if (coordinateXy.length > 1) { | ||||||
|  |           let textXy = coordinateXy[1].split(','); | ||||||
|  |           edge.text.x = parseInt(textXy[0]); | ||||||
|  |           edge.text.y = parseInt(textXy[1]); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       graphData.edges.push(edge); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   console.log(graphData); | ||||||
|  |   return graphData; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 将LogicFlow的数据转成warm-flow的json定义文件 | ||||||
|  |  * @param {*} data(...definitionInfo,nodes,edges) | ||||||
|  |  * @returns | ||||||
|  |  */ | ||||||
|  | export const logicFlowJsonToWarmFlow = (data) => { | ||||||
|  |   // 先构建成流程对象 | ||||||
|  |   const definition = { | ||||||
|  |     nodeList: [] | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 根据节点的类型值,获取key | ||||||
|  |    * @param {*} mapValue 节点类型映射 | ||||||
|  |    * @returns | ||||||
|  |    */ | ||||||
|  |   const getNodeTypeValue = (mapValue) => { | ||||||
|  |     for (const key in NODE_TYPE_MAP) { | ||||||
|  |       if (NODE_TYPE_MAP[key] === mapValue) { | ||||||
|  |         return key; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |   /** | ||||||
|  |    * 根据节点的编码,获取节点的类型 | ||||||
|  |    * @param {*} nodeCode 当前节点名称 | ||||||
|  |    * @returns | ||||||
|  |    */ | ||||||
|  |   const getNodeType = (nodeCode) => { | ||||||
|  |     for (const node of definition.nodeList) { | ||||||
|  |       if (nodeCode === node.nodeCode) { | ||||||
|  |         return node.nodeType; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |   /** | ||||||
|  |    * 拼接skip坐标 | ||||||
|  |    * @param {*} edge logicFlow的edge | ||||||
|  |    * @returns | ||||||
|  |    */ | ||||||
|  |   const getCoordinate = (edge) => { | ||||||
|  |     let coordinate = ''; | ||||||
|  |     for (let i = 0; i < edge.pointsList.length; i++) { | ||||||
|  |       coordinate = coordinate + parseInt(edge.pointsList[i].x) + ',' + parseInt(edge.pointsList[i].y); | ||||||
|  |       if (i !== edge.pointsList.length - 1) { | ||||||
|  |         coordinate = coordinate + ';'; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     if (edge.text) { | ||||||
|  |       coordinate = coordinate + '|' + parseInt(edge.text.x) + ',' + parseInt(edge.text.y); | ||||||
|  |     } | ||||||
|  |     return coordinate; | ||||||
|  |   }; | ||||||
|  |   // 流程定义 | ||||||
|  |   definition.id = data.id; | ||||||
|  |   definition.flowCode = data.flowCode; | ||||||
|  |   definition.flowName = data.flowName; | ||||||
|  |   definition.version = data.version; | ||||||
|  |   definition.fromCustom = data.fromCustom; | ||||||
|  |   definition.fromPath = data.fromPath; | ||||||
|  |   // 流程节点 | ||||||
|  |   data.nodes.forEach((anyNode) => { | ||||||
|  |     let node = {}; | ||||||
|  |     node.nodeType = getNodeTypeValue(anyNode.type); | ||||||
|  |     node.nodeCode = anyNode.id; | ||||||
|  |     if (anyNode.text) { | ||||||
|  |       node.nodeName = anyNode.text.value; | ||||||
|  |     } | ||||||
|  |     node.permissionFlag = anyNode.properties.permissionFlag; | ||||||
|  |     node.nodeRatio = anyNode.properties.nodeRatio; | ||||||
|  |     node.anyNodeSkip = anyNode.properties.anyNodeSkip; | ||||||
|  |     node.listenerType = anyNode.properties.listenerType; | ||||||
|  |     node.listenerPath = anyNode.properties.listenerPath; | ||||||
|  |     node.formCustom = anyNode.properties.formCustom; | ||||||
|  |     node.formPath = anyNode.properties.formPath; | ||||||
|  |     node.ext = []; | ||||||
|  |     for (const key in anyNode.properties.ext) { | ||||||
|  |       if (Object.prototype.hasOwnProperty.call(anyNode.properties.ext, key)) { | ||||||
|  |         let e = anyNode.properties.ext[key]; | ||||||
|  |         node.ext.push({ code: key, value: Array.isArray(e) ? e.join(',') : e }); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     node.ext = JSON.stringify(node.ext); | ||||||
|  |     node.coordinate = anyNode.x + ',' + anyNode.y; | ||||||
|  |     if (anyNode.text && anyNode.text.x && anyNode.text.y) { | ||||||
|  |       node.coordinate = node.coordinate + '|' + anyNode.text.x + ',' + anyNode.text.y; | ||||||
|  |     } | ||||||
|  |     node.handlerType = anyNode.properties.handlerType; | ||||||
|  |     node.handlerPath = anyNode.properties.handlerPath; | ||||||
|  |     node.version = definition.version; | ||||||
|  |     node.skipList = []; | ||||||
|  |     data.edges.forEach((anyEdge) => { | ||||||
|  |       if (anyEdge.sourceNodeId === anyNode.id) { | ||||||
|  |         let skip = {}; | ||||||
|  |         skip.skipType = anyEdge.properties.skipType; | ||||||
|  |         skip.skipCondition = anyEdge.properties.skipCondition; | ||||||
|  |         skip.skipName = anyEdge?.text?.value || anyEdge.properties.skipName; | ||||||
|  |         skip.nowNodeCode = anyEdge.sourceNodeId; | ||||||
|  |         skip.nowNodeType = getNodeType(skip.nowNodeCode); | ||||||
|  |         skip.nextNodeCode = anyEdge.targetNodeId; | ||||||
|  |         skip.nextNodeType = getNodeType(skip.nextNodeCode); | ||||||
|  |         skip.coordinate = getCoordinate(anyEdge); | ||||||
|  |         node.skipList.push(skip); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |     definition.nodeList.push(node); | ||||||
|  |   }); | ||||||
|  |   return JSON.stringify(definition); | ||||||
|  | }; | ||||||
		Reference in New Issue
	
	Block a user
	 gssong
					gssong