123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341 |
- <template>
- <!-- 同意 拒绝 弹窗 同意/拒绝审批 -->
- <el-dialog v-model="operaVisibleDialog" class="le-dialog" :title="currentTip + '审批'" width="700" destroy-on-close :close-on-click-modal="false">
- <el-form ref="formRef" v-loading="uploadLoading" label-position="top" element-loading-text="图片上传中..." :model="form" label-width="80px">
- <el-form-item
- v-if="rejectStrategy === 3 && currentType === 'reject'"
- label="回退节点"
- prop="nodeKey"
- :rules="[{ required: true, message: '请选择回退节点' }]"
- >
- <el-select v-model="form.nodeKey" placeholder="请选择回退节点">
- <el-option v-for="item in rollbackOptions" :key="item.nodeKey" :label="item.nodeName" :value="item.nodeKey" />
- </el-select>
- </el-form-item>
- <el-form-item label="审批意见" prop="content" :rules="[{ required: true, message: '审批意见不能为空' }]">
- <el-input v-model="form.content" type="textarea" placeholder="请输入内容" maxlength="64" show-word-limit> </el-input>
- </el-form-item>
- <el-form-item
- v-if="nodeModelsData.length"
- label="下一节点审批人"
- prop="assigneeMap"
- required
- :rules="[{ required: true, message: '请选择下一节点审批人' }]"
- >
- <el-timeline>
- <el-timeline-item v-for="(item, index) in nodeModelsData" :key="index" placement="top" :timestamp="item.nodeName">
- <div class="flex">
- <!-- type: 1[审核人] 2[抄送人] 4[条件路由] 5[子流程] 6[延时处理] 7[触发器] 8[并行路由] 9[包容路由] 10[路由分支]-->
- <template v-if="item.type === 1">
- <div v-if="[2, 5].indexOf(item.setType) > -1">
- <el-tag v-if="item.setType === 5" type="primary">{{ setTypeOptions_config[item.setType] }}</el-tag>
- <el-tag v-if="item.setType === 2" type="primary">{{
- item.examineLevel === 1 ? '直接主管' : `发起人的第${item.examineLevel}级主管`
- }}</el-tag>
- </div>
- <template v-if="item.setType === 4">
- <!-- item.nodeCandidate里面的type属性 type:1|0 角色|用户-->
- <div v-if="item.nodeCandidate.type === 1" class="mr-2">
- <el-tooltip content="添加角色" placement="bottom">
- <el-button style="width: 32px" @click="selectHandler(item, 3)">
- <svg-icon style="font-size: 18px" icon-class="flow-group-add" />
- </el-button>
- </el-tooltip>
- </div>
- <div v-if="item.nodeCandidate.type === 0" class="mr-2">
- <el-tooltip content="添加用户" placement="bottom">
- <el-button style="width: 32px" @click="selectHandler(item, 1)">
- <svg-icon style="font-size: 18px" icon-class="flow-user-add" />
- </el-button>
- </el-tooltip>
- </div>
- </template>
- </template>
- <template v-else>{{ item.nodeName }}</template>
- <div v-if="item.selectMode === 1" class="flex flex-wrap gap-[6px]">
- <FlowNodeAvatar v-for="(childItem, childIndex) in item.nodeAssigneeList" :key="childIndex" :name="childItem.name" />
- </div>
- <div v-if="item.selectMode !== 1" class="flex flex-wrap gap-[6px]">
- <FlowNodeAvatar v-for="(childItem, childIndex) in item.nodeAssigneeList" :key="childIndex" :name="childItem.name">
- <template #avatar>
- <svg-icon icon-class="flow-group" color="#fff" />
- </template>
- </FlowNodeAvatar>
- </div>
- </div>
- </el-timeline-item>
- </el-timeline>
- </el-form-item>
- <el-form-item v-if="currentType !== 'reject'" label="推荐回复">
- <el-tag v-for="(label, index) in buttonLabels" :key="index" type="info" class="ml-2 cursor-pointer" @click="appendToApprovalComments(label)">
- {{ label }}
- </el-tag>
- </el-form-item>
- <el-form-item v-if="currentType === 'reject'" prop="termination">
- <el-checkbox v-model="form.termination" label="终止流程" />
- </el-form-item>
- <el-form-item v-if="false" prop="attachment" label="附件" class="example-img-box">
- <!--'.docx', '.doc', '.pptx', '.ppt', '.xlsx', '.xls', '.zip', '.csv', '.pdf', '.png', '.jpg' 因前端不支持图片以外格式,所以注释 -->
- <FileUpload
- v-model="form.attachment"
- source="project"
- return="array"
- :limit="5"
- :file-size="10"
- :accept="['.png', '.jpg']"
- @success="clearValidate"
- />
- </el-form-item>
- </el-form>
- <template #footer>
- <span class="dialog-footer">
- <el-button :loading="btnDisabled" class="dialog-btn" @click="closeDialog">取 消</el-button>
- <el-button type="primary" :loading="btnDisabled" class="dialog-btn" @click="submitForm">确 定</el-button>
- </span>
- </template>
- <!-- 选择人员/角色-->
- <use-select ref="useSelectRef" v-bind="active_selectOpts" @update:selected="updateActive_assigneeMap"></use-select>
- </el-dialog>
- </template>
- <script setup>
- import { computed, reactive, watch, toRefs, nextTick } from 'vue'
- import FileUpload from '@/components/FileUpload.vue'
- import FlowNodeAvatar from '@/components/Flow/FlowNodeAvatar.vue'
- import { nextNodesApi, processConsentTaskApi, processPreviousNodeNameApi, processRejectionTaskApi } from '@/api/flow/processTask'
- import { ElMessage } from 'element-plus'
- import { debounce } from 'lodash-es'
- import UseSelect from '@/components/scWorkflow/select.vue'
- import { setTypeOptions_config } from '@/components/scWorkflow/nodes/config'
- const props = defineProps({
- // 弹窗是否显示
- modelValue: {
- type: Boolean,
- default: false
- },
- // 审核id
- taskId: {
- type: String,
- default: undefined
- },
- // 审核类型 同意(agree) or 拒绝(reject)
- currentType: {
- type: String,
- default: 'agree'
- },
- // 额外 表单数据
- formData: {
- type: Object,
- default: () => ({})
- },
- // 拒绝策略
- rejectStrategy: {
- type: Number || undefined,
- default: undefined
- },
- instanceId: {
- type: String,
- default: ''
- }
- })
- const buttonLabels = ['同意', '已阅', '收到', '已核对', '合格', '情况属实', '确认', '已复核', '知悉', '辛苦了', '已安排']
- const form = reactive({
- nodeKey: undefined,
- content: '',
- assigneeMap: null
- // attachment: []
- })
- const datas = reactive({
- formRef: null,
- uploadLoading: false,
- rollbackOptions: [],
- btnDisabled: false,
- nodeModelsData: [],
- active_selectOpts: {},
- useSelectRef: null,
- active_assigneeKey: null
- })
- const { formRef, uploadLoading, rollbackOptions, btnDisabled, nodeModelsData, active_selectOpts, useSelectRef, active_assigneeKey } =
- toRefs(datas)
- const $myEmit = defineEmits(['update:modelValue', 'successCb'])
- const closeDialog = () => {
- $myEmit('update:modelValue', false)
- }
- const operaVisibleDialog = computed({
- get() {
- return props.modelValue
- },
- set(val) {
- $myEmit('update:modelValue', val)
- }
- })
- const currentTip = computed(() => {
- return props.currentType === 'agree' ? '同意' : '拒绝'
- })
- const clearValidate = () => {}
- // 回退节点列表
- const getProcessPreviousNodeNameApi = async () => {
- const res = await processPreviousNodeNameApi(props.taskId)
- rollbackOptions.value = res || []
- }
- const appendToApprovalComments = label => {
- form.content += (form.content ? '' : '') + label
- }
- const getNextNodesEv = async params => {
- const { nodeModels } = await nextNodesApi(params)
- nodeModelsData.value = nodeModels || []
- /**
- * "nodeModels": [
- {
- "nodeName": "CEO审批",
- "nodeKey": "flk1720532364452",
- "nodeAssigneeList": [
- {
- "id": "0",
- "name": "CEO"
- },
- {
- "id": "1",
- "name": "CEO1"
- }
- ],
- "selectMode": 1
- },
- ],
- "nodeType": 1
- */
- }
- const findNodeByKey = nodeKey => {
- return nodeModelsData.value.find(node => node.nodeKey === nodeKey)
- }
- const validateNodeAssigneeLists = () => {
- return (nodeModelsData.value || []).every(node => node.nodeAssigneeList && node.nodeAssigneeList.length > 0)
- }
- const updateActive_assigneeMap = assignees => {
- const _cur = findNodeByKey(active_assigneeKey.value)
- if (_cur) {
- _cur.nodeAssigneeList = assignees
- }
- }
- const selectHandler = (item, type) => {
- // nodeModelsData.value数组中查找和nodeKey相匹配的对象值
- const foundNode = findNodeByKey(item.nodeKey)
- let config = foundNode ? { assignees: foundNode.nodeAssigneeList, type } : { assignees: [], type }
- if (item.selectMode === 1) {
- config.selectOpts = { maxSelected: 1 }
- }
- // 插入备选列表 candidateAssignees
- config.selectOpts = { ...config.selectOpts, candidateAssignees: foundNode.nodeCandidate?.assignees }
- active_assigneeKey.value = item.nodeKey
- active_selectOpts.value = config.selectOpts || {}
- nextTick(() => {
- useSelectRef.value.open(type, config.assignees)
- })
- }
- const submitForm = debounce(() => {
- const isAgree = props.currentType === 'agree'
- if (isAgree && nodeModelsData.value.length) {
- const areAllAssigneeListsValid = validateNodeAssigneeLists()
- form.assigneeMap = !areAllAssigneeListsValid ? null : nodeModelsData.value
- }
- const formData = { taskId: props.taskId, ...props.formData, ...form }
- formRef.value
- .validate()
- .then(async valid => {
- if (valid) {
- btnDisabled.value = true
- let data = null
- if (isAgree) {
- // 同意
- formData.termination = false
- const validNodes = nodeModelsData.value.filter(node => node.selectMode)
- if (validNodes.length) {
- formData.assigneeMap = validNodes.reduce((acc, node) => {
- acc[node.nodeKey] = { assigneeList: node.nodeAssigneeList, type: node.nodeCandidate.type }
- return acc
- }, {}) // 组装成后台需要的格式
- } else {
- formData.assigneeMap = undefined
- }
- data = await processConsentTaskApi(formData)
- } else {
- // 拒绝
- data = await processRejectionTaskApi(formData)
- }
- if (!data) {
- ElMessage({
- message: '执行失败',
- type: 'error'
- })
- return false
- }
- $myEmit('successCb')
- closeDialog()
- btnDisabled.value = false
- }
- })
- .catch(err => {
- btnDisabled.value = false
- })
- })
- // 驳回 && rejectStrategy === 3
- watch(
- () => props.rejectStrategy,
- item => {
- if (item && Number(item) === 3 && props.currentType !== 'agree') {
- getProcessPreviousNodeNameApi()
- }
- },
- {
- immediate: true
- }
- )
- watch(
- () => operaVisibleDialog.value,
- newVal => {
- if (newVal && props.currentType === 'agree') {
- const t = JSON.parse(props.formData.processForm || '{}')?.formData
- const _param = {
- instanceId: props.instanceId,
- args: t
- }
- getNextNodesEv(_param)
- }
- },
- { immediate: true }
- )
- </script>
- <style scoped lang="scss">
- .example-img-box {
- width: 100% !important;
- }
- .example-img-box .el-form-item__content {
- display: block;
- }
- </style>
|