feat: 功能优化
This commit is contained in:
@@ -69,7 +69,15 @@
|
||||
"Bash(npm run)",
|
||||
"Skill(plan)",
|
||||
"Skill(plan:*)",
|
||||
"Bash(py:*)"
|
||||
"Bash(py:*)",
|
||||
"mcp__firecrawl__firecrawl_search",
|
||||
"mcp__firecrawl__firecrawl_scrape",
|
||||
"Bash(winget install:*)",
|
||||
"mcp__context7__resolve-library-id",
|
||||
"mcp__context7__query-docs",
|
||||
"Bash(netstat:*)",
|
||||
"Bash(npm run dev)",
|
||||
"Bash(curl:*)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
"tailwindcss": "^4.1.14",
|
||||
"typescript": "^5.7.3",
|
||||
"vite": "^7.1.7",
|
||||
"vite-plugin-compression2": "^2.4.0",
|
||||
"vite-plugin-vue-devtools": "^8.0.2",
|
||||
"vue-tsc": "^2.1.28"
|
||||
}
|
||||
|
||||
@@ -1,106 +1,125 @@
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
|
||||
// 配置常量
|
||||
const API_SUCCESS_CODE = 0
|
||||
const DOWNLOAD_INTERVAL = 500
|
||||
|
||||
/**
|
||||
* 任务操作通用逻辑 Composable
|
||||
* @param {Object} apis - API 对象,包含 deleteApi, cancelApi, retryApi, getSignedUrlsApi
|
||||
* @param {Function} onSuccess - 操作成功后的回调函数
|
||||
* @returns {Object} 操作方法
|
||||
*
|
||||
* @param {Object} apiHandlers - API 处理器对象
|
||||
* @param {Function} apiHandlers.deleteApi - 删除任务的 API 函数 (id) => Promise<void>
|
||||
* @param {Function} [apiHandlers.cancelApi] - 取消任务的 API 函数 (id) => Promise<void>
|
||||
* @param {Function} [apiHandlers.retryApi] - 重试任务的 API 函数 (id) => Promise<void>
|
||||
* @param {Function} [apiHandlers.getSignedUrlsApi] - 获取签名URL的 API 函数 (taskId) => Promise<{code: number, data: string[]}>
|
||||
* @param {Function} [onSuccess] - 操作成功后的回调函数 () => void
|
||||
* @returns {Object} 操作方法集合
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* const { handleDelete, handleCancel, handleRetry, handleBatchDownload, handlePreview } = useTaskOperations({
|
||||
* deleteApi: (id) => taskApi.delete(id),
|
||||
* cancelApi: (id) => taskApi.cancel(id),
|
||||
* retryApi: (id) => taskApi.retry(id),
|
||||
* getSignedUrlsApi: (taskId) => taskApi.getSignedUrls(taskId)
|
||||
* }, () => {
|
||||
* // 刷新列表
|
||||
* fetchTasks()
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
export function useTaskOperations(apis, onSuccess) {
|
||||
const { deleteApi, cancelApi, retryApi, getSignedUrlsApi } = apis
|
||||
export function useTaskOperations(apiHandlers, onSuccess) {
|
||||
if (!apiHandlers || typeof apiHandlers !== 'object') {
|
||||
throw new Error('apiHandlers must be an object')
|
||||
}
|
||||
|
||||
// 删除任务
|
||||
const handleDelete = (id) => {
|
||||
if (!apiHandlers.deleteApi || typeof apiHandlers.deleteApi !== 'function') {
|
||||
throw new Error('deleteApi is required and must be a function')
|
||||
}
|
||||
|
||||
const {
|
||||
deleteApi,
|
||||
cancelApi,
|
||||
retryApi,
|
||||
getSignedUrlsApi
|
||||
} = apiHandlers
|
||||
|
||||
// 通用模态框确认
|
||||
function confirmModal({ title, content, okType = 'primary', onOk }) {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: '确定删除这个任务吗?删除后无法恢复。',
|
||||
title,
|
||||
content,
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
okType: 'danger',
|
||||
okType,
|
||||
onOk: async () => {
|
||||
try {
|
||||
await deleteApi(id)
|
||||
message.success('删除成功')
|
||||
await onOk()
|
||||
onSuccess && onSuccess()
|
||||
} catch (error) {
|
||||
console.error('删除任务失败:', error)
|
||||
message.error('删除失败')
|
||||
message.error('操作失败')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 执行 API 操作并显示成功消息
|
||||
async function executeApiOperation(apiFn, successMessage) {
|
||||
await apiFn()
|
||||
message.success(successMessage)
|
||||
}
|
||||
|
||||
// 删除任务
|
||||
function deleteTask(id) {
|
||||
confirmModal({
|
||||
title: '确认删除',
|
||||
content: '确定删除这个任务吗?删除后无法恢复。',
|
||||
okType: 'danger',
|
||||
onOk: () => executeApiOperation(() => deleteApi(id), '删除成功')
|
||||
})
|
||||
}
|
||||
|
||||
// 取消任务
|
||||
const handleCancel = (id) => {
|
||||
Modal.confirm({
|
||||
function cancelTask(id) {
|
||||
confirmModal({
|
||||
title: '确认取消',
|
||||
content: '确定要取消这个任务吗?',
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
try {
|
||||
await cancelApi(id)
|
||||
message.success('已取消任务')
|
||||
onSuccess && onSuccess()
|
||||
} catch (error) {
|
||||
console.error('取消任务失败:', error)
|
||||
message.error('操作失败')
|
||||
}
|
||||
}
|
||||
onOk: () => executeApiOperation(() => cancelApi(id), '已取消任务')
|
||||
})
|
||||
}
|
||||
|
||||
// 重试任务
|
||||
const handleRetry = (id) => {
|
||||
Modal.confirm({
|
||||
function retryTask(id) {
|
||||
confirmModal({
|
||||
title: '确认重试',
|
||||
content: '确定要重新生成这个任务吗?',
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
try {
|
||||
await retryApi(id)
|
||||
message.success('已重新提交任务')
|
||||
onSuccess && onSuccess()
|
||||
} catch (error) {
|
||||
console.error('重试任务失败:', error)
|
||||
message.error('操作失败')
|
||||
}
|
||||
}
|
||||
onOk: () => executeApiOperation(() => retryApi(id), '已重新提交任务')
|
||||
})
|
||||
}
|
||||
|
||||
// 批量删除
|
||||
const handleBatchDelete = async (ids, deleteApiFn) => {
|
||||
async function batchDeleteTasks(ids, deleteApiFn) {
|
||||
if (!ids || ids.length === 0) {
|
||||
message.warning('请选择要删除的任务')
|
||||
return
|
||||
}
|
||||
|
||||
Modal.confirm({
|
||||
confirmModal({
|
||||
title: '确认批量删除',
|
||||
content: `确定要删除选中的 ${ids.length} 个任务吗?删除后无法恢复。`,
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
okType: 'danger',
|
||||
onOk: async () => {
|
||||
try {
|
||||
const deleteFn = deleteApiFn || deleteApi
|
||||
for (const id of ids) {
|
||||
await deleteFn(id)
|
||||
}
|
||||
message.success(`成功删除 ${ids.length} 个任务`)
|
||||
onSuccess && onSuccess()
|
||||
} catch (error) {
|
||||
console.error('批量删除失败:', error)
|
||||
message.error('批量删除失败')
|
||||
const deleteFn = deleteApiFn || deleteApi
|
||||
for (const id of ids) {
|
||||
await deleteFn(id)
|
||||
}
|
||||
message.success(`成功删除 ${ids.length} 个任务`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 下载单个文件
|
||||
const handleDownload = (url, filename = 'download') => {
|
||||
function downloadFile(url, filename = 'download') {
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = filename
|
||||
@@ -110,66 +129,67 @@ export function useTaskOperations(apis, onSuccess) {
|
||||
document.body.removeChild(link)
|
||||
}
|
||||
|
||||
// 批量下载
|
||||
const handleBatchDownload = async (urls, getSignedUrlsApi, taskId) => {
|
||||
if (!urls || urls.length === 0) {
|
||||
message.warning('没有可下载的文件')
|
||||
return
|
||||
// 获取签名URL
|
||||
async function fetchSignedUrl(getSignedUrlsApi, taskId) {
|
||||
const res = await getSignedUrlsApi(taskId)
|
||||
if (res.code === API_SUCCESS_CODE && res.data) {
|
||||
return res.data
|
||||
}
|
||||
throw new Error('获取签名URL失败')
|
||||
}
|
||||
|
||||
// 批量下载
|
||||
async function batchDownload(urls, getSignedUrlsApi, taskId) {
|
||||
try {
|
||||
message.loading('正在获取下载链接...', 0)
|
||||
message.loading('正在准备下载...', 0)
|
||||
let downloadUrls = urls
|
||||
|
||||
// 如果需要获取签名URL
|
||||
if (getSignedUrlsApi && taskId) {
|
||||
const res = await getSignedUrlsApi(taskId)
|
||||
if (res.code === 0 && res.data) {
|
||||
downloadUrls = res.data
|
||||
} else {
|
||||
message.warning('获取下载链接失败')
|
||||
return
|
||||
}
|
||||
downloadUrls = await fetchSignedUrl(getSignedUrlsApi, taskId)
|
||||
}
|
||||
|
||||
// 检查下载链接
|
||||
if (!downloadUrls || downloadUrls.length === 0) {
|
||||
message.warning('没有可下载的文件')
|
||||
return
|
||||
}
|
||||
|
||||
message.destroy()
|
||||
message.loading('正在准备下载...', 0)
|
||||
message.loading('正在下载文件...', 0)
|
||||
|
||||
// 逐个触发下载,避免浏览器阻止多个弹窗
|
||||
downloadUrls.forEach((url, index) => {
|
||||
setTimeout(() => {
|
||||
handleDownload(url)
|
||||
}, index * 500) // 每个下载间隔500ms
|
||||
downloadFile(url)
|
||||
}, index * DOWNLOAD_INTERVAL)
|
||||
})
|
||||
|
||||
message.destroy()
|
||||
message.success(`已触发 ${downloadUrls.length} 个文件的下载`)
|
||||
} catch (error) {
|
||||
console.error('批量下载失败:', error)
|
||||
message.destroy()
|
||||
message.error('获取下载链接失败')
|
||||
message.error('下载失败,请稍后重试')
|
||||
}
|
||||
}
|
||||
|
||||
// 获取签名URL
|
||||
const getSignedUrl = async (getSignedUrlsApi, taskId, index) => {
|
||||
// 获取单个签名URL
|
||||
async function getSignedUrl(getSignedUrlsApi, taskId, index) {
|
||||
try {
|
||||
const res = await getSignedUrlsApi(taskId)
|
||||
if (res.code === 0 && res.data && res.data[index]) {
|
||||
if (res.code === API_SUCCESS_CODE && res.data && res.data[index]) {
|
||||
return res.data[index]
|
||||
} else {
|
||||
message.warning('获取预览链接失败')
|
||||
return null
|
||||
}
|
||||
message.warning('获取预览链接失败')
|
||||
return null
|
||||
} catch (error) {
|
||||
console.error('获取签名URL失败:', error)
|
||||
message.error('获取预览链接失败')
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// 预览
|
||||
const handlePreview = async (getSignedUrlsApi, taskId, index) => {
|
||||
// 预览文件
|
||||
async function previewFile(getSignedUrlsApi, taskId, index) {
|
||||
try {
|
||||
message.loading('正在获取预览链接...', 0)
|
||||
const url = await getSignedUrl(getSignedUrlsApi, taskId, index)
|
||||
@@ -179,7 +199,6 @@ export function useTaskOperations(apis, onSuccess) {
|
||||
window.open(url, '_blank')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('预览失败:', error)
|
||||
message.destroy()
|
||||
message.error('预览失败')
|
||||
}
|
||||
@@ -187,17 +206,17 @@ export function useTaskOperations(apis, onSuccess) {
|
||||
|
||||
return {
|
||||
// 基本操作
|
||||
handleDelete,
|
||||
handleCancel,
|
||||
handleRetry,
|
||||
handleDelete: deleteTask,
|
||||
handleCancel: cancelTask,
|
||||
handleRetry: retryTask,
|
||||
|
||||
// 批量操作
|
||||
handleBatchDelete,
|
||||
handleBatchDownload,
|
||||
handleBatchDelete: batchDeleteTasks,
|
||||
handleBatchDownload: batchDownload,
|
||||
|
||||
// 下载和预览
|
||||
handleDownload,
|
||||
handlePreview,
|
||||
handleDownload: downloadFile,
|
||||
handlePreview: previewFile,
|
||||
getSignedUrl
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,6 @@
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.js"]
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
/* eslint-env node */
|
||||
import path from 'node:path'
|
||||
import { defineConfig, loadEnv } from 'vite'
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
import process from 'node:process'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import vueJsx from '@vitejs/plugin-vue-jsx'
|
||||
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||
import UnoCSS from 'unocss/vite'
|
||||
// import electron from 'vite-plugin-electron/simple'
|
||||
|
||||
/**
|
||||
* Vite 配置文件
|
||||
* 支持 TypeScript 和 JavaScript
|
||||
* @param {Object} param0 - 配置参数
|
||||
* @param {string} param0.mode - 环境模式
|
||||
* @returns {import('vite').UserConfig}
|
||||
*/
|
||||
export default defineConfig(({ mode }) => {
|
||||
const env = loadEnv(mode, process.cwd(), '')
|
||||
const DEV_TOKEN = env.VITE_DEV_TOKEN || ''
|
||||
const TENANT_ID = env.VITE_TENANT_ID || '1'
|
||||
const API_TARGET = env.VITE_PROXY_TARGET || 'http://localhost:9900'
|
||||
|
||||
return {
|
||||
plugins: [
|
||||
vue(),
|
||||
vueJsx(),
|
||||
UnoCSS(),
|
||||
vueDevTools(),
|
||||
tailwindcss()
|
||||
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
'@gold': fileURLToPath(new URL('../../', import.meta.url))
|
||||
},
|
||||
},
|
||||
server: {
|
||||
proxy: {
|
||||
// 代理 /webApi 开头的请求
|
||||
'/webApi': {
|
||||
target: API_TARGET,
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/webApi/, ''),
|
||||
configure: (proxy, options) => {
|
||||
proxy.on('proxyReq', (proxyReq, req) => {
|
||||
// 从客户端请求头中获取 authorization
|
||||
const authHeader = req.headers?.authorization
|
||||
if (authHeader) {
|
||||
proxyReq.setHeader('authorization', authHeader)
|
||||
} else if (DEV_TOKEN) {
|
||||
// 兜底:使用环境变量中的 token
|
||||
proxyReq.setHeader('authorization', `Bearer ${DEV_TOKEN}`)
|
||||
}
|
||||
|
||||
// 添加 RuoYi 租户 ID
|
||||
proxyReq.setHeader('tenant-id', TENANT_ID)
|
||||
})
|
||||
},
|
||||
},
|
||||
// OSS 代理 - 用于直传上传
|
||||
'/oss': {
|
||||
target: 'https://muye-ai-chat.oss-cn-hangzhou.aliyuncs.com',
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/oss/, ''),
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
||||
'Access-Control-Allow-Headers': '*',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
117
frontend/app/web-gold/vite.config.ts
Normal file
117
frontend/app/web-gold/vite.config.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import { defineConfig, loadEnv } from 'vite'
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
import process from 'node:process'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import vueJsx from '@vitejs/plugin-vue-jsx'
|
||||
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||
import UnoCSS from 'unocss/vite'
|
||||
import { compression, defineAlgorithm } from 'vite-plugin-compression2'
|
||||
import zlib from 'zlib'
|
||||
|
||||
// 压缩配置常量
|
||||
const COMPRESSION_THRESHOLD = 1024
|
||||
|
||||
// 环境变量默认值
|
||||
const DEFAULT_ENV_VALUES = {
|
||||
DEV_TOKEN: '',
|
||||
TENANT_ID: '1',
|
||||
API_TARGET: 'http://localhost:9900',
|
||||
}
|
||||
|
||||
export default defineConfig(({ mode }) => {
|
||||
const env = loadEnv(mode, process.cwd(), '')
|
||||
const DEV_TOKEN = env.VITE_DEV_TOKEN || DEFAULT_ENV_VALUES.DEV_TOKEN
|
||||
const TENANT_ID = env.VITE_TENANT_ID || DEFAULT_ENV_VALUES.TENANT_ID
|
||||
const API_TARGET = env.VITE_PROXY_TARGET || DEFAULT_ENV_VALUES.API_TARGET
|
||||
|
||||
return {
|
||||
plugins: [
|
||||
vue(),
|
||||
vueJsx(),
|
||||
UnoCSS(),
|
||||
vueDevTools(),
|
||||
tailwindcss(),
|
||||
// 压缩配置:同时生成 gzip 和 brotli 压缩文件
|
||||
compression({
|
||||
algorithms: [
|
||||
defineAlgorithm('gzip', { level: 9 }),
|
||||
defineAlgorithm('brotliCompress', {
|
||||
params: {
|
||||
[zlib.constants.BROTLI_PARAM_QUALITY]: 11
|
||||
}
|
||||
})
|
||||
],
|
||||
threshold: COMPRESSION_THRESHOLD,
|
||||
}),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
'@gold': fileURLToPath(new URL('../../', import.meta.url)),
|
||||
},
|
||||
},
|
||||
server: {
|
||||
proxy: {
|
||||
'/webApi': {
|
||||
target: API_TARGET,
|
||||
changeOrigin: true,
|
||||
rewrite: (path: string) => path.replace(/^\/webApi/, ''),
|
||||
configure: (proxy: any) => {
|
||||
proxy.on('proxyReq', (proxyReq: any, req: any) => {
|
||||
const authHeader = req.headers?.authorization
|
||||
if (authHeader) {
|
||||
proxyReq.setHeader('authorization', authHeader)
|
||||
} else if (DEV_TOKEN) {
|
||||
proxyReq.setHeader('authorization', `Bearer ${DEV_TOKEN}`)
|
||||
}
|
||||
proxyReq.setHeader('tenant-id', TENANT_ID)
|
||||
})
|
||||
},
|
||||
},
|
||||
'/oss': {
|
||||
target: 'https://muye-ai-chat.oss-cn-hangzhou.aliyuncs.com',
|
||||
changeOrigin: true,
|
||||
rewrite: (path: string) => path.replace(/^\/oss/, ''),
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
||||
'Access-Control-Allow-Headers': '*',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
build: {
|
||||
// 生产环境去除 console 和 debugger
|
||||
esbuild: {
|
||||
drop: mode === 'production' ? ['console', 'debugger'] : [],
|
||||
},
|
||||
// 使用 esbuild 压缩
|
||||
minify: 'esbuild',
|
||||
// 关闭 source map 以减小体积
|
||||
sourcemap: false,
|
||||
// 块大小警告阈值
|
||||
chunkSizeWarningLimit: 1500,
|
||||
// 浏览器兼容性目标(使用最新的广泛支持版本)
|
||||
target: 'baseline-widely-available',
|
||||
// Rollup 配置优化
|
||||
rollupOptions: {
|
||||
output: {
|
||||
// 手动代码分割
|
||||
manualChunks: {
|
||||
// Vue 生态系统
|
||||
'vue-vendor': ['vue', 'vue-router', 'pinia'],
|
||||
// UI 组件库
|
||||
'ui-vendor': ['ant-design-vue', '@ant-design/icons-vue'],
|
||||
// 工具库
|
||||
'utils': ['dayjs', 'qs', 'path-to-regexp'],
|
||||
// AI 相关
|
||||
'ai-sdk': ['@ai-sdk/anthropic', '@ai-sdk/openai', 'ai'],
|
||||
// 文件处理
|
||||
'file-tools': ['xlsx', 'xlsx-js-style'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
@@ -16,6 +16,7 @@
|
||||
"github-markdown-css": "^5.8.1",
|
||||
"localforage": "^1.10.0",
|
||||
"unocss": "^66.5.4",
|
||||
"vite-plugin-compression2": "^2.4.0",
|
||||
"web-storage-cache": "^1.1.1"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user