123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695 |
- <template>
- <div class="flow-detail-content">
- <div class="flow-detail-container">
- <!-- 值为空 -->
- <div v-if="!currentTaskRow.instanceId" class="flow-empty-detail-box">
- <el-empty description="暂无数据" />
- </div>
- <!-- 值不为空 -->
- <template v-if="currentTaskRow.instanceId">
- <!-- 1、头部信息 -->
- <div class="flow-status-stamp">
- <div class="flow-stamp-container">
- <FlowStatusStamp :status="currentTaskRow.instanceState" />
- </div>
- </div>
- <div class="flow-header-box">
- <div class="flow-no">编号:{{ currentTaskRow.instanceId }}</div>
- <div class="action-area">
- <div class="action-item"></div>
- </div>
- </div>
- <!-- 2、内容体 -->
- <div class="flow-detail-box">
- <!--头部-->
- <div class="header-box">
- <div class="summary-info">
- <div class="title">{{ currentTaskRow.processName }}</div>
- <FlowStatusTag :status="currentTaskRow.instanceState" />
- </div>
- <div class="initiator-info">
- <FlowNodeAvatar :name="currentTaskRow.createBy" />
- <div class="begin-time">{{ currentTaskRow.createTime }} 提交</div>
- </div>
- </div>
- <el-tabs v-model="tabName">
- <el-tab-pane v-for="v of tabList" :key="v.value" :label="v.label" :name="v.value" />
- </el-tabs>
- <!-- 审批信息 -->
- <div v-show="tabName === 'ApprovalInfo'" class="scroll-wrap">
- <!-- 表单 -->
- <div v-loading="validateForm.loading" class="form-wrap self-Everright-formEditor">
- <er-form-preview
- v-show="validateForm.rule.length"
- ref="EReditorRef"
- :file-upload-u-r-i="uploadFileApi"
- :is-show-complete-button="false"
- />
- <LeNoData v-if="!validateForm.rule.length" message="表单无数据" />
- </div>
- <div class="area-divider"></div>
- <!--审批流-->
- <el-timeline style="margin-left: 50px">
- <el-timeline-item v-for="active in activeData" :key="active.id" hollow :timestamp="active.local_timestamp">
- <template #dot>
- <FlowTypeDot :status="active.id ? 0 : 1" :type="active.type" :name="active.createBy" />
- </template>
- <div v-show="active.type === 0" class="timeline-box flex-1">
- <span style="color: #86909c; display: block; margin-bottom: 3px; padding-left: 4px">评论</span>
- <div class="flex flex-align-center">
- <div class="timeline-box-user flex-1">
- <span style="padding-left: 4px">{{ active.createBy }}</span>
- <div v-if="active.local_content" class="comment">
- <div class="comment-content">{{ active.local_content }}</div>
- </div>
- </div>
- <span class="timeline-box-date">{{ formatTimestamp(active.finishTime) }}</span>
- </div>
- </div>
- <div v-show="active.type !== 0" class="timeline-box flex-1">
- <span style="display: block" class="pl-1.5 mb-0">{{ active.taskName }}</span>
- <div class="flex flex-align-center">
- <div class="timeline-box-user flex-1">
- <span v-if="active.id" class="text-gray-500 pl-1.5">
- {{ active.createBy }}
- </span>
- <span v-if="active.type === 22">
- 发起的子流程<el-tooltip content="点击查看详情">
- <a class="el-button el-button--primary is-link" style="padding-top: 0" @click="lookSubProcess(active)">
- ({{ active.content.callProcess.split(':')[1] }})
- </a>
- </el-tooltip>
- </span>
- <div style="display: flex; gap: 6px; margin-top: 3px">
- <FlowNodeAvatar v-for="nodeUser in active.local_nodeUserList" :key="nodeUser.id" :name="nodeUser.name" />
- <FlowNodeAvatar v-for="nodeRole in active.local_nodeRoleList" :key="nodeRole.id" :name="nodeRole.name">
- <template #avatar>
- <svg-icon icon-class="flow-group" color="#fff" />
- </template>
- </FlowNodeAvatar>
- </div>
- <div v-if="active.local_content" class="comment">
- <div class="comment-content">{{ active.local_content }}</div>
- </div>
- </div>
- <span class="timeline-box-date">{{ formatTimestamp(active.finishTime) }}</span>
- </div>
- </div>
- </el-timeline-item>
- </el-timeline>
- </div>
- <!-- 流程图 -->
- <div v-show="tabName === 'FlowChart'" class="scroll-wrap">
- <div class="tags-desc">
- <!--0: 已执行 1正在执行-->
- <el-tag style="margin-left: 4px" type="success" size="small">已执行</el-tag>
- <el-tag style="margin-left: 4px" type="warning" size="small">执行中</el-tag>
- <el-tag style="margin-left: 4px" type="info" size="small">未执行</el-tag>
- </div>
- <ScWorkflow style="overflow-x: auto" :model-value="modelContentConfig" disabled />
- </div>
- </div>
- <!-- 3、底部操作按钮 审批拒绝 强制终止不展示操作按钮-->
- <!--
- 1、已审批的任务不显示操作按钮
- 2、我的申请显示撤回按钮
- 3、认领任务显示认领按钮
- 4、我收到的任务显示评论
- -->
- <div v-if="currentTaskRow.instanceState === 0" class="flow-actions">
- <el-button :icon="ChatLineSquare" @click="openComment('reviewVisible', 'review')">评论</el-button>
- <template v-if="currentTaskType !== 'myReceived' || currentTaskType !== 'approved'">
- <el-button
- v-if="currentTaskType === 'pendingApproval'"
- :icon="Check"
- type="primary"
- @click="openComment('consentOrRefuseVisible', 'agree')"
- >同意</el-button
- >
- <el-button
- v-if="currentTaskType === 'pendingApproval' && allowReject"
- :icon="Close"
- type="danger"
- @click="openComment('consentOrRefuseVisible', 'reject')"
- >拒绝</el-button
- >
- <el-button
- v-if="currentTaskType === 'myApplication' && allowRevocation"
- :icon="Close"
- type="danger"
- @click="openComment('reviewVisible', 'revoke')"
- >撤回</el-button
- >
- <el-button v-if="currentTaskType === 'pendingClaim'" :icon="Close" @click="claimTaskEv">认领</el-button>
- <el-dropdown
- v-if="currentTaskType === 'pendingApproval' && (allowTransfer || allowRollback || allowAppendNode)"
- style="margin-left: 12px"
- >
- <el-button :icon="More">更多</el-button>
- <template #dropdown>
- <el-dropdown-menu>
- <el-dropdown-item v-if="allowTransfer" @click.native="openComment('deliverToReviewVisible')">
- <el-icon><DArrowLeft /></el-icon>
- 转交
- </el-dropdown-item>
- <el-dropdown-item v-if="allowRollback" @click.native="openComment('rollbackVisible')">
- <el-icon><Switch /></el-icon>
- 回退
- </el-dropdown-item>
- <el-dropdown-item v-if="allowAppendNode" @click.native="openComment('addSignVisible')">
- <el-icon><Plus /></el-icon>
- 加签
- </el-dropdown-item>
- <el-dropdown-item v-if="false" @click.native="openComment('loseSignVisible')">
- <el-icon><Minus /></el-icon>
- 减签
- </el-dropdown-item>
- </el-dropdown-menu>
- </template>
- </el-dropdown>
- </template>
- </div>
- </template>
- </div>
- <!-- 评论弹窗-->
- <review-dialog
- v-if="reviewVisible"
- v-model="reviewVisible"
- :instance-id="currentTaskRow.instanceId"
- :task-id="taskId"
- :current-type="currentType"
- @success-cb="closeDetailEv"
- ></review-dialog>
- <!-- 加签弹窗 -->
- <add-sign-dialog v-if="addSignVisible" v-model="addSignVisible" :task-id="taskId" @success-cb="closeDetailEv"></add-sign-dialog>
- <!-- 同意或拒绝弹窗 -->
- <consent-or-refuse-dialog
- v-if="consentOrRefuseVisible"
- v-model="consentOrRefuseVisible"
- :task-id="taskId"
- :current-type="currentType"
- :form-data="currentFormData"
- @success-cb="closeDetailEv"
- ></consent-or-refuse-dialog>
- <!-- 转交审批弹窗 -->
- <deliver-to-review-dialog
- v-if="deliverToReviewVisible"
- v-model="deliverToReviewVisible"
- :task-id="taskId"
- @success-cb="closeDetailEv"
- ></deliver-to-review-dialog>
- <!-- 减签弹窗 -->
- <lose-sign-dialog v-if="loseSignVisible" v-model="loseSignVisible" :task-id="taskId" @success-cb="closeDetailEv"></lose-sign-dialog>
- <!-- 回退弹窗 -->
- <rollback-dialog v-if="rollbackVisible" v-model="rollbackVisible" :task-id="taskId" @success-cb="closeDetailEv"></rollback-dialog>
- <!-- 查看子流程表单 -->
- <ViewProcessDialog v-if="processInfo.visible" v-model="processInfo.visible" :processInfo="processInfo" />
- </div>
- </template>
- <script setup>
- import useTaskProcessStore from '@/store/modules/taskProcess'
- import { computed, ref, nextTick, watch } from 'vue'
- import FlowStatusStamp from '@/components/Flow/FlowStatusStamp.vue'
- import FlowStatusTag from '@/components/Flow/FlowStatusTag.vue'
- import FlowNodeAvatar from '@/components/Flow/FlowNodeAvatar.vue'
- import FlowTypeDot from '@/components/Flow/FlowTypeDot.vue'
- import { ChatLineSquare, Check, Close, Switch, DArrowLeft, Plus, Minus, More } from '@element-plus/icons-vue'
- import { processTaskApprovalInfo, processClaimTaskApi } from '@/api/flow/processTask'
- import { formatTimestamp } from '@/utils/datetime'
- import ReviewDialog from './reviewDialog'
- import AddSignDialog from './addSignDialog'
- import ConsentOrRefuseDialog from './consentOrRefuseDialog'
- import DeliverToReviewDialog from './deliverToReviewDialog'
- import LoseSignDialog from './loseSignDialog'
- import RollbackDialog from './rollbackDialog'
- import ViewProcessDialog from './ViewProcessDialog.vue'
- import { storeToRefs } from 'pinia'
- import { ElMessage, ElMessageBox } from 'element-plus'
- import { erFormPreview } from '@ER/formEditor'
- const { VITE_APP_BASE_API } = import.meta.env
- const uploadFileApi = ref(`${VITE_APP_BASE_API}/v1/oss/upload`)
- import { package_modelContentConfig } from './config.ts'
- const props = defineProps({
- /**
- * pendingApproval 待审批
- * myApplication 我的申请
- * myReceived 我收到的
- * pendingClaim 认领任务
- * approved 已审批
- */
- currentTaskType: {
- type: String,
- default: ''
- }
- })
- const tabName = ref('ApprovalInfo')
- const tabList = computed(() => {
- const list = [
- {
- label: '审批信息',
- value: 'ApprovalInfo'
- },
- {
- label: '流程图',
- value: 'FlowChart'
- }
- ]
- /*// 待审批 去掉流程图 todo?
- if (props.currentTaskType === 'pendingApproval') {
- list.splice(1, 1)
- }*/
- return list
- })
- // store值
- const taskProcessInfo = useTaskProcessStore()
- const { currentTaskRow } = storeToRefs(taskProcessInfo)
- // 各种操作弹窗显示隐藏 start
- const reviewVisible = ref(false)
- const addSignVisible = ref(false)
- const consentOrRefuseVisible = ref(false)
- const deliverToReviewVisible = ref(false)
- const loseSignVisible = ref(false)
- const rollbackVisible = ref(false)
- const allowReject = ref(false)
- const allowRevocation = ref(false)
- // 各种操作弹窗显示隐藏 end
- const activeData = ref([])
- const currentType = ref(null)
- const currentFormData = ref({})
- // 当前form 表单数据字符串
- let cur_processForm_str = ''
- const EReditorRef = ref()
- const taskId = computed(() => {
- return currentTaskRow.value.taskId || ''
- })
- const validateForm = ref({
- rule: [],
- loading: false
- })
- // 允许转交
- const allowTransfer = ref(true)
- // 允许加减签
- const allowAppendNode = ref(true)
- // 允许回退
- const allowRollback = ref(true)
- const modelContentConfig = ref({})
- /**
- * 详情按钮各个操作弹窗
- * @param visibleType 评论 拒绝 同意等
- */
- const openComment = async (visibleType, item) => {
- switch (visibleType) {
- case 'reviewVisible':
- currentType.value = item
- reviewVisible.value = !reviewVisible.value
- break
- case 'addSignVisible':
- addSignVisible.value = !addSignVisible.value
- break
- case 'consentOrRefuseVisible':
- currentFormData.value = {}
- // 点击同意
- let bool = true
- if (item === 'agree') {
- const form = EReditorRef.value.getSelfFormRef()
- bool = await form.validate(valid => {
- if (valid) {
- // 表单验证通过 进行保存
- const formData = EReditorRef.value.getData()
- let processForm = JSON.parse(cur_processForm_str)
- processForm = { ...processForm, formData }
- currentFormData.value = { processForm: JSON.stringify(processForm) }
- console.log('验证通过')
- }
- })
- // const api = validateForm.value.api
- // bool = await api.validate((valid, fail) => {
- // if (valid) {
- // // 表单验证通过
- // const values = api.formData()
- // const processForm = JSON.parse(cur_processForm_str)
- // processForm.forEach(v => {
- // // 填写的数据存储(local_: 本地数据处理标识)
- // v.local_value = values[v.field]
- // })
- // console.warn(processForm, 'processForm')
- // // 流程表单JSON内容 & local_value 保存
- // currentFormData.value = { processForm: JSON.stringify(processForm) }
- // }
- // })
- }
- if (!bool) return
- currentType.value = item
- consentOrRefuseVisible.value = !consentOrRefuseVisible.value
- break
- case 'deliverToReviewVisible':
- deliverToReviewVisible.value = !deliverToReviewVisible.value
- break
- case 'loseSignVisible':
- loseSignVisible.value = !loseSignVisible.value
- break
- case 'rollbackVisible':
- rollbackVisible.value = !rollbackVisible.value
- break
- }
- }
- /**
- * 获取taskId对应的详情
- */
- const getTaskDetail = () => {
- const cur = currentTaskRow.value || {}
- // 提交的表单 数据展示
- validateForm.value.loading = true
- processTaskApprovalInfo({
- taskId: cur.taskId,
- instanceId: cur.instanceId,
- instanceState: cur.instanceState
- })
- .then(data => {
- const activeList = data.processApprovals
- activeList.forEach(v => {
- v.local_timestamp = v.id && formatTimestamp(v.createTime)
- const _content = v.content
- v.local_nodeUserList = _content?.nodeUserList || []
- v.local_nodeRoleList = _content?.nodeRoleList || []
- v.local_content = _content?.opinion
- })
- activeData.value = activeList
- // todo 获取当前的操作权限,进行赋值
- try {
- const modelContent = JSON.parse(data.modelContent || '{}')
- const modelContent_config = modelContent.nodeConfig // ?? modelContent.childNode
- if (modelContent_config) {
- package_modelContentConfig(modelContent_config, data.renderNodes || {})
- }
- modelContentConfig.value = modelContent_config || {}
- window.modelContentConfig = modelContentConfig
- if (props.currentTaskType === 'pendingApproval') {
- allowTransfer.value = data?.allowTransfer
- allowAppendNode.value = data?.allowAppendNode
- allowRollback.value = data?.allowRollback
- }
- if (data?.taskType !== 0) {
- allowReject.value = true
- }
- if (props.currentTaskType === 'myApplication') {
- allowRevocation.value = data.processSetting.allowRevocation
- }
- /*descItemsData.value.list = JSON.parse(data.formContent).map(item => {
- const showLabel = item.title
- let showValue = item.local_value
- const options = item.options
- if (Array.isArray(options) && showValue !== undefined) {
- if (Array.isArray(showValue)) {
- showValue = showValue.reduce(val => {
- const cur = options.find(option => option.value === val)
- return cur?.label || val
- }, [])
- } else {
- const cur = options.find(option => option.value === showValue)
- showValue = cur?.label || showValue
- }
- }
- return {
- showLabel,
- showValue
- }
- })*/
- cur_processForm_str = data.formContent
- const { formStructure, formData } = JSON.parse(cur_processForm_str)
- const formConfig = data.formConfig || []
- let newFormStructure = formStructure // 存储一份
- let newFields = newFormStructure.fields
- let newFieldsList = newFormStructure.list
- // pendingApproval, 除了[待审批]这个状态, 其余只能读取
- if (props.currentTaskType !== 'pendingApproval') {
- newFields.map(item1 => (item1.options.disabled = true))
- } else {
- // data.formConfig
- newFields.forEach(item => {
- const matchingFormItem = formConfig.find(formItem => formItem.id == item.id)
- // 如果找到匹配的对象,且opera为'2',则删除newFields中的对象
- if (matchingFormItem && matchingFormItem.opera === '2') {
- newFields = newFields.filter(t => t.id !== item.id)
- } else if (matchingFormItem) {
- // 否则,将opera属性添加到newFields中的对象
- item.opera = matchingFormItem.opera
- // '0': 只读 '1': 编辑 '2': 隐藏
- item.options.disabled = ['0', '1'].indexOf(matchingFormItem.opera) > -1 ? !Number(matchingFormItem.opera) : false
- }
- })
- newFieldsList.forEach(item => {
- const matchingFormItem = formConfig.find(formItem => formItem.id == item.id)
- // 如果找到匹配的对象,且opera为'2',则删除newFields中的对象
- if (matchingFormItem && matchingFormItem.opera === '2') {
- newFieldsList = newFieldsList.filter(t => t.id !== item.id)
- }
- })
- }
- newFormStructure = {
- ...newFormStructure,
- fields: newFields,
- list: newFieldsList
- }
- EReditorRef.value.setData(newFormStructure, formData)
- validateForm.value.rule = newFields
- } catch (e) {
- console.error('解析 descItems 数据出现问题', e)
- // descItemsData.value.list = []
- validateForm.value.rule = []
- }
- })
- .finally(() => {
- validateForm.value.loading = false
- })
- }
- const processInfo = ref({ name: '', id: '', instanceId: '', visible: false })
- const lookSubProcess = item => {
- const [id, name, instanceId] = item.content.callProcess.split(':')
- processInfo.value = {
- name,
- id,
- instanceId,
- visible: true
- }
- }
- /**
- * 详情页面操作按钮回调
- */
- const closeDetailEv = () => {
- ElMessage({
- message: '操作成功',
- type: 'success'
- })
- setTimeout(() => {
- if (currentType.value === 'review') {
- // 评论按钮,不要刷新左侧的列表,只更新右侧的详情即可
- getTaskDetail()
- return
- }
- taskProcessInfo.refresh = true
- taskProcessInfo.setCurrentTaskRow({})
- }, 1000)
- }
- /** 认领任务 **/
- const claimTaskEv = async () => {
- ElMessageBox.confirm('确定认领当前审批流程?', '提示', {
- confirmButtonText: '确认',
- cancelButtonText: '取消',
- type: 'warning',
- buttonSize: 'default'
- })
- .then(async () => {
- const data = await processClaimTaskApi(currentTaskRow.value.taskId)
- if (!data) return
- closeDetailEv()
- })
- .catch(() => {
- console.log('取消')
- })
- }
- /**
- * 监听同级子组件的instanceId的值变化 这里可能也有实例Id
- * 1、监听instanceId的值变化,如果值有变化,则重新获取审批详情
- * 2、如果instanceId没有值,则不请求接口,暂时暂无数据img
- */
- watch(
- () => currentTaskRow.value.instanceId,
- (nValue, oValue) => {
- if (!nValue) return
- nextTick(() => {
- getTaskDetail()
- })
- }
- )
- </script>
- <style scoped lang="scss">
- .flow-detail-content {
- height: 100%;
- overflow: hidden;
- background: var(--el-bg-color);
- border-radius: 6px;
- flex: auto;
- .flow-detail-container {
- display: flex;
- flex-direction: column;
- position: relative;
- height: 100%;
- overflow: hidden;
- }
- // 通过、不通过样式
- .flow-status-stamp {
- position: absolute;
- right: 10px;
- top: 10px;
- z-index: 99;
- }
- // 头部
- .flow-header-box {
- font-weight: 400;
- font-size: 13px;
- border-bottom: 1px solid var(--el-border-color);
- padding: 0 20px;
- height: 39px;
- display: flex;
- align-items: center;
- justify-content: space-between;
- color: var(--el-color-info);
- .action-area {
- display: flex;
- gap: 4px;
- .action-item {
- cursor: pointer;
- padding: 4px;
- border-radius: 6px;
- width: fit-content;
- height: fit-content;
- }
- }
- }
- // 内容体
- .flow-detail-box {
- //height: calc(100% - 92px);
- //overflow: hidden;
- //overflow-y: auto;
- flex: 1;
- min-height: 0;
- padding: 0 20px;
- display: flex;
- flex-direction: column;
- height: 100%;
- .header-box {
- display: flex;
- flex-direction: column;
- justify-content: center;
- padding-bottom: 10px;
- // padding-top: 20px;
- .summary-info {
- display: flex;
- align-items: center;
- padding-top: 10px;
- font-size: 24px;
- .title {
- font-family: PingFangSC-Semibold, PingFang SC;
- color: var(--el-text-color-primary);
- }
- }
- .initiator-info {
- display: flex;
- align-items: center;
- margin-top: 16px;
- .begin-time {
- margin-left: 16px;
- font-weight: 350;
- color: var(--el-text-color-placeholder);
- font-size: 13px;
- -webkit-user-select: none;
- user-select: none;
- }
- }
- }
- .area-divider {
- border-bottom: 1px solid var(--el-border-color);
- margin: 20px 0;
- //position: relative;
- }
- .scroll-wrap {
- position: relative;
- overflow: hidden;
- overflow-y: auto;
- flex: 1;
- .tags-desc {
- position: absolute;
- left: 6px;
- top: 10px;
- z-index: 1;
- }
- :deep(.zoom-scale) {
- position: absolute;
- top: 16px;
- right: 16px;
- }
- }
- }
- // 底部
- .flow-actions {
- display: flex;
- align-items: center;
- justify-content: flex-end;
- height: 52px;
- //border-top: 1px solid var(--color-neutral-3);
- border-top: 1px solid var(--el-border-color-light);
- background: var(--el-bg-color);
- padding: 0 20px;
- }
- }
- .comment-content {
- user-select: none;
- margin-top: 4px;
- padding: 8px;
- border-radius: 4px;
- background-color: var(--el-bg-color-page);
- }
- .timeline-box {
- margin-left: 5px;
- }
- :deep(.el-timeline-item__timestamp) {
- margin-left: 6px;
- }
- // 修改everright 表单的样式
- .self-Everright-formEditor {
- :deep(.Everright-formEditor-selectElement) {
- padding: 0px 16px;
- }
- }
- </style>
|