提交 c90615cf authored 作者: kxjia's avatar kxjia

工作流添加了 多实例,修改了扩展属性

上级 e2396e12
...@@ -320,6 +320,21 @@ ...@@ -320,6 +320,21 @@
"isAttr": true, "isAttr": true,
"type": "Boolean" "type": "Boolean"
}, },
{
"name": "isTransmit",
"isAttr": true,
"type": "Boolean"
},
{
"name": "isReject",
"isAttr": true,
"type": "Boolean"
},
{
"name": "isSend",
"isAttr": true,
"type": "Boolean"
},
{ {
"name": "followUpDate", "name": "followUpDate",
"isAttr": true, "isAttr": true,
......
...@@ -24,6 +24,17 @@ ...@@ -24,6 +24,17 @@
</template> </template>
<user-task-panel :id="elementId" /> <user-task-panel :id="elementId" />
</a-tab-pane> </a-tab-pane>
<!-- 多实例配置 -->
<a-tab-pane
v-if="elementType.indexOf('Task') !== -1"
key="multiInstance"
tab="多实例"
>
<template #tab>
<span><block-outlined /> 多实例</span>
</template>
<multi-instance-panel :id="elementId" />
</a-tab-pane>
<!-- 表单 --> <!-- 表单 -->
<a-tab-pane <a-tab-pane
...@@ -69,7 +80,9 @@ import { ref, watch, onMounted, onUnmounted, computed } from 'vue' ...@@ -69,7 +80,9 @@ import { ref, watch, onMounted, onUnmounted, computed } from 'vue'
import { import {
InfoCircleOutlined, InfoCircleOutlined,
ContainerOutlined, ContainerOutlined,
FormOutlined FormOutlined,
TeamOutlined,
BlockOutlined
} from '@ant-design/icons-vue' } from '@ant-design/icons-vue'
import CommonPanel from './panel/commonPanel.vue' import CommonPanel from './panel/commonPanel.vue'
...@@ -77,6 +90,8 @@ import PropertiesPanel from './panel/PropertiesPanel.vue' ...@@ -77,6 +90,8 @@ import PropertiesPanel from './panel/PropertiesPanel.vue'
import UserTaskPanel from './panel/taskPanel.vue' import UserTaskPanel from './panel/taskPanel.vue'
import FormPanel from './panel/formPanel.vue' import FormPanel from './panel/formPanel.vue'
import ConditionPanel from './panel/conditionPanel.vue' import ConditionPanel from './panel/conditionPanel.vue'
import CandidateGroupPanel from './panel/candidateGroupPanel.vue'
import MultiInstancePanel from './panel/multiInstance.vue'
import { translateNodeName } from "./common/bpmnUtils" import { translateNodeName } from "./common/bpmnUtils"
import { useModelerStore } from './store/modeler-store.js' import { useModelerStore } from './store/modeler-store.js'
...@@ -138,30 +153,27 @@ const getActiveElement = () => { ...@@ -138,30 +153,27 @@ const getActiveElement = () => {
} }
const initFormOnChanged = (element: any) => { const initFormOnChanged = (element: any) => {
let activatedElement = element console.log('[designer] initFormOnChanged:', element?.id, element?.type)
if (!activatedElement && modelerStore.elRegistry) { if (!element) {
activatedElement = modelerStore.elRegistry.find((el: any) => el.type === "bpmn:Process") ??
modelerStore.elRegistry.find((el: any) => el.type === "bpmn:Collaboration")
}
if (!activatedElement) {
elementId.value = "" elementId.value = ""
elementType.value = "" elementType.value = ""
formVisible.value = false formVisible.value = false
return return
} }
modelerStore.setElement(activatedElement) // Process 或 Collaboration 不设置 elementId,但保留之前的值
elementId.value = activatedElement.id if (element.type === 'bpmn:Process' || element.type === 'bpmn:Collaboration') {
elementType.value = activatedElement.type.split(":")[1] || "" return
}
modelerStore.setElement(element)
elementId.value = element.id
elementType.value = element.type.split(":")[1] || ""
// 表单配置只对特定元素可见 // 表单配置只对特定元素可见
formVisible.value = elementType.value === "UserTask" || elementType.value === "StartEvent" formVisible.value = elementType.value === "UserTask" || elementType.value === "StartEvent"
console.log('当前选中元素:', { console.log('[designer] elementId:', elementId.value, 'elementType:', elementType.value)
id: elementId.value,
type: elementType.value,
element: activatedElement
})
} }
</script> </script>
......
...@@ -2,72 +2,42 @@ ...@@ -2,72 +2,42 @@
<div class="panel-tab__content"> <div class="panel-tab__content">
<a-table <a-table
:dataSource="elementPropertyList" :dataSource="elementPropertyList"
:columns="columns" :columns="editableColumns"
size="small" size="small"
:pagination="false" :pagination="false"
bordered bordered
:scroll="{ y: 240 }" :scroll="{ y: 500 }"
> >
<template #bodyCell="{ column, record, index }"> <template #bodyCell="{ column, record, index }">
<template v-if="column.key === 'index'"> <template v-if="column.key === 'name'">
{{ index + 1 }} <a-input v-if="editingIndex === index" v-model:value="record.name" />
<span v-else @click="startEdit(index)">{{ record.name }}</span>
</template> </template>
<template v-if="column.key === 'operation'"> <template v-else-if="column.key === 'value'">
<a-button size="small" type="link" @click="openAttributesForm(record, index)"> <a-input v-if="editingIndex === index" v-model:value="record.value" @blur="finishEdit(index)" />
编辑 <span v-else @click="startEdit(index)">{{ record.value }}</span>
</a-button> </template>
<a-divider type="vertical" /> <template v-else-if="column.key === 'operation'">
<a-button size="small" type="link" danger @click="removeAttributes(record, index)"> <a-button size="small" type="link" danger @click="removeAttributes(index)">
</a-button> </a-button>
</template> </template>
</template> </template>
</a-table> </a-table>
<div class="element-drawer__button"> <div class="element-drawer__button">
<a-button size="small" type="primary" @click="openAttributesForm(null, -1)"> <a-button size="small" type="primary" @click="addProperty">
<template #icon><plus-outlined /></template> <template #icon><plus-outlined /></template>
添加属性 添加属性
</a-button> </a-button>
</div> </div>
<a-modal
v-model:open="propertyFormModelVisible"
title="属性配置"
width="600px"
:getContainer="false"
:destroyOnClose="true"
@cancel="handleCancel"
@ok="saveAttribute"
>
<div style="margin:10px;">
<a-form
:model="propertyForm"
layout="vertical"
size="small"
ref="attributeFormRef"
>
<a-form-item label="属性名:" name="name" :rules="[{ required: true, message: '请输入属性名' }]">
<a-input v-model:value="propertyForm.name" allow-clear />
</a-form-item>
<a-form-item label="属性值:" name="value" :rules="[{ required: true, message: '请输入属性值' }]">
<a-input v-model:value="propertyForm.value" allow-clear />
</a-form-item>
</a-form>
</div>
<template #footer>
<a-button size="small" @click="propertyFormModelVisible = false">取 消</a-button>
<a-button size="small" type="primary" @click="saveAttribute">确 定</a-button>
</template>
</a-modal>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, watch, reactive, nextTick, onMounted } from 'vue' import { ref, watch, reactive, nextTick, onMounted } from 'vue'
import { Modal, message } from 'ant-design-vue' import { message } from 'ant-design-vue'
import { PlusOutlined } from '@ant-design/icons-vue' import { PlusOutlined } from '@ant-design/icons-vue'
import { StrUtil } from "../common/StrUtil" import { StrUtil } from "../common/StrUtil"
import { Form } from 'ant-design-vue'
import { useModelerStore } from '../store/modeler-store.js' import { useModelerStore } from '../store/modeler-store.js'
interface PropertyItem { interface PropertyItem {
...@@ -75,329 +45,137 @@ interface PropertyItem { ...@@ -75,329 +45,137 @@ interface PropertyItem {
value: string value: string
} }
interface PropertyForm {
name?: string
value?: string
}
const props = defineProps<{ const props = defineProps<{
id: string id: string
}>() }>()
const useForm = Form.useForm const modelerStore = useModelerStore()
const elementPropertyList = ref<PropertyItem[]>([]) const elementPropertyList = ref<PropertyItem[]>([])
const otherExtensionList = ref<any[]>([])
const bpmnElementPropertyList = ref<any[]>([]) const bpmnElementPropertyList = ref<any[]>([])
const bpmnElement = ref<any>(null) const bpmnElement = ref<any>(null)
const propertyForm = reactive<PropertyForm>({}) const editingIndex = ref(-1)
const editingPropertyIndex = ref(-1)
const propertyFormModelVisible = ref(false)
const attributeFormRef = ref()
// 表格列定义 const editableColumns = [
const columns = [ // { title: '序号', key: 'index', width: '50px' },
{ { title: '属性名', dataIndex: 'name', key: 'name', ellipsis: true },
title: '序号', { title: '属性值', dataIndex: 'value', key: 'value', ellipsis: true },
key: 'index', { title: '操作', key: 'operation', width: '70px' }
width: '50px'
},
{
title: '属性名',
dataIndex: 'name',
key: 'name',
ellipsis: true,
width: '100px'
},
{
title: '属性值',
dataIndex: 'value',
key: 'value',
ellipsis: true,
width: '100px'
},
{
title: '操作',
key: 'operation',
width: '90px'
}
] ]
const modelerStore = useModelerStore() const defaultProperties = [
{ name: 'isApprove', value: 'false' }
]
// 在组件挂载时初始化
onMounted(() => { onMounted(() => {
resetAttributesList() resetAttributesList()
}) })
// 监听 id 变化
watch(() => props.id, (val) => { watch(() => props.id, (val) => {
if (StrUtil.isNotBlank(val)) { if (StrUtil.isNotBlank(val)) {
resetAttributesList() resetAttributesList()
} }
}) })
// 监听元素变化
watch(() => modelerStore.element, (newElement) => { watch(() => modelerStore.element, (newElement) => {
if (newElement) { if (newElement) {
resetAttributesList() resetAttributesList()
} }
}) })
// 重置属性列表
const resetAttributesList = () => { const resetAttributesList = () => {
console.log('重置属性列表,当前元素:', modelerStore.element)
bpmnElement.value = modelerStore.element bpmnElement.value = modelerStore.element
otherExtensionList.value = []
if (!bpmnElement.value?.businessObject) { if (!bpmnElement.value?.businessObject) {
elementPropertyList.value = [] elementPropertyList.value = []
bpmnElementPropertyList.value = []
return return
} }
const extensionElements = bpmnElement.value.businessObject?.extensionElements const extensionElements = bpmnElement.value.businessObject?.extensionElements
const bpmnElementProperties = extensionElements?.values?.filter((ex: any) => ex.$type === 'flowable:Properties') ?? []
// 查找 flowable:Properties
const bpmnElementProperties = extensionElements?.values?.filter((ex: any) => {
if (ex.$type !== 'flowable:Properties') {
otherExtensionList.value.push(ex)
return false
}
return true
}) ?? []
console.log('找到的属性元素:', bpmnElementProperties)
// 保存所有的扩展属性字段
bpmnElementPropertyList.value = bpmnElementProperties.reduce((pre: any[], current: any) => bpmnElementPropertyList.value = bpmnElementProperties.reduce((pre: any[], current: any) =>
pre.concat(current.values || []), []) pre.concat(current.values || []), [])
// 复制用于显示 elementPropertyList.value = bpmnElementPropertyList.value.map((prop: any) => ({
elementPropertyList.value = JSON.parse(JSON.stringify(bpmnElementPropertyList.value.map((prop: any) => ({
name: prop.name, name: prop.name,
value: prop.value value: prop.value
})) ?? [])) })) ?? []
console.log('提取的属性列表:', elementPropertyList.value)
}
// 打开属性表单 if (elementPropertyList.value.length === 0) {
const openAttributesForm = (attr: PropertyItem | null, index: number) => { elementPropertyList.value = [...defaultProperties]
editingPropertyIndex.value = index bpmnElementPropertyList.value = [...defaultProperties]
if (index === -1) { updateElementProperties()
propertyForm.name = ''
propertyForm.value = ''
} else {
propertyForm.name = attr?.name || ''
propertyForm.value = attr?.value || ''
}
propertyFormModelVisible.value = true
nextTick(() => {
if (attributeFormRef.value) {
attributeFormRef.value.clearValidate()
} }
})
} }
// 移除属性 const startEdit = (index: number) => {
const removeAttributes = (attr: PropertyItem, index: number) => { editingIndex.value = index
Modal.confirm({ }
title: '提示',
content: '确认移除该属性吗?',
okText: '确认',
cancelText: '取消',
onOk: () => {
elementPropertyList.value.splice(index, 1)
bpmnElementPropertyList.value.splice(index, 1)
// 使用安全的更新方式 const finishEdit = (index: number) => {
editingIndex.value = -1
updateElementProperties() updateElementProperties()
message.success('移除成功')
},
onCancel: () => {
console.info('操作取消')
}
})
} }
// 保存属性 const addProperty = () => {
const saveAttribute = async () => { elementPropertyList.value.push({ name: '', value: '' })
try { bpmnElementPropertyList.value.push({ name: '', value: '' })
// 验证表单 editingIndex.value = elementPropertyList.value.length - 1
if (!propertyForm.name || !propertyForm.value) { }
message.warning('请填写完整信息')
return
}
// 检查属性名是否已存在
const existingIndex = elementPropertyList.value.findIndex((item, index) =>
item.name === propertyForm.name && index !== editingPropertyIndex.value
)
if (existingIndex !== -1) {
message.warning('属性名已存在')
return
}
if (editingPropertyIndex.value !== -1) {
// 编辑现有属性
const updatedProperty = { name: propertyForm.name, value: propertyForm.value }
elementPropertyList.value[editingPropertyIndex.value] = updatedProperty
// 更新实际的属性对象
if (bpmnElementPropertyList.value[editingPropertyIndex.value]) {
bpmnElementPropertyList.value[editingPropertyIndex.value].name = updatedProperty.name
bpmnElementPropertyList.value[editingPropertyIndex.value].value = updatedProperty.value
}
} else {
// 添加新属性
const newProperty = { name: propertyForm.name, value: propertyForm.value }
elementPropertyList.value.push(newProperty)
// 新建属性字段
const newPropertyObject = modelerStore.moddle.create('flowable:Property', newProperty)
bpmnElementPropertyList.value.push(newPropertyObject)
}
// 统一更新元素属性 const removeAttributes = (index: number) => {
elementPropertyList.value.splice(index, 1)
bpmnElementPropertyList.value.splice(index, 1)
updateElementProperties() updateElementProperties()
message.success('删除成功')
propertyFormModelVisible.value = false
// 重置表单
propertyForm.name = ''
propertyForm.value = ''
editingPropertyIndex.value = -1
message.success('保存成功')
} catch (error) {
console.error('保存属性失败:', error)
message.error('保存失败')
}
} }
// 安全的更新元素属性方法
const updateElementProperties = () => { const updateElementProperties = () => {
if (!modelerStore.modeler || !bpmnElement.value) { if (!modelerStore.modeler || !bpmnElement.value) return
console.warn('Modeler 或元素不可用')
return false
}
try { try {
const modeling = modelerStore.modeler.get('modeling') const modeling = modelerStore.modeler.get('modeling')
const moddle = modelerStore.modeler.get('moddle') const moddle = modelerStore.modeler.get('moddle')
if (!modeling || !moddle) { const validProps = elementPropertyList.value.filter(p => p.name && p.value)
console.warn('Modeler 组件不可用')
return false
}
// 创建 properties 元素 const properties = moddle.create('flowable:Properties', {
let propertiesElement = null values: validProps.map(p => moddle.create('flowable:Property', { name: p.name, value: p.value }))
if (bpmnElementPropertyList.value.length > 0) {
try {
propertiesElement = moddle.create('flowable:Properties', {
values: [...bpmnElementPropertyList.value]
}) })
} catch (error) {
console.error('创建 properties 元素失败:', error)
return false
}
}
// 合并所有扩展元素 const extensionElements = bpmnElement.value.businessObject.extensionElements
const allExtensions = [...otherExtensionList.value] if (!extensionElements) {
if (propertiesElement) { modeling.updateProperties(bpmnElement.value, {
allExtensions.push(propertiesElement) extensionElements: moddle.create('bpmn:ExtensionElements', { values: [properties] })
}
// 创建或更新扩展元素
let extensionElements = null
if (allExtensions.length > 0) {
try {
extensionElements = moddle.create('bpmn:ExtensionElements', {
values: allExtensions
}) })
} catch (error) { return
console.error('创建扩展元素失败:', error)
return false
}
} }
// 使用安全的属性更新方式 const existingProps = extensionElements.values?.find((ex: any) => ex.$type === 'flowable:Properties')
try { if (existingProps) {
// 避免直接更新可能包含只读属性的对象 modeling.updateModdleProperties(bpmnElement.value, existingProps, { values: properties.values })
const propertiesToUpdate: any = {}
// 只更新必要的属性
if (extensionElements) {
propertiesToUpdate.extensionElements = extensionElements
} else { } else {
// 如果没有扩展元素,设置为 undefined 来移除 modeling.updateModdleProperties(bpmnElement.value, extensionElements, { values: [...(extensionElements.values || []), properties] })
propertiesToUpdate.extensionElements = undefined
} }
// 使用建模服务更新属性 message.success('保存成功')
modeling.updateProperties(bpmnElement.value, propertiesToUpdate)
console.log('元素属性更新成功')
return true
} catch (error) {
console.error('更新元素属性失败:', error)
// 尝试使用备选方案
return updateElementPropertiesFallback()
}
} catch (error) { } catch (error) {
console.error('更新元素属性失败:', error) console.error('保存属性失败:', error)
return false
} }
} }
</script>
// 备选更新方案 <style lang="scss">
const updateElementPropertiesFallback = () => { .panel-tab__content {
try { .ant-input {
const modeling = modelerStore.modeler.get('modeling') border: 1px solid #d9d9d9;
const moddle = modelerStore.modeler.get('moddle') &:focus {
border-color: #40a9ff;
// 创建新的扩展元素,避免代理问题 outline: none;
const allExtensions = [...otherExtensionList.value]
if (bpmnElementPropertyList.value.length > 0) {
const propertiesElement = moddle.create('flowable:Properties', {
values: bpmnElementPropertyList.value
})
allExtensions.push(propertiesElement)
} }
const extensionElements = allExtensions.length > 0
? moddle.create('bpmn:ExtensionElements', { values: allExtensions })
: undefined
// 使用更安全的方式更新
modeling.updateProperties(bpmnElement.value, {
extensionElements
})
return true
} catch (error) {
console.error('备选更新方案也失败:', error)
return false
} }
} }
.element-drawer__button {
// 取消操作
const handleCancel = () => {
propertyFormModelVisible.value = false
propertyForm.name = ''
propertyForm.value = ''
editingPropertyIndex.value = -1
}
</script>
<style lang="scss" scoped>
.panel-tab__content {
.element-drawer__button {
margin-top: 10px; margin-top: 10px;
text-align: center; text-align: center;
}
} }
</style> </style>
\ No newline at end of file
<template>
<div class="panel-tab__content">
<a-form layout="vertical" @submit.prevent size="small">
<a-form-item label="人员模式">
<a-select v-model:value="assignMode" @change="changeAssignMode">
<a-select-option value="direct">指定人员</a-select-option>
<a-select-option value="candidate">候选人员(抢单模式)</a-select-option>
</a-select>
</a-form-item>
<template v-if="assignMode === 'candidate'">
<a-form-item label="候选人员">
<a-select
v-model:value="candidateUsers"
mode="multiple"
placeholder="请选择候选人员"
:disabled="readonly"
:options="userOptions"
@change="updateCandidateUsers"
/>
</a-form-item>
<a-form-item label="候选角色">
<a-select
v-model:value="candidateRoles"
mode="multiple"
placeholder="请选择候选角色"
:disabled="readonly"
:options="roleOptions"
@change="updateCandidateRoles"
/>
</a-form-item>
</template>
<template v-else>
<a-form-item label="指定人员">
<a-select
v-model:value="directAssignee"
placeholder="请选择指定人员"
:disabled="readonly"
show-search
:options="userOptions"
@change="updateDirectAssignee"
/>
</a-form-item>
</template>
</a-form>
</div>
</template>
<script lang="ts" setup>
import { ref, watch, computed, nextTick } from 'vue'
import { StrUtil } from '../common/StrUtil'
import { useModelerStore } from '../store/modeler-store.js'
import { defHttp } from '/@/utils/http/axios'
const props = defineProps<{
id: string
readonly?: boolean
}>()
const modelerStore = useModelerStore()
const currentElement = computed(() => modelerStore.element)
const assignMode = ref('direct')
const candidateUsers = ref<string[]>([])
const candidateRoles = ref<string[]>([])
const directAssignee = ref<string>('')
const userOptions = ref<{label: string, value: string}[]>([])
const roleOptions = ref<{label: string, value: string}[]>([])
watch(() => props.id, (newVal) => {
if (StrUtil.isNotBlank(newVal) && currentElement.value) {
nextTick(() => loadElementData())
}
}, { immediate: true })
watch(() => currentElement.value, () => {
if (props.id) {
loadElementData()
}
}, { deep: true })
const loadUsers = async () => {
try {
const result = await defHttp.get({ url: '/sys/user/list', params: { pageSize: 1000 } })
if (result?.records) {
userOptions.value = result.records.map((u: any) => ({ label: u.realname, value: u.id }))
}
} catch (error) {
console.error('加载用户列表失败:', error)
}
}
const loadRoles = async () => {
try {
const result = await defHttp.get({ url: '/sys/role/queryall' })
if (result) {
roleOptions.value = result.map((r: any) => ({ label: r.roleName, value: r.id }))
}
} catch (error) {
console.error('加载角色列表失败:', error)
}
}
loadUsers()
loadRoles()
const loadElementData = () => {
const el = currentElement.value
if (!el?.businessObject) return
const bo = el.businessObject
assignMode.value = (bo.candidateUsers || bo.candidateGroups) ? 'candidate' : 'direct'
candidateUsers.value = bo.candidateUsers ? bo.candidateUsers.split(',') : []
candidateRoles.value = bo.candidateGroups ? bo.candidateGroups.split(',') : []
directAssignee.value = bo.assignee || ''
}
const changeAssignMode = (mode: string) => {
if (mode === 'direct') {
candidateUsers.value = []
candidateRoles.value = []
updateElementProperty('candidateUsers', '')
updateElementProperty('candidateGroups', '')
}
}
const updateCandidateUsers = (values: string[]) => {
candidateUsers.value = values
updateElementProperty('candidateUsers', values.join(','))
}
const updateCandidateRoles = (values: string[]) => {
candidateRoles.value = values
updateElementProperty('candidateGroups', values.join(','))
}
const updateDirectAssignee = (value: string) => {
directAssignee.value = value
updateElementProperty('assignee', value)
}
const updateElementProperty = (key: string, value: string) => {
if (!modelerStore.modeler || !currentElement.value) return
try {
const modeling = modelerStore.modeler.get('modeling')
modeling.updateProperties(currentElement.value, { [key]: value || undefined })
} catch (error) {
console.error('更新属性失败:', error)
}
}
</script>
<style lang="scss">
.panel-tab__content {
.ant-select {
width: 100%;
}
}
</style>
\ No newline at end of file
<template> <template>
<div class="panel-tab__content"> <div class="panel-tab__content">
<a-form layout="vertical" @submit.prevent size="small"> <a-form layout="horizontal" :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }" @submit.prevent size="small">
<a-form-item label="参数说明"> <a-form-item label="参数说明">
<a-button size="small" type="primary" @click="dialogVisible = true">查看</a-button> <a-button size="small" type="primary" @click="dialogVisible = true">查看</a-button>
</a-form-item> </a-form-item>
...@@ -14,6 +14,13 @@ ...@@ -14,6 +14,13 @@
<a-select-option value="Null"></a-select-option> <a-select-option value="Null"></a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label="多实例模式" v-if="loopCharacteristics !== 'Null' && loopCharacteristics !== 'StandardLoop'">
<a-radio-group v-model:value="multiInstanceMode" @change="changeMultiInstanceMode">
<a-radio value="counterSign">会签(全部完成)</a-radio>
<a-radio value="orSign">或签(任一完成)</a-radio>
<a-radio value="normal">普通</a-radio>
</a-radio-group>
</a-form-item>
<template v-if="loopCharacteristics === 'ParallelMultiInstance' || loopCharacteristics === 'SequentialMultiInstance'"> <template v-if="loopCharacteristics === 'ParallelMultiInstance' || loopCharacteristics === 'SequentialMultiInstance'">
<a-form-item label="循环基数" key="loopCardinality"> <a-form-item label="循环基数" key="loopCardinality">
<a-input v-model:value="loopInstanceForm.loopCardinality" allow-clear @change="updateLoopCardinality" /> <a-input v-model:value="loopInstanceForm.loopCardinality" allow-clear @change="updateLoopCardinality" />
...@@ -77,8 +84,9 @@ ...@@ -77,8 +84,9 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, watch, reactive } from 'vue' import { ref, watch, reactive, nextTick, onMounted } from 'vue'
import { StrUtil } from '../common/StrUtil' import { StrUtil } from '../common/StrUtil'
import { useModelerStore } from '../store/modeler-store.js'
interface LoopInstanceForm { interface LoopInstanceForm {
completionCondition?: string completionCondition?: string
...@@ -95,11 +103,15 @@ interface LoopInstanceForm { ...@@ -95,11 +103,15 @@ interface LoopInstanceForm {
const props = defineProps<{ const props = defineProps<{
id: string id: string
readonly?: boolean
}>() }>()
const modelerStore = useModelerStore()
// 响应式数据 // 响应式数据
const dialogVisible = ref(false) const dialogVisible = ref(false)
const loopCharacteristics = ref("") const loopCharacteristics = ref("")
const multiInstanceMode = ref("normal")
const loopInstanceForm = reactive<LoopInstanceForm>({}) const loopInstanceForm = reactive<LoopInstanceForm>({})
const multiLoopInstance = ref<any>(null) const multiLoopInstance = ref<any>(null)
...@@ -112,25 +124,25 @@ const defaultLoopInstanceForm: LoopInstanceForm = { ...@@ -112,25 +124,25 @@ const defaultLoopInstanceForm: LoopInstanceForm = {
exclusive: false exclusive: false
} }
// 假设这是从其他地方引入的 store
const modelerStore = {
element: {} as any,
modeling: {
updateProperties: (element: any, properties: any) => {},
updateModdleProperties: (element: any, property: any, values: any) => {}
},
moddle: {
create: (type: string, values: any) => ({})
}
}
// 监听 id 变化 // 监听 id 变化
watch(() => props.id, (newVal) => { watch(() => props.id, (newVal) => {
if (StrUtil.isNotBlank(newVal)) { console.log('[multiInstance] id changed:', newVal, 'store element:', modelerStore.element?.id)
if (StrUtil.isNotBlank(newVal) && modelerStore.element) {
nextTick(() => {
getElementLoop(modelerStore.element.businessObject) getElementLoop(modelerStore.element.businessObject)
})
} }
}, { immediate: true }) }, { immediate: true })
onMounted(() => {
console.log('[multiInstance] mounted, id:', props.id, 'store element:', modelerStore.element?.id)
if (props.id && modelerStore.element) {
getElementLoop(modelerStore.element.businessObject)
}
})
// 获取元素循环配置 // 获取元素循环配置
const getElementLoop = (businessObject: any) => { const getElementLoop = (businessObject: any) => {
if (!businessObject.loopCharacteristics) { if (!businessObject.loopCharacteristics) {
...@@ -159,6 +171,20 @@ const getElementLoop = (businessObject: any) => { ...@@ -159,6 +171,20 @@ const getElementLoop = (businessObject: any) => {
loopCardinality: businessObject.loopCharacteristics?.loopCardinality?.body ?? "" loopCardinality: businessObject.loopCharacteristics?.loopCardinality?.body ?? ""
}) })
// 读取多实例模式
if (businessObject.loopCharacteristics?.completionCondition) {
const condition = businessObject.loopCharacteristics.completionCondition?.body || ''
if (condition.includes('${nrOfCompletedInstances') && condition.includes('nrOfInstances')) {
multiInstanceMode.value = 'counterSign'
} else if (condition.includes('${nrOfActiveInstances') === 0) {
multiInstanceMode.value = 'orSign'
} else {
multiInstanceMode.value = 'normal'
}
} else {
multiInstanceMode.value = 'normal'
}
// 保留当前元素 businessObject 上的 loopCharacteristics 实例 // 保留当前元素 businessObject 上的 loopCharacteristics 实例
multiLoopInstance.value = businessObject.loopCharacteristics multiLoopInstance.value = businessObject.loopCharacteristics
...@@ -207,6 +233,28 @@ const changeLoopCharacteristicsType = (type: string) => { ...@@ -207,6 +233,28 @@ const changeLoopCharacteristicsType = (type: string) => {
}) })
} }
// 改变多实例模式
const changeMultiInstanceMode = (mode: string) => {
if (!multiLoopInstance.value) return
let condition = null
if (mode === 'counterSign') {
// 会签: 全部人完成才结束
condition = modelerStore.moddle.create("bpmn:FormalExpression", {
body: '${nrOfCompletedInstances >= nrOfInstances}'
})
} else if (mode === 'orSign') {
// 或签: 任一人完成就结束
condition = modelerStore.moddle.create("bpmn:FormalExpression", {
body: '${nrOfActiveInstances == 0}'
})
}
modelerStore.modeling.updateModdleProperties(modelerStore.element, multiLoopInstance.value, {
completionCondition: condition
})
}
// 循环基数 // 循环基数
const updateLoopCardinality = (cardinality: string) => { const updateLoopCardinality = (cardinality: string) => {
let loopCardinality = null let loopCardinality = null
......
...@@ -48,10 +48,10 @@ ...@@ -48,10 +48,10 @@
const bpmnFormData = reactive<BpmnFormData>({ const bpmnFormData = reactive<BpmnFormData>({
async: false, async: false,
userType: 'user', userType: 'user',
isTransmit: true, isTransmit: undefined,
isReject: true, isReject: undefined,
isSend: true, isSend: undefined,
isRead: true, isRead: undefined,
}); });
const selectData = reactive<SelectData>({}) const selectData = reactive<SelectData>({})
...@@ -252,14 +252,14 @@ ...@@ -252,14 +252,14 @@
label: '是否转阅', label: '是否转阅',
field: 'isRead', field: 'isRead',
component: 'RadioGroup', component: 'RadioGroup',
defaultValue: true,
componentProps: { componentProps: {
options: [ options: [
{ label: '是', value: true }, { label: '是', value: true },
{ label: '否', value: false }, { label: '否', value: false },
], ],
disabled: props.readonly, disabled: props.readonly,
onChange: (isRead: boolean) => { onChange: (e) => {
const isRead = e.target.value
bpmnFormData.isRead = isRead bpmnFormData.isRead = isRead
updateCustomElement("isRead", isRead) updateCustomElement("isRead", isRead)
emit('update', { isRead }) emit('update', { isRead })
...@@ -270,14 +270,14 @@ ...@@ -270,14 +270,14 @@
label: '是否转办', label: '是否转办',
field: 'isTransmit', field: 'isTransmit',
component: 'RadioGroup', component: 'RadioGroup',
defaultValue: true,
componentProps: { componentProps: {
options: [ options: [
{ label: '是', value: true }, { label: '是', value: true },
{ label: '否', value: false }, { label: '否', value: false },
], ],
disabled: props.readonly, disabled: props.readonly,
onChange: (isTransmit: boolean) => { onChange: (e) => {
const isTransmit = e.target.value
bpmnFormData.isTransmit = isTransmit bpmnFormData.isTransmit = isTransmit
updateCustomElement("isTransmit", isTransmit) updateCustomElement("isTransmit", isTransmit)
emit('update', { isTransmit }) emit('update', { isTransmit })
...@@ -288,14 +288,14 @@ ...@@ -288,14 +288,14 @@
label: '是否可退', label: '是否可退',
field: 'isReject', field: 'isReject',
component: 'RadioGroup', component: 'RadioGroup',
defaultValue: true,
componentProps: { componentProps: {
options: [ options: [
{ label: '是', value: true }, { label: '是', value: true },
{ label: '否', value: false }, { label: '否', value: false },
], ],
disabled: props.readonly, disabled: props.readonly,
onChange: (isReject: boolean) => { onChange: (e) => {
const isReject = e.target.value
bpmnFormData.isReject = isReject bpmnFormData.isReject = isReject
updateCustomElement("isReject", isReject) updateCustomElement("isReject", isReject)
emit('update', { isReject }) emit('update', { isReject })
...@@ -306,14 +306,14 @@ ...@@ -306,14 +306,14 @@
label: '是否发送', label: '是否发送',
field: 'isSend', field: 'isSend',
component: 'RadioGroup', component: 'RadioGroup',
defaultValue: true,
componentProps: { componentProps: {
options: [ options: [
{ label: '是', value: true }, { label: '是', value: true },
{ label: '否', value: false }, { label: '否', value: false },
], ],
disabled: props.readonly, disabled: props.readonly,
onChange: (isSend: boolean) => { onChange: (e) => {
const isSend = e.target.value
bpmnFormData.isSend = isSend bpmnFormData.isSend = isSend
updateCustomElement("isSend", isSend) updateCustomElement("isSend", isSend)
emit('update', { isSend }) emit('update', { isSend })
...@@ -410,7 +410,7 @@ ...@@ -410,7 +410,7 @@
try { try {
// BPMN 标准属性列表(这些可以直接更新) // BPMN 标准属性列表(这些可以直接更新)
const standardProps = ['async', 'dueDate', 'priority', 'assignee', 'candidateGroups', 'userType'] const standardProps = ['async', 'dueDate', 'priority', 'assignee', 'candidateGroups', 'userType', 'isRead', 'isTransmit', 'isReject', 'isSend']
if (standardProps.includes(key)) { if (standardProps.includes(key)) {
// 标准属性直接更新 // 标准属性直接更新
...@@ -441,16 +441,8 @@ ...@@ -441,16 +441,8 @@
return false return false
} }
// 获取原始对象,避免 Vue 响应式代理问题
let targetElement = element
// 如果是 Vue 代理对象,获取原始对象
if (isProxy(element)) {
targetElement = toRaw(element)
}
// 执行更新 // 执行更新
modeling.updateProperties(targetElement, properties) modeling.updateProperties(element, properties)
return true return true
} catch (error) { } catch (error) {
...@@ -477,11 +469,11 @@ ...@@ -477,11 +469,11 @@
candidateGroups: businessObject.candidateGroups || '', candidateGroups: businessObject.candidateGroups || '',
dueDate: businessObject.dueDate || '', dueDate: businessObject.dueDate || '',
priority: businessObject.priority || 'medium', priority: businessObject.priority || 'medium',
// 从自定义属性中读取 // 从 BPMN 属性中读取
isRead: customProps.isRead !== undefined ? customProps.isRead : true, isRead: businessObject.isRead === true,
isTransmit: customProps.isTransmit !== undefined ? customProps.isTransmit : true, isTransmit: businessObject.isTransmit === true,
isReject: customProps.isReject !== undefined ? customProps.isReject : true, isReject: businessObject.isReject === true,
isSend: customProps.isSend !== undefined ? customProps.isSend : true isSend: businessObject.isSend === true
} }
isUser.value = formData.userType === "user" isUser.value = formData.userType === "user"
......
<template> <template>
<div> <div>
<component :is="loadComponent(mainFormListUrl)" <component :is="loadComponent(mainFormListUrl)"
:ref="(el) => setFormComponentRef(el, 1)" :ref="(el) => setFormComponentRef(el, 0)"
@startWorkFlow="handleDefinitionStart" @startWorkFlow="handleDefinitionStart"
@sendWorkFlow="handleDefinitionSend" @sendWorkFlow="handleDefinitionSend"
@endWorkFlow="handleDefinitionEnd" @endWorkFlow="handleDefinitionEnd"
@open-multi-form="handleOpenMultiForm" @open-multi-form="handleOpenMultiForm"
@open-flow-form="handleOpenFlowForm"
:todo-list="todoList" :todo-list="todoList"
/> />
<WorkFlowFormDrawer <WorkFlowFormDrawer
...@@ -113,10 +114,13 @@ ...@@ -113,10 +114,13 @@
} }
const handleDefinitionStart = async (data) => { const handleDefinitionStart = async (data) => {
if (data.procInsId) {
return;
}
const formData = { dataId: data.id, dataName: 'id' }; const formData = { dataId: data.id, dataName: 'id' };
const nodeDeployId = currentNode.value.deployId || ''; const nodeDeployId = currentNode.value.deployId || '';
const startResRaw = await definitionStartByDeployId(nodeDeployId, formData); const startResRaw = await definitionStartByDeployId(nodeDeployId, formData);
if (startResRaw?.instanceId) { if (startResRaw?.instanceId) {
procInsId.value = startResRaw.instanceId; procInsId.value = startResRaw.instanceId;
taskId.value = startResRaw.taskId; taskId.value = startResRaw.taskId;
...@@ -145,7 +149,7 @@ ...@@ -145,7 +149,7 @@
}; };
const handleDefinitionSend = async (data) => { const handleDefinitionSend = async (data) => {
if(currentMultiFormIndex.value <=1){ if (!data.procInsId) {
await handleDefinitionStart(data) await handleDefinitionStart(data)
} }
...@@ -263,7 +267,9 @@ ...@@ -263,7 +267,9 @@
nextTick(() => { nextTick(() => {
if(resData.formUrl) { if(resData.formUrl) {
mainFormUrl.value = resData.formUrl mainFormUrl.value = resData.formUrl
alert(mainFormUrl.value)
mainFormListUrl.value = resData.formListurl || '' mainFormListUrl.value = resData.formListurl || ''
alert(mainFormListUrl.value)
} else { } else {
mainFormUrl.value = "" mainFormUrl.value = ""
refInnerForm.value.iniData(resData) refInnerForm.value.iniData(resData)
...@@ -288,7 +294,6 @@ ...@@ -288,7 +294,6 @@
try { try {
const nodes = await getNodesByTableName(formTableName); const nodes = await getNodesByTableName(formTableName);
todoaLLList.value = await todoList(queryParams); todoaLLList.value = await todoList(queryParams);
alert(JSON.stringify(todoaLLList.value))
workflowNodes.value = nodes; workflowNodes.value = nodes;
if (workflowNodes.value?.length > 0) { if (workflowNodes.value?.length > 0) {
......
<template>
<a-drawer :title="drawerTitle" placement="right" width="100%" :visible="isVisible" @close="closeDrawer" style="margin: 10px;">
<template #extra>
<!-- <a-button type="primary" @click="saveDataAndClose" :disabled="!curTask||!curTpl" style="margin-right: 10px;">分配并关闭</a-button> -->
<a-button type="primary" @click="saveData" :disabled="!curTask||!curTpl" style="margin-right: 60px;">分配用户</a-button>
<a-button type="primary" @click="clearAllocUser" :disabled="!curTask||!curTpl" tyle="margin-right: 10px;">清除分配</a-button>
<a-button @click="closeDrawer" style="margin-left: 20px;">关闭</a-button>
</template>
<a-layout style="height:85vh">
<a-layout-sider width="500" :style="{ backgroundColor: '#fff', overflow: 'hidden',margin:'10px 0px 10px 10px' }">
<Tpl ref ="refTpl" @callback="callBackTpl"/>
</a-layout-sider>
<a-layout-content>
<div style="height:83vh;padding: 10px">
<a-row :gutter="10">
<a-col :span="12">
<a-card title="请选择任务项" :bordered="false" style="height:83vh;">
<TplItem ref="refTplItem"></TplItem>
</a-card>
</a-col>
<a-col :span="12">
<a-card title="请选择填写人" :bordered="false" style="height:83vh;">
<UserList ref="refUserList" @callback="userCallBack"></UserList>
</a-card>
</a-col>
</a-row>
</div>
</a-layout-content>
</a-layout>
</a-drawer>
</template>
<script lang="ts" setup>
import { ref, nextTick } from 'vue';
import Tpl from '../../components/Tpl.vue';
import TplItem from './TplItem.vue';
import UserList from './UserList.vue';
import { saveOrUpdateBatch } from '../../alloc/BaosongTaskAlloc.api';
const emit = defineEmits(['callback']);
const isVisible = ref(false)
const refTpl = ref()
const refTplItem = ref()
const refUserList = ref()
const drawerTitle= ref();
const curTask = ref()
const curTpl = ref()
function closeDrawer() {
isVisible.value = false
emit("callback")
}
function setIniData(task) {
drawerTitle.value="任务分配【"+task.name+"】"
curTask.value = task
isVisible.value = true
nextTick(async () => {
await refTplItem.value.setCurTaskHaveAllocUser(task)
await refTpl.value.setData(task.tp);
await refUserList.value.setData()
});
}
function callBackTpl(Tpl) {
curTpl.value = Tpl;
refTplItem.value.setData(Tpl)
}
async function saveData() {
let allocObj = {taskid:curTask.value?.id};
allocObj["tplid"] = curTpl.value?.id;
allocObj["itemids"] = await refTplItem.value.getSelectIds();
const user =refUserList.value.getSelUser();
allocObj["fillUser"]= user?.id
if(!allocObj["fillUser"]) {
VXETable.modal.message({ content: '请选择用户', status: 'warning' });
return false ;
}
if(allocObj["itemids"].length==0) {
VXETable.modal.message({ content: '请选择任务', status: 'warning' });
return false ;
}
await saveOrUpdateBatch(allocObj)
await refTplItem.value.clearSelect()
await refUserList.value.clearSelect()
emit("callback")
return true
}
async function saveDataAndClose(){
let ret = await saveData();
if(ret) {
closeDrawer();
}
}
async function userCallBack(user){
await refTplItem.value.setSelUser(user);
}
async function clearAllocUser() {
let allocObj = {taskid:curTask.value?.id};
allocObj["tplid"] = curTpl.value?.id;
allocObj["itemids"] = await refTplItem.value.getSelectIds();
if(allocObj["itemids"]&& allocObj["itemids"].length>0) {
await saveOrUpdateBatch(allocObj)
await refTplItem.value.clearSelect()
await refUserList.value.clearSelect()
await refTplItem.value.setCurTaskHaveAllocUser(curTask.value)
await refTplItem.value.setData(Tpl)
} else {
VXETable.modal.message({ content: '请选择记录', status: 'warning' });
return false ;
}
}
defineExpose({
setIniData,
});
</script>
<style scoped></style>
<template>
<a-drawer :title="drawerTitle" placement="right" width="100%" :visible="isVisible" @close="closeDrawer" style="margin: 10px;">
<template #extra>
<!-- <a-button type="primary" @click="saveDataAndClose" :disabled="!curTask||!curTpl" style="margin-right: 10px;">分配并关闭</a-button> -->
<a-button type="primary" @click="saveData" :disabled="!curTask||!curTpl" style="margin-right: 60px;">分配用户</a-button>
<a-button type="primary" @click="clearAllocUser" :disabled="!curTask||!curTpl" tyle="margin-right: 10px;">清除分配</a-button>
<a-button @click="closeDrawer" style="margin-left: 20px;">关闭</a-button>
</template>
<a-layout style="height:85vh">
<a-layout-sider width="500" :style="{ backgroundColor: '#fff', overflow: 'hidden',margin:'10px 0px 10px 10px' }">
<Tpl ref ="refTpl" @callback="callBackTpl"/>
</a-layout-sider>
<a-layout-content>
<div style="height:83vh;padding: 10px">
<a-row :gutter="10">
<a-col :span="12">
<a-card title="请选择任务项" :bordered="false" style="height:83vh;">
<TplItem ref="refTplItem"></TplItem>
</a-card>
</a-col>
<a-col :span="12">
<a-card title="请选择填写人" :bordered="false" style="height:83vh;">
<UserList ref="refUserList" @callback="userCallBack"></UserList>
</a-card>
</a-col>
</a-row>
</div>
</a-layout-content>
</a-layout>
</a-drawer>
</template>
<script lang="ts" setup>
import { ref, nextTick } from 'vue';
import Tpl from '../../components/Tpl.vue';
import TplItem from './TplItem.vue';
import UserList from './UserList.vue';
import { saveOrUpdateBatch } from '../../alloc/BaosongTaskAlloc.api';
import { VXETable } from 'vxe-table';
const emit = defineEmits(['callback']);
const isVisible = ref(false)
const refTpl = ref()
const refTplItem = ref()
const refUserList = ref()
const drawerTitle= ref();
const curTask = ref()
const curTpl = ref()
function closeDrawer() {
isVisible.value = false
emit("callback")
}
function setIniData(task) {
drawerTitle.value="任务分配【"+task.name+"】"
curTask.value = task
isVisible.value = true
nextTick(async () => {
await refTplItem.value.setCurTaskHaveAllocUser(task)
await refTpl.value.setData(task.tp);
await refUserList.value.setData()
});
}
function callBackTpl(Tpl) {
curTpl.value = Tpl;
refTplItem.value.setData(Tpl)
}
async function saveData() {
let allocObj = {taskid:curTask.value?.id};
allocObj["tplid"] = curTpl.value?.id;
allocObj["itemids"] = await refTplItem.value.getSelectIds();
const user =refUserList.value.getSelUser();
allocObj["fillUser"]= user?.id
if(!allocObj["fillUser"]) {
VXETable.modal.message({ content: '请选择用户', status: 'warning' });
return false ;
}
if(allocObj["itemids"].length==0) {
VXETable.modal.message({ content: '请选择任务', status: 'warning' });
return false ;
}
await saveOrUpdateBatch(allocObj)
await refTplItem.value.clearSelect()
await refUserList.value.clearSelect()
emit("callback")
return true
}
async function saveDataAndClose(){
let ret = await saveData();
if(ret) {
closeDrawer();
}
}
async function userCallBack(user){
await refTplItem.value.setSelUser(user);
}
async function clearAllocUser() {
let allocObj = {taskid:curTask.value?.id};
allocObj["tplid"] = curTpl.value?.id;
allocObj["itemids"] = await refTplItem.value.getSelectIds();
if(allocObj["itemids"]&& allocObj["itemids"].length>0) {
await saveOrUpdateBatch(allocObj)
await refTplItem.value.clearSelect()
await refUserList.value.clearSelect()
await refTplItem.value.setCurTaskHaveAllocUser(curTask.value)
await refTplItem.value.setData(Tpl)
} else {
VXETable.modal.message({ content: '请选择记录', status: 'warning' });
return false ;
}
}
defineExpose({
setIniData,
});
</script>
<style scoped></style>
<template>
<BasicModal v-bind="$attrs" @register="registerModal" destroyOnClose :title="title" :width="800" @ok="handleSubmit" okText="确定">
<BasicForm @register="registerForm" />
</BasicModal>
</template>
<script lang="ts" setup>
import { ref, computed, unref } from 'vue';
import { useMessage } from '/@/hooks/web/useMessage';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { BasicForm, useForm } from '/@/components/Form/index';
import { formSchemaApprove } from '../data/BaosongTaskReview.data';
import { saveOrUpdate } from '../api/BaosongTaskReview.api';
const { createConfirm } = useMessage();
// Emits声明
const emit = defineEmits(['register', 'success']);
const isUpdate = ref(true);
//表单配置
const [registerForm, { setProps, resetFields, setFieldsValue, validate }] = useForm({
//labelWidth: 150,
schemas: formSchemaApprove,
showActionButtonGroup: false,
baseColProps: { span: 24 },
});
///const recordTp = ref()
//表单赋值
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
//重置表单
await resetFields();
isUpdate.value = !!data?.isUpdate;
if (isUpdate.value) {
data.record.tp = '' + data.record.tp;
}
setModalProps({ confirmLoading: false, showCancelBtn: !!data?.showFooter, showOkBtn: !!data?.showFooter });
//recordTp.value = data.record.tp
// 隐藏底部时禁用整个表单
setProps({ disabled: !data?.showFooter });
if (unref(isUpdate)) {
await setFieldsValue({
...data.record,
});
}
});
const title = '审批';
async function handleSubmit() {
try {
let values = await validate();
createConfirm({
iconType: 'warning',
title: '确认',
content: '是否完成审批并确认无误?',
okText: '确认',
cancelText: '取消',
onOk: async () => {
console.log('values', values);
setModalProps({ confirmLoading: true });
await saveOrUpdate(values, true);
await resetFields();
closeModal();
emit('success');
},
});
} finally {
setModalProps({ confirmLoading: false });
}
}
</script>
<style lang="less" scoped>
/** 时间和数字输入框样式 */
:deep(.ant-input-number) {
width: 100%;
}
:deep(.ant-calendar-picker) {
width: 100%;
}
</style>
<template> <template>
<div style="min-height: 400px"> <a-layout style="height:85vh">
<BasicForm @register="registerForm"></BasicForm> <a-layout-sider width="500" :style="{ backgroundColor: '#fff', overflow: 'hidden', margin: '10px 0px 10px 10px' }">
<div style="width: 100%;text-align: center" v-if="!formDisabled"> <Tpl ref="refTpl" @callback="callBackTpl" />
<a-button @click="submitForm" pre-icon="ant-design:check" type="primary">提 交</a-button> </a-layout-sider>
</div> <a-layout-content>
<div style="height:83vh; padding: 10px">
<a-row :gutter="10">
<a-col :span="12">
<a-card title="请选择任务项" :bordered="false" style="height:83vh;">
<TplItem ref="refTplItem" />
</a-card>
</a-col>
<a-col :span="12">
<a-card title="请选择填写人" :bordered="false" style="height:83vh;">
<UserList ref="refUserList" @callback="userCallBack" />
</a-card>
</a-col>
</a-row>
</div> </div>
</a-layout-content>
</a-layout>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import {BasicForm, useForm} from '/@/components/Form/index'; import { ref, nextTick } from 'vue';
import {computed, defineComponent} from 'vue'; import Tpl from '../../components/Tpl.vue';
import {defHttp} from '/@/utils/http/axios'; import TplItem from './TplItem.vue';
import { propTypes } from '/@/utils/propTypes'; import UserList from './UserList.vue';
import {getBpmFormSchema} from '../BaosongTaskAlloc.data'; import { saveOrUpdateBatch } from '../../alloc/BaosongTaskAlloc.api';
import {saveOrUpdate} from '../BaosongTaskAlloc.api'; import { VXETable } from 'vxe-table';
const emit = defineEmits(['callback']);
const refTpl = ref();
const refTplItem = ref();
const refUserList = ref();
export default defineComponent({ const curTask = ref();
name: "BaosongTaskAllocForm", const curTpl = ref();
components:{
BasicForm function setIniData(task) {
}, curTask.value = task;
props:{ nextTick(async () => {
formData: propTypes.object.def({}), await refTplItem.value.setCurTaskHaveAllocUser(task);
formBpm: propTypes.bool.def(true), await refTpl.value.setData(task.tp);
}, await refUserList.value.setData();
setup(props){
const [registerForm, { setFieldsValue, setProps, getFieldsValue }] = useForm({
labelWidth: 150,
schemas: getBpmFormSchema(props.formData),
showActionButtonGroup: false,
baseColProps: {span: 24}
}); });
}
function callBackTpl(Tpl) {
curTpl.value = Tpl;
refTplItem.value.setData(Tpl);
}
const formDisabled = computed(()=>{ async function saveData() {
if(props.formData.disabled === false){ const allocObj = { taskid: curTask.value?.id };
allocObj.tplid = curTpl.value?.id;
allocObj.itemids = await refTplItem.value.getSelectIds();
const user = refUserList.value.getSelUser();
allocObj.fillUser = user?.id;
if (!allocObj.fillUser) {
VXETable.modal.message({ content: '请选择用户', status: 'warning' });
return false; return false;
} }
return true; if (allocObj.itemids.length === 0) {
}); VXETable.modal.message({ content: '请选择任务', status: 'warning' });
return false;
let formData = {};
const queryByIdUrl = '/baosong/baosongTaskAlloc/queryById';
async function initFormData(){
let params = {id: props.formData.dataId};
const data = await defHttp.get({url: queryByIdUrl, params});
formData = {...data}
//设置表单的值
await setFieldsValue(formData);
//默认是禁用
await setProps({disabled: formDisabled.value})
} }
async function submitForm() { await saveOrUpdateBatch(allocObj);
let data = getFieldsValue(); await refTplItem.value.clearSelect();
let params = Object.assign({}, formData, data); await refUserList.value.clearSelect();
console.log('表单数据', params) emit('callback');
const isUpdate = params.id?true:false return true;
await saveOrUpdate(params, false) }
}
//initFormData(); async function userCallBack(user) {
await refTplItem.value.setSelUser(user);
}
return { async function clearAllocUser() {
registerForm, const allocObj = { taskid: curTask.value?.id };
formDisabled, allocObj.tplid = curTpl.value?.id;
submitForm, allocObj.itemids = await refTplItem.value.getSelectIds();
}
if (allocObj.itemids && allocObj.itemids.length > 0) {
await saveOrUpdateBatch(allocObj);
await refTplItem.value.clearSelect();
await refUserList.value.clearSelect();
await refTplItem.value.setCurTaskHaveAllocUser(curTask.value);
await refTplItem.value.setData(curTpl.value);
} else {
return false;
} }
}); }
defineExpose({
setIniData,
saveData,
clearAllocUser,
});
</script> </script>
<style scoped></style>
\ No newline at end of file
...@@ -12,8 +12,8 @@ ...@@ -12,8 +12,8 @@
import {computed, defineComponent} from 'vue'; import {computed, defineComponent} from 'vue';
import {defHttp} from '/@/utils/http/axios'; import {defHttp} from '/@/utils/http/axios';
import { propTypes } from '/@/utils/propTypes'; import { propTypes } from '/@/utils/propTypes';
import {getBpmFormSchema} from '../BaosongTask.data'; import {getBpmFormSchema} from '../data/BaosongTask.data';
import {saveOrUpdate} from '../BaosongTask.api'; import {saveOrUpdate} from '../api/BaosongTask.api';
export default defineComponent({ export default defineComponent({
name: "BaosongTaskForm", name: "BaosongTaskForm",
......
<template>
<BasicModal v-bind="$attrs" @register="registerModal" destroyOnClose :title="title" :width="800" @ok="handleSubmit" :centered="true">
<BasicForm @register="registerForm" />
</BasicModal>
</template>
<script lang="ts" setup>
import { ref, computed, unref } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { BasicForm, useForm } from '/@/components/Form/index';
import { formImportDataSchema } from '../data/BaosongTask.data';
import { saveOrUpdate,importData } from '../api/BaosongTask.api';
// Emits声明
const emit = defineEmits(['register', 'success']);
const isUpdate = ref(true);
const curTask = ref({});
//表单配置
const [registerForm, { setProps, resetFields, setFieldsValue, validate }] = useForm({
//labelWidth: 150,
schemas: formImportDataSchema,
showActionButtonGroup: false,
baseColProps: { span: 24 },
});
//表单赋值
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
//重置表单
await resetFields();
setModalProps({ confirmLoading: false, showCancelBtn: !!data?.showFooter, showOkBtn: !!data?.showFooter });
isUpdate.value = !!data?.isUpdate;
curTask.value = data.record
if (unref(isUpdate)) {
//表单赋值
await setFieldsValue({
...data.record,
});
}
// 隐藏底部时禁用整个表单
setProps({ disabled: !data?.showFooter });
});
//设置标题
const title = computed(() => (!unref(isUpdate) ? '新增' : '编辑'));
//表单提交事件
async function handleSubmit(v) {
try {
let values = await validate();
values["taskId"] =curTask.value.id
setModalProps({ confirmLoading: true });
//提交表单
await importData(values, handleSuccess);
//关闭弹窗
closeModal();
//刷新列表
emit('success');
} finally {
setModalProps({ confirmLoading: false });
}
}
async function handleSuccess() {
emit("success")
}
</script>
<style lang="less" scoped>
/** 时间和数字输入框样式 */
:deep(.ant-input-number) {
width: 100%;
}
:deep(.ant-calendar-picker) {
width: 100%;
}
</style>
...@@ -12,8 +12,8 @@ ...@@ -12,8 +12,8 @@
import {computed, defineComponent} from 'vue'; import {computed, defineComponent} from 'vue';
import {defHttp} from '/@/utils/http/axios'; import {defHttp} from '/@/utils/http/axios';
import { propTypes } from '/@/utils/propTypes'; import { propTypes } from '/@/utils/propTypes';
import {getBpmFormSchema} from '../BaosongTaskRecord.data'; import {getBpmFormSchema} from '../data/BaosongTaskRecord.data';
import {saveOrUpdate} from '../BaosongTaskRecord.api'; import {saveOrUpdate} from '../api/BaosongTaskRecord.api';
export default defineComponent({ export default defineComponent({
name: "BaosongTaskRecordForm", name: "BaosongTaskRecordForm",
......
...@@ -12,8 +12,8 @@ ...@@ -12,8 +12,8 @@
import {computed, defineComponent} from 'vue'; import {computed, defineComponent} from 'vue';
import {defHttp} from '/@/utils/http/axios'; import {defHttp} from '/@/utils/http/axios';
import { propTypes } from '/@/utils/propTypes'; import { propTypes } from '/@/utils/propTypes';
import {getBpmFormSchema} from '../BaosongTaskReview.data'; import {getBpmFormSchema} from '../data/BaosongTaskReview.data';
import {saveOrUpdate} from '../BaosongTaskReview.api'; import {saveOrUpdate} from '../api/BaosongTaskReview.api';
export default defineComponent({ export default defineComponent({
name: "BaosongTaskReviewForm", name: "BaosongTaskReviewForm",
......
<template>
<BasicModal v-bind="$attrs" @register="registerModal" destroyOnClose :title="title" :width="800" @ok="handleSubmit">
<BasicForm @register="registerForm"/>
</BasicModal>
</template>
<script lang="ts" setup>
import {ref, computed, unref} from 'vue';
import {BasicModal, useModalInner} from '/@/components/Modal';
import {BasicForm, useForm} from '/@/components/Form/index';
import {formSchema} from '../data/BaosongTaskReview.data';
import {saveOrUpdate} from '../api/BaosongTaskReview.api';
// Emits声明
const emit = defineEmits(['register','success']);
const isUpdate = ref(true);
//表单配置
const [registerForm, {setProps,resetFields, setFieldsValue, validate}] = useForm({
//labelWidth: 150,
schemas: formSchema,
showActionButtonGroup: false,
baseColProps: {span: 24}
});
//表单赋值
const [registerModal, {setModalProps, closeModal}] = useModalInner(async (data) => {
//重置表单
await resetFields();
setModalProps({confirmLoading: false,showCancelBtn:!!data?.showFooter,showOkBtn:!!data?.showFooter});
isUpdate.value = !!data?.isUpdate;
if (unref(isUpdate)) {
//表单赋值
await setFieldsValue({
...data.record,
});
}
// 隐藏底部时禁用整个表单
setProps({ disabled: !data?.showFooter })
});
//设置标题
const title = computed(() => (!unref(isUpdate) ? '新增' : '编辑'));
//表单提交事件
async function handleSubmit(v) {
try {
let values = await validate();
setModalProps({confirmLoading: true});
//提交表单
await saveOrUpdate(values, isUpdate.value);
//关闭弹窗
closeModal();
//刷新列表
emit('success');
} finally {
setModalProps({confirmLoading: false});
}
}
</script>
<style lang="less" scoped>
/** 时间和数字输入框样式 */
:deep(.ant-input-number){
width: 100%
}
:deep(.ant-calendar-picker){
width: 100%
}
</style>
\ No newline at end of file
<template>
<div style="height:73vh">
<vxe-toolbar ref="toolbarRef" export print custom :refresh="{ queryMethod }">
<template #buttons >
<vxe-button @click="expandAllEvent">展开所有</vxe-button>
<vxe-button @click="clearExpandEvent">关闭所有</vxe-button>
</template>
</vxe-toolbar>
<vxe-table
border
show-overflow
keep-source
height="auto"
ref="tableRef"
:columnConfig="{ resizable: true }"
:row-config="{keyField: 'id'}"
:edit-config="{trigger: 'click', mode: 'row', showStatus: true}"
:tree-config="{transform: true, rowField: 'code', parentField: 'pcode'}"
:checkbox-config="{highlight: true}"
@checkbox-change="selectChangeEvent"
@checkbox-all="selectAllEvent"
:data="tableData">
<vxe-column type="checkbox" width="50" fixed="left">
</vxe-column>
<vxe-column field="title" title="标题" width="50%" tree-node fixed="left">
</vxe-column>
<vxe-column field="code" title="编码">
</vxe-column>
<vxe-column field="fillUserName" title="填报人">
</vxe-column>
<vxe-column field="fillUser" title="填报人" :visible="false">
</vxe-column>
</vxe-table>
</div>
</template>
<script lang="ts" setup>
import { ref,nextTick } from 'vue'
import { VxeTableEvents } from 'vxe-table'
import { allTplItems } from '../../tpl/BaosongTplItem.api';
import { queryCurTaskAllocUser } from '../../alloc/BaosongTaskAlloc.api';
interface RowVO {
id: number
title: string
tp: number
pcode: string
code: string
childNum: number
hasChild?: boolean
fillUserName?:string
fillUser?:string
}
const curTpl = ref()
const loading = ref(false)
const tableRef = ref()
const tableData = ref<RowVO>()
const itemUserNameMap= ref({})
const curSelUser= ref()
async function setData(tplData){
loading.value =true
curTpl.value = tplData
tableData.value = await allTplItems({tplid:tplData.id});
loading.value =false
const $table = tableRef.value;
nextTick(async ()=>{
if(!tableData.value||!Array.isArray(tableData.value)){
return ;
}
tableData.value.forEach((item)=>{
if(itemUserNameMap.value[item.id]) {
item['fillUserName'] = itemUserNameMap.value[item.id]
$table.setCheckboxRow(item, true)
} else {
item['fillUserName'] = ""
$table.setCheckboxRow(item, false)
}
})
})
}
function getSelectIds() {
let ids = [];
const $table = tableRef.value;
if ($table) {
const selRecords = $table.getCheckboxRecords();
selRecords.forEach(function (ele) {
if(ele.childNum==0) {
ids.push(ele.id);
}
});
}
return ids;
}
function getSelectDatas() {
const $table = tableRef.value;
const selRecords = $table?.getCheckboxRecords();
return selRecords;
}
const selectChangeEvent: VxeTableEvents.CheckboxChange<RowVO> = ({ $table }) => {
const records = $table.getCheckboxRecords()
records.forEach(record => {
$table.setTreeExpand(record, true)
})
return records;
}
function setSelUser(user) {
curSelUser.value = user
const $table = tableRef.value;
const selRecords = $table?.getCheckboxRecords();
selRecords.forEach((item)=>{
item["fillUserName"] = user.realname
item["fillUser"] = user.id
})
}
const clearSelect = () => {
const $table = tableRef.value
if ($table) {
$table.clearCheckboxRow()
}
}
async function setCurTaskHaveAllocUser(task) {
let retData = await queryCurTaskAllocUser(task);
if(!retData||retData.length==0) return
retData.forEach((item)=>{
itemUserNameMap.value[item.itemid] = item.realname
})
}
const expandAllEvent = () => {
const $table = tableRef.value
if ($table) {
$table.setAllTreeExpand(true)
}
}
const clearExpandEvent = () => {
const $table = tableRef.value
if ($table) {
$table.clearTreeExpand()
}
}
const selectAllEvent: VxeTableEvents.CheckboxChange<RowVO> = ({ $table }) => {
if ($table) {
$table.setAllTreeExpand(true)
}
}
defineExpose({
clearSelect,
setData,
setSelUser,
getSelectDatas,
getSelectIds,
setCurTaskHaveAllocUser
});
</script>
<template>
<div style="height:73vh;">
<vxe-grid ref="tableRef"
v-bind="gridOptions"
:radio-config="{highlight: true}"
v-on="gridEvents"
:columnConfig="{ resizable: true }"
:row-config="{ isCurrent: true, isHover: true }"
>
</vxe-grid>
</div>
</template>
<script lang="ts" setup>
import { reactive,ref } from 'vue'
import { VxeGridProps,VxeGridListeners } from 'vxe-table'
import { defHttp } from '/@/utils/http/axios';
const emit = defineEmits(['callback']);
interface RowVO {
id: string
username: string
nickname: string
}
interface SrhParam {
username: string
column:string
order:string
pageNo:number
pageSize:number
}
const tableRef = ref();
const selectRow = ref<RowVO | null>(null)
const srhParams = ref<SrhParam>({
username: '',
column: '',
order: '',
pageNo: 1,
pageSize: 30
});
const gridOptions = reactive<VxeGridProps<RowVO>>({
border: true,
height:"auto",
rowConfig: {
keyField: 'id'
},
columnConfig: {
resizable: true
},
radioConfig: {
highlight: true,
},
pagerConfig: {
enabled: true,
pageSize: srhParams.value.pageSize
},
columns: [
{ type: 'radio', width: 50},
{ type: 'seq', width: 60 },
{ field: 'username', title: '用户账号'},
{ field: 'realname', title: '用户名称' }
],
proxyConfig: {
seq: true,
props: {
result: 'result',
total: 'page.total'
},
ajax: {
query: ({ page }) => {
return findPageList(page.currentPage, page.pageSize)
}
}
}
})
const findPageList = async (currentPage: number, pageSize: number) => {
srhParams.value.pageNo = currentPage;
srhParams.value.pageSize = pageSize
const retData = await setUserData();
return {
page: {
total: retData.total
},
result: retData.records
};
};
async function setData() {
findPageList(1,srhParams.value.pageSize)
}
async function setUserData() {
return await defHttp.get({url: "/sys/user/list", params:srhParams.value})
}
function getSelUser() {
const $table = tableRef.value;
if ($table) {
const currRow = $table.getRadioRecord()
return currRow
}
return null
}
const gridEvents: VxeGridListeners<RowVO> = {
cellClick ({ row, column }) {
selectRow.value = row
setSelectRow(row)
emit("callback",row)
},
cellDblclick ({ row, column }) {
},
radioChange({ row }) {
selectRow.value = row;
setSelectRow(row);
emit("callback", row);
}
}
const setSelectRow = (row) => {
const $table = tableRef.value
if ($table) {
$table.setRadioRow(row)
}
}
const clearSelect = () => {
const $table = tableRef.value
if ($table) {
selectRow.value = null
$table.clearRadioRow()
}
}
defineExpose({
clearSelect,
setData,
getSelUser
});
</script>
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
</div> </div>
</template> </template>
<script lang="ts" name="baosong-baosongTaskAlloc" setup> <script lang="ts" setup>
import {BasicTable} from '/@/components/Table'; import {BasicTable} from '/@/components/Table';
import { useListPage } from '/@/hooks/system/useListPage' import { useListPage } from '/@/hooks/system/useListPage'
import {columns, searchFormSchema} from '../data/BaosongTaskAlloc.data'; import {columns, searchFormSchema} from '../data/BaosongTaskAlloc.data';
......
...@@ -43,12 +43,12 @@ ...@@ -43,12 +43,12 @@
import { BasicTable, useTable, TableAction } from '/@/components/Table'; import { BasicTable, useTable, TableAction } from '/@/components/Table';
import { useModal } from '/@/components/Modal'; import { useModal } from '/@/components/Modal';
import { useListPage } from '/@/hooks/system/useListPage'; import { useListPage } from '/@/hooks/system/useListPage';
import BaosongTaskModal from './components/BaosongTaskModal.vue'; import BaosongTaskModal from '../../task/components/BaosongTaskModal.vue';
import BaosongTaskDrawer from './components/BaosongTaskDrawer.vue'; import BaosongTaskDrawer from '../../task/components/BaosongTaskDrawer.vue';
import BaosongTaskImportDataModal from './components/BaosongTaskImportDataModal.vue';
import BaosongTaskImportDataModal from '../form/BaosongTaskImportDataModal.vue';
import { columns, searchFormSchema } from '../data/BaosongTask.data'; import { columns, searchFormSchema } from '../data/BaosongTask.data';
import { list, deleteOne, batchDelete, addApproval, saveOrUpdate, importData } from '../api/BaosongTask.api'; import { list, deleteOne, batchDelete, addApproval, saveOrUpdate, importData } from '../api/BaosongTask.api';
import { useMessage } from '/@/hooks/web/useMessage'; import { useMessage } from '/@/hooks/web/useMessage';
const { createMessage } = useMessage(); const { createMessage } = useMessage();
......
...@@ -20,18 +20,22 @@ ...@@ -20,18 +20,22 @@
</template> </template>
</BasicTable> </BasicTable>
<BaosongTaskModal @register="registerModal" @success="handleSuccess" /> <BaosongTaskModal @register="registerModal" @success="handleSuccess" />
<BaosongAllocDrawer ref="refAllocDrawer" @callback="handleSuccess" />
</div> </div>
</template> </template>
<script lang="ts" name="baosong-baosongTask" setup> <script lang="ts" setup>
import { ref, computed, unref } from 'vue';
import { BasicTable, TableAction } from '/@/components/Table'; import { BasicTable, TableAction } from '/@/components/Table';
import { useModal } from '/@/components/Modal'; import { useModal } from '/@/components/Modal';
import { useListPage } from '/@/hooks/system/useListPage'; import { useListPage } from '/@/hooks/system/useListPage';
import BaosongTaskModal from '../form/BaosongTaskModal.vue'; import BaosongTaskModal from '../form/BaosongTaskModal.vue';
import { columns, searchFormSchema } from '../data/BaosongTask.data'; import { columns, searchFormSchema } from '../data/BaosongTask.data';
import { list, deleteOne, batchDelete, saveOrUpdate } from '../api/BaosongTask.api'; import { list, deleteOne, batchDelete, saveOrUpdate } from '../api/BaosongTask.api';
import BaosongAllocDrawer from '../form/BaosongAllocDrawer.vue';
const [registerModal, { openModal }] = useModal(); const [registerModal, { openModal }] = useModal();
const refAllocDrawer = ref<InstanceType<typeof BaosongAllocDrawer>>();
const { tableContext } = useListPage({ const { tableContext } = useListPage({
tableProps: { tableProps: {
...@@ -61,6 +65,11 @@ ...@@ -61,6 +65,11 @@
}); });
} }
function handleAlloc(record: Recordable) {
refAllocDrawer.value.setIniData(record);
}
function handleEdit(record: Recordable) { function handleEdit(record: Recordable) {
openModal(true, { openModal(true, {
record, record,
...@@ -92,10 +101,18 @@ ...@@ -92,10 +101,18 @@
function getTableAction(record: Recordable) { function getTableAction(record: Recordable) {
return [ return [
{
label: '分配',
onClick: handleAlloc.bind(null, record),
},
{ {
label: '提交', label: '提交',
onClick: () => emit('sendWorkFlow', record), onClick: () => emit('sendWorkFlow', record),
}, },
{
label: '待办',
onClick: () => emit('open-multi-form', record),
},
]; ];
} }
...@@ -119,7 +136,7 @@ ...@@ -119,7 +136,7 @@
]; ];
} }
const emit = defineEmits(['sendWorkFlow']); const emit = defineEmits(['sendWorkFlow', 'open-multi-form']);
const handleUpdate = async (dataId: string, flowNode: Recordable) => { const handleUpdate = async (dataId: string, flowNode: Recordable) => {
const record = { const record = {
......
...@@ -27,8 +27,8 @@ ...@@ -27,8 +27,8 @@
import { listForFillData } from '../api/BaosongTask.api'; import { listForFillData } from '../api/BaosongTask.api';
import { updateFillStaBatch } from '../api/BaosongTaskAlloc.api'; import { updateFillStaBatch } from '../api/BaosongTaskAlloc.api';
import RecordDrawer from './components/RecordDrawer.vue'; import RecordDrawer from '../../components/CommonForm.vue';
import RecordDrawer2 from './components/RecordDrawer2.vue'; import RecordDrawer2 from '../../components/CommonTable.vue';
import FileViewerDrawer from '/@/components/onlinePreview/FileViewerDrawer.vue'; import FileViewerDrawer from '/@/components/onlinePreview/FileViewerDrawer.vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
import { useListPage } from '/@/hooks/system/useListPage'; import { useListPage } from '/@/hooks/system/useListPage';
import { columns, searchFormSchema } from '../data/BaosongTaskReview.data'; import { columns, searchFormSchema } from '../data/BaosongTaskReview.data';
import { list, deleteOne, batchDelete, downLoadTaskXml, saveOrUpdate } from '../api/BaosongTaskReview.api'; import { list, deleteOne, batchDelete, downLoadTaskXml, saveOrUpdate } from '../api/BaosongTaskReview.api';
import BaosongApproveModal from './components/BaosongApproveModal.vue'; import BaosongApproveModal from '../form/BaosongApproveModal.vue';
import { useRoute,useRouter } from 'vue-router'; import { useRoute,useRouter } from 'vue-router';
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils.js'; import { getFileAccessHttpUrl } from '/@/utils/common/compUtils.js';
......
<template>
<div>
<a-tabs v-model:activeKey="activeTab" @change="handleTabChange" style="margin-left: 16px">
<a-tab-pane v-for="(node, index) in workflowNodes" :key="index + 1" :tab="node.name">
<div v-if="node.formListUrl" class="tab-content">
<component :is="loadComponent(node.formListUrl)"
:ref="(el) => setFormComponentRef(el, index)"
@startWorkFlow="handleDefinitionStart"
@sendWorkFlow="handleDefinitionSend"
@endWorkFlow="handleDefinitionEnd"
:beforeFlowNode="workflowNodes[index-1]"
:currentFlowNode="node"
:nextFlowNode="workflowNodes[index+1]"
@open-multi-form="handleOpenMultiForm"
:todo-list="todoList"
/>
</div>
<div v-else class="no-form">
该节点未配置表单
</div>
</a-tab-pane>
</a-tabs>
<WorkFlowFormDrawer
v-model:visible="drawerVisible"
:title="drawerTitle"
:current-node-index="currentMultiFormIndex"
:workflow-nodes="workflowNodes"
:external-form-data="externalFormData"
:proc-def-id="currentProcDefId"
:proc-ins-id="procInsId"
:show-history-form-data="true"
:data-id="dataId"
:deploy-id="deployId"
:task-id="taskId"
:show-approval-panel="isShowApprovalPanel"
:assignee="assignee"
:user-type="userType"
@submit="handleMultiFormSubmit"
@close="handleDrawerClose"
@form-data-update="handleMultiFormDataUpdate"
width="90%"
@success="handlSendSuccess"
@approval-success="handlApprovalSuccess"
@approval-fail="handlApprovalFail"
/>
<task-assignee-drawer @register="registerAssigneeDrawer" @success="handlSendSuccess" @error="handleError" />
</div>
</template>
<script lang="ts" name="problem-stProblemCheck" setup>
import { ref, nextTick, onMounted, defineAsyncComponent, h } from 'vue';
import { getNodesByTableName } from '/@/components/Process/api/definition';
import { definitionStart, definitionStartByDeployId, addMyTaskFlow } from "/@/components/Process/api/definition";
import { todoListAll, complete,getMyTaskFlow} from "/@/components/Process/api/todo";
import WorkFlowFormDrawer from '/@/views/common/WorkFlowFormDrawer.vue';
import TaskAssigneeDrawer from '/@/views/common/TaskAssigneeDrawer.vue';
import { useDrawer } from '/@/components/Drawer';
const formTableName = "baosong_task";
const workflowNodes = ref<any[]>([]);
const activeTab = ref(1);
const dataId = ref('');
const currentNode = ref<any>({});
const isShowApprovalPanel = ref(true);
const deployId = ref('');
const procInsId = ref('');
const taskId = ref('');
const formComponentRefs = ref<Map<number, any>>(new Map());
const assignee = ref<any>("");
const userType = ref<any>("");
const todoList = ref<any[]>([]);
const drawerVisible = ref(false);
const drawerTitle = ref('表单处理');
const currentMultiFormIndex = ref(0);
const currentProcDefId = ref('');
const externalFormData = ref<Record<string, any>>({});
const componentCache = new Map();
const modules = import.meta.glob('@/views/**/*.vue');
const [registerAssigneeDrawer, { openDrawer: openAssigneeDrawer }] = useDrawer();
function setFormComponentRef(el: any, index: number) {
if (el) formComponentRefs.value.set(index, el);
else formComponentRefs.value.delete(index);
}
function getCurrentFormComponent() {
return formComponentRefs.value.get(activeTab.value - 1);
}
function handleTabChange(key) {
activeTab.value = key;
currentMultiFormIndex.value = key - 1;
currentNode.value = workflowNodes.value[currentMultiFormIndex.value];
const currentFormComponent = getCurrentFormComponent();
if (currentFormComponent?.handleRefresh) {
currentFormComponent.handleRefresh();
}
}
function loadComponent(url: string) {
if (componentCache.has(url)) return componentCache.get(url);
let componentPath = url.includes('/views') ? `/src${url}` : `/src/views${url}`;
if (!componentPath.match(/\.(vue|js|ts|jsx|tsx)$/)) componentPath += '.vue';
const loader = modules[componentPath];
if (!loader) {
const ErrorComponent = { render: () => h('div', { style: 'color: red; padding: 20px;' }, `组件未找到: ${componentPath}`) };
componentCache.set(url, ErrorComponent);
return ErrorComponent;
}
const AsyncComponent = defineAsyncComponent({
loader: () => loader() as Promise<{ default: any }>,
loadingComponent: { render: () => h('div', { style: 'text-align: center; padding: 20px;' }, '加载中...') },
errorComponent: { render: () => h('div', { style: 'color: red; padding: 20px;' }, '组件加载失败,请刷新页面重试') },
delay: 200,
timeout: 3000,
onError(error) { console.error('异步组件加载错误:', error); }
});
componentCache.set(url, AsyncComponent);
return AsyncComponent;
}
const handleDefinitionStart = async (data) => {
const formData = { dataId: data.id, dataName: 'id' };
const nodeDeployId = currentNode.value.deployId || '';
const startResRaw = await definitionStartByDeployId(nodeDeployId, formData);
if (startResRaw?.instanceId) {
procInsId.value = startResRaw.instanceId;
taskId.value = startResRaw.taskId;
const myTaskFlow = {
taskId: startResRaw.taskId,
deployId: startResRaw.deployId,
procInsId: startResRaw.instanceId,
executionId: startResRaw.executionId,
targetId: data.id,
taskDefinitionKey: currentNode.value.id,
formTableName: formTableName,
};
const attributes = currentNode.value?.attributes || {};
const userTypes = attributes.userType || [];
if (userTypes.length > 0) {
userType.value = userTypes[0].value || '';
if (userType.value === "role") myTaskFlow["roleid"] = currentNode.value.assignee;
else myTaskFlow["uid"] = currentNode.value.assignee;
}
await addMyTaskFlow(myTaskFlow);
const currentFormComponent = getCurrentFormComponent();
if (currentFormComponent?.handleStartUpdate) {
currentFormComponent.handleStartUpdate({ dataId: data.id, procInsId: procInsId.value });
}
}
};
const handleDefinitionSend = async (data) => {
dataId.value = data.id;
deployId.value = currentNode.value.deployId || '';
await setNextNodeUser();
openAssigneeDrawer(true, {
assignee: assignee.value,
assigneeName: '',
userType: userType.value,
dataId: data.id,
deployId: deployId.value,
});
};
const handleDefinitionEnd = async (data) => {
dataId.value = data.id;
deployId.value = currentNode.value.deployId || '';
const myTaskFlow = await getMyTaskFlow({ deploymentId: deployId.value, dataId: dataId.value });
procInsId.value = myTaskFlow?.procInsId || '';
taskId.value = myTaskFlow?.taskId || '';
await complete({
taskId: taskId.value,
deployId: deployId.value,
procInsId: procInsId.value,
targetId: data.id,
taskDefinitionKey: currentNode.value.id,
formTableName: formTableName,
comment: '完成',
values:{}
});
};
const setNextNodeUser = async () => {
const nextNode = workflowNodes.value[activeTab.value];
const attributes = nextNode?.attributes || {};
const userTypes = attributes.userType || [];
if (userTypes.length > 0) {
userType.value = userTypes[0].value || '';
if (userType.value === "role") {
assignee.value = nextNode.candidateGroups?.[0]?.id;
} else {
assignee.value = nextNode.assignee;
}
}
};
const handleMultiFormSubmit = async (submitData: { nodeId: string; nodeName: string; formData: any; procDefId: string; }) => {
try {
await definitionStart(currentProcDefId.value, { ...submitData.formData });
drawerVisible.value = false;
} catch (error) {
console.error('提交失败:', error);
throw error;
}
};
const handleDrawerClose = () => { drawerVisible.value = false; };
const handleMultiFormDataUpdate = (_data: any) => {
const currentFormComponent = getCurrentFormComponent();
if (currentFormComponent?.handleRefresh) {
currentFormComponent.handleRefresh();
}
};
function handlSendSuccess(dataId: any) {
const currentFormComponent = getCurrentFormComponent();
if (currentFormComponent?.handleUpdate) {
const nextNode = workflowNodes.value[currentMultiFormIndex.value + 1];
currentFormComponent.handleUpdate(dataId, nextNode);
}
}
function handleError(error: any) { console.error('任务处理失败:', error); }
async function handlApprovalFail(dataId: any) {
const currentFormComponent = getCurrentFormComponent();
if (currentFormComponent?.handleUpdate) {
const beforeNode = workflowNodes.value[currentMultiFormIndex.value - 1];
await currentFormComponent.handleUpdate(dataId, beforeNode);
}
}
function handlApprovalSuccess(dataId: any) {
const currentFormComponent = getCurrentFormComponent();
if (currentFormComponent?.handleUpdate) {
const nextNode = workflowNodes.value[currentMultiFormIndex.value + 1];
currentFormComponent.handleUpdate(dataId, nextNode);
}
}
async function handleOpenMultiForm(data: any) {
deployId.value = currentNode.value.deployId || '';
procInsId.value = data.procInsId || '';
drawerVisible.value = true;
drawerTitle.value = currentNode.value.name || '表单处理';
dataId.value = data.id || '';
currentNode.value = workflowNodes.value[currentMultiFormIndex.value];
currentProcDefId.value = currentNode.value.procDefId || '';
const isApprovalNode = JSON.parse(currentNode.value["isApprove"]);
isShowApprovalPanel.value = isApprovalNode;
await setNextNodeUser();
}
onMounted(async () => {
await nextTick();
try {
const nodes = await getNodesByTableName(formTableName);
todoList.value = await todoListAll();
workflowNodes.value = nodes;
if (workflowNodes.value?.length > 0) {
workflowNodes.value.forEach(node => {
if (node.formListUrl) loadComponent(node.formListUrl);
});
currentNode.value = workflowNodes.value[0];
}
} catch (error) {
console.error('获取工作流节点失败:', error);
}
});
</script>
<style scoped>
.tab-content { padding: 0px; }
.no-form { padding: 40px; text-align: center; color: #999; }
</style>
<template>
<div class="form-panel">
<a-card class="form-card" :bordered="false">
<template #title>
<div class="form-header">
<span class="form-title">当前待办<font color="red">[{{ editableNode?.name || '无' }}]</font></span>
<a-space>
<a-button v-show="props.showApprovalPanel" type="primary" ghost @click="handleApproval">审批</a-button>
<a-button v-show="!props.showApprovalPanel" type="primary" ghost @click="handleSave">保存</a-button>
<a-button v-show="!props.showApprovalPanel" type="primary" ghost @click="handleSend">保存并发送</a-button>
<a-button v-show="!props.showApprovalPanel" type="primary" ghost @click="handleReject">驳回</a-button>
<a-button type="primary" ghost @click="handleTransmit">转办</a-button>
<a-button type="primary" ghost @click="handleRead">转阅</a-button>
</a-space>
</div>
</template>
<div class="form-content" v-if="editableNode">
<component
:is="getComponent(editableNode.formUrl)"
:ref="(el) => setFormRef(el, editableNode.id)"
:disabled="props.disabled"
:readonly="false"
:form-data="props.formData"
:current-flow-node="editableNode"
:showApprovalPanel="props.showApprovalPanel"
@update:form-data="handleFormDataUpdate"
/>
</div>
<div v-else class="empty-form-state">
<a-empty description="未找到可编辑的表单节点" />
</div>
</a-card>
<ApprovalModal @register="registerApprovalModal" @success="updateApprovalSuccess" @approval-fail="updateApprovalFail"/>
<TaskModal @register="registerTaskModal" @success="handleFormDataUpdate" />
</div>
</template>
<script lang="ts" setup>
import { ref, defineAsyncComponent, h, watch, ComponentPublicInstance, nextTick } from 'vue'
import ApprovalModal from './components/ApprovalModal.vue';
import TaskModal from './components/TaskModal.vue';
import { useModal } from '/@/components/Modal';
const [registerApprovalModal, { openModal }] = useModal();
const [registerTaskModal, { openModal: taskOpenModal }] = useModal();
interface WorkflowNode {
id: string
name: string
formUrl?: string
formListUrl?: string
[key: string]: any
}
interface FormComponentInstance extends ComponentPublicInstance {
validate?: () => Promise<any>
getFormData?: () => any
initFormData?: (dataId: string) => Promise<void> | void
formData?: any
[key: string]: any
}
const props = defineProps({
editableNode: { type: Object as () => WorkflowNode | null, default: null },
nextNode: { type: Object as () => WorkflowNode | null, default: null },
beforeNode: { type: Object as () => WorkflowNode | null, default: null },
dataId: { type: String, default: '' },
deployId: { type: String, default: '' },
taskId: { type: String, default: '' },
procInsId: { type: String, default: '' },
externalFormData: { type: Object as () => Record<string, any>, default: () => ({}) },
formData: { type: Object as () => Record<string, any>, default: () => ({}) },
disabled: { type: Boolean, default: false },
assignee: { type: String, default: '' },
userType: { type: String, default: '' },
showApprovalPanel: { type: Boolean, default: false }
})
const emit = defineEmits(['update:form-data', 'form-mounted','approval-success', 'approval-fail'])
const componentCache = new Map()
const modules = import.meta.glob('@/views/**/*.vue')
const formComponentRef = ref<FormComponentInstance | null>(null)
const formData = ref<any>({})
function handleApproval() {
openModal(true, {
deployId: props.deployId,
dataId: props.dataId,
userType: props.userType,
assignee: props.assignee,
beforeFlowNode: props.beforeNode,
isUpdate: false,
showFooter: true,
taskType: 'approval'
});
}
function handleSend() {
taskOpenModal(true, {
taskTitle: '发送并保存',
isUpdate: false,
showFooter: true,
taskType: 'send',
deployId: props.deployId,
taskId: props.taskId,
procInsId: props.procInsId, dataId: props.dataId, assignee: props.assignee, userType: props.userType,
});
}
const handleSave = async () => {
const curFormData = await getFormData()
formData.value = { ...curFormData}
if (formComponentRef.value?.saveForm){
await formComponentRef.value.saveForm(formData.value);
}
emit('update:form-data', formData.value)
}
function handleReject() {
taskOpenModal(true, {
taskTitle: '驳回', isUpdate: false, showFooter: true, taskType: 'reject',
deployId: props.deployId, taskId: props.taskId, procInsId: props.procInsId, dataId: props.dataId, assignee: props.assignee, userType: props.userType,
});
}
function handleTransmit() {
taskOpenModal(true, {
taskTitle: '转办', isUpdate: false, showFooter: true, taskType: 'transmit',
deployId: props.deployId, taskId: props.taskId, procInsId: props.procInsId, dataId: props.dataId,
});
}
function handleRead() {
taskOpenModal(true, {
taskTitle: '转阅',
isUpdate: false,
showFooter: true,
taskType: 'read',
deployId: props.deployId,
taskId: props.taskId,
procInsId: props.procInsId,
dataId: props.dataId,
assignee: props.assignee,
userType: props.userType,
});
}
function setFormRef(el: any, nodeId: string) {
if (el) {
formComponentRef.value = el
emit('form-mounted', { nodeId, instance: el })
callInitFormData(el)
}
}
async function callInitFormData(formComponent: FormComponentInstance) {
if (!props.dataId || !formComponent?.initFormData) return
try { await formComponent.initFormData(props.dataId) } catch (error) { console.error('initFormData 调用失败:', error) }
}
async function handleFormDataUpdate(data: any) {
const curFormData = await getFormData()
formData.value = { ...curFormData, ...data }
formData.value["bpmNodeId"] = props.nextNode?.id
if (formComponentRef.value?.saveForm){
await formComponentRef.value.saveForm(formData.value);
}
emit('update:form-data', formData.value)
}
async function updateApprovalSuccess(data: any) {
emit('approval-success', data.dataId)
}
async function updateApprovalFail(data: any) {
emit('approval-fail', data.dataId)
}
function getFormInstance(): FormComponentInstance | null {
return formComponentRef.value
}
async function getFormData(): Promise<any> {
const formComponent = formComponentRef.value
if (!formComponent) return formData.value
if (typeof formComponent.getFormData === 'function') {
try {
return await formComponent.getFormData()
} catch (error) {
console.error('调用 getFormData 失败:', error)
}
}
if (formComponent.formData !== undefined) {
return formComponent.formData
}
return formData.value
}
async function validateForm(): Promise<boolean> {
const formComponent = formComponentRef.value
if (!formComponent) return true
if (typeof formComponent.validate === 'function') {
try { await formComponent.validate(); return true } catch (error) { return false }
}
return true
}
function resetFormData() {
formData.value = props.editableNode && props.externalFormData[props.editableNode.id] ? { ...props.externalFormData[props.editableNode.id] } : {}
}
async function reloadFormData() {
if (formComponentRef.value?.initFormData) await formComponentRef.value.initFormData(props.dataId)
}
watch(() => props.dataId, async () => {
if (formComponentRef.value) { await nextTick(); await reloadFormData() }
})
watch(() => props.externalFormData, (newData) => {
if (newData && props.editableNode && newData[props.editableNode.id]) formData.value = newData[props.editableNode.id]
}, { deep: true })
watch(() => props.editableNode, () => { resetFormData() })
function getComponent(url: string) {
if (!url) return createEmptyComponent()
if (componentCache.has(url)) return componentCache.get(url)
let componentPath = url.includes('/views') ? `/src${url}` : `/src/views${url}`
if (!componentPath.match(/\.(vue|js|ts|jsx|tsx)$/)) componentPath += '.vue'
const loader = modules[componentPath]
if (!loader) {
const ErrorComponent = createErrorComponent(`组件未找到: ${componentPath}`)
componentCache.set(url, ErrorComponent)
return ErrorComponent
}
const AsyncComponent = defineAsyncComponent({
loader: () => loader() as Promise<{ default: any }>,
loadingComponent: { render: () => h('div', { style: 'text-align: center; padding: 20px;' }, '加载中...') },
errorComponent: { render: () => h('div', { style: 'color: red; padding: 20px;' }, '组件加载失败') },
delay: 200, timeout: 3000
})
componentCache.set(url, AsyncComponent)
return AsyncComponent
}
function createEmptyComponent() {
return { render: () => h('div', { style: 'color: #999; padding: 20px; text-align: center;' }, '该节点未配置表单') }
}
function createErrorComponent(msg: string) {
return { render: () => h('div', { style: 'color: red; padding: 20px;' }, msg) }
}
async function handleSaveCurForm(curFormData: any) {
//const valid = await validateForm()
// if (!valid || !formComponentRef.value?.saveForm) return
await formComponentRef.value.saveForm(curFormData)
}
defineExpose({ getFormData, validateForm, getFormInstance, resetFormData, reloadFormData })
</script>
<style scoped lang="scss">
.form-panel {
flex: 1; display: flex; flex-direction: column; overflow: hidden; background-color: #f5f7fa;
.form-card { height: 100%; display: flex; flex-direction: column; background-color: #fff; :deep(.ant-card-head) { padding: 0 24px; background-color: #e9ecef; border-bottom: 1px solid #e8eef2; min-height: auto; .ant-card-head-wrapper { height: auto; } } :deep(.ant-card-body) { flex: 1; padding: 0; overflow: hidden; display: flex; flex-direction: column; } }
.form-header { display: flex; align-items: center; justify-content: space-between; width: 100%; padding: 16px 0; .form-title { font-size: 16px; font-weight: 500; color: #1f2f3d; } }
.form-content { flex: 1; overflow-y: auto; padding: 24px; &::-webkit-scrollbar { width: 6px; } &::-webkit-scrollbar-track { background: #f1f1f1; border-radius: 3px; } &::-webkit-scrollbar-thumb { background: #c1c1c1; border-radius: 3px; &:hover { background: #a8a8a8; } } }
.empty-form-state { flex: 1; display: flex; align-items: center; justify-content: center; }
}
</style>
<template>
<div>
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<template #tableTitle></template>
<template #action="{ record }">
<TableAction :actions="getTableAction(record)" />
</template>
</BasicTable>
</div>
</template>
<script lang="ts" name="problem-stProblemCheck" setup>
import { BasicTable, TableAction } from '/@/components/Table';
import { useListPage } from '/@/hooks/system/useListPage';
import { columns } from './StProblemCheck.data';
import { list, saveOrUpdate } from './StProblemCheck.api';
const props = defineProps({
beforeFlowNode: { type: Object, default: () => ({}) },
currentFlowNode: { type: Object, default: () => ({}) },
nextFlowNode: { type: Object, default: () => ({}) },
});
const emit = defineEmits(['callback', 'sendWorkFlow', 'openMultiForm']);
const { tableContext } = useListPage({
tableProps: {
title: '问题整改',
api: list,
columns,
canResize: true,
beforeFetch(params) {
params['bpmNodeId'] = props.currentFlowNode.id;
},
actionColumn: { width: 200, fixed: 'right' },
},
});
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
function handleExecute(record: Recordable) {
emit('openMultiForm', record);
}
function handleSendNext(record: Recordable) {
emit('sendWorkFlow', record);
}
function handleSuccess() {
selectedRowKeys.value = [];
reload();
}
function getTableAction(record) {
return [
{ label: '整改执行', onClick: handleExecute.bind(null, record) },
{ label: '提交', onClick: handleSendNext.bind(null, record) },
];
}
async function handleUpdate(dataId, flowNode) {
const record = { bpmNodeId: flowNode.id, deployId: flowNode.deployId, bpmStatus: 2, id: dataId };
await saveOrUpdate(record, true);
handleSuccess();
}
defineExpose({ handleUpdate });
</script>
<style scoped></style>
...@@ -124,6 +124,11 @@ ...@@ -124,6 +124,11 @@
} }
const handleDefinitionStart = async (data) => { const handleDefinitionStart = async (data) => {
if (data.procInsId) {
return;
}
const formData = { dataId: data.id, dataName: 'id' }; const formData = { dataId: data.id, dataName: 'id' };
const nodeDeployId = currentNode.value.deployId || ''; const nodeDeployId = currentNode.value.deployId || '';
const startResRaw = await definitionStartByDeployId(nodeDeployId, formData); const startResRaw = await definitionStartByDeployId(nodeDeployId, formData);
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论