feat(management): 接入文档用户权限与报表

This commit is contained in:
yelan
2026-09-01 10:13:11 +08:00
parent beeb98d18f
commit e467f03f23
12 changed files with 2361 additions and 41 deletions
@@ -1,6 +1,11 @@
<script setup lang="ts">
import { computed, onMounted, reactive, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { useRouter } from 'vue-router'
import { getCampuses, getDepartments } from '@/api/management/organization'
import { getSigningTasks, getSigningTemplates, isSigningMockEnabled } from '@/api/workbench/signing'
import type { SigningTaskRecord, SigningTemplate, WorkbenchCampus } from '@/api/workbench/types'
import DepartmentDistributionChart from './components/DepartmentDistributionChart.vue'
import ReportDetailTable from './components/ReportDetailTable.vue'
@@ -22,12 +27,16 @@ import type {
ReportFilterForm,
ReportMetricCard as ReportMetricCardData,
ReportPeriodOption,
ReportSelectOption,
ReportTask,
ReportTaskStatus,
ReportTrendPoint,
} from './types'
const loading = ref(true)
const tasks = ref<ReportTask[]>([])
const departmentOptions = ref<ReportSelectOption[]>(reportDepartmentOptions)
const router = useRouter()
const filters = reactive<ReportFilterForm>({
period: '14d',
@@ -145,21 +154,159 @@ function updateCategory(value: ReportDocumentCategory) {
}
function handleExport() {
ElMessage.info('原型演示:导出 Excel')
const escapeCsv = (value: string | number) => `"${String(value).replaceAll('"', '""')}"`
const header = ['任务号', '文档', '科室', '院区', '方式', '状态', '发起时间']
const rows = filteredTasks.value.map((task) => [
task.id,
task.documentName,
task.department,
task.campus,
task.method === 'pad' ? '手写板' : '短信',
getReportStatusLabel(task.status),
formatExportDate(task.createdAt),
])
const csv = `\uFEFF${[header, ...rows].map((row) => row.map(escapeCsv).join(',')).join('\n')}`
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' })
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = `医签通签署报表-${new Date().toISOString().slice(0, 10)}.csv`
link.click()
window.setTimeout(() => URL.revokeObjectURL(url), 0)
ElMessage.success('报表已导出')
}
function handleView(row: ReportTask) {
ElMessage.info(`原型演示:查看任务${row.id},签署工作台暂未实现`)
void router.push({ name: 'workbench-signing', query: { taskId: row.id } })
}
function getReportStatusLabel(status: ReportTaskStatus) {
const labels: Record<ReportTaskStatus, string> = {
signed: '已签署',
pending: '待签署',
expired: '已超时',
void: '已作废',
}
return labels[status]
}
function formatExportDate(value: Date) {
const pad = (part: number) => String(part).padStart(2, '0')
return `${value.getFullYear()}-${pad(value.getMonth() + 1)}-${pad(value.getDate())} ${pad(
value.getHours(),
)}:${pad(value.getMinutes())}`
}
function mapCategory(
category: string,
documentName: string,
): Exclude<ReportDocumentCategory, 'all'> {
const value = `${category} ${documentName}`
if (value.includes('入院') || value.includes('住院')) {
return 'admission'
}
if (value.includes('风险') || value.includes('告知')) {
return 'risk'
}
return 'consent'
}
function mapReportTask(task: SigningTaskRecord, templates: SigningTemplate[]): ReportTask {
const template = templates.find(
(item) => item.versionId === task.templateVersionId || item.id === task.documentId,
)
const createdAt = new Date((task.createdAt ?? task.updatedAt).replace(' ', 'T'))
const normalizedCreatedAt = Number.isNaN(createdAt.getTime()) ? new Date() : createdAt
const status: ReportTask['status'] =
task.status === 'signed'
? 'signed'
: task.status === 'expired'
? 'expired'
: task.status === 'void' || task.status === 'failed' || task.status === 'rejected'
? 'void'
: 'pending'
return {
id: task.id,
patientName: task.patientName,
patientId: task.patientId,
documentName: task.documentName,
department: task.department,
campus: task.campus as Exclude<ReportCampus, 'all'>,
category: mapCategory(template?.category ?? '', task.documentName),
method: task.method,
status,
createdAt: normalizedCreatedAt,
}
}
function isWorkbenchCampus(value: string): value is WorkbenchCampus {
return value === '本部院区' || value === '东院区' || value === '西院区'
}
async function loadReports() {
loading.value = true
await Promise.resolve()
tasks.value = reportTasks.map((task) => ({
...task,
createdAt: new Date(task.createdAt),
}))
loading.value = false
try {
if (isSigningMockEnabled) {
tasks.value = reportTasks.map((task) => ({
...task,
createdAt: new Date(task.createdAt),
}))
departmentOptions.value = reportDepartmentOptions
return
}
const [templatesResult, campusesResult, departmentsResult] = await Promise.allSettled([
getSigningTemplates(),
getCampuses(),
getDepartments(),
])
const templates = templatesResult.status === 'fulfilled' ? templatesResult.value : []
const campusNames: Record<string, WorkbenchCampus> =
campusesResult.status === 'fulfilled'
? (Object.fromEntries(
campusesResult.value
.filter((campus) => isWorkbenchCampus(campus.name))
.map((campus) => [campus.id, campus.name]),
) as Record<string, WorkbenchCampus>)
: {}
const taskResponse = await getSigningTasks(
{
page: 1,
pageSize: 200,
status: 'all',
dateRange: 'all',
campus: 'all',
method: 'all',
},
{ templates, campusNames },
)
tasks.value = taskResponse.records.map((task) => mapReportTask(task, templates))
const departmentNames = new Set<string>(
departmentsResult.status === 'fulfilled'
? departmentsResult.value.map((department) => department.name)
: [],
)
tasks.value.forEach((task) => departmentNames.add(task.department))
departmentOptions.value = [
{ value: 'all', label: '全部科室' },
...[...departmentNames]
.filter((name) => name && name !== '未指定科室')
.sort((left, right) => left.localeCompare(right, 'zh-CN'))
.map((name) => ({ value: name, label: name })),
]
} catch {
tasks.value = []
ElMessage.error('报表数据加载失败,请稍后重试')
} finally {
loading.value = false
}
}
onMounted(loadReports)
@@ -174,7 +321,7 @@ onMounted(loadReports)
:category="filters.category"
:period-options="reportPeriodOptions"
:campus-options="reportCampusOptions"
:department-options="reportDepartmentOptions"
:department-options="departmentOptions"
:category-options="reportCategoryOptions"
@update:period="updatePeriod"
@update:campus="updateCampus"