feat(oa): render instance detail progress from API projection
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
98beade090
commit
265d790ea6
2 files changed
+747
No files matched your search
@@ -0,0 +1,279 @@
|
|||||||
|
import request from '@/utils/http'
|
||||||
|
|
||||||
|
const BASE = '/web/admin/oa-approval'
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 工作流角色
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export function fetchOaRoleList(params?: { keyword?: string; status?: string }) {
|
||||||
|
return request.get<{ list: any[] }>({
|
||||||
|
url: `${BASE}/roles`,
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchEnsureOaRolePresets() {
|
||||||
|
return request.post<{ createdCount: number }>({
|
||||||
|
url: `${BASE}/roles/ensure-presets`,
|
||||||
|
showSuccessMessage: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchCreateOaRole(data: {
|
||||||
|
roleName: string
|
||||||
|
roleCode: string
|
||||||
|
status?: string
|
||||||
|
orderNum?: number
|
||||||
|
remark?: string
|
||||||
|
}) {
|
||||||
|
return request.post({
|
||||||
|
url: `${BASE}/roles`,
|
||||||
|
data,
|
||||||
|
showSuccessMessage: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchUpdateOaRole(
|
||||||
|
id: string,
|
||||||
|
data: Partial<{
|
||||||
|
roleName: string
|
||||||
|
status: string
|
||||||
|
orderNum: number
|
||||||
|
remark: string
|
||||||
|
}>
|
||||||
|
) {
|
||||||
|
return request.put({
|
||||||
|
url: `${BASE}/roles/${id}`,
|
||||||
|
data,
|
||||||
|
showSuccessMessage: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchDeleteOaRole(id: string) {
|
||||||
|
return request.del({
|
||||||
|
url: `${BASE}/roles/${id}`,
|
||||||
|
showSuccessMessage: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchOaRoleUsers(roleId: string) {
|
||||||
|
return request.get<{ list: any[] }>({
|
||||||
|
url: `${BASE}/roles/${roleId}/users`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchReplaceOaRoleUsers(roleId: string, userIds: string[]) {
|
||||||
|
return request.put<{ list: any[] }>({
|
||||||
|
url: `${BASE}/roles/${roleId}/users`,
|
||||||
|
data: { userIds },
|
||||||
|
showSuccessMessage: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 分组 / 应用 / 草稿 / 发布
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export function fetchOaGroupList() {
|
||||||
|
return request.get<{ list: any[] }>({ url: `${BASE}/groups` })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchCreateOaGroup(data: {
|
||||||
|
groupName: string
|
||||||
|
orderNum?: number
|
||||||
|
remark?: string
|
||||||
|
}) {
|
||||||
|
return request.post({
|
||||||
|
url: `${BASE}/groups`,
|
||||||
|
data,
|
||||||
|
showSuccessMessage: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchUpdateOaGroup(
|
||||||
|
id: string,
|
||||||
|
data: Partial<{ groupName: string; orderNum: number; remark: string }>
|
||||||
|
) {
|
||||||
|
return request.put({
|
||||||
|
url: `${BASE}/groups/${id}`,
|
||||||
|
data,
|
||||||
|
showSuccessMessage: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchDeleteOaGroup(id: string) {
|
||||||
|
return request.del({
|
||||||
|
url: `${BASE}/groups/${id}`,
|
||||||
|
showSuccessMessage: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchOaAppList(params?: {
|
||||||
|
groupId?: string
|
||||||
|
keyword?: string
|
||||||
|
status?: string
|
||||||
|
}) {
|
||||||
|
return request.get<{ list: any[] }>({
|
||||||
|
url: `${BASE}/apps`,
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchCreateOaApp(data: Record<string, any>) {
|
||||||
|
return request.post({
|
||||||
|
url: `${BASE}/apps`,
|
||||||
|
data,
|
||||||
|
showSuccessMessage: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchUpdateOaApp(id: string, data: Record<string, any>) {
|
||||||
|
return request.put({
|
||||||
|
url: `${BASE}/apps/${id}`,
|
||||||
|
data,
|
||||||
|
showSuccessMessage: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchDeleteOaApp(id: string) {
|
||||||
|
return request.del({
|
||||||
|
url: `${BASE}/apps/${id}`,
|
||||||
|
showSuccessMessage: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchOaAppDraft(appId: string) {
|
||||||
|
return request.get<any>({ url: `${BASE}/apps/${appId}/draft` })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchSaveOaAppDraft(
|
||||||
|
appId: string,
|
||||||
|
data: {
|
||||||
|
basic?: Record<string, any>
|
||||||
|
formSchemaJson?: any
|
||||||
|
flowTreeJson?: any
|
||||||
|
advancedSettingsJson?: any
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
return request.put({
|
||||||
|
url: `${BASE}/apps/${appId}/draft`,
|
||||||
|
data,
|
||||||
|
showSuccessMessage: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchPublishOaApp(appId: string) {
|
||||||
|
return request.post({
|
||||||
|
url: `${BASE}/apps/${appId}/publish`,
|
||||||
|
showSuccessMessage: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 运行时
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export function fetchOaTemplates() {
|
||||||
|
return request.get<{ list: any[] }>({ url: `${BASE}/templates` })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchOaTemplateDetail(appId: string) {
|
||||||
|
return request.get<any>({ url: `${BASE}/templates/${appId}` })
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 发起前审批流预览(按当前登录人解析审批人) */
|
||||||
|
export function fetchOaFlowPreview(appId: string, initiatorSelectUserIds?: string[]) {
|
||||||
|
return request.get<{ steps: OaFlowPreviewStep[] }>({
|
||||||
|
url: `${BASE}/templates/${appId}/flow-preview`,
|
||||||
|
params: initiatorSelectUserIds?.length
|
||||||
|
? { initiatorSelectUserIds: initiatorSelectUserIds.join(',') }
|
||||||
|
: undefined
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export type OaFlowPreviewStep = {
|
||||||
|
id: string
|
||||||
|
type: 'start' | 'approval' | 'cc' | 'branch' | 'end' | string
|
||||||
|
name: string
|
||||||
|
signMode?: string
|
||||||
|
assigneeType?: string
|
||||||
|
meta?: string
|
||||||
|
people?: Array<{ id: string; nickName: string }>
|
||||||
|
resolveError?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchStartOaInstance(data: {
|
||||||
|
appId: string
|
||||||
|
formData?: Record<string, any>
|
||||||
|
title?: string
|
||||||
|
initiatorDeptId?: string
|
||||||
|
initiatorSelectUserIds?: string[]
|
||||||
|
relatedInstanceIds?: string[]
|
||||||
|
}) {
|
||||||
|
return request.post({
|
||||||
|
url: `${BASE}/instances`,
|
||||||
|
data,
|
||||||
|
showSuccessMessage: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 详情审批进度投影项(后端 build_progress_items) */
|
||||||
|
export interface OaProgressItem {
|
||||||
|
kind: 'submit' | 'task' | 'pending_node' | 'terminal' | string
|
||||||
|
roundNo?: number
|
||||||
|
title?: string
|
||||||
|
status?: string
|
||||||
|
assigneeUserId?: string
|
||||||
|
assigneeUserName?: string
|
||||||
|
timestamp?: string
|
||||||
|
commentText?: string
|
||||||
|
nodeKey?: string
|
||||||
|
taskType?: string
|
||||||
|
resolveSource?: string
|
||||||
|
taskId?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchOaInstanceDetail(id: string) {
|
||||||
|
return request.get<{ progress?: OaProgressItem[]; tasks?: any[] } & Record<string, any>>({
|
||||||
|
url: `${BASE}/instances/${id}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchOaTasks(kind: 'pending' | 'done' | 'mine' | 'cc') {
|
||||||
|
return request.get<{ list: any[] }>({ url: `${BASE}/tasks/${kind}` })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchApproveOaTask(taskId: string, comment?: string) {
|
||||||
|
return request.post({
|
||||||
|
url: `${BASE}/tasks/${taskId}/approve`,
|
||||||
|
data: { comment },
|
||||||
|
showSuccessMessage: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchRejectOaTask(taskId: string, comment?: string) {
|
||||||
|
return request.post({
|
||||||
|
url: `${BASE}/tasks/${taskId}/reject`,
|
||||||
|
data: { comment },
|
||||||
|
showSuccessMessage: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchAddSignOaTask(
|
||||||
|
taskId: string,
|
||||||
|
data: { userIds: string[]; comment?: string }
|
||||||
|
) {
|
||||||
|
return request.post({
|
||||||
|
url: `${BASE}/tasks/${taskId}/add-sign`,
|
||||||
|
data,
|
||||||
|
showSuccessMessage: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchWithdrawOaInstance(instanceId: string) {
|
||||||
|
return request.post({
|
||||||
|
url: `${BASE}/instances/${instanceId}/withdraw`,
|
||||||
|
showSuccessMessage: true
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,468 @@
|
|||||||
|
<!-- 审批详情 -->
|
||||||
|
<template>
|
||||||
|
<div class="page-container">
|
||||||
|
<section class="panel">
|
||||||
|
<div class="panel-top oa-detail__top">
|
||||||
|
<div class="oa-detail__title-wrap">
|
||||||
|
<el-button class="btn-ghost" @click="goBack">
|
||||||
|
<el-icon><ArrowLeft /></el-icon>
|
||||||
|
返回
|
||||||
|
</el-button>
|
||||||
|
<h2 class="title-bar-heading">{{ detail?.title || '审批详情' }}</h2>
|
||||||
|
<el-tag v-if="detail?.status" size="small" :type="statusType(detail.status)">
|
||||||
|
{{ statusText(detail.status) }}
|
||||||
|
</el-tag>
|
||||||
|
</div>
|
||||||
|
<div class="oa-detail__actions">
|
||||||
|
<el-button
|
||||||
|
v-if="canApprove"
|
||||||
|
class="btn-main"
|
||||||
|
type="primary"
|
||||||
|
:loading="acting"
|
||||||
|
@click="act('approve')"
|
||||||
|
>
|
||||||
|
<el-icon><Check /></el-icon>
|
||||||
|
同意
|
||||||
|
</el-button>
|
||||||
|
<el-button v-if="canApprove" class="btn-ghost" :loading="acting" @click="act('reject')">
|
||||||
|
<el-icon><Close /></el-icon>
|
||||||
|
拒绝
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-if="canApprove && detail?.allowAddSign !== false"
|
||||||
|
class="btn-ghost"
|
||||||
|
:loading="acting"
|
||||||
|
@click="openAddSign"
|
||||||
|
>
|
||||||
|
<el-icon><Plus /></el-icon>
|
||||||
|
加签
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-if="detail?.status === 'running' && isInitiator"
|
||||||
|
class="btn-ghost"
|
||||||
|
:loading="acting"
|
||||||
|
@click="withdraw"
|
||||||
|
>
|
||||||
|
<el-icon><RefreshLeft /></el-icon>
|
||||||
|
撤销
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-loading="loading" class="oa-detail__body">
|
||||||
|
<section class="oa-detail__section">
|
||||||
|
<h3>表单内容</h3>
|
||||||
|
<div class="oa-detail__form-wrap">
|
||||||
|
<form-create
|
||||||
|
v-if="viewRule.length"
|
||||||
|
v-model="viewFormData"
|
||||||
|
:rule="viewRule"
|
||||||
|
:option="viewOption"
|
||||||
|
/>
|
||||||
|
<pre v-else class="oa-detail__json">{{ prettyForm }}</pre>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="oa-detail__section">
|
||||||
|
<h3>审批进度</h3>
|
||||||
|
<div class="oa-detail__timeline-wrap">
|
||||||
|
<el-timeline v-if="timelineItems.length">
|
||||||
|
<el-timeline-item
|
||||||
|
v-for="item in timelineItems"
|
||||||
|
:key="item.key"
|
||||||
|
:type="timelineType(item.status)"
|
||||||
|
:timestamp="item.timestamp"
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<div class="oa-tl">
|
||||||
|
<div class="oa-tl__title">
|
||||||
|
{{ item.title }}
|
||||||
|
<el-tag size="small" :type="statusType(item.status)">
|
||||||
|
{{ statusText(item.status) }}
|
||||||
|
</el-tag>
|
||||||
|
<el-tag v-if="item.taskType === 'cc'" size="small" type="info">抄送</el-tag>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="item.assignee && item.kind !== 'pending_node' && item.kind !== 'terminal'"
|
||||||
|
class="oa-tl__meta"
|
||||||
|
>
|
||||||
|
处理人:{{ item.assignee }}
|
||||||
|
</div>
|
||||||
|
<div v-if="item.resolveSource" class="oa-tl__meta">
|
||||||
|
解析来源:{{ resolveSourceText(item.resolveSource) }}
|
||||||
|
</div>
|
||||||
|
<div v-if="item.commentText" class="oa-tl__comment">意见:{{ item.commentText }}</div>
|
||||||
|
</div>
|
||||||
|
</el-timeline-item>
|
||||||
|
</el-timeline>
|
||||||
|
<p v-else class="oa-detail__empty">暂无任务记录</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-dialog
|
||||||
|
v-model="addSignVisible"
|
||||||
|
title="加签"
|
||||||
|
width="520px"
|
||||||
|
class="dialog-form-layout"
|
||||||
|
align-center
|
||||||
|
append-to-body
|
||||||
|
modal-class="protocol-dialog-mask"
|
||||||
|
destroy-on-close
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
>
|
||||||
|
<el-form label-position="top">
|
||||||
|
<el-form-item label="加签人">
|
||||||
|
<el-select
|
||||||
|
v-model="addSignUserIds"
|
||||||
|
multiple
|
||||||
|
filterable
|
||||||
|
collapse-tags
|
||||||
|
collapse-tags-tooltip
|
||||||
|
placeholder="选择加签用户"
|
||||||
|
style="width: 100%"
|
||||||
|
:loading="userLoading"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="u in userOptions"
|
||||||
|
:key="u.id"
|
||||||
|
:label="`${u.nickName}(${u.userNo})`"
|
||||||
|
:value="u.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="说明(可选)">
|
||||||
|
<el-input v-model="addSignComment" type="textarea" :rows="2" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button class="btn-ghost" @click="addSignVisible = false">
|
||||||
|
<el-icon><Close /></el-icon>
|
||||||
|
取消
|
||||||
|
</el-button>
|
||||||
|
<el-button class="btn-main" type="primary" :loading="acting" @click="submitAddSign">
|
||||||
|
<el-icon><Check /></el-icon>
|
||||||
|
确定
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, onMounted, ref } from 'vue'
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { ArrowLeft, Check, Close, RefreshLeft, Plus } from '@element-plus/icons-vue'
|
||||||
|
import { fetchUserList } from '@/api/admin'
|
||||||
|
import {
|
||||||
|
fetchAddSignOaTask,
|
||||||
|
fetchApproveOaTask,
|
||||||
|
fetchOaInstanceDetail,
|
||||||
|
fetchRejectOaTask,
|
||||||
|
fetchWithdrawOaInstance
|
||||||
|
} from '@/api/oaApproval'
|
||||||
|
import { MAX_ADMIN_LIST_PAGE_SIZE } from '@/utils/fetch-paged-list'
|
||||||
|
import { useUserStore } from '@/store/modules/user'
|
||||||
|
|
||||||
|
defineOptions({ name: 'OaApprovalInstanceDetail' })
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const loading = ref(false)
|
||||||
|
const acting = ref(false)
|
||||||
|
const detail = ref<any>(null)
|
||||||
|
const viewFormData = ref<Record<string, any>>({})
|
||||||
|
const viewRule = ref<any[]>([])
|
||||||
|
const viewOption = ref({
|
||||||
|
form: { disabled: true, labelPosition: 'top' },
|
||||||
|
submitBtn: false,
|
||||||
|
resetBtn: false
|
||||||
|
})
|
||||||
|
const addSignVisible = ref(false)
|
||||||
|
const addSignUserIds = ref<string[]>([])
|
||||||
|
const addSignComment = ref('')
|
||||||
|
const userLoading = ref(false)
|
||||||
|
const userOptions = ref<Array<{ id: string; nickName: string; userNo: string }>>([])
|
||||||
|
|
||||||
|
const prettyForm = computed(() => JSON.stringify(detail.value?.formDataJson || {}, null, 2))
|
||||||
|
|
||||||
|
/** 进度以详情 API progress 投影为准(勿回落无序 tasks) */
|
||||||
|
const timelineItems = computed(() => {
|
||||||
|
const list = detail.value?.progress
|
||||||
|
if (!Array.isArray(list) || !list.length) return []
|
||||||
|
return list.map((p: any, idx: number) => ({
|
||||||
|
key: `${p.kind}-${p.taskId || p.nodeKey || idx}-${p.roundNo || 1}`,
|
||||||
|
title: p.title || '-',
|
||||||
|
status: p.status || 'info',
|
||||||
|
timestamp: p.timestamp || '',
|
||||||
|
assignee: p.assigneeUserName || p.assigneeUserId || '',
|
||||||
|
resolveSource: p.resolveSource || '',
|
||||||
|
commentText: p.commentText || '',
|
||||||
|
taskType: p.taskType || '',
|
||||||
|
kind: p.kind || '',
|
||||||
|
roundNo: p.roundNo || 1
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
const currentUserId = computed(() => {
|
||||||
|
const info = userStore.info || userStore.getUserInfo
|
||||||
|
return String(info?.id || info?.userId || '').trim()
|
||||||
|
})
|
||||||
|
|
||||||
|
const isInitiator = computed(
|
||||||
|
() => !!detail.value && detail.value.initiatorUserId === currentUserId.value
|
||||||
|
)
|
||||||
|
|
||||||
|
const myPendingTask = computed(() => {
|
||||||
|
const uid = currentUserId.value
|
||||||
|
const list = detail.value?.tasks || []
|
||||||
|
return list.find(
|
||||||
|
(t: any) => t.taskType === 'approval' && t.status === 'pending' && t.assigneeUserId === uid
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const canApprove = computed(() => !!myPendingTask.value)
|
||||||
|
|
||||||
|
function statusType(s: string) {
|
||||||
|
if (s === 'pending' || s === 'running') return 'warning'
|
||||||
|
if (s === 'approved' || s === 'completed' || s === 'done' || s === 'submitted') return 'success'
|
||||||
|
if (s === 'rejected' || s === 'cancelled') return 'danger'
|
||||||
|
if (s === 'not_reached') return 'info'
|
||||||
|
return 'info'
|
||||||
|
}
|
||||||
|
|
||||||
|
function statusText(s: string) {
|
||||||
|
const map: Record<string, string> = {
|
||||||
|
pending: '待处理',
|
||||||
|
running: '进行中',
|
||||||
|
approved: '已同意',
|
||||||
|
rejected: '已拒绝',
|
||||||
|
completed: '已完成',
|
||||||
|
cancelled: '已撤销',
|
||||||
|
done: '已结束',
|
||||||
|
submitted: '已提交',
|
||||||
|
not_reached: '未到达'
|
||||||
|
}
|
||||||
|
return map[s] || s
|
||||||
|
}
|
||||||
|
|
||||||
|
function timelineType(s: string) {
|
||||||
|
if (s === 'approved' || s === 'completed' || s === 'done' || s === 'submitted') return 'success'
|
||||||
|
if (s === 'rejected' || s === 'cancelled') return 'danger'
|
||||||
|
if (s === 'pending') return 'warning'
|
||||||
|
if (s === 'not_reached') return 'info'
|
||||||
|
return 'primary'
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveSourceText(src: string) {
|
||||||
|
const map: Record<string, string> = {
|
||||||
|
users: '指定成员',
|
||||||
|
oa_roles: '工作流角色',
|
||||||
|
dept_leader: '部门负责人',
|
||||||
|
direct_supervisor: '直属主管',
|
||||||
|
supervisor_fallback_dept_leader: '部门负责人(主管回退)',
|
||||||
|
initiator_select: '发起人自选'
|
||||||
|
}
|
||||||
|
return map[src] || src
|
||||||
|
}
|
||||||
|
|
||||||
|
function applySchema(schema: any, data: any) {
|
||||||
|
viewFormData.value = { ...(data || {}) }
|
||||||
|
const rule = Array.isArray(schema?.rule) ? schema.rule : Array.isArray(schema) ? schema : []
|
||||||
|
viewRule.value = rule
|
||||||
|
viewOption.value = {
|
||||||
|
...(schema?.option || {}),
|
||||||
|
form: { ...(schema?.option?.form || {}), disabled: true, labelPosition: 'top' },
|
||||||
|
submitBtn: false,
|
||||||
|
resetBtn: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function load() {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
detail.value = await fetchOaInstanceDetail(String(route.params.id || ''))
|
||||||
|
applySchema(detail.value?.formSchemaJson, detail.value?.formDataJson)
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function act(kind: 'approve' | 'reject') {
|
||||||
|
const task = myPendingTask.value
|
||||||
|
if (!task) return
|
||||||
|
let comment = ''
|
||||||
|
try {
|
||||||
|
const res = await ElMessageBox.prompt(
|
||||||
|
'请输入审批意见(可选)',
|
||||||
|
kind === 'approve' ? '同意' : '拒绝',
|
||||||
|
{
|
||||||
|
inputPlaceholder: '意见',
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
comment = res.value || ''
|
||||||
|
} catch {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
acting.value = true
|
||||||
|
try {
|
||||||
|
if (kind === 'approve') await fetchApproveOaTask(task.id, comment || undefined)
|
||||||
|
else await fetchRejectOaTask(task.id, comment || undefined)
|
||||||
|
await load()
|
||||||
|
} finally {
|
||||||
|
acting.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function withdraw() {
|
||||||
|
await ElMessageBox.confirm('确定撤销该审批吗?', '提示', { type: 'warning' })
|
||||||
|
acting.value = true
|
||||||
|
try {
|
||||||
|
await fetchWithdrawOaInstance(String(route.params.id || ''))
|
||||||
|
await load()
|
||||||
|
} finally {
|
||||||
|
acting.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadUsers() {
|
||||||
|
userLoading.value = true
|
||||||
|
try {
|
||||||
|
const pageSize = MAX_ADMIN_LIST_PAGE_SIZE
|
||||||
|
const raw: any[] = []
|
||||||
|
let page = 1
|
||||||
|
for (;;) {
|
||||||
|
const res = await fetchUserList({ page, pageSize, status: '1' })
|
||||||
|
const list = res?.list || []
|
||||||
|
raw.push(...list)
|
||||||
|
if (list.length < pageSize) break
|
||||||
|
if (typeof res?.total === 'number' && raw.length >= res.total) break
|
||||||
|
page += 1
|
||||||
|
}
|
||||||
|
userOptions.value = raw.map((u) => ({
|
||||||
|
id: u.id,
|
||||||
|
nickName: u.nickName || '',
|
||||||
|
userNo: u.userNo || ''
|
||||||
|
}))
|
||||||
|
} finally {
|
||||||
|
userLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openAddSign() {
|
||||||
|
addSignUserIds.value = []
|
||||||
|
addSignComment.value = ''
|
||||||
|
addSignVisible.value = true
|
||||||
|
if (!userOptions.value.length) await loadUsers()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitAddSign() {
|
||||||
|
const task = myPendingTask.value
|
||||||
|
if (!task) return
|
||||||
|
if (!addSignUserIds.value.length) {
|
||||||
|
ElMessage.warning('请选择加签人')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
acting.value = true
|
||||||
|
try {
|
||||||
|
await fetchAddSignOaTask(task.id, {
|
||||||
|
userIds: addSignUserIds.value,
|
||||||
|
comment: addSignComment.value || undefined
|
||||||
|
})
|
||||||
|
addSignVisible.value = false
|
||||||
|
await load()
|
||||||
|
} finally {
|
||||||
|
acting.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function goBack() {
|
||||||
|
router.back()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(load)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.oa-detail__top {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.oa-detail__title-wrap {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.oa-detail__actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.oa-detail__body {
|
||||||
|
padding: 0 20px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.oa-detail__section {
|
||||||
|
max-width: 960px;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 16px 0 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--mint-text, #0f172a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.oa-detail__form-wrap {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 4px 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.oa-detail__json {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow: auto;
|
||||||
|
font-size: 12px;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.oa-detail__empty {
|
||||||
|
color: var(--mint-text-muted, #94a3b8);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.oa-tl__title {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.oa-tl__meta {
|
||||||
|
margin-top: 4px;
|
||||||
|
color: var(--mint-text-muted, #64748b);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.oa-tl__comment {
|
||||||
|
margin-top: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in new issue
Block a user