diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..5a5809d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}] +charset = utf-8 +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +end_of_line = lf +max_line_length = 100 diff --git a/.env b/.env new file mode 100644 index 0000000..6a61e22 --- /dev/null +++ b/.env @@ -0,0 +1,13 @@ +VITE_SOCKET_URL=http://172.18.32.98:8888/ +VITE_URL=http://172.18.32.98:8888/ +VITE_PREVIEW=false +VITE_BASE_API=/api +VITE_OPENAI_API=/openai +VITE_VERSION='0.1.16' + +PASTED_TEXT_CHARACTER_LIMIT = 1000; + +WEBUI_VERSION = APP_VERSION; +WEBUI_BUILD_HASH = APP_BUILD_HASH; +REQUIRED_OLLAMA_VERSION = '0.1.16'; + diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..4825456 --- /dev/null +++ b/.env.development @@ -0,0 +1,13 @@ +VITE_SOCKET_URL=http://localhost:5173 +VITE_URL=http://172.18.32.98:8888/ +VITE_PREVIEW=false +VITE_BASE_API=/api +VITE_OPENAI_API=/openai +VITE_VERSION='0.1.16' + +PASTED_TEXT_CHARACTER_LIMIT = 1000; + +WEBUI_VERSION = APP_VERSION; +WEBUI_BUILD_HASH = APP_BUILD_HASH; +REQUIRED_OLLAMA_VERSION = '0.1.16'; + diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6313b56 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8ee54e8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +.DS_Store +dist +dist-ssr +coverage +*.local + +/cypress/videos/ +/cypress/screenshots/ + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +*.tsbuildinfo diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..a06a8c6 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "Vue.volar", + "vitest.explorer", + "dbaeumer.vscode-eslint", + "EditorConfig.EditorConfig", + "esbenp.prettier-vscode" + ] +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..9e5fc8f --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite App + + +
+ + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..e6e9278 --- /dev/null +++ b/package.json @@ -0,0 +1,60 @@ +{ + "name": "radar-system", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --open ", + "build": "vite build", + "preview": "vite preview", + "test:unit": "vitest", + "build-only": "vite build", + "lint": "eslint . --fix", + "format": "prettier --write src/" + }, + "dependencies": { + "socket.io-client": "^4.2.0", + "ant-design-vue": "4.x", + "echarts": "^5.6.0", + "lodash": "^4.17.21", + "pinia": "^3.0.1", + "vue": "^3.5.13", + "vue-router": "^4.5.0", + "screenfull": "^5.1.0" + }, + "devDependencies": { + "@ant-design/icons-vue": "^7.0.1", + "@tsconfig/node22": "^22.0.1", + "@types/jsdom": "^21.1.7", + "@types/node": "^22.14.0", + "@vitejs/plugin-vue": "^5.2.3", + "@vitejs/plugin-vue-jsx": "^4.1.2", + "@vitest/eslint-plugin": "^1.1.39", + "@vue/eslint-config-prettier": "^10.2.0", + "@vue/eslint-config-typescript": "^14.5.0", + "@vue/test-utils": "^2.4.6", + "@vue/tsconfig": "^0.7.0", + "autoprefixer": "^10.4.19", + "dayjs": "^1.11.13", + "eslint": "^9.22.0", + "eslint-plugin-vue": "~10.0.0", + "jiti": "^2.4.2", + "jsdom": "^26.0.0", + "npm-run-all2": "^7.0.2", + "postcss-normalize-charset": "^6.0.1", + "postcss-preset-env": "^9.3.0", + "prettier": "3.5.3", + "sass": "^1.66.1", + "sass-loader": "^13.3.2", + "typescript": "~5.8.0", + "vite": "^6.2.4", + "vite-plugin-vue-devtools": "^7.7.2", + "vitest": "^3.1.1", + "vue-tsc": "^2.2.8" + }, + "packageManager": "pnpm@6.11.0", + "engines": { + "node": ">=18.18.2 <= 22.10.0", + "pnpm": ">=6.11.0" + } +} diff --git a/public/css/tinymceEdit--wirte.css b/public/css/tinymceEdit--wirte.css new file mode 100644 index 0000000..48aec65 --- /dev/null +++ b/public/css/tinymceEdit--wirte.css @@ -0,0 +1,18 @@ +@font-face { + font-family: '黑体'; + src: url('/static/font/MyFont.ttf'); +} + +@font-face { + font-family: 'Times New Roman'; + src: url('/static/font/Times-New-Romance-1.ttf'); +} + +@font-face { + font-family: '宋体'; + src: url('/static/font/ZiTiGuanJiaFangSongTi-2.ttf'); +} + +* { + line-height: 1.5; +} diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..46c2933 Binary files /dev/null and b/public/favicon.ico differ diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..986629a --- /dev/null +++ b/src/App.vue @@ -0,0 +1,45 @@ + + + + + diff --git a/src/apis/auth/index.ts b/src/apis/auth/index.ts new file mode 100644 index 0000000..8945976 --- /dev/null +++ b/src/apis/auth/index.ts @@ -0,0 +1,10 @@ +import { request } from '@/utils/request/index.ts'; + +// 中断任务 +// export const interruptApi = async (taskId: string) => { +// return await request({ +// base: '/api', +// url: `/tasks/stop/${taskId}`, +// method: 'POST', +// }) +// }; diff --git a/src/apis/dataManage/dataExport.ts b/src/apis/dataManage/dataExport.ts new file mode 100644 index 0000000..1b93477 --- /dev/null +++ b/src/apis/dataManage/dataExport.ts @@ -0,0 +1,43 @@ +// 数据导出相关 +import { request } from '@/utils/request/index.ts'; +// 列表接口 +export const listApi = async (data?: Record) => { + return await request({ + url: `/data/list_info`, + method: 'GET', + params: data + }) +}; + +// 更新标注 +export const updateLabelApi = async (data: Record) => { + return await request({ + url: `/data/save_label`, + method: 'POST', + data: data + }) +} + +// 获取导出数据文件在线地址 +export const getExportUrlApi = async (data: Record) => { + return await request({ + url: `/data/export_data`, + method: 'GET', + params: data, + getRaw: true + }) +} + +// 导入标注 +export const importLabelByFileApi = async (data: Record) => { +return await request({ + url: `/data/batch_label`, + method: 'POST', + headers: { + Accept: 'application/json', + authorization: `Bearer ${localStorage.token}` + }, + data, + dataRaw: true, + }) +} diff --git a/src/apis/dataManage/dataImport.ts b/src/apis/dataManage/dataImport.ts new file mode 100644 index 0000000..8dec990 --- /dev/null +++ b/src/apis/dataManage/dataImport.ts @@ -0,0 +1,43 @@ +// 数据导入相关 + +import { request } from '@/utils/request/index.ts'; +// 列表接口 +export const listApi = async () => { + return await request({ + url: `/data/list_data`, + method: 'GET', + }) +}; + +export const deleteApi = async (data: Record) => { + return await request({ + url: `/data/delete/${data.fileName}`, + method: 'DELETE', + }) +} + +export const importApi = async (data: Record) => { + console.log('importApi', data) + return await request({ + url: `/data/import`, + method: 'POST', + headers: { + Accept: 'application/json', + authorization: `Bearer ${localStorage.token}` + }, + data, + dataRaw: true, + }) + +} + + + + // 更新 + export const updateApi = async (data: Record) => { + return await request({ + url: `/data/data`, + method: 'POST', + data: data + }) + } diff --git a/src/apis/dataManage/statistics.ts b/src/apis/dataManage/statistics.ts new file mode 100644 index 0000000..a490521 --- /dev/null +++ b/src/apis/dataManage/statistics.ts @@ -0,0 +1,22 @@ +// 统计相关 + +import { request } from '@/utils/request/index.ts'; + +// 统计数据 +export const statisticsApi = async () => { + return await request({ + url: `/data/statistics`, + method: 'GET', + }) +}; + +// 根据bacthid获取统计数据 +export const lineStatisticsApi = async (bacthId: string, controller?: AbortController) => { + return await request({ + url: `/data/query_plot_data/${bacthId}`, + method: 'GET', + controller: controller + }) +} + + diff --git a/src/apis/dataset/index.ts b/src/apis/dataset/index.ts new file mode 100644 index 0000000..a816dc2 --- /dev/null +++ b/src/apis/dataset/index.ts @@ -0,0 +1,52 @@ +// 数据集相关 +import { request } from '@/utils/request/index.ts'; + +// ?数据集 +export const reasonDatasetListApi = async () => { + return request({ + url: '/performance/datasets', + method: 'GET', + }) +} + +// 测试集 +// export const testDatasetListApi = async () => { +// return request({ +// url: '/config/test_datasets', +// method: 'GET', +// }) +// } + +// // 训练集 +// export const trainDatasetListApi = async () => { +// return request({ +// url: '/config/train_datasets', +// method: 'GET', +// }) +// } + +// 数据集 +export const datasetListApi = async (params: Record) => { + return request({ + url: '/data/exported_data', + method: 'GET', + params: params + }) +} + +// 更新数据集 +export const updateDatasetApi = async (params: Record) => { + return request({ + url: '/data/exported_data', + method: 'PUT', + data: params + }) +} + +// 删除数据集 +export const deleteDatasetApi = async (id: number | string) => { +return request({ + url: `/data/exported_data/${id}`, + method: 'DELETE', + }) +} diff --git a/src/apis/model/index.ts b/src/apis/model/index.ts new file mode 100644 index 0000000..99199f7 --- /dev/null +++ b/src/apis/model/index.ts @@ -0,0 +1,45 @@ +import { request } from '@/utils/request/index.ts'; + + +// 新增 +export const addApi = async (data: Record, controller?: AbortController) => { + return await request({ + url: `/train/model/import`, + method: 'POST', + headers: { + Accept: 'application/json', + authorization: `Bearer ${localStorage.token}` + }, + data: data, + dataRaw: true, + }) +}; + +// 修改 +export const updateApi = async (params: string, controller?: AbortController) => { + return await request({ + url: `/train/model`, + method: 'PUT', + data: params + }) +}; + +// 删除 +export const deleteApi = async (modelId: string) => { + return await request({ + url: `/train/model/${modelId}`, + method: 'DELETE', + }) +} + +// 列表 +export const listApi = async (params?: Record) => { + return await request({ + url: `/train/model/list`, + method: 'GET', + params: params + }) +} + + + diff --git a/src/apis/model/reasoning.ts b/src/apis/model/reasoning.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/apis/model/train.ts b/src/apis/model/train.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/apis/system/config.ts b/src/apis/system/config.ts new file mode 100644 index 0000000..5b15288 --- /dev/null +++ b/src/apis/system/config.ts @@ -0,0 +1,25 @@ +// 配置相关 +import { request } from '@/utils/request/index.ts'; + +// 获取模型配置 +export const modelConfigApi = async () => { + return await request({ + url: `/config/models`, + method: 'GET', + }) +} + +// 获取数据集配置 +export const testDatasetConfigApi = async () => { + return await request({ + url: `/config/test_datasets`, + method: 'GET', + }) +} + +export const getPlatformByModuleNameApi = (moduleName: string) => { + return request({ + url: `/config/model/${moduleName}/platform`, + method: 'GET', + }) +} diff --git a/src/apis/system/index.ts b/src/apis/system/index.ts new file mode 100644 index 0000000..6af1da5 --- /dev/null +++ b/src/apis/system/index.ts @@ -0,0 +1,34 @@ +// 系统相关 +import { request } from '@/utils/request/index.ts'; + +// 获取cpu使用情况 +export const cpuUsageApi = async () => { + return await request({ + url: `/system/cpu/usage`, + method: 'GET', + }) +} + +// 获取gpu使用情况 +export const gpuUsageApi = async () => { + return await request({ + url: `/system/gpu/usage`, + method: 'GET', + }) +} + +// 获取gpu信息 +export const gpuInfoApi = async () => { + return await request({ + url: `/system/gpu/info`, + method: 'GET', + }) +} + +// 获取内存使用情况 +export const memoryInfoApi = async () => { + return await request({ + url: `/system/memory/usage/formatted`, + method: 'GET', + }) +} diff --git a/src/apis/system/model.ts b/src/apis/system/model.ts new file mode 100644 index 0000000..2aca0c6 --- /dev/null +++ b/src/apis/system/model.ts @@ -0,0 +1 @@ +// 模型相关 diff --git a/src/apis/train/index.ts b/src/apis/train/index.ts new file mode 100644 index 0000000..d86fd7d --- /dev/null +++ b/src/apis/train/index.ts @@ -0,0 +1,67 @@ +// 训练相关 +import { request } from '@/utils/request' + +// 获取列表 +export const listApi = async () => { + return await request({ + url: `/train/tasks`, + method: 'GET' + }) +} + +export const deleteApi = async (taskId: string) => { + return await request({ + url: `/train/${taskId}`, + method: 'DELETE' + }) +} + +// 获取任务参数 +export const taskParamsApi = async (taskId: string) => { + return await request({ + url: `/train/${taskId}/params`, + method: 'GET' + }) +} + +// 获取训练详情 +export const statusApi = async (taskId: string) => { + return await request({ + url: `/train/${taskId}/status`, + method: 'GET' + }) +} + +// 获取训练日志 +export const logsApi = async (taskId: string) => { + return request({ + url: `/train/${taskId}/logs/stream`, + method: 'GET', + getRaw: true + }) +} + +// 创建任务 +export const createTaskApi = async (data: Record) => { + return await request({ + url: '/train/create', + method: 'POST', + data: data + }) +} + +// 中断 +export const onInterruptApi = async (taskId: string) => { + return await request({ + url: `/train/${taskId}/stop`, + method: 'POST' + }) +} + +// 模型导出 +export const modelExportApi = async (taskId: string) => { + return await request({ + url: `/train/${taskId}/export`, + method: 'GET' + }) +} diff --git a/src/apis/train/inference.ts b/src/apis/train/inference.ts new file mode 100644 index 0000000..0b8a72f --- /dev/null +++ b/src/apis/train/inference.ts @@ -0,0 +1,84 @@ +// 推理任务相关 +import { request } from '@/utils/request' + +// 获取推理任务列表 +export const listApi = async () => { + return await request({ + url: '/inference/tasks', + method: 'GET' + }) +} + +// 删除任务 +export const deleteApi = async (taskId: string) => { + return await request({ + url: `/inference/tasks/${taskId}`, + method: 'DELETE', + }) +} + +// 获取推理任务结果 +export const taskResultApi = async (taskId: string, controller?: AbortController) => { + return await request({ + url: `/inference/results/${taskId}`, + method: 'GET', + controller: controller, + }) +} + +// 获取推理任务状态 +export const taskStatusApi = async (taskId: string, controller?: AbortController) => { + return await request({ + url: `/inference/status/${taskId}`, + method: 'GET', + controller: controller, + }) +} + +// 启动推理任务 +export const createTaskApi = async (data: Record, controller?: AbortController) => { + return await request({ + url: '/inference/run', + method: 'POST', + data: data, + controller: controller, + }) +} + + +// 获取推理任务模式 +export const optionsApi = async () => { + return await request({ + url: '/inference/infer_types', + method: 'GET', + }) +} + +// 获取权重目录 +export const weightOptionsApi = async (params: Record) => { + return await request({ + url: '/inference/directories', + method: 'GET', + params: params + }) +} + +// 获取推理任务日志 +export const logsApi = async (taskId: string, controller?: AbortController) => { + return await request({ + url: `/inference/${taskId}/logs/stream`, + method: 'GET', + controller: controller, + getRaw: true + }) +} + +// 停止推理任务 +export const stopTaskApi = async (taskId: string, controller?: AbortController) => { + return await request({ + url: `/inference/${taskId}/stop`, + method: 'POST', + controller: controller, + }) +} + diff --git a/src/apis/train/performance.ts b/src/apis/train/performance.ts new file mode 100644 index 0000000..05532ef --- /dev/null +++ b/src/apis/train/performance.ts @@ -0,0 +1,19 @@ +// 运行效果相关 +import { request } from '@/utils/request' + +// 获取在特定数据集上运行过的模型列表 +export const getModelByDatasetApi = async (datasetName: string) => { + return await request({ + url: `/performance/datasets/${datasetName}/models`, + method: 'GET' + }) +} + +// 获取特定模型在特定数据集上的性能记录 +export const getPerformanceApi = async (data: Record) => { + return await request({ + url: `/performance/datasets/${data.datasetName}/models/${data.modelName}/${data.checkpoint_dir}`, + method: 'GET' + }) +} + diff --git a/src/assets/files/dataExport/导入标注模板文件.csv b/src/assets/files/dataExport/导入标注模板文件.csv new file mode 100644 index 0000000..8996a98 --- /dev/null +++ b/src/assets/files/dataExport/导入标注模板文件.csv @@ -0,0 +1,2 @@ +batch_id,label +20250612_960_0,A330 diff --git a/src/assets/icons/back.svg b/src/assets/icons/back.svg new file mode 100644 index 0000000..01871cb --- /dev/null +++ b/src/assets/icons/back.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/icons/chat.svg b/src/assets/icons/chat.svg new file mode 100644 index 0000000..f29db5d --- /dev/null +++ b/src/assets/icons/chat.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/close.svg b/src/assets/icons/close.svg new file mode 100644 index 0000000..9525858 --- /dev/null +++ b/src/assets/icons/close.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/correct.svg b/src/assets/icons/correct.svg new file mode 100644 index 0000000..6295d00 --- /dev/null +++ b/src/assets/icons/correct.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/data.svg b/src/assets/icons/data.svg new file mode 100644 index 0000000..0c3904b --- /dev/null +++ b/src/assets/icons/data.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/delete.svg b/src/assets/icons/delete.svg new file mode 100644 index 0000000..bec1ad7 --- /dev/null +++ b/src/assets/icons/delete.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/edit.svg b/src/assets/icons/edit.svg new file mode 100644 index 0000000..4563ba2 --- /dev/null +++ b/src/assets/icons/edit.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/email.svg b/src/assets/icons/email.svg new file mode 100644 index 0000000..4351ca4 --- /dev/null +++ b/src/assets/icons/email.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/icons/export.svg b/src/assets/icons/export.svg new file mode 100644 index 0000000..ccb2992 --- /dev/null +++ b/src/assets/icons/export.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/fileUpload.svg b/src/assets/icons/fileUpload.svg new file mode 100644 index 0000000..def82c7 --- /dev/null +++ b/src/assets/icons/fileUpload.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/import.svg b/src/assets/icons/import.svg new file mode 100644 index 0000000..7a95baf --- /dev/null +++ b/src/assets/icons/import.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/interrupt.svg b/src/assets/icons/interrupt.svg new file mode 100644 index 0000000..0c4fd14 --- /dev/null +++ b/src/assets/icons/interrupt.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/assets/icons/interruptLine.svg b/src/assets/icons/interruptLine.svg new file mode 100644 index 0000000..50889ff --- /dev/null +++ b/src/assets/icons/interruptLine.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/icons/lock.svg b/src/assets/icons/lock.svg new file mode 100644 index 0000000..e4f2778 --- /dev/null +++ b/src/assets/icons/lock.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/icons/plus.svg b/src/assets/icons/plus.svg new file mode 100644 index 0000000..67be93c --- /dev/null +++ b/src/assets/icons/plus.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/assets/icons/plusFill.svg b/src/assets/icons/plusFill.svg new file mode 100644 index 0000000..1fe152a --- /dev/null +++ b/src/assets/icons/plusFill.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/icons/refresh.svg b/src/assets/icons/refresh.svg new file mode 100644 index 0000000..afbfc1a --- /dev/null +++ b/src/assets/icons/refresh.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/save.svg b/src/assets/icons/save.svg new file mode 100644 index 0000000..2a6590e --- /dev/null +++ b/src/assets/icons/save.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/sort.svg b/src/assets/icons/sort.svg new file mode 100644 index 0000000..0c78b98 --- /dev/null +++ b/src/assets/icons/sort.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/stop.svg b/src/assets/icons/stop.svg new file mode 100644 index 0000000..3987afe --- /dev/null +++ b/src/assets/icons/stop.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/zoomIn.svg b/src/assets/icons/zoomIn.svg new file mode 100644 index 0000000..d952efe --- /dev/null +++ b/src/assets/icons/zoomIn.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/images/auth/auth-bg.jpg b/src/assets/images/auth/auth-bg.jpg new file mode 100644 index 0000000..557e6ab Binary files /dev/null and b/src/assets/images/auth/auth-bg.jpg differ diff --git a/src/assets/images/index/bg.jpg b/src/assets/images/index/bg.jpg new file mode 100644 index 0000000..442d8ce Binary files /dev/null and b/src/assets/images/index/bg.jpg differ diff --git a/src/assets/images/index/bg_old.jpg b/src/assets/images/index/bg_old.jpg new file mode 100644 index 0000000..21b43ba Binary files /dev/null and b/src/assets/images/index/bg_old.jpg differ diff --git a/src/assets/images/index/sider-radar.png b/src/assets/images/index/sider-radar.png new file mode 100644 index 0000000..1820bdc Binary files /dev/null and b/src/assets/images/index/sider-radar.png differ diff --git a/src/assets/images/logo/logo.png b/src/assets/images/logo/logo.png new file mode 100644 index 0000000..3b1aa9f Binary files /dev/null and b/src/assets/images/logo/logo.png differ diff --git a/src/assets/logo.png b/src/assets/logo.png new file mode 100644 index 0000000..46c2933 Binary files /dev/null and b/src/assets/logo.png differ diff --git a/src/components/Base/Layout/LoginLayout/index.vue b/src/components/Base/Layout/LoginLayout/index.vue new file mode 100644 index 0000000..c282c51 --- /dev/null +++ b/src/components/Base/Layout/LoginLayout/index.vue @@ -0,0 +1,49 @@ + + + + + + + diff --git a/src/components/Base/Layout/SystemLayout/Content/index.vue b/src/components/Base/Layout/SystemLayout/Content/index.vue new file mode 100644 index 0000000..21bf243 --- /dev/null +++ b/src/components/Base/Layout/SystemLayout/Content/index.vue @@ -0,0 +1,85 @@ + + + + + + + + diff --git a/src/components/Base/Layout/SystemLayout/Header/index.vue b/src/components/Base/Layout/SystemLayout/Header/index.vue new file mode 100644 index 0000000..7458f03 --- /dev/null +++ b/src/components/Base/Layout/SystemLayout/Header/index.vue @@ -0,0 +1,58 @@ + + + + + + + diff --git a/src/components/Base/Layout/SystemLayout/Sider/hooks/router.ts b/src/components/Base/Layout/SystemLayout/Sider/hooks/router.ts new file mode 100644 index 0000000..b00b489 --- /dev/null +++ b/src/components/Base/Layout/SystemLayout/Sider/hooks/router.ts @@ -0,0 +1,44 @@ +import { h } from 'vue' +import Icon from '@/components/Module/Icon/index.vue' + + +export const useMain = () => { + // 路由过滤,只显示要显示的路由 + const routerFilter = (menus: Array>) => { + return menus.filter((item: Record) => { + if (item.children) { + item.children = routerFilter(item.children) + } + + return !item.meta.hidden + }) + } + + // 路由格式化 + const routerFormat = (arr: Array>) => { + return arr.map((item: Record) => { + if (item.children && item.children.length) { + item.children = routerFormat(item.children) + } + return { + meta: item.meta, + icon: () => + h(Icon, { + name: item.meta.icon, + }), + fullPath: item.fullPath, + pid: item.parentId, + key: item.name, + label: item.meta.title || '', + title: item.meta.title || '', + children: item.children?.length ? item.children : '', + type: item.type || null, + } + }) + } + + return { + routerFilter, + routerFormat + } +} diff --git a/src/components/Base/Layout/SystemLayout/Sider/index.vue b/src/components/Base/Layout/SystemLayout/Sider/index.vue new file mode 100644 index 0000000..0cafa3f --- /dev/null +++ b/src/components/Base/Layout/SystemLayout/Sider/index.vue @@ -0,0 +1,242 @@ + + + + + + + diff --git a/src/components/Base/Layout/SystemLayout/index.vue b/src/components/Base/Layout/SystemLayout/index.vue new file mode 100644 index 0000000..a8acda7 --- /dev/null +++ b/src/components/Base/Layout/SystemLayout/index.vue @@ -0,0 +1,48 @@ + + + + + diff --git a/src/components/Base/Layout/ViewLayout/index.vue b/src/components/Base/Layout/ViewLayout/index.vue new file mode 100644 index 0000000..3084ee8 --- /dev/null +++ b/src/components/Base/Layout/ViewLayout/index.vue @@ -0,0 +1,12 @@ + + + + + diff --git a/src/components/Base/Tools/Info/index.vue b/src/components/Base/Tools/Info/index.vue new file mode 100644 index 0000000..7bf4ae7 --- /dev/null +++ b/src/components/Base/Tools/Info/index.vue @@ -0,0 +1,56 @@ + + + + + + + diff --git a/src/components/Base/Tools/Menus/index.vue b/src/components/Base/Tools/Menus/index.vue new file mode 100644 index 0000000..9d82de0 --- /dev/null +++ b/src/components/Base/Tools/Menus/index.vue @@ -0,0 +1,81 @@ + + + + + + + diff --git a/src/components/Base/Tools/UserBox/hooks/index.ts b/src/components/Base/Tools/UserBox/hooks/index.ts new file mode 100644 index 0000000..de4dc6d --- /dev/null +++ b/src/components/Base/Tools/UserBox/hooks/index.ts @@ -0,0 +1,36 @@ +export const useMain = () => { + const menus = [ + { + name: '设置', + path:'/toolsSet', + key: 'toolsSet', + icon: 'briefCase' + }, + { + name: '已归档对话', + path:'/workspace/knowledge', + key: 'knowledge', + icon: 'book' + }, + { + name: '管理员面板', + path:'/fileManagementSet', + key: 'fileManagementSet', + icon: 'file' + }, + ] + + const operateMenus = [ + { + name: '退出登录', + path:'/login', + key: 'login', + icon: 'briefCase' + } + ] + + return { + menus, + operateMenus + } +} diff --git a/src/components/Base/Tools/UserBox/index.vue b/src/components/Base/Tools/UserBox/index.vue new file mode 100644 index 0000000..f09d5c4 --- /dev/null +++ b/src/components/Base/Tools/UserBox/index.vue @@ -0,0 +1,71 @@ + + + + + + + diff --git a/src/components/Module/AsyncContent/ErrorLoading.vue b/src/components/Module/AsyncContent/ErrorLoading.vue new file mode 100644 index 0000000..5bf6e0f --- /dev/null +++ b/src/components/Module/AsyncContent/ErrorLoading.vue @@ -0,0 +1,21 @@ + + + + + + + diff --git a/src/components/Module/AsyncContent/index.vue b/src/components/Module/AsyncContent/index.vue new file mode 100644 index 0000000..8693f4b --- /dev/null +++ b/src/components/Module/AsyncContent/index.vue @@ -0,0 +1,14 @@ + + + + + + + diff --git a/src/components/Module/Icon/components/SvgIcon.vue b/src/components/Module/Icon/components/SvgIcon.vue new file mode 100644 index 0000000..fb45c66 --- /dev/null +++ b/src/components/Module/Icon/components/SvgIcon.vue @@ -0,0 +1,80 @@ + + + + + diff --git a/src/components/Module/Icon/components/icon.ts b/src/components/Module/Icon/components/icon.ts new file mode 100644 index 0000000..db1dde4 --- /dev/null +++ b/src/components/Module/Icon/components/icon.ts @@ -0,0 +1,92 @@ +// Icon Store - For efficient SVG icon imports (Vue3 Composition API) +import { ref } from 'vue'; + +type IconMap = { + [key: string]: string; +} + +// 响应式状态 +const availableIcons = ref({}); +const iconContents = ref({}); +const iconCache = new Map(); + +// 使用Vite的import.meta.glob动态导入 +export const initialize = async () => { + const iconModules = import.meta.glob('/src/assets/icons/*.svg', { + eager: true, + as: 'raw' + }); + + Object.entries(iconModules).forEach(([path, content]: [string, string]) => { + const name = path?.split('/')?.pop()?.replace('.svg', '') as string; + iconContents.value[name] = content; + availableIcons.value[name] = `data:image/svg+xml;base64,${btoa(content)}`; + }); + + return Object.keys(availableIcons).length; +}; + +// 获取图标URL +export const getIconUrl = (name: string) => { + if (iconCache.has(name)) return iconCache.get(name); + + const url = availableIcons.value[name]; + if (url) iconCache.set(name, url); + return url; +}; + +// 获取原始SVG内容 +export const getIconContent = (name: string) => { + return iconContents.value[name] || null; +}; + +initialize() + +// return { +// availableIcons: ref(availableIcons), +// iconContents: ref(iconContents), +// initialize, +// getIconUrl, +// getIconContent +// }; + +// 初始化图标存储 +// export function useIconStore() { +// // 使用Vite的import.meta.glob动态导入 +// const initialize = async () => { +// const iconModules = import.meta.glob('/src/lib/assets/icons/*.svg', { +// eager: true, +// as: 'raw' +// }); + +// Object.entries(iconModules).forEach(([path, content]: [string, string]) => { +// const name = path?.split('/')?.pop()?.replace('.svg', '') as string; +// iconContents.value[name] = content; +// availableIcons.value[name] = `data:image/svg+xml;base64,${btoa(content)}`; +// }); + +// return Object.keys(availableIcons).length; +// }; + +// // 获取图标URL +// const getIconUrl = (name: string) => { +// if (iconCache.has(name)) return iconCache.get(name); + +// const url = availableIcons.value[name]; +// if (url) iconCache.set(name, url); +// return url; +// }; + +// // 获取原始SVG内容 +// const getIconContent = (name: string) => { +// return iconContents.value[name] || null; +// }; + +// return { +// availableIcons: ref(availableIcons), +// iconContents: ref(iconContents), +// initialize, +// getIconUrl, +// getIconContent +// }; +// } diff --git a/src/components/Module/Icon/index.vue b/src/components/Module/Icon/index.vue new file mode 100644 index 0000000..397dcdf --- /dev/null +++ b/src/components/Module/Icon/index.vue @@ -0,0 +1,73 @@ + + + + + + + diff --git a/src/components/Module/Modal/components/ModalHeader.vue b/src/components/Module/Modal/components/ModalHeader.vue new file mode 100644 index 0000000..3813b7e --- /dev/null +++ b/src/components/Module/Modal/components/ModalHeader.vue @@ -0,0 +1,171 @@ + + + + + + + diff --git a/src/components/Module/Modal/index.vue b/src/components/Module/Modal/index.vue new file mode 100644 index 0000000..1eb79f2 --- /dev/null +++ b/src/components/Module/Modal/index.vue @@ -0,0 +1,187 @@ + + + + + + + diff --git a/src/components/__tests__/HelloWorld.spec.ts b/src/components/__tests__/HelloWorld.spec.ts new file mode 100644 index 0000000..2533202 --- /dev/null +++ b/src/components/__tests__/HelloWorld.spec.ts @@ -0,0 +1,11 @@ +import { describe, it, expect } from 'vitest' + +import { mount } from '@vue/test-utils' +import HelloWorld from '../HelloWorld.vue' + +describe('HelloWorld', () => { + it('renders properly', () => { + const wrapper = mount(HelloWorld, { props: { msg: 'Hello Vitest' } }) + expect(wrapper.text()).toContain('Hello Vitest') + }) +}) diff --git a/src/config/index.ts b/src/config/index.ts new file mode 100644 index 0000000..b53e802 --- /dev/null +++ b/src/config/index.ts @@ -0,0 +1,8 @@ +import logo from '@/assets/images/logo/logo.png' +import authBg from '@/assets/images/auth/auth-bg.jpg' + +export const info = { + logo: logo, + title: "雷达⽬标识别迁移部署系统", + bgImg: authBg +} diff --git a/src/core/lazy_use.ts b/src/core/lazy_use.ts new file mode 100644 index 0000000..90a4e7e --- /dev/null +++ b/src/core/lazy_use.ts @@ -0,0 +1,115 @@ +import type { App } from 'vue' +import { + App as antApp, + Button, + Tree, + ConfigProvider, + Layout, + Menu, + PageHeader, + Dropdown, + Drawer, + Space, + Switch, + Input, + Form, + Popover, + Row, + Col, + Select, + Checkbox, + Modal, + Tag, + AutoComplete, + Upload, + Radio, + Tabs, + Timeline, + Carousel, + Spin, + List, + Tooltip, + Card, + DatePicker, + Divider, + message, + InputNumber, + TreeSelect, + Steps, + Pagination, + Popconfirm, + Cascader, + Badge, + Empty, + Progress, + Affix, + Descriptions, + Slider, + Table +} from 'ant-design-vue' + +import { router } from '@/router/index.ts' + + +const [ messageApi, contextHolder] = message.useMessage(); + +// 注册全局组件 +export function loadComponent(app: App) { + app + .use(ConfigProvider) + .use(Layout) + .use(Checkbox) + .use(Menu) + .use(Button) + .use(Tree) + .use(antApp) + .use(PageHeader) + .use(Dropdown) + .use(Drawer) + .use(Space) + .use(Switch) + .use(Input) + .use(Form) + .use(Popover) + .use(Row) + .use(Col) + .use(Select) + .use(Table) + .use(Modal) + .use(Tag) + .use(AutoComplete) + .use(Upload) + .use(Radio) + .use(Tabs) + .use(Timeline) + .use(Carousel) + .use(Spin) + .use(List) + .use(Tooltip) + .use(Card) + .use(DatePicker) + .use(Divider) + .use(List) + .use(InputNumber) + .use(TreeSelect) + // .use(hevueImgPreview) + .use(Steps) + .use(Pagination) + .use(Popconfirm) + .use(Cascader) + .use(Badge) + .use(Empty) + .use(Progress) + .use(Affix) + .use(Descriptions) + .use(Slider) + + +} + +// 注册全局方法 +export function loadFunc (app: App) { + + +} + diff --git a/src/directive/index.ts b/src/directive/index.ts new file mode 100644 index 0000000..c3043ae --- /dev/null +++ b/src/directive/index.ts @@ -0,0 +1,8 @@ +import type { App } from 'vue' + + +function directive(app: App) { + // 注册指令 +} + +export default directive; diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..6a56953 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,36 @@ +// import './assets/main.css' +import '@/styles/index.css' +import 'ant-design-vue/dist/reset.css' + +import { createApp } from 'vue' +import { setupStore } from '@/stores' +import { router, setupRouter } from '@/router' +import { setRouteGuard } from '@/router/guard/index' +import { loadComponent, loadFunc } from '@/core/lazy_use' + +import * as Icons from '@ant-design/icons-vue' + +import App from './App.vue' +// import router from './router' + + + +const app = createApp(App) + +loadComponent(app) + +loadFunc(app) + +setupStore(app) + +setupRouter(app) + +setRouteGuard(router) + +const icons:any = Icons +for (const i in icons) { + app.component(i, icons[i]) +} + + +app.mount('#app') diff --git a/src/router/base/index.ts b/src/router/base/index.ts new file mode 100644 index 0000000..c7193ad --- /dev/null +++ b/src/router/base/index.ts @@ -0,0 +1,40 @@ +import type { AppRouter } from '@/router/config/index' + +export const baseRouter: AppRouter= { + name: 'Base', + path: '', + meta: { + title: '首页', + hidden: false, + hiddenInMenu: true, + }, + component: 'BaseLayout', + children: [] +} + +export const globalRouter = [ + { + name: 'LoginView', + path: '/login', + redirect: '/login/index', + icon: 'HomeOutlined', + hidden: false, + meta: { + icon: 'HomeOutlined', + title: '用户登录', + }, + component: () => import('@/components/Base/Layout/LoginLayout/index.vue'), + children: [ + { + name: 'Login', + path: 'index', + meta: { + hidden: false, + title: '用户登录', + }, + component: () => import('@/views/login/index.vue'), + + }, + ] + }, +] diff --git a/src/router/config/index.ts b/src/router/config/index.ts new file mode 100644 index 0000000..b90d0ab --- /dev/null +++ b/src/router/config/index.ts @@ -0,0 +1,61 @@ +import type { + RouteRecordRaw, + RouteMeta +} from 'vue-router' + +import type { defineComponent } from 'vue' + + +export type Component = ReturnType | (() => Promise) | (() => Promise) + +// @ts-expect-error +export interface AppRouter extends Omit { + keepAlive?: boolean + visible?: boolean + icon?: string + name?: string + key?: string + sort?: number + parent?: AppRouter | null + parentId?: number | string + menuId?: number | string + meta: RouteMeta + component?: Component | string + components?: Component + componentName?: string + children?: AppRouter[] + props?: Recordable, + path?: string, + fullPath?: string, + hidden?: boolean, + type?: string | null, + typeName?: string | null, + menuName?: string | null + menuType?: string | null +} + + +// export interface AppRouter { +// keepAlive?: boolean, // 缓存 +// component?: RawRouteComponent | string | null | undefined | any, // 组件 +// children?: AppRouter[], // 子路由 +// path?: string, // 路径 +// redirect?: string, // 重定向 +// isFrame?: number, // 外链 +// target?: string // 外链打开方式 新标签打开还是当前页面打开 +// key?: string, +// hidden: boolean, // 是否隐藏 +// title?: string // 标题 +// // visible?: boolean // +// // icon?: string // 图标 +// name: string // 路由name +// sort?: number // 排序 +// parentId?: number // 父节点 +// // component?: Component | string +// // components?: Component +// // componentName?: string // +// hideChildrenInMenu?: boolean, // 隐藏下级节点 +// meta?: RouteMeta, // 信息 +// // props?: Recordable +// // fullPath?: string // 全路径 +// } diff --git a/src/router/func/index.ts b/src/router/func/index.ts new file mode 100644 index 0000000..c70b1f2 --- /dev/null +++ b/src/router/func/index.ts @@ -0,0 +1,70 @@ +import { layouts, staticRouters } from "@/router/modules/routerComponents" +import type { AppRouter } from '@/router/config/index' +import { validURL } from "@/utils/valid" +import type { Component } from "vue" +import { uuid } from '@/utils/util' + + +const constantRouterComponents: Component = { + ...layouts, + ...staticRouters +} + +const menuTypeFormat = (type?: string) => { + if (!type) return null + const menuTypeObj: ExtraObj = { + 'G': 'group', // 分组 + 'F': 'button', // 按钮 + 'C': 'menu', // 菜单 + 'M': 'catalogue', // 目录 + } + return menuTypeObj[type] || null +} + +// 构造路由 +export const generator = async (routerMap: AppRouter[] | ExtraObj[], parent:AppRouter | null): Promise => { + const newRouter = await Promise.all(routerMap.map(async (item: AppRouter | ExtraObj) => { + const { title, hideChildren, hiddenHeaderContent, hidden, icon, isFrame, target } = item.meta || {} + const currentRouter: AppRouter = { + path: item.path, + key: item.key, + fullPath: (parent ? `${parent?.fullPath ?? ''}${item.path?.includes('/') ? item.path : '/' + item.path}` : `${item.path}`), + // 路由名称,建议唯一 + name: item.menuId || item.name || item.key || uuid(), + // 该路由对应页面的 组件(动态加载) + component: constantRouterComponents[item.component] || (() => import(`@/views/${item.component}`)), + // hideChildrenInMenu: item.hideChildrenInMenu, + // meta: 页面标题, 菜单图标, 页面权限(供指令权限用,可去掉) + hidden: item.hidden, + parent: parent, + parentId: item.parentId ?? parent?.name, + icon: item?.meta?.icon || item.icon as string | undefined, + meta: { + icon: item?.meta?.icon || item.icon, + isFrame: isFrame, + title: title || item.menuName, + hideChildrenInMenu: hideChildren, + hiddenHeaderContent: hiddenHeaderContent, + // 目前只能通过判断path的http链接来判断是否外链 + target: (validURL(item.path) ? '_blank' : ''), + permission: item.name, + hidden: hidden + }, + type: item.type || menuTypeFormat(item.menuType), + typeName: item.typeName || null, + redirect: item.redirect + } + if (item.component && !constantRouterComponents[item.component]) { + currentRouter.path = `${parent?.path ?? ''}/${item.path}` + } + // 是否设置了隐藏子菜单 + // 是否有子菜单,并递归处理,并将父path传入 + if (item.children && item.children.length > 0) { + // Recursion + currentRouter.children = await generator(item.children, currentRouter) + } + return currentRouter + })) + + return newRouter +} diff --git a/src/router/guard/before.ts b/src/router/guard/before.ts new file mode 100644 index 0000000..3dd24e7 --- /dev/null +++ b/src/router/guard/before.ts @@ -0,0 +1,20 @@ +import { useSocketStoreWithOut } from '@/stores/modules/socket' + +const socketStore = useSocketStoreWithOut() + + + +// 初始化socket +export const initSocket = () => { + if (!socketStore.socket) { + socketStore.initSocket() + } +} + + +// 初始化 +export const init = async () => { + return Promise.all([ + await initSocket(), + ]) +} diff --git a/src/router/guard/index.ts b/src/router/guard/index.ts new file mode 100644 index 0000000..465f13c --- /dev/null +++ b/src/router/guard/index.ts @@ -0,0 +1,90 @@ +import type { Router, RouteRecordRaw, RouteRecordName } from 'vue-router' +import { useUserStore } from '@/stores/modules/user' +import { useRouterStoreWithOut } from '@/stores/modules/async-router' +import type { AppRouter } from '@/router/config/index' +import { APP_VERSION } from '@/stores/modules/mutation-types' +import { info } from '@/config/index.ts' +import { init as beforeInit } from '@/router/guard/before.ts' + +const allowName = ['Login', 'LoginView', '404'] +const defaultRouter = '/' + + +export function _window(key: string) { + if (window.hasOwnProperty('admin_config')) { + //@ts-ignore + return window.admin_config[key] + } + + return null +} + +// 版本校验 +const checkVersion = () => { + +} + + + + + + +export const setRouteGuard = (router:Router) => { + router.beforeEach(async (to, from, next) => { + window.document.title = `${to.meta.title as string} | ${info.title}` + console.log('from', from) + // beforeInit() + // 校验版本 + // checkVersion() + const userStore = useUserStore() + const token = userStore.getToken() + console.log('getToken', token) + // token失效走退出 + // if (token && !userStore.validToken()) { + // // userStore.logout() + // } + + // 无token + // if(!token || token === '') { + // console.log(to.name && allowName.includes(to.name as string)) + // if (to.name && allowName.includes(to.name as string)) { + // next() + // } else { + // // next({ path: '/login/index', query: { redirect: to.fullPath } }) + // next() + // } + // } else { + console.log('toROuter', to) + const routerStore = useRouterStoreWithOut() + const nowRouter = routerStore.ROUTERS + // 无角色, 重新初始化路由 + // if (!userStore.permissions?.length || (userStore.permissions?.length && !nowRouter.length)) + // if (!(userStore.permissions?.length && nowRouter.length)){ + // 结构化路由 + if (!(nowRouter.length)){ + await routerStore.loadRouter().then(res => { + const addRoters:AppRouter[] = res + console.log('addRoters', addRoters) + // routerStore.setRouter(addRoters) + addRoters.map((route)=> { + router.addRoute(route as RouteRecordRaw) + }) + }) + next({ path: to.fullPath, replace: true, query: to.query }) + } else { + // const userInfo = userStore.USERINFO ? JSON.parse(userStore.USERINFO) : {} + // console.log('userInfo', userInfo) + // if ((token || token !== '') && !userInfo.sysUserId) { + // userStore.getUserInfo() + // } + next() + // if (to.path == '/') { + // next({ path: defaultRouter, replace: true, query: to.query }) + + // } else { + // next() + // } + } + // } + }) +} diff --git a/src/router/index.ts b/src/router/index.ts new file mode 100644 index 0000000..4d8be1a --- /dev/null +++ b/src/router/index.ts @@ -0,0 +1,17 @@ +import { createRouter, createWebHashHistory } from 'vue-router' +import { globalRouter } from './base/index' +import type { App } from 'vue' + + +export const router = createRouter({ + history: createWebHashHistory(), + routes: [ + ...globalRouter + ], +}) + + +export function setupRouter(app: App) { + app.use(router) +} + diff --git a/src/router/modules/routerComponents.ts b/src/router/modules/routerComponents.ts new file mode 100644 index 0000000..894c741 --- /dev/null +++ b/src/router/modules/routerComponents.ts @@ -0,0 +1,21 @@ +import { defineAsyncComponent, markRaw } from 'vue' + +export const layouts = { + BaseLayout: () => import('@/components/Base/Layout/SystemLayout/index.vue'), + ViewLayout: () => import('@/components/Base/Layout/ViewLayout/index.vue'), + LoginLayout: () => import('@/components/Base/Layout/LoginLayout/index.vue'), +} + + + +export const staticRouters = { + Index: () => import('@/views/index/index.vue'), + Test: () => import('@/views/test/index.vue'), + NotFound: defineAsyncComponent(() => import('@/views/exception/404.vue')), + DataManage: defineAsyncComponent(() => import('@/views/dataManage/index/index.vue')), + ModelReasoning: defineAsyncComponent(() => import('@/views/modelReasoning/index/index.vue')), + ModelTraining: defineAsyncComponent(() => import('@/views/modelTraining/index/index.vue')), + ModelTrainingDetail: defineAsyncComponent(() => import('@/views/modelTraining/detail/index.vue')), + ModelManage: defineAsyncComponent(() => import('@/views/setting/model/index/index.vue')), +} + diff --git a/src/router/modules/staticRouter.ts b/src/router/modules/staticRouter.ts new file mode 100644 index 0000000..c3ba8a6 --- /dev/null +++ b/src/router/modules/staticRouter.ts @@ -0,0 +1,109 @@ +export const staticRouter = [ + + { + name: 'DataManage', + path: '/data-manage', + meta: { + icon: 'chat', + title: '数据管理', + hidden: false, + }, + component: 'DataManage', + }, + { + name: 'ModelTraining', + path: '/model-training', + redirect: '/model-training/index', + meta: { + icon: 'sort', + title: '模型训练', + hidden: false, + }, + component: 'ViewLayout', + children: [ + { + name: 'ModelTrainingIndex', + path: 'index', + hidden: false, + meta: { + icon: 'sort', + title: '模型训练', + hidden: true, + }, + component: 'ModelTraining', + }, + { + name: 'ModelTrainingDetail', + path: 'detail', + hidden: false, + meta: { + icon: 'sort', + title: '模型训练', + hidden: true, + }, + component: 'ModelTrainingDetail', + } + ] + }, + { + name: 'ModelReasoning', + path: '/model-reasoning', + meta: { + icon: 'data', + title: '模型推理', + hidden: false, + }, + component: 'ModelReasoning', + }, + { + name: 'ModelManage', + path: '/model-manage', + meta: { + icon: 'data', + title: '模型管理', + hidden: false, + }, + component: 'ModelManage', + }, + { + path: '', + meta: { + hidden: true, + }, + redirect: '/data-manage' + }, +] + +export const exceptionRouter = [ + { + path: '/404', + name: '404', + component: 'NotFound', + meta: { + icon: '', + title: '', + hidden: true, + }, + }, + { + path: '/test', + name: 'test', + component: 'Test', + meta: { + icon: '', + title: '', + hidden: true, + }, + }, + { + path: '/:pathMatch(.*)', + redirect: '/404', + hidden: true, + meta: { + icon: '', + title: '', + hidden: true, + }, + } +] + diff --git a/src/stores/counter.ts b/src/stores/counter.ts new file mode 100644 index 0000000..b6757ba --- /dev/null +++ b/src/stores/counter.ts @@ -0,0 +1,12 @@ +import { ref, computed } from 'vue' +import { defineStore } from 'pinia' + +export const useCounterStore = defineStore('counter', () => { + const count = ref(0) + const doubleCount = computed(() => count.value * 2) + function increment() { + count.value++ + } + + return { count, doubleCount, increment } +}) diff --git a/src/stores/index.ts b/src/stores/index.ts new file mode 100644 index 0000000..cd30903 --- /dev/null +++ b/src/stores/index.ts @@ -0,0 +1,10 @@ +import type { App } from 'vue' +import { createPinia } from 'pinia' + +const store = createPinia() + +export function setupStore(app: App) { + app.use(store) +} + +export { store } \ No newline at end of file diff --git a/src/stores/modules/async-router.ts b/src/stores/modules/async-router.ts new file mode 100644 index 0000000..ca10bfe --- /dev/null +++ b/src/stores/modules/async-router.ts @@ -0,0 +1,54 @@ +import { defineStore } from "pinia" +import { store } from '@/stores' +import { staticRouter, exceptionRouter } from '@/router/modules/staticRouter' +import type { AppRouter } from '@/router/config/index' +import { generator } from '@/router/func/index' +import { baseRouter } from '@/router/base/index' +import { handleTree } from "@/utils/util" +import { Row } from "ant-design-vue" + +interface router { + routerArr: AppRouter[] + asyncRouter: ExtraObj[] +} + + +export const useRouterStore = defineStore('router', { + state: (): router => ({ + routerArr: [], + asyncRouter: [] + }), + getters: { + ROUTERS(state):AppRouter[] { + return state.routerArr + }, + ASYNCROUTERS(state): ExtraObj[] { + return state.asyncRouter + }, + }, + actions: { + setRouter(data: AppRouter[] ) { + this.routerArr = data + }, + async loadRouter(): Promise { + let routers:AppRouter[] = [] + return new Promise(async (resolve) => { + + // 组装 + const menuNav: ExtraObj[] = [] + baseRouter.children = staticRouter.concat() as AppRouter[] + menuNav.push(baseRouter) + menuNav.push(...exceptionRouter) + // menuNav.push(...globalRouter) + routers = await generator(menuNav, null) + console.log('routers', routers) + this.routerArr = routers + resolve(routers) + }) + } + }, +}) + +export const useRouterStoreWithOut = () => { + return useRouterStore(store) +} diff --git a/src/stores/modules/mutation-types.ts b/src/stores/modules/mutation-types.ts new file mode 100644 index 0000000..01daca2 --- /dev/null +++ b/src/stores/modules/mutation-types.ts @@ -0,0 +1,6 @@ + +export const ACCESS_TOKEN = 'token' +export const ACCESS_EXPIRES = 'expires' +export const APP_VERSION = 'app_version' + + diff --git a/src/stores/modules/socket.ts b/src/stores/modules/socket.ts new file mode 100644 index 0000000..54f9e07 --- /dev/null +++ b/src/stores/modules/socket.ts @@ -0,0 +1,78 @@ +import { defineStore, acceptHMRUpdate } from "pinia" +import { store } from '@/stores' +import { socketInit } from '@/utils/socket/index.ts' +import type { Socket } from "socket.io-client" + +interface UserState { + socket: Socket | null, + connected:any, + reconnectAttempts: number, +} + +export const useSocketStore = defineStore('socket', { + state: (): UserState => ({ + socket: null, + connected: false, + reconnectAttempts: 0, + }), + getters: { + SOCKET(state) { + return state.socket + }, + isConnected(state) { + return state.connected + }, + attempts(state) { + return state.reconnectAttempts + } + }, + actions: { + // 获取token + initSocket() { + console.log('?initSocket') + const socket = socketInit(true) + this.$patch((state) => { + state.socket = socket + }) + // ===== 事件监听 ===== + + socket.on('connect', () => { + this.connected = true + console.log('[Socket] 已连接,socket id:', socket.id) + }) + + socket.on('disconnect', (reason) => { + this.connected = false + console.log(`[Socket] 已断开,原因: ${reason}`) + }) + + // 重连中,每次尝试都会触发 + socket.io.on('reconnect_attempt', (attempt: number) => { + this.reconnectAttempts = attempt + console.log(`[Socket] 重连尝试 #${attempt}`) + }) + + // 重连成功 + socket.io.on('reconnect', (attempt: number) => { + console.log(`[Socket] 重连成功,尝试次数: ${attempt}`) + }) + + // 重连失败 + socket.io.on('reconnect_failed', () => { + console.warn('[Socket] 重连失败,放弃继续重连') + }) + }, + // 清除仓库数据 + cleanStore() { + this.$reset() + } + }, +}) + +if (import.meta.hot) { + import.meta.hot.accept(acceptHMRUpdate(useSocketStore, import.meta.hot)) +} + +export const useSocketStoreWithOut = () => { + return useSocketStore(store) +} diff --git a/src/stores/modules/system.ts b/src/stores/modules/system.ts new file mode 100644 index 0000000..afd8fea --- /dev/null +++ b/src/stores/modules/system.ts @@ -0,0 +1,22 @@ +import { defineStore } from "pinia" +import { store } from '@/stores' + + +interface info { +} + + +export const systemStore = defineStore('system', { + state: (): info => ({ + }), + getters: { + + }, + actions: { + + }, +}) + +export const systemStoreWithOut = () => { + return systemStore(store) +} diff --git a/src/stores/modules/user.ts b/src/stores/modules/user.ts new file mode 100644 index 0000000..7922a1e --- /dev/null +++ b/src/stores/modules/user.ts @@ -0,0 +1,103 @@ +import { defineStore, acceptHMRUpdate } from "pinia" +import { store } from '@/stores' +import { ACCESS_TOKEN, ACCESS_EXPIRES, USER_INFO } from '@/stores/modules/mutation-types' +// import { loginApi } from '@/apis/auths/index' +import { notification } from 'ant-design-vue'; +import { useRouterStoreWithOut } from './async-router' + +interface LoginForm { + email: string; + password: string; +} + +interface UserState { + token: string | null + role: string | null + permissions: { + [key: string]: ExtraObj + }, + userInfo?: ExtraObj, +} + +export const useUserStore = defineStore('user', { + state: (): UserState => ({ + token: '', + role: null, + permissions: {}, + userInfo: {}, + }), + getters: { + ROLE(state) { + return state.role + }, + TOKEN(): string { + return this.getToken() + }, + USERINFO(state) { + return state.userInfo + }, + + }, + actions: { + // 获取token + getToken() { + const raw = localStorage.getItem(ACCESS_TOKEN) || '' + return raw + }, + // 设置token + setToken(token: string) { + console.log('ACCESS_TOKEN', ACCESS_TOKEN) + localStorage.setItem(ACCESS_TOKEN, token || '') + }, + // 设置用户数据 + setUserInfo(data: ExtraObj | null) { + this.$patch((state) => { + if (data) { + state.userInfo = data + state.permissions = data.permissions + state.role = data.role + } else { + state.userInfo = {} + state.permissions = {} + state.role = null + state.userInfo = {} + } + + }) + }, + // 登录 + async login(data: LoginForm) { + // let res: ExtraObj + // res = await loginApi(data) + // .catch((err) => { + // notification.error({ + // message: '登录失败', + // description: err.message, + // }) + // return err + // }) + // if (res.code == 200) { + // this.setToken(res.data.token) + // this.setUserInfo(res.data) + // this.getUserInfo() + // } + // return res + }, + // 获取用户信息 + async getUserInfo() { + + }, + // 退出 + logout() { + + }, + }, +}) + +if (import.meta.hot) { + import.meta.hot.accept(acceptHMRUpdate(useUserStore, import.meta.hot)) +} + +export const useUserStoreWithOut = () => { + return useUserStore(store) +} diff --git a/src/styles/index.css b/src/styles/index.css new file mode 100644 index 0000000..bbc22d8 --- /dev/null +++ b/src/styles/index.css @@ -0,0 +1,30 @@ +@charset "UTF-8"; +#app { + padding: 0; + margin: 0; + max-width: 100%; + width: 100%; + height: 100vh; +} + +p { + margin-top: 0; + margin-bottom: 0; +} + +button { + margin: 0; + /* 清除外边距 */ + padding: 0; + /* 清除内边距 */ + border: none; + /* 去除边框 */ + background: none; + /* 清除背景色 */ + font: inherit; + /* 继承父元素字体 */ + color: inherit; + /* 继承父元素文字颜色 */ + cursor: pointer; + /* 保持指针样式 */ +} diff --git a/src/styles/index.scss b/src/styles/index.scss new file mode 100644 index 0000000..a44ae04 --- /dev/null +++ b/src/styles/index.scss @@ -0,0 +1,34 @@ +html, +body, +p { +} + +#app { + padding: 0; + margin: 0; + max-width: 100%; + width: 100%; + height: 100vh; +} + +p { + margin-top: 0; + margin-bottom: 0; +} + +a, +a:hover, +a:visited, +a:link, +a:active { +} + +button { + margin: 0; /* 清除外边距 */ + padding: 0; /* 清除内边距 */ + border: none; /* 去除边框 */ + background: none; /* 清除背景色 */ + font: inherit; /* 继承父元素字体 */ + color: inherit; /* 继承父元素文字颜色 */ + cursor: pointer; /* 保持指针样式 */ +} diff --git a/src/styles/theme/default.json b/src/styles/theme/default.json new file mode 100644 index 0000000..abf96df --- /dev/null +++ b/src/styles/theme/default.json @@ -0,0 +1,30 @@ +{ + "token": { + "fontSize": 16, + "wireframe": false, + "fontSizeXL": 20, + "fontSizeHeading1": 36, + "fontSizeHeading2": 20, + "fontSizeHeading3": 16, + "fontSizeHeading4": 14, + "fontSizeHeading5": 12, + "lineHeight": 1.5, + "sizeStep": 4, + "borderRadiusLG": 16 + }, + "components": { + "Dropdown": { + "borderRadiusLG": 6, + "fontSize": 14, + "fontSizeSM": 12, + "lineWidthBold": 2 + }, + "Menu": { + "zIndexPopup": 1050, + "dropdownWidth": 260, + "radiusItem": 6 + }, + "Pagination": {}, + "Cascader": {} + } +} diff --git a/src/types/global.d.ts b/src/types/global.d.ts new file mode 100644 index 0000000..688a850 --- /dev/null +++ b/src/types/global.d.ts @@ -0,0 +1,37 @@ +import type { DefineComponent } from 'vue' + + +declare global { + type ExtraObj = { + [key?: string]: any + } + type Recordable = Record + + declare module '*.csv' { + const value: any; + export default value; + } +} + + +declare module '*.jpg' { + const value: any; + export default value; +} + +declare module '*.png' { + const value: any; + export default value; +} + + + +declare module '*.vue' { + const component: DefineComponent<{}, {}, any> + export default component +} + +declare module '@components/*'; + + + diff --git a/src/types/module.d.ts b/src/types/module.d.ts new file mode 100644 index 0000000..5468342 --- /dev/null +++ b/src/types/module.d.ts @@ -0,0 +1,37 @@ +declare module '*.vue' { + import { DefineComponent } from 'vue' + const Component: DefineComponent<{}, {}, any> + export default Component +} + +// declare module 'ant-design-vue/es/locale/*' { +// import { Locale } from 'ant-design-vue/types/locale-provider' +// const locale: Locale & ReadonlyRecordable +// export default locale as Locale & ReadonlyRecordable +// } + +declare module 'virtual:*' { + const result: any + export default result +} + +declare module 'lodash' { + const content: any + export = content +} +declare module '*.ts' { + const content: any + export = content +} + +declare module 'hevue-img-preview' { + const content: any + export = content +} + +declare module 'fabric-with-erasing' { + const content: any + export = content +} + +declare module '@ant-design/icons-vue'; diff --git a/src/utils/EvenStream/index.ts b/src/utils/EvenStream/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/utils/download/index.ts b/src/utils/download/index.ts new file mode 100644 index 0000000..97c2d48 --- /dev/null +++ b/src/utils/download/index.ts @@ -0,0 +1,187 @@ +// import { getEnv } from '@/lib/utils/env/index.ts' +import { request } from '@/utils/request/index.ts'; + + + +// 创建一个a标签,并做点击下载事件 +export function downloadFile(hrefUrl:any, fileName:any){ + console.log('downloadFile',hrefUrl,fileName); + const a = document.createElement('a') + a.href = hrefUrl + a.target = '_blank' // 新窗口打开 20250320 yjc 新增 + a.download = fileName // 下载后文件名 + document.body.appendChild(a) + a.click() // 点击下载 + document.body.removeChild(a) // 下载完成移除元素 +} +// 封装blob对象 +function dataURLToBlob(base64Str:any, mimeTypeStr:any) { + const bstr = window.atob(base64Str); // 解码 base-64 编码的字符串,base-64 编码使用方法是 btoa() + let length = bstr.length; + const u8arr = new Uint8Array(length); // 创建初始化为0的,包含length个元素的无符号整型数组 + while (length--) { + u8arr[length] = bstr.charCodeAt(length); // 返回在指定的位置的字符的 Unicode 编码 + } + return new Blob([u8arr], { type: mimeTypeStr }); // 返回一个blob对象 +} + +// 后端返回base64公共导出 +export function downloadFileByBase64(base64Str:any, mimeTypeStr:any, fileName:any){ + const myBlob = dataURLToBlob(base64Str, mimeTypeStr) + const myUrl = window.URL.createObjectURL(myBlob) + downloadFile(myUrl, fileName) +} +// 后端返回文件流公共导出 +export function downloadFileByFileFlow(blobData:any, mimeTypeStr:any, fileName:any) { + const blob = new Blob([blobData], { type: mimeTypeStr }) + const hrefUrl = window.URL.createObjectURL(blob) // 创建下载的链接 + downloadFile(hrefUrl, fileName); +} + +//将base64转换为文件 +export function base64Tofile(dataurl:any,fileName:any){ + console.log(dataurl,fileName); + // debugger; + let arr = dataurl.split(','), + mime = arr[0].match(/:(.*?);/)[1], + bstr = atob(arr[1]), + n = bstr.length, + u8arr = new Uint8Array(n); + while (n--) { + u8arr[n] = bstr.charCodeAt(n); + } + const blob =new Blob([u8arr], { type: mime }); + // @ts-ignore + blob.lastModifiedDate = new Date(); + // @ts-ignore + blob.name = fileName; + + console.log(blob,'blob'); + download(blob) + // window.location.replace(blobUrl) + + return blob; +} + + +export function download(downfile:any, fileName?: string) { + const tmpLink = document.createElement("a"); + const objectUrl = URL.createObjectURL(downfile); + + tmpLink.href = objectUrl; + tmpLink.target = '_blank' // 新窗口打开 20250320 yjc 新增 + tmpLink.download = fileName || downfile.name; + document.body.appendChild(tmpLink); + tmpLink.click(); + + document.body.removeChild(tmpLink); + URL.revokeObjectURL(objectUrl); +} + +// 生成唯一的uuid +export function getUUID(randomLength:any) { + function S4() { + return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1); + } + return ("p"+S4() + S4() + S4() + S4() + S4() + S4() + S4() + S4()) +} + +export function downloadWav(base64Data:any,name:any) { + // const tmpLink = document.createElement("a"); + // const objectUrl = URL.createObjectURL(downfile); + + // tmpLink.href = objectUrl; + // tmpLink.download = downfile.name; + // document.body.appendChild(tmpLink); + // tmpLink.click(); + + // document.body.removeChild(tmpLink); + // URL.revokeObjectURL(objectUrl); + const downloadLink = document.createElement('a'); + downloadLink.href = base64Data; // 这里的 base64Data 是之前转换得到的 Base64 字符串 + downloadLink.download = name; // 设置下载文件的名称 + downloadLink.click(); // 模拟点击下载链接 + } + +export function saveDatatoJson(data:any, filename:any) { + if (!data) { + console.error('Console.save: No data') + return; + } + if (!filename) filename = 'console.json' + if (typeof data === "object") { + data = JSON.stringify(data, undefined, 4) + } + const blob = new Blob([data], { + type: 'text/json' + }), + e = document.createEvent('MouseEvents'), + a = document.createElement('a') + a.download = filename + a.href = window.URL.createObjectURL(blob) + a.dataset.downloadurl = ['text/json', a.download, a.href].join(':') + e.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null) + a.dispatchEvent(e) +} +// file 文件转 base64 +export function fileToBase64(file:any) { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = () => { + resolve(reader.result); + }; + reader.onerror = (error) => { + reject(error); + }; + }); +} +// file 文件转换成 字节流 +export function fileToBytes(file:any) { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.readAsArrayBuffer(file); + reader.onload = () => { + resolve(reader.result); + }; + reader.onerror = (error) => { + reject(error); + }; + }); +} + +// base64转文件 +export function base64ToFile(base64Data: string, fileName: string, mimeType?: string): File { + // 从base64字符串中提取数据部分和MIME类型 + let dataType = ''; + let base64Content = ''; + + if (base64Data.includes(';base64,')) { + const parts = base64Data.split(';base64,'); + dataType = parts[0].split(':')[1]; + base64Content = parts[1]; + } else { + // 如果base64字符串不包含MIME信息,则使用传入的mimeType + dataType = mimeType || 'application/octet-stream'; + base64Content = base64Data; + } + + // 解码base64 + const byteCharacters = atob(base64Content); + const byteArrays = []; + + // 将字符串转换为字节数组 + for (let i = 0; i < byteCharacters.length; i++) { + byteArrays.push(byteCharacters.charCodeAt(i)); + } + + // 创建Uint8Array + const uint8Array = new Uint8Array(byteArrays); + + // 创建Blob对象 + const blob = new Blob([uint8Array], { type: dataType }); + + // 创建File对象 + return new File([blob], fileName, { type: dataType, lastModified: new Date().getTime() }); +} + diff --git a/src/utils/request/index.ts b/src/utils/request/index.ts new file mode 100644 index 0000000..5cb5bc5 --- /dev/null +++ b/src/utils/request/index.ts @@ -0,0 +1,106 @@ +import { Config, RequestHead, ResponseData, ResponseError, RequestParams } from '@/utils/request/types/index.ts' +import { message } from "ant-design-vue"; + +// 默认配置 +const createDefaultConfig = (): { + headers: RequestHead +} => { + const defaultConfig = { + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${localStorage.token}` + }, + } + return defaultConfig +} + +// 构造数据 +const genData = (data: any, dataRaw: boolean) => { + if (dataRaw) { + return data instanceof FormData || data instanceof URLSearchParams + ? data + : new URLSearchParams(data); + } + return JSON.stringify(data); +}; + +// 构造返回 +const genResult = (result: ExtraObj, status: 'success' | 'error'): ResponseData | ResponseError => { + if (result.code) { + return { + ...result, + code: result.code, + data: result.data + } + } else { + if (status == 'success') { + return { + code: 200, + data: result + } + } else { + let error + if ('detail' in result) { + error = result.detail + } else { + error = result + } + return { + code: 500, + message: error, + _raw: result + } + } + + } +} + +// 构造请求参数 +const createRuestParams = (config: Config) => { + const { method, data, dataRaw, headers, controller } = config; + const requetParams: RequestParams = { + method, + headers: headers as HeadersInit ?? createDefaultConfig().headers, + body: data instanceof URLSearchParams ? data : genData(data, dataRaw || false), + signal: controller?.signal + }; + return requetParams +} + +// 请求 +export const request = async (config: Config): Promise => { + const { url, base, params, getRaw } = config; + // 路径参数格式化 + const urlParams = params ? new URLSearchParams(params) : null + const extra = `${urlParams ? '?' + urlParams.toString() : ''}` + const requestUrl = `${base || import.meta.env.VITE_BASE_API}${url}${extra}` + const requestParams = createRuestParams(config) + + // 请求逻辑封装 + const res = await fetch(requestUrl, requestParams).then(async (res) => { + // 是否需要返回原始数据 + const result = getRaw ? res : await res.json(); + if (!res.ok) throw result + if (result?.code && result?.code !== 200) { + throw result + } + return getRaw ? result : genResult(result, 'success') + }).catch((err) => { + if (err.name == 'AbortError') { + return Promise.reject(err) + } + let error + if ('code' in err) { + error = err + } else { + error = genResult(err, 'error') + } + if (error.message) { + message.error(error.message) + } + return Promise.reject(error) + }); + + return res; +} diff --git a/src/utils/request/types/index.ts b/src/utils/request/types/index.ts new file mode 100644 index 0000000..8ad4ad7 --- /dev/null +++ b/src/utils/request/types/index.ts @@ -0,0 +1,43 @@ +export interface Config { + base?: string, // 前缀 + url: string // 请求地址 + method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' + data?: string | Record // 请求体 + dataRaw?: boolean // 请求体是否为原始数据 + params?: string | Record | URLSearchParams | string[][] // 请求参数 + headers?: HeadersInit | undefined // 请求头 + controller?: AbortController // 请求控制器 + getRaw?: boolean // 是否需要返回原始数据 +} + +// 请求头 +export interface RequestHead { + [key: string]: string +} + +// 标准响应 +export interface ResponseData { + code: number + data: T + [key: string]: any +} + +// 错误响应 +export interface ResponseError { + code: number + message?: string + _raw: T +} + +// 请求参数 +export interface RequestParams extends RequestInit { + signal?: AbortSignal + method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' + headers: HeadersInit + body?: BodyInit | null + [key: string]: any +} + + + + diff --git a/src/utils/socket/index.ts b/src/utils/socket/index.ts new file mode 100644 index 0000000..90b0dc0 --- /dev/null +++ b/src/utils/socket/index.ts @@ -0,0 +1,19 @@ +import { io } from 'socket.io-client'; +import { Socket } from 'socket.io-client/build/esm/socket' +// 设置socket链接 +export const socketInit = (enableWebsocket: boolean) => { + // const socketUrl = import.meta.env.VITE_SOCKET_URL; + + const _socket: Socket = io(undefined, { + reconnection: true, + reconnectionDelay: 1000, + reconnectionDelayMax: 5000, + randomizationFactor: 0.5, + path: '/ws/socket.io', + transports: enableWebsocket ? ['websocket'] : ['polling', 'websocket'], + auth: { token: localStorage.token } + }); + console.log('_socket', _socket) + return _socket +} + diff --git a/src/utils/text/index.ts b/src/utils/text/index.ts new file mode 100644 index 0000000..8f42f2f --- /dev/null +++ b/src/utils/text/index.ts @@ -0,0 +1,48 @@ + +// 复制文本 +/** + * 复制文本到剪贴板 + * @param text 要复制的文本 + * @returns 是否复制成功 + */ +export const copyToClipboard = async (text: string) => { + let result = false; + if (!navigator.clipboard) { + const textArea = document.createElement('textarea'); + textArea.value = text; + + // Avoid scrolling to bottom + textArea.style.top = '0'; + textArea.style.left = '0'; + textArea.style.position = 'fixed'; + + document.body.appendChild(textArea); + textArea.focus(); + textArea.select(); + + try { + const successful = document.execCommand('copy'); + const msg = successful ? 'successful' : 'unsuccessful'; + console.log('Fallback: Copying text command was ' + msg); + result = true; + } catch (err) { + console.error('Fallback: Oops, unable to copy', err); + } + + document.body.removeChild(textArea); + return result; + } + + result = await navigator.clipboard + .writeText(text) + .then(() => { + console.log('Async: Copying to clipboard was successful!'); + return true; + }) + .catch((error) => { + console.error('Async: Could not copy text: ', error); + return false; + }); + + return result; +}; diff --git a/src/utils/transition/file.ts b/src/utils/transition/file.ts new file mode 100644 index 0000000..51effca --- /dev/null +++ b/src/utils/transition/file.ts @@ -0,0 +1,21 @@ +// 文件格式转换 +export const sizeFormat = ( + number: number | string, + start: number = 0, + max: number = 999, + showUnit: boolean = true, + decimal: number = 2 +) => { + const unit = ['b', 'Kb', 'Mb', 'Gb', 'Tb'] + let limitUnitIndex = Math.min(max, unit.length - 1) + let unitIndex = start + let useNumber = Number(number) + console.log('useNumber', useNumber) + while (useNumber >= 1024 && unitIndex < limitUnitIndex) { + useNumber /= 1024 + unitIndex++ + } + console.log('useNumber', useNumber) + + return `${Math.ceil(useNumber).toFixed(decimal)}${showUnit ? unit[unitIndex] : ''}` +} diff --git a/src/utils/util.ts b/src/utils/util.ts new file mode 100644 index 0000000..19842cc --- /dev/null +++ b/src/utils/util.ts @@ -0,0 +1,64 @@ + +export function uuid() { + let d = Date.now() + if (typeof performance !== 'undefined' && typeof performance.now === 'function') { + d += performance.now() // use high-precision timer if available + } + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + const r = (d + Math.random() * 16) % 16 | 0 + d = Math.floor(d / 16) + return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16) + }) +} + +/** + * 构造树型结构数据 + * @param {*} data 数据源 + * @param {string} id id字段 默认 'id' + * @param {string} parentId 父节点字段 默认 'parentId' + * @param {*} children 孩子节点字段 默认 'children' + * @param {string | number} rootId 根Id 默认 0 + */ +export function handleTree(data: ExtraObj[], id?: string, parentId?: string, children?: string, rootId?: number | string) { + const reNameId = id || 'id' + const reNameParentId = parentId || 'parentId' + const reNameChildren = children || 'children' + const reNameRootId = + rootId || + Math.min.apply( + Math, + data.map(item => { + return item[reNameParentId] + }) + ) || + 0 + // 对源数据深度克隆 + const cloneData = JSON.parse(JSON.stringify(data)) + // 循环所有项 + const treeData = cloneData.filter((father: ExtraObj) => { + var branchArr = cloneData.filter((child: ExtraObj) => { + // 返回每一项的子级数组 + return father[reNameId] === child[reNameParentId] + }) + if (branchArr.length > 0) { + father[reNameChildren] = branchArr + } else { + father[reNameChildren] = '' + } + // 返回第一层 + return father[reNameParentId] == reNameRootId + }) + return treeData !== '' ? treeData : data +} + + + + + + + + + + + + diff --git a/src/utils/valid.ts b/src/utils/valid.ts new file mode 100644 index 0000000..c30fc51 --- /dev/null +++ b/src/utils/valid.ts @@ -0,0 +1,23 @@ +// 网址校验 +export const httpReg = /^([hH][tT]{2}[pP]:\/\/|[hH][tT]{2}[pP][sS]:\/\/)(([A-Za-z0-9-~]+)\.)+([A-Za-z0-9-~/])+$/ + +export function validURL (url?:string) { + const reg = /^([hH][tT]{2}[pP]:\/\/|[hH][tT]{2}[pP][sS]:\/\/)(([A-Za-z0-9-~]+)\.)+([A-Za-z0-9-~/])+$/ + return reg.test(url||'') +} + +// 手机校验 +export const phoneReg = /^1[3456789]\d{9}$/ + +export const MobReg = /^((0\d{2,3})-)?(\d{7,8})$/ + +export function validPhone (phone: string) { + return phoneReg.test(phone) || MobReg.test(phone) +} + +// 邮箱校验 +export const mailReg = /^[a-zA-Z0-9]+([._\\-]?[a-zA-Z0-9]+)*@[a-zA-Z0-9]+([._\\-]?[a-zA-Z0-9]+)*\.[a-zA-Z]{2,}$/ + +export function validMail (mail: string) { + return mailReg.test(mail) +} diff --git a/src/views/dataManage/index/components/Card/ModuleCard.vue b/src/views/dataManage/index/components/Card/ModuleCard.vue new file mode 100644 index 0000000..b8d333d --- /dev/null +++ b/src/views/dataManage/index/components/Card/ModuleCard.vue @@ -0,0 +1,87 @@ + + + + + + + diff --git a/src/views/dataManage/index/components/Chat/chat.vue b/src/views/dataManage/index/components/Chat/chat.vue new file mode 100644 index 0000000..fec54d0 --- /dev/null +++ b/src/views/dataManage/index/components/Chat/chat.vue @@ -0,0 +1,88 @@ + + + + + + + diff --git a/src/views/dataManage/index/components/Modal/ExportModal.vue b/src/views/dataManage/index/components/Modal/ExportModal.vue new file mode 100644 index 0000000..8712991 --- /dev/null +++ b/src/views/dataManage/index/components/Modal/ExportModal.vue @@ -0,0 +1,209 @@ + + + + + + + diff --git a/src/views/dataManage/index/components/Modal/ImportDataLabelModa.vue b/src/views/dataManage/index/components/Modal/ImportDataLabelModa.vue new file mode 100644 index 0000000..1cb0142 --- /dev/null +++ b/src/views/dataManage/index/components/Modal/ImportDataLabelModa.vue @@ -0,0 +1,271 @@ + + + + + + + diff --git a/src/views/dataManage/index/components/Modal/ImportModal.vue b/src/views/dataManage/index/components/Modal/ImportModal.vue new file mode 100644 index 0000000..b9a3b6c --- /dev/null +++ b/src/views/dataManage/index/components/Modal/ImportModal.vue @@ -0,0 +1,310 @@ + + + + + + + diff --git a/src/views/dataManage/index/components/Module/Chat1.vue b/src/views/dataManage/index/components/Module/Chat1.vue new file mode 100644 index 0000000..2dc4ee3 --- /dev/null +++ b/src/views/dataManage/index/components/Module/Chat1.vue @@ -0,0 +1,155 @@ + + + + + + + diff --git a/src/views/dataManage/index/components/Module/Chat2.vue b/src/views/dataManage/index/components/Module/Chat2.vue new file mode 100644 index 0000000..e78886b --- /dev/null +++ b/src/views/dataManage/index/components/Module/Chat2.vue @@ -0,0 +1,49 @@ + + + + + + + diff --git a/src/views/dataManage/index/components/Module/DataExport.vue b/src/views/dataManage/index/components/Module/DataExport.vue new file mode 100644 index 0000000..0904980 --- /dev/null +++ b/src/views/dataManage/index/components/Module/DataExport.vue @@ -0,0 +1,487 @@ + + + + + + + diff --git a/src/views/dataManage/index/components/Module/DataImport.vue b/src/views/dataManage/index/components/Module/DataImport.vue new file mode 100644 index 0000000..95985ad --- /dev/null +++ b/src/views/dataManage/index/components/Module/DataImport.vue @@ -0,0 +1,608 @@ + + + + + + + diff --git a/src/views/dataManage/index/components/Module/RadarChat.vue b/src/views/dataManage/index/components/Module/RadarChat.vue new file mode 100644 index 0000000..a522820 --- /dev/null +++ b/src/views/dataManage/index/components/Module/RadarChat.vue @@ -0,0 +1,102 @@ + + + + + + + diff --git a/src/views/dataManage/index/components/Module/testDataset.vue b/src/views/dataManage/index/components/Module/testDataset.vue new file mode 100644 index 0000000..9e9db49 --- /dev/null +++ b/src/views/dataManage/index/components/Module/testDataset.vue @@ -0,0 +1,416 @@ + + + + + + + diff --git a/src/views/dataManage/index/components/Module/trainDataset.vue b/src/views/dataManage/index/components/Module/trainDataset.vue new file mode 100644 index 0000000..55ee722 --- /dev/null +++ b/src/views/dataManage/index/components/Module/trainDataset.vue @@ -0,0 +1,409 @@ + + + + + + + diff --git a/src/views/dataManage/index/components/Table/index.vue b/src/views/dataManage/index/components/Table/index.vue new file mode 100644 index 0000000..fb446d2 --- /dev/null +++ b/src/views/dataManage/index/components/Table/index.vue @@ -0,0 +1,344 @@ + + + + + + + diff --git a/src/views/dataManage/index/config/index.ts b/src/views/dataManage/index/config/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/views/dataManage/index/hooks/circleChat.ts b/src/views/dataManage/index/hooks/circleChat.ts new file mode 100644 index 0000000..cf9a8a3 --- /dev/null +++ b/src/views/dataManage/index/hooks/circleChat.ts @@ -0,0 +1,99 @@ +import { ref } from "vue"; + + + +export const useMain = () => { + var colorList = ["#73DDFF", "#73ACFF", "#FDD56A", "#FDB36A", "#FD866A", "#9E87FF", "#58D5FF"]; + + const options = ref>({ + title: { + text: `{a|总量}\n{b|0}`, + textStyle: { + rich: { + a: { + fontSize: 14, + color: "#4E5969", + lineHeight: 24, + }, + b: { + fontSize: 16, + color: "#1D2129", + lineHeight: 28, + fontWeight: "bold", + }, + }, + }, + left: "50%", // 水平居中 + top: "50%", // 垂直居中 + textAlign: "center", + textVerticalAlign: "middle", + }, + legend: { + icon: "circle", + bottom: "0", + left: "center", + itemWidth: 6, + itemGap: 20, + textStyle: { + color: "#556677", + }, + }, + tooltip: { + trigger: "item", + }, + series: [ + { + type: "pie", + center: ["50%", "50%"], + radius: ["40%", "65%"], + clockwise: true, + avoidLabelOverlap: true, + hoverOffset: 15, + // itemStyle: { + // normal: { + // color: function (params: Record) { + // return colorList[params.dataIndex]; + // }, + // }, + // }, + label: { + show: true, + position: "outer", + formatter: "{a|{b}:}\n{value|{d}}%", + distanceToLabelLine: 10, + rich: { + a: { + fontSize: 14, + color: "#4E5969", + lineHeight: 20, + padding: [0, 0, 8, 0], + // padding: [-30, 15, -20, 15], + }, + value: { + fontSize: 14, + color: "#1D2129", + lineHeight: 24, + }, + }, + }, + labelLine: { + normal: { + length: 20, + length2: 20, + lineStyle: { + width: 1, + }, + }, + }, + data: [], + }, + ], + }); + + // const options = ref>(t); + + return { + options, + colorList, + }; +}; diff --git a/src/views/dataManage/index/hooks/dataExport.ts b/src/views/dataManage/index/hooks/dataExport.ts new file mode 100644 index 0000000..8ef9aac --- /dev/null +++ b/src/views/dataManage/index/hooks/dataExport.ts @@ -0,0 +1,77 @@ +import { listApi, updateLabelApi, getExportUrlApi, importLabelByFileApi } from '@/apis/dataManage/dataExport.ts' +import { ref } from 'vue' + +export const useMain = () => { + // 列表loading + const listLoading = ref(false) + + // 获取列表 + const getList = async (params?: Record) => { + if (listLoading.value) return false + listLoading.value = true + const res = await listApi(params).catch(err => { + return err + }) + listLoading.value = false + return res + } + + + // 编辑 + const updateLabelLoading = ref(false) + + const onUpdateLabel = async (params: Record) => { + if (updateLabelLoading.value) return false + updateLabelLoading.value = true + const res = await updateLabelApi(params).catch(err => { + return err + }) + updateLabelLoading.value = false + return res + } + + // 获取导出地址 + const exportUrlLoading = ref(false) + const getExportUrl = async (params: Record) => { + if (exportUrlLoading.value) return false + exportUrlLoading.value = true + const res = await getExportUrlApi(params).catch(err => { + return err + }) + exportUrlLoading.value = false + return res + } + + // 导入标注 + const importLabelLoading = ref(false) + const importLabelByFile = async (params: Record) => { + if (importLabelLoading.value) return false + importLabelLoading.value = true + const res = await importLabelByFileApi(params).catch(err => { + return err + }) + importLabelLoading.value = false + return res + } + + + + return { + listRequest: { + request: getList, + loading: listLoading + }, + updateRequest: { + request: onUpdateLabel, + loading: updateLabelLoading + }, + exportUrlRequest: { + request: getExportUrl, + loading: exportUrlLoading + }, + importLabelRequest: { + request: importLabelByFile, + loading: importLabelLoading + } + } +} diff --git a/src/views/dataManage/index/hooks/dataImport.ts b/src/views/dataManage/index/hooks/dataImport.ts new file mode 100644 index 0000000..e8235fe --- /dev/null +++ b/src/views/dataManage/index/hooks/dataImport.ts @@ -0,0 +1,78 @@ +import { listApi, deleteApi, importApi, updateApi } from '@/apis/dataManage/dataImport.ts' +import { ref } from 'vue' + +export const useMain = () => { + // 列表loading + const listLoading = ref(false) + + // 获取列表 + const getList = async () => { + if (listLoading.value) return false + listLoading.value = true + const res = await listApi().catch(err => { + return err + }) + listLoading.value = false + return res + } + + const importLoading = ref(false) + + // 导入 + const importFile = async (data: Record) => { + if (importLoading.value) return false + importLoading.value = true + const res = await importApi(data).catch(err => { + return err + }) + importLoading.value = false + return res + } + + + const deleteLoading = ref(false) + + // 删除 + const onDelete = async (data: Record) => { + if (deleteLoading.value) return false + deleteLoading.value = true + const res = await deleteApi(data).catch(err => { + return err + }) + deleteLoading.value = false + return res + } + + + const updateLoading = ref(false) + + // 更新 + const onUpdate = async (data: Record) => { + if (updateLoading.value) return false + updateLoading.value = true + const res = await updateApi(data).catch(err => { + return err + }) + updateLoading.value = false + return res + } + + return { + listRequest: { + request: getList, + loading: listLoading + }, + importRequest: { + request: importFile, + loading: importLoading + }, + deleteRequest: { + request: onDelete, + loading: deleteLoading + }, + updateRequest: { + request: onUpdate, + loading: updateLoading + } + } +} diff --git a/src/views/dataManage/index/hooks/dataset.ts b/src/views/dataManage/index/hooks/dataset.ts new file mode 100644 index 0000000..edf4c85 --- /dev/null +++ b/src/views/dataManage/index/hooks/dataset.ts @@ -0,0 +1,87 @@ +import { datasetListApi, deleteDatasetApi, updateDatasetApi } from '@/apis/dataset/index.ts' + +import { ref } from 'vue' + + +export const useDatasetHooks = () => { + + // 获取测试集loading + const testDatasetListLoading = ref(false) + + // 获取测试集 + const getTestDatasetList = async (params?: Record) => { + if (testDatasetListLoading.value) return false + testDatasetListLoading.value = true + const res = await datasetListApi({ + ...params || {}, + type: 'test' + }).catch(err => { + return err + }) + testDatasetListLoading.value = false + return res + } + + // 获取训练集loading + const trainDatasetListLoading = ref(false) + + // 获取训练集 + const getTrainDatasetList = async (params?: Record) => { + if (trainDatasetListLoading.value) return false + trainDatasetListLoading.value = true + const res = await datasetListApi({ + ...params || {}, + type: 'train' + }).catch(err => { + return err + }) + trainDatasetListLoading.value = false + return res + } + + const deleteLoading = ref(false) + + // 删除数据集 + const deleteRequest = async (id: string) => { + if (deleteLoading.value) return false + deleteLoading.value = true + const res = await deleteDatasetApi(id).catch(err => { + return err + }) + deleteLoading.value = false + return res + } + + // 更新数据集 + const updateLoading = ref(false) + + const updateRequest = async (params: Record) => { + if (updateLoading.value) return false + updateLoading.value = true + const res = await updateDatasetApi(params).catch(err => { + return err + }) + updateLoading.value = false + return res + } + + + return { + testDatasetListRequest: { + request: getTestDatasetList, + loading: testDatasetListLoading + }, + trainDatasetListRequest: { + request: getTrainDatasetList, + loading: trainDatasetListLoading + }, + deleteRequest: { + request: deleteRequest, + loading: deleteLoading + }, + updateRequest: { + request: updateRequest, + loading: updateLoading + } + } +} diff --git a/src/views/dataManage/index/hooks/lineChat.ts b/src/views/dataManage/index/hooks/lineChat.ts new file mode 100644 index 0000000..50a3159 --- /dev/null +++ b/src/views/dataManage/index/hooks/lineChat.ts @@ -0,0 +1,240 @@ +import { ref } from 'vue' + +interface SeriesOptions { + title: string, + color?: string, + data: number[], + lineColor?: string +} + + +export const useMain = () => { + const colorList = ["#9E87FF", "#73DDFF", "#fe9a8b", "#F56948", "#9E87FF"]; + + // 构建serise配置 + const createSeriesOptions = (options: SeriesOptions) => { + const item = { + name: options.title, + type: "line", + data: options.data, + symbolSize: 1, + symbol: "circle", + smooth: true, + showSymbol: false, + lineStyle: { + width: 2, + // color: options.lineColor, + // shadowColor: "rgba(158,135,255, 0.3)", + // shadowBlur: 10, + // shadowOffsetY: 20, + }, + // itemStyle: { + // normal: { + // color: options.lineColor, + // borderColor: options.lineColor, + // }, + // }, + } + return item + } + + // tooltip格式化 + const tooltipFormat = (params: Array>) => { + // 标题部分 - 黑色 + let result = `
${params[0].axisValue}
`; + + // 每项数据 + params.forEach((item: Record, index: number) => { + const marginBottom = index < params.length - 1 ? 'margin-bottom:4px;' : ''; + result += ` +
+
+ ${item.marker} + ${item.seriesName}: +
+
+ ${item.value} +
+
+ `; + }); + return result; + } + + const options = ref>({ + backgroundColor: "rgba(255, 255, 255, 0)", + title: { + text: "", + textStyle: { + fontSize: 12, + fontWeight: 400, + }, + left: "center", + top: "5%", + }, + legend: { + icon: "circle", + bottom: "0", + left: "center", + itemWidth: 6, + itemGap: 20, + textStyle: { + color: "#556677", + }, + }, + + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'none' + }, + borderColor: 'transparent', + borderWidth: 0, + padding: [10, 15], + textStyle: { + color: '#333', + fontSize: 14, + fontWeight: 'bold' + }, + extraCssText: 'border-radius: 6px; background: rgba(255, 255, 255, 0.2); backdrop-filter: blur(10px)', + }, + grid: { + top: "15%", + }, + xAxis: [ + { + type: "category", + data: [], + axisLine: { + lineStyle: { + color: "#A9AEB8", + }, + }, + axisTick: { + show: true, + color: '#A9AEB8' + }, + axisLabel: { + // interval: 0, + textStyle: { + color: "#556677", + }, + // 默认x轴字体大小 + fontSize: 12, + // margin:文字到x轴的距离 + margin: 15, + }, + axisPointer: { + label: { + // padding: [11, 5, 7], + padding: [0, 0, 10, 0], + margin: 15, + fontSize: 12, + backgroundColor: { + type: "linear", + x: 0, + y: 0, + x2: 0, + y2: 1, + colorStops: [ + { + offset: 0, + color: "#fff", // 0% 处的颜色 + }, + { + offset: 0.86, + color: "#fff", // 0% 处的颜色 + }, + { + offset: 0.86, + color: "#33c0cd", // 0% 处的颜色 + }, + { + offset: 1, + color: "#33c0cd", // 100% 处的颜色 + }, + ], + global: false, // 缺省为 false + }, + }, + }, + boundaryGap: false, + }, + ], + yAxis: [ + { + type: "value", + axisTick: { + show: false, + }, + axisLine: { + show: false, + lineStyle: { + color: "#333", + }, + }, + axisLabel: { + textStyle: { + color: "#556677", + }, + }, + splitLine: { + show: true, + color: '#E5E6EB' + }, + }, + ], + series: [ + // { + // name: "老北京布鞋", + // type: "line", + // data: [150, 120, 170, 140, 500, 160, 110], + // symbolSize: 1, + // symbol: "circle", + // smooth: true, + // showSymbol: false, + // lineStyle: { + // width: 5, + // color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [ + // { + // offset: 0, + // color: "#fe9a", + // }, + // { + // offset: 1, + // color: "#fe9a8b", + // }, + // ]), + // shadowColor: "rgba(254,154,139, 0.3)", + // shadowBlur: 10, + // shadowOffsetY: 20, + // }, + // itemStyle: { + // normal: { + // color: colorList[2], + // borderColor: colorList[2], + // }, + // }, + // }, + ] + }) + + + return { + colorList, + tooltipFormat, + createSeriesOptions, + options + } +}; + + diff --git a/src/views/dataManage/index/hooks/mock.ts b/src/views/dataManage/index/hooks/mock.ts new file mode 100644 index 0000000..3988fda --- /dev/null +++ b/src/views/dataManage/index/hooks/mock.ts @@ -0,0 +1,25 @@ +export const useMain = () => { + const generatorList = (template: Record, number: number) => { + const list = [] + for (let i = 0; i < number; i++) { + const item = { ...template } + item.id = `a${i}` + item.name = `name ${i}` + list.push(item) + } + return list + } + + const sleep = async (time: number) => { + return new Promise((resolve) => { + setTimeout(() => { + resolve(true) + }, time) + }) + } + + return { + generatorList, + sleep + } +} diff --git a/src/views/dataManage/index/hooks/options.ts b/src/views/dataManage/index/hooks/options.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/views/dataManage/index/hooks/radarChat.ts b/src/views/dataManage/index/hooks/radarChat.ts new file mode 100644 index 0000000..a9fef5e --- /dev/null +++ b/src/views/dataManage/index/hooks/radarChat.ts @@ -0,0 +1,335 @@ +import { ref, Ref, onMounted, onUnmounted } from "vue"; +import * as echarts from "echarts"; + +/** + * 雷达图扫描区域功能实现 + * 提供30度扇形扫描区域,带动画旋转和渐变效果 + */ +export const useMain = () => { + // 主色调定义 + const primaryColor = "#509e38"; + const splitLineColor = "rgba(80, 158, 56, 0.7)"; // 主色调的70%透明度 + // 扫描动画相关变量 + let animationTimer: number | null = null; + let currentAngle = 0; + + // 动画配置参数 + const animationConfig = { + rotationSpeed: -2, // 每次旋转的角度 + interval: 16, // 旋转间隔时间(毫秒) + scanAngle: 45, // 扫描扇形角度 + isRunning: false // 动画运行状态标记 + }; + + // 自定义数据提示框格式化函数 + const tooltipFormat = (params: Array>) => { + // 标题部分 - 黑色 + let result = `
${params[0].axisValue}
`; + + // 每项数据 + params.forEach((item: Record, index: number) => { + const { angle, dis, height } = item.data?._rawData || {}; + if (!item.data?._rawData) return; + const marginBottom = index < params.length - 1 ? "margin-bottom:4px;" : ""; + result += ` +
+
+ ${item.marker} + ${item.seriesName} +
+
+ 角度: ${angle.toFixed(2)}°
+ 距离: ${(dis / 1000).toFixed(2)}km
+ 高度: ${(height / 1000).toFixed(2)}km +
+
+ `; + }); + return result; + }; + + // ECharts配置项 + const options = ref>({ + title: { + text: "", + }, + legend: { + show: false, + }, + polar: {}, + tooltip: { + trigger: "axis", + axisPointer: { + type: "cross", + crossStyle: { + color: primaryColor, + }, + angleAxis: { + type: "line", + }, + radiusAxis: { + type: "line", + }, + }, + formatter: tooltipFormat, + }, + angleAxis: { + type: "value", + min: 0, + max: 360, + interval: 20, + sliptNumber: 360, + axisTick: { + show: false, + }, + axisLabel: { + show: true, + fontSize: 12, + color: "#333", + }, + axisLine: { + show: true, + lineStyle: { + color: primaryColor, + width: 3, + }, + }, + splitLine: { + show: true, + lineStyle: { + color: splitLineColor, + width: 1, + type: "solid", + }, + }, + }, + radiusAxis: { + type: "value", + min: 0, + max: 0, + splitNumber: 3, + axisLine: { + show: false, + }, + axisTick: { + show: false, + }, + axisLabel: { + show: false, + formatter: (value: number) => value.toFixed(2), + fontSize: 12, + color: "#333", + }, + splitLine: { + show: true, + lineStyle: { + color: splitLineColor, + width: 1, + type: "solid", + }, + }, + }, + series: [ + // 主要数据点系列 + { + coordinateSystem: "polar", + name: "", + animation: false, + zlevel: 20, + symbolSize: 15, + label: { + normal: { + show: true, + formatter: "{b}", + position: "inside", + textStyle: { + color: "#fff", + fontSize: 15, + }, + }, + }, + type: "scatter", + data: [], + }, + // 扫描区域系列 - 使用饼图实现30度扇形 + { + type: "pie", + radius: ["0%", "80%"], + startAngle: 0, + zlevel: 10, + animation: false, + legend: { + show: false, + }, + tooltip: { + show: false, + }, + label: { + show: false, + }, + labelLine: { + show: false, + }, + data: [ + // 30度扫描扇形 + { + value: animationConfig.scanAngle, // 30度扇形区域 + name: "扫描区", + itemStyle: { + color: { + type: "linear", + x: 0.5 - Math.cos(animationConfig.scanAngle / 2 * Math.PI / 180) * 0.5, // animationConfig.scanAngle / 2度扇形的中线角度 + y: 0.5 - Math.sin(animationConfig.scanAngle / 2 * Math.PI / 180) * 0.5, + x2: 0.5 + Math.cos(animationConfig.scanAngle / 2 * Math.PI / 180) * 0.5, + y2: 0.5 + Math.sin(animationConfig.scanAngle / 2 * Math.PI / 180) * 0.5, + colorStops: [ + { offset: 0, color: "rgba(128, 251, 128, 1)" }, // 左侧完全不透明 + { offset: 1, color: "rgba(80, 158, 56, 0)" } // 右侧完全透明 + ], + global: false + } + } + }, + // 剩余330度透明区域 + { + value: 360 - animationConfig.scanAngle, + itemStyle: { + color: 'transparent' // 透明,只显示30度的扫描扇形 + } + } + ], + }, + ], + }); + + /** + * 创建系列数据 + * @param data 原始数据数组 + * @returns 处理后的ECharts数据格式 + */ + const createSeriesData = (data: Array>) => { + const points = data.map((item) => { + const itemStyle = { + color: { + type: "radial", + x: 0.5, + y: 0.5, + r: 0.5, + colorStops: [ + { offset: 0, color: "#80fb80" }, + { offset: 0.5, color: "rgba(80, 158, 56, 0.7)" }, + { offset: 1, color: "rgba(80, 158, 56, 0)" }, + ], + global: false, + }, + }; + const point = { + value: [item.radius.toFixed(2), item.angle.toFixed(2)], + _rawData: item, + itemStyle: itemStyle, + }; + return point; + }); + return points; + }; + + + + /** + * 启动扫描动画 + * 使30度扇形区域以指定速度旋转 + */ + const startScanAnimation = (echartsRef: Ref) => { + // 清除已有的定时器 + if (animationTimer) { + clearInterval(animationTimer); + } + + animationConfig.isRunning = true; + + // 创建新的定时器 + animationTimer = window.setInterval(() => { + if (!echartsRef.value) return; + // 更新当前角度,确保在0-360范围内 + currentAngle = (currentAngle + animationConfig.rotationSpeed) % 360; + if (echartsRef.value) { + // 计算扇形中线角度,用于渐变方向 + const gradientAngle = currentAngle + (animationConfig.scanAngle / 2); + + // 局部更新扫描区域的起始角度和渐变方向 + echartsRef.value.setOption({ + series: [ + {}, // 第一个系列保持不变 + { + startAngle: currentAngle, + data: [ + { + value: animationConfig.scanAngle, + itemStyle: { + color: { + type: "linear", + x: 0.5 - Math.cos(gradientAngle * Math.PI / 180) * 0.5, + y: 0.5 - Math.sin(gradientAngle * Math.PI / 180) * 0.5, + x2: 0.5 + Math.cos(gradientAngle * Math.PI / 180) * 0.5, + y2: 0.5 + Math.sin(gradientAngle * Math.PI / 180) * 0.5, + colorStops: [ + { offset: 0, color: "rgba(128, 251, 128, 1)" }, + { offset: 1, color: "rgba(80, 158, 56, 0)" } + ], + global: false + } + } + }, + { value: 360 - animationConfig.scanAngle, itemStyle: { color: 'transparent' } } + ] + } + ] + }, false); // 第二个参数设为 false,禁止合并动画 + } + }, animationConfig.interval); + }; + + /** + * 停止扫描动画 + */ + const stopScanAnimation = () => { + if (animationTimer) { + clearInterval(animationTimer); + animationTimer = null; + animationConfig.isRunning = false; + } + }; + + /** + * 设置动画速度 + * @param speed 新的旋转速度 + */ + // const setAnimationSpeed = (speed: number) => { + // animationConfig.rotationSpeed = speed; + // // 如果动画正在运行,重新启动以应用新速度 + // if (animationConfig.isRunning) { + // startScanAnimation(); + // } + // }; + + + + + return { + options, + createSeriesData, + tooltipFormat, + startScanAnimation, + stopScanAnimation, + // setAnimationSpeed + }; +}; diff --git a/src/views/dataManage/index/hooks/statistics.ts b/src/views/dataManage/index/hooks/statistics.ts new file mode 100644 index 0000000..9fe83f8 --- /dev/null +++ b/src/views/dataManage/index/hooks/statistics.ts @@ -0,0 +1,52 @@ + +import { statisticsApi, lineStatisticsApi } from '@/apis/dataManage/statistics.ts' +import { ref } from 'vue' + +export const useMain = () => { + // 列表loading + const loading = ref(false) + + // 获取统计 + const getStatistics = async () => { + if (loading.value) return false + loading.value = true + const res = await statisticsApi().catch(err => { + return err + }) + loading.value = false + return res + } + + const lineLoading = ref(false) + let lineController: undefined | AbortController = undefined + const getLineStatistics = async (batchId: string) => { + if (lineLoading.value) return false + lineController = new AbortController() + lineLoading.value = true + const res = await lineStatisticsApi(batchId, lineController).catch(err => { + return err + }).finally(() => { + lineController = undefined + }) + lineLoading.value = false + return res + } + + const onLineStatisticsInterrupt = () => { + if (lineController) lineController.abort() + } + + + + return { + statisticsRequest: { + request: getStatistics, + loading: loading + }, + lineStatisticsRequest: { + request: getLineStatistics, + loading: lineLoading, + interrupt: onLineStatisticsInterrupt + } + } +} diff --git a/src/views/dataManage/index/index.vue b/src/views/dataManage/index/index.vue new file mode 100644 index 0000000..0983f81 --- /dev/null +++ b/src/views/dataManage/index/index.vue @@ -0,0 +1,161 @@ + + + + + + + diff --git a/src/views/dataManage/index/services/dataExport.ts b/src/views/dataManage/index/services/dataExport.ts new file mode 100644 index 0000000..762ff28 --- /dev/null +++ b/src/views/dataManage/index/services/dataExport.ts @@ -0,0 +1,91 @@ +import { useMain as useDataExportMain } from '@/views/dataManage/index/hooks/dataExport.ts' +import TemplateFile from '@/assets/files/dataExport/导入标注模板文件.csv?url' +import { downloadFile } from '@/utils/download/index.ts' +import _ from "lodash"; +import { ref } from 'vue' + +export const useMain = () => { + + const { listRequest, updateRequest, exportUrlRequest, importLabelRequest } = useDataExportMain() + + const exportData = ref>([]) + const exportListParams = ref({ + date_str: null, + label_info: null, + track_id: null + }) + + const createListParams = () => { + const raw = exportListParams.value + const params = {} as Record + if (raw.date_str) params.date_str = raw.date_str + if (raw.label_info) params.label_info = raw.label_info + if (raw.track_id) params.label_info = raw.track_id + // if (raw) + return params + } + + // 获取导出数据 + const getExportData = async (params?: Record) => { + const customParams = params ?? createListParams() + const res = await listRequest.request(customParams) + if (res?.code == 200) { + exportData.value = res.data + console.log('exportData', exportData) + } + return res + } + + const onExportDataSearch = _.debounce((params?: Record) => { + getExportData(params) + }, 1000) + + const onUpdateLabel = async (params: Record) => { + const res = await updateRequest.request(params) + // if (res?.code == 200) { + // exportData.value = res.data + // console.log('exportData', exportData) + // } + return res + } + + // 获取导出文件地址 + const getExportFileUrl = async (params: Record) => { + const res = await exportUrlRequest.request(params) + return res + } + + // 下载导入标注数据模板 + const onDownloadLabelTemplate = () => { + downloadFile(TemplateFile, '导入标注模板文件.csv') + } + + // 导入标注数据 + const onImportLabel = async (params: Record) => { + const res = await importLabelRequest.request(params) + return res + } + + const state = { + exportListParams: exportListParams, + exportData: exportData + } + + return { + state, + api: { + getExportData, + onExportDataSearch, + onUpdateLabel, + onDownloadLabelTemplate, + getExportFileUrl, + onImportLabel + }, + requestMap: { + listRequest, + updateRequest, + exportUrlRequest, + importLabelRequest + } + } +} diff --git a/src/views/dataManage/index/services/dataImport.ts b/src/views/dataManage/index/services/dataImport.ts new file mode 100644 index 0000000..bdc2e63 --- /dev/null +++ b/src/views/dataManage/index/services/dataImport.ts @@ -0,0 +1,59 @@ +import { useMain as useDataImportMain } from '@/views/dataManage/index/hooks/dataImport.ts' +import { ref } from 'vue' + +export const useMain = () => { + + const { listRequest, importRequest, deleteRequest, updateRequest } = useDataImportMain() + + const importData = ref([]) + + // 获取导入数据 + const getImportData = async () => { + const res = await listRequest.request() + console.log('res', res) + if (res?.code == 200) { + importData.value = res.data + } + return res + } + + // 导入数据 + const onImportData = async (data: Record) => { + const res = await importRequest.request(data) + console.log('res', res) + return res + } + + // 删除 + const onDelete = async (data: Record) => { + const res = await deleteRequest.request(data) + console.log('res', res) + return res + } + + // 更新 + const onUpdate = async (data: Record) => { + const res = await updateRequest.request(data) + console.log('res', res) + return res + } + + const state = { + importData: importData + } + + return { + state, + api: { + getImportData, + onDelete, + onImportData, + onUpdate + }, + requestMap: { + listRequest, + importRequest, + deleteRequest + } + } +} diff --git a/src/views/dataManage/index/services/dataset.ts b/src/views/dataManage/index/services/dataset.ts new file mode 100644 index 0000000..961cc96 --- /dev/null +++ b/src/views/dataManage/index/services/dataset.ts @@ -0,0 +1,60 @@ +import { useDatasetHooks } from '@/views/dataManage/index/hooks/dataset.ts' +import { ref } from 'vue' + +export const useDatasetServer = () => { + const { testDatasetListRequest, trainDatasetListRequest, deleteRequest, updateRequest } = useDatasetHooks() + + const testDataset = ref([]) + const getTestDataset = async () => { + const res = await testDatasetListRequest.request() + if (res?.code == 200) { + testDataset.value = res.data?.datasets + } + return res + } + + const trainDataset = ref([]) + const getTrainDataset = async () => { + const res = await trainDatasetListRequest.request() + if (res?.code == 200) { + trainDataset.value = res.data?.datasets + } + return res + } + + const deleteDataset = async (id: string) => { + const res = await deleteRequest.request(id) + return res + } + + const updateDataset = async (params: Record) => { + const res = await updateRequest.request(params) + return res + } + + + const state = { + testDataset, + trainDataset, + } + + const api = { + getTestDataset, + getTrainDataset, + deleteDataset, + updateDataset + } + + const requestMap = { + testDatasetListRequest, + trainDatasetListRequest, + deleteRequest, + updateRequest + } + + return { + state, + api, + requestMap, + } +} diff --git a/src/views/dataManage/index/services/index.ts b/src/views/dataManage/index/services/index.ts new file mode 100644 index 0000000..bad2c17 --- /dev/null +++ b/src/views/dataManage/index/services/index.ts @@ -0,0 +1,22 @@ +import { useMain as useDataServicesMain } from '@/views/dataManage/index/services/dataImport.ts' +import { useMain as useStatisticsServicesMain } from '@/views/dataManage/index/services/statistics.ts' +import { useMain as useDataExportServicesMain } from '@/views/dataManage/index/services/dataExport.ts' +import { useDatasetServer } from '@/views/dataManage/index/services/dataset.ts' + +const dataImportServices = useDataServicesMain() +const statisticsServices= useStatisticsServicesMain() +const dataExportServices = useDataExportServicesMain() +const datasetServers = useDatasetServer() + +export const useMain = () => { + const dataManageServices = { + common: {}, // 公共状态模块 + dataImport: dataImportServices, // 数据导入模块 + dataExport: dataExportServices, // 数据导出模块 + statistics: statisticsServices, // 统计数据模块 + dataset: datasetServers, // 数据集模块 + } + return { + dataManageServices, + } +} diff --git a/src/views/dataManage/index/services/statistics.ts b/src/views/dataManage/index/services/statistics.ts new file mode 100644 index 0000000..a00c2e8 --- /dev/null +++ b/src/views/dataManage/index/services/statistics.ts @@ -0,0 +1,210 @@ +import { useMain as useStatisticsMain } from "@/views/dataManage/index/hooks/statistics.ts"; +import { useMain as useCircleChatMain } from "@/views/dataManage/index/hooks/circleChat.ts"; +import { useMain as useLineChatMain } from "@/views/dataManage/index/hooks/lineChat.ts"; +import { useMain as useDataExportMain } from "@/views/dataManage/index/hooks/dataExport.ts"; +import { useMain as useRadarChatMain } from "@/views/dataManage/index/hooks/radarChat.ts"; + +import { ref } from "vue"; + +export const useMain = () => { + const { options: circleChatOptions } = useCircleChatMain(); + const { + listRequest: exportDataListRequest, + updateRequest, + exportUrlRequest, + } = useDataExportMain(); + + const { + options: lineChatOptions, + createSeriesOptions: createLineSeriesOptions, + tooltipFormat, + } = useLineChatMain(); + + const { + options: radarChatOptions, + createSeriesData: createRadarSeriesData, + tooltipFormat: radarTooltipFormat, + startScanAnimation: radarStartScanAnimation, + stopScanAnimation: radarStopScanAnimation, + } = useRadarChatMain(); + + const { statisticsRequest, lineStatisticsRequest } = useStatisticsMain(); + const { lineStatisticsRequest: radarStatisticsRequest } = useStatisticsMain(); + + const statisticsData = ref({}); + + // 获取统计数据数据 + const getStatisticsData = async () => { + const res = await statisticsRequest.request(); + console.log("getStatisticsData", res); + if (res?.code == 200) { + statisticsData.value = res.data; + } + return res; + }; + + // 更新环型图表配置 + const updateCircleChatOptions = (chatData: Record) => { + const { batch_count, hrrp_by_model, model_count } = chatData; + circleChatOptions.value.title.text = `{a|批次}\n{b|${batch_count}}`; + const showData: Record[] = []; + for (const plane in hrrp_by_model) { + showData.push({ + name: `${plane}`, + value: hrrp_by_model[plane], + }); + } + circleChatOptions.value = { + ...circleChatOptions.value, + title: { + ...circleChatOptions.value.title, + text: `{a|批次}\n{b|${batch_count}}`, + }, + series: [ + { + ...circleChatOptions.value.series[0], + data: showData, + }, + ], + }; + }; + + // 根据batchId获取折线图、雷达图统计数据 + const lineStatisticsData = ref({}); + const getLineStaticsData = async (batchId: string) => { + const res = await lineStatisticsRequest.request(batchId); + console.log("getLineStaticsData", res); + if (res.code == 200) { + lineStatisticsData.value = res.data; + } + }; + + // 折线图 + const updateLineChatOptions = (chatData: Record) => { + if (!chatData || !chatData.hrrp) return; + const { hrrp } = chatData; + console.log("hrrp", hrrp); + // 获取hrrp数组的子数组里最长的长度 + const maxLen = Math.max(...hrrp.map((item: number[] | string[]) => item.length)); + // 获取最值 + const maxValue = Math.max(...hrrp.flat(Infinity)); + const xPoint = 10; + const xSide = Math.ceil(maxLen / xPoint); + const xAxis = []; + for (let i = 0; i < maxLen + 1; i++) { + xAxis.push(i); + } + lineChatOptions.value.xAxis[0].data = xAxis; + const series = hrrp.map((item: number[]) => { + const seriesItem = createLineSeriesOptions({ + title: "", + data: [...item], + }); + return seriesItem; + }); + lineChatOptions.value.yAxis[0].name = "幅度"; + lineChatOptions.value.series = series; + lineChatOptions.value = { + ...lineChatOptions.value, + }; + console.log("lineChatOptions", lineChatOptions.value); + }; + + const onUpdateLineChat = async (batchId: string) => { + await getLineStaticsData(batchId); + updateLineChatOptions(lineStatisticsData.value); + }; + + // 根据batchId获取雷达图统计数据 + const radarStatisticsData = ref({}); + const getRadatStaticsData = async (batchId: string) => { + const res = await radarStatisticsRequest.request(batchId); + if (res.code == 200) { + radarStatisticsData.value = res.data; + } + }; + + // 雷达图 + const updateRadarChatOptions = (chatData: Record) => { + if (!chatData || !chatData.dis) return; + const { dis, height, azimuth } = chatData; + console.log("chatData", chatData); + const pointKey = "dis"; + const pointData = chatData[pointKey] || []; + const maxPoint = Math.max(...pointData); + radarChatOptions.value.radiusAxis.max = Math.ceil(maxPoint / 10) * 10; + const showData = []; + for (let i = 0; i < azimuth.length; i++) { + showData.push({ + radius: pointData[i], + angle: azimuth[i] * (180 / Math.PI), + azimuth: azimuth[i], + dis: dis[i], + height: height[i], + // value: [pointData[i], azimuth[i] * (180/Math.PI)], + // name: "", + }); + } + const seriesData = createRadarSeriesData(showData); + console.log("seriesData", seriesData); + radarChatOptions.value.series[0].data = seriesData; + radarChatOptions.value = { + ...radarChatOptions.value, + }; + }; + + const onUpdateRadarChat = async (batchId: string) => { + await getRadatStaticsData(batchId); + updateRadarChatOptions(radarStatisticsData.value); + }; + + // 更新图表 + const onUpdateChat = async () => { + await getStatisticsData(); + updateCircleChatOptions(statisticsData.value); + }; + + const exportData = ref>([]); + const exportListParams = ref({}); + + // 获取导出数据 + const getExportData = async () => { + const res = await exportDataListRequest.request(); + if (res?.code == 200) { + exportData.value = res.data; + } + return res; + }; + + const state = { + statisticsData: statisticsData, + circleChatOptions, + lineStatisticsData: lineStatisticsData, + lineChatOptions, + radarStatisticsData: radarStatisticsData, + radarChatOptions, + exportData: exportData, + }; + + const api = { + getStatisticsData, + updateCircleChatOptions, + onUpdateChat, + onUpdateLineChat, + onUpdateRadarChat, + radarStartScanAnimation, + radarStopScanAnimation, + getExportData, + }; + + return { + state, + api, + requestMap: { + statisticsRequest, + lineStatisticsRequest, + radarStatisticsRequest, + exportDataListRequest, + }, + }; +}; diff --git a/src/views/exception/404.vue b/src/views/exception/404.vue new file mode 100644 index 0000000..23c0bd8 --- /dev/null +++ b/src/views/exception/404.vue @@ -0,0 +1,30 @@ + + + + + diff --git a/src/views/index/index.vue b/src/views/index/index.vue new file mode 100644 index 0000000..37e4804 --- /dev/null +++ b/src/views/index/index.vue @@ -0,0 +1,13 @@ + + + + + diff --git a/src/views/login/hooks/index.ts b/src/views/login/hooks/index.ts new file mode 100644 index 0000000..c021b89 --- /dev/null +++ b/src/views/login/hooks/index.ts @@ -0,0 +1,54 @@ +import { ref } from 'vue' + + +export const useMain = () => { + // 表单 + const formList: Record[] = [ + { + label: '邮箱', + key: 'email', + icon: 'email', + type: 'input', + placeholder: '请输入邮箱', + rules: [ + { required: true, message: '请输入邮箱', trigger: 'blur' }, + ] + }, + { + label: '密码', + key: 'password', + icon: 'lock', + type: 'password', + placeholder: '请输入密码', + rules: [ + { required: true, message: '请输入密码', trigger: 'blur' }, + ] + } + ] + + // 数据 + const form = ref>({ + email: '', + password: '', + }) + + // 构造登录参数 + const getLoginParams = () => { + return form.value + } + + // 登陆 + const onLogin = () => { + + } + + + + return { + formList, + form, + getLoginParams, + onLogin + } + +} diff --git a/src/views/login/index.vue b/src/views/login/index.vue new file mode 100644 index 0000000..de3f223 --- /dev/null +++ b/src/views/login/index.vue @@ -0,0 +1,156 @@ + + + + + diff --git a/src/views/modelReasoning/index/components/Module/BarChat.vue b/src/views/modelReasoning/index/components/Module/BarChat.vue new file mode 100644 index 0000000..68c038c --- /dev/null +++ b/src/views/modelReasoning/index/components/Module/BarChat.vue @@ -0,0 +1,45 @@ + + + + + + + diff --git a/src/views/modelReasoning/index/components/Module/Compare.vue b/src/views/modelReasoning/index/components/Module/Compare.vue new file mode 100644 index 0000000..81b7986 --- /dev/null +++ b/src/views/modelReasoning/index/components/Module/Compare.vue @@ -0,0 +1,486 @@ + + + + + + + diff --git a/src/views/modelReasoning/index/components/Module/Reasoning.vue b/src/views/modelReasoning/index/components/Module/Reasoning.vue new file mode 100644 index 0000000..0fc7f33 --- /dev/null +++ b/src/views/modelReasoning/index/components/Module/Reasoning.vue @@ -0,0 +1,932 @@ + + + + +