add 增加logicflow流程图预览
This commit is contained in:
parent
1ea70dd3ce
commit
3019701856
@ -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);
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user