文件上传下载修正

This commit is contained in:
2025-12-20 15:14:44 +08:00
parent 9b6e959973
commit 81508a1fdc
28 changed files with 1059 additions and 192 deletions

View File

@@ -1,34 +0,0 @@
/**
* Workcase 应用配置
*/
/**
* AES 加密密钥(与后端保持一致)
* 对应后端配置security.aes.secret-key
* Base64 编码的 32 字节密钥256 位)
*/
export const AES_SECRET_KEY = 'MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=' // Base64 编码,解码后是 "12345678901234567890123456789012" (32字节)
/**
* API 基础地址
* 注意:使用 shared 的 APP_CONFIG 统一管理,这里保留用于特殊场景
*/
export const API_BASE_URL = (import.meta as any).env?.VITE_API_BASE_URL || '/api'
/**
* Platform URL单点登录入口
* 开发环境:
* - 通过nginx访问时使用 '/'(推荐)
* - 直接访问各服务时使用 'http://localhost:7001'
* 生产环境:统一使用 '/'
*/
export const PLATFORM_URL = (import.meta as any).env?.VITE_PLATFORM_URL || '/'
/**
* 应用配置
*/
export const APP_CONFIG = {
name: '泰豪小电',
version: '1.0.0',
copyright: '泰豪电源'
}

View File

