|
@@ -131,7 +131,9 @@
|
|
|
:status="getProgressStatus(scope.row)"
|
|
:status="getProgressStatus(scope.row)"
|
|
|
/>
|
|
/>
|
|
|
<div class="converted-file-name" v-if="scope.row.converted_file_name">
|
|
<div class="converted-file-name" v-if="scope.row.converted_file_name">
|
|
|
- <el-icon><Link /></el-icon> {{ scope.row.converted_file_name }}
|
|
|
|
|
|
|
+ <el-link type="primary" :underline="false" @click="handleDownloadConverted(scope.row)">
|
|
|
|
|
+ <el-icon><Link /></el-icon> 下载转换后文件
|
|
|
|
|
+ </el-link>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
@@ -156,61 +158,72 @@
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
|
|
|
|
|
- <el-table-column label="操作" width="140" fixed="right" align="center">
|
|
|
|
|
- <template #default="scope">
|
|
|
|
|
- <div class="action-buttons">
|
|
|
|
|
- <el-tooltip content="编辑文档" placement="top">
|
|
|
|
|
- <el-button
|
|
|
|
|
- link
|
|
|
|
|
- type="primary"
|
|
|
|
|
- @click="handleEdit(scope.row)"
|
|
|
|
|
- class="action-btn-icon"
|
|
|
|
|
- >
|
|
|
|
|
- <el-icon><Edit /></el-icon>
|
|
|
|
|
- </el-button>
|
|
|
|
|
- </el-tooltip>
|
|
|
|
|
-
|
|
|
|
|
- <el-tooltip content="查看详情" placement="top">
|
|
|
|
|
- <el-button
|
|
|
|
|
- link
|
|
|
|
|
- type="primary"
|
|
|
|
|
- @click="handleView(scope.row)"
|
|
|
|
|
- class="action-btn-icon"
|
|
|
|
|
- >
|
|
|
|
|
- <el-icon><View /></el-icon>
|
|
|
|
|
- </el-button>
|
|
|
|
|
- </el-tooltip>
|
|
|
|
|
-
|
|
|
|
|
- <el-tooltip
|
|
|
|
|
- :content="scope.row.conversion_status === 1 ? '转换中' : (scope.row.conversion_status === 2 ? '重新转换' : '开始转换')"
|
|
|
|
|
- placement="top"
|
|
|
|
|
|
|
+ <el-table-column label="操作" width="260" fixed="right" align="center">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ <div class="action-buttons">
|
|
|
|
|
+ <el-tooltip content="编辑" placement="top">
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ link
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ @click="handleEdit(scope.row)"
|
|
|
|
|
+ class="action-btn-icon"
|
|
|
>
|
|
>
|
|
|
- <el-button
|
|
|
|
|
- link
|
|
|
|
|
- type="warning"
|
|
|
|
|
- @click="handleConvert(scope.row)"
|
|
|
|
|
- :disabled="scope.row.conversion_status === 1"
|
|
|
|
|
- class="action-btn-icon"
|
|
|
|
|
- >
|
|
|
|
|
- <el-icon><Switch /></el-icon>
|
|
|
|
|
- </el-button>
|
|
|
|
|
- </el-tooltip>
|
|
|
|
|
-
|
|
|
|
|
- <el-tooltip content="删除文档" placement="top">
|
|
|
|
|
- <el-button
|
|
|
|
|
- link
|
|
|
|
|
- type="danger"
|
|
|
|
|
- @click="handleDelete(scope.row)"
|
|
|
|
|
- class="action-btn-icon"
|
|
|
|
|
- >
|
|
|
|
|
- <el-icon><Delete /></el-icon>
|
|
|
|
|
- </el-button>
|
|
|
|
|
- </el-tooltip>
|
|
|
|
|
- </div>
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-table-column>
|
|
|
|
|
|
|
+ <el-icon><Edit /></el-icon>
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </el-tooltip>
|
|
|
|
|
+
|
|
|
|
|
+ <el-tooltip content="详情" placement="top">
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ link
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ @click="handleView(scope.row)"
|
|
|
|
|
+ class="action-btn-icon"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-icon><View /></el-icon>
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </el-tooltip>
|
|
|
|
|
+
|
|
|
|
|
+ <el-tooltip content="下载" placement="top" v-if="scope.row.file_url">
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ link
|
|
|
|
|
+ type="success"
|
|
|
|
|
+ @click="handleDownload(scope.row)"
|
|
|
|
|
+ class="action-btn-icon"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-icon><Download /></el-icon>
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </el-tooltip>
|
|
|
|
|
+
|
|
|
|
|
+ <el-tooltip
|
|
|
|
|
+ :content="scope.row.conversion_status === 1 ? '转换中' : (scope.row.conversion_status === 2 ? '重新转换' : '开始转换')"
|
|
|
|
|
+ placement="top"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ link
|
|
|
|
|
+ type="warning"
|
|
|
|
|
+ @click="handleConvert(scope.row)"
|
|
|
|
|
+ :disabled="scope.row.conversion_status === 1"
|
|
|
|
|
+ class="action-btn-icon"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-icon><Switch /></el-icon>
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </el-tooltip>
|
|
|
|
|
+
|
|
|
|
|
+ <el-tooltip content="删除" placement="top">
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ link
|
|
|
|
|
+ type="danger"
|
|
|
|
|
+ @click="handleDelete(scope.row)"
|
|
|
|
|
+ class="action-btn-icon"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-icon><Delete /></el-icon>
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </el-tooltip>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
</el-table>
|
|
</el-table>
|
|
|
-...
|
|
|
|
|
|
|
+
|
|
|
<!-- 文档预览对话框 -->
|
|
<!-- 文档预览对话框 -->
|
|
|
<el-dialog v-model="previewVisible" :title="previewTitle" width="85%" top="5vh" custom-class="preview-dialog" @closed="previewUrl = ''">
|
|
<el-dialog v-model="previewVisible" :title="previewTitle" width="85%" top="5vh" custom-class="preview-dialog" @closed="previewUrl = ''">
|
|
|
<template #header>
|
|
<template #header>
|
|
@@ -272,7 +285,24 @@
|
|
|
<el-input v-model="uploadForm.title" placeholder="请输入文档标题" />
|
|
<el-input v-model="uploadForm.title" placeholder="请输入文档标题" />
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
<el-form-item label="文档链接">
|
|
<el-form-item label="文档链接">
|
|
|
- <el-input v-model="uploadForm.file_url" placeholder="请输入文档链接 (URL)" />
|
|
|
|
|
|
|
+ <el-input v-model="uploadForm.file_url" placeholder="请输入文档链接 (URL) 或通过下方上传文件" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="文件上传">
|
|
|
|
|
+ <el-upload
|
|
|
|
|
+ class="upload-demo"
|
|
|
|
|
+ action="#"
|
|
|
|
|
+ :http-request="customUpload"
|
|
|
|
|
+ :limit="1"
|
|
|
|
|
+ :on-exceed="handleExceed"
|
|
|
|
|
+ :before-upload="beforeUpload"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-button type="primary">点击上传</el-button>
|
|
|
|
|
+ <template #tip>
|
|
|
|
|
+ <div class="el-upload__tip">
|
|
|
|
|
+ 支持 PDF, Word, Excel, PPT, TXT 等文件,最大 50MB
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-upload>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
<el-form-item label="文档内容">
|
|
<el-form-item label="文档内容">
|
|
|
<el-input v-model="uploadForm.content" type="textarea" :rows="4" placeholder="请输入文档内容" />
|
|
<el-input v-model="uploadForm.content" type="textarea" :rows="4" placeholder="请输入文档内容" />
|
|
@@ -394,6 +424,9 @@
|
|
|
</el-descriptions>
|
|
</el-descriptions>
|
|
|
<template #footer>
|
|
<template #footer>
|
|
|
<el-button @click="detailDialogVisible = false">关闭</el-button>
|
|
<el-button @click="detailDialogVisible = false">关闭</el-button>
|
|
|
|
|
+ <el-button type="primary" @click="handleEditFromDetail" v-if="currentDoc">
|
|
|
|
|
+ <el-icon><Edit /></el-icon> 编辑文档
|
|
|
|
|
+ </el-button>
|
|
|
<el-button type="success" @click="handleSingleEnter(currentDoc)" v-if="currentDoc && !isEntered(currentDoc.whether_to_enter)">加入知识库</el-button>
|
|
<el-button type="success" @click="handleSingleEnter(currentDoc)" v-if="currentDoc && !isEntered(currentDoc.whether_to_enter)">加入知识库</el-button>
|
|
|
<el-button type="primary" @click="handlePreview(currentDoc)" v-if="currentDoc?.file_url">预览原文档</el-button>
|
|
<el-button type="primary" @click="handlePreview(currentDoc)" v-if="currentDoc?.file_url">预览原文档</el-button>
|
|
|
</template>
|
|
</template>
|
|
@@ -404,55 +437,15 @@
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
import { ref, reactive, onMounted, onUnmounted, computed } from 'vue'
|
|
import { ref, reactive, onMounted, onUnmounted, computed } from 'vue'
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
-import { Search, Filter, Upload, CircleCheck, Delete, Document, Warning, TopRight, Grid, DataAnalysis, Link, View, Switch, Edit, User } from '@element-plus/icons-vue'
|
|
|
|
|
|
|
+import { Search, Filter, Upload, CircleCheck, Delete, Document, Warning, TopRight, Grid, DataAnalysis, Link, View, Switch, Edit, User, Download } from '@element-plus/icons-vue'
|
|
|
import request from '@/api/request'
|
|
import request from '@/api/request'
|
|
|
|
|
+import axios from 'axios'
|
|
|
|
|
+import { downloadFile } from '@/utils/download'
|
|
|
import { useAuthStore } from '@/stores/auth'
|
|
import { useAuthStore } from '@/stores/auth'
|
|
|
import dayjs from 'dayjs'
|
|
import dayjs from 'dayjs'
|
|
|
|
|
+import { documentApi, type DocumentItem } from '@/api/document'
|
|
|
|
|
|
|
|
-// 接口定义
|
|
|
|
|
-interface DocumentItem {
|
|
|
|
|
- id: string
|
|
|
|
|
- source_id: string | null
|
|
|
|
|
- source_type: string | null
|
|
|
|
|
- title: string
|
|
|
|
|
- content: string
|
|
|
|
|
- document_type: string | null
|
|
|
|
|
- professional_field: string | null
|
|
|
|
|
- year: number | null
|
|
|
|
|
- release_date: string | null
|
|
|
|
|
- standard_no: string | null
|
|
|
|
|
- status: string | null
|
|
|
|
|
- whether_to_enter: number
|
|
|
|
|
- file_url: string | null
|
|
|
|
|
- conversion_status: number | null
|
|
|
|
|
- conversion_progress: number | null
|
|
|
|
|
- conversion_error: string | null
|
|
|
|
|
- created_at: string
|
|
|
|
|
- updated_at: string
|
|
|
|
|
- created_by: string | null
|
|
|
|
|
- updated_by: string | null
|
|
|
|
|
- validity: string | null
|
|
|
|
|
- converted_file_name: string | null
|
|
|
|
|
- created_time: string | null
|
|
|
|
|
- updated_time: string | null
|
|
|
|
|
- file_extension: string | null
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-interface ApiResponse<T = any> {
|
|
|
|
|
- code: number
|
|
|
|
|
- message: string
|
|
|
|
|
- data: T
|
|
|
|
|
- timestamp: string
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-interface PageResult<T> {
|
|
|
|
|
- items: T[]
|
|
|
|
|
- total: number
|
|
|
|
|
- page: number
|
|
|
|
|
- size: number
|
|
|
|
|
- all_total?: number
|
|
|
|
|
- total_entered?: number
|
|
|
|
|
-}
|
|
|
|
|
|
|
+// 接口定义已移至 @/api/document
|
|
|
|
|
|
|
|
// 状态变量
|
|
// 状态变量
|
|
|
const loading = ref(false)
|
|
const loading = ref(false)
|
|
@@ -478,7 +471,7 @@ const editForm = reactive({
|
|
|
source_id: '',
|
|
source_id: '',
|
|
|
title: '',
|
|
title: '',
|
|
|
content: '',
|
|
content: '',
|
|
|
- table_type: 'basis',
|
|
|
|
|
|
|
+ table_type: 'basis' as 'basis' | 'work' | 'job',
|
|
|
// 扩展字段 (子表特有属性)
|
|
// 扩展字段 (子表特有属性)
|
|
|
standard_no: '',
|
|
standard_no: '',
|
|
|
issuing_authority: '',
|
|
issuing_authority: '',
|
|
@@ -508,7 +501,7 @@ const uploadForm = reactive({
|
|
|
title: '',
|
|
title: '',
|
|
|
content: '',
|
|
content: '',
|
|
|
file_url: '',
|
|
file_url: '',
|
|
|
- table_type: 'basis',
|
|
|
|
|
|
|
+ table_type: 'basis' as 'basis' | 'work' | 'job',
|
|
|
year: new Date().getFullYear()
|
|
year: new Date().getFullYear()
|
|
|
})
|
|
})
|
|
|
|
|
|
|
@@ -517,7 +510,7 @@ const proxyPreviewUrl = computed(() => {
|
|
|
if (!previewUrl.value) return ''
|
|
if (!previewUrl.value) return ''
|
|
|
// 使用后端代理接口查看外部网页,附加 token 以通过认证
|
|
// 使用后端代理接口查看外部网页,附加 token 以通过认证
|
|
|
const baseUrl = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000'
|
|
const baseUrl = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000'
|
|
|
- return `${baseUrl}/api/v1/documents/proxy-view?url=${encodeURIComponent(previewUrl.value)}&token=${authStore.token}`
|
|
|
|
|
|
|
+ return `${baseUrl}/api/v1/sample/documents/proxy-view?url=${encodeURIComponent(previewUrl.value)}&token=${authStore.token}`
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
const isHtmlPage = computed(() => {
|
|
const isHtmlPage = computed(() => {
|
|
@@ -615,17 +608,20 @@ const getKBTagType = (sourceType: string | null | undefined) => {
|
|
|
return types[sourceType || ''] || 'info'
|
|
return types[sourceType || ''] || 'info'
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// 为已入库的行添加特定类名
|
|
|
|
|
|
|
+// 为已入库或未转化的行添加特定类名
|
|
|
const tableRowClassName = ({ row }: { row: DocumentItem }) => {
|
|
const tableRowClassName = ({ row }: { row: DocumentItem }) => {
|
|
|
if (isEntered(row.whether_to_enter)) {
|
|
if (isEntered(row.whether_to_enter)) {
|
|
|
return 'row-entered'
|
|
return 'row-entered'
|
|
|
}
|
|
}
|
|
|
|
|
+ if (row.conversion_status !== 2) {
|
|
|
|
|
+ return 'row-not-ready'
|
|
|
|
|
+ }
|
|
|
return ''
|
|
return ''
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// 判断是否可以勾选(已入库的数据不可勾选)
|
|
|
|
|
|
|
+// 判断是否可以勾选(已入库的数据不可勾选,未转化成功的数据也不可勾选)
|
|
|
const canSelect = (row: DocumentItem) => {
|
|
const canSelect = (row: DocumentItem) => {
|
|
|
- return !isEntered(row.whether_to_enter)
|
|
|
|
|
|
|
+ return !isEntered(row.whether_to_enter) && row.conversion_status === 2
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const handleSelectionChange = (selection: DocumentItem[]) => {
|
|
const handleSelectionChange = (selection: DocumentItem[]) => {
|
|
@@ -635,10 +631,17 @@ const handleSelectionChange = (selection: DocumentItem[]) => {
|
|
|
const handleBatchEnter = async () => {
|
|
const handleBatchEnter = async () => {
|
|
|
if (selectedIds.value.length === 0) return
|
|
if (selectedIds.value.length === 0) return
|
|
|
|
|
|
|
|
|
|
+ // 过滤掉未转化的 ID (理论上 UI 已经拦截,但双重保险)
|
|
|
|
|
+ const readyIds = documents.value
|
|
|
|
|
+ .filter(doc => selectedIds.value.includes(doc.id) && doc.conversion_status === 2)
|
|
|
|
|
+ .map(doc => doc.id)
|
|
|
|
|
+
|
|
|
|
|
+ if (readyIds.length === 0) {
|
|
|
|
|
+ return ElMessage.warning('选中的文档中没有已完成转化的,无法入库')
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
try {
|
|
try {
|
|
|
- const res = await request.post<any, ApiResponse>('/api/v1/documents/batch-enter', {
|
|
|
|
|
- ids: selectedIds.value
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ const res = await documentApi.batchEnter(readyIds)
|
|
|
if (res.code === 0) {
|
|
if (res.code === 0) {
|
|
|
ElMessage.success(res.message || '入库成功')
|
|
ElMessage.success(res.message || '入库成功')
|
|
|
fetchDocuments()
|
|
fetchDocuments()
|
|
@@ -653,17 +656,23 @@ const handleBatchEnter = async () => {
|
|
|
|
|
|
|
|
const handleSingleEnter = async (doc: DocumentItem | null) => {
|
|
const handleSingleEnter = async (doc: DocumentItem | null) => {
|
|
|
if (!doc) return
|
|
if (!doc) return
|
|
|
|
|
+
|
|
|
|
|
+ if (doc.conversion_status !== 2) {
|
|
|
|
|
+ return ElMessage.warning('该文档尚未转化完成,暂不允许入库')
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
try {
|
|
try {
|
|
|
- const res = await request.post<any, ApiResponse>('/api/v1/documents/batch-enter', {
|
|
|
|
|
- ids: [doc.id]
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ const res = await documentApi.batchEnter([doc.id])
|
|
|
if (res.code === 0) {
|
|
if (res.code === 0) {
|
|
|
ElMessage.success('成功加入知识库')
|
|
ElMessage.success('成功加入知识库')
|
|
|
detailDialogVisible.value = false
|
|
detailDialogVisible.value = false
|
|
|
fetchDocuments()
|
|
fetchDocuments()
|
|
|
|
|
+ } else {
|
|
|
|
|
+ ElMessage.error(res.message || '入库失败')
|
|
|
}
|
|
}
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
console.error('入库失败:', error)
|
|
console.error('入库失败:', error)
|
|
|
|
|
+ ElMessage.error('入库失败')
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -679,9 +688,7 @@ const handleDelete = async (row: DocumentItem) => {
|
|
|
}
|
|
}
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
- const res = await request.post<any, ApiResponse>('/api/v1/documents/batch-delete', {
|
|
|
|
|
- ids: [row.id]
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ const res = await documentApi.batchDelete([row.id])
|
|
|
|
|
|
|
|
if (res.code === 0) {
|
|
if (res.code === 0) {
|
|
|
ElMessage.success('删除成功')
|
|
ElMessage.success('删除成功')
|
|
@@ -711,9 +718,7 @@ const handleBatchDelete = async () => {
|
|
|
}
|
|
}
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
- const res = await request.post<any, ApiResponse>('/api/v1/documents/batch-delete', {
|
|
|
|
|
- ids: selectedIds.value
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ const res = await documentApi.batchDelete(selectedIds.value)
|
|
|
|
|
|
|
|
if (res.code === 0) {
|
|
if (res.code === 0) {
|
|
|
ElMessage.success(res.message || '批量删除成功')
|
|
ElMessage.success(res.message || '批量删除成功')
|
|
@@ -743,11 +748,7 @@ const getProgressStatus = (row: DocumentItem) => {
|
|
|
const fetchDocuments = async () => {
|
|
const fetchDocuments = async () => {
|
|
|
loading.value = true
|
|
loading.value = true
|
|
|
try {
|
|
try {
|
|
|
- const res = await request.get<any, ApiResponse<PageResult<DocumentItem>>>('/api/v1/documents/list', {
|
|
|
|
|
- params: {
|
|
|
|
|
- ...searchQuery
|
|
|
|
|
- }
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ const res = await documentApi.getList(searchQuery)
|
|
|
if (res.code === 0) {
|
|
if (res.code === 0) {
|
|
|
documents.value = res.data.items
|
|
documents.value = res.data.items
|
|
|
total.value = res.data.total
|
|
total.value = res.data.total
|
|
@@ -788,11 +789,7 @@ const stopPolling = () => {
|
|
|
|
|
|
|
|
const refreshDocumentsSilently = async () => {
|
|
const refreshDocumentsSilently = async () => {
|
|
|
try {
|
|
try {
|
|
|
- const res = await request.get<any, ApiResponse<PageResult<DocumentItem>>>('/api/v1/documents/list', {
|
|
|
|
|
- params: {
|
|
|
|
|
- ...searchQuery
|
|
|
|
|
- }
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ const res = await documentApi.getList(searchQuery)
|
|
|
if (res.code === 0) {
|
|
if (res.code === 0) {
|
|
|
documents.value = res.data.items
|
|
documents.value = res.data.items
|
|
|
total.value = res.data.total
|
|
total.value = res.data.total
|
|
@@ -824,9 +821,58 @@ const handleCurrentChange = (val: number) => {
|
|
|
fetchDocuments()
|
|
fetchDocuments()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+const beforeUpload = (file: File) => {
|
|
|
|
|
+ const isLt50M = file.size / 1024 / 1024 < 50
|
|
|
|
|
+ if (!isLt50M) {
|
|
|
|
|
+ ElMessage.error('上传文件大小不能超过 50MB!')
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+ return true
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const handleExceed = () => {
|
|
|
|
|
+ ElMessage.warning('只能上传一个文件,请先移除已上传的文件')
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const customUpload = async (options: any) => {
|
|
|
|
|
+ const { file, onSuccess, onError } = options
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 1. 获取预签名 URL
|
|
|
|
|
+ const res = await documentApi.getUploadUrl(file.name, file.type || 'application/octet-stream')
|
|
|
|
|
+
|
|
|
|
|
+ if (res.code !== 0) {
|
|
|
|
|
+ throw new Error(res.message || '获取上传链接失败')
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const { upload_url, file_url } = res.data
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 直接上传到 MinIO (PUT 请求)
|
|
|
|
|
+ await axios.put(upload_url, file, {
|
|
|
|
|
+ headers: {
|
|
|
|
|
+ 'Content-Type': file.type || 'application/octet-stream'
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 上传成功,更新表单
|
|
|
|
|
+ uploadForm.file_url = file_url
|
|
|
|
|
+ if (!uploadForm.title) {
|
|
|
|
|
+ // 如果标题为空,自动填充文件名(去掉后缀)
|
|
|
|
|
+ uploadForm.title = file.name.replace(/\.[^/.]+$/, "")
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ ElMessage.success('文件上传成功')
|
|
|
|
|
+ onSuccess(res.data)
|
|
|
|
|
+ } catch (error: any) {
|
|
|
|
|
+ console.error('文件上传失败:', error)
|
|
|
|
|
+ ElMessage.error(error.message || '文件上传失败')
|
|
|
|
|
+ onError(error)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
const handleUpload = () => {
|
|
const handleUpload = () => {
|
|
|
uploadForm.title = ''
|
|
uploadForm.title = ''
|
|
|
uploadForm.content = ''
|
|
uploadForm.content = ''
|
|
|
|
|
+ uploadForm.file_url = ''
|
|
|
uploadDialogVisible.value = true
|
|
uploadDialogVisible.value = true
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -836,9 +882,7 @@ const submitUpload = async () => {
|
|
|
}
|
|
}
|
|
|
submitting.value = true
|
|
submitting.value = true
|
|
|
try {
|
|
try {
|
|
|
- const res = await request.post<any, ApiResponse>('/api/v1/documents/add', {
|
|
|
|
|
- ...uploadForm
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ const res = await documentApi.add(uploadForm)
|
|
|
if (res.code === 0) {
|
|
if (res.code === 0) {
|
|
|
ElMessage.success('上传成功')
|
|
ElMessage.success('上传成功')
|
|
|
uploadDialogVisible.value = false
|
|
uploadDialogVisible.value = false
|
|
@@ -853,7 +897,7 @@ const submitUpload = async () => {
|
|
|
|
|
|
|
|
const handleEdit = async (row: DocumentItem) => {
|
|
const handleEdit = async (row: DocumentItem) => {
|
|
|
try {
|
|
try {
|
|
|
- const res = await request.get<any, ApiResponse>(`/api/v1/documents/detail/${row.id}`)
|
|
|
|
|
|
|
+ const res = await documentApi.getDetail(row.id)
|
|
|
if (res.code === 0 && res.data) {
|
|
if (res.code === 0 && res.data) {
|
|
|
const data = res.data
|
|
const data = res.data
|
|
|
editForm.id = data.id
|
|
editForm.id = data.id
|
|
@@ -889,9 +933,7 @@ const submitEdit = async () => {
|
|
|
}
|
|
}
|
|
|
submitting.value = true
|
|
submitting.value = true
|
|
|
try {
|
|
try {
|
|
|
- const res = await request.post<any, ApiResponse>('/api/v1/documents/edit', {
|
|
|
|
|
- ...editForm
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ const res = await documentApi.edit(editForm)
|
|
|
if (res.code === 0) {
|
|
if (res.code === 0) {
|
|
|
ElMessage.success('更新成功')
|
|
ElMessage.success('更新成功')
|
|
|
editDialogVisible.value = false
|
|
editDialogVisible.value = false
|
|
@@ -913,6 +955,14 @@ const handleView = (row: DocumentItem) => {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+const handleEditFromDetail = () => {
|
|
|
|
|
+ if (currentDoc.value) {
|
|
|
|
|
+ const doc = currentDoc.value
|
|
|
|
|
+ detailDialogVisible.value = false
|
|
|
|
|
+ handleEdit(doc)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
const handlePreview = (row: DocumentItem | null) => {
|
|
const handlePreview = (row: DocumentItem | null) => {
|
|
|
if (!row || !row.file_url) return
|
|
if (!row || !row.file_url) return
|
|
|
previewTitle.value = row.title
|
|
previewTitle.value = row.title
|
|
@@ -921,11 +971,28 @@ const handlePreview = (row: DocumentItem | null) => {
|
|
|
previewLoading.value = true
|
|
previewLoading.value = true
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+const handleDownload = (row: DocumentItem) => {
|
|
|
|
|
+ if (row.file_url) {
|
|
|
|
|
+ downloadFile(row.file_url, row.title)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ ElMessage.warning('该文档暂无下载链接')
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const handleDownloadConverted = (row: DocumentItem) => {
|
|
|
|
|
+ if (row.converted_file_name) {
|
|
|
|
|
+ // 提取后缀,默认为 md
|
|
|
|
|
+ const ext = row.converted_file_name.split('.').pop() || 'md'
|
|
|
|
|
+ const filename = `${row.title}_转换后.${ext}`
|
|
|
|
|
+ downloadFile(row.converted_file_name, filename)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ ElMessage.warning('该文档暂无转换后的文件')
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
const handleConvert = async (row: DocumentItem) => {
|
|
const handleConvert = async (row: DocumentItem) => {
|
|
|
try {
|
|
try {
|
|
|
- const res = await request.post<any, ApiResponse>('/api/v1/documents/convert', {
|
|
|
|
|
- id: row.id
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ const res = await documentApi.convert(row.id)
|
|
|
if (res.code === 0) {
|
|
if (res.code === 0) {
|
|
|
ElMessage.success(res.message || '转换任务已启动')
|
|
ElMessage.success(res.message || '转换任务已启动')
|
|
|
fetchDocuments()
|
|
fetchDocuments()
|
|
@@ -1028,10 +1095,36 @@ onUnmounted(() => {
|
|
|
text-decoration: underline;
|
|
text-decoration: underline;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.action-buttons {
|
|
|
|
|
|
|
+.action-btn {
|
|
|
|
|
+ padding: 4px 8px;
|
|
|
|
|
+ height: auto;
|
|
|
|
|
+ font-size: 13px;
|
|
|
display: flex;
|
|
display: flex;
|
|
|
- gap: 8px;
|
|
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 4px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.action-btn-icon {
|
|
|
|
|
+ padding: 4px;
|
|
|
|
|
+ height: 28px;
|
|
|
|
|
+ width: 28px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
justify-content: center;
|
|
justify-content: center;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ transition: all 0.2s;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.action-btn-icon:hover {
|
|
|
|
|
+ background-color: #f5f7fa;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.action-btn-icon .el-icon {
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.action-btn .el-icon {
|
|
|
|
|
+ font-size: 14px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.file-info-cell {
|
|
.file-info-cell {
|
|
@@ -1264,21 +1357,8 @@ onUnmounted(() => {
|
|
|
.action-buttons {
|
|
.action-buttons {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
justify-content: center;
|
|
justify-content: center;
|
|
|
- gap: 12px;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.action-btn-icon {
|
|
|
|
|
- padding: 8px !important;
|
|
|
|
|
- font-size: 20px !important;
|
|
|
|
|
- transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) !important;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.action-btn-icon:hover {
|
|
|
|
|
- transform: scale(1.2);
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.action-btn-icon .el-icon {
|
|
|
|
|
- font-size: 20px;
|
|
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 4px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.dialog-header-custom {
|
|
.dialog-header-custom {
|
|
@@ -1316,8 +1396,9 @@ onUnmounted(() => {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/* 隐藏已入库行的复选框 */
|
|
|
|
|
-:deep(.row-entered .el-table-column--selection .el-checkbox) {
|
|
|
|
|
|
|
+/* 隐藏已入库或未转化完成行的复选框 */
|
|
|
|
|
+:deep(.row-entered .el-table-column--selection .el-checkbox),
|
|
|
|
|
+:deep(.row-not-ready .el-table-column--selection .el-checkbox) {
|
|
|
display: none;
|
|
display: none;
|
|
|
}
|
|
}
|
|
|
</style>
|
|
</style>
|