Files
X-Financial/web/src/views/scripts/PoliciesView.js

362 lines
11 KiB
JavaScript
Raw Normal View History

import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { useSystemState } from '../../composables/useSystemState.js'
import { useToast } from '../../composables/useToast.js'
import {
deleteKnowledgeDocument,
fetchKnowledgeDocument,
fetchKnowledgeDocumentBlob,
fetchKnowledgeLibrary,
fetchKnowledgeOnlyOfficeConfig,
uploadKnowledgeDocument
} from '../../services/knowledge.js'
import { loadOnlyOfficeApi } from '../../services/onlyoffice.js'
import { isManagerUser } from '../../utils/accessControl.js'
import {
buildExcelPreviewTable,
buildPreviewMetaLine,
buildPreviewSecondaryMetaLine
} from './policiesPreviewFormatters.js'
function triggerFileDownload(blob, filename) {
const url = URL.createObjectURL(blob)
const anchor = document.createElement('a')
anchor.href = url
anchor.download = filename
anchor.click()
URL.revokeObjectURL(url)
}
const ONLYOFFICE_EXTENSIONS = new Set(['docx', 'xlsx', 'pptx'])
function supportsOnlyOfficePreview(document) {
return ONLYOFFICE_EXTENSIONS.has(String(document?.extension || '').toLowerCase())
}
export default {
name: 'PoliciesView',
emits: ['summary-change'],
setup(_, { emit }) {
const { currentUser } = useSystemState()
const { toast } = useToast()
const documentSearch = ref('')
const activeFolder = ref('差旅规范')
const folders = ref([])
const documents = ref([])
const selectedDocument = ref(null)
const pageSizeOpen = ref(false)
const currentPage = ref(1)
const pageSize = ref(10)
const pageSizes = [10, 20, 50]
const loading = ref(false)
const uploadInput = ref(null)
const uploading = ref(false)
const deletingId = ref('')
const previewLoading = ref(false)
const previewBlobUrl = ref('')
const previewError = ref('')
const onlyOfficeLoading = ref(false)
const onlyOfficeError = ref('')
const onlyOfficeEditor = ref(null)
const onlyOfficeHostId = ref('knowledge-onlyoffice-preview')
const currentPreviewPageIndex = ref(0)
const isAdmin = computed(() => isManagerUser(currentUser.value))
const uploadHint = computed(() =>
isAdmin.value
? '支持 PDF / Word / Excel / PPT / 图片 / 文本文件,重复同名文件将自动覆盖并升级版本'
: '当前账号只有查阅权限,上传、删除和修改仅管理员可用'
)
const filteredFolders = computed(() => folders.value)
const filteredDocuments = computed(() => {
const key = documentSearch.value.trim()
return documents.value.filter((doc) => {
const inFolder = activeFolder.value ? doc.folder === activeFolder.value : true
const matchesSearch = key ? doc.name.includes(key) : true
return inFolder && matchesSearch
})
})
const totalCount = computed(() => filteredDocuments.value.length)
const totalPages = computed(() => Math.max(1, Math.ceil(totalCount.value / pageSize.value)))
const visibleDocuments = computed(() => {
const start = (currentPage.value - 1) * pageSize.value
return filteredDocuments.value.slice(start, start + pageSize.value)
})
const activePreviewPage = computed(() => {
const pages = selectedDocument.value?.previewPages || []
return pages[currentPreviewPageIndex.value] || pages[0] || null
})
const previewMetaLine = computed(() => buildPreviewMetaLine(selectedDocument.value))
const previewSecondaryMetaLine = computed(() =>
buildPreviewSecondaryMetaLine(selectedDocument.value, activePreviewPage.value)
)
const shouldUseOnlyOffice = computed(() => supportsOnlyOfficePreview(selectedDocument.value))
const excelPreviewTable = computed(() =>
selectedDocument.value?.previewKind === 'table'
? buildExcelPreviewTable(activePreviewPage.value)
: { headers: [], rows: [] }
)
function revokePreviewBlob() {
if (previewBlobUrl.value) {
URL.revokeObjectURL(previewBlobUrl.value)
previewBlobUrl.value = ''
}
}
function destroyOnlyOfficeEditor() {
if (onlyOfficeEditor.value?.destroyEditor) {
onlyOfficeEditor.value.destroyEditor()
}
onlyOfficeEditor.value = null
}
async function mountOnlyOfficeEditor(documentId) {
onlyOfficeLoading.value = true
onlyOfficeError.value = ''
destroyOnlyOfficeEditor()
try {
const payload = await fetchKnowledgeOnlyOfficeConfig(documentId)
await loadOnlyOfficeApi(payload.documentServerUrl)
await nextTick()
if (!window.DocsAPI?.DocEditor) {
throw new Error('ONLYOFFICE 编辑器未正确加载。')
}
onlyOfficeHostId.value = `knowledge-onlyoffice-preview-${documentId}`
await nextTick()
onlyOfficeEditor.value = new window.DocsAPI.DocEditor(onlyOfficeHostId.value, payload.config)
} catch (error) {
onlyOfficeError.value = error.message || 'ONLYOFFICE 预览加载失败。'
} finally {
onlyOfficeLoading.value = false
}
}
async function loadLibrary(options = {}) {
loading.value = true
try {
const payload = await fetchKnowledgeLibrary()
folders.value = payload.folders || []
documents.value = payload.documents || []
emit('summary-change', { totalDocuments: documents.value.length })
const activeExists = folders.value.some((folder) => folder.name === activeFolder.value)
if (!activeExists) {
activeFolder.value = folders.value[0]?.name || ''
}
if (options.preserveSelection && selectedDocument.value?.id) {
const exists = documents.value.some((doc) => doc.id === selectedDocument.value.id)
if (!exists) {
selectedDocument.value = null
revokePreviewBlob()
}
}
} catch (error) {
emit('summary-change', { totalDocuments: 0 })
toast(error.message || '知识库加载失败。')
} finally {
loading.value = false
}
}
async function selectDocument(documentId) {
previewLoading.value = true
previewError.value = ''
onlyOfficeError.value = ''
revokePreviewBlob()
destroyOnlyOfficeEditor()
try {
const payload = await fetchKnowledgeDocument(documentId)
selectedDocument.value = payload
currentPreviewPageIndex.value = 0
if (supportsOnlyOfficePreview(payload)) {
await mountOnlyOfficeEditor(documentId)
} else if (payload.previewKind === 'pdf' || payload.previewKind === 'image') {
const blob = await fetchKnowledgeDocumentBlob(documentId, 'inline')
previewBlobUrl.value = URL.createObjectURL(blob)
}
} catch (error) {
previewError.value = error.message || '预览加载失败。'
toast(previewError.value)
} finally {
previewLoading.value = false
}
}
async function handleDownload(document) {
try {
const blob = await fetchKnowledgeDocumentBlob(document.id, 'attachment')
triggerFileDownload(blob, document.name)
} catch (error) {
toast(error.message || '下载失败。')
}
}
function triggerUpload() {
if (!isAdmin.value || uploading.value) {
return
}
uploadInput.value?.click()
}
async function uploadFiles(fileList) {
const files = Array.from(fileList || []).filter(Boolean)
if (!files.length || !activeFolder.value || !isAdmin.value) {
return
}
uploading.value = true
try {
let latestDocumentId = ''
for (const file of files) {
const payload = await uploadKnowledgeDocument({ folder: activeFolder.value, file })
latestDocumentId = payload.id
}
await loadLibrary({ preserveSelection: true })
toast(files.length > 1 ? `已上传 ${files.length} 个知识库文件。` : '知识库文件已上传。')
if (latestDocumentId) {
await selectDocument(latestDocumentId)
}
} catch (error) {
toast(error.message || '上传失败。')
} finally {
uploading.value = false
if (uploadInput.value) {
uploadInput.value.value = ''
}
}
}
async function handleFileInput(event) {
await uploadFiles(event.target.files)
}
async function handleDrop(event) {
if (!isAdmin.value) {
return
}
await uploadFiles(event.dataTransfer?.files)
}
async function handleDelete(document) {
if (!isAdmin.value || deletingId.value) {
return
}
const confirmed = window.confirm(`确认删除文件“${document.name}”吗?`)
if (!confirmed) {
return
}
deletingId.value = document.id
try {
await deleteKnowledgeDocument(document.id)
if (selectedDocument.value?.id === document.id) {
selectedDocument.value = null
revokePreviewBlob()
}
await loadLibrary()
toast('知识库文件已删除。')
} catch (error) {
toast(error.message || '删除失败。')
} finally {
deletingId.value = ''
}
}
function changePageSize(size) {
pageSize.value = size
pageSizeOpen.value = false
currentPage.value = 1
}
function closePreview() {
selectedDocument.value = null
previewError.value = ''
currentPreviewPageIndex.value = 0
revokePreviewBlob()
destroyOnlyOfficeEditor()
onlyOfficeError.value = ''
}
function selectPreviewPage(index) {
currentPreviewPageIndex.value = index
}
watch(filteredDocuments, () => {
currentPage.value = 1
pageSizeOpen.value = false
if (selectedDocument.value && !filteredDocuments.value.some((doc) => doc.id === selectedDocument.value.id)) {
closePreview()
}
})
watch(activeFolder, () => {
closePreview()
})
onMounted(() => {
loadLibrary()
})
onBeforeUnmount(() => {
revokePreviewBlob()
})
return {
activeFolder,
activePreviewPage,
changePageSize,
closePreview,
excelPreviewTable,
currentPage,
currentPreviewPageIndex,
deletingId,
documentSearch,
filteredFolders,
handleDelete,
handleDownload,
handleDrop,
handleFileInput,
isAdmin,
loading,
pageSize,
pageSizeOpen,
pageSizes,
onlyOfficeError,
onlyOfficeHostId,
onlyOfficeLoading,
previewMetaLine,
previewSecondaryMetaLine,
previewBlobUrl,
previewError,
previewLoading,
shouldUseOnlyOffice,
selectDocument,
selectPreviewPage,
selectedDocument,
totalCount,
totalPages,
triggerUpload,
uploadHint,
uploadInput,
uploading,
visibleDocuments
}
}
}