@@ -0,0 +1,265 @@
/**
* @description 应用运行时配置
* @author yslg
* @since 2025-12-06
*
* 配置加载策略:
* 1. 开发环境:使用下面定义的开发配置
* 2. 生产环境:从 window.APP_RUNTIME_CONFIG 读取(来自 app-config.js
* 3. Docker部署替换 app-config.js 文件实现配置外挂
*
* 配置结构说明:
* 此文件的配置结构与 app-config.js 完全对应
* 修改 app-config.js 后,这里的配置会自动应用
*/
// ============================================
// AES 加密密钥
// ============================================
/**
* AES 加密密钥(与后端保持一致)
* 对应后端配置security.aes.secret-key
* Base64 编码的 32 字节密钥256 位)
*/
export const AES_SECRET_KEY = 'MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=' // Base64 编码,解码后是 "12345678901234567890123456789012" (32字节)
// ============================================
// 类型定义
// ============================================
export interface AppRuntimeConfig {
env?: string;
api: {
baseUrl: string;
timeout: number;
};
baseUrl: string;
file: {
downloadUrl: string;
uploadUrl: string;
maxSize: {
image: number;
video: number;
document: number;
};
acceptTypes: {
image: string;
video: string;
document: string;
};
};
token: {
key: string;
refreshThreshold: number;
};
publicImgPath: string;
publicWebPath: string;
// 单点登录配置
sso?: {
platformUrl: string; // platform 平台地址
workcaseUrl: string; // workcase 服务地址
biddingUrl: string; // bidding 服务地址
};
features?: {
enableDebug?: boolean;
enableMockData?: boolean;
[key: string]: any;
};
}
// ============================================
// 配置定义(与 app-config.js 结构一致)
// ============================================
const isDev = (import.meta as any).env?.DEV ?? false;
// 开发环境配置
const devConfig: AppRuntimeConfig = {
env: 'development',
api: {
// 开发环境通过 Vite 代理转发到后端,避免浏览器直接跨域
// 实际请求路径示例:/api/... → 由代理转发到实际后端
baseUrl: '/api',
timeout: 30000
},
baseUrl: '/',
file: {
// 通过 Nginx → Gateway 访问文件服务,使用 /urban-lifeline 前缀
downloadUrl: '/api/urban-lifeline/file/download/',
uploadUrl: '/api/urban-lifeline/file/upload',
maxSize: {
image: 5,
video: 100,
document: 10
},
acceptTypes: {
image: 'image/*',
video: 'video/*',
document: '.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx'
}
},
token: {
key: 'token',
refreshThreshold: 300000
},
publicImgPath: 'http://localhost:5173/img',
publicWebPath: 'http://localhost:5173',
// 单点登录配置
// 推荐开发环境也通过nginx访问http://localhost
// 备选直接访问各服务端口platformUrl: 'http://localhost:7001'
sso: {
platformUrl: '/', // 通过nginx访问platform
workcaseUrl: '/workcase', // 通过nginx访问workcase
biddingUrl: '/bidding' // 通过nginx访问bidding
},
features: {
enableDebug: true,
enableMockData: false
}
};
// 生产环境默认配置(兜底)
const prodDefaultConfig: AppRuntimeConfig = {
env: 'production',
api: {
baseUrl: '/api',
timeout: 30000
},
baseUrl: '/',
file: {
downloadUrl: '/api/file/download/',
uploadUrl: '/api/file/upload',
maxSize: {
image: 5,
video: 100,
document: 10
},
acceptTypes: {
image: 'image/*',
video: 'video/*',
document: '.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx'
}
},
token: {
key: 'token',
refreshThreshold: 300000
},
publicImgPath: '/img',
publicWebPath: '/',
// 单点登录配置生产环境通过nginx代理
sso: {
platformUrl: '/',
workcaseUrl: '/workcase',
biddingUrl: '/bidding'
},
features: {
enableDebug: false,
enableMockData: false
}
};
// ============================================
// 配置加载
// ============================================
/**
* 获取运行时配置
* 生产环境优先从 window.APP_RUNTIME_CONFIG 读取app-config.js 注入)
*/
const getRuntimeConfig = (): AppRuntimeConfig => {
if (isDev) {
console.log('[配置] 开发环境,使用内置配置');
return devConfig;
}
// 生产环境:尝试读取外部配置
try {
const runtimeConfig = (window as any).APP_RUNTIME_CONFIG;
if (runtimeConfig && typeof runtimeConfig === 'object') {
console.log('[配置] 加载外部配置 app-config.js');
console.log('[配置] API地址:', runtimeConfig.api?.baseUrl);
console.log('[配置] 环境:', runtimeConfig.env || 'production');
return runtimeConfig as AppRuntimeConfig;
}
} catch (e) {
console.warn('[配置] 无法读取外部配置,使用默认配置', e);
}
console.log('[配置] 使用默认生产配置');
return prodDefaultConfig;
};
// 当前应用配置
const config = getRuntimeConfig();
console.log('[配置] 当前配置', config);
// ============================================
// 导出配置(向后兼容)
// ============================================
// 单独导出常用配置项
export const API_BASE_URL = config.api.baseUrl;
export const FILE_DOWNLOAD_URL = config.file.downloadUrl;
export const PUBLIC_IMG_PATH = config.publicImgPath;
export const PUBLIC_WEB_PATH = config.publicWebPath;
// 导出完整配置对象
export const APP_CONFIG = {
// 应用标题
title: '泰豪电源 AI 数智化平台',
// 环境标识
env: config.env || 'production',
// 应用基础路径
baseUrl: config.baseUrl,
// API 配置
api: {
baseUrl: config.api.baseUrl,
timeout: config.api.timeout
},
// 文件配置
file: {
downloadUrl: config.file.downloadUrl,
uploadUrl: config.file.uploadUrl,
maxSize: config.file.maxSize,
acceptTypes: config.file.acceptTypes
},
// Token 配置
token: {
key: config.token.key,
refreshThreshold: config.token.refreshThreshold
},
// 公共路径
publicImgPath: config.publicImgPath,
publicWebPath: config.publicWebPath,
// 单点登录配置
sso: config.sso || {
platformUrl: '/',
workcaseUrl: '/workcase',
biddingUrl: '/bidding'
},
// 功能开关
features: config.features || {}
};
// 默认导出
export default APP_CONFIG;

View File

@@ -7,7 +7,7 @@ import './assets/css/common.scss'
import App from './App.vue'
import router from './router/'
import { AES_SECRET_KEY } from './config'
import { AES_SECRET_KEY } from './config/index'
// @ts-ignore
import { initAesEncrypt } from 'shared/utils'

View File

