【新增】前端代码第一次提交
This commit is contained in:
663
src/views/bpm/model/CategoryDraggableModel.vue
Normal file
663
src/views/bpm/model/CategoryDraggableModel.vue
Normal file
@@ -0,0 +1,663 @@
|
||||
<template>
|
||||
<div class="flex items-center h-50px" v-memo="[categoryInfo.name, isCategorySorting]">
|
||||
<!-- 头部:分类名 -->
|
||||
<div class="flex items-center">
|
||||
<el-tooltip content="拖动排序" v-if="isCategorySorting">
|
||||
<Icon
|
||||
:size="22"
|
||||
icon="ic:round-drag-indicator"
|
||||
class="ml-10px category-drag-icon cursor-move text-#8a909c"
|
||||
/>
|
||||
</el-tooltip>
|
||||
<h3 class="ml-20px mr-8px text-18px">{{ categoryInfo.name }}</h3>
|
||||
<div class="color-gray-600 text-16px"> ({{ categoryInfo.modelList?.length || 0 }}) </div>
|
||||
</div>
|
||||
<!-- 头部:操作 -->
|
||||
<div class="flex-1 flex" v-show="!isCategorySorting">
|
||||
<div
|
||||
v-if="categoryInfo.modelList.length > 0"
|
||||
class="ml-20px flex items-center"
|
||||
:class="[
|
||||
'transition-transform duration-300 cursor-pointer',
|
||||
isExpand ? 'rotate-180' : 'rotate-0'
|
||||
]"
|
||||
@click="isExpand = !isExpand"
|
||||
>
|
||||
<Icon icon="ep:arrow-down-bold" color="#999" />
|
||||
</div>
|
||||
<div class="ml-auto flex items-center" :class="isModelSorting ? 'mr-15px' : 'mr-45px'">
|
||||
<template v-if="!isModelSorting">
|
||||
<el-button
|
||||
v-if="categoryInfo.modelList.length > 0"
|
||||
link
|
||||
type="info"
|
||||
class="mr-20px"
|
||||
@click.stop="handleModelSort"
|
||||
>
|
||||
<Icon icon="fa:sort-amount-desc" class="mr-5px" />
|
||||
排序
|
||||
</el-button>
|
||||
<el-button v-else link type="info" class="mr-20px" @click.stop="openModelForm('create')">
|
||||
<Icon icon="fa:plus" class="mr-5px" />
|
||||
新建
|
||||
</el-button>
|
||||
<el-dropdown
|
||||
@command="(command) => handleCategoryCommand(command, categoryInfo)"
|
||||
placement="bottom"
|
||||
>
|
||||
<el-button link type="info">
|
||||
<Icon icon="ep:setting" class="mr-5px" />
|
||||
分类
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="handleRename"> 重命名 </el-dropdown-item>
|
||||
<el-dropdown-item command="handleDeleteCategory"> 删除该类 </el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-button @click.stop="handleModelSortCancel"> 取 消 </el-button>
|
||||
<el-button type="primary" @click.stop="handleModelSortSubmit"> 保存排序 </el-button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 模型列表 -->
|
||||
<el-collapse-transition>
|
||||
<div v-show="isExpand">
|
||||
<el-table
|
||||
v-if="modelList && modelList.length > 0"
|
||||
:class="categoryInfo.name"
|
||||
ref="tableRef"
|
||||
:data="modelList"
|
||||
row-key="id"
|
||||
:header-cell-style="tableHeaderStyle"
|
||||
:cell-style="tableCellStyle"
|
||||
:row-style="{ height: '68px' }"
|
||||
>
|
||||
<el-table-column label="流程名" prop="name" min-width="150">
|
||||
<template #default="{ row }">
|
||||
<div class="flex items-center">
|
||||
<el-tooltip content="拖动排序" v-if="isModelSorting">
|
||||
<Icon
|
||||
icon="ic:round-drag-indicator"
|
||||
class="drag-icon cursor-move text-#8a909c mr-10px"
|
||||
/>
|
||||
</el-tooltip>
|
||||
<el-image v-if="row.icon" :src="row.icon" class="h-38px w-38px mr-10px rounded" />
|
||||
<div v-else class="flow-icon">
|
||||
<span style="font-size: 12px; color: #fff">{{ subString(row.name, 0, 2) }}</span>
|
||||
</div>
|
||||
{{ row.name }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="可见范围" prop="startUserIds" min-width="150">
|
||||
<template #default="{ row }">
|
||||
<el-text v-if="!row.startUsers?.length && !row.startDepts?.length"> 全部可见 </el-text>
|
||||
<el-text v-else-if="row.startUsers.length === 1">
|
||||
{{ row.startUsers[0].nickname }}
|
||||
</el-text>
|
||||
<el-text v-else-if="row.startDepts?.length === 1">
|
||||
{{ row.startDepts[0].name }}
|
||||
</el-text>
|
||||
<el-text v-else-if="row.startDepts?.length > 1">
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
placement="top"
|
||||
:content="row.startDepts.map((dept: any) => dept.name).join('、')"
|
||||
>
|
||||
{{ row.startDepts[0].name }}等 {{ row.startDepts.length }} 个部门可见
|
||||
</el-tooltip>
|
||||
</el-text>
|
||||
<el-text v-else>
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
placement="top"
|
||||
:content="row.startUsers.map((user: any) => user.nickname).join('、')"
|
||||
>
|
||||
{{ row.startUsers[0].nickname }}等 {{ row.startUsers.length }} 人可见
|
||||
</el-tooltip>
|
||||
</el-text>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="流程类型" prop="type" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :value="row.type" :type="DICT_TYPE.BPM_MODEL_TYPE" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="表单信息" prop="formType" min-width="150">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-if="scope.row.formType === BpmModelFormType.NORMAL"
|
||||
type="primary"
|
||||
link
|
||||
@click="handleFormDetail(scope.row)"
|
||||
>
|
||||
<span>{{ scope.row.formName }}</span>
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else-if="scope.row.formType === BpmModelFormType.CUSTOM"
|
||||
type="primary"
|
||||
link
|
||||
@click="handleFormDetail(scope.row)"
|
||||
>
|
||||
<span>{{ scope.row.formCustomCreatePath }}</span>
|
||||
</el-button>
|
||||
<label v-else>暂无表单</label>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="最后发布" prop="deploymentTime" min-width="250">
|
||||
<template #default="scope">
|
||||
<div class="flex items-center">
|
||||
<span v-if="scope.row.processDefinition" class="w-150px">
|
||||
{{ formatDate(scope.row.processDefinition.deploymentTime) }}
|
||||
</span>
|
||||
<el-tag v-if="scope.row.processDefinition">
|
||||
v{{ scope.row.processDefinition.version }}
|
||||
</el-tag>
|
||||
<el-tag v-else type="warning">未部署</el-tag>
|
||||
<el-tag
|
||||
v-if="scope.row.processDefinition?.suspensionState === 2"
|
||||
type="warning"
|
||||
class="ml-10px"
|
||||
>
|
||||
已停用
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openModelForm('update', scope.row.id)"
|
||||
v-if="hasPermiUpdate"
|
||||
:disabled="!isManagerUser(scope.row)"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openModelForm('copy', scope.row.id)"
|
||||
v-if="hasPermiUpdate"
|
||||
:disabled="!isManagerUser(scope.row)"
|
||||
>
|
||||
复制
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
class="!ml-5px"
|
||||
type="primary"
|
||||
@click="handleDeploy(scope.row)"
|
||||
v-if="hasPermiDeploy"
|
||||
:disabled="!isManagerUser(scope.row)"
|
||||
>
|
||||
发布
|
||||
</el-button>
|
||||
<el-dropdown
|
||||
class="!align-middle ml-5px"
|
||||
@command="(command) => handleModelCommand(command, scope.row)"
|
||||
v-if="hasPermiMore"
|
||||
>
|
||||
<el-button type="primary" link>更多</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="handleDefinitionList" v-if="hasPermiPdQuery">
|
||||
历史
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
command="handleReport"
|
||||
v-if="
|
||||
checkPermi(['bpm:process-instance:manager-query']) &&
|
||||
scope.row.processDefinition
|
||||
"
|
||||
:disabled="!isManagerUser(scope.row)"
|
||||
>
|
||||
报表
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
command="handleChangeState"
|
||||
v-if="hasPermiUpdate && scope.row.processDefinition"
|
||||
:disabled="!isManagerUser(scope.row)"
|
||||
>
|
||||
{{ scope.row.processDefinition.suspensionState === 1 ? '停用' : '启用' }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
type="danger"
|
||||
command="handleClean"
|
||||
v-if="checkPermi(['bpm:model:clean'])"
|
||||
:disabled="!isManagerUser(scope.row)"
|
||||
>
|
||||
清理
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
type="danger"
|
||||
command="handleDelete"
|
||||
v-if="hasPermiDelete"
|
||||
:disabled="!isManagerUser(scope.row)"
|
||||
>
|
||||
删除
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-collapse-transition>
|
||||
|
||||
<!-- 弹窗:重命名分类 -->
|
||||
<Dialog :fullscreen="false" class="rename-dialog" v-model="renameCategoryVisible" width="400">
|
||||
<template #title>
|
||||
<div class="pl-10px font-bold text-18px"> 重命名分类 </div>
|
||||
</template>
|
||||
<div class="px-30px">
|
||||
<el-input v-model="renameCategoryForm.name" />
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="pr-25px pb-25px">
|
||||
<el-button @click="renameCategoryVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="handleRenameConfirm">确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</Dialog>
|
||||
|
||||
<!-- 弹窗:表单详情 -->
|
||||
<Dialog title="表单详情" :fullscreen="true" v-model="formDetailVisible">
|
||||
<form-create :rule="formDetailPreview.rule" :option="formDetailPreview.option" />
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { CategoryApi, CategoryVO } from '@/api/bpm/category'
|
||||
import Sortable from 'sortablejs'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import * as ModelApi from '@/api/bpm/model'
|
||||
import * as FormApi from '@/api/bpm/form'
|
||||
import { setConfAndFields2 } from '@/utils/formCreate'
|
||||
import { BpmModelFormType } from '@/utils/constants'
|
||||
import { checkPermi } from '@/utils/permission'
|
||||
import { useUserStoreWithOut } from '@/store/modules/user'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { cloneDeep, isEqual } from 'lodash-es'
|
||||
import { useDebounceFn } from '@vueuse/core'
|
||||
import { subString } from '@/utils/index'
|
||||
|
||||
defineOptions({ name: 'BpmModel' })
|
||||
|
||||
// 优化 Props 类型定义
|
||||
interface UserInfo {
|
||||
nickname: string
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
interface ProcessDefinition {
|
||||
deploymentTime: string
|
||||
version: number
|
||||
suspensionState: number
|
||||
}
|
||||
|
||||
interface ModelInfo {
|
||||
id: number
|
||||
name: string
|
||||
icon?: string
|
||||
startUsers?: UserInfo[]
|
||||
processDefinition?: ProcessDefinition
|
||||
formType?: number
|
||||
formId?: number
|
||||
formName?: string
|
||||
formCustomCreatePath?: string
|
||||
managerUserIds?: number[]
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
interface CategoryInfoProps {
|
||||
id: number
|
||||
name: string
|
||||
modelList: ModelInfo[]
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
categoryInfo: CategoryInfoProps
|
||||
isCategorySorting: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['success'])
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { push } = useRouter() // 路由
|
||||
const userStore = useUserStoreWithOut() // 用户信息缓存
|
||||
const isDark = computed(() => useAppStore().getIsDark) // 是否黑暗模式
|
||||
const router = useRouter() // 路由
|
||||
|
||||
const isModelSorting = ref(false) // 是否正处于排序状态
|
||||
const originalData = ref<ModelInfo[]>([]) // 原始数据
|
||||
const modelList = ref<ModelInfo[]>([]) // 模型列表
|
||||
const isExpand = ref(false) // 是否处于展开状态
|
||||
|
||||
// 使用 computed 优化表格样式计算
|
||||
const tableHeaderStyle = computed(() => ({
|
||||
backgroundColor: isDark.value ? '' : '#edeff0',
|
||||
paddingLeft: '10px'
|
||||
}))
|
||||
|
||||
const tableCellStyle = computed(() => ({
|
||||
paddingLeft: '10px'
|
||||
}))
|
||||
|
||||
/** 权限校验:通过 computed 解决列表的卡顿问题 */
|
||||
const hasPermiUpdate = computed(() => {
|
||||
return checkPermi(['bpm:model:update'])
|
||||
})
|
||||
const hasPermiDelete = computed(() => {
|
||||
return checkPermi(['bpm:model:delete'])
|
||||
})
|
||||
const hasPermiDeploy = computed(() => {
|
||||
return checkPermi(['bpm:model:deploy'])
|
||||
})
|
||||
const hasPermiMore = computed(() => {
|
||||
return checkPermi(['bpm:process-definition:query', 'bpm:model:update', 'bpm:model:delete'])
|
||||
})
|
||||
const hasPermiPdQuery = computed(() => {
|
||||
return checkPermi(['bpm:process-definition:query'])
|
||||
})
|
||||
|
||||
/** '更多'操作按钮 */
|
||||
const handleModelCommand = (command: string, row: any) => {
|
||||
switch (command) {
|
||||
case 'handleDefinitionList':
|
||||
handleDefinitionList(row)
|
||||
break
|
||||
case 'handleDelete':
|
||||
handleDelete(row)
|
||||
break
|
||||
case 'handleChangeState':
|
||||
handleChangeState(row)
|
||||
break
|
||||
case 'handleClean':
|
||||
handleClean(row)
|
||||
break
|
||||
case 'handleReport':
|
||||
router.push({
|
||||
name: 'BpmProcessInstanceReport',
|
||||
query: {
|
||||
processDefinitionId: row.processDefinition.id,
|
||||
processDefinitionKey: row.key
|
||||
}
|
||||
})
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/** '分类'操作按钮 */
|
||||
const handleCategoryCommand = async (command: string, row: any) => {
|
||||
switch (command) {
|
||||
case 'handleRename':
|
||||
renameCategoryForm.value = await CategoryApi.getCategory(row.id)
|
||||
renameCategoryVisible.value = true
|
||||
break
|
||||
case 'handleDeleteCategory':
|
||||
await handleDeleteCategory()
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row: any) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await ModelApi.deleteModel(row.id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
emit('success')
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 清理按钮操作 */
|
||||
const handleClean = async (row: any) => {
|
||||
try {
|
||||
// 清理的二次确认
|
||||
await message.confirm('是否确认清理流程名字为"' + row.name + '"的数据项?')
|
||||
// 发起清理
|
||||
await ModelApi.cleanModel(row.id)
|
||||
message.success('清理成功')
|
||||
// 刷新列表
|
||||
emit('success')
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 更新状态操作 */
|
||||
const handleChangeState = async (row: any) => {
|
||||
const state = row.processDefinition.suspensionState
|
||||
const newState = state === 1 ? 2 : 1
|
||||
try {
|
||||
// 修改状态的二次确认
|
||||
const id = row.id
|
||||
const statusState = state === 1 ? '停用' : '启用'
|
||||
const content = '是否确认' + statusState + '流程名字为"' + row.name + '"的数据项?'
|
||||
await message.confirm(content)
|
||||
// 发起修改状态
|
||||
await ModelApi.updateModelState(id, newState)
|
||||
message.success(statusState + '成功')
|
||||
// 刷新列表
|
||||
emit('success')
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 发布流程 */
|
||||
const handleDeploy = async (row: any) => {
|
||||
try {
|
||||
await message.confirm('是否确认发布该流程?')
|
||||
// 发起部署
|
||||
await ModelApi.deployModel(row.id)
|
||||
message.success(t('发布成功'))
|
||||
// 刷新列表
|
||||
emit('success')
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 跳转到指定流程定义列表 */
|
||||
const handleDefinitionList = (row: any) => {
|
||||
push({
|
||||
name: 'BpmProcessDefinition',
|
||||
query: {
|
||||
key: row.key
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 流程表单的详情按钮操作 */
|
||||
const formDetailVisible = ref(false)
|
||||
const formDetailPreview = ref({
|
||||
rule: [],
|
||||
option: {}
|
||||
})
|
||||
const handleFormDetail = async (row: any) => {
|
||||
if (row.formType == BpmModelFormType.NORMAL) {
|
||||
// 设置表单
|
||||
const data = await FormApi.getForm(row.formId)
|
||||
setConfAndFields2(formDetailPreview, data.conf, data.fields)
|
||||
// 弹窗打开
|
||||
formDetailVisible.value = true
|
||||
} else {
|
||||
await push({
|
||||
path: row.formCustomCreatePath
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/** 判断是否可以操作 */
|
||||
const isManagerUser = (row: any) => {
|
||||
const userId = userStore.getUser.id
|
||||
return row.managerUserIds && row.managerUserIds.includes(userId)
|
||||
}
|
||||
|
||||
/** 处理模型的排序 **/
|
||||
const handleModelSort = () => {
|
||||
// 保存初始数据
|
||||
originalData.value = cloneDeep(props.categoryInfo.modelList)
|
||||
isModelSorting.value = true
|
||||
initSort()
|
||||
}
|
||||
|
||||
/** 处理模型的排序提交 */
|
||||
const handleModelSortSubmit = async () => {
|
||||
// 保存排序
|
||||
const ids = modelList.value.map((item: any) => item.id)
|
||||
await ModelApi.updateModelSortBatch(ids)
|
||||
// 刷新列表
|
||||
isModelSorting.value = false
|
||||
message.success('排序模型成功')
|
||||
emit('success')
|
||||
}
|
||||
|
||||
/** 处理模型的排序取消 */
|
||||
const handleModelSortCancel = () => {
|
||||
// 恢复初始数据
|
||||
modelList.value = cloneDeep(originalData.value)
|
||||
isModelSorting.value = false
|
||||
}
|
||||
|
||||
/** 创建拖拽实例 */
|
||||
const tableRef = ref()
|
||||
const initSort = useDebounceFn(() => {
|
||||
const table = document.querySelector(`.${props.categoryInfo.name} .el-table__body-wrapper tbody`)
|
||||
if (!table) return
|
||||
|
||||
Sortable.create(table, {
|
||||
group: 'shared',
|
||||
animation: 150,
|
||||
draggable: '.el-table__row',
|
||||
handle: '.drag-icon',
|
||||
onEnd: ({ newDraggableIndex, oldDraggableIndex }) => {
|
||||
if (oldDraggableIndex !== newDraggableIndex) {
|
||||
modelList.value.splice(
|
||||
newDraggableIndex,
|
||||
0,
|
||||
modelList.value.splice(oldDraggableIndex, 1)[0]
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}, 200)
|
||||
|
||||
/** 更新 modelList 模型列表 */
|
||||
const updateModeList = useDebounceFn(() => {
|
||||
const newModelList = props.categoryInfo.modelList
|
||||
if (!isEqual(modelList.value, newModelList)) {
|
||||
modelList.value = cloneDeep(newModelList)
|
||||
if (newModelList?.length > 0) {
|
||||
isExpand.value = true
|
||||
}
|
||||
}
|
||||
}, 100)
|
||||
|
||||
/** 重命名弹窗确定 */
|
||||
const renameCategoryVisible = ref(false)
|
||||
const renameCategoryForm = ref({
|
||||
name: ''
|
||||
})
|
||||
const handleRenameConfirm = async () => {
|
||||
if (renameCategoryForm.value?.name.length === 0) {
|
||||
return message.warning('请输入名称')
|
||||
}
|
||||
// 发起修改
|
||||
await CategoryApi.updateCategory(renameCategoryForm.value as CategoryVO)
|
||||
message.success('重命名成功')
|
||||
// 刷新列表
|
||||
renameCategoryVisible.value = false
|
||||
emit('success')
|
||||
}
|
||||
|
||||
/** 删除分类 */
|
||||
const handleDeleteCategory = async () => {
|
||||
try {
|
||||
if (props.categoryInfo.modelList.length > 0) {
|
||||
return message.warning('该分类下仍有流程定义,不允许删除')
|
||||
}
|
||||
await message.confirm('确认删除分类吗?')
|
||||
// 发起删除
|
||||
await CategoryApi.deleteCategory(props.categoryInfo.id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
emit('success')
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 添加/修改/复制流程模型弹窗 */
|
||||
const openModelForm = async (type: string, id?: number) => {
|
||||
if (type === 'create') {
|
||||
await push({ name: 'BpmModelCreate' })
|
||||
} else {
|
||||
await push({
|
||||
name: 'BpmModelUpdate',
|
||||
params: { id, type }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.categoryInfo?.modelList) {
|
||||
updateModeList()
|
||||
}
|
||||
|
||||
if (props.isCategorySorting) {
|
||||
isExpand.value = false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.rename-dialog.el-dialog {
|
||||
padding: 0 !important;
|
||||
|
||||
.el-dialog__header {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.el-dialog__footer {
|
||||
border-top: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="scss" scoped>
|
||||
.flow-icon {
|
||||
display: flex;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
margin-right: 10px;
|
||||
background-color: var(--el-color-primary);
|
||||
border-radius: 0.25rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.category-draggable-model {
|
||||
:deep(.el-table__cell) {
|
||||
overflow: hidden;
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
// 优化表格渲染性能
|
||||
:deep(.el-table__body) {
|
||||
will-change: transform;
|
||||
transform: translateZ(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
174
src/views/bpm/model/definition/index.vue
Normal file
174
src/views/bpm/model/definition/index.vue
Normal file
@@ -0,0 +1,174 @@
|
||||
<template>
|
||||
<doc-alert title="工作流手册" url="https://doc.iocoder.cn/bpm/" />
|
||||
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="定义编号" align="center" prop="id" min-width="250" />
|
||||
<el-table-column label="流程名称" align="center" prop="name" min-width="150" />
|
||||
<el-table-column label="流程图标" align="center" min-width="50">
|
||||
<template #default="{ row }">
|
||||
<el-image v-if="row.icon" :src="row.icon" class="h-24px w-24pxrounded" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="可见范围" prop="startUserIds" min-width="100">
|
||||
<template #default="{ row }">
|
||||
<el-text v-if="!row.startUsers?.length"> 全部可见 </el-text>
|
||||
<el-text v-else-if="row.startUsers.length === 1">
|
||||
{{ row.startUsers[0].nickname }}
|
||||
</el-text>
|
||||
<el-text v-else>
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
placement="top"
|
||||
:content="row.startUsers.map((user: any) => user.nickname).join('、')"
|
||||
>
|
||||
{{ row.startUsers[0].nickname }}等 {{ row.startUsers.length }} 人可见
|
||||
</el-tooltip>
|
||||
</el-text>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="流程类型" prop="modelType" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :value="row.modelType" :type="DICT_TYPE.BPM_MODEL_TYPE" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="表单信息" prop="formType" min-width="150">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-if="scope.row.formType === BpmModelFormType.NORMAL"
|
||||
type="primary"
|
||||
link
|
||||
@click="handleFormDetail(scope.row)"
|
||||
>
|
||||
<span>{{ scope.row.formName }}</span>
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else-if="scope.row.formType === BpmModelFormType.CUSTOM"
|
||||
type="primary"
|
||||
link
|
||||
@click="handleFormDetail(scope.row)"
|
||||
>
|
||||
<span>{{ scope.row.formCustomCreatePath }}</span>
|
||||
</el-button>
|
||||
<label v-else>暂无表单</label>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="流程版本" align="center" min-width="80">
|
||||
<template #default="scope">
|
||||
<el-tag>v{{ scope.row.version }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="部署时间"
|
||||
align="center"
|
||||
prop="deploymentTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column label="操作" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openModelForm(scope.row.id)"
|
||||
v-hasPermi="['bpm:model:update']"
|
||||
>
|
||||
恢复
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 弹窗:表单详情 -->
|
||||
<Dialog title="表单详情" v-model="formDetailVisible" width="800">
|
||||
<form-create :rule="formDetailPreview.rule" :option="formDetailPreview.option" />
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as DefinitionApi from '@/api/bpm/definition'
|
||||
import { setConfAndFields2 } from '@/utils/formCreate'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { BpmModelFormType } from '@/utils/constants'
|
||||
|
||||
defineOptions({ name: 'BpmProcessDefinition' })
|
||||
|
||||
const { push } = useRouter() // 路由
|
||||
const { query } = useRoute() // 查询参数
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
key: query.key
|
||||
})
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await DefinitionApi.getProcessDefinitionPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 流程表单的详情按钮操作 */
|
||||
const formDetailVisible = ref(false)
|
||||
const formDetailPreview = ref({
|
||||
rule: [],
|
||||
option: {}
|
||||
})
|
||||
const handleFormDetail = async (row: any) => {
|
||||
if (row.formType == BpmModelFormType.NORMAL) {
|
||||
// 设置表单
|
||||
setConfAndFields2(formDetailPreview, row.formConf, row.formFields)
|
||||
// 弹窗打开
|
||||
formDetailVisible.value = true
|
||||
} else {
|
||||
await push({
|
||||
path: row.formCustomCreatePath
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/** 恢复流程模型弹窗 */
|
||||
const openModelForm = async (id?: number) => {
|
||||
await push({
|
||||
name: 'BpmModelUpdate',
|
||||
params: { id, type: 'definition' }
|
||||
})
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.flow-icon {
|
||||
display: flex;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
margin-right: 10px;
|
||||
background-color: var(--el-color-primary);
|
||||
border-radius: 0.25rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
344
src/views/bpm/model/form/BasicInfo.vue
Normal file
344
src/views/bpm/model/form/BasicInfo.vue
Normal file
@@ -0,0 +1,344 @@
|
||||
<template>
|
||||
<el-form ref="formRef" :model="modelData" :rules="rules" label-width="120px" class="mt-20px">
|
||||
<el-form-item label="流程标识" prop="key" class="mb-20px">
|
||||
<div class="flex items-center">
|
||||
<el-input
|
||||
class="!w-440px"
|
||||
v-model="modelData.key"
|
||||
:disabled="!!modelData.id"
|
||||
placeholder="请输入流程标识,以字母或下划线开头"
|
||||
/>
|
||||
<el-tooltip
|
||||
class="item"
|
||||
:content="modelData.id ? '流程标识不可修改!' : '新建后,流程标识不可修改!'"
|
||||
effect="light"
|
||||
placement="top"
|
||||
>
|
||||
<Icon icon="ep:question-filled" class="ml-5px" />
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程名称" prop="name" class="mb-20px">
|
||||
<el-input
|
||||
v-model="modelData.name"
|
||||
:disabled="!!modelData.id"
|
||||
clearable
|
||||
placeholder="请输入流程名称"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程分类" prop="category" class="mb-20px">
|
||||
<el-select
|
||||
class="!w-full"
|
||||
v-model="modelData.category"
|
||||
clearable
|
||||
placeholder="请选择流程分类"
|
||||
>
|
||||
<el-option
|
||||
v-for="category in categoryList"
|
||||
:key="category.code"
|
||||
:label="category.name"
|
||||
:value="category.code"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程图标" class="mb-20px">
|
||||
<UploadImg v-model="modelData.icon" :limit="1" height="64px" width="64px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="流程描述" prop="description" class="mb-20px">
|
||||
<el-input v-model="modelData.description" clearable type="textarea" />
|
||||
</el-form-item>
|
||||
<el-form-item label="流程类型" prop="type" class="mb-20px">
|
||||
<el-radio-group v-model="modelData.type">
|
||||
<el-radio
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_TYPE)"
|
||||
:key="dict.value"
|
||||
:value="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否可见" prop="visible" class="mb-20px">
|
||||
<el-radio-group v-model="modelData.visible">
|
||||
<el-radio
|
||||
v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
|
||||
:key="dict.value as string"
|
||||
:value="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="谁可以发起" prop="startUserType" class="mb-20px">
|
||||
<el-select
|
||||
v-model="modelData.startUserType"
|
||||
placeholder="请选择谁可以发起"
|
||||
@change="handleStartUserTypeChange"
|
||||
>
|
||||
<el-option label="全员" :value="0" />
|
||||
<el-option label="指定人员" :value="1" />
|
||||
<el-option label="指定部门" :value="2" />
|
||||
</el-select>
|
||||
<div v-if="modelData.startUserType === 1" class="mt-2 flex flex-wrap gap-2">
|
||||
<div
|
||||
v-for="user in selectedStartUsers"
|
||||
:key="user.id"
|
||||
class="bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
|
||||
>
|
||||
<el-avatar class="!m-5px" :size="28" v-if="user.avatar" :src="user.avatar" />
|
||||
<el-avatar class="!m-5px" :size="28" v-else>
|
||||
{{ user.nickname.substring(0, 1) }}
|
||||
</el-avatar>
|
||||
{{ user.nickname }}
|
||||
<Icon
|
||||
icon="ep:close"
|
||||
class="ml-2 cursor-pointer hover:text-red-500"
|
||||
@click="handleRemoveStartUser(user)"
|
||||
/>
|
||||
</div>
|
||||
<el-button type="primary" link @click="openStartUserSelect">
|
||||
<Icon icon="ep:plus" /> 选择人员
|
||||
</el-button>
|
||||
</div>
|
||||
<div v-if="modelData.startUserType === 2" class="mt-2 flex flex-wrap gap-2">
|
||||
<div
|
||||
v-for="dept in selectedStartDepts"
|
||||
:key="dept.id"
|
||||
class="bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
|
||||
>
|
||||
<Icon icon="ep:office-building" class="!m-5px text-20px" />
|
||||
{{ dept.name }}
|
||||
<Icon
|
||||
icon="ep:close"
|
||||
class="ml-2 cursor-pointer hover:text-red-500"
|
||||
@click="handleRemoveStartDept(dept)"
|
||||
/>
|
||||
</div>
|
||||
<el-button type="primary" link @click="openStartDeptSelect">
|
||||
<Icon icon="ep:plus" /> 选择部门
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程管理员" prop="managerUserIds" class="mb-20px">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<div
|
||||
v-for="user in selectedManagerUsers"
|
||||
:key="user.id"
|
||||
class="bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
|
||||
>
|
||||
<el-avatar class="!m-5px" :size="28" v-if="user.avatar" :src="user.avatar" />
|
||||
<el-avatar class="!m-5px" :size="28" v-else>
|
||||
{{ user.nickname.substring(0, 1) }}
|
||||
</el-avatar>
|
||||
{{ user.nickname }}
|
||||
<Icon
|
||||
icon="ep:close"
|
||||
class="ml-2 cursor-pointer hover:text-red-500"
|
||||
@click="handleRemoveManagerUser(user)"
|
||||
/>
|
||||
</div>
|
||||
<el-button type="primary" link @click="openManagerUserSelect">
|
||||
<Icon icon="ep:plus" />选择人员
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 用户选择弹窗 -->
|
||||
<UserSelectForm ref="userSelectFormRef" @confirm="handleUserSelectConfirm" />
|
||||
<!-- 部门选择弹窗 -->
|
||||
<DeptSelectForm
|
||||
ref="deptSelectFormRef"
|
||||
:multiple="true"
|
||||
:check-strictly="true"
|
||||
@confirm="handleDeptSelectConfirm"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getBoolDictOptions, getIntDictOptions } from '@/utils/dict'
|
||||
import { UserVO } from '@/api/system/user'
|
||||
import { DeptVO } from '@/api/system/dept'
|
||||
import { CategoryVO } from '@/api/bpm/category'
|
||||
|
||||
const props = defineProps({
|
||||
categoryList: {
|
||||
type: Array as PropType<CategoryVO[]>,
|
||||
required: true
|
||||
},
|
||||
userList: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
deptList: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const formRef = ref()
|
||||
const selectedStartUsers = ref<UserVO[]>([])
|
||||
const selectedStartDepts = ref<DeptVO[]>([])
|
||||
const selectedManagerUsers = ref<UserVO[]>([])
|
||||
const userSelectFormRef = ref()
|
||||
const deptSelectFormRef = ref()
|
||||
const currentSelectType = ref<'start' | 'manager'>('start')
|
||||
|
||||
const rules = {
|
||||
name: [{ required: true, message: '流程名称不能为空', trigger: 'blur' }],
|
||||
key: [{ required: true, message: '流程标识不能为空', trigger: 'blur' }],
|
||||
category: [{ required: true, message: '流程分类不能为空', trigger: 'blur' }],
|
||||
type: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }],
|
||||
visible: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }],
|
||||
managerUserIds: [{ required: true, message: '流程管理员不能为空', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
// 创建本地数据副本
|
||||
const modelData = defineModel<any>()
|
||||
|
||||
// 初始化选中的用户
|
||||
watch(
|
||||
() => modelData.value,
|
||||
(newVal) => {
|
||||
if (newVal.startUserIds?.length) {
|
||||
selectedStartUsers.value = props.userList.filter((user: UserVO) =>
|
||||
newVal.startUserIds.includes(user.id)
|
||||
) as UserVO[]
|
||||
} else {
|
||||
selectedStartUsers.value = []
|
||||
}
|
||||
if (newVal.startDeptIds?.length) {
|
||||
selectedStartDepts.value = props.deptList.filter((dept: DeptVO) =>
|
||||
newVal.startDeptIds.includes(dept.id)
|
||||
) as DeptVO[]
|
||||
} else {
|
||||
selectedStartDepts.value = []
|
||||
}
|
||||
if (newVal.managerUserIds?.length) {
|
||||
selectedManagerUsers.value = props.userList.filter((user: UserVO) =>
|
||||
newVal.managerUserIds.includes(user.id)
|
||||
) as UserVO[]
|
||||
} else {
|
||||
selectedManagerUsers.value = []
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
/** 打开发起人选择 */
|
||||
const openStartUserSelect = () => {
|
||||
currentSelectType.value = 'start'
|
||||
userSelectFormRef.value.open(0, selectedStartUsers.value)
|
||||
}
|
||||
|
||||
/** 打开部门选择 */
|
||||
const openStartDeptSelect = () => {
|
||||
deptSelectFormRef.value.open(selectedStartDepts.value)
|
||||
}
|
||||
|
||||
/** 打开管理员选择 */
|
||||
const openManagerUserSelect = () => {
|
||||
currentSelectType.value = 'manager'
|
||||
userSelectFormRef.value.open(0, selectedManagerUsers.value)
|
||||
}
|
||||
|
||||
/** 处理用户选择确认 */
|
||||
const handleUserSelectConfirm = (_, users: UserVO[]) => {
|
||||
if (currentSelectType.value === 'start') {
|
||||
modelData.value = {
|
||||
...modelData.value,
|
||||
startUserIds: users.map((u) => u.id)
|
||||
}
|
||||
} else {
|
||||
modelData.value = {
|
||||
...modelData.value,
|
||||
managerUserIds: users.map((u) => u.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 处理部门选择确认 */
|
||||
const handleDeptSelectConfirm = (depts: DeptVO[]) => {
|
||||
modelData.value = {
|
||||
...modelData.value,
|
||||
startDeptIds: depts.map((d) => d.id)
|
||||
}
|
||||
}
|
||||
|
||||
/** 处理发起人类型变化 */
|
||||
const handleStartUserTypeChange = (value: number) => {
|
||||
if (value === 0) {
|
||||
modelData.value = {
|
||||
...modelData.value,
|
||||
startUserIds: [],
|
||||
startDeptIds: []
|
||||
}
|
||||
} else if (value === 1) {
|
||||
modelData.value = {
|
||||
...modelData.value,
|
||||
startDeptIds: []
|
||||
}
|
||||
} else if (value === 2) {
|
||||
modelData.value = {
|
||||
...modelData.value,
|
||||
startUserIds: []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 移除发起人 */
|
||||
const handleRemoveStartUser = (user: UserVO) => {
|
||||
modelData.value = {
|
||||
...modelData.value,
|
||||
startUserIds: modelData.value.startUserIds.filter((id: number) => id !== user.id)
|
||||
}
|
||||
}
|
||||
|
||||
/** 移除部门 */
|
||||
const handleRemoveStartDept = (dept: DeptVO) => {
|
||||
modelData.value = {
|
||||
...modelData.value,
|
||||
startDeptIds: modelData.value.startDeptIds.filter((id: number) => id !== dept.id)
|
||||
}
|
||||
}
|
||||
|
||||
/** 移除管理员 */
|
||||
const handleRemoveManagerUser = (user: UserVO) => {
|
||||
modelData.value = {
|
||||
...modelData.value,
|
||||
managerUserIds: modelData.value.managerUserIds.filter((id: number) => id !== user.id)
|
||||
}
|
||||
}
|
||||
|
||||
/** 表单校验 */
|
||||
const validate = async () => {
|
||||
await formRef.value?.validate()
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
validate
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.bg-gray-100 {
|
||||
background-color: #f5f7fa;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
background-color: #e6e8eb;
|
||||
}
|
||||
|
||||
.ep-close {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
transition: color 0.3s;
|
||||
|
||||
&:hover {
|
||||
color: #f56c6c;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
442
src/views/bpm/model/form/ExtraSettings.vue
Normal file
442
src/views/bpm/model/form/ExtraSettings.vue
Normal file
@@ -0,0 +1,442 @@
|
||||
<template>
|
||||
<el-form ref="formRef" :model="modelData" label-width="120px" class="mt-20px">
|
||||
<el-form-item class="mb-20px">
|
||||
<template #label>
|
||||
<el-text size="large" tag="b">提交人权限</el-text>
|
||||
</template>
|
||||
<div class="flex flex-col">
|
||||
<el-checkbox v-model="modelData.allowCancelRunningProcess" label="允许撤销审批中的申请" />
|
||||
<div class="ml-22px">
|
||||
<el-text type="info"> 第一个审批节点通过后,提交人仍可撤销申请 </el-text>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="modelData.processIdRule" class="mb-20px">
|
||||
<template #label>
|
||||
<el-text size="large" tag="b">流程编码</el-text>
|
||||
</template>
|
||||
<div class="flex flex-col">
|
||||
<div>
|
||||
<el-input
|
||||
v-model="modelData.processIdRule.prefix"
|
||||
class="w-130px!"
|
||||
placeholder="前缀"
|
||||
:disabled="!modelData.processIdRule.enable"
|
||||
>
|
||||
<template #prepend>
|
||||
<el-checkbox v-model="modelData.processIdRule.enable" />
|
||||
</template>
|
||||
</el-input>
|
||||
<el-select
|
||||
v-model="modelData.processIdRule.infix"
|
||||
class="w-130px! ml-5px"
|
||||
placeholder="中缀"
|
||||
:disabled="!modelData.processIdRule.enable"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in timeOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
<el-input
|
||||
v-model="modelData.processIdRule.postfix"
|
||||
class="w-80px! ml-5px"
|
||||
placeholder="后缀"
|
||||
:disabled="!modelData.processIdRule.enable"
|
||||
/>
|
||||
<el-input-number
|
||||
v-model="modelData.processIdRule.length"
|
||||
class="w-120px! ml-5px"
|
||||
:min="5"
|
||||
:disabled="!modelData.processIdRule.enable"
|
||||
/>
|
||||
</div>
|
||||
<div class="ml-22px" v-if="modelData.processIdRule.enable">
|
||||
<el-text type="info"> 编码示例:{{ numberExample }} </el-text>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item class="mb-20px">
|
||||
<template #label>
|
||||
<el-text size="large" tag="b">自动去重</el-text>
|
||||
</template>
|
||||
<div class="flex flex-col">
|
||||
<div>
|
||||
<el-text> 同一审批人在流程中重复出现时: </el-text>
|
||||
</div>
|
||||
<el-radio-group v-model="modelData.autoApprovalType">
|
||||
<div class="flex flex-col">
|
||||
<el-radio :value="0">不自动通过</el-radio>
|
||||
<el-radio :value="1">仅审批一次,后续重复的审批节点均自动通过</el-radio>
|
||||
<el-radio :value="2">仅针对连续审批的节点自动通过</el-radio>
|
||||
</div>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="modelData.titleSetting" class="mb-20px">
|
||||
<template #label>
|
||||
<el-text size="large" tag="b">标题设置</el-text>
|
||||
</template>
|
||||
<div class="flex flex-col">
|
||||
<el-radio-group v-model="modelData.titleSetting.enable">
|
||||
<div class="flex flex-col">
|
||||
<el-radio :value="false"
|
||||
>系统默认 <el-text type="info"> 展示流程名称 </el-text></el-radio
|
||||
>
|
||||
<el-radio :value="true">
|
||||
自定义标题
|
||||
<el-text>
|
||||
<el-tooltip content="输入字符 '{' 即可插入表单字段" effect="light" placement="top">
|
||||
<Icon icon="ep:question-filled" class="ml-5px" />
|
||||
</el-tooltip>
|
||||
</el-text>
|
||||
</el-radio>
|
||||
</div>
|
||||
</el-radio-group>
|
||||
<el-mention
|
||||
v-if="modelData.titleSetting.enable"
|
||||
v-model="modelData.titleSetting.title"
|
||||
type="textarea"
|
||||
prefix="{"
|
||||
split="}"
|
||||
whole
|
||||
:options="formFieldOptions4Title"
|
||||
placeholder="请插入表单字段(输入 '{' 可以选择表单字段)或输入文本"
|
||||
class="w-600px!"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="modelData.summarySetting && modelData.formType === BpmModelFormType.NORMAL"
|
||||
class="mb-20px"
|
||||
>
|
||||
<template #label>
|
||||
<el-text size="large" tag="b">摘要设置</el-text>
|
||||
</template>
|
||||
<div class="flex flex-col">
|
||||
<el-radio-group v-model="modelData.summarySetting.enable">
|
||||
<div class="flex flex-col">
|
||||
<el-radio :value="false">
|
||||
系统默认 <el-text type="info"> 展示表单前 3 个字段 </el-text>
|
||||
</el-radio>
|
||||
<el-radio :value="true"> 自定义摘要 </el-radio>
|
||||
</div>
|
||||
</el-radio-group>
|
||||
<el-select
|
||||
class="w-500px!"
|
||||
v-if="modelData.summarySetting.enable"
|
||||
v-model="modelData.summarySetting.summary"
|
||||
multiple
|
||||
placeholder="请选择要展示的表单字段"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in formFieldOptions4Summary"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item class="mb-20px">
|
||||
<template #label>
|
||||
<el-text size="large" tag="b">流程前置通知</el-text>
|
||||
</template>
|
||||
<div class="flex flex-col w-100%">
|
||||
<div class="flex">
|
||||
<el-switch
|
||||
v-model="processBeforeTriggerEnable"
|
||||
@change="handleProcessBeforeTriggerEnableChange"
|
||||
/>
|
||||
<div class="ml-80px">流程启动后通知</div>
|
||||
</div>
|
||||
<HttpRequestSetting
|
||||
v-if="processBeforeTriggerEnable"
|
||||
v-model:setting="modelData.processBeforeTriggerSetting"
|
||||
:responseEnable="true"
|
||||
:formItemPrefix="'processBeforeTriggerSetting'"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item class="mb-20px">
|
||||
<template #label>
|
||||
<el-text size="large" tag="b">流程后置通知</el-text>
|
||||
</template>
|
||||
<div class="flex flex-col w-100%">
|
||||
<div class="flex">
|
||||
<el-switch
|
||||
v-model="processAfterTriggerEnable"
|
||||
@change="handleProcessAfterTriggerEnableChange"
|
||||
/>
|
||||
<div class="ml-80px">流程结束后通知</div>
|
||||
</div>
|
||||
<HttpRequestSetting
|
||||
v-if="processAfterTriggerEnable"
|
||||
v-model:setting="modelData.processAfterTriggerSetting"
|
||||
:responseEnable="true"
|
||||
:formItemPrefix="'processAfterTriggerSetting'"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item class="mb-20px">
|
||||
<template #label>
|
||||
<el-text size="large" tag="b">任务前置通知</el-text>
|
||||
</template>
|
||||
<div class="flex flex-col w-100%">
|
||||
<div class="flex">
|
||||
<el-switch
|
||||
v-model="taskBeforeTriggerEnable"
|
||||
@change="handleTaskBeforeTriggerEnableChange"
|
||||
/>
|
||||
<div class="ml-80px">任务执行时通知</div>
|
||||
</div>
|
||||
<HttpRequestSetting
|
||||
v-if="taskBeforeTriggerEnable"
|
||||
v-model:setting="modelData.taskBeforeTriggerSetting"
|
||||
:responseEnable="true"
|
||||
:formItemPrefix="'taskBeforeTriggerSetting'"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item class="mb-20px">
|
||||
<template #label>
|
||||
<el-text size="large" tag="b">任务后置通知</el-text>
|
||||
</template>
|
||||
<div class="flex flex-col w-100%">
|
||||
<div class="flex">
|
||||
<el-switch
|
||||
v-model="taskAfterTriggerEnable"
|
||||
@change="handleTaskAfterTriggerEnableChange"
|
||||
/>
|
||||
<div class="ml-80px">任务结束后通知</div>
|
||||
</div>
|
||||
<HttpRequestSetting
|
||||
v-if="taskAfterTriggerEnable"
|
||||
v-model:setting="modelData.taskAfterTriggerSetting"
|
||||
:responseEnable="true"
|
||||
:formItemPrefix="'taskAfterTriggerSetting'"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import dayjs from 'dayjs'
|
||||
import { BpmAutoApproveType, BpmModelFormType } from '@/utils/constants'
|
||||
import * as FormApi from '@/api/bpm/form'
|
||||
import { parseFormFields } from '@/components/FormCreate/src/utils'
|
||||
import { ProcessVariableEnum } from '@/components/SimpleProcessDesignerV2/src/consts'
|
||||
import HttpRequestSetting from '@/components/SimpleProcessDesignerV2/src/nodes-config/components/HttpRequestSetting.vue'
|
||||
|
||||
const modelData = defineModel<any>()
|
||||
|
||||
/** 自定义 ID 流程编码 */
|
||||
const timeOptions = ref([
|
||||
{
|
||||
value: '',
|
||||
label: '无'
|
||||
},
|
||||
{
|
||||
value: 'DAY',
|
||||
label: '精确到日'
|
||||
},
|
||||
{
|
||||
value: 'HOUR',
|
||||
label: '精确到时'
|
||||
},
|
||||
{
|
||||
value: 'MINUTE',
|
||||
label: '精确到分'
|
||||
},
|
||||
{
|
||||
value: 'SECOND',
|
||||
label: '精确到秒'
|
||||
}
|
||||
])
|
||||
const numberExample = computed(() => {
|
||||
if (modelData.value.processIdRule.enable) {
|
||||
let infix = ''
|
||||
switch (modelData.value.processIdRule.infix) {
|
||||
case 'DAY':
|
||||
infix = dayjs().format('YYYYMMDD')
|
||||
break
|
||||
case 'HOUR':
|
||||
infix = dayjs().format('YYYYMMDDHH')
|
||||
break
|
||||
case 'MINUTE':
|
||||
infix = dayjs().format('YYYYMMDDHHmm')
|
||||
break
|
||||
case 'SECOND':
|
||||
infix = dayjs().format('YYYYMMDDHHmmss')
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
return (
|
||||
modelData.value.processIdRule.prefix +
|
||||
infix +
|
||||
modelData.value.processIdRule.postfix +
|
||||
'1'.padStart(modelData.value.processIdRule.length - 1, '0')
|
||||
)
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
})
|
||||
|
||||
/** 是否开启流程前置通知 */
|
||||
const processBeforeTriggerEnable = ref(false)
|
||||
const handleProcessBeforeTriggerEnableChange = (val: boolean | string | number) => {
|
||||
if (val) {
|
||||
modelData.value.processBeforeTriggerSetting = {
|
||||
url: '',
|
||||
header: [],
|
||||
body: [],
|
||||
response: []
|
||||
}
|
||||
} else {
|
||||
modelData.value.processBeforeTriggerSetting = null
|
||||
}
|
||||
}
|
||||
|
||||
/** 是否开启流程后置通知 */
|
||||
const processAfterTriggerEnable = ref(false)
|
||||
const handleProcessAfterTriggerEnableChange = (val: boolean | string | number) => {
|
||||
if (val) {
|
||||
modelData.value.processAfterTriggerSetting = {
|
||||
url: '',
|
||||
header: [],
|
||||
body: [],
|
||||
response: []
|
||||
}
|
||||
} else {
|
||||
modelData.value.processAfterTriggerSetting = null
|
||||
}
|
||||
}
|
||||
|
||||
/** 是否开启任务前置通知 */
|
||||
const taskBeforeTriggerEnable = ref(false)
|
||||
const handleTaskBeforeTriggerEnableChange = (val: boolean | string | number) => {
|
||||
if (val) {
|
||||
modelData.value.taskBeforeTriggerSetting = {
|
||||
url: '',
|
||||
header: [],
|
||||
body: [],
|
||||
response: []
|
||||
}
|
||||
} else {
|
||||
modelData.value.taskBeforeTriggerSetting = null
|
||||
}
|
||||
}
|
||||
|
||||
/** 是否开启任务后置通知 */
|
||||
const taskAfterTriggerEnable = ref(false)
|
||||
const handleTaskAfterTriggerEnableChange = (val: boolean | string | number) => {
|
||||
if (val) {
|
||||
modelData.value.taskAfterTriggerSetting = {
|
||||
url: '',
|
||||
header: [],
|
||||
body: [],
|
||||
response: []
|
||||
}
|
||||
} else {
|
||||
modelData.value.taskAfterTriggerSetting = null
|
||||
}
|
||||
}
|
||||
|
||||
/** 表单选项 */
|
||||
const formField = ref<Array<{ field: string; title: string }>>([])
|
||||
const formFieldOptions4Title = computed(() => {
|
||||
let cloneFormField = formField.value.map((item) => {
|
||||
return {
|
||||
label: item.title,
|
||||
value: item.field
|
||||
}
|
||||
})
|
||||
// 固定添加发起人 ID 字段
|
||||
cloneFormField.unshift({
|
||||
label: '流程名称',
|
||||
value: ProcessVariableEnum.PROCESS_DEFINITION_NAME
|
||||
})
|
||||
cloneFormField.unshift({
|
||||
label: '发起时间',
|
||||
value: ProcessVariableEnum.START_TIME
|
||||
})
|
||||
cloneFormField.unshift({
|
||||
label: '发起人',
|
||||
value: ProcessVariableEnum.START_USER_ID
|
||||
})
|
||||
return cloneFormField
|
||||
})
|
||||
const formFieldOptions4Summary = computed(() => {
|
||||
return formField.value.map((item) => {
|
||||
return {
|
||||
label: item.title,
|
||||
value: item.field
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
/** 兼容以前未配置更多设置的流程 */
|
||||
const initData = () => {
|
||||
if (!modelData.value.processIdRule) {
|
||||
modelData.value.processIdRule = {
|
||||
enable: false,
|
||||
prefix: '',
|
||||
infix: '',
|
||||
postfix: '',
|
||||
length: 5
|
||||
}
|
||||
}
|
||||
if (!modelData.value.autoApprovalType) {
|
||||
modelData.value.autoApprovalType = BpmAutoApproveType.NONE
|
||||
}
|
||||
if (!modelData.value.titleSetting) {
|
||||
modelData.value.titleSetting = {
|
||||
enable: false,
|
||||
title: ''
|
||||
}
|
||||
}
|
||||
if (!modelData.value.summarySetting) {
|
||||
modelData.value.summarySetting = {
|
||||
enable: false,
|
||||
summary: []
|
||||
}
|
||||
}
|
||||
if (modelData.value.processBeforeTriggerSetting) {
|
||||
processBeforeTriggerEnable.value = true
|
||||
}
|
||||
if (modelData.value.processAfterTriggerSetting) {
|
||||
processAfterTriggerEnable.value = true
|
||||
}
|
||||
if (modelData.value.taskBeforeTriggerSetting) {
|
||||
taskBeforeTriggerEnable.value = true
|
||||
}
|
||||
if (modelData.value.taskAfterTriggerSetting) {
|
||||
taskAfterTriggerEnable.value = true
|
||||
}
|
||||
}
|
||||
defineExpose({ initData })
|
||||
|
||||
/** 监听表单 ID 变化,加载表单数据 */
|
||||
watch(
|
||||
() => modelData.value.formId,
|
||||
async (newFormId) => {
|
||||
if (newFormId && modelData.value.formType === BpmModelFormType.NORMAL) {
|
||||
const data = await FormApi.getForm(newFormId)
|
||||
const result: Array<{ field: string; title: string }> = []
|
||||
if (data.fields) {
|
||||
data.fields.forEach((fieldStr: string) => {
|
||||
parseFormFields(JSON.parse(fieldStr), result)
|
||||
})
|
||||
}
|
||||
formField.value = result
|
||||
} else {
|
||||
formField.value = []
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
</script>
|
||||
129
src/views/bpm/model/form/FormDesign.vue
Normal file
129
src/views/bpm/model/form/FormDesign.vue
Normal file
@@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<el-form ref="formRef" :model="modelData" :rules="rules" label-width="120px" class="mt-20px">
|
||||
<el-form-item label="表单类型" prop="formType" class="mb-20px">
|
||||
<el-radio-group v-model="modelData.formType">
|
||||
<el-radio
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_FORM_TYPE)"
|
||||
:key="dict.value"
|
||||
:value="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="modelData.formType === BpmModelFormType.NORMAL" label="流程表单" prop="formId">
|
||||
<el-select v-model="modelData.formId" clearable style="width: 100%">
|
||||
<el-option v-for="form in formList" :key="form.id" :label="form.name" :value="form.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="modelData.formType === BpmModelFormType.CUSTOM" label="表单提交路由" prop="formCustomCreatePath">
|
||||
<el-input
|
||||
v-model="modelData.formCustomCreatePath"
|
||||
placeholder="请输入表单提交路由"
|
||||
style="width: 330px"
|
||||
/>
|
||||
<el-tooltip
|
||||
class="item"
|
||||
content="自定义表单的提交路径,使用 Vue 的路由地址,例如说:bpm/oa/leave/create.vue"
|
||||
effect="light"
|
||||
placement="top"
|
||||
>
|
||||
<Icon icon="ep:question" class="ml-5px" />
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="modelData.formType === BpmModelFormType.CUSTOM" label="表单查看地址" prop="formCustomViewPath">
|
||||
<el-input
|
||||
v-model="modelData.formCustomViewPath"
|
||||
placeholder="请输入表单查看的组件地址"
|
||||
style="width: 330px"
|
||||
/>
|
||||
<el-tooltip
|
||||
class="item"
|
||||
content="自定义表单的查看组件地址,使用 Vue 的组件地址,例如说:bpm/oa/leave/detail.vue"
|
||||
effect="light"
|
||||
placement="top"
|
||||
>
|
||||
<Icon icon="ep:question" class="ml-5px" />
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
<!-- 表单预览 -->
|
||||
<div
|
||||
v-if="modelData.formType === BpmModelFormType.NORMAL && modelData.formId && formPreview.rule.length > 0"
|
||||
class="mt-20px"
|
||||
>
|
||||
<div class="flex items-center mb-15px">
|
||||
<div class="h-15px w-4px bg-[#1890ff] mr-10px"></div>
|
||||
<span class="text-15px font-bold">表单预览</span>
|
||||
</div>
|
||||
<form-create
|
||||
v-model="formPreview.formData"
|
||||
:rule="formPreview.rule"
|
||||
:option="formPreview.option"
|
||||
/>
|
||||
</div>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import * as FormApi from '@/api/bpm/form'
|
||||
import { setConfAndFields2 } from '@/utils/formCreate'
|
||||
import { BpmModelFormType } from '@/utils/constants'
|
||||
|
||||
const props = defineProps({
|
||||
formList: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const formRef = ref()
|
||||
|
||||
// 创建本地数据副本
|
||||
const modelData = defineModel<any>()
|
||||
|
||||
// 表单预览数据
|
||||
const formPreview = ref({
|
||||
formData: {},
|
||||
rule: [],
|
||||
option: {
|
||||
submitBtn: false,
|
||||
resetBtn: false,
|
||||
formData: {}
|
||||
}
|
||||
})
|
||||
|
||||
// 监听表单ID变化,加载表单数据
|
||||
watch(
|
||||
() => modelData.value.formId,
|
||||
async (newFormId) => {
|
||||
if (newFormId && modelData.value.formType === BpmModelFormType.NORMAL) {
|
||||
const data = await FormApi.getForm(newFormId)
|
||||
setConfAndFields2(formPreview.value, data.conf, data.fields)
|
||||
// 设置只读
|
||||
formPreview.value.rule.forEach((item: any) => {
|
||||
item.props = { ...item.props, disabled: true }
|
||||
})
|
||||
} else {
|
||||
formPreview.value.rule = []
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
const rules = {
|
||||
formType: [{ required: true, message: '表单类型不能为空', trigger: 'blur' }],
|
||||
formId: [{ required: true, message: '流程表单不能为空', trigger: 'blur' }],
|
||||
formCustomCreatePath: [{ required: true, message: '表单提交路由不能为空', trigger: 'blur' }],
|
||||
formCustomViewPath: [{ required: true, message: '表单查看地址不能为空', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
/** 表单校验 */
|
||||
const validate = async () => {
|
||||
await formRef.value?.validate()
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
validate
|
||||
})
|
||||
</script>
|
||||
72
src/views/bpm/model/form/ProcessDesign.vue
Normal file
72
src/views/bpm/model/form/ProcessDesign.vue
Normal file
@@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<!-- BPMN设计器 -->
|
||||
<template v-if="modelData.type === BpmModelType.BPMN">
|
||||
<BpmModelEditor
|
||||
v-if="showDesigner"
|
||||
:model-id="modelData.id"
|
||||
:model-key="modelData.key"
|
||||
:model-name="modelData.name"
|
||||
@success="handleDesignSuccess"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- Simple设计器 -->
|
||||
<template v-else>
|
||||
<SimpleModelDesign
|
||||
v-if="showDesigner"
|
||||
:model-id="modelData.id"
|
||||
:model-key="modelData.key"
|
||||
:model-name="modelData.name"
|
||||
:start-user-ids="modelData.startUserIds"
|
||||
:start-dept-ids="modelData.startDeptIds"
|
||||
@success="handleDesignSuccess"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { BpmModelType } from '@/utils/constants'
|
||||
import BpmModelEditor from './editor/index.vue'
|
||||
import SimpleModelDesign from '../../simple/SimpleModelDesign.vue'
|
||||
|
||||
// 创建本地数据副本
|
||||
const modelData = defineModel<any>()
|
||||
|
||||
const processData = inject('processData') as Ref
|
||||
|
||||
/** 表单校验 */
|
||||
const validate = async () => {
|
||||
try {
|
||||
// 获取最新的流程数据
|
||||
if (!processData.value) {
|
||||
throw new Error('请设计流程')
|
||||
}
|
||||
return true
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
/** 处理设计器保存成功 */
|
||||
const handleDesignSuccess = async (data?: any) => {
|
||||
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()
|
||||
//更新表单的模型数据部分
|
||||
modelData.value = newModelData
|
||||
}
|
||||
}
|
||||
|
||||
/** 是否显示设计器 */
|
||||
const showDesigner = computed(() => {
|
||||
return Boolean(modelData.value?.key && modelData.value?.name)
|
||||
})
|
||||
defineExpose({
|
||||
validate
|
||||
})
|
||||
</script>
|
||||
124
src/views/bpm/model/form/editor/index.vue
Normal file
124
src/views/bpm/model/form/editor/index.vue
Normal file
@@ -0,0 +1,124 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 流程设计器,负责绘制流程等 -->
|
||||
<MyProcessDesigner
|
||||
key="designer"
|
||||
v-model="xmlString"
|
||||
:value="xmlString"
|
||||
v-bind="controlForm"
|
||||
keyboard
|
||||
ref="processDesigner"
|
||||
@init-finished="initModeler"
|
||||
:additionalModel="controlForm.additionalModel"
|
||||
:model="model"
|
||||
@save="save"
|
||||
:process-id="modelKey"
|
||||
:process-name="modelName"
|
||||
/>
|
||||
<!-- 流程属性器,负责编辑每个流程节点的属性 -->
|
||||
<MyProcessPenal
|
||||
v-if="modeler"
|
||||
key="penal"
|
||||
:bpmnModeler="modeler"
|
||||
:prefix="controlForm.prefix"
|
||||
class="process-panel"
|
||||
:model="model"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { MyProcessDesigner, MyProcessPenal } from '@/components/bpmnProcessDesigner/package'
|
||||
// 自定义元素选中时的弹出菜单(修改 默认任务 为 用户任务)
|
||||
import CustomContentPadProvider from '@/components/bpmnProcessDesigner/package/designer/plugins/content-pad'
|
||||
// 自定义左侧菜单(修改 默认任务 为 用户任务)
|
||||
import CustomPaletteProvider from '@/components/bpmnProcessDesigner/package/designer/plugins/palette'
|
||||
import * as ModelApi from '@/api/bpm/model'
|
||||
import { BpmModelFormType } from '@/utils/constants'
|
||||
import * as FormApi from '@/api/bpm/form'
|
||||
|
||||
defineOptions({ name: 'BpmModelEditor' })
|
||||
|
||||
defineProps<{
|
||||
modelId?: string
|
||||
modelKey: string
|
||||
modelName: string
|
||||
value?: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['success', 'init-finished'])
|
||||
const message = useMessage() // 国际化
|
||||
|
||||
// 表单信息
|
||||
const formFields = ref<string[]>([])
|
||||
// 表单类型,暂仅限流程表单
|
||||
const formType = ref(BpmModelFormType.NORMAL)
|
||||
provide('formFields', formFields)
|
||||
provide('formType', formType)
|
||||
|
||||
// 注入流程数据
|
||||
const xmlString = inject('processData') as Ref
|
||||
// 注入模型数据
|
||||
const modelData = inject('modelData') as Ref
|
||||
|
||||
const modeler = shallowRef() // BPMN Modeler
|
||||
const processDesigner = ref()
|
||||
const controlForm = ref({
|
||||
simulation: true,
|
||||
labelEditing: false,
|
||||
labelVisible: false,
|
||||
prefix: 'flowable',
|
||||
headerButtonSize: 'mini',
|
||||
additionalModel: [CustomContentPadProvider, CustomPaletteProvider]
|
||||
})
|
||||
const model = ref<ModelApi.ModelVO>() // 流程模型的信息
|
||||
|
||||
/** 初始化 modeler */
|
||||
const initModeler = async (item: any) => {
|
||||
// 先初始化模型数据
|
||||
model.value = modelData.value
|
||||
modeler.value = item
|
||||
}
|
||||
|
||||
/** 添加/修改模型 */
|
||||
const save = async (bpmnXml: string) => {
|
||||
try {
|
||||
xmlString.value = bpmnXml
|
||||
emit('success', bpmnXml)
|
||||
} catch (error) {
|
||||
console.error('保存失败:', error)
|
||||
message.error('保存失败')
|
||||
}
|
||||
}
|
||||
|
||||
/** 监听表单 ID 变化,加载表单数据 */
|
||||
watch(
|
||||
() => modelData.value.formId,
|
||||
async (newFormId) => {
|
||||
if (newFormId && modelData.value.formType === BpmModelFormType.NORMAL) {
|
||||
const data = await FormApi.getForm(newFormId)
|
||||
formFields.value = data.fields
|
||||
} else {
|
||||
formFields.value = []
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
// 在组件卸载时清理
|
||||
onBeforeUnmount(() => {
|
||||
modeler.value = null
|
||||
// 清理全局实例
|
||||
const w = window as any
|
||||
if (w.bpmnInstances) {
|
||||
w.bpmnInstances = null
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.process-panel__container {
|
||||
position: absolute;
|
||||
top: 172px;
|
||||
right: 70px;
|
||||
}
|
||||
</style>
|
||||
442
src/views/bpm/model/form/index.vue
Normal file
442
src/views/bpm/model/form/index.vue
Normal file
@@ -0,0 +1,442 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<div class="mx-auto">
|
||||
<!-- 头部导航栏 -->
|
||||
<div
|
||||
class="absolute top-0 left-0 right-0 h-50px bg-white border-bottom z-10 flex items-center px-20px"
|
||||
>
|
||||
<!-- 左侧标题 -->
|
||||
<div class="w-200px flex items-center overflow-hidden">
|
||||
<Icon icon="ep:arrow-left" class="cursor-pointer flex-shrink-0" @click="handleBack" />
|
||||
<span class="ml-10px text-16px truncate" :title="formData.name || '创建流程'">
|
||||
{{ formData.name || '创建流程' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 步骤条 -->
|
||||
<div class="flex-1 flex items-center justify-center h-full">
|
||||
<div class="w-400px flex items-center justify-between h-full">
|
||||
<div
|
||||
v-for="(step, index) in steps"
|
||||
:key="index"
|
||||
class="flex items-center cursor-pointer mx-15px relative h-full"
|
||||
:class="[
|
||||
currentStep === index
|
||||
? 'text-[#3473ff] border-[#3473ff] border-b-2 border-b-solid'
|
||||
: 'text-gray-500'
|
||||
]"
|
||||
@click="handleStepClick(index)"
|
||||
>
|
||||
<div
|
||||
class="w-28px h-28px rounded-full flex items-center justify-center mr-8px border-2 border-solid text-15px"
|
||||
:class="[
|
||||
currentStep === index
|
||||
? 'bg-[#3473ff] text-white border-[#3473ff]'
|
||||
: 'border-gray-300 bg-white text-gray-500'
|
||||
]"
|
||||
>
|
||||
{{ index + 1 }}
|
||||
</div>
|
||||
<span class="text-16px font-bold whitespace-nowrap">{{ step.title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧按钮 -->
|
||||
<div class="w-200px flex items-center justify-end gap-2">
|
||||
<el-button v-if="actionType === 'update'" type="success" @click="handleDeploy">
|
||||
发 布
|
||||
</el-button>
|
||||
<el-button type="primary" @click="handleSave">
|
||||
<span v-if="actionType === 'definition'">恢 复</span>
|
||||
<span v-else>保 存</span>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 主体内容 -->
|
||||
<div class="mt-50px">
|
||||
<!-- 第一步:基本信息 -->
|
||||
<div v-if="currentStep === 0" class="mx-auto w-560px">
|
||||
<BasicInfo
|
||||
v-model="formData"
|
||||
:categoryList="categoryList"
|
||||
:userList="userList"
|
||||
:deptList="deptList"
|
||||
ref="basicInfoRef"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 第二步:表单设计 -->
|
||||
<div v-if="currentStep === 1" class="mx-auto w-560px">
|
||||
<FormDesign v-model="formData" :formList="formList" ref="formDesignRef" />
|
||||
</div>
|
||||
|
||||
<!-- 第三步:流程设计 -->
|
||||
<ProcessDesign v-if="currentStep === 2" v-model="formData" ref="processDesignRef" />
|
||||
|
||||
<!-- 第四步:更多设置 -->
|
||||
<div v-show="currentStep === 3" class="mx-auto w-700px">
|
||||
<ExtraSettings v-model="formData" ref="extraSettingsRef" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useMessage } from '@/hooks/web/useMessage'
|
||||
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||
import { useUserStoreWithOut } from '@/store/modules/user'
|
||||
import * as ModelApi from '@/api/bpm/model'
|
||||
import * as FormApi from '@/api/bpm/form'
|
||||
import { CategoryApi, CategoryVO } from '@/api/bpm/category'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import * as DeptApi from '@/api/system/dept'
|
||||
import * as DefinitionApi from '@/api/bpm/definition'
|
||||
import { BpmModelFormType, BpmModelType, BpmAutoApproveType } from '@/utils/constants'
|
||||
import BasicInfo from './BasicInfo.vue'
|
||||
import FormDesign from './FormDesign.vue'
|
||||
import ProcessDesign from './ProcessDesign.vue'
|
||||
import ExtraSettings from './ExtraSettings.vue'
|
||||
import { useTagsView } from '@/hooks/web/useTagsView'
|
||||
|
||||
const router = useRouter()
|
||||
const { delView } = useTagsViewStore() // 视图操作
|
||||
const tagsView = useTagsView()
|
||||
const route = useRoute()
|
||||
const message = useMessage()
|
||||
const userStore = useUserStoreWithOut()
|
||||
|
||||
// 组件引用
|
||||
const basicInfoRef = ref()
|
||||
const formDesignRef = ref()
|
||||
const processDesignRef = ref()
|
||||
const extraSettingsRef = ref()
|
||||
|
||||
/** 步骤校验函数 */
|
||||
const validateBasic = async () => {
|
||||
await basicInfoRef.value?.validate()
|
||||
}
|
||||
|
||||
/** 表单设计校验 */
|
||||
const validateForm = async () => {
|
||||
await formDesignRef.value?.validate()
|
||||
}
|
||||
|
||||
/** 流程设计校验 */
|
||||
const validateProcess = async () => {
|
||||
await processDesignRef.value?.validate()
|
||||
}
|
||||
|
||||
const currentStep = ref(-1) // 步骤控制。-1 用于,一开始全部不展示等当前页面数据初始化完成
|
||||
|
||||
const steps = [
|
||||
{ title: '基本信息', validator: validateBasic },
|
||||
{ title: '表单设计', validator: validateForm },
|
||||
{ title: '流程设计', validator: validateProcess },
|
||||
{ title: '更多设置', validator: null }
|
||||
]
|
||||
|
||||
// 表单数据
|
||||
const formData: any = ref({
|
||||
id: undefined,
|
||||
name: '',
|
||||
key: '',
|
||||
category: undefined,
|
||||
icon: undefined,
|
||||
description: '',
|
||||
type: BpmModelType.BPMN,
|
||||
formType: BpmModelFormType.NORMAL,
|
||||
formId: '',
|
||||
formCustomCreatePath: '',
|
||||
formCustomViewPath: '',
|
||||
visible: true,
|
||||
startUserType: undefined,
|
||||
startUserIds: [],
|
||||
startDeptIds: [],
|
||||
managerUserIds: [],
|
||||
allowCancelRunningProcess: true,
|
||||
processIdRule: {
|
||||
enable: false,
|
||||
prefix: '',
|
||||
infix: '',
|
||||
postfix: '',
|
||||
length: 5
|
||||
},
|
||||
autoApprovalType: BpmAutoApproveType.NONE,
|
||||
titleSetting: {
|
||||
enable: false,
|
||||
title: ''
|
||||
},
|
||||
summarySetting: {
|
||||
enable: false,
|
||||
summary: []
|
||||
}
|
||||
})
|
||||
|
||||
// 流程数据
|
||||
const processData = ref<any>()
|
||||
|
||||
provide('processData', processData)
|
||||
provide('modelData', formData)
|
||||
|
||||
// 数据列表
|
||||
const formList = ref([])
|
||||
const categoryList = ref<CategoryVO[]>([])
|
||||
const userList = ref<UserApi.UserVO[]>([])
|
||||
const deptList = ref<DeptApi.DeptVO[]>([])
|
||||
|
||||
/** 初始化数据 */
|
||||
const actionType = route.params.type as string
|
||||
const initData = async () => {
|
||||
if (actionType === 'definition') {
|
||||
// 情况一:流程定义场景(恢复)
|
||||
const definitionId = route.params.id as string
|
||||
const data = await DefinitionApi.getProcessDefinition(definitionId)
|
||||
// 将 definition => model,最终赋值
|
||||
data.type = data.modelType
|
||||
delete data.modelType
|
||||
data.id = data.modelId
|
||||
delete data.modelId
|
||||
if (data.simpleModel) {
|
||||
data.simpleModel = JSON.parse(data.simpleModel)
|
||||
}
|
||||
formData.value = data
|
||||
formData.value.startUserType =
|
||||
formData.value.startUserIds?.length > 0 ? 1 : formData.value?.startDeptIds?.length > 0 ? 2 : 0
|
||||
} else if (['update', 'copy'].includes(actionType)) {
|
||||
// 情况二:修改场景/复制场景
|
||||
const modelId = route.params.id as string
|
||||
formData.value = await ModelApi.getModel(modelId)
|
||||
formData.value.startUserType =
|
||||
formData.value.startUserIds?.length > 0 ? 1 : formData.value?.startDeptIds?.length > 0 ? 2 : 0
|
||||
|
||||
// 特殊:复制场景
|
||||
if (route.params.type === 'copy') {
|
||||
delete formData.value.id
|
||||
formData.value.name += '副本'
|
||||
formData.value.key += '_copy'
|
||||
tagsView.setTitle('复制流程')
|
||||
}
|
||||
} else {
|
||||
// 情况三:新增场景
|
||||
formData.value.startUserType = 0 // 全体
|
||||
formData.value.managerUserIds.push(userStore.getUser.id)
|
||||
}
|
||||
|
||||
// 获取表单列表
|
||||
formList.value = await FormApi.getFormSimpleList()
|
||||
// 获取分类列表
|
||||
categoryList.value = await CategoryApi.getCategorySimpleList()
|
||||
// 获取用户列表
|
||||
userList.value = await UserApi.getSimpleUserList()
|
||||
// 获取部门列表
|
||||
deptList.value = await DeptApi.getSimpleDeptList()
|
||||
|
||||
// 最终,设置 currentStep 切换到第一步
|
||||
currentStep.value = 0
|
||||
|
||||
// 兼容,以前未配置更多设置的流程
|
||||
extraSettingsRef.value.initData()
|
||||
}
|
||||
|
||||
/** 根据类型切换流程数据 */
|
||||
watch(
|
||||
async () => formData.value.type,
|
||||
() => {
|
||||
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 () => {
|
||||
try {
|
||||
// 基本信息校验
|
||||
try {
|
||||
await validateBasic()
|
||||
} catch (error) {
|
||||
currentStep.value = 0
|
||||
throw new Error('请完善基本信息')
|
||||
}
|
||||
|
||||
// 表单设计校验
|
||||
try {
|
||||
await validateForm()
|
||||
} catch (error) {
|
||||
currentStep.value = 1
|
||||
throw new Error('请完善自定义表单信息')
|
||||
}
|
||||
|
||||
// 流程设计校验
|
||||
|
||||
// 表单设计校验
|
||||
try {
|
||||
await validateProcess()
|
||||
} catch (error) {
|
||||
currentStep.value = 2
|
||||
throw new Error('请设计流程')
|
||||
}
|
||||
|
||||
return true
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/** 保存操作 */
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
// 保存前校验所有步骤的数据
|
||||
await validateAllSteps()
|
||||
|
||||
// 更新表单数据
|
||||
const modelData = {
|
||||
...formData.value
|
||||
}
|
||||
|
||||
if (actionType === 'definition') {
|
||||
// 情况一:流程定义场景(恢复)
|
||||
await ModelApi.updateModel(modelData)
|
||||
// 提示成功
|
||||
message.success('恢复成功,可点击【发布】按钮,进行发布模型')
|
||||
} else if (actionType === 'update') {
|
||||
// 修改场景
|
||||
await ModelApi.updateModel(modelData)
|
||||
// 提示成功
|
||||
message.success('修改成功,可点击【发布】按钮,进行发布模型')
|
||||
} else if (actionType === 'copy') {
|
||||
// 情况三:复制场景
|
||||
formData.value.id = await ModelApi.createModel(modelData)
|
||||
// 提示成功
|
||||
message.success('复制成功,可点击【发布】按钮,进行发布模型')
|
||||
} else {
|
||||
// 情况四:新增场景
|
||||
formData.value.id = await ModelApi.createModel(modelData)
|
||||
// 提示成功
|
||||
message.success('新建成功,可点击【发布】按钮,进行发布模型')
|
||||
}
|
||||
|
||||
// 返回列表页(排除更新的情况)
|
||||
if (actionType !== 'update') {
|
||||
await router.push({ name: 'BpmModel' })
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('保存失败:', error)
|
||||
message.warning(error.message || '请完善所有步骤的必填信息')
|
||||
}
|
||||
}
|
||||
|
||||
/** 发布操作 */
|
||||
const handleDeploy = async () => {
|
||||
try {
|
||||
// 修改场景下直接发布,新增场景下需要先确认
|
||||
if (!formData.value.id) {
|
||||
await message.confirm('是否确认发布该流程?')
|
||||
}
|
||||
// 校验所有步骤
|
||||
await validateAllSteps()
|
||||
|
||||
// 更新表单数据
|
||||
const modelData = {
|
||||
...formData.value
|
||||
}
|
||||
|
||||
// 先保存所有数据
|
||||
if (formData.value.id) {
|
||||
await ModelApi.updateModel(modelData)
|
||||
} else {
|
||||
const result = await ModelApi.createModel(modelData)
|
||||
formData.value.id = result.id
|
||||
}
|
||||
|
||||
// 发布
|
||||
await ModelApi.deployModel(formData.value.id)
|
||||
message.success('发布成功')
|
||||
// 返回列表页
|
||||
await router.push({ name: 'BpmModel' })
|
||||
} catch (error: any) {
|
||||
console.error('发布失败:', error)
|
||||
message.warning(error.message || '发布失败')
|
||||
}
|
||||
}
|
||||
|
||||
/** 步骤切换处理 */
|
||||
const handleStepClick = async (index: number) => {
|
||||
try {
|
||||
if (index !== 0) {
|
||||
await validateBasic()
|
||||
}
|
||||
if (index !== 1) {
|
||||
await validateForm()
|
||||
}
|
||||
if (index !== 2) {
|
||||
await validateProcess()
|
||||
}
|
||||
|
||||
// 切换步骤
|
||||
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) {
|
||||
console.error('步骤切换失败:', error)
|
||||
message.warning('请先完善当前步骤必填信息')
|
||||
}
|
||||
}
|
||||
|
||||
/** 返回列表页 */
|
||||
const handleBack = () => {
|
||||
// 先删除当前页签
|
||||
delView(unref(router.currentRoute))
|
||||
// 跳转到列表页
|
||||
router.push({ name: 'BpmModel' })
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
await initData()
|
||||
})
|
||||
|
||||
// 添加组件卸载前的清理代码
|
||||
onBeforeUnmount(() => {
|
||||
// 清理所有的引用
|
||||
basicInfoRef.value = null
|
||||
formDesignRef.value = null
|
||||
processDesignRef.value = null
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.border-bottom {
|
||||
border-bottom: 1px solid #dcdfe6;
|
||||
}
|
||||
|
||||
.text-primary {
|
||||
color: #3473ff;
|
||||
}
|
||||
|
||||
.bg-primary {
|
||||
background-color: #3473ff;
|
||||
}
|
||||
|
||||
.border-primary {
|
||||
border-color: #3473ff;
|
||||
}
|
||||
</style>
|
||||
225
src/views/bpm/model/index.vue
Normal file
225
src/views/bpm/model/index.vue
Normal file
@@ -0,0 +1,225 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<div class="flex justify-between pl-20px items-center">
|
||||
<h3 class="font-extrabold">流程模型</h3>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
v-if="!isCategorySorting"
|
||||
class="-mb-15px flex mr-10px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
@submit.prevent
|
||||
>
|
||||
<el-form-item prop="name" class="ml-auto">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="搜索流程"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
>
|
||||
<template #prefix>
|
||||
<Icon icon="ep:search" class="mx-10px" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<!-- 右上角:新建模型、更多操作 -->
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="openForm('create')" v-hasPermi="['bpm:model:create']">
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新建模型
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-dropdown @command="(command) => handleCommand(command)" placement="bottom-end">
|
||||
<el-button class="w-30px" plain>
|
||||
<Icon icon="ep:setting" />
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="handleCategoryAdd">
|
||||
<Icon icon="ep:circle-plus" :size="13" class="mr-5px" />
|
||||
新建分类
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="handleCategorySort">
|
||||
<Icon icon="fa:sort-amount-desc" :size="13" class="mr-5px" />
|
||||
分类排序
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="mr-20px" v-else>
|
||||
<el-button @click="handleCategorySortCancel"> 取 消 </el-button>
|
||||
<el-button type="primary" @click="handleCategorySortSubmit"> 保存排序 </el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-divider />
|
||||
|
||||
<!-- 按照分类,展示其所属的模型列表 -->
|
||||
<div class="px-15px">
|
||||
<draggable
|
||||
:disabled="!isCategorySorting"
|
||||
v-model="categoryGroup"
|
||||
item-key="id"
|
||||
:animation="400"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<ContentWrap
|
||||
class="rounded-lg transition-all duration-300 ease-in-out hover:shadow-xl"
|
||||
v-loading="loading"
|
||||
:body-style="{ padding: 0 }"
|
||||
:key="element.id"
|
||||
>
|
||||
<CategoryDraggableModel
|
||||
:isCategorySorting="isCategorySorting"
|
||||
:categoryInfo="element"
|
||||
@success="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
</draggable>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加分类 -->
|
||||
<CategoryForm ref="categoryFormRef" @success="getList" />
|
||||
<!-- 弹窗:表单详情 -->
|
||||
<Dialog title="表单详情" v-model="formDetailVisible" width="800">
|
||||
<form-create :rule="formDetailPreview.rule" :option="formDetailPreview.option" />
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import draggable from 'vuedraggable'
|
||||
import { CategoryApi } from '@/api/bpm/category'
|
||||
import * as ModelApi from '@/api/bpm/model'
|
||||
import CategoryForm from '../category/CategoryForm.vue'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import CategoryDraggableModel from './CategoryDraggableModel.vue'
|
||||
|
||||
defineOptions({ name: 'BpmModel' })
|
||||
|
||||
const { push } = useRouter()
|
||||
const message = useMessage() // 消息弹窗
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const isCategorySorting = ref(false) // 是否 category 正处于排序状态
|
||||
const queryParams = reactive({
|
||||
name: undefined
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const categoryGroup: any = ref([]) // 按照 category 分组的数据
|
||||
const originalData: any = ref([]) // 原始数据
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const openForm = (type: string, id?: number) => {
|
||||
if (type === 'create') {
|
||||
push({ name: 'BpmModelCreate' })
|
||||
} else {
|
||||
push({
|
||||
name: 'BpmModelUpdate',
|
||||
params: { id }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/** 流程表单的详情按钮操作 */
|
||||
const formDetailVisible = ref(false)
|
||||
const formDetailPreview = ref({
|
||||
rule: [],
|
||||
option: {}
|
||||
})
|
||||
|
||||
/** 右上角设置按钮 */
|
||||
const handleCommand = (command: string) => {
|
||||
switch (command) {
|
||||
case 'handleCategoryAdd':
|
||||
handleCategoryAdd()
|
||||
break
|
||||
case 'handleCategorySort':
|
||||
handleCategorySort()
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/** 新建分类 */
|
||||
const categoryFormRef = ref()
|
||||
const handleCategoryAdd = () => {
|
||||
categoryFormRef.value.open('create')
|
||||
}
|
||||
|
||||
/** 分类排序的提交 */
|
||||
const handleCategorySort = () => {
|
||||
// 保存初始数据
|
||||
originalData.value = cloneDeep(categoryGroup.value)
|
||||
isCategorySorting.value = true
|
||||
}
|
||||
|
||||
/** 分类排序的取消 */
|
||||
const handleCategorySortCancel = () => {
|
||||
// 恢复初始数据
|
||||
categoryGroup.value = cloneDeep(originalData.value)
|
||||
isCategorySorting.value = false
|
||||
}
|
||||
|
||||
/** 分类排序的保存 */
|
||||
const handleCategorySortSubmit = async () => {
|
||||
// 保存排序
|
||||
const ids = categoryGroup.value.map((item: any) => item.id)
|
||||
await CategoryApi.updateCategorySortBatch(ids)
|
||||
// 刷新列表
|
||||
isCategorySorting.value = false
|
||||
message.success('排序分类成功')
|
||||
await getList()
|
||||
}
|
||||
|
||||
/** 加载数据 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// 查询模型 + 分裂的列表
|
||||
const modelList = await ModelApi.getModelList(queryParams.name)
|
||||
const categoryList = await CategoryApi.getCategorySimpleList()
|
||||
// 按照 category 聚合
|
||||
// 注意:必须一次性赋值给 categoryGroup,否则每次操作后,列表会重新渲染,滚动条的位置会偏离!!!
|
||||
categoryGroup.value = categoryList.map((category: any) => ({
|
||||
...category,
|
||||
modelList: modelList.filter((model: any) => model.categoryName == category.name)
|
||||
}))
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onActivated(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep() {
|
||||
.el-table--fit .el-table__inner-wrapper:before {
|
||||
height: 0;
|
||||
}
|
||||
.el-card {
|
||||
border-radius: 8px;
|
||||
}
|
||||
.el-form--inline .el-form-item {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.el-divider--horizontal {
|
||||
margin-top: 6px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user