refactor(documents): 文档库改用本地真实文书内容
This commit is contained in:
@@ -2,20 +2,9 @@
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
import { getCampuses, getDepartments } from '@/api/management/organization'
|
||||
import {
|
||||
createTemplate,
|
||||
createTemplateVersion,
|
||||
getDocumentDetail,
|
||||
getDocuments,
|
||||
getTemplateVersionDetail,
|
||||
isDocumentMockEnabled,
|
||||
updateTemplateVersionStatus,
|
||||
} from '@/api/management/documents'
|
||||
import type { TemplateVersionAction } from '@/api/management/documents'
|
||||
import type { CampusRecord, DepartmentRecord, DocumentRecord } from '@/api/management/types'
|
||||
import NewSigningTaskDialog from '@/components/signing/NewSigningTaskDialog.vue'
|
||||
import type { SigningTemplate } from '@/api/workbench/types'
|
||||
import type { CampusRecord, DepartmentRecord } from '@/api/management/types'
|
||||
|
||||
import DocumentFilterTabs from './components/DocumentFilterTabs.vue'
|
||||
import DocumentLibraryHeader from './components/DocumentLibraryHeader.vue'
|
||||
@@ -23,6 +12,7 @@ import DocumentPreviewDialog from './components/DocumentPreviewDialog.vue'
|
||||
import DocumentTemplateCard from './components/DocumentTemplateCard.vue'
|
||||
import TemplateEditorDialog from './components/TemplateEditorDialog.vue'
|
||||
import { documentLibrary, documentStatusOptions } from './mock'
|
||||
import type { TemplateVersionAction } from './types'
|
||||
import type {
|
||||
DepartmentTab,
|
||||
DocumentStatus,
|
||||
@@ -33,41 +23,33 @@ import type {
|
||||
TemplateEditorForm,
|
||||
} from './types'
|
||||
|
||||
const loading = ref(true)
|
||||
// 原型阶段:文档库数据与文书内容全部来自本地真实附件(mock.ts + contents.ts),暂不调用后端接口。
|
||||
// 后续接入时,仅需将 loadLibrary / saveTemplate / handleWorkflow 中的本地数据操作替换为文档模板接口。
|
||||
|
||||
const library = ref<DocumentDepartment[]>([])
|
||||
const selectedTemplate = ref<DocumentTemplate | null>(null)
|
||||
const signingTemplate = ref<SigningTemplate | null>(null)
|
||||
const editorTemplate = ref<DocumentTemplate | null>(null)
|
||||
const campuses = ref<CampusRecord[]>([])
|
||||
const departments = ref<DepartmentRecord[]>([])
|
||||
const signingDialogVisible = ref(false)
|
||||
const editorVisible = ref(false)
|
||||
const editorSaving = ref(false)
|
||||
const workflowSaving = ref(false)
|
||||
const previewLoading = ref(false)
|
||||
const previewError = ref(false)
|
||||
let previewRequestId = 0
|
||||
|
||||
const mockCampuses: CampusRecord[] = [
|
||||
{ id: 'mock-main', code: 'MAIN', name: '本部院区', status: 'ENABLED', sortNo: 1 },
|
||||
{ id: 'mock-east', code: 'EAST', name: '东院区', status: 'ENABLED', sortNo: 2 },
|
||||
{ id: 'mock-west', code: 'WEST', name: '西院区', status: 'ENABLED', sortNo: 3 },
|
||||
const campuses: CampusRecord[] = [
|
||||
{ id: 'campus-main', code: 'MAIN', name: '本部院区', status: 'ENABLED', sortNo: 1 },
|
||||
{ id: 'campus-fengxian', code: 'FENGXIAN', name: '奉贤院区', status: 'ENABLED', sortNo: 2 },
|
||||
]
|
||||
|
||||
const mockDepartments: DepartmentRecord[] = [
|
||||
'放射科',
|
||||
'心内科',
|
||||
'骨科',
|
||||
'健康管理中心',
|
||||
'全院通用',
|
||||
].map((name, index) => ({
|
||||
id: `mock-template-department-${index + 1}`,
|
||||
campusId: index === 3 ? 'mock-east' : 'mock-main',
|
||||
code: `MOCK-TPL-${index + 1}`,
|
||||
name,
|
||||
status: 'ENABLED',
|
||||
sortNo: index + 1,
|
||||
}))
|
||||
const departments: DepartmentRecord[] = ['医学影像科', '医学检验科', '临床遗传中心'].map(
|
||||
(name, index) => ({
|
||||
id: `template-department-${index + 1}`,
|
||||
campusId: 'campus-main',
|
||||
code: `TPL-DEPT-${index + 1}`,
|
||||
name,
|
||||
status: 'ENABLED',
|
||||
sortNo: index + 1,
|
||||
}),
|
||||
)
|
||||
|
||||
const filters = reactive<DocumentFilterForm>({
|
||||
keyword: '',
|
||||
@@ -119,169 +101,26 @@ const filteredTemplates = computed(() => {
|
||||
})
|
||||
})
|
||||
|
||||
async function loadLibrary() {
|
||||
loading.value = true
|
||||
try {
|
||||
if (isDocumentMockEnabled) {
|
||||
campuses.value = mockCampuses.map((campus) => ({ ...campus }))
|
||||
departments.value = mockDepartments.map((department) => ({ ...department }))
|
||||
library.value = documentLibrary.map((department) => ({
|
||||
...department,
|
||||
categories: department.categories.map((category) => ({
|
||||
...category,
|
||||
documents: category.documents.map((document) => ({ ...document })),
|
||||
})),
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
const [documentsResult, campusesResult, departmentsResult] = await Promise.allSettled([
|
||||
getDocuments({ page: 1, pageSize: 200, status: 'all' }),
|
||||
getCampuses(),
|
||||
getDepartments(),
|
||||
])
|
||||
|
||||
if (documentsResult.status === 'rejected') {
|
||||
throw documentsResult.reason
|
||||
}
|
||||
|
||||
campuses.value = campusesResult.status === 'fulfilled' ? campusesResult.value : []
|
||||
departments.value = departmentsResult.status === 'fulfilled' ? departmentsResult.value : []
|
||||
library.value = buildDocumentLibrary(
|
||||
documentsResult.value.records,
|
||||
new Map(departments.value.map((department) => [department.id, department.name])),
|
||||
new Map(campuses.value.map((campus) => [campus.id, campus.name])),
|
||||
)
|
||||
} catch {
|
||||
library.value = []
|
||||
ElMessage.error('文档模板加载失败,请稍后重试')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
function cloneLibrary(): DocumentDepartment[] {
|
||||
return documentLibrary.map((department) => ({
|
||||
...department,
|
||||
categories: department.categories.map((category) => ({
|
||||
...category,
|
||||
documents: category.documents.map((document) => ({ ...document })),
|
||||
})),
|
||||
}))
|
||||
}
|
||||
|
||||
function mapDocumentStatus(status: DocumentRecord['status']): DocumentStatus {
|
||||
if (status === 'published') {
|
||||
return 'enabled'
|
||||
}
|
||||
|
||||
if (status === 'archived') {
|
||||
return 'archived'
|
||||
}
|
||||
|
||||
if (status === 'review') {
|
||||
return 'review'
|
||||
}
|
||||
|
||||
return 'draft'
|
||||
function loadLibrary() {
|
||||
library.value = cloneLibrary()
|
||||
}
|
||||
|
||||
function buildDocumentLibrary(
|
||||
records: DocumentRecord[],
|
||||
departmentNames = new Map<string, string>(),
|
||||
campusNames = new Map<string, string>(),
|
||||
): DocumentDepartment[] {
|
||||
const departments = new Map<string, DocumentDepartment>()
|
||||
|
||||
records.forEach((record) => {
|
||||
const departmentName =
|
||||
record.departmentName || departmentNames.get(record.departmentId ?? '') || '全院通用'
|
||||
const departmentKey = record.departmentId || departmentName
|
||||
const campusName = record.campusName || campusNames.get(record.campusId ?? '')
|
||||
const department = departments.get(departmentKey) ?? {
|
||||
key: departmentKey,
|
||||
name: departmentName,
|
||||
campus:
|
||||
campusName === '本部院区' || campusName === '东院区' || campusName === '西院区'
|
||||
? campusName
|
||||
: 'all',
|
||||
categories: [],
|
||||
}
|
||||
const category =
|
||||
department.categories.find((item) => item.name === record.category) ??
|
||||
(() => {
|
||||
const nextCategory = { name: record.category, documents: [] }
|
||||
department.categories.push(nextCategory)
|
||||
return nextCategory
|
||||
})()
|
||||
|
||||
category.documents.push({
|
||||
id: record.id,
|
||||
name: record.name,
|
||||
code: record.code ?? record.id,
|
||||
version: record.version,
|
||||
status: mapDocumentStatus(record.status),
|
||||
backendStatus: record.backendStatus,
|
||||
description: record.description ?? '',
|
||||
versionId: record.versionId,
|
||||
departmentId: record.departmentId,
|
||||
campusId: record.campusId,
|
||||
contentHtml: record.contentHtml,
|
||||
contentSha256: record.contentSha256,
|
||||
})
|
||||
departments.set(departmentKey, department)
|
||||
})
|
||||
|
||||
return [...departments.values()]
|
||||
}
|
||||
|
||||
async function handlePreview(template: DocumentTemplate) {
|
||||
function handlePreview(template: DocumentTemplate) {
|
||||
selectedTemplate.value = template
|
||||
previewError.value = false
|
||||
|
||||
if (isDocumentMockEnabled) {
|
||||
previewLoading.value = false
|
||||
return
|
||||
}
|
||||
|
||||
const requestId = ++previewRequestId
|
||||
previewLoading.value = true
|
||||
|
||||
try {
|
||||
const detail = await getDocumentDetail(template.id)
|
||||
|
||||
if (!detail) {
|
||||
throw new Error('模板不存在')
|
||||
}
|
||||
|
||||
let contentHtml = detail.contentHtml
|
||||
const versionId = detail.versionId ?? template.versionId
|
||||
|
||||
if (!contentHtml && versionId) {
|
||||
const version = await getTemplateVersionDetail(template.id, versionId)
|
||||
contentHtml = version?.contentHtml ?? version?.content ?? undefined
|
||||
}
|
||||
|
||||
if (requestId !== previewRequestId) {
|
||||
return
|
||||
}
|
||||
|
||||
selectedTemplate.value = {
|
||||
...template,
|
||||
code: detail.code ?? template.code,
|
||||
version: detail.version || template.version,
|
||||
versionId,
|
||||
description: detail.description ?? template.description,
|
||||
contentHtml,
|
||||
contentSha256: detail.contentSha256,
|
||||
}
|
||||
} catch {
|
||||
if (requestId === previewRequestId) {
|
||||
previewError.value = true
|
||||
ElMessage.error('模板内容加载失败,请稍后重试')
|
||||
}
|
||||
} finally {
|
||||
if (requestId === previewRequestId) {
|
||||
previewLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function closePreview() {
|
||||
previewRequestId += 1
|
||||
selectedTemplate.value = null
|
||||
previewLoading.value = false
|
||||
previewError.value = false
|
||||
}
|
||||
|
||||
function showAddMessage() {
|
||||
@@ -293,35 +132,9 @@ function showExportMessage() {
|
||||
ElMessage.info('原型演示:导出模板包(ZIP)')
|
||||
}
|
||||
|
||||
async function showEditMessage(template: DocumentTemplate) {
|
||||
function showEditMessage(template: DocumentTemplate) {
|
||||
editorTemplate.value = template
|
||||
editorVisible.value = true
|
||||
|
||||
if (isDocumentMockEnabled || template.contentHtml) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const detail = await getDocumentDetail(template.id)
|
||||
const versionId = detail?.versionId ?? template.versionId
|
||||
let contentHtml = detail?.contentHtml
|
||||
|
||||
if (!contentHtml && versionId) {
|
||||
const version = await getTemplateVersionDetail(template.id, versionId)
|
||||
contentHtml = version?.contentHtml ?? version?.content ?? undefined
|
||||
}
|
||||
|
||||
if (editorVisible.value && editorTemplate.value?.id === template.id) {
|
||||
editorTemplate.value = {
|
||||
...template,
|
||||
versionId,
|
||||
description: detail?.description ?? template.description,
|
||||
contentHtml,
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
ElMessage.warning('模板内容加载失败,可先关闭后重试')
|
||||
}
|
||||
}
|
||||
|
||||
function closeEditor() {
|
||||
@@ -329,10 +142,10 @@ function closeEditor() {
|
||||
editorTemplate.value = null
|
||||
}
|
||||
|
||||
function addMockTemplate(form: TemplateEditorForm) {
|
||||
const department = departments.value.find((item) => item.id === form.departmentId)
|
||||
function addLocalTemplate(form: TemplateEditorForm) {
|
||||
const department = departments.find((item) => item.id === form.departmentId)
|
||||
const departmentName = department?.name ?? '全院通用'
|
||||
const departmentKey = department?.id ?? 'mock-general'
|
||||
const departmentKey = department?.id ?? 'local-general'
|
||||
let departmentGroup = library.value.find((item) => item.key === departmentKey)
|
||||
|
||||
if (!departmentGroup) {
|
||||
@@ -354,59 +167,37 @@ function addMockTemplate(form: TemplateEditorForm) {
|
||||
|
||||
const timestamp = Date.now()
|
||||
category.documents.push({
|
||||
id: `mock-template-${timestamp}`,
|
||||
id: `local-template-${timestamp}`,
|
||||
name: form.name,
|
||||
code: form.templateCode,
|
||||
version: form.versionNo || 'v1',
|
||||
status: 'draft',
|
||||
description: form.description,
|
||||
versionId: `mock-version-${timestamp}`,
|
||||
versionId: `local-version-${timestamp}`,
|
||||
departmentId: form.departmentId || null,
|
||||
campusId: form.campusId,
|
||||
contentHtml: form.contentHtml,
|
||||
})
|
||||
}
|
||||
|
||||
async function saveTemplate(form: TemplateEditorForm) {
|
||||
function saveTemplate(form: TemplateEditorForm) {
|
||||
editorSaving.value = true
|
||||
|
||||
try {
|
||||
if (isDocumentMockEnabled) {
|
||||
if (form.id) {
|
||||
library.value.forEach((department) =>
|
||||
department.categories.forEach((category) =>
|
||||
category.documents.forEach((document) => {
|
||||
if (document.id === form.id) {
|
||||
document.version = form.versionNo || document.version
|
||||
document.contentHtml = form.contentHtml
|
||||
document.description = form.description
|
||||
}
|
||||
}),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
addMockTemplate(form)
|
||||
}
|
||||
} else if (form.id) {
|
||||
await createTemplateVersion(form.id, {
|
||||
versionNo: form.versionNo || undefined,
|
||||
contentHtml: form.contentHtml,
|
||||
})
|
||||
await loadLibrary()
|
||||
if (form.id) {
|
||||
library.value.forEach((department) =>
|
||||
department.categories.forEach((category) =>
|
||||
category.documents.forEach((document) => {
|
||||
if (document.id === form.id) {
|
||||
document.version = form.versionNo || document.version
|
||||
document.contentHtml = form.contentHtml
|
||||
document.description = form.description
|
||||
}
|
||||
}),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
const template = await createTemplate({
|
||||
templateCode: form.templateCode,
|
||||
name: form.name,
|
||||
description: form.description || undefined,
|
||||
campusId: form.campusId,
|
||||
departmentId: form.departmentId || null,
|
||||
category: form.category || undefined,
|
||||
})
|
||||
await createTemplateVersion(template.id, {
|
||||
versionNo: form.versionNo || undefined,
|
||||
contentHtml: form.contentHtml,
|
||||
})
|
||||
await loadLibrary()
|
||||
addLocalTemplate(form)
|
||||
}
|
||||
|
||||
closeEditor()
|
||||
@@ -418,8 +209,8 @@ async function saveTemplate(form: TemplateEditorForm) {
|
||||
}
|
||||
}
|
||||
|
||||
function updateMockTemplateStatus(templateId: string, action: TemplateVersionAction) {
|
||||
const nextStatus =
|
||||
function updateLocalTemplateStatus(templateId: string, action: TemplateVersionAction) {
|
||||
const nextStatus: DocumentStatus =
|
||||
action === 'submit-review' || action === 'reject'
|
||||
? 'review'
|
||||
: action === 'disable' || action === 'archive'
|
||||
@@ -437,11 +228,11 @@ function updateMockTemplateStatus(templateId: string, action: TemplateVersionAct
|
||||
)
|
||||
}
|
||||
|
||||
async function handleWorkflow(action: TemplateVersionAction, comment?: string) {
|
||||
function handleWorkflow(action: TemplateVersionAction) {
|
||||
const template = editorTemplate.value
|
||||
const versionId = template?.versionId
|
||||
|
||||
if (!template || (!versionId && !isDocumentMockEnabled)) {
|
||||
if (!template || !versionId) {
|
||||
ElMessage.error('当前模板没有可操作的版本')
|
||||
return
|
||||
}
|
||||
@@ -449,19 +240,9 @@ async function handleWorkflow(action: TemplateVersionAction, comment?: string) {
|
||||
workflowSaving.value = true
|
||||
|
||||
try {
|
||||
if (isDocumentMockEnabled) {
|
||||
updateMockTemplateStatus(template.id, action)
|
||||
} else if (versionId) {
|
||||
await updateTemplateVersionStatus(template.id, versionId, action, comment)
|
||||
await loadLibrary()
|
||||
} else {
|
||||
throw new Error('当前模板没有可操作的版本')
|
||||
}
|
||||
|
||||
updateLocalTemplateStatus(template.id, action)
|
||||
closeEditor()
|
||||
ElMessage.success('模板版本状态已更新')
|
||||
} catch {
|
||||
ElMessage.error('模板版本操作失败,请稍后重试')
|
||||
} finally {
|
||||
workflowSaving.value = false
|
||||
}
|
||||
@@ -522,17 +303,7 @@ onMounted(loadLibrary)
|
||||
:statuses="statusTabs"
|
||||
/>
|
||||
|
||||
<div v-if="loading" class="template-grid" aria-label="正在加载文档模板">
|
||||
<div v-for="index in 8" :key="index" class="template-skeleton">
|
||||
<span class="skeleton-title" />
|
||||
<span class="skeleton-line wide" />
|
||||
<span class="skeleton-line" />
|
||||
<span class="skeleton-line short" />
|
||||
<span class="skeleton-footer" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="filteredTemplates.length" class="template-grid">
|
||||
<div v-if="filteredTemplates.length" class="template-grid">
|
||||
<DocumentTemplateCard
|
||||
v-for="template in filteredTemplates"
|
||||
:key="template.id"
|
||||
@@ -559,8 +330,8 @@ onMounted(loadLibrary)
|
||||
<DocumentPreviewDialog
|
||||
:visible="Boolean(selectedTemplate)"
|
||||
:template="selectedTemplate"
|
||||
:loading="previewLoading"
|
||||
:error="previewError"
|
||||
:loading="false"
|
||||
:error="false"
|
||||
@close="closePreview"
|
||||
@use="useTemplate"
|
||||
/>
|
||||
@@ -596,59 +367,6 @@ onMounted(loadLibrary)
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.template-skeleton {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
min-height: 206px;
|
||||
padding: 16px;
|
||||
background: var(--card);
|
||||
border-radius: var(--r);
|
||||
box-shadow: var(--sh);
|
||||
}
|
||||
|
||||
.template-skeleton span {
|
||||
display: block;
|
||||
background: linear-gradient(90deg, #edf3f5 25%, #f7fafb 37%, #edf3f5 63%);
|
||||
background-size: 400% 100%;
|
||||
border-radius: 6px;
|
||||
animation: skeleton-shimmer 1.4s ease infinite;
|
||||
}
|
||||
|
||||
.skeleton-title {
|
||||
width: 70%;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.skeleton-line {
|
||||
width: 90%;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.skeleton-line.wide {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.skeleton-line.short {
|
||||
width: 62%;
|
||||
}
|
||||
|
||||
.skeleton-footer {
|
||||
width: 100%;
|
||||
height: 26px;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
@keyframes skeleton-shimmer {
|
||||
0% {
|
||||
background-position: 100% 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
background-position: -100% 0;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
padding: 56px 24px;
|
||||
color: var(--mut);
|
||||
|
||||
Reference in New Issue
Block a user