@@ -1,10 +1,18 @@
<template>
<AdminLayout title="知识库管理" info="管理外部和内部知识库文档">
<template #action>
<el-button type="primary" @click="openUploadDialog">
<el-icon><Upload /></el-icon>
上传文档
</el-button>
<!-- 上传文档组件 -->
<FileUpload
ref="fileUploadRef"
mode="dialog"
:title="'上传文档到:' + currentKnowledgeName"
button-text="上传文档"
accept=".pdf,.doc,.docx,.txt,.md"
:max-size="50 * 1024 * 1024"
:max-count="10"
:custom-upload="customKnowledgeUpload"
@upload-error="handleUploadError"
/>
</template>
<div class="knowledge-container">
@@ -67,19 +75,6 @@
</div>
</el-card>
</div>
<!-- 上传文档组件 -->
<FileUpload
ref="fileUploadRef"
mode="dialog"
:title="'上传文档到:' + currentKnowledgeName"
button-text="上传文档"
accept=".pdf,.doc,.docx,.txt,.md"
:max-size="50 * 1024 * 1024"
:max-count="10"
:custom-upload="customKnowledgeUpload"
@upload-error="handleUploadError"
/>
</AdminLayout>
</template>
@@ -90,6 +85,7 @@ import { Upload, Search, Document, View, Download, Delete } from '@element-plus/
import { ElMessage, ElMessageBox } from 'element-plus'
import { aiKnowledgeAPI } from 'shared/api/ai'
import { FileUpload } from 'shared/components'
import { FILE_DOWNLOAD_URL } from '@/config/index'
import type { TbKnowledge } from 'shared/types'
// Tab 配置
@@ -113,12 +109,11 @@ interface DocumentItem {
name: string
uploader: string
uploadTime: string
position: number
dataSourceType: string
wordCount: number
hitCount: number
indexingStatus: string
enabled: boolean
fileId?: string
fileRootId?: string
knowledgeId?: string
difyFileId?: string
version?: number
}
const documents = ref<DocumentItem[]>([])
@@ -185,18 +180,17 @@ const fetchDocuments = async (knowledgeId: string) => {
loading.value = true
try {
const result = await aiKnowledgeAPI.getDocumentList(knowledgeId, 1, 100)
if (result.success && result.data) {
documents.value = (result.data.data || []).map((doc: any) => ({
id: doc.id,
name: doc.name,
uploader: doc.created_by || '-',
uploadTime: doc.created_at ? new Date(doc.created_at * 1000).toLocaleString() : '-',
position: doc.position,
dataSourceType: doc.data_source_type,
wordCount: doc.word_count || 0,
hitCount: doc.hit_count || 0,
indexingStatus: doc.indexing_status,
enabled: doc.enabled
if (result.success && result.pageDomain) {
documents.value = (result.pageDomain.dataList || []).map((file: any) => ({
id: file.fileId,
name: file.fileName || '-',
uploader: file.creator || '-',
uploadTime: file.createTime ? new Date(file.createTime).toLocaleString() : '-',
fileId: file.fileId,
fileRootId: file.fileRootId,
knowledgeId: file.knowledgeId,
difyFileId: file.difyFileId,
version: file.version
}))
}
} catch (error) {
@@ -226,12 +220,30 @@ watch(activeKnowledgeId, (newVal) => {
if (newVal) fetchDocuments(newVal)
})
const previewFile = (row: DocumentItem) => {
ElMessage.info(`预览文件: ${row.name}`)
const previewFile = async (row: DocumentItem) => {
if (!row.fileId) {
ElMessage.warning('文件信息不完整')
return
}
// 使用 FILE_DOWNLOAD_URL 构建文件 URL 并在新窗口打开
const fileUrl = `${FILE_DOWNLOAD_URL}${row.fileId}`
window.open(fileUrl, '_blank')
}
const downloadFile = (row: DocumentItem) => {
ElMessage.success(`下载文件: ${row.name}`)
if (!row.fileId) {
ElMessage.warning('文件信息不完整')
return
}
// 创建隐藏的下载链接并触发下载
const fileUrl = `${FILE_DOWNLOAD_URL}${row.fileId}`
const link = document.createElement('a')
link.href = fileUrl
link.download = row.name || 'file'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
ElMessage.success('开始下载')
}
const deleteFile = async (row: DocumentItem) => {
@@ -291,15 +303,6 @@ const handleUploadError = (error: string) => {
ElMessage.error(error)
}
// 打开上传弹窗
const openUploadDialog = () => {
if (!activeKnowledgeId.value) {
ElMessage.warning('请先选择一个知识库')
return
}
fileUploadRef.value?.openDialog()
}
onMounted(() => {
fetchKnowledges()
})