Commit 45cf482c by 芋道源码 Committed by GitHub

Merge pull request #121 from zws-code/master_fix_bpm

perf: bpm 流程模型表单代码优化. 去掉大量watch 代码. 用依赖注入替换props 传递流程数据. 增加导入导出
parents 89c3af52 b1174313
...@@ -40,7 +40,7 @@ defineOptions({ ...@@ -40,7 +40,7 @@ defineOptions({
name: 'SimpleProcessDesigner' name: 'SimpleProcessDesigner'
}) })
const emits = defineEmits(['success', 'init-finished']) // 保存成功事件 const emits = defineEmits(['success']) // 保存成功事件
const props = defineProps({ const props = defineProps({
modelId: { modelId: {
...@@ -59,13 +59,12 @@ const props = defineProps({ ...@@ -59,13 +59,12 @@ const props = defineProps({
startUserIds: { startUserIds: {
type: Array, type: Array,
required: false required: false
},
value: {
type: [String, Object],
required: false
} }
}) })
const processData = inject('processData') as Ref
const loading = ref(false) const loading = ref(false)
const formFields = ref<string[]>([]) const formFields = ref<string[]>([])
const formType = ref(20) const formType = ref(20)
...@@ -76,9 +75,6 @@ const deptOptions = ref<DeptApi.DeptVO[]>([]) // 部门列表 ...@@ -76,9 +75,6 @@ const deptOptions = ref<DeptApi.DeptVO[]>([]) // 部门列表
const deptTreeOptions = ref() const deptTreeOptions = ref()
const userGroupOptions = ref<UserGroupApi.UserGroupVO[]>([]) // 用户组列表 const userGroupOptions = ref<UserGroupApi.UserGroupVO[]>([]) // 用户组列表
// 添加当前值的引用
const currentValue = ref<SimpleFlowNode | undefined>()
provide('formFields', formFields) provide('formFields', formFields)
provide('formType', formType) provide('formType', formType)
provide('roleList', roleOptions) provide('roleList', roleOptions)
...@@ -88,7 +84,8 @@ provide('deptList', deptOptions) ...@@ -88,7 +84,8 @@ provide('deptList', deptOptions)
provide('userGroupList', userGroupOptions) provide('userGroupList', userGroupOptions)
provide('deptTree', deptTreeOptions) provide('deptTree', deptTreeOptions)
provide('startUserIds', props.startUserIds) provide('startUserIds', props.startUserIds)
provide('tasks', [])
provide('processInstance', {})
const message = useMessage() // 国际化 const message = useMessage() // 国际化
const processNodeTree = ref<SimpleFlowNode | undefined>() const processNodeTree = ref<SimpleFlowNode | undefined>()
provide('processNodeTree', processNodeTree) provide('processNodeTree', processNodeTree)
...@@ -113,70 +110,14 @@ const updateModel = () => { ...@@ -113,70 +110,14 @@ const updateModel = () => {
} }
} }
// 加载流程数据
const loadProcessData = async (data: any) => {
try {
if (data) {
const parsedData = typeof data === 'string' ? JSON.parse(data) : data
processNodeTree.value = parsedData
currentValue.value = parsedData
// 确保数据加载后刷新视图
await nextTick()
if (simpleProcessModelRef.value?.refresh) {
await simpleProcessModelRef.value.refresh()
}
}
} catch (error) {
console.error('加载流程数据失败:', error)
}
}
// 监听属性变化
watch(
() => props.value,
async (newValue, oldValue) => {
if (newValue && newValue !== oldValue) {
await loadProcessData(newValue)
}
},
{ immediate: true, deep: true }
)
// 监听流程节点树变化,自动保存
watch(
() => processNodeTree.value,
async (newValue, oldValue) => {
if (newValue && oldValue && JSON.stringify(newValue) !== JSON.stringify(oldValue)) {
await saveSimpleFlowModel(newValue)
}
},
{ deep: true }
)
const saveSimpleFlowModel = async (simpleModelNode: SimpleFlowNode) => { const saveSimpleFlowModel = async (simpleModelNode: SimpleFlowNode) => {
if (!simpleModelNode) { if (!simpleModelNode) {
return return
} }
// 校验节点
errorNodes = []
validateNode(simpleModelNode, errorNodes)
if (errorNodes.length > 0) {
errorDialogVisible.value = true
return
}
try { try {
if (props.modelId) { processData.value = simpleModelNode
// 编辑模式
const data = {
id: props.modelId,
simpleModel: simpleModelNode
}
await updateBpmSimpleModel(data)
}
// 无论是编辑还是新建模式,都更新当前值并触发事件
currentValue.value = simpleModelNode
emits('success', simpleModelNode) emits('success', simpleModelNode)
} catch (error) { } catch (error) {
console.error('保存失败:', error) console.error('保存失败:', error)
...@@ -247,61 +188,20 @@ onMounted(async () => { ...@@ -247,61 +188,20 @@ onMounted(async () => {
deptTreeOptions.value = handleTree(deptOptions.value as DeptApi.DeptVO[], 'id') deptTreeOptions.value = handleTree(deptOptions.value as DeptApi.DeptVO[], 'id')
// 获取用户组列表 // 获取用户组列表
userGroupOptions.value = await UserGroupApi.getUserGroupSimpleList() userGroupOptions.value = await UserGroupApi.getUserGroupSimpleList()
// 加载流程数据 // 加载流程数据
if (props.modelId) { if (processData.value) {
// 获取 SIMPLE 设计器模型 processNodeTree.value = processData?.value
const result = await getBpmSimpleModel(props.modelId)
if (result) {
await loadProcessData(result)
} else {
updateModel()
}
} else if (props.value) {
await loadProcessData(props.value)
} else { } else {
updateModel() updateModel()
} }
} finally { } finally {
loading.value = false loading.value = false
emits('init-finished')
} }
}) })
const simpleProcessModelRef = ref() const simpleProcessModelRef = ref()
/** 获取当前流程数据 */
const getCurrentFlowData = async () => {
try {
if (simpleProcessModelRef.value) {
const data = await simpleProcessModelRef.value.getCurrentFlowData()
if (data) {
currentValue.value = data
return data
}
}
return currentValue.value
} catch (error) {
console.error('获取流程数据失败:', error)
return currentValue.value
}
}
// 刷新方法
const refresh = async () => {
try {
if (currentValue.value) {
await loadProcessData(currentValue.value)
}
} catch (error) {
console.error('刷新失败:', error)
}
}
defineExpose({ defineExpose({
getCurrentFlowData,
updateModel,
loadProcessData,
refresh
}) })
</script> </script>
...@@ -3,11 +3,31 @@ ...@@ -3,11 +3,31 @@
<div class="position-absolute top-0px right-0px bg-#fff"> <div class="position-absolute top-0px right-0px bg-#fff">
<el-row type="flex" justify="end"> <el-row type="flex" justify="end">
<el-button-group key="scale-control" size="default"> <el-button-group key="scale-control" size="default">
<el-button size="default" @click="exportJson()"><Icon icon="ep:download" />导出</el-button>
<el-button size="default" @click="importJson()"><Icon icon="ep:upload" />导入</el-button>
<!-- 用于打开本地文件-->
<input
type="file"
id="files"
ref="refFile"
style="display: none"
accept=".json"
@change="importLocalFile"
/>
<el-button size="default" :icon="ScaleToOriginal" @click="processReZoom()" /> <el-button size="default" :icon="ScaleToOriginal" @click="processReZoom()" />
<el-button size="default" :plain="true" :icon="ZoomOut" @click="zoomOut()" /> <el-button size="default" :plain="true" :icon="ZoomOut" @click="zoomOut()" />
<el-button size="default" class="w-80px"> {{ scaleValue }}% </el-button> <el-button size="default" class="w-80px"> {{ scaleValue }}% </el-button>
<el-button size="default" :plain="true" :icon="ZoomIn" @click="zoomIn()" /> <el-button size="default" :plain="true" :icon="ZoomIn" @click="zoomIn()" />
</el-button-group> </el-button-group>
<!-- <el-button-->
<!-- v-if="!readonly"-->
<!-- size="default"-->
<!-- class="ml-4px"-->
<!-- type="primary"-->
<!-- :icon="Select"-->
<!-- @click="saveSimpleFlowModel"-->
<!-- >保存模型</el-button-->
<!-- >-->
</el-row> </el-row>
</div> </div>
<div class="simple-process-model" :style="`transform: scale(${scaleValue / 100});`"> <div class="simple-process-model" :style="`transform: scale(${scaleValue / 100});`">
...@@ -33,7 +53,8 @@ ...@@ -33,7 +53,8 @@
import ProcessNodeTree from './ProcessNodeTree.vue' import ProcessNodeTree from './ProcessNodeTree.vue'
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from './consts' import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from './consts'
import { useWatchNode } from './node' import { useWatchNode } from './node'
import { ZoomOut, ZoomIn, ScaleToOriginal } from '@element-plus/icons-vue' import { ZoomOut, ZoomIn, ScaleToOriginal, Select } from '@element-plus/icons-vue'
import { isString } from '@/utils/is'
defineOptions({ defineOptions({
name: 'SimpleProcessModel' name: 'SimpleProcessModel'
...@@ -85,6 +106,16 @@ const processReZoom = () => { ...@@ -85,6 +106,16 @@ const processReZoom = () => {
const errorDialogVisible = ref(false) const errorDialogVisible = ref(false)
let errorNodes: SimpleFlowNode[] = [] let errorNodes: SimpleFlowNode[] = []
const saveSimpleFlowModel = async () => {
errorNodes = []
validateNode(processNodeTree.value, errorNodes)
if (errorNodes.length > 0) {
errorDialogVisible.value = true
return
}
emits('save', processNodeTree.value)
}
// 校验节点设置。 暂时以 showText 为空 未节点错误配置 // 校验节点设置。 暂时以 showText 为空 未节点错误配置
const validateNode = (node: SimpleFlowNode | undefined, errorNodes: SimpleFlowNode[]) => { const validateNode = (node: SimpleFlowNode | undefined, errorNodes: SimpleFlowNode[]) => {
if (node) { if (node) {
...@@ -143,6 +174,36 @@ const getCurrentFlowData = async () => { ...@@ -143,6 +174,36 @@ const getCurrentFlowData = async () => {
defineExpose({ defineExpose({
getCurrentFlowData getCurrentFlowData
}) })
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对象
}
const importJson = () => {
refFile.value.click()
}
const refFile = ref()
// 加载本地文件
const importLocalFile = () => {
const file = refFile.value.files[0]
const reader = new FileReader()
reader.readAsText(file)
reader.onload = function () {
if (isString(this.result)) {
processNodeTree.value = JSON.parse(this.result)
}
}
}
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>
...@@ -308,28 +308,6 @@ const props = defineProps({ ...@@ -308,28 +308,6 @@ const props = defineProps({
} }
}) })
// 监听value变化,重新加载流程图
watch(
() => props.value,
(newValue) => {
if (newValue && bpmnModeler) {
createNewDiagram(newValue)
}
},
{ immediate: true }
)
// 监听processId和processName变化
watch(
[() => props.processId, () => props.processName],
([newId, newName]) => {
if (newId && newName && !props.value) {
createNewDiagram(null)
}
},
{ immediate: true }
)
provide('configGlobal', props) provide('configGlobal', props)
let bpmnModeler: any = null let bpmnModeler: any = null
const defaultZoom = ref(1) const defaultZoom = ref(1)
...@@ -480,6 +458,7 @@ const initModelListeners = () => { ...@@ -480,6 +458,7 @@ const initModelListeners = () => {
emit('commandStack-changed', event) emit('commandStack-changed', event)
emit('input', xml) emit('input', xml)
emit('change', xml) emit('change', xml)
emit('save', xml)
} catch (e: any) { } catch (e: any) {
console.error(`[Process Designer Warn]: ${e.message || e}`) console.error(`[Process Designer Warn]: ${e.message || e}`)
} }
......
...@@ -243,7 +243,7 @@ const move = (to: number) => { ...@@ -243,7 +243,7 @@ const move = (to: number) => {
start() start()
} }
onMounted(() => { onBeforeMount(() => {
initTags() initTags()
addTags() addTags()
}) })
......
...@@ -344,7 +344,7 @@ const remainingRouter: AppRouteRecordRaw[] = [ ...@@ -344,7 +344,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
} }
}, },
{ {
path: 'manager/model/update/:id', path: 'manager/model/:type/:id',
component: () => import('@/views/bpm/model/form/index.vue'), component: () => import('@/views/bpm/model/form/index.vue'),
name: 'BpmModelUpdate', name: 'BpmModelUpdate',
meta: { meta: {
......
...@@ -165,6 +165,15 @@ ...@@ -165,6 +165,15 @@
</el-button> </el-button>
<el-button <el-button
link link
type="primary"
@click="openModelForm('copy', scope.row.id)"
v-hasPermi="['bpm:model:update']"
:disabled="!isManagerUser(scope.row)"
>
复制
</el-button>
<el-button
link
class="!ml-5px" class="!ml-5px"
type="primary" type="primary"
@click="handleDeploy(scope.row)" @click="handleDeploy(scope.row)"
...@@ -473,7 +482,7 @@ const openModelForm = (type: string, id?: number) => { ...@@ -473,7 +482,7 @@ const openModelForm = (type: string, id?: number) => {
} else { } else {
push({ push({
name: 'BpmModelUpdate', name: 'BpmModelUpdate',
params: { id } params: { id, type }
}) })
} }
} }
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
/> />
<!-- 流程属性器,负责编辑每个流程节点的属性 --> <!-- 流程属性器,负责编辑每个流程节点的属性 -->
<MyProcessPenal <MyProcessPenal
v-if="isModelerReady && modeler" v-if="modeler"
key="penal" key="penal"
:bpmnModeler="modeler" :bpmnModeler="modeler"
:prefix="controlForm.prefix" :prefix="controlForm.prefix"
...@@ -37,8 +37,8 @@ defineOptions({ name: 'BpmModelEditor' }) ...@@ -37,8 +37,8 @@ defineOptions({ name: 'BpmModelEditor' })
const props = defineProps<{ const props = defineProps<{
modelId?: string modelId?: string
modelKey?: string modelKey: string
modelName?: string modelName: string
value?: string value?: string
}>() }>()
...@@ -51,10 +51,11 @@ const formType = ref(20) ...@@ -51,10 +51,11 @@ const formType = ref(20)
provide('formFields', formFields) provide('formFields', formFields)
provide('formType', formType) provide('formType', formType)
const xmlString = ref<string>('') // BPMN XML //注入 流程数据
const xmlString = inject('processData') as Ref
const modeler = shallowRef() // BPMN Modeler const modeler = shallowRef() // BPMN Modeler
const processDesigner = ref() const processDesigner = ref()
const isModelerReady = ref(false)
const controlForm = ref({ const controlForm = ref({
simulation: true, simulation: true,
labelEditing: false, labelEditing: false,
...@@ -65,154 +66,27 @@ const controlForm = ref({ ...@@ -65,154 +66,27 @@ const controlForm = ref({
}) })
const model = ref<ModelApi.ModelVO>() // 流程模型的信息 const model = ref<ModelApi.ModelVO>() // 流程模型的信息
// 初始化 bpmnInstances
const initBpmnInstances = () => {
if (!modeler.value) return false
try {
const instances = {
modeler: modeler.value,
modeling: modeler.value.get('modeling'),
moddle: modeler.value.get('moddle'),
eventBus: modeler.value.get('eventBus'),
bpmnFactory: modeler.value.get('bpmnFactory'),
elementFactory: modeler.value.get('elementFactory'),
elementRegistry: modeler.value.get('elementRegistry'),
replace: modeler.value.get('replace'),
selection: modeler.value.get('selection')
}
// 检查所有实例是否都存在
return Object.values(instances).every((instance) => instance)
} catch (error) {
console.error('初始化 bpmnInstances 失败:', error)
return false
}
}
/** 初始化 modeler */ /** 初始化 modeler */
const initModeler = async (item) => { const initModeler = async (item) => {
try { modeler.value = item
modeler.value = item
// 等待 modeler 初始化完成
await nextTick()
// 确保 modeler 的所有实例都已经准备好
if (initBpmnInstances()) {
isModelerReady.value = true
emit('init-finished')
// 初始化完成后,设置初始值
if (props.modelId) {
// 编辑模式
const data = await ModelApi.getModel(props.modelId)
model.value = {
...data,
bpmnXml: undefined // 清空 bpmnXml 属性
}
xmlString.value = data.bpmnXml || getDefaultBpmnXml(data.key, data.name)
} else if (props.modelKey && props.modelName) {
// 新建模式
xmlString.value = props.value || getDefaultBpmnXml(props.modelKey, props.modelName)
model.value = {
key: props.modelKey,
name: props.modelName
} as ModelApi.ModelVO
}
// 导入XML并刷新视图
await nextTick()
try {
await modeler.value.importXML(xmlString.value)
if (processDesigner.value?.refresh) {
processDesigner.value.refresh()
}
} catch (error) {
console.error('导入XML失败:', error)
}
} else {
console.error('modeler 实例未完全初始化')
}
} catch (error) {
console.error('初始化 modeler 失败:', error)
}
} }
/** 获取默认的BPMN XML */
const getDefaultBpmnXml = (key: string, name: string) => {
return `<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.activiti.org/processdef">
<process id="${key}" name="${name}" isExecutable="true" />
<bpmndi:BPMNDiagram id="BPMNDiagram">
<bpmndi:BPMNPlane id="${key}_di" bpmnElement="${key}" />
</bpmndi:BPMNDiagram>
</definitions>`
}
/** 添加/修改模型 */ /** 添加/修改模型 */
const save = async (bpmnXml: string) => { const save = async (bpmnXml: string) => {
try { try {
xmlString.value = bpmnXml xmlString.value = bpmnXml
if (props.modelId) { emit('success', bpmnXml)
// 编辑模式
const data = {
...model.value,
bpmnXml: bpmnXml
} as unknown as ModelApi.ModelVO
await ModelApi.updateModelBpmn(data)
emit('success')
} else {
// 新建模式,直接返回XML
emit('success', bpmnXml)
}
} catch (error) { } catch (error) {
console.error('保存失败:', error) console.error('保存失败:', error)
message.error('保存失败') message.error('保存失败')
} }
} }
// 监听 key、name 和 value 的变化
watch(
[() => props.modelKey, () => props.modelName, () => props.value],
async ([newKey, newName, newValue]) => {
if (!props.modelId && isModelerReady.value) {
let shouldRefresh = false
if (newKey && newName) {
const newXml = newValue || getDefaultBpmnXml(newKey, newName)
if (newXml !== xmlString.value) {
xmlString.value = newXml
shouldRefresh = true
}
model.value = {
...model.value,
key: newKey,
name: newName
} as ModelApi.ModelVO
} else if (newValue && newValue !== xmlString.value) {
xmlString.value = newValue
shouldRefresh = true
}
if (shouldRefresh) {
// 确保更新后重新渲染
await nextTick()
if (processDesigner.value?.refresh) {
try {
await modeler.value?.importXML(xmlString.value)
processDesigner.value.refresh()
} catch (error) {
console.error('导入XML失败:', error)
}
}
}
}
},
{ deep: true }
)
// 在组件卸载时清理 // 在组件卸载时清理
onBeforeUnmount(() => { onBeforeUnmount(() => {
isModelerReady.value = false
modeler.value = null modeler.value = null
// 清理全局实例 // 清理全局实例
const w = window as any const w = window as any
...@@ -221,54 +95,7 @@ onBeforeUnmount(() => { ...@@ -221,54 +95,7 @@ onBeforeUnmount(() => {
} }
}) })
/** 获取 XML 字符串 */
const saveXML = async () => {
if (!modeler.value) {
return { xml: xmlString.value }
}
try {
const result = await modeler.value.saveXML({ format: true })
xmlString.value = result.xml
return result
} catch (error) {
console.error('获取XML失败:', error)
return { xml: xmlString.value }
}
}
/** 获取SVG字符串 */
const saveSVG = async () => {
if (!modeler.value) {
return { svg: undefined }
}
try {
return await modeler.value.saveSVG()
} catch (error) {
console.error('获取SVG失败:', error)
return { svg: undefined }
}
}
/** 刷新视图 */
const refresh = async () => {
if (processDesigner.value?.refresh && modeler.value) {
try {
await modeler.value.importXML(xmlString.value)
processDesigner.value.refresh()
} catch (error) {
console.error('刷新视图失败:', error)
}
}
}
// 暴露必要的属性和方法给父组件
defineExpose({
modeler,
isModelerReady,
saveXML,
saveSVG,
refresh
})
</script> </script>
<style lang="scss"> <style lang="scss">
.process-panel__container { .process-panel__container {
......
...@@ -144,10 +144,6 @@ import { DICT_TYPE, getBoolDictOptions, getIntDictOptions } from '@/utils/dict' ...@@ -144,10 +144,6 @@ import { DICT_TYPE, getBoolDictOptions, getIntDictOptions } from '@/utils/dict'
import { UserVO } from '@/api/system/user' import { UserVO } from '@/api/system/user'
const props = defineProps({ const props = defineProps({
modelValue: {
type: Object,
required: true
},
categoryList: { categoryList: {
type: Array, type: Array,
required: true required: true
...@@ -158,8 +154,6 @@ const props = defineProps({ ...@@ -158,8 +154,6 @@ const props = defineProps({
} }
}) })
const emit = defineEmits(['update:modelValue'])
const formRef = ref() const formRef = ref()
const selectedStartUsers = ref<UserVO[]>([]) const selectedStartUsers = ref<UserVO[]>([])
const selectedManagerUsers = ref<UserVO[]>([]) const selectedManagerUsers = ref<UserVO[]>([])
...@@ -177,27 +171,30 @@ const rules = { ...@@ -177,27 +171,30 @@ const rules = {
} }
// 创建本地数据副本 // 创建本地数据副本
const modelData = computed({ const modelData = defineModel<any>()
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val)
})
// 初始化选中的用户 // 初始化选中的用户
watch( watch(
() => props.modelValue, () => modelData.value,
(newVal) => { (newVal) => {
if (newVal.startUserIds?.length) { if (newVal.startUserIds?.length) {
selectedStartUsers.value = props.userList.filter((user: UserVO) => selectedStartUsers.value = props.userList.filter((user: UserVO) =>
newVal.startUserIds.includes(user.id) newVal.startUserIds.includes(user.id)
) as UserVO[] ) as UserVO[]
} else {
selectedStartUsers.value = []
} }
if (newVal.managerUserIds?.length) { if (newVal.managerUserIds?.length) {
selectedManagerUsers.value = props.userList.filter((user: UserVO) => selectedManagerUsers.value = props.userList.filter((user: UserVO) =>
newVal.managerUserIds.includes(user.id) newVal.managerUserIds.includes(user.id)
) as UserVO[] ) as UserVO[]
} else {
selectedManagerUsers.value = []
} }
}, },
{ immediate: true } {
immediate: true
}
) )
/** 打开发起人选择 */ /** 打开发起人选择 */
...@@ -215,58 +212,52 @@ const openManagerUserSelect = () => { ...@@ -215,58 +212,52 @@ const openManagerUserSelect = () => {
/** 处理用户选择确认 */ /** 处理用户选择确认 */
const handleUserSelectConfirm = (_, users: UserVO[]) => { const handleUserSelectConfirm = (_, users: UserVO[]) => {
if (currentSelectType.value === 'start') { if (currentSelectType.value === 'start') {
selectedStartUsers.value = users modelData.value = {
emit('update:modelValue', {
...modelData.value, ...modelData.value,
startUserIds: users.map((u) => u.id) startUserIds: users.map((u) => u.id)
}) }
} else { } else {
selectedManagerUsers.value = users modelData.value = {
emit('update:modelValue', {
...modelData.value, ...modelData.value,
managerUserIds: users.map((u) => u.id) managerUserIds: users.map((u) => u.id)
}) }
} }
} }
/** 处理发起人类型变化 */ /** 处理发起人类型变化 */
const handleStartUserTypeChange = (value: number) => { const handleStartUserTypeChange = (value: number) => {
if (value !== 1) { if (value !== 1) {
selectedStartUsers.value = [] modelData.value = {
emit('update:modelValue', {
...modelData.value, ...modelData.value,
startUserIds: [] startUserIds: []
}) }
} }
} }
/** 处理管理员类型变化 */ /** 处理管理员类型变化 */
const handleManagerUserTypeChange = (value: number) => { const handleManagerUserTypeChange = (value: number) => {
if (value !== 1) { if (value !== 1) {
selectedManagerUsers.value = [] modelData.value = {
emit('update:modelValue', {
...modelData.value, ...modelData.value,
managerUserIds: [] managerUserIds: []
}) }
} }
} }
/** 移除发起人 */ /** 移除发起人 */
const handleRemoveStartUser = (user: UserVO) => { const handleRemoveStartUser = (user: UserVO) => {
selectedStartUsers.value = selectedStartUsers.value.filter((u) => u.id !== user.id) modelData.value = {
emit('update:modelValue', {
...modelData.value, ...modelData.value,
startUserIds: modelData.value.startUserIds.filter((id: number) => id !== user.id) startUserIds: modelData.value.startUserIds.filter((id: number) => id !== user.id)
}) }
} }
/** 移除管理员 */ /** 移除管理员 */
const handleRemoveManagerUser = (user: UserVO) => { const handleRemoveManagerUser = (user: UserVO) => {
selectedManagerUsers.value = selectedManagerUsers.value.filter((u) => u.id !== user.id) modelData.value = {
emit('update:modelValue', {
...modelData.value, ...modelData.value,
managerUserIds: modelData.value.managerUserIds.filter((id: number) => id !== user.id) managerUserIds: modelData.value.managerUserIds.filter((id: number) => id !== user.id)
}) }
} }
/** 表单校验 */ /** 表单校验 */
......
...@@ -70,25 +70,16 @@ import * as FormApi from '@/api/bpm/form' ...@@ -70,25 +70,16 @@ import * as FormApi from '@/api/bpm/form'
import { setConfAndFields2 } from '@/utils/formCreate' import { setConfAndFields2 } from '@/utils/formCreate'
const props = defineProps({ const props = defineProps({
modelValue: {
type: Object,
required: true
},
formList: { formList: {
type: Array, type: Array,
required: true required: true
} }
}) })
const emit = defineEmits(['update:modelValue'])
const formRef = ref() const formRef = ref()
// 创建本地数据副本 // 创建本地数据副本
const modelData = computed({ const modelData = defineModel<any>()
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val)
})
// 表单预览数据 // 表单预览数据
const formPreview = ref({ const formPreview = ref({
......
...@@ -6,10 +6,7 @@ ...@@ -6,10 +6,7 @@
:model-id="modelData.id" :model-id="modelData.id"
:model-key="modelData.key" :model-key="modelData.key"
:model-name="modelData.name" :model-name="modelData.name"
:value="currentBpmnXml"
ref="bpmnEditorRef"
@success="handleDesignSuccess" @success="handleDesignSuccess"
@init-finished="handleEditorInit"
/> />
</template> </template>
...@@ -21,10 +18,7 @@ ...@@ -21,10 +18,7 @@
:model-key="modelData.key" :model-key="modelData.key"
:model-name="modelData.name" :model-name="modelData.name"
:start-user-ids="modelData.startUserIds" :start-user-ids="modelData.startUserIds"
:value="currentSimpleModel"
ref="simpleEditorRef"
@success="handleDesignSuccess" @success="handleDesignSuccess"
@init-finished="handleEditorInit"
/> />
</template> </template>
</template> </template>
...@@ -34,137 +28,16 @@ import { BpmModelType } from '@/utils/constants' ...@@ -34,137 +28,16 @@ import { BpmModelType } from '@/utils/constants'
import BpmModelEditor from '../editor/index.vue' import BpmModelEditor from '../editor/index.vue'
import SimpleModelDesign from '../../simple/SimpleModelDesign.vue' import SimpleModelDesign from '../../simple/SimpleModelDesign.vue'
const props = defineProps({
modelValue: {
type: Object,
required: true
}
})
const emit = defineEmits(['update:modelValue', 'success'])
const bpmnEditorRef = ref()
const simpleEditorRef = ref()
const isEditorInitialized = ref(false)
// 创建本地数据副本 // 创建本地数据副本
const modelData = computed({ const modelData = defineModel<any>()
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val)
})
// 保存当前的流程XML或数据 const processData = inject('processData') as Ref
const currentBpmnXml = ref('')
const currentSimpleModel = ref('')
// 初始化或更新当前的XML数据
const initOrUpdateXmlData = () => {
if (modelData.value) {
if (modelData.value.type === BpmModelType.BPMN) {
currentBpmnXml.value = modelData.value.bpmnXml || ''
} else {
currentSimpleModel.value = modelData.value.simpleModel || ''
}
}
}
// 监听modelValue的变化,更新数据
watch(
() => props.modelValue,
(newVal) => {
if (newVal) {
if (newVal.type === BpmModelType.BPMN) {
if (newVal.bpmnXml && newVal.bpmnXml !== currentBpmnXml.value) {
currentBpmnXml.value = newVal.bpmnXml
// 如果编辑器已经初始化,刷新视图
if (isEditorInitialized.value && bpmnEditorRef.value?.refresh) {
nextTick(() => {
bpmnEditorRef.value.refresh()
})
}
}
} else {
if (newVal.simpleModel && newVal.simpleModel !== currentSimpleModel.value) {
currentSimpleModel.value = newVal.simpleModel
// 如果编辑器已经初始化,刷新视图
if (isEditorInitialized.value && simpleEditorRef.value?.refresh) {
nextTick(() => {
simpleEditorRef.value.refresh()
})
}
}
}
}
},
{ immediate: true, deep: true }
)
/** 编辑器初始化完成的回调 */
const handleEditorInit = async () => {
isEditorInitialized.value = true
// 等待下一个tick,确保编辑器已经准备好
await nextTick()
// 初始化完成后,设置初始值
if (modelData.value.type === BpmModelType.BPMN) {
if (modelData.value.bpmnXml) {
currentBpmnXml.value = modelData.value.bpmnXml
if (bpmnEditorRef.value?.refresh) {
await nextTick()
bpmnEditorRef.value.refresh()
}
}
} else {
if (modelData.value.simpleModel) {
currentSimpleModel.value = modelData.value.simpleModel
if (simpleEditorRef.value?.refresh) {
await nextTick()
simpleEditorRef.value.refresh()
}
}
}
}
/** 获取当前流程数据 */
const getProcessData = async () => {
try {
if (modelData.value.type === BpmModelType.BPMN) {
if (!bpmnEditorRef.value || !isEditorInitialized.value) {
return currentBpmnXml.value || undefined
}
const { xml } = await bpmnEditorRef.value.saveXML()
if (xml) {
currentBpmnXml.value = xml
return xml
}
} else {
if (!simpleEditorRef.value || !isEditorInitialized.value) {
return currentSimpleModel.value || undefined
}
const flowData = await simpleEditorRef.value.getCurrentFlowData()
if (flowData) {
currentSimpleModel.value = flowData
return flowData
}
}
return modelData.value.type === BpmModelType.BPMN
? currentBpmnXml.value
: currentSimpleModel.value
} catch (error) {
console.error('获取流程数据失败:', error)
return modelData.value.type === BpmModelType.BPMN
? currentBpmnXml.value
: currentSimpleModel.value
}
}
/** 表单校验 */ /** 表单校验 */
const validate = async () => { const validate = async () => {
try { try {
// 获取最新的流程数据 // 获取最新的流程数据
const processData = await getProcessData() if (!processData.value) {
if (!processData) {
throw new Error('请设计流程') throw new Error('请设计流程')
} }
return true return true
...@@ -172,27 +45,19 @@ const validate = async () => { ...@@ -172,27 +45,19 @@ const validate = async () => {
throw error throw error
} }
} }
/** 处理设计器保存成功 */ /** 处理设计器保存成功 */
const handleDesignSuccess = async (data?: any) => { const handleDesignSuccess = async (data?: any) => {
if (data) { if (data) {
if (modelData.value.type === BpmModelType.BPMN) {
currentBpmnXml.value = data
} else {
currentSimpleModel.value = data
}
// 创建新的对象以触发响应式更新 // 创建新的对象以触发响应式更新
const newModelData = { const newModelData = {
...modelData.value, ...modelData.value,
bpmnXml: modelData.value.type === BpmModelType.BPMN ? data : null, bpmnXml: modelData.value.type === BpmModelType.BPMN ? data : null,
simpleModel: modelData.value.type === BpmModelType.BPMN ? null : data simpleModel: modelData.value.type === BpmModelType.BPMN ? null : data
} }
// 使用emit更新父组件的数据 // 使用emit更新父组件的数据
await nextTick() await nextTick()
emit('update:modelValue', newModelData) //更新表单的模型数据部分
emit('success', data) modelData.value = newModelData
} }
} }
...@@ -203,33 +68,14 @@ const showDesigner = computed(() => { ...@@ -203,33 +68,14 @@ const showDesigner = computed(() => {
// 组件创建时初始化数据 // 组件创建时初始化数据
onMounted(() => { onMounted(() => {
initOrUpdateXmlData()
}) })
// 组件卸载前保存数据 // 组件卸载前保存数据
onBeforeUnmount(async () => { onBeforeUnmount(async () => {
try {
// 获取并保存最新的流程数据
const data = await getProcessData()
if (data) {
// 创建新的对象以触发响应式更新
const newModelData = {
...modelData.value,
bpmnXml: modelData.value.type === BpmModelType.BPMN ? data : null,
simpleModel: modelData.value.type === BpmModelType.BPMN ? null : data
}
// 使用emit更新父组件的数据
await nextTick()
emit('update:modelValue', newModelData)
}
} catch (error) {
console.error('保存数据失败:', error)
}
}) })
defineExpose({ defineExpose({
validate, validate
getProcessData
}) })
</script> </script>
...@@ -71,7 +71,6 @@ ...@@ -71,7 +71,6 @@
v-if="currentStep === 2" v-if="currentStep === 2"
v-model="formData" v-model="formData"
ref="processDesignRef" ref="processDesignRef"
@success="handleDesignSuccess"
/> />
</div> </div>
</div> </div>
...@@ -118,7 +117,8 @@ const validateProcess = async () => { ...@@ -118,7 +117,8 @@ const validateProcess = async () => {
await processDesignRef.value?.validate() await processDesignRef.value?.validate()
} }
const currentStep = ref(0) // 步骤控制 const currentStep = ref(-1) // 步骤控制 一开始全部不展示等当前页面数据初始化完成
const steps = [ const steps = [
{ title: '基本信息', validator: validateBasic }, { title: '基本信息', validator: validateBasic },
{ title: '表单设计', validator: validateForm }, { title: '表单设计', validator: validateForm },
...@@ -145,17 +145,27 @@ const formData: any = ref({ ...@@ -145,17 +145,27 @@ const formData: any = ref({
managerUserIds: [] managerUserIds: []
}) })
//流程数据
const processData = ref<any>()
provide("processData", processData)
// 数据列表 // 数据列表
const formList = ref([]) const formList = ref([])
const categoryList = ref([]) const categoryList = ref([])
const userList = ref<UserApi.UserVO[]>([]) const userList = ref<UserApi.UserVO[]>([])
/** 初始化数据 */ /** 初始化数据 */
const initData = async () => { const initData = async () => {
const modelId = route.params.id as string const modelId = route.params.id as string
if (modelId) { if (modelId) {
// 修改场景 // 修改场景
formData.value = await ModelApi.getModel(modelId) formData.value = await ModelApi.getModel(modelId)
// 复制场景
if (route.params.type === 'copy') {
delete formData.value.id
}
} else { } else {
// 新增场景 // 新增场景
formData.value.managerUserIds.push(userStore.getUser.id) formData.value.managerUserIds.push(userStore.getUser.id)
...@@ -167,59 +177,49 @@ const initData = async () => { ...@@ -167,59 +177,49 @@ const initData = async () => {
categoryList.value = await CategoryApi.getCategorySimpleList() categoryList.value = await CategoryApi.getCategorySimpleList()
// 获取用户列表 // 获取用户列表
userList.value = await UserApi.getSimpleUserList() userList.value = await UserApi.getSimpleUserList()
currentStep.value = 0
} }
//根据类型切换流程数据
watch(async () => formData.value.type, (newValue, oldValue) => {
if (formData.value.type === BpmModelType.BPMN) {
processData.value = formData.value.bpmnXml
} else if (formData.value.type === BpmModelType.SIMPLE) {
processData.value = formData.value.simpleModel
}
console.log('加载流程数据', processData.value)
}, {
immediate: true,
})
/** 校验所有步骤数据是否完整 */ /** 校验所有步骤数据是否完整 */
const validateAllSteps = async () => { const validateAllSteps = async () => {
try { try {
// 基本信息校验 // 基本信息校验
await basicInfoRef.value?.validate() try {
if (!formData.value.key || !formData.value.name || !formData.value.category) { await validateBasic()
} catch (error) {
currentStep.value = 0 currentStep.value = 0
throw new Error('请完善基本信息') throw new Error('请完善基本信息')
} }
// 表单设计校验 // 表单设计校验
await formDesignRef.value?.validate() try {
if (formData.value.formType === 10 && !formData.value.formId) { await validateForm()
currentStep.value = 1 } catch (error) {
throw new Error('请选择流程表单')
}
if (
formData.value.formType === 20 &&
(!formData.value.formCustomCreatePath || !formData.value.formCustomViewPath)
) {
currentStep.value = 1 currentStep.value = 1
throw new Error('请完善自定义表单信息') throw new Error('请完善自定义表单信息')
} }
// 流程设计校验 // 流程设计校验
// 如果已经有流程数据,则不需要重新校验
if (!formData.value.bpmnXml && !formData.value.simpleModel) {
// 如果当前不在第三步,需要先保存当前步骤数据
if (currentStep.value !== 2) {
await steps[currentStep.value].validator()
// 切换到第三步
currentStep.value = 2
// 等待组件渲染完成
await nextTick()
}
// 校验流程设计 // 表单设计校验
await processDesignRef.value?.validate() try {
const processData = await processDesignRef.value?.getProcessData() await validateProcess()
if (!processData) { } catch (error) {
throw new Error('请设计流程') currentStep.value = 2
} throw new Error('请设计流程')
// 保存流程数据
if (formData.value.type === BpmModelType.BPMN) {
formData.value.bpmnXml = processData
formData.value.simpleModel = null
} else {
formData.value.bpmnXml = null
formData.value.simpleModel = processData
}
} }
return true return true
...@@ -239,20 +239,6 @@ const handleSave = async () => { ...@@ -239,20 +239,6 @@ const handleSave = async () => {
...formData.value ...formData.value
} }
// 如果当前在第三步,获取最新的流程设计数据
if (currentStep.value === 2) {
const processData = await processDesignRef.value?.getProcessData()
if (processData) {
if (formData.value.type === BpmModelType.BPMN) {
modelData.bpmnXml = processData
modelData.simpleModel = null
} else {
modelData.bpmnXml = null
modelData.simpleModel = processData
}
}
}
if (formData.value.id) { if (formData.value.id) {
// 修改场景 // 修改场景
await ModelApi.updateModel(modelData) await ModelApi.updateModel(modelData)
...@@ -308,20 +294,6 @@ const handleDeploy = async () => { ...@@ -308,20 +294,6 @@ const handleDeploy = async () => {
...formData.value ...formData.value
} }
// 如果当前在第三步,获取最新的流程设计数据
if (currentStep.value === 2) {
const processData = await processDesignRef.value?.getProcessData()
if (processData) {
if (formData.value.type === BpmModelType.BPMN) {
modelData.bpmnXml = processData
modelData.simpleModel = null
} else {
modelData.bpmnXml = null
modelData.simpleModel = processData
}
}
}
// 先保存所有数据 // 先保存所有数据
if (formData.value.id) { if (formData.value.id) {
await ModelApi.updateModel(modelData) await ModelApi.updateModel(modelData)
...@@ -344,59 +316,26 @@ const handleDeploy = async () => { ...@@ -344,59 +316,26 @@ const handleDeploy = async () => {
/** 步骤切换处理 */ /** 步骤切换处理 */
const handleStepClick = async (index: number) => { const handleStepClick = async (index: number) => {
try { try {
// 如果是切换到第三步(流程设计),需要校验key和name console.log('index', index);
if (index === 2) { if (index !== 0) {
if (!formData.value.key || !formData.value.name) { await validateBasic()
message.warning('请先填写流程标识和流程名称')
return
}
} }
if (index !== 1) {
// 保存当前步骤的数据 await validateForm()
if (currentStep.value === 2) { }
const processData = await processDesignRef.value?.getProcessData() if (index !== 2) {
if (processData) { await validateProcess()
if (formData.value.type === BpmModelType.BPMN) {
formData.value.bpmnXml = processData
formData.value.simpleModel = null
} else {
formData.value.bpmnXml = null
formData.value.simpleModel = processData
}
}
} else {
// 只有在向后切换时才进行校验
if (index > currentStep.value) {
if (typeof steps[currentStep.value].validator === 'function') {
await steps[currentStep.value].validator()
}
}
} }
// 切换步骤 // 切换步骤
currentStep.value = index currentStep.value = index
// 如果切换到流程设计步骤,等待组件渲染完成后刷新设计器
if (index === 2) {
await nextTick()
// 等待更长时间确保组件完全初始化
await new Promise(resolve => setTimeout(resolve, 200))
if (processDesignRef.value?.refresh) {
await processDesignRef.value.refresh()
}
}
} catch (error) { } catch (error) {
console.error('步骤切换失败:', error) console.error('步骤切换失败:', error)
message.warning('请先完善当前步骤必填信息') message.warning('请先完善当前步骤必填信息')
} }
} }
/** 处理设计器保存成功 */
const handleDesignSuccess = (bpmnXml?: string) => {
if (bpmnXml) {
formData.value.bpmnXml = bpmnXml
}
}
/** 返回列表页 */ /** 返回列表页 */
const handleBack = () => { const handleBack = () => {
......
...@@ -207,6 +207,8 @@ const getList = async () => { ...@@ -207,6 +207,8 @@ const getList = async () => {
/** 初始化 **/ /** 初始化 **/
onMounted(() => { onMounted(() => {
})
onActivated(()=>{
getList() getList()
}) })
</script> </script>
......
...@@ -4,9 +4,7 @@ ...@@ -4,9 +4,7 @@
:model-id="modelId" :model-id="modelId"
:model-key="modelKey" :model-key="modelKey"
:model-name="modelName" :model-name="modelName"
:value="currentValue"
@success="handleSuccess" @success="handleSuccess"
@init-finished="handleInit"
:start-user-ids="startUserIds" :start-user-ids="startUserIds"
ref="designerRef" ref="designerRef"
/> />
...@@ -23,133 +21,30 @@ const props = defineProps<{ ...@@ -23,133 +21,30 @@ const props = defineProps<{
modelId?: string modelId?: string
modelKey?: string modelKey?: string
modelName?: string modelName?: string
value?: string
startUserIds?: number[] startUserIds?: number[]
}>() }>()
const emit = defineEmits(['success', 'init-finished']) const emit = defineEmits(['success'])
const designerRef = ref() const designerRef = ref()
const isInitialized = ref(false)
const currentValue = ref('')
// 初始化或更新当前值
const initOrUpdateValue = async () => {
console.log('initOrUpdateValue', props.value)
if (props.value) {
currentValue.value = props.value
// 如果设计器已经初始化,立即加载数据
if (isInitialized.value && designerRef.value) {
try {
await designerRef.value.loadProcessData(props.value)
await nextTick()
if (designerRef.value.refresh) {
await designerRef.value.refresh()
}
} catch (error) {
console.error('加载流程数据失败:', error)
}
}
}
}
// 监听属性变化
watch(
[() => props.modelKey, () => props.modelName, () => props.value],
async ([newKey, newName, newValue], [oldKey, oldName, oldValue]) => {
if (designerRef.value && isInitialized.value) {
try {
if (newKey && newName && (newKey !== oldKey || newName !== oldName)) {
await designerRef.value.updateModel(newKey, newName)
}
if (newValue && newValue !== oldValue) {
currentValue.value = newValue
await designerRef.value.loadProcessData(newValue)
await nextTick()
if (designerRef.value.refresh) {
await designerRef.value.refresh()
}
}
} catch (error) {
console.error('更新流程数据失败:', error)
}
}
},
{ deep: true, immediate: true }
)
// 初始化完成回调
const handleInit = async () => {
try {
isInitialized.value = true
emit('init-finished')
// 等待下一个tick,确保设计器已经准备好
await nextTick()
// 初始化完成后,设置初始值
if (props.modelKey && props.modelName) {
await designerRef.value.updateModel(props.modelKey, props.modelName)
}
if (props.value) {
currentValue.value = props.value
await designerRef.value.loadProcessData(props.value)
// 再次刷新确保数据正确加载
await nextTick()
if (designerRef.value.refresh) {
await designerRef.value.refresh()
}
}
} catch (error) {
console.error('初始化流程数据失败:', error)
}
}
// 修改成功回调 // 修改成功回调
const handleSuccess = (data?: any) => { const handleSuccess = (data?: any) => {
console.warn('handleSuccess', data) console.info('handleSuccess', data)
if (data && data !== currentValue.value) { if (data) {
currentValue.value = data
emit('success', data) emit('success', data)
} }
} }
/** 获取当前流程数据 */
const getCurrentFlowData = async () => {
try {
if (designerRef.value) {
const data = await designerRef.value.getCurrentFlowData()
if (data) {
currentValue.value = data
}
return data
}
return currentValue.value || undefined
} catch (error) {
console.error('获取流程数据失败:', error)
return currentValue.value || undefined
}
}
// 组件创建时初始化数据 // 组件创建时初始化数据
onMounted(() => { onMounted(() => {
initOrUpdateValue()
}) })
// 组件卸载前保存数据 // 组件卸载前保存数据
onBeforeUnmount(async () => { onBeforeUnmount(async () => {
try {
const data = await getCurrentFlowData()
if (data) {
emit('success', data)
}
} catch (error) {
console.error('保存数据失败:', error)
}
}) })
defineExpose({ defineExpose({
getCurrentFlowData,
refresh: () => designerRef.value?.refresh?.()
}) })
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment