|
@@ -30,8 +30,23 @@
|
|
|
|
|
|
|
|
<!-- 表单数据 -->
|
|
<!-- 表单数据 -->
|
|
|
<div v-if="formDataDisplay" class="form-data-section">
|
|
<div v-if="formDataDisplay" class="form-data-section">
|
|
|
- <h4>表单数据</h4>
|
|
|
|
|
|
|
+ <div class="section-title">
|
|
|
|
|
+ <h4>表单数据</h4>
|
|
|
|
|
+ <el-button v-if="canEditForm && !formEditing" type="primary" size="small" link @click="startEditForm">补充/编辑材料</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <template v-if="formEditing">
|
|
|
|
|
+ <FlowFormFields
|
|
|
|
|
+ ref="formFieldsRef"
|
|
|
|
|
+ :definition="progress?.definition"
|
|
|
|
|
+ v-model="editFormData"
|
|
|
|
|
+ />
|
|
|
|
|
+ <div class="form-edit-actions">
|
|
|
|
|
+ <el-button size="small" @click="cancelEditForm">取消</el-button>
|
|
|
|
|
+ <el-button type="primary" size="small" :loading="savingForm" @click="saveFormData">保存</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
<FormDataDisplay
|
|
<FormDataDisplay
|
|
|
|
|
+ v-else
|
|
|
:form-schema="progress?.definition?.formSchema"
|
|
:form-schema="progress?.definition?.formSchema"
|
|
|
:form-data="progress?.instance?.formData"
|
|
:form-data="progress?.instance?.formData"
|
|
|
/>
|
|
/>
|
|
@@ -110,23 +125,17 @@
|
|
|
<el-radio label="pass">通过</el-radio>
|
|
<el-radio label="pass">通过</el-radio>
|
|
|
<el-radio label="reject">拒绝</el-radio>
|
|
<el-radio label="reject">拒绝</el-radio>
|
|
|
<el-radio label="rollback">回退</el-radio>
|
|
<el-radio label="rollback">回退</el-radio>
|
|
|
- <el-radio label="transfer">转办</el-radio>
|
|
|
|
|
</el-radio-group>
|
|
</el-radio-group>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
- <el-form-item v-if="form.action === 'rollback'" label="回退到">
|
|
|
|
|
- <el-select v-model="form.targetNodeId" filterable placeholder="请选择回退节点" style="width: 100%">
|
|
|
|
|
- <el-option
|
|
|
|
|
- v-for="node in rollbackNodeOptions"
|
|
|
|
|
- :key="node.nodeId"
|
|
|
|
|
- :label="node.nodeName"
|
|
|
|
|
- :value="node.nodeId"
|
|
|
|
|
- />
|
|
|
|
|
- </el-select>
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- <el-form-item v-if="form.action === 'transfer'" label="转给人">
|
|
|
|
|
- <el-select v-model="form.transferTo" filterable placeholder="请选择转办人" style="width: 100%">
|
|
|
|
|
- <el-option v-for="u in userList" :key="u.id" :label="u.realName || u.username" :value="u.id" />
|
|
|
|
|
- </el-select>
|
|
|
|
|
|
|
+ <el-form-item v-if="form.action === 'rollback'" label="回退说明">
|
|
|
|
|
+ <el-alert type="warning" :closable="false" show-icon>
|
|
|
|
|
+ <template #title>
|
|
|
|
|
+ 提交后将自动回退到上一个审批节点
|
|
|
|
|
+ <div v-if="lastRollbackRecord" class="rollback-reason">
|
|
|
|
|
+ 上次回退原因:{{ lastRollbackRecord.comment }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-alert>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
<el-form-item label="附件">
|
|
<el-form-item label="附件">
|
|
|
<el-upload
|
|
<el-upload
|
|
@@ -212,14 +221,15 @@
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
import { ref, reactive, computed, watch } from 'vue'
|
|
import { ref, reactive, computed, watch } from 'vue'
|
|
|
import { ElMessage } from 'element-plus'
|
|
import { ElMessage } from 'element-plus'
|
|
|
-import { getProgress, getInstanceAttachments } from '@/api/flow/instance'
|
|
|
|
|
-import { approveTask, rejectTask, returnTask, transferTask, addSignTask, getTransferableUsers } from '@/api/flow/task'
|
|
|
|
|
|
|
+import { getProgress, getInstanceAttachments, updateInstanceFormData } from '@/api/flow/instance'
|
|
|
|
|
+import { approveTask, rejectTask, returnTask, addSignTask, getTransferableUsers } from '@/api/flow/task'
|
|
|
import { uploadFile } from '@/api/file'
|
|
import { uploadFile } from '@/api/file'
|
|
|
import { beforeFileUpload, getFileUrl, getFileName, getUploadUrl, parseAttachments, collectAttachmentUrls } from '@/utils/file'
|
|
import { beforeFileUpload, getFileUrl, getFileName, getUploadUrl, parseAttachments, collectAttachmentUrls } from '@/utils/file'
|
|
|
import { instanceStatusText, instanceStatusTagType, taskStatusText, taskStatusType, recordActionText, recordActionType } from '@/utils/flow'
|
|
import { instanceStatusText, instanceStatusTagType, taskStatusText, taskStatusType, recordActionText, recordActionType } from '@/utils/flow'
|
|
|
import FilePreview from '@/components/FilePreview/index.vue'
|
|
import FilePreview from '@/components/FilePreview/index.vue'
|
|
|
import FormDataDisplay from '@/components/FormDataDisplay/index.vue'
|
|
import FormDataDisplay from '@/components/FormDataDisplay/index.vue'
|
|
|
-import type { ProcessProgress, FlowTask, NodeProgress, ApprovalAction, Attachment } from '@/types/flow'
|
|
|
|
|
|
|
+import FlowFormFields from '@/components/FlowFormFields/index.vue'
|
|
|
|
|
+import type { ProcessProgress, FlowTask, ApprovalAction, Attachment } from '@/types/flow'
|
|
|
import type { User } from '@/types/system'
|
|
import type { User } from '@/types/system'
|
|
|
import { CircleCheck, Clock, Document } from '@element-plus/icons-vue'
|
|
import { CircleCheck, Clock, Document } from '@element-plus/icons-vue'
|
|
|
|
|
|
|
@@ -243,30 +253,20 @@ const previewVisible = ref(false)
|
|
|
const previewUrl = ref('')
|
|
const previewUrl = ref('')
|
|
|
const progress = ref<ProcessProgress | null>(null)
|
|
const progress = ref<ProcessProgress | null>(null)
|
|
|
|
|
|
|
|
-type ApprovalActionType = 'pass' | 'reject' | 'rollback' | 'transfer'
|
|
|
|
|
|
|
+type ApprovalActionType = 'pass' | 'reject' | 'rollback'
|
|
|
|
|
|
|
|
const form = reactive<{
|
|
const form = reactive<{
|
|
|
action: ApprovalActionType
|
|
action: ApprovalActionType
|
|
|
comment: string
|
|
comment: string
|
|
|
- transferTo: number | undefined
|
|
|
|
|
- targetNodeId: string | undefined
|
|
|
|
|
}>({
|
|
}>({
|
|
|
action: 'pass',
|
|
action: 'pass',
|
|
|
- comment: '',
|
|
|
|
|
- transferTo: undefined,
|
|
|
|
|
- targetNodeId: undefined
|
|
|
|
|
|
|
+ comment: ''
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
const attachmentList = ref<any[]>([])
|
|
const attachmentList = ref<any[]>([])
|
|
|
const submitting = ref(false)
|
|
const submitting = ref(false)
|
|
|
const addSignDialogVisible = ref(false)
|
|
const addSignDialogVisible = ref(false)
|
|
|
const addSignUserId = ref<number | undefined>(undefined)
|
|
const addSignUserId = ref<number | undefined>(undefined)
|
|
|
-
|
|
|
|
|
-const rollbackNodeOptions = computed<NodeProgress[]>(() => {
|
|
|
|
|
- if (!progress.value) return []
|
|
|
|
|
- return progress.value.nodes.filter(n => n.status === 'completed')
|
|
|
|
|
-})
|
|
|
|
|
-
|
|
|
|
|
const userList = ref<User[]>([])
|
|
const userList = ref<User[]>([])
|
|
|
|
|
|
|
|
async function loadTransferableUsers() {
|
|
async function loadTransferableUsers() {
|
|
@@ -276,7 +276,6 @@ async function loadTransferableUsers() {
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
try {
|
|
try {
|
|
|
- // 按当前节点审批角色过滤可选用户,已自动排除当前登录人
|
|
|
|
|
userList.value = await getTransferableUsers(task.id)
|
|
userList.value = await getTransferableUsers(task.id)
|
|
|
} catch {
|
|
} catch {
|
|
|
userList.value = []
|
|
userList.value = []
|
|
@@ -379,26 +378,12 @@ async function loadProgress() {
|
|
|
async function submitApprove() {
|
|
async function submitApprove() {
|
|
|
const task = myPendingTask.value
|
|
const task = myPendingTask.value
|
|
|
if (!task) return
|
|
if (!task) return
|
|
|
- if (form.action === 'transfer' && !form.transferTo) {
|
|
|
|
|
- ElMessage.warning('请选择转办人')
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
- if (form.action === 'rollback' && !form.targetNodeId) {
|
|
|
|
|
- ElMessage.warning('请选择回退节点')
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
submitting.value = true
|
|
submitting.value = true
|
|
|
try {
|
|
try {
|
|
|
const data: ApprovalAction = {
|
|
const data: ApprovalAction = {
|
|
|
action: form.action,
|
|
action: form.action,
|
|
|
comment: form.comment
|
|
comment: form.comment
|
|
|
}
|
|
}
|
|
|
- if (form.action === 'transfer' && form.transferTo) {
|
|
|
|
|
- data.transferTo = Number(form.transferTo)
|
|
|
|
|
- }
|
|
|
|
|
- if (form.action === 'rollback' && form.targetNodeId) {
|
|
|
|
|
- data.targetNodeId = form.targetNodeId
|
|
|
|
|
- }
|
|
|
|
|
if (attachmentList.value.length > 0) {
|
|
if (attachmentList.value.length > 0) {
|
|
|
data.attachmentUrls = collectAttachmentUrls(attachmentList.value)
|
|
data.attachmentUrls = collectAttachmentUrls(attachmentList.value)
|
|
|
}
|
|
}
|
|
@@ -412,15 +397,10 @@ async function submitApprove() {
|
|
|
case 'rollback':
|
|
case 'rollback':
|
|
|
await returnTask(task.id, data)
|
|
await returnTask(task.id, data)
|
|
|
break
|
|
break
|
|
|
- case 'transfer':
|
|
|
|
|
- await transferTask(task.id, data)
|
|
|
|
|
- break
|
|
|
|
|
}
|
|
}
|
|
|
ElMessage.success('审批提交成功')
|
|
ElMessage.success('审批提交成功')
|
|
|
form.action = 'pass'
|
|
form.action = 'pass'
|
|
|
form.comment = ''
|
|
form.comment = ''
|
|
|
- form.transferTo = undefined
|
|
|
|
|
- form.targetNodeId = undefined
|
|
|
|
|
attachmentList.value = []
|
|
attachmentList.value = []
|
|
|
// 刷新进度展示最新状态,不关闭抽屉
|
|
// 刷新进度展示最新状态,不关闭抽屉
|
|
|
await loadProgress()
|
|
await loadProgress()
|
|
@@ -434,8 +414,63 @@ function handleClose() {
|
|
|
progress.value = null
|
|
progress.value = null
|
|
|
attachmentList.value = []
|
|
attachmentList.value = []
|
|
|
attachments.value = []
|
|
attachments.value = []
|
|
|
|
|
+ cancelEditForm()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 表单补充/编辑
|
|
|
|
|
+const formEditing = ref(false)
|
|
|
|
|
+const savingForm = ref(false)
|
|
|
|
|
+const editFormData = ref<Record<string, unknown>>({})
|
|
|
|
|
+const formFieldsRef = ref<{ validate: () => boolean } | null>(null)
|
|
|
|
|
+
|
|
|
|
|
+const canEditForm = computed(() => !!myPendingTask.value)
|
|
|
|
|
+
|
|
|
|
|
+function parseInstanceFormData(): Record<string, unknown> {
|
|
|
|
|
+ const fd = progress.value?.instance?.formData
|
|
|
|
|
+ if (!fd) return {}
|
|
|
|
|
+ if (fd !== null && typeof fd === 'object') return { ...fd as Record<string, unknown> }
|
|
|
|
|
+ try {
|
|
|
|
|
+ return JSON.parse(fd as string)
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ return {}
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function startEditForm() {
|
|
|
|
|
+ editFormData.value = parseInstanceFormData()
|
|
|
|
|
+ formEditing.value = true
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function cancelEditForm() {
|
|
|
|
|
+ formEditing.value = false
|
|
|
|
|
+ editFormData.value = {}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+async function saveFormData() {
|
|
|
|
|
+ const valid = formFieldsRef.value?.validate?.()
|
|
|
|
|
+ if (valid === false) return
|
|
|
|
|
+ savingForm.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ await updateInstanceFormData(props.instanceId, editFormData.value)
|
|
|
|
|
+ ElMessage.success('材料已更新')
|
|
|
|
|
+ cancelEditForm()
|
|
|
|
|
+ await loadProgress()
|
|
|
|
|
+ emit('approved')
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ savingForm.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 当前节点最近一次被回退的记录
|
|
|
|
|
+const lastRollbackRecord = computed(() => {
|
|
|
|
|
+ if (!progress.value?.records) return null
|
|
|
|
|
+ const currentNodeId = progress.value.instance?.currentNode
|
|
|
|
|
+ const list = progress.value.records.filter(r =>
|
|
|
|
|
+ r.nodeId === currentNodeId && (r.actionType === 'RETURN' || r.actionResult === 'RETURN')
|
|
|
|
|
+ )
|
|
|
|
|
+ return list.length > 0 ? list[list.length - 1] : null
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
function handleAddSign() {
|
|
function handleAddSign() {
|
|
|
const task = myPendingTask.value
|
|
const task = myPendingTask.value
|
|
|
if (!task) return
|
|
if (!task) return
|
|
@@ -609,4 +644,25 @@ watch(() => props.modelValue, async (val) => {
|
|
|
flex-wrap: wrap;
|
|
flex-wrap: wrap;
|
|
|
gap: 8px;
|
|
gap: 8px;
|
|
|
}
|
|
}
|
|
|
|
|
+.section-title {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ margin-bottom: 8px;
|
|
|
|
|
+}
|
|
|
|
|
+.section-title h4 {
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+}
|
|
|
|
|
+.form-edit-actions {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: flex-end;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ margin-top: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+.rollback-reason {
|
|
|
|
|
+ margin-top: 4px;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ color: #e6a23c;
|
|
|
|
|
+ line-height: 1.5;
|
|
|
|
|
+}
|
|
|
</style>
|
|
</style>
|