feat(web-admin): add OaAppHistoryDialog for per-app instance history
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
107bf66586
commit
7cd6a87203
1 file changed
+388
@@ -0,0 +1,388 @@
|
||||
<!-- OA 发起审批 · 单流程历史记录弹窗 -->
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
class="dialog-form-layout"
|
||||
:title="`历史记录 · ${appName}`"
|
||||
width="1080px"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
align-center
|
||||
modal-class="protocol-dialog-mask"
|
||||
@closed="onClosed"
|
||||
>
|
||||
<div class="filter-block">
|
||||
<el-form :model="searchForm" class="filter-form" @submit.prevent>
|
||||
<div class="filter-grid">
|
||||
<el-form-item label="流程名称">
|
||||
<el-input :model-value="appName" readonly placeholder="流程名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="审批状态">
|
||||
<el-select v-model="searchForm.instanceStatus" placeholder="全部" clearable>
|
||||
<el-option label="全部" value="" />
|
||||
<el-option label="进行中" value="running" />
|
||||
<el-option label="已完成" value="completed" />
|
||||
<el-option label="已拒绝" value="rejected" />
|
||||
<el-option label="已打回" value="returned" />
|
||||
<el-option label="已撤销" value="cancelled" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="审批 ID">
|
||||
<el-input
|
||||
v-model="searchForm.instanceId"
|
||||
placeholder="请输入审批 ID"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="发起时间">
|
||||
<el-date-picker
|
||||
v-model="applyTimeRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="完成时间">
|
||||
<el-date-picker
|
||||
v-model="finishTimeRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="canViewAll" label="申请人">
|
||||
<el-input
|
||||
v-model="searchForm.initiatorName"
|
||||
placeholder="请输入申请人"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="filter-actions-row">
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">
|
||||
<el-icon><RefreshLeft /></el-icon>
|
||||
重置
|
||||
</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch">
|
||||
<el-icon><Search /></el-icon>
|
||||
搜索
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<div class="list-body list-wrap">
|
||||
<el-table
|
||||
class="list-table"
|
||||
:data="rows"
|
||||
v-loading="loading"
|
||||
stripe
|
||||
empty-text="暂无历史记录"
|
||||
>
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="68"
|
||||
align="center"
|
||||
header-align="center"
|
||||
:index="indexMethod"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="id"
|
||||
label="审批 ID"
|
||||
min-width="160"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">{{ row.id || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="appName"
|
||||
label="流程名称"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">{{ row.appName || appName || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="initiatorUserName"
|
||||
label="申请人"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.initiatorUserName || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-for="label in previewFieldLabels"
|
||||
:key="label"
|
||||
:label="label"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">{{ previewValue(row, label) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="applyTime"
|
||||
label="发起时间"
|
||||
min-width="170"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.applyTime || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="finishTime"
|
||||
label="完成时间"
|
||||
min-width="170"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.finishTime || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="instanceStatus"
|
||||
label="审批状态"
|
||||
min-width="110"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small" :type="statusType(row.instanceStatus || row.status)">
|
||||
{{ statusText(row.instanceStatus || row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="currentNodeName"
|
||||
label="当前节点"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">{{ row.currentNodeName || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="260" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item primary" @click="openDetail(row)">
|
||||
<el-icon><View /></el-icon>
|
||||
查看
|
||||
</el-button>
|
||||
<el-button
|
||||
class="btn-op-item"
|
||||
:loading="printingId === row.id"
|
||||
@click="printRow(row)"
|
||||
>
|
||||
<el-icon><Printer /></el-icon>
|
||||
打印
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="pagination list-pager-bar">
|
||||
<el-pagination
|
||||
v-model:current-page="page"
|
||||
v-model:page-size="pageSize"
|
||||
:total="total"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
background
|
||||
@size-change="load"
|
||||
@current-change="load"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Printer, RefreshLeft, Search, View } from '@element-plus/icons-vue'
|
||||
import { fetchOaInstanceDetail, fetchOaInstanceHistory } from '@/api/oaApproval'
|
||||
import { useMenuPerm } from '@/hooks/core/useMenuPerm'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { exportOaApprovalPdf } from '@/utils/oa-approval-print'
|
||||
|
||||
defineOptions({ name: 'OaAppHistoryDialog' })
|
||||
|
||||
const OA_START_HISTORY_ALL = 'oa:start:history:all'
|
||||
const { hasMenuPerm } = useMenuPerm()
|
||||
const canViewAll = computed(() => hasMenuPerm(OA_START_HISTORY_ALL))
|
||||
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const visible = ref(false)
|
||||
const appId = ref('')
|
||||
const appName = ref('')
|
||||
const loading = ref(false)
|
||||
const printingId = ref('')
|
||||
const rows = ref<any[]>([])
|
||||
const page = ref(1)
|
||||
const pageSize = ref(20)
|
||||
const total = ref(0)
|
||||
const applyTimeRange = ref<string[] | null>(null)
|
||||
const finishTimeRange = ref<string[] | null>(null)
|
||||
const searchForm = reactive({
|
||||
instanceStatus: '',
|
||||
instanceId: '',
|
||||
initiatorName: ''
|
||||
})
|
||||
|
||||
const previewFieldLabels = computed(() => {
|
||||
const labels: string[] = []
|
||||
for (const row of rows.value) {
|
||||
for (const f of row.previewFields || []) {
|
||||
const lb = String(f?.label || '').trim()
|
||||
if (lb && !labels.includes(lb)) labels.push(lb)
|
||||
}
|
||||
}
|
||||
return labels.slice(0, 6)
|
||||
})
|
||||
|
||||
function indexMethod(index: number) {
|
||||
return (page.value - 1) * pageSize.value + index + 1
|
||||
}
|
||||
|
||||
function previewValue(row: any, label: string) {
|
||||
const hit = (row.previewFields || []).find((f: any) => f.label === label)
|
||||
return hit?.value || '-'
|
||||
}
|
||||
|
||||
function currentPrinterName() {
|
||||
const info: any = userStore.info || userStore.getUserInfo || {}
|
||||
return String(
|
||||
info.realName || info.nickName || info.userName || info.username || ''
|
||||
).trim()
|
||||
}
|
||||
|
||||
function statusType(s: string) {
|
||||
if (s === 'pending' || s === 'running') return 'warning'
|
||||
if (s === 'approved' || s === 'completed' || s === 'done') return 'success'
|
||||
if (s === 'rejected' || s === 'cancelled' || s === 'returned') return 'danger'
|
||||
return 'info'
|
||||
}
|
||||
|
||||
function statusText(s: string) {
|
||||
if (!s) return '-'
|
||||
const map: Record<string, string> = {
|
||||
pending: '待处理',
|
||||
running: '进行中',
|
||||
approved: '已同意',
|
||||
rejected: '已拒绝',
|
||||
completed: '已完成',
|
||||
cancelled: '已撤销',
|
||||
done: '已结束',
|
||||
returned: '已打回',
|
||||
transferred: '已转交'
|
||||
}
|
||||
return map[s] || s
|
||||
}
|
||||
|
||||
function resetFilters() {
|
||||
searchForm.instanceStatus = ''
|
||||
searchForm.instanceId = ''
|
||||
searchForm.initiatorName = ''
|
||||
applyTimeRange.value = null
|
||||
finishTimeRange.value = null
|
||||
page.value = 1
|
||||
}
|
||||
|
||||
function open(app: { id: string; appName?: string }) {
|
||||
appId.value = String(app?.id || '').trim()
|
||||
appName.value = String(app?.appName || '').trim()
|
||||
resetFilters()
|
||||
visible.value = true
|
||||
void load()
|
||||
}
|
||||
|
||||
function onClosed() {
|
||||
rows.value = []
|
||||
total.value = 0
|
||||
printingId.value = ''
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
page.value = 1
|
||||
void load()
|
||||
}
|
||||
|
||||
function handleReset() {
|
||||
resetFilters()
|
||||
void load()
|
||||
}
|
||||
|
||||
async function load() {
|
||||
if (!appId.value) return
|
||||
loading.value = true
|
||||
try {
|
||||
const params: any = {
|
||||
appId: appId.value,
|
||||
instanceId: searchForm.instanceId || undefined,
|
||||
instanceStatus: searchForm.instanceStatus || undefined,
|
||||
applyTimeFrom: applyTimeRange.value?.[0],
|
||||
applyTimeTo: applyTimeRange.value?.[1],
|
||||
finishTimeFrom: finishTimeRange.value?.[0],
|
||||
finishTimeTo: finishTimeRange.value?.[1],
|
||||
page: page.value,
|
||||
pageSize: pageSize.value
|
||||
}
|
||||
if (canViewAll.value && searchForm.initiatorName) {
|
||||
params.initiatorName = searchForm.initiatorName
|
||||
}
|
||||
const data = await fetchOaInstanceHistory(params)
|
||||
rows.value = data?.list || []
|
||||
total.value = Number(data?.total || 0)
|
||||
} catch {
|
||||
rows.value = []
|
||||
total.value = 0
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openDetail(row: any) {
|
||||
const id = row?.id
|
||||
if (!id) return
|
||||
visible.value = false
|
||||
router.push({ path: `/system-manage/oa-approval/instances/${id}` })
|
||||
}
|
||||
|
||||
async function printRow(row: any) {
|
||||
const id = String(row?.id || '').trim()
|
||||
if (!id || printingId.value) return
|
||||
printingId.value = id
|
||||
let detail: any
|
||||
try {
|
||||
detail = await fetchOaInstanceDetail(id)
|
||||
} catch {
|
||||
printingId.value = ''
|
||||
ElMessage.error('加载审批单失败')
|
||||
return
|
||||
}
|
||||
try {
|
||||
await exportOaApprovalPdf(detail, { printerName: currentPrinterName() })
|
||||
ElMessage.success('PDF 已下载')
|
||||
} catch {
|
||||
ElMessage.error('导出 PDF 失败')
|
||||
} finally {
|
||||
printingId.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
Reference in new issue
Block a user