初始化代码
This commit is contained in:
357
src/views/bpm/processInstance/create/ProcessDefinitionDetail.vue
Normal file
357
src/views/bpm/processInstance/create/ProcessDefinitionDetail.vue
Normal file
@@ -0,0 +1,357 @@
|
||||
<template>
|
||||
<ContentWrap :bodyStyle="{ padding: '10px 20px 0' }">
|
||||
<div class="processInstance-wrap-main">
|
||||
<el-scrollbar>
|
||||
<div class="text-#878c93 h-15px">流程:{{ selectProcessDefinition.name }}</div>
|
||||
<el-divider class="!my-8px" />
|
||||
|
||||
<!-- 中间主要内容 tab 栏 -->
|
||||
<el-tabs v-model="activeTab">
|
||||
<!-- 表单信息 -->
|
||||
<el-tab-pane label="表单填写" name="form">
|
||||
<div class="form-scroll-area" v-loading="processInstanceStartLoading">
|
||||
<el-scrollbar>
|
||||
<el-row>
|
||||
<el-col :span="17">
|
||||
<form-create
|
||||
:rule="detailForm.rule"
|
||||
v-model:api="fApi"
|
||||
v-model="detailForm.value"
|
||||
:option="detailForm.option"
|
||||
@submit="submitForm"
|
||||
/>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="6" :offset="1">
|
||||
<!-- 流程时间线 -->
|
||||
<ProcessInstanceTimeline
|
||||
ref="timelineRef"
|
||||
:activity-nodes="activityNodes"
|
||||
:show-status-icon="false"
|
||||
@select-user-confirm="selectUserConfirm"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<!-- 流程图 -->
|
||||
<el-tab-pane label="流程图" name="diagram">
|
||||
<div class="form-scroll-area">
|
||||
<!-- BPMN 流程图预览 -->
|
||||
<ProcessInstanceBpmnViewer
|
||||
:bpmn-xml="bpmnXML"
|
||||
v-if="BpmModelType.BPMN === selectProcessDefinition.modelType"
|
||||
/>
|
||||
|
||||
<!-- Simple 流程图预览 -->
|
||||
<ProcessInstanceSimpleViewer
|
||||
:simple-json="simpleJson"
|
||||
v-if="BpmModelType.SIMPLE === selectProcessDefinition.modelType"
|
||||
/>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<!-- 底部操作栏 -->
|
||||
<div class="b-t-solid border-t-1px border-[var(--el-border-color)]">
|
||||
<!-- 操作栏按钮 -->
|
||||
<div
|
||||
v-if="activeTab === 'form'"
|
||||
class="h-50px bottom-10 text-14px flex items-center color-#32373c dark:color-#fff font-bold btn-container"
|
||||
>
|
||||
<el-button plain type="success" @click="submitForm">
|
||||
<Icon icon="ep:select" /> 发起
|
||||
</el-button>
|
||||
<el-button plain type="danger" @click="handleCancel">
|
||||
<Icon icon="ep:close" /> 取消
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { decodeFields, setConfAndFields2 } from '@/utils/formCreate'
|
||||
import { BpmModelType, BpmModelFormType } from '@/utils/constants'
|
||||
import {
|
||||
CandidateStrategy,
|
||||
NodeId,
|
||||
FieldPermissionType
|
||||
} from '@/components/SimpleProcessDesignerV2/src/consts'
|
||||
import ProcessInstanceBpmnViewer from '../detail/ProcessInstanceBpmnViewer.vue'
|
||||
import ProcessInstanceSimpleViewer from '../detail/ProcessInstanceSimpleViewer.vue'
|
||||
import ProcessInstanceTimeline from '../detail/ProcessInstanceTimeline.vue'
|
||||
import type { ApiAttrs } from '@form-create/element-ui/types/config'
|
||||
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
|
||||
import * as DefinitionApi from '@/api/bpm/definition'
|
||||
import { ApprovalNodeInfo } from '@/api/bpm/processInstance'
|
||||
import formCreate from '@form-create/element-ui'
|
||||
|
||||
defineOptions({ name: 'ProcessDefinitionDetail' })
|
||||
const props = defineProps<{
|
||||
selectProcessDefinition: any
|
||||
}>()
|
||||
const emit = defineEmits(['cancel'])
|
||||
const processInstanceStartLoading = ref(false) // 流程实例发起中
|
||||
const { push, currentRoute } = useRouter() // 路由
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { delView } = useTagsViewStore() // 视图操作
|
||||
|
||||
const detailForm: any = ref({
|
||||
rule: [],
|
||||
option: {},
|
||||
value: {}
|
||||
}) // 流程表单详情
|
||||
const fApi = ref<ApiAttrs>()
|
||||
// 指定审批人
|
||||
const startUserSelectTasks: any = ref([]) // 发起人需要选择审批人或抄送人的任务列表
|
||||
const startUserSelectAssignees = ref({}) // 发起人选择审批人的数据
|
||||
const tempStartUserSelectAssignees = ref({}) // 历史发起人选择审批人的数据,用于每次表单变更时,临时保存
|
||||
const bpmnXML: any = ref(null) // BPMN 数据
|
||||
const simpleJson = ref<string | undefined>() // Simple 设计器数据 json 格式
|
||||
|
||||
const activeTab = ref('form') // 当前的 Tab
|
||||
const activityNodes = ref<ProcessInstanceApi.ApprovalNodeInfo[]>([]) // 审批节点信息
|
||||
|
||||
/** 设置表单信息、获取流程图数据 **/
|
||||
const initProcessInfo = async (row: any, formVariables?: any) => {
|
||||
// 重置指定审批人
|
||||
startUserSelectTasks.value = []
|
||||
startUserSelectAssignees.value = {}
|
||||
|
||||
// 情况一:流程表单
|
||||
if (row.formType == BpmModelFormType.NORMAL) {
|
||||
// 设置表单
|
||||
// 注意:需要从 formVariables 中,移除不在 row.formFields 的值。
|
||||
// 原因是:后端返回的 formVariables 里面,会有一些非表单的信息。例如说,某个流程节点的审批人。
|
||||
// 这样,就可能导致一个流程被审批不通过后,重新发起时,会直接后端报错!!!
|
||||
const formApi = formCreate.create(decodeFields(row.formFields))
|
||||
const allowedFields = formApi.fields()
|
||||
for (const key in formVariables) {
|
||||
if (!allowedFields.includes(key)) {
|
||||
delete formVariables[key]
|
||||
}
|
||||
}
|
||||
setConfAndFields2(detailForm, row.formConf, row.formFields, formVariables)
|
||||
|
||||
await nextTick()
|
||||
fApi.value?.btn.show(false) // 隐藏提交按钮
|
||||
|
||||
// 获取流程审批信息,当再次发起时,流程审批节点要根据原始表单参数预测出来
|
||||
await getApprovalDetail({
|
||||
id: row.id,
|
||||
processVariablesStr: JSON.stringify(formVariables)
|
||||
})
|
||||
|
||||
// 加载流程图
|
||||
const processDefinitionDetail = await DefinitionApi.getProcessDefinition(row.id)
|
||||
if (processDefinitionDetail) {
|
||||
bpmnXML.value = processDefinitionDetail.bpmnXml
|
||||
simpleJson.value = processDefinitionDetail.simpleModel
|
||||
}
|
||||
// 情况二:业务表单
|
||||
} else if (row.formCustomCreatePath) {
|
||||
await push({
|
||||
path: row.formCustomCreatePath
|
||||
})
|
||||
// 这里暂时无需加载流程图,因为跳出到另外个 Tab;
|
||||
}
|
||||
}
|
||||
|
||||
/** 预测流程节点会因为输入的参数值而产生新的预测结果值,所以需重新预测一次 */
|
||||
watch(
|
||||
detailForm.value,
|
||||
(newValue) => {
|
||||
if (newValue && Object.keys(newValue.value).length > 0) {
|
||||
// 记录之前的节点审批人
|
||||
tempStartUserSelectAssignees.value = startUserSelectAssignees.value
|
||||
startUserSelectAssignees.value = {}
|
||||
// 加载最新的审批详情
|
||||
getApprovalDetail({
|
||||
id: props.selectProcessDefinition.id,
|
||||
processVariablesStr: JSON.stringify(newValue.value) // 解决 GET 无法传递对象的问题,后端 String 再转 JSON
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
/** 获取审批详情 */
|
||||
const getApprovalDetail = async (row: any) => {
|
||||
try {
|
||||
// TODO 获取审批详情,设置 activityId 为发起人节点(为了获取字段权限。暂时只对 Simple 设计器有效);@jason:这里可以去掉 activityId 么?
|
||||
const data = await ProcessInstanceApi.getApprovalDetail({
|
||||
processDefinitionId: row.id,
|
||||
activityId: NodeId.START_USER_NODE_ID,
|
||||
processVariablesStr: row.processVariablesStr // 解决 GET 无法传递对象的问题,后端 String 再转 JSON
|
||||
})
|
||||
|
||||
if (!data) {
|
||||
message.error('查询不到审批详情信息!')
|
||||
return
|
||||
}
|
||||
// 获取审批节点,显示 Timeline 的数据
|
||||
activityNodes.value = data.activityNodes
|
||||
|
||||
// 获取发起人自选的任务
|
||||
startUserSelectTasks.value = data.activityNodes?.filter(
|
||||
(node: ApprovalNodeInfo) => CandidateStrategy.START_USER_SELECT === node.candidateStrategy
|
||||
)
|
||||
// 恢复之前的选择审批人
|
||||
if (startUserSelectTasks.value?.length > 0) {
|
||||
for (const node of startUserSelectTasks.value) {
|
||||
if (
|
||||
tempStartUserSelectAssignees.value[node.id] &&
|
||||
tempStartUserSelectAssignees.value[node.id].length > 0
|
||||
) {
|
||||
startUserSelectAssignees.value[node.id] = tempStartUserSelectAssignees.value[node.id]
|
||||
} else {
|
||||
startUserSelectAssignees.value[node.id] = []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取表单字段权限
|
||||
const formFieldsPermission = data.formFieldsPermission
|
||||
// 设置表单字段权限
|
||||
if (formFieldsPermission) {
|
||||
Object.keys(formFieldsPermission).forEach((item) => {
|
||||
setFieldPermission(item, formFieldsPermission[item])
|
||||
})
|
||||
}
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置表单权限
|
||||
*/
|
||||
const setFieldPermission = (field: string, permission: string) => {
|
||||
if (permission === FieldPermissionType.READ) {
|
||||
// 1. 设置字段为只读
|
||||
//@ts-ignore
|
||||
fApi.value?.disabled(true, field)
|
||||
// 2. 只读字段, 去掉验证规则
|
||||
// fApi.value?.updateValidate(field, []); 这个方法貌似不起作用,
|
||||
try {
|
||||
//@ts-ignore
|
||||
const rule = fApi.value?.getRule(field)
|
||||
if (rule) {
|
||||
// 必填验证设置为false
|
||||
rule.$required = false
|
||||
// 清空所有验证规则
|
||||
if (rule.validate) {
|
||||
rule.validate = []
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('修改字段验证规则失败:', error)
|
||||
}
|
||||
}
|
||||
if (permission === FieldPermissionType.WRITE) {
|
||||
//@ts-ignore
|
||||
fApi.value?.disabled(false, field)
|
||||
}
|
||||
if (permission === FieldPermissionType.NONE) {
|
||||
//@ts-ignore
|
||||
fApi.value?.hidden(true, field)
|
||||
}
|
||||
}
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = async () => {
|
||||
if (!fApi.value || !props.selectProcessDefinition) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// 流程表单校验
|
||||
await fApi.value.validate()
|
||||
} catch (error) {
|
||||
// 如果验证失败,检查是否是只读字段的验证错误
|
||||
console.warn('表单验证失败:', error)
|
||||
return
|
||||
}
|
||||
// 如果有指定审批人,需要校验
|
||||
if (startUserSelectTasks.value?.length > 0) {
|
||||
for (const userTask of startUserSelectTasks.value) {
|
||||
if (
|
||||
Array.isArray(startUserSelectAssignees.value[userTask.id]) &&
|
||||
startUserSelectAssignees.value[userTask.id].length === 0
|
||||
)
|
||||
return message.warning(`请选择${userTask.name}的候选人`)
|
||||
}
|
||||
}
|
||||
|
||||
// 提交请求
|
||||
processInstanceStartLoading.value = true
|
||||
try {
|
||||
await ProcessInstanceApi.createProcessInstance({
|
||||
processDefinitionId: props.selectProcessDefinition.id,
|
||||
variables: detailForm.value.value,
|
||||
startUserSelectAssignees: startUserSelectAssignees.value
|
||||
})
|
||||
// 提示
|
||||
message.success('发起流程成功')
|
||||
// 跳转回去
|
||||
delView(unref(currentRoute))
|
||||
await push({
|
||||
name: 'BpmProcessInstanceMy'
|
||||
})
|
||||
} finally {
|
||||
processInstanceStartLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 取消发起审批 */
|
||||
const handleCancel = () => {
|
||||
emit('cancel')
|
||||
}
|
||||
|
||||
/** 选择发起人 */
|
||||
const selectUserConfirm = (id: string, userList: any[]) => {
|
||||
startUserSelectAssignees.value[id] = userList?.map((item: any) => item.id)
|
||||
}
|
||||
|
||||
defineExpose({ initProcessInfo })
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$wrap-padding-height: 20px;
|
||||
$wrap-margin-height: 15px;
|
||||
$button-height: 51px;
|
||||
$process-header-height: 105px;
|
||||
|
||||
.processInstance-wrap-main {
|
||||
height: calc(
|
||||
100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px
|
||||
);
|
||||
max-height: calc(
|
||||
100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px
|
||||
);
|
||||
overflow: auto;
|
||||
|
||||
.form-scroll-area {
|
||||
height: calc(
|
||||
100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px -
|
||||
$process-header-height - 40px
|
||||
);
|
||||
max-height: calc(
|
||||
100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px -
|
||||
$process-header-height - 40px
|
||||
);
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.form-box {
|
||||
:deep(.el-card) {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
321
src/views/bpm/processInstance/create/index.vue
Normal file
321
src/views/bpm/processInstance/create/index.vue
Normal file
@@ -0,0 +1,321 @@
|
||||
<template>
|
||||
<!-- 第一步,通过流程定义的列表,选择对应的流程 -->
|
||||
<template v-if="!selectProcessDefinition">
|
||||
<el-input
|
||||
v-model="searchName"
|
||||
class="!w-50% mb-15px"
|
||||
placeholder="请输入流程名称"
|
||||
clearable
|
||||
@input="handleQuery"
|
||||
@clear="handleQuery"
|
||||
>
|
||||
<template #prefix>
|
||||
<Icon icon="ep:search" />
|
||||
</template>
|
||||
</el-input>
|
||||
<ContentWrap
|
||||
:class="{ 'process-definition-container': filteredProcessDefinitionList?.length }"
|
||||
class="position-relative pb-20px h-700px"
|
||||
v-loading="loading"
|
||||
>
|
||||
<el-row v-if="filteredProcessDefinitionList?.length" :gutter="20" class="!flex-nowrap">
|
||||
<el-col :span="5">
|
||||
<div class="flex flex-col">
|
||||
<div
|
||||
v-for="category in availableCategories"
|
||||
:key="category.code"
|
||||
class="flex items-center p-10px cursor-pointer text-14px rounded-md"
|
||||
:class="categoryActive.code === category.code ? 'text-#3e7bff bg-#e8eeff' : ''"
|
||||
@click="handleCategoryClick(category)"
|
||||
>
|
||||
{{ category.name }}
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="19">
|
||||
<el-scrollbar ref="scrollWrapper" height="700" @scroll="handleScroll">
|
||||
<div
|
||||
class="mb-20px pl-10px"
|
||||
v-for="(definitions, categoryCode) in processDefinitionGroup"
|
||||
:key="categoryCode"
|
||||
:ref="`category-${categoryCode}`"
|
||||
>
|
||||
<h3 class="text-18px font-bold mb-10px mt-5px">
|
||||
{{ getCategoryName(categoryCode as any) }}
|
||||
</h3>
|
||||
<div class="grid grid-cols-3 gap3">
|
||||
<el-tooltip
|
||||
v-for="definition in definitions"
|
||||
:key="definition.id"
|
||||
:content="definition.description"
|
||||
:disabled="!definition.description || definition.description.trim().length === 0"
|
||||
placement="top"
|
||||
>
|
||||
<el-card
|
||||
shadow="hover"
|
||||
class="cursor-pointer definition-item-card"
|
||||
@click="handleSelect(definition)"
|
||||
>
|
||||
<template #default>
|
||||
<div class="flex">
|
||||
<el-image
|
||||
v-if="definition.icon"
|
||||
:src="definition.icon"
|
||||
class="w-32px h-32px"
|
||||
/>
|
||||
<div v-else class="flow-icon">
|
||||
<span style="font-size: 12px; color: #fff">
|
||||
{{ subString(definition.name, 0, 2) }}
|
||||
</span>
|
||||
</div>
|
||||
<el-text class="!ml-10px" size="large">{{ definition.name }}</el-text>
|
||||
</div>
|
||||
</template>
|
||||
</el-card>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-empty class="!py-200px" :image-size="200" description="没有找到搜索结果" v-else />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<!-- 第二步,填写表单,进行流程的提交 -->
|
||||
<ProcessDefinitionDetail
|
||||
v-else
|
||||
ref="processDefinitionDetailRef"
|
||||
:selectProcessDefinition="selectProcessDefinition"
|
||||
@cancel="selectProcessDefinition = undefined"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import * as DefinitionApi from '@/api/bpm/definition'
|
||||
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
|
||||
import { CategoryApi, CategoryVO } from '@/api/bpm/category'
|
||||
import ProcessDefinitionDetail from './ProcessDefinitionDetail.vue'
|
||||
import { groupBy } from 'lodash-es'
|
||||
import { subString } from '@/utils/index'
|
||||
|
||||
defineOptions({ name: 'BpmProcessInstanceCreate' })
|
||||
|
||||
const { proxy } = getCurrentInstance() as any
|
||||
const route = useRoute() // 路由
|
||||
const message = useMessage() // 消息
|
||||
|
||||
const searchName = ref('') // 当前搜索关键字
|
||||
const processInstanceId: any = route.query.processInstanceId // 流程实例编号。场景:重新发起时
|
||||
const loading = ref(true) // 加载中
|
||||
const categoryList: any = ref([]) // 分类的列表
|
||||
const categoryActive: any = ref({}) // 选中的分类
|
||||
const processDefinitionList = ref([]) // 流程定义的列表
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// 所有流程分类数据
|
||||
await getCategoryList()
|
||||
// 所有流程定义数据
|
||||
await getProcessDefinitionList()
|
||||
|
||||
// 如果 processInstanceId 非空,说明是重新发起
|
||||
if (processInstanceId?.length > 0) {
|
||||
const processInstance = await ProcessInstanceApi.getProcessInstance(processInstanceId)
|
||||
if (!processInstance) {
|
||||
message.error('重新发起流程失败,原因:流程实例不存在')
|
||||
return
|
||||
}
|
||||
const processDefinition = processDefinitionList.value.find(
|
||||
(item: any) => item.key == processInstance.processDefinition?.key
|
||||
)
|
||||
if (!processDefinition) {
|
||||
message.error('重新发起流程失败,原因:流程定义不存在')
|
||||
return
|
||||
}
|
||||
await handleSelect(processDefinition, processInstance.formVariables)
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取所有流程分类数据 */
|
||||
const getCategoryList = async () => {
|
||||
try {
|
||||
// 流程分类
|
||||
categoryList.value = await CategoryApi.getCategorySimpleList()
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取所有流程定义数据 */
|
||||
const getProcessDefinitionList = async () => {
|
||||
try {
|
||||
// 流程定义
|
||||
processDefinitionList.value = await DefinitionApi.getProcessDefinitionList({
|
||||
suspensionState: 1
|
||||
})
|
||||
// 初始化过滤列表为全部流程定义
|
||||
filteredProcessDefinitionList.value = processDefinitionList.value
|
||||
|
||||
// 在获取完所有数据后,设置第一个有效分类为激活状态
|
||||
if (availableCategories.value.length > 0 && !categoryActive.value?.code) {
|
||||
categoryActive.value = availableCategories.value[0]
|
||||
}
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索流程 */
|
||||
const filteredProcessDefinitionList = ref([]) // 用于存储搜索过滤后的流程定义
|
||||
const handleQuery = () => {
|
||||
if (searchName.value.trim()) {
|
||||
// 如果有搜索关键字,进行过滤
|
||||
filteredProcessDefinitionList.value = processDefinitionList.value.filter(
|
||||
(definition: any) => definition.name.toLowerCase().includes(searchName.value.toLowerCase()) // 假设搜索依据是流程定义的名称
|
||||
)
|
||||
} else {
|
||||
// 如果没有搜索关键字,恢复所有数据
|
||||
filteredProcessDefinitionList.value = processDefinitionList.value
|
||||
}
|
||||
}
|
||||
|
||||
/** 流程定义的分组 */
|
||||
const processDefinitionGroup: any = computed(() => {
|
||||
if (!processDefinitionList.value?.length) {
|
||||
return {}
|
||||
}
|
||||
|
||||
const grouped = groupBy(filteredProcessDefinitionList.value, 'category')
|
||||
// 按照 categoryList 的顺序重新组织数据
|
||||
const orderedGroup = {}
|
||||
categoryList.value.forEach((category: any) => {
|
||||
if (grouped[category.code]) {
|
||||
orderedGroup[category.code] = grouped[category.code]
|
||||
}
|
||||
})
|
||||
return orderedGroup
|
||||
})
|
||||
|
||||
/** 左侧分类切换 */
|
||||
const handleCategoryClick = (category: any) => {
|
||||
categoryActive.value = category
|
||||
const categoryRef = proxy.$refs[`category-${category.code}`] // 获取点击分类对应的 DOM 元素
|
||||
if (categoryRef?.length) {
|
||||
const scrollWrapper = proxy.$refs.scrollWrapper // 获取右侧滚动容器
|
||||
const categoryOffsetTop = categoryRef[0].offsetTop
|
||||
|
||||
// 滚动到对应位置
|
||||
scrollWrapper.scrollTo({ top: categoryOffsetTop, behavior: 'smooth' })
|
||||
}
|
||||
}
|
||||
|
||||
/** 通过分类 code 获取对应的名称 */
|
||||
const getCategoryName = (categoryCode: string) => {
|
||||
return categoryList.value?.find((ctg: any) => ctg.code === categoryCode)?.name
|
||||
}
|
||||
|
||||
// ========== 表单相关 ==========
|
||||
const selectProcessDefinition = ref() // 选择的流程定义
|
||||
const processDefinitionDetailRef = ref()
|
||||
|
||||
/** 处理选择流程的按钮操作 **/
|
||||
const handleSelect = async (row, formVariables?) => {
|
||||
// 设置选择的流程
|
||||
selectProcessDefinition.value = row
|
||||
// 初始化流程定义详情
|
||||
await nextTick()
|
||||
processDefinitionDetailRef.value?.initProcessInfo(row, formVariables)
|
||||
}
|
||||
|
||||
/** 处理滚动事件,和左侧分类联动 */
|
||||
const handleScroll = (e: any) => {
|
||||
// 直接使用事件对象获取滚动位置
|
||||
const scrollTop = e.scrollTop
|
||||
|
||||
// 获取所有分类区域的位置信息
|
||||
const categoryPositions = categoryList.value
|
||||
.map((category: CategoryVO) => {
|
||||
const categoryRef = proxy.$refs[`category-${category.code}`]
|
||||
if (categoryRef?.[0]) {
|
||||
return {
|
||||
code: category.code,
|
||||
offsetTop: categoryRef[0].offsetTop,
|
||||
height: categoryRef[0].offsetHeight
|
||||
}
|
||||
}
|
||||
return null
|
||||
})
|
||||
.filter(Boolean)
|
||||
|
||||
// 查找当前滚动位置对应的分类
|
||||
let currentCategory = categoryPositions[0]
|
||||
for (const position of categoryPositions) {
|
||||
// 为了更好的用户体验,可以添加一个缓冲区域(比如 50px)
|
||||
if (scrollTop >= position.offsetTop - 50) {
|
||||
currentCategory = position
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 更新当前 active 的分类
|
||||
if (currentCategory && categoryActive.value.code !== currentCategory.code) {
|
||||
categoryActive.value = categoryList.value.find(
|
||||
(c: CategoryVO) => c.code === currentCategory.code
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** 过滤出有流程的分类列表。目的:只展示有流程的分类 */
|
||||
const availableCategories = computed(() => {
|
||||
if (!categoryList.value?.length || !processDefinitionGroup.value) {
|
||||
return []
|
||||
}
|
||||
|
||||
// 获取所有有流程的分类代码
|
||||
const availableCategoryCodes = Object.keys(processDefinitionGroup.value)
|
||||
|
||||
// 过滤出有流程的分类
|
||||
return categoryList.value.filter((category: CategoryVO) =>
|
||||
availableCategoryCodes.includes(category.code)
|
||||
)
|
||||
})
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.flow-icon {
|
||||
display: flex;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin-right: 10px;
|
||||
background-color: var(--el-color-primary);
|
||||
border-radius: 0.25rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.process-definition-container::before {
|
||||
position: absolute;
|
||||
left: 20.8%;
|
||||
height: 100%;
|
||||
border-left: 1px solid #e6e6e6;
|
||||
content: '';
|
||||
}
|
||||
|
||||
:deep() {
|
||||
.definition-item-card {
|
||||
.el-card__body {
|
||||
padding: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
234
src/views/bpm/processInstance/detail/PrintDialog.vue
Normal file
234
src/views/bpm/processInstance/detail/PrintDialog.vue
Normal file
@@ -0,0 +1,234 @@
|
||||
<script setup lang="ts">
|
||||
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import { DICT_TYPE, getDictLabel } from '@/utils/dict'
|
||||
import { decodeFields } from '@/utils/formCreate'
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
|
||||
const printData = ref()
|
||||
const userName = computed(() => userStore.user.nickname ?? '')
|
||||
const printTime = ref(formatDate(new Date(), 'YYYY-MM-DD HH:mm'))
|
||||
const formFields = ref()
|
||||
const printDataMap = ref({})
|
||||
|
||||
const open = async (id: string) => {
|
||||
loading.value = true
|
||||
try {
|
||||
printData.value = await ProcessInstanceApi.getProcessInstancePrintData(id)
|
||||
initPrintDataMap()
|
||||
parseFormFields()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
visible.value = true
|
||||
}
|
||||
defineExpose({ open })
|
||||
|
||||
const parseFormFields = () => {
|
||||
if (!printData.value) return
|
||||
|
||||
const formFieldsObj = decodeFields(
|
||||
printData.value.processInstance.processDefinition?.formFields || []
|
||||
)
|
||||
const processVariables = printData.value.processInstance.formVariables
|
||||
let res: any = []
|
||||
for (const item of formFieldsObj) {
|
||||
const id = item['field']
|
||||
const name = item['title']
|
||||
const variable = processVariables[item['field']]
|
||||
let html = variable
|
||||
switch (item['type']) {
|
||||
case 'UploadImg': {
|
||||
let imgEl = document.createElement('img')
|
||||
imgEl.setAttribute('src', variable)
|
||||
imgEl.setAttribute('style', 'max-width: 600px;')
|
||||
html = imgEl.outerHTML
|
||||
break
|
||||
}
|
||||
case 'radio':
|
||||
case 'checkbox':
|
||||
case 'select': {
|
||||
const options = item['options'] || []
|
||||
const temp: any = []
|
||||
if (Array.isArray(variable)) {
|
||||
const labels = options.filter((o) => variable.includes(o.value)).map((o) => o.label)
|
||||
temp.push(...labels)
|
||||
} else {
|
||||
const opt = options.find((o) => o.value === variable)
|
||||
temp.push(opt.label)
|
||||
}
|
||||
html = temp.join(',')
|
||||
}
|
||||
// TODO 更多表单打印展示
|
||||
}
|
||||
printDataMap.value[item['field']] = html
|
||||
res.push({ id, name, html })
|
||||
}
|
||||
formFields.value = res
|
||||
}
|
||||
|
||||
const initPrintDataMap = () => {
|
||||
printDataMap.value['startUser'] = printData.value.processInstance.startUser.nickname
|
||||
printDataMap.value['startUserDept'] = printData.value.processInstance.startUser.deptName
|
||||
printDataMap.value['processName'] = printData.value.processInstance.name
|
||||
printDataMap.value['processNum'] = printData.value.processInstance.id
|
||||
printDataMap.value['startTime'] = formatDate(printData.value.processInstance.startTime)
|
||||
printDataMap.value['endTime'] = formatDate(printData.value.processInstance.endTime)
|
||||
printDataMap.value['processStatus'] = getDictLabel(
|
||||
DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS,
|
||||
printData.value.processInstance.status
|
||||
)
|
||||
printDataMap.value['printUser'] = userName.value
|
||||
printDataMap.value['printTime'] = printTime.value
|
||||
}
|
||||
|
||||
const getPrintTemplateHTML = () => {
|
||||
const parser = new DOMParser()
|
||||
let doc = parser.parseFromString(printData.value.printTemplateHtml, 'text/html')
|
||||
// table 添加border
|
||||
let tables = doc.querySelectorAll('table')
|
||||
tables.forEach((item) => {
|
||||
item.setAttribute('border', '1')
|
||||
item.setAttribute('style', (item.getAttribute('style') || '') + 'border-collapse:collapse;')
|
||||
})
|
||||
// 替换 mentions
|
||||
let mentions = doc.querySelectorAll('[data-w-e-type="mention"]')
|
||||
mentions.forEach((item) => {
|
||||
const mentionId = JSON.parse(decodeURIComponent(item.getAttribute('data-info') ?? ''))['id']
|
||||
item.innerHTML = printDataMap.value[mentionId] ?? ''
|
||||
})
|
||||
// 替换流程记录
|
||||
let processRecords = doc.querySelectorAll('[data-w-e-type="process-record"]')
|
||||
let processRecordTable: Element = document.createElement('table')
|
||||
if (processRecords.length > 0) {
|
||||
// 构建流程记录html
|
||||
processRecordTable.setAttribute('border', '1')
|
||||
processRecordTable.setAttribute('style', 'width:100%;border-collapse:collapse;')
|
||||
const headTr = document.createElement('tr')
|
||||
const headTd = document.createElement('td')
|
||||
headTd.setAttribute('colspan', '2')
|
||||
headTd.setAttribute('width', 'auto')
|
||||
headTd.setAttribute('style', 'text-align: center;')
|
||||
headTd.innerHTML = '流程节点'
|
||||
headTr.appendChild(headTd)
|
||||
processRecordTable.appendChild(headTr)
|
||||
printData.value.tasks.forEach((item) => {
|
||||
const tr = document.createElement('tr')
|
||||
const td1 = document.createElement('td')
|
||||
td1.innerHTML = item.name
|
||||
const td2 = document.createElement('td')
|
||||
td2.innerHTML = item.description
|
||||
tr.appendChild(td1)
|
||||
tr.appendChild(td2)
|
||||
processRecordTable.appendChild(tr)
|
||||
})
|
||||
}
|
||||
processRecords.forEach((item) => {
|
||||
item.innerHTML = processRecordTable.outerHTML
|
||||
})
|
||||
// 返回 html
|
||||
return doc.body.innerHTML
|
||||
}
|
||||
|
||||
const printObj = ref({
|
||||
id: 'printDivTag',
|
||||
popTitle: ' ',
|
||||
extraCss: '/print.css',
|
||||
extraHead: '',
|
||||
zIndex: 20003
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dialog v-loading="loading" v-model="visible" :show-close="false">
|
||||
<div id="printDivTag" style="word-break: break-all">
|
||||
<div v-if="printData.printTemplateEnable" v-html="getPrintTemplateHTML()"></div>
|
||||
<div v-else>
|
||||
<h2 class="text-center">{{ printData.processInstance.name }}</h2>
|
||||
<div class="text-right text-15px">{{ '打印人员: ' + userName }}</div>
|
||||
<div class="flex justify-between">
|
||||
<div class="text-15px">{{ '流程编号: ' + printData.processInstance.id }}</div>
|
||||
<div class="text-15px">{{ '打印时间: ' + printTime }}</div>
|
||||
</div>
|
||||
<table class="mt-20px w-100%" border="1" style="border-collapse: collapse">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="p-5px w-25%">发起人</td>
|
||||
<td class="p-5px w-25%">{{ printData.processInstance.startUser.nickname }}</td>
|
||||
<td class="p-5px w-25%">发起时间</td>
|
||||
<td class="p-5px w-25%">{{ formatDate(printData.processInstance.startTime) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="p-5px w-25%">所属部门</td>
|
||||
<td class="p-5px w-25%">{{ printData.processInstance.startUser.deptName }}</td>
|
||||
<td class="p-5px w-25%">流程状态</td>
|
||||
<td class="p-5px w-25%">
|
||||
{{
|
||||
getDictLabel(
|
||||
DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS,
|
||||
printData.processInstance.status
|
||||
)
|
||||
}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="p-5px w-100% text-center" colspan="4">
|
||||
<h4>表单内容</h4>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-for="item in formFields" :key="item.id">
|
||||
<td class="p-5px w-20%">
|
||||
{{ item.name }}
|
||||
</td>
|
||||
<td class="p-5px w-80%" colspan="3">
|
||||
<div v-html="item.html"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="p-5px w-100% text-center" colspan="4">
|
||||
<h4>流程节点</h4>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-for="item in printData.tasks" :key="item.id">
|
||||
<td class="p-5px w-20%">
|
||||
{{ item.name }}
|
||||
</td>
|
||||
<td class="p-5px w-80%" colspan="3">
|
||||
{{ item.description }}
|
||||
<div v-if="item.signPicUrl && item.signPicUrl.length > 0">
|
||||
<img class="w-90px h-40px" :src="item.signPicUrl" alt="" />
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="visible = false">取 消</el-button>
|
||||
<el-button type="primary" v-print="printObj"> 打 印</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
/* 修复打印只显示一页 */
|
||||
@media print {
|
||||
@page {
|
||||
size: auto;
|
||||
}
|
||||
|
||||
body,
|
||||
html,
|
||||
div {
|
||||
height: auto !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<el-card v-loading="loading" class="box-card">
|
||||
<MyProcessViewer key="designer" :xml="view.bpmnXml" :view="view" class="process-viewer" />
|
||||
</el-card>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { MyProcessViewer } from '@/components/bpmnProcessDesigner/package'
|
||||
|
||||
defineOptions({ name: 'BpmProcessInstanceBpmnViewer' })
|
||||
|
||||
const props = defineProps({
|
||||
loading: propTypes.bool.def(false), // 是否加载中
|
||||
bpmnXml: propTypes.string, // BPMN XML
|
||||
modelView: propTypes.object
|
||||
})
|
||||
|
||||
const view = ref({
|
||||
bpmnXml: ''
|
||||
}) // BPMN 流程图数据
|
||||
|
||||
|
||||
/** 只有 loading 完成时,才去加载流程列表 */
|
||||
watch(
|
||||
() => props.modelView,
|
||||
async (newModelView) => {
|
||||
// 加载最新
|
||||
if (newModelView) {
|
||||
//@ts-ignore
|
||||
view.value = newModelView
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
/** 监听 bpmnXml */
|
||||
watch(
|
||||
() => props.bpmnXml,
|
||||
(value) => {
|
||||
view.value.bpmnXml = value
|
||||
}
|
||||
)
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.box-card {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
margin-bottom: 0;
|
||||
|
||||
:deep(.el-card__body) {
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
:deep(.process-viewer) {
|
||||
height: 100% !important;
|
||||
min-height: 100%;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,174 @@
|
||||
<template>
|
||||
<div v-loading="loading" class="process-viewer-container">
|
||||
<SimpleProcessViewer
|
||||
:flow-node="simpleModel"
|
||||
:tasks="tasks"
|
||||
:process-instance="processInstance"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { TaskStatusEnum } from '@/api/bpm/task'
|
||||
import { SimpleFlowNode, NodeType } from '@/components/SimpleProcessDesignerV2/src/consts'
|
||||
import { SimpleProcessViewer } from '@/components/SimpleProcessDesignerV2/src/'
|
||||
defineOptions({ name: 'BpmProcessInstanceSimpleViewer' })
|
||||
|
||||
const props = defineProps({
|
||||
loading: propTypes.bool.def(false), // 是否加载中
|
||||
modelView: propTypes.object,
|
||||
simpleJson: propTypes.string // Simple 模型结构数据 (json 格式)
|
||||
})
|
||||
const simpleModel = ref<any>({})
|
||||
// 用户任务
|
||||
const tasks = ref([])
|
||||
// 流程实例
|
||||
const processInstance = ref()
|
||||
|
||||
/** 监控模型视图 包括任务列表、进行中的活动节点编号等 */
|
||||
watch(
|
||||
() => props.modelView,
|
||||
async (newModelView) => {
|
||||
if (newModelView) {
|
||||
tasks.value = newModelView.tasks
|
||||
processInstance.value = newModelView.processInstance
|
||||
// 已经拒绝的活动节点编号集合,只包括 UserTask
|
||||
const rejectedTaskActivityIds: string[] = newModelView.rejectedTaskActivityIds
|
||||
// 进行中的活动节点编号集合, 只包括 UserTask
|
||||
const unfinishedTaskActivityIds: string[] = newModelView.unfinishedTaskActivityIds
|
||||
// 已经完成的活动节点编号集合, 包括 UserTask、Gateway 等
|
||||
const finishedActivityIds: string[] = newModelView.finishedTaskActivityIds
|
||||
// 已经完成的连线节点编号集合,只包括 SequenceFlow
|
||||
const finishedSequenceFlowActivityIds: string[] = newModelView.finishedSequenceFlowActivityIds
|
||||
setSimpleModelNodeTaskStatus(
|
||||
newModelView.simpleModel,
|
||||
newModelView.processInstance?.status,
|
||||
rejectedTaskActivityIds,
|
||||
unfinishedTaskActivityIds,
|
||||
finishedActivityIds,
|
||||
finishedSequenceFlowActivityIds
|
||||
)
|
||||
simpleModel.value = newModelView.simpleModel ? newModelView.simpleModel : {}
|
||||
}
|
||||
}
|
||||
)
|
||||
/** 监控模型结构数据 */
|
||||
watch(
|
||||
() => props.simpleJson,
|
||||
async (value) => {
|
||||
if (value) {
|
||||
simpleModel.value = JSON.parse(value)
|
||||
}
|
||||
}
|
||||
)
|
||||
const setSimpleModelNodeTaskStatus = (
|
||||
simpleModel: SimpleFlowNode | undefined,
|
||||
processStatus: number,
|
||||
rejectedTaskActivityIds: string[],
|
||||
unfinishedTaskActivityIds: string[],
|
||||
finishedActivityIds: string[],
|
||||
finishedSequenceFlowActivityIds: string[]
|
||||
) => {
|
||||
if (!simpleModel) {
|
||||
return
|
||||
}
|
||||
// 结束节点
|
||||
if (simpleModel.type === NodeType.END_EVENT_NODE) {
|
||||
if (finishedActivityIds.includes(simpleModel.id)) {
|
||||
simpleModel.activityStatus = processStatus
|
||||
} else {
|
||||
simpleModel.activityStatus = TaskStatusEnum.NOT_START
|
||||
}
|
||||
return
|
||||
}
|
||||
// 审批节点
|
||||
if (
|
||||
simpleModel.type === NodeType.START_USER_NODE ||
|
||||
simpleModel.type === NodeType.USER_TASK_NODE ||
|
||||
simpleModel.type === NodeType.TRANSACTOR_NODE ||
|
||||
simpleModel.type === NodeType.CHILD_PROCESS_NODE
|
||||
) {
|
||||
simpleModel.activityStatus = TaskStatusEnum.NOT_START
|
||||
if (rejectedTaskActivityIds.includes(simpleModel.id)) {
|
||||
simpleModel.activityStatus = TaskStatusEnum.REJECT
|
||||
} else if (unfinishedTaskActivityIds.includes(simpleModel.id)) {
|
||||
simpleModel.activityStatus = TaskStatusEnum.RUNNING
|
||||
} else if (finishedActivityIds.includes(simpleModel.id)) {
|
||||
simpleModel.activityStatus = TaskStatusEnum.APPROVE
|
||||
}
|
||||
// TODO 是不是还缺一个 cancel 的状态
|
||||
}
|
||||
// 抄送节点
|
||||
if (simpleModel.type === NodeType.COPY_TASK_NODE) {
|
||||
// 抄送节点,只有通过和未执行状态
|
||||
if (finishedActivityIds.includes(simpleModel.id)) {
|
||||
simpleModel.activityStatus = TaskStatusEnum.APPROVE
|
||||
} else {
|
||||
simpleModel.activityStatus = TaskStatusEnum.NOT_START
|
||||
}
|
||||
}
|
||||
// 延迟器节点
|
||||
if (simpleModel.type === NodeType.DELAY_TIMER_NODE) {
|
||||
// 延迟器节点,只有通过和未执行状态
|
||||
if (finishedActivityIds.includes(simpleModel.id)) {
|
||||
simpleModel.activityStatus = TaskStatusEnum.APPROVE
|
||||
} else {
|
||||
simpleModel.activityStatus = TaskStatusEnum.NOT_START
|
||||
}
|
||||
}
|
||||
// 触发器节点
|
||||
if (simpleModel.type === NodeType.TRIGGER_NODE) {
|
||||
// 触发器节点,只有通过和未执行状态
|
||||
if (finishedActivityIds.includes(simpleModel.id)) {
|
||||
simpleModel.activityStatus = TaskStatusEnum.APPROVE
|
||||
} else {
|
||||
simpleModel.activityStatus = TaskStatusEnum.NOT_START
|
||||
}
|
||||
}
|
||||
|
||||
// 条件节点对应 SequenceFlow
|
||||
if (simpleModel.type === NodeType.CONDITION_NODE) {
|
||||
// 条件节点,只有通过和未执行状态
|
||||
if (finishedSequenceFlowActivityIds.includes(simpleModel.id)) {
|
||||
simpleModel.activityStatus = TaskStatusEnum.APPROVE
|
||||
} else {
|
||||
simpleModel.activityStatus = TaskStatusEnum.NOT_START
|
||||
}
|
||||
}
|
||||
// 网关节点
|
||||
if (
|
||||
simpleModel.type === NodeType.CONDITION_BRANCH_NODE ||
|
||||
simpleModel.type === NodeType.PARALLEL_BRANCH_NODE ||
|
||||
simpleModel.type === NodeType.INCLUSIVE_BRANCH_NODE ||
|
||||
simpleModel.type === NodeType.ROUTER_BRANCH_NODE
|
||||
) {
|
||||
// 网关节点。只有通过和未执行状态
|
||||
if (finishedActivityIds.includes(simpleModel.id)) {
|
||||
simpleModel.activityStatus = TaskStatusEnum.APPROVE
|
||||
} else {
|
||||
simpleModel.activityStatus = TaskStatusEnum.NOT_START
|
||||
}
|
||||
simpleModel.conditionNodes?.forEach((node) => {
|
||||
setSimpleModelNodeTaskStatus(
|
||||
node,
|
||||
processStatus,
|
||||
rejectedTaskActivityIds,
|
||||
unfinishedTaskActivityIds,
|
||||
finishedActivityIds,
|
||||
finishedSequenceFlowActivityIds
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
setSimpleModelNodeTaskStatus(
|
||||
simpleModel.childNode,
|
||||
processStatus,
|
||||
rejectedTaskActivityIds,
|
||||
unfinishedTaskActivityIds,
|
||||
finishedActivityIds,
|
||||
finishedSequenceFlowActivityIds
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
103
src/views/bpm/processInstance/detail/ProcessInstanceTaskList.vue
Normal file
103
src/views/bpm/processInstance/detail/ProcessInstanceTaskList.vue
Normal file
@@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<el-table :data="tasks" border header-cell-class-name="table-header-gray">
|
||||
<el-table-column label="审批节点" prop="name" min-width="120" align="center" />
|
||||
<el-table-column label="审批人" min-width="100" align="center">
|
||||
<template #default="scope">
|
||||
{{ scope.row.assigneeUser?.nickname || scope.row.ownerUser?.nickname }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="开始时间"
|
||||
prop="createTime"
|
||||
min-width="140"
|
||||
/>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="结束时间"
|
||||
prop="endTime"
|
||||
min-width="140"
|
||||
/>
|
||||
<el-table-column align="center" label="审批状态" prop="status" min-width="90">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_TASK_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="审批建议" prop="reason" min-width="200">
|
||||
<template #default="scope">
|
||||
{{ scope.row.reason }}
|
||||
<el-button
|
||||
class="ml-10px"
|
||||
size="small"
|
||||
v-if="scope.row.formId > 0"
|
||||
@click="handleFormDetail(scope.row)"
|
||||
>
|
||||
<Icon icon="ep:document" /> 查看表单
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="耗时" prop="durationInMillis" min-width="100">
|
||||
<template #default="scope">
|
||||
{{ formatPast2(scope.row.durationInMillis) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 弹窗:表单 -->
|
||||
<Dialog title="表单详情" v-model="taskFormVisible" width="600">
|
||||
<form-create
|
||||
ref="fApi"
|
||||
v-model="taskForm.value"
|
||||
:option="taskForm.option"
|
||||
:rule="taskForm.rule"
|
||||
/>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { dateFormatter, formatPast2 } from '@/utils/formatTime'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import type { ApiAttrs } from '@form-create/element-ui/types/config'
|
||||
import { setConfAndFields2 } from '@/utils/formCreate'
|
||||
import * as TaskApi from '@/api/bpm/task'
|
||||
|
||||
defineOptions({ name: 'BpmProcessInstanceTaskList' })
|
||||
|
||||
const props = defineProps({
|
||||
loading: propTypes.bool.def(false), // 是否加载中
|
||||
id: propTypes.string // 流程实例的编号
|
||||
})
|
||||
const tasks = ref([]) // 流程任务的数组
|
||||
|
||||
/** 查看表单 */
|
||||
const fApi = ref<ApiAttrs>() // form-create 的 API 操作类
|
||||
const taskForm = ref({
|
||||
rule: [],
|
||||
option: {},
|
||||
value: {}
|
||||
}) // 流程任务的表单详情
|
||||
const taskFormVisible = ref(false)
|
||||
const handleFormDetail = async (row: any) => {
|
||||
// 设置表单
|
||||
setConfAndFields2(taskForm, row.formConf, row.formFields, row.formVariables)
|
||||
// 弹窗打开
|
||||
taskFormVisible.value = true
|
||||
// 隐藏提交、重置按钮,设置禁用只读
|
||||
await nextTick()
|
||||
fApi.value.fapi.btn.show(false)
|
||||
fApi.value?.fapi?.resetBtn.show(false)
|
||||
fApi.value?.fapi?.disabled(true)
|
||||
}
|
||||
|
||||
/** 只有 loading 完成时,才去加载流程列表 */
|
||||
watch(
|
||||
() => props.loading,
|
||||
async (value) => {
|
||||
if (value) {
|
||||
tasks.value = await TaskApi.getTaskListByProcessInstanceId(props.id)
|
||||
}
|
||||
}
|
||||
)
|
||||
</script>
|
||||
361
src/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue
Normal file
361
src/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue
Normal file
@@ -0,0 +1,361 @@
|
||||
<!-- 审批详情的右侧:审批流 -->
|
||||
<template>
|
||||
<el-timeline class="pt-20px">
|
||||
<!-- 遍历每个审批节点 -->
|
||||
<el-timeline-item
|
||||
v-for="(activity, index) in activityNodes"
|
||||
:key="index"
|
||||
size="large"
|
||||
:icon="getApprovalNodeIcon(activity.status, activity.nodeType)"
|
||||
:color="getApprovalNodeColor(activity.status)"
|
||||
>
|
||||
<template #dot>
|
||||
<div
|
||||
class="position-absolute left--10px top--6px rounded-full border border-solid border-#dedede w-30px h-30px flex justify-center items-center bg-#3f73f7 p-5px"
|
||||
>
|
||||
<img class="w-full h-full" :src="getApprovalNodeImg(activity.nodeType)" alt="" />
|
||||
<div
|
||||
v-if="props.showStatusIcon"
|
||||
class="position-absolute top-17px left-17px rounded-full flex items-center p-1px border-2 border-white border-solid"
|
||||
:style="{ backgroundColor: getApprovalNodeColor(activity.status) }"
|
||||
>
|
||||
<el-icon :size="11" color="#fff">
|
||||
<component :is="getApprovalNodeIcon(activity.status, activity.nodeType)" />
|
||||
</el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="flex flex-col items-start gap2" :id="`activity-task-${activity.id}-${index}`">
|
||||
<!-- 第一行:节点名称、时间 -->
|
||||
<div class="flex w-full">
|
||||
<div class="font-bold">
|
||||
{{ activity.name }} <span v-if="activity.status === TaskStatusEnum.SKIP">【跳过】</span>
|
||||
</div>
|
||||
<!-- 信息:时间 -->
|
||||
<div
|
||||
v-if="activity.status !== TaskStatusEnum.NOT_START"
|
||||
class="text-#a5a5a5 text-13px mt-1 ml-auto"
|
||||
>
|
||||
{{ getApprovalNodeTime(activity) }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="activity.nodeType === NodeType.CHILD_PROCESS_NODE">
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
size="small"
|
||||
@click="handleChildProcess(activity)"
|
||||
:disabled="!activity.processInstanceId"
|
||||
>
|
||||
查看子流程
|
||||
</el-button>
|
||||
</div>
|
||||
<!-- 需要自定义选择审批人 -->
|
||||
<div
|
||||
class="flex flex-wrap gap2 items-center"
|
||||
v-if="
|
||||
isEmpty(activity.tasks) &&
|
||||
((CandidateStrategy.START_USER_SELECT === activity.candidateStrategy &&
|
||||
isEmpty(activity.candidateUsers)) ||
|
||||
(props.enableApproveUserSelect &&
|
||||
CandidateStrategy.APPROVE_USER_SELECT === activity.candidateStrategy))
|
||||
"
|
||||
>
|
||||
<!-- && activity.nodeType === NodeType.USER_TASK_NODE -->
|
||||
<el-tooltip content="添加用户" placement="left">
|
||||
<el-button
|
||||
class="!px-6px"
|
||||
@click="handleSelectUser(activity.id, customApproveUsers[activity.id])"
|
||||
>
|
||||
<img class="w-18px text-#ccc" src="@/assets/svgs/bpm/add-user.svg" alt="" />
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<div
|
||||
v-for="(user, idx1) in customApproveUsers[activity.id]"
|
||||
:key="idx1"
|
||||
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 }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="flex items-center flex-wrap mt-1 gap2">
|
||||
<!-- 情况一:遍历每个审批节点下的【进行中】task 任务 -->
|
||||
<div v-for="(task, idx) in activity.tasks" :key="idx" class="flex flex-col pr-2 gap2">
|
||||
<div
|
||||
class="position-relative flex flex-wrap gap2"
|
||||
v-if="task.assigneeUser || task.ownerUser"
|
||||
>
|
||||
<!-- 信息:头像昵称 -->
|
||||
<div
|
||||
class="bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
|
||||
>
|
||||
<template v-if="task.assigneeUser?.avatar || task.assigneeUser?.nickname">
|
||||
<el-avatar
|
||||
class="!m-5px"
|
||||
:size="28"
|
||||
v-if="task.assigneeUser?.avatar"
|
||||
:src="task.assigneeUser?.avatar"
|
||||
/>
|
||||
<el-avatar class="!m-5px" :size="28" v-else>
|
||||
{{ task.assigneeUser?.nickname.substring(0, 1) }}
|
||||
</el-avatar>
|
||||
{{ task.assigneeUser?.nickname }}
|
||||
</template>
|
||||
<template v-else-if="task.ownerUser?.avatar || task.ownerUser?.nickname">
|
||||
<el-avatar
|
||||
class="!m-5px"
|
||||
:size="28"
|
||||
v-if="task.ownerUser?.avatar"
|
||||
:src="task.ownerUser?.avatar"
|
||||
/>
|
||||
<el-avatar class="!m-5px" :size="28" v-else>
|
||||
{{ task.ownerUser?.nickname.substring(0, 1) }}
|
||||
</el-avatar>
|
||||
{{ task.ownerUser?.nickname }}
|
||||
</template>
|
||||
<!-- 信息:任务 ICON -->
|
||||
<div
|
||||
v-if="props.showStatusIcon && onlyStatusIconShow.includes(task.status)"
|
||||
class="position-absolute top-19px left-23px rounded-full flex items-center p-1px border-2 border-white border-solid"
|
||||
:style="{ backgroundColor: statusIconMap2[task.status]?.color }"
|
||||
>
|
||||
<Icon :size="11" :icon="statusIconMap2[task.status]?.icon" color="#FFFFFF" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<teleport defer :to="`#activity-task-${activity.id}-${index}`">
|
||||
<div
|
||||
v-if="
|
||||
task.reason &&
|
||||
[NodeType.USER_TASK_NODE, NodeType.END_EVENT_NODE].includes(activity.nodeType)
|
||||
"
|
||||
class="text-#a5a5a5 text-13px mt-1 w-full bg-#f8f8fa p2 rounded-md"
|
||||
>
|
||||
<!-- TODO lesan:这里如果是办理,需要是办理意见 -->
|
||||
审批意见:{{ task.reason }}
|
||||
</div>
|
||||
<div
|
||||
v-if="task.signPicUrl && activity.nodeType === NodeType.USER_TASK_NODE"
|
||||
class="text-#a5a5a5 text-13px mt-1 w-full bg-#f8f8fa p2 rounded-md"
|
||||
>
|
||||
签名:
|
||||
<el-image
|
||||
class="w-90px h-40px ml-5px"
|
||||
:src="task.signPicUrl"
|
||||
:preview-src-list="[task.signPicUrl]"
|
||||
/>
|
||||
</div>
|
||||
</teleport>
|
||||
</div>
|
||||
<!-- 情况二:遍历每个审批节点下的【候选的】task 任务。例如说,1)依次审批,2)未来的审批任务等 -->
|
||||
<div
|
||||
v-for="(user, idx1) in activity.candidateUsers"
|
||||
:key="idx1"
|
||||
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 -->
|
||||
<div
|
||||
v-if="props.showStatusIcon"
|
||||
class="position-absolute top-20px left-24px rounded-full flex items-center p-1px border-2 border-white border-solid"
|
||||
:style="{ backgroundColor: statusIconMap2['-1']?.color }"
|
||||
>
|
||||
<Icon :size="11" :icon="statusIconMap2['-1']?.icon" color="#FFFFFF" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
|
||||
<!-- 用户选择弹窗 -->
|
||||
<UserSelectForm ref="userSelectFormRef" @confirm="handleUserSelectConfirm" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
|
||||
import { TaskStatusEnum } from '@/api/bpm/task'
|
||||
import { NodeType, CandidateStrategy } from '@/components/SimpleProcessDesignerV2/src/consts'
|
||||
import { isEmpty } from '@/utils/is'
|
||||
import { Check, Close, Loading, Clock, Minus, Delete, ArrowDown } from '@element-plus/icons-vue'
|
||||
import starterSvg from '@/assets/svgs/bpm/starter.svg'
|
||||
import auditorSvg from '@/assets/svgs/bpm/auditor.svg'
|
||||
import copySvg from '@/assets/svgs/bpm/copy.svg'
|
||||
import conditionSvg from '@/assets/svgs/bpm/condition.svg'
|
||||
import parallelSvg from '@/assets/svgs/bpm/parallel.svg'
|
||||
import finishSvg from '@/assets/svgs/bpm/finish.svg'
|
||||
import transactorSvg from '@/assets/svgs/bpm/transactor.svg'
|
||||
import childProcessSvg from '@/assets/svgs/bpm/child-process.svg'
|
||||
|
||||
defineOptions({ name: 'BpmProcessInstanceTimeline' })
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
activityNodes: ProcessInstanceApi.ApprovalNodeInfo[] // 审批节点信息
|
||||
showStatusIcon?: boolean // 是否显示头像右下角状态图标
|
||||
enableApproveUserSelect?: boolean // 是否开启审批人自选功能
|
||||
}>(),
|
||||
{
|
||||
showStatusIcon: true, // 默认值为 true
|
||||
enableApproveUserSelect: false // 默认值为 false
|
||||
}
|
||||
)
|
||||
const { push } = useRouter() // 路由
|
||||
|
||||
// 审批节点
|
||||
const statusIconMap2 = {
|
||||
// 跳过
|
||||
'-2': { color: '#cccccc', icon: 'ep:arrow-down' },
|
||||
// 未开始
|
||||
'-1': { color: '#909398', icon: 'ep-clock' },
|
||||
// 待审批
|
||||
'0': { color: '#00b32a', icon: 'ep:loading' },
|
||||
// 审批中
|
||||
'1': { color: '#448ef7', icon: 'ep:loading' },
|
||||
// 审批通过
|
||||
'2': { color: '#00b32a', icon: 'ep:circle-check-filled' },
|
||||
// 审批不通过
|
||||
'3': { color: '#f46b6c', icon: 'fa-solid:times-circle' },
|
||||
// 取消
|
||||
'4': { color: '#cccccc', icon: 'ep:delete-filled' },
|
||||
// 退回
|
||||
'5': { color: '#f46b6c', icon: 'ep:remove-filled' },
|
||||
// 委派中
|
||||
'6': { color: '#448ef7', icon: 'ep:loading' },
|
||||
// 审批通过中
|
||||
'7': { color: '#00b32a', icon: 'ep:circle-check-filled' }
|
||||
}
|
||||
|
||||
const statusIconMap = {
|
||||
// 跳过
|
||||
'-2': { color: '#909398', icon: ArrowDown },
|
||||
// 审批未开始
|
||||
'-1': { color: '#909398', icon: Clock },
|
||||
'0': { color: '#00b32a', icon: Clock },
|
||||
// 审批中
|
||||
'1': { color: '#448ef7', icon: Loading },
|
||||
// 审批通过
|
||||
'2': { color: '#00b32a', icon: Check },
|
||||
// 审批不通过
|
||||
'3': { color: '#f46b6c', icon: Close },
|
||||
// 已取消
|
||||
'4': { color: '#cccccc', icon: Delete },
|
||||
// 退回
|
||||
'5': { color: '#f46b6c', icon: Minus },
|
||||
// 委派中
|
||||
'6': { color: '#448ef7', icon: Loading },
|
||||
// 审批通过中
|
||||
'7': { color: '#00b32a', icon: Check }
|
||||
}
|
||||
|
||||
const nodeTypeSvgMap = {
|
||||
// 结束节点
|
||||
[NodeType.END_EVENT_NODE]: { color: '#909398', svg: finishSvg },
|
||||
// 发起人节点
|
||||
[NodeType.START_USER_NODE]: { color: '#909398', svg: starterSvg },
|
||||
// 审批人节点
|
||||
[NodeType.USER_TASK_NODE]: { color: '#ff943e', svg: auditorSvg },
|
||||
// 办理人节点
|
||||
[NodeType.TRANSACTOR_NODE]: { color: '#ff943e', svg: transactorSvg },
|
||||
// 抄送人节点
|
||||
[NodeType.COPY_TASK_NODE]: { color: '#3296fb', svg: copySvg },
|
||||
// 条件分支节点
|
||||
[NodeType.CONDITION_NODE]: { color: '#14bb83', svg: conditionSvg },
|
||||
// 并行分支节点
|
||||
[NodeType.PARALLEL_BRANCH_NODE]: { color: '#14bb83', svg: parallelSvg },
|
||||
// 子流程节点
|
||||
[NodeType.CHILD_PROCESS_NODE]: { color: '#14bb83', svg: childProcessSvg }
|
||||
}
|
||||
|
||||
// 只有只有状态是 -1、0、1 才展示头像右小角状态小icon
|
||||
const onlyStatusIconShow = [-1, 0, 1]
|
||||
|
||||
// timeline时间线上icon图标
|
||||
const getApprovalNodeImg = (nodeType: NodeType) => {
|
||||
return nodeTypeSvgMap[nodeType]?.svg
|
||||
}
|
||||
|
||||
const getApprovalNodeIcon = (taskStatus: number, nodeType: NodeType) => {
|
||||
if (taskStatus == TaskStatusEnum.NOT_START) {
|
||||
return statusIconMap[taskStatus]?.icon
|
||||
}
|
||||
|
||||
if (
|
||||
nodeType === NodeType.START_USER_NODE ||
|
||||
nodeType === NodeType.USER_TASK_NODE ||
|
||||
nodeType === NodeType.TRANSACTOR_NODE ||
|
||||
nodeType === NodeType.CHILD_PROCESS_NODE ||
|
||||
nodeType === NodeType.END_EVENT_NODE
|
||||
) {
|
||||
return statusIconMap[taskStatus]?.icon
|
||||
}
|
||||
}
|
||||
|
||||
const getApprovalNodeColor = (taskStatus: number) => {
|
||||
return statusIconMap[taskStatus]?.color
|
||||
}
|
||||
|
||||
const getApprovalNodeTime = (node: ProcessInstanceApi.ApprovalNodeInfo) => {
|
||||
if (node.nodeType === NodeType.START_USER_NODE && node.startTime) {
|
||||
return `${formatDate(node.startTime)}`
|
||||
}
|
||||
if (node.endTime) {
|
||||
return `${formatDate(node.endTime)}`
|
||||
}
|
||||
if (node.startTime) {
|
||||
return `${formatDate(node.startTime)}`
|
||||
}
|
||||
}
|
||||
|
||||
// 选择自定义审批人
|
||||
const userSelectFormRef = ref()
|
||||
const handleSelectUser = (activityId, selectedList) => {
|
||||
userSelectFormRef.value.open(activityId, selectedList)
|
||||
}
|
||||
const emit = defineEmits<{
|
||||
selectUserConfirm: [id: any, userList: any[]]
|
||||
}>()
|
||||
const customApproveUsers: any = ref({}) // key:activityId,value:用户列表
|
||||
// 选择完成
|
||||
const handleUserSelectConfirm = (activityId: string, userList: any[]) => {
|
||||
customApproveUsers.value[activityId] = userList || []
|
||||
emit('selectUserConfirm', activityId, userList)
|
||||
}
|
||||
|
||||
/** 跳转子流程 */
|
||||
const handleChildProcess = (activity: any) => {
|
||||
if (!activity.processInstanceId) {
|
||||
return
|
||||
}
|
||||
push({
|
||||
name: 'BpmProcessInstanceDetail',
|
||||
query: {
|
||||
id: activity.processInstanceId
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 设置自定义审批人 */
|
||||
const setCustomApproveUsers = (activityId: string, users: any[]) => {
|
||||
customApproveUsers.value[activityId] = users || []
|
||||
}
|
||||
|
||||
/** 批量设置多个节点的自定义审批人 */
|
||||
const batchSetCustomApproveUsers = (data: Record<string, any[]>) => {
|
||||
Object.keys(data).forEach((activityId) => {
|
||||
customApproveUsers.value[activityId] = data[activityId] || []
|
||||
})
|
||||
}
|
||||
|
||||
// 暴露方法给父组件
|
||||
defineExpose({ setCustomApproveUsers, batchSetCustomApproveUsers })
|
||||
</script>
|
||||
50
src/views/bpm/processInstance/detail/SignDialog.vue
Normal file
50
src/views/bpm/processInstance/detail/SignDialog.vue
Normal file
@@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<el-dialog v-model="signDialogVisible" title="签名" width="935">
|
||||
<div class="position-relative">
|
||||
<Vue3Signature class="b b-solid b-gray" ref="signature" w="900px" h="400px" />
|
||||
<el-button
|
||||
class="pos-absolute bottom-20px right-10px"
|
||||
type="primary"
|
||||
text
|
||||
size="small"
|
||||
@click="signature.clear()"
|
||||
>
|
||||
<Icon icon="ep:delete" class="mr-5px" />
|
||||
清除
|
||||
</el-button>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="signDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submit"> 提交 </el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Vue3Signature from 'vue3-signature'
|
||||
import * as FileApi from '@/api/infra/file'
|
||||
import download from '@/utils/download'
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const signDialogVisible = ref(false)
|
||||
const signature = ref()
|
||||
|
||||
const open = async () => {
|
||||
signDialogVisible.value = true
|
||||
}
|
||||
defineExpose({ open })
|
||||
|
||||
const emits = defineEmits(['success'])
|
||||
const submit = async () => {
|
||||
message.success('签名上传中请稍等。。。')
|
||||
const res = await FileApi.updateFile({
|
||||
file: download.base64ToFile(signature.value.save('image/png'), '签名')
|
||||
})
|
||||
emits('success', res.data)
|
||||
signDialogVisible.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
363
src/views/bpm/processInstance/detail/index.vue
Normal file
363
src/views/bpm/processInstance/detail/index.vue
Normal file
@@ -0,0 +1,363 @@
|
||||
<template>
|
||||
<ContentWrap :bodyStyle="{ padding: '10px 20px 0' }" class="position-relative">
|
||||
<div class="processInstance-wrap-main">
|
||||
<el-scrollbar>
|
||||
<img
|
||||
class="position-absolute right-20px"
|
||||
width="150"
|
||||
:src="auditIconsMap[processInstance.status]"
|
||||
alt=""
|
||||
/>
|
||||
<div class="flex">
|
||||
<div class="text-#878c93 h-15px">编号:{{ id }}</div>
|
||||
<Icon icon="ep:printer" class="ml-15px cursor-pointer" @click="handlePrint" />
|
||||
</div>
|
||||
<el-divider class="!my-8px" />
|
||||
<div class="flex items-center gap-5 mb-10px h-40px">
|
||||
<div class="text-26px font-bold mb-5px">{{ processInstance.name }}</div>
|
||||
<dict-tag
|
||||
v-if="processInstance.status"
|
||||
:type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS"
|
||||
:value="processInstance.status"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-5 mb-10px text-13px h-35px">
|
||||
<div
|
||||
class="bg-gray-100 h-35px rounded-3xl flex items-center p-8px gap-2 dark:color-gray-600"
|
||||
>
|
||||
<el-avatar
|
||||
:size="28"
|
||||
v-if="processInstance?.startUser?.avatar"
|
||||
:src="processInstance?.startUser?.avatar"
|
||||
/>
|
||||
<el-avatar :size="28" v-else-if="processInstance?.startUser?.nickname">
|
||||
{{ processInstance?.startUser?.nickname.substring(0, 1) }}
|
||||
</el-avatar>
|
||||
{{ processInstance?.startUser?.nickname }}
|
||||
</div>
|
||||
<div class="text-#878c93"> {{ formatDate(processInstance.startTime) }} 提交 </div>
|
||||
</div>
|
||||
|
||||
<el-tabs v-model="activeTab">
|
||||
<!-- 表单信息 -->
|
||||
<el-tab-pane label="审批详情" name="form">
|
||||
<div class="form-scroll-area">
|
||||
<el-scrollbar>
|
||||
<el-row>
|
||||
<el-col :span="17" class="!flex !flex-col formCol">
|
||||
<!-- 表单信息 -->
|
||||
<div
|
||||
v-loading="processInstanceLoading"
|
||||
class="form-box flex flex-col mb-30px flex-1"
|
||||
>
|
||||
<!-- 情况一:流程表单 -->
|
||||
<el-col v-if="processDefinition?.formType === BpmModelFormType.NORMAL">
|
||||
<form-create
|
||||
v-model="detailForm.value"
|
||||
v-model:api="fApi"
|
||||
:option="detailForm.option"
|
||||
:rule="detailForm.rule"
|
||||
/>
|
||||
</el-col>
|
||||
<!-- 情况二:业务表单 -->
|
||||
<div v-if="processDefinition?.formType === BpmModelFormType.CUSTOM">
|
||||
<BusinessFormComponent :id="processInstance.businessKey" />
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="7">
|
||||
<!-- 审批记录时间线 -->
|
||||
<ProcessInstanceTimeline :activity-nodes="activityNodes" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 流程图 -->
|
||||
<el-tab-pane label="流程图" name="diagram">
|
||||
<div class="form-scroll-area">
|
||||
<ProcessInstanceSimpleViewer
|
||||
v-show="
|
||||
processDefinition.modelType && processDefinition.modelType === BpmModelType.SIMPLE
|
||||
"
|
||||
:loading="processInstanceLoading"
|
||||
:model-view="processModelView"
|
||||
/>
|
||||
<ProcessInstanceBpmnViewer
|
||||
v-show="
|
||||
processDefinition.modelType && processDefinition.modelType === BpmModelType.BPMN
|
||||
"
|
||||
:loading="processInstanceLoading"
|
||||
:model-view="processModelView"
|
||||
/>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 流转记录 -->
|
||||
<el-tab-pane label="流转记录" name="record">
|
||||
<div class="form-scroll-area">
|
||||
<el-scrollbar>
|
||||
<ProcessInstanceTaskList :loading="processInstanceLoading" :id="id" />
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 流转评论 TODO 待开发 -->
|
||||
<el-tab-pane label="流转评论" name="comment" v-if="false">
|
||||
<div class="form-scroll-area">
|
||||
<el-scrollbar> 流转评论 </el-scrollbar>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<div class="b-t-solid border-t-1px border-[var(--el-border-color)]">
|
||||
<!-- 操作栏按钮 -->
|
||||
<ProcessInstanceOperationButton
|
||||
ref="operationButtonRef"
|
||||
:process-instance="processInstance"
|
||||
:process-definition="processDefinition"
|
||||
:userOptions="userOptions"
|
||||
:normal-form="detailForm"
|
||||
:normal-form-api="fApi"
|
||||
:writable-fields="writableFields"
|
||||
@success="refresh"
|
||||
/>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 打印预览弹窗 -->
|
||||
<PrintDialog ref="printRef" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { BpmModelType, BpmModelFormType } from '@/utils/constants'
|
||||
import { setConfAndFields2 } from '@/utils/formCreate'
|
||||
import { registerComponent } from '@/utils/routerHelper'
|
||||
import type { ApiAttrs } from '@form-create/element-ui/types/config'
|
||||
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import ProcessInstanceBpmnViewer from './ProcessInstanceBpmnViewer.vue'
|
||||
import ProcessInstanceSimpleViewer from './ProcessInstanceSimpleViewer.vue'
|
||||
import ProcessInstanceTaskList from './ProcessInstanceTaskList.vue'
|
||||
import ProcessInstanceOperationButton from './ProcessInstanceOperationButton.vue'
|
||||
import ProcessInstanceTimeline from './ProcessInstanceTimeline.vue'
|
||||
import { FieldPermissionType } from '@/components/SimpleProcessDesignerV2/src/consts'
|
||||
import { TaskStatusEnum } from '@/api/bpm/task'
|
||||
import runningSvg from '@/assets/svgs/bpm/running.svg'
|
||||
import approveSvg from '@/assets/svgs/bpm/approve.svg'
|
||||
import rejectSvg from '@/assets/svgs/bpm/reject.svg'
|
||||
import cancelSvg from '@/assets/svgs/bpm/cancel.svg'
|
||||
import PrintDialog from './PrintDialog.vue'
|
||||
|
||||
defineOptions({ name: 'BpmProcessInstanceDetail' })
|
||||
const props = defineProps<{
|
||||
id: string // 流程实例的编号
|
||||
taskId?: string // 任务编号
|
||||
activityId?: string //流程活动编号,用于抄送查看
|
||||
}>()
|
||||
const message = useMessage() // 消息弹窗
|
||||
const processInstanceLoading = ref(false) // 流程实例的加载中
|
||||
const processInstance = ref<any>({}) // 流程实例
|
||||
const processDefinition = ref<any>({}) // 流程定义
|
||||
const processModelView = ref<any>({}) // 流程模型视图
|
||||
const operationButtonRef = ref() // 操作按钮组件 ref
|
||||
const auditIconsMap = {
|
||||
[TaskStatusEnum.RUNNING]: runningSvg,
|
||||
[TaskStatusEnum.APPROVE]: approveSvg,
|
||||
[TaskStatusEnum.REJECT]: rejectSvg,
|
||||
[TaskStatusEnum.CANCEL]: cancelSvg
|
||||
}
|
||||
|
||||
// ========== 申请信息 ==========
|
||||
const fApi = ref<ApiAttrs>() //
|
||||
const detailForm = ref({
|
||||
rule: [],
|
||||
option: {},
|
||||
value: {}
|
||||
}) // 流程实例的表单详情
|
||||
|
||||
const writableFields: Array<string> = [] // 表单可以编辑的字段
|
||||
|
||||
/** 获得详情 */
|
||||
const getDetail = () => {
|
||||
// 获得审批详情
|
||||
getApprovalDetail()
|
||||
// 获得流程模型视图
|
||||
getProcessModelView()
|
||||
}
|
||||
|
||||
/** 加载流程实例 */
|
||||
const BusinessFormComponent = ref<any>(null) // 异步组件
|
||||
/** 获取审批详情 */
|
||||
const activityNodes = ref<ProcessInstanceApi.ApprovalNodeInfo[]>([]) // 审批节点信息
|
||||
const getApprovalDetail = async () => {
|
||||
processInstanceLoading.value = true
|
||||
try {
|
||||
const param = {
|
||||
processInstanceId: props.id,
|
||||
activityId: props.activityId,
|
||||
taskId: props.taskId
|
||||
}
|
||||
const data = await ProcessInstanceApi.getApprovalDetail(param)
|
||||
if (!data) {
|
||||
message.error('查询不到审批详情信息!')
|
||||
return
|
||||
}
|
||||
if (!data.processDefinition || !data.processInstance) {
|
||||
message.error('查询不到流程信息!')
|
||||
return
|
||||
}
|
||||
processInstance.value = data.processInstance
|
||||
processDefinition.value = data.processDefinition
|
||||
|
||||
// 设置表单信息
|
||||
if (processDefinition.value.formType === BpmModelFormType.NORMAL) {
|
||||
// 获取表单字段权限
|
||||
const formFieldsPermission = data.formFieldsPermission
|
||||
// 清空可编辑字段为空
|
||||
writableFields.splice(0)
|
||||
if (detailForm.value.rule?.length > 0) {
|
||||
// 避免刷新 form-create 显示不了
|
||||
detailForm.value.value = processInstance.value.formVariables
|
||||
} else {
|
||||
setConfAndFields2(
|
||||
detailForm,
|
||||
processDefinition.value.formConf,
|
||||
processDefinition.value.formFields,
|
||||
processInstance.value.formVariables
|
||||
)
|
||||
}
|
||||
nextTick().then(() => {
|
||||
fApi.value?.btn.show(false)
|
||||
fApi.value?.resetBtn.show(false)
|
||||
//@ts-ignore
|
||||
fApi.value?.disabled(true)
|
||||
// 设置表单字段权限
|
||||
if (formFieldsPermission) {
|
||||
Object.keys(data.formFieldsPermission).forEach((item) => {
|
||||
setFieldPermission(item, formFieldsPermission[item])
|
||||
})
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// 注意:data.processDefinition.formCustomViewPath 是组件的全路径,例如说:/crm/contract/detail/index.vue
|
||||
BusinessFormComponent.value = registerComponent(data.processDefinition.formCustomViewPath)
|
||||
}
|
||||
|
||||
// 获取审批节点,显示 Timeline 的数据
|
||||
activityNodes.value = data.activityNodes
|
||||
|
||||
// 获取待办任务显示操作按钮
|
||||
operationButtonRef.value?.loadTodoTask(data.todoTask)
|
||||
} finally {
|
||||
processInstanceLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取流程模型视图*/
|
||||
const getProcessModelView = async () => {
|
||||
if (BpmModelType.BPMN === processDefinition.value?.modelType) {
|
||||
// 重置,解决 BPMN 流程图刷新不会重新渲染问题
|
||||
processModelView.value = {
|
||||
bpmnXml: ''
|
||||
}
|
||||
}
|
||||
const data = await ProcessInstanceApi.getProcessInstanceBpmnModelView(props.id)
|
||||
if (data) {
|
||||
processModelView.value = data
|
||||
}
|
||||
}
|
||||
|
||||
/** 设置表单权限 */
|
||||
const setFieldPermission = (field: string, permission: string) => {
|
||||
if (permission === FieldPermissionType.READ) {
|
||||
//@ts-ignore
|
||||
fApi.value?.disabled(true, field)
|
||||
}
|
||||
if (permission === FieldPermissionType.WRITE) {
|
||||
//@ts-ignore
|
||||
fApi.value?.disabled(false, field)
|
||||
// 加入可以编辑的字段
|
||||
writableFields.push(field)
|
||||
}
|
||||
if (permission === FieldPermissionType.NONE) {
|
||||
//@ts-ignore
|
||||
fApi.value?.hidden(true, field)
|
||||
}
|
||||
}
|
||||
|
||||
/** 操作成功后刷新 */
|
||||
const refresh = () => {
|
||||
// 重新获取详情
|
||||
getDetail()
|
||||
}
|
||||
|
||||
/** 处理打印 */
|
||||
const printRef = ref()
|
||||
const handlePrint = async () => {
|
||||
printRef.value.open(props.id)
|
||||
}
|
||||
|
||||
/** 当前的 Tab */
|
||||
const activeTab = ref('form')
|
||||
|
||||
/** 初始化 */
|
||||
const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||
onMounted(async () => {
|
||||
getDetail()
|
||||
// 获得用户列表
|
||||
userOptions.value = await UserApi.getSimpleUserList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$wrap-padding-height: 20px;
|
||||
$wrap-margin-height: 15px;
|
||||
$button-height: 51px;
|
||||
$process-header-height: 194px;
|
||||
|
||||
.processInstance-wrap-main {
|
||||
height: calc(
|
||||
100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px
|
||||
);
|
||||
max-height: calc(
|
||||
100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px
|
||||
);
|
||||
overflow: auto;
|
||||
|
||||
.form-scroll-area {
|
||||
display: flex;
|
||||
height: calc(
|
||||
100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px -
|
||||
$process-header-height - 40px
|
||||
);
|
||||
max-height: calc(
|
||||
100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px -
|
||||
$process-header-height - 40px
|
||||
);
|
||||
overflow: auto;
|
||||
flex-direction: column;
|
||||
|
||||
:deep(.box-card) {
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
|
||||
.el-card__body {
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-box {
|
||||
:deep(.el-card) {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
338
src/views/bpm/processInstance/index.vue
Normal file
338
src/views/bpm/processInstance/index.vue
Normal file
@@ -0,0 +1,338 @@
|
||||
<template>
|
||||
<doc-alert title="流程发起、取消、重新发起" url="https://doc.iocoder.cn/bpm/process-instance/" />
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="请输入流程名称"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="" prop="category" class="absolute right-[300px]">
|
||||
<el-select
|
||||
v-model="queryParams.category"
|
||||
placeholder="请选择流程分类"
|
||||
clearable
|
||||
class="!w-155px"
|
||||
@change="handleQuery"
|
||||
>
|
||||
<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="" prop="status" class="absolute right-[130px]">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
placeholder="请选择流程状态"
|
||||
clearable
|
||||
class="!w-155px"
|
||||
@change="handleQuery"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 高级筛选 -->
|
||||
<el-form-item class="absolute right-0">
|
||||
<el-popover
|
||||
:visible="showPopover"
|
||||
persistent
|
||||
:width="400"
|
||||
:show-arrow="false"
|
||||
placement="bottom-end"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button @click="showPopover = !showPopover">
|
||||
<Icon icon="ep:plus" class="mr-5px" />高级筛选
|
||||
</el-button>
|
||||
</template>
|
||||
<el-form-item
|
||||
label="所属流程"
|
||||
class="font-bold"
|
||||
label-position="top"
|
||||
prop="processDefinitionKey"
|
||||
>
|
||||
<el-select
|
||||
v-model="queryParams.processDefinitionKey"
|
||||
placeholder="请选择流程定义"
|
||||
clearable
|
||||
class="!w-390px"
|
||||
@change="handleQuery"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in processDefinitionList"
|
||||
:key="item.key"
|
||||
:label="item.name"
|
||||
:value="item.key"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="发起时间" class="font-bold" label-position="top" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="daterange"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item class="font-bold" label-position="top">
|
||||
<div class="flex justify-end w-full">
|
||||
<el-button @click="resetQuery">清空</el-button>
|
||||
<el-button @click="showPopover = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleQuery">确认</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-popover>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="流程名称" align="center" prop="name" min-width="200px" fixed="left" />
|
||||
<el-table-column label="摘要" prop="summary" width="180" fixed="left">
|
||||
<template #default="scope">
|
||||
<div class="flex flex-col" v-if="scope.row.summary && scope.row.summary.length > 0">
|
||||
<div v-for="(item, index) in scope.row.summary" :key="index">
|
||||
<el-text type="info"> {{ item.key }} : {{ item.value }} </el-text>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="流程分类"
|
||||
align="center"
|
||||
prop="categoryName"
|
||||
min-width="100"
|
||||
fixed="left"
|
||||
/>
|
||||
<el-table-column label="流程状态" prop="status" min-width="200">
|
||||
<template #default="scope">
|
||||
<!-- 审批中状态 -->
|
||||
<template
|
||||
v-if="
|
||||
scope.row.status === BpmProcessInstanceStatus.RUNNING && scope.row.tasks?.length > 0
|
||||
"
|
||||
>
|
||||
<!-- 单人审批 -->
|
||||
<template v-if="scope.row.tasks.length === 1">
|
||||
<span>
|
||||
<el-button link type="primary" @click="handleDetail(scope.row)">
|
||||
{{ scope.row.tasks[0].assigneeUser?.nickname }}
|
||||
</el-button>
|
||||
({{ scope.row.tasks[0].name }}) 审批中
|
||||
</span>
|
||||
</template>
|
||||
<!-- 多人审批 -->
|
||||
<template v-else>
|
||||
<span>
|
||||
<el-button link type="primary" @click="handleDetail(scope.row)">
|
||||
{{ scope.row.tasks[0].assigneeUser?.nickname }}
|
||||
</el-button>
|
||||
等 {{ scope.row.tasks.length }} 人 ({{ scope.row.tasks[0].name }})审批中
|
||||
</span>
|
||||
</template>
|
||||
</template>
|
||||
<!-- 非审批中状态 -->
|
||||
<template v-else>
|
||||
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="发起时间"
|
||||
align="center"
|
||||
prop="startTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column
|
||||
label="结束时间"
|
||||
align="center"
|
||||
prop="endTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column label="操作" align="center" fixed="right" width="180">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['bpm:process-instance:cancel']"
|
||||
@click="handleDetail(scope.row)"
|
||||
>
|
||||
详情
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-if="scope.row.status === 1"
|
||||
v-hasPermi="['bpm:process-instance:query']"
|
||||
@click="handleCancel(scope.row)"
|
||||
>
|
||||
取消
|
||||
</el-button>
|
||||
<el-button link type="primary" v-else @click="handleCreate(scope.row)">
|
||||
重新发起
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
|
||||
import { CategoryApi, CategoryVO } from '@/api/bpm/category'
|
||||
import { ProcessInstanceVO } from '@/api/bpm/processInstance'
|
||||
import * as DefinitionApi from '@/api/bpm/definition'
|
||||
import { BpmProcessInstanceStatus } from '@/utils/constants'
|
||||
|
||||
defineOptions({ name: 'BpmProcessInstanceMy' })
|
||||
|
||||
const router = useRouter() // 路由
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const processDefinitionList = ref<any[]>([]) // 流程定义列表
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: '',
|
||||
processDefinitionKey: undefined,
|
||||
category: undefined,
|
||||
status: undefined,
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const categoryList = ref<CategoryVO[]>([]) // 流程分类列表
|
||||
const showPopover = ref(false) // 高级筛选是否展示
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ProcessInstanceApi.getProcessInstanceMyPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 发起流程操作 **/
|
||||
const handleCreate = async (row?: ProcessInstanceVO) => {
|
||||
if (row?.id) {
|
||||
const processDefinitionDetail = await DefinitionApi.getProcessDefinition(
|
||||
row.processDefinitionId
|
||||
)
|
||||
// 如果是【业务表单】,跳转到对应的发起界面
|
||||
if (processDefinitionDetail.formType === 20) {
|
||||
await router.push({
|
||||
path: processDefinitionDetail.formCustomCreatePath,
|
||||
query: {
|
||||
id: row.businessKey
|
||||
}
|
||||
})
|
||||
} else if (processDefinitionDetail.formType === 10) {
|
||||
//如果是【流程表单】,跳转到流程发起界面
|
||||
await router.push({
|
||||
name: 'BpmProcessInstanceCreate',
|
||||
query: { processInstanceId: row.id }
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 查看详情 */
|
||||
const handleDetail = (row: ProcessInstanceVO) => {
|
||||
router.push({
|
||||
name: 'BpmProcessInstanceDetail',
|
||||
query: {
|
||||
id: row.id
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 取消按钮操作 */
|
||||
const handleCancel = async (row: ProcessInstanceVO) => {
|
||||
// 二次确认
|
||||
const { value } = await ElMessageBox.prompt('请输入取消原因', '取消流程', {
|
||||
confirmButtonText: t('common.ok'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
inputPattern: /^[\s\S]*.*\S[\s\S]*$/, // 判断非空,且非空格
|
||||
inputErrorMessage: '取消原因不能为空'
|
||||
})
|
||||
// 发起取消
|
||||
await ProcessInstanceApi.cancelProcessInstanceByStartUser(row.id, value)
|
||||
message.success('取消成功')
|
||||
// 刷新列表
|
||||
await getList()
|
||||
}
|
||||
|
||||
/** 激活时 **/
|
||||
onActivated(() => {
|
||||
getList()
|
||||
})
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getList()
|
||||
categoryList.value = await CategoryApi.getCategorySimpleList()
|
||||
// 获取流程定义列表
|
||||
processDefinitionList.value = await DefinitionApi.getSimpleProcessDefinitionList()
|
||||
})
|
||||
</script>
|
||||
259
src/views/bpm/processInstance/manager/index.vue
Normal file
259
src/views/bpm/processInstance/manager/index.vue
Normal file
@@ -0,0 +1,259 @@
|
||||
<template>
|
||||
<doc-alert title="工作流手册" url="https://doc.iocoder.cn/bpm/" />
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="发起人" prop="startUserId">
|
||||
<el-select v-model="queryParams.startUserId" placeholder="请选择发起人" class="!w-240px">
|
||||
<el-option
|
||||
v-for="user in userList"
|
||||
:key="user.id"
|
||||
:label="user.nickname"
|
||||
:value="user.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程名称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="请输入流程名称"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="所属流程" prop="processDefinitionId">
|
||||
<el-input
|
||||
v-model="queryParams.processDefinitionId"
|
||||
placeholder="请输入流程定义的编号"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程分类" prop="category">
|
||||
<el-select
|
||||
v-model="queryParams.category"
|
||||
placeholder="请选择流程分类"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<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="流程状态" prop="status">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
placeholder="请选择流程状态"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="发起时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="daterange"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="流程名称" align="center" prop="name" min-width="200px" fixed="left" />
|
||||
<el-table-column
|
||||
label="流程分类"
|
||||
align="center"
|
||||
prop="categoryName"
|
||||
min-width="100"
|
||||
fixed="left"
|
||||
/>
|
||||
<el-table-column label="流程发起人" align="center" prop="startUser.nickname" width="120" />
|
||||
<el-table-column label="发起部门" align="center" prop="startUser.deptName" width="120" />
|
||||
<el-table-column label="流程状态" prop="status" width="120">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="发起时间"
|
||||
align="center"
|
||||
prop="startTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column
|
||||
label="结束时间"
|
||||
align="center"
|
||||
prop="endTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column align="center" label="耗时" prop="durationInMillis" width="169">
|
||||
<template #default="scope">
|
||||
{{ scope.row.durationInMillis > 0 ? formatPast2(scope.row.durationInMillis) : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="当前审批任务" align="center" prop="tasks" min-width="120px">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" v-for="task in scope.row.tasks" :key="task.id" link>
|
||||
<span>{{ task.name }}</span>
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="流程编号" align="center" prop="id" min-width="320px" />
|
||||
<el-table-column label="操作" align="center" fixed="right" width="180">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['bpm:process-instance:cancel']"
|
||||
@click="handleDetail(scope.row)"
|
||||
>
|
||||
详情
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-if="scope.row.status === 1"
|
||||
v-hasPermi="['bpm:process-instance:query']"
|
||||
@click="handleCancel(scope.row)"
|
||||
>
|
||||
取消
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter, formatPast2 } from '@/utils/formatTime'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
|
||||
import { CategoryApi } from '@/api/bpm/category'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import { cancelProcessInstanceByAdmin } from '@/api/bpm/processInstance'
|
||||
|
||||
// 它和【我的流程】的差异是,该菜单可以看全部的流程实例
|
||||
defineOptions({ name: 'BpmProcessInstanceManager' })
|
||||
|
||||
const router = useRouter() // 路由
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
startUserId: undefined,
|
||||
name: '',
|
||||
processDefinitionId: undefined,
|
||||
category: undefined,
|
||||
status: undefined,
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const categoryList = ref([]) // 流程分类列表
|
||||
const userList = ref<any[]>([]) // 用户列表
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ProcessInstanceApi.getProcessInstanceManagerPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 查看详情 */
|
||||
const handleDetail = (row) => {
|
||||
router.push({
|
||||
name: 'BpmProcessInstanceDetail',
|
||||
query: {
|
||||
id: row.id
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 取消按钮操作 */
|
||||
const handleCancel = async (row) => {
|
||||
// 二次确认
|
||||
const { value } = await ElMessageBox.prompt('请输入取消原因', '取消流程', {
|
||||
confirmButtonText: t('common.ok'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
inputPattern: /^[\s\S]*.*\S[\s\S]*$/, // 判断非空,且非空格
|
||||
inputErrorMessage: '取消原因不能为空'
|
||||
})
|
||||
// 发起取消
|
||||
await ProcessInstanceApi.cancelProcessInstanceByAdmin(row.id, value)
|
||||
message.success('取消成功')
|
||||
// 刷新列表
|
||||
await getList()
|
||||
}
|
||||
|
||||
/** 激活时 **/
|
||||
onActivated(() => {
|
||||
getList()
|
||||
})
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getList()
|
||||
categoryList.value = await CategoryApi.getCategorySimpleList()
|
||||
userList.value = await UserApi.getSimpleUserList()
|
||||
})
|
||||
</script>
|
||||
274
src/views/bpm/processInstance/report/index.vue
Normal file
274
src/views/bpm/processInstance/report/index.vue
Normal file
@@ -0,0 +1,274 @@
|
||||
<template>
|
||||
<doc-alert title="工作流手册" url="https://doc.iocoder.cn/bpm/" />
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="发起人" prop="startUserId">
|
||||
<el-select v-model="queryParams.startUserId" placeholder="请选择发起人" class="!w-240px">
|
||||
<el-option
|
||||
v-for="user in userList"
|
||||
:key="user.id"
|
||||
:label="user.nickname"
|
||||
:value="user.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程名称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="请输入流程名称"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程状态" prop="status">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
placeholder="请选择流程状态"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="发起时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="daterange"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="结束时间" prop="endTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.endTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="daterange"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-for="(item, index) in formFields"
|
||||
:key="index"
|
||||
:label="item.title"
|
||||
:prop="item.field"
|
||||
>
|
||||
<!-- TODO @lesan:目前只支持input类型的字符串搜索 -->
|
||||
<el-input
|
||||
:disabled="item.type !== 'input'"
|
||||
v-model="queryParams.formFieldsParams[item.field]"
|
||||
:placeholder="`请输入${item.title}`"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" border :data="list">
|
||||
<el-table-column label="流程名称" align="center" prop="name" fixed="left" width="200" />
|
||||
<el-table-column label="流程发起人" align="center" prop="startUser.nickname" width="120" />
|
||||
<el-table-column label="流程状态" prop="status" width="120">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="发起时间"
|
||||
align="center"
|
||||
prop="startTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column
|
||||
label="结束时间"
|
||||
align="center"
|
||||
prop="endTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column
|
||||
v-for="(item, index) in formFields"
|
||||
:key="index"
|
||||
:label="item.title"
|
||||
:prop="item.field"
|
||||
width="120"
|
||||
>
|
||||
<!-- TODO @lesan:可以根据formField的type进行展示方式的控制,现在全部以字符串 -->
|
||||
<template #default="scope">
|
||||
{{ scope.row.formVariables[item.field] ?? '' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" fixed="right" width="180">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['bpm:process-instance:cancel']"
|
||||
@click="handleDetail(scope.row)"
|
||||
>
|
||||
详情
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-if="scope.row.status === 1"
|
||||
v-hasPermi="['bpm:process-instance:query']"
|
||||
@click="handleCancel(scope.row)"
|
||||
>
|
||||
取消
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import * as DefinitionApi from '@/api/bpm/definition'
|
||||
import { parseFormFields } from '@/components/FormCreate/src/utils'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
|
||||
defineOptions({ name: 'BpmProcessInstanceReport' })
|
||||
|
||||
const router = useRouter() // 路由
|
||||
const { query } = useRoute()
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const formFields = ref()
|
||||
const processDefinitionId = query.processDefinitionId as string
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
startUserId: undefined,
|
||||
name: '',
|
||||
processDefinitionKey: query.processDefinitionKey,
|
||||
status: undefined,
|
||||
createTime: [],
|
||||
endTime: [],
|
||||
formFieldsParams: {}
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const userList = ref<any[]>([]) // 用户列表
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ProcessInstanceApi.getProcessInstanceManagerPage({
|
||||
...queryParams,
|
||||
formFieldsParams: JSON.stringify(queryParams.formFieldsParams)
|
||||
})
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取流程定义 */
|
||||
const getProcessDefinition = async () => {
|
||||
const processDefinition = await DefinitionApi.getProcessDefinition(processDefinitionId)
|
||||
formFields.value = parseFormCreateFields(processDefinition.formFields)
|
||||
}
|
||||
|
||||
/** 解析表单字段 */
|
||||
const parseFormCreateFields = (formFields?: string[]) => {
|
||||
const result: Array<Record<string, any>> = []
|
||||
if (formFields) {
|
||||
formFields.forEach((fieldStr: string) => {
|
||||
parseFormFields(JSON.parse(fieldStr), result)
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
queryParams.formFieldsParams = {}
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 查看详情 */
|
||||
const handleDetail = (row) => {
|
||||
router.push({
|
||||
name: 'BpmProcessInstanceDetail',
|
||||
query: {
|
||||
id: row.id
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 取消按钮操作 */
|
||||
const handleCancel = async (row) => {
|
||||
// 二次确认
|
||||
const { value } = await ElMessageBox.prompt('请输入取消原因', '取消流程', {
|
||||
confirmButtonText: t('common.ok'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
inputPattern: /^[\s\S]*.*\S[\s\S]*$/, // 判断非空,且非空格
|
||||
inputErrorMessage: '取消原因不能为空'
|
||||
})
|
||||
// 发起取消
|
||||
await ProcessInstanceApi.cancelProcessInstanceByAdmin(row.id, value)
|
||||
message.success('取消成功')
|
||||
// 刷新列表
|
||||
await getList()
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
// 获取流程定义,用于 table column 的展示
|
||||
await getProcessDefinition()
|
||||
// 获取流程列表
|
||||
await getList()
|
||||
// 获取用户列表
|
||||
userList.value = await UserApi.getSimpleUserList()
|
||||
})
|
||||
</script>
|
||||
Reference in New Issue
Block a user