123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557 |
- <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>
- <div class="area-divider"></div>
- <div class="scroll-wrap">
- <!-- 表单 -->
- <div v-loading="validateForm.loading" class="form-wrap">
- <!--
- <FormCreate v-show="validateForm.rule.length" v-model:api="validateForm.api" :option="validateForm.option" :rule="validateForm.rule" />
- -->
- <er-form-preview ref="EReditorRef" :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.taskType" :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="color: #86909c; display: block; margin-bottom: 3px; padding-left: 4px">{{ active.taskName }}</span>
- <div class="flex flex-align-center">
- <div class="timeline-box-user flex-1">
- <span v-if="active.id" style="padding-left: 4px">{{ active.createBy }}</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" />
- </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>
- <!-- 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'"
- :icon="Close"
- type="danger"
- @click="openComment('consentOrRefuseVisible', 'reject')"
- >拒绝</el-button
- >
- <el-button v-if="currentTaskType === 'myApplication'" :icon="Close" @click="openComment('reviewVisible', 'revoke')">撤回</el-button>
- <el-button v-if="currentTaskType === 'pendingClaim'" :icon="Close" @click="claimTaskEv">认领</el-button>
- <el-dropdown v-if="currentTaskType === 'pendingApproval'" style="margin-left: 12px">
- <el-button :icon="More">更多</el-button>
- <template #dropdown>
- <el-dropdown-menu>
- <el-dropdown-item @click.native="openComment('deliverToReviewVisible')">
- <el-icon><DArrowLeft /></el-icon>
- 转交
- </el-dropdown-item>
- <el-dropdown-item @click.native="openComment('rollbackVisible')">
- <el-icon><Switch /></el-icon>
- 回退
- </el-dropdown-item>
- <el-dropdown-item @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>
- </div>
- </template>
- <script setup>
- import useTaskProcessStore from '@/store/modules/taskProcess'
- import { computed, ref, onMounted, 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 viewForm from '@/utils/form'
- import { storeToRefs } from 'pinia'
- import { ElMessage, ElMessageBox } from 'element-plus'
- import { erFormPreview } from 'everright-formeditor'
- defineProps({
- /**
- * pendingApproval 待审批
- * myApplication 我的申请
- * myReceived 我收到的
- * pendingClaim 认领任务
- * approved 已审批
- */
- currentTaskType: {
- type: String,
- default: ''
- }
- })
- // 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)
- // 各种操作弹窗显示隐藏 end
- const activeData = ref([])
- const currentType = ref(null)
- const currentFormData = ref({})
- // 当前form 表单数据字符串
- let cur_processForm_str =
- '{"formStructure":{"list":[{"type":"inline","columns":["G87FL_bjPlVjZjSd2P3SE"],"style":{},"id":"XqyjpOONTVRdA9XeA667G","key":"inline_XqyjpOONTVRdA9XeA667G"},{"type":"inline","columns":["fg_137feCy7pJSJs9sOaU"],"style":{},"id":"RsbiEBhHked3LYw_GDwYt","key":"inline_RsbiEBhHked3LYw_GDwYt"}],"config":{"isSync":true,"pc":{"size":"default","labelPosition":"left","completeButton":{"text":"提交","color":"","backgroundColor":""}},"mobile":{"labelPosition":"left","completeButton":{"text":"提交","color":"","backgroundColor":""}}},"fields":[{"type":"input","label":"姓名","icon":"input","key":"input_name","id":"G87FL_bjPlVjZjSd2P3SE","options":{"clearable":true,"isShowWordLimit":false,"renderType":1,"disabled":false,"showPassword":false,"defaultValue":"","placeholder":"请输入","labelWidth":100,"isShowLabel":true,"required":false,"min":null,"max":null},"style":{"width":{"pc":"100%","mobile":"100%"}}},{"type":"input","label":"别名","icon":"input","key":"input_alias","id":"fg_137feCy7pJSJs9sOaU","options":{"clearable":true,"isShowWordLimit":false,"renderType":1,"disabled":false,"showPassword":false,"defaultValue":"","placeholder":"请输入","labelWidth":100,"isShowLabel":true,"required":false,"min":null,"max":null},"style":{"width":{"pc":"100%","mobile":"100%"}}}],"data":{},"logic":{}},"formData":{"input_name":"罗小胖","input_alias":"luoxiaopang"}}'
- const EReditorRef = ref()
- const taskId = computed(() => {
- return currentTaskRow.value.taskId || ''
- })
- const FormCreate = viewForm.$form()
- const validateForm = ref({
- api: {},
- option: {
- submitBtn: false
- },
- rule: [],
- loading: false
- })
- /**
- * 详情按钮各个操作弹窗
- * @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 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 // JSON.parse(v.content || '{}')
- v.local_nodeUserList = _content.nodeUserList || []
- v.local_nodeRoleList = _content.nodeRoleList || []
- v.local_content = _content?.opinion
- })
- activeData.value = activeList
- console.log(data, 'data.......')
- // validateForm.value.origin = data
- try {
- /*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
- }
- })*/
- const { formStructure, formData } = JSON.parse(cur_processForm_str)
- EReditorRef.value.setData(formStructure, formData)
- return
- const forms = JSON.parse(data.formContent)
- cur_processForm_str = data.formContent
- if (Array.isArray(forms)) {
- validateForm.value.rule = forms
- const api = validateForm.value.api
- api.setValue(
- forms.reduce((obj, item) => {
- obj[item.field] = item.local_value
- return obj
- }, {})
- )
- api.nextTick(() => {
- // 是否有编辑权限 操作
- api.disabled(false /*true*/)
- })
- }
- } catch (e) {
- console.error('解析 descItems 数据出现问题', e)
- // descItemsData.value.list = []
- validateForm.value.rule = []
- }
- })
- .finally(() => {
- validateForm.value.loading = false
- })
- }
- /**
- * 详情页面操作按钮回调
- */
- const closeDetailEv = () => {
- ElMessage({
- message: '操作成功',
- type: 'success'
- })
- setTimeout(() => {
- // 如果这里有表单,是否要把所有表单的内容进行存储,存储完毕后,才能关闭这个详情,刷新左侧的列表 todo
- 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-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 {
- overflow: hidden;
- overflow-y: auto;
- flex: 1;
- }
- }
- // 底部
- .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;
- }
- </style>
|