第一次提交

This commit is contained in:
yans 2025-11-11 09:54:39 +08:00
parent 21013490dc
commit 958e11caaf
186 changed files with 17140 additions and 0 deletions

9
.editorconfig Normal file
View File

@ -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

13
.env Normal file
View File

@ -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';

13
.env.development Normal file
View File

@ -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';

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
* text=auto eol=lf

30
.gitignore vendored Normal file
View File

@ -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

9
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,9 @@
{
"recommendations": [
"Vue.volar",
"vitest.explorer",
"dbaeumer.vscode-eslint",
"EditorConfig.EditorConfig",
"esbenp.prettier-vscode"
]
}

13
index.html Normal file
View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

60
package.json Normal file
View File

@ -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"
}
}

View File

@ -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;
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

45
src/App.vue Normal file
View File

@ -0,0 +1,45 @@
<script setup lang="ts">
import { ref } from "vue";
import defaultJSON from "@/styles/theme/default.json";
import zhCN from "ant-design-vue/es/locale/zh_CN";
import { useRoute } from "vue-router";
const data = {
theme: defaultJSON,
};
const route = useRoute();
const reload = ref<boolean>(false);
</script>
<template>
<a-config-provider :theme="data.theme" :locale="zhCN">
<a-app>
<Suspense>
<RouterView :reload="reload" />
<template #fallback>
<div class="loading-box">
<a-spin />
</div>
</template>
</Suspense>
</a-app>
</a-config-provider>
</template>
<style lang="scss" scoped>
.ant-app {
width: 100%;
height: 100%;
overflow: hidden;
}
.loading-box {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
</style>

10
src/apis/auth/index.ts Normal file
View File

@ -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',
// })
// };

View File

@ -0,0 +1,43 @@
// 数据导出相关
import { request } from '@/utils/request/index.ts';
// 列表接口
export const listApi = async (data?: Record<string, any>) => {
return await request({
url: `/data/list_info`,
method: 'GET',
params: data
})
};
// 更新标注
export const updateLabelApi = async (data: Record<string, any>) => {
return await request({
url: `/data/save_label`,
method: 'POST',
data: data
})
}
// 获取导出数据文件在线地址
export const getExportUrlApi = async (data: Record<string, any>) => {
return await request({
url: `/data/export_data`,
method: 'GET',
params: data,
getRaw: true
})
}
// 导入标注
export const importLabelByFileApi = async (data: Record<string, any>) => {
return await request({
url: `/data/batch_label`,
method: 'POST',
headers: {
Accept: 'application/json',
authorization: `Bearer ${localStorage.token}`
},
data,
dataRaw: true,
})
}

View File

@ -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<string, any>) => {
return await request({
url: `/data/delete/${data.fileName}`,
method: 'DELETE',
})
}
export const importApi = async (data: Record<string, any>) => {
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<string, any>) => {
return await request({
url: `/data/data`,
method: 'POST',
data: data
})
}

View File

@ -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
})
}

52
src/apis/dataset/index.ts Normal file
View File

@ -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<string, any>) => {
return request({
url: '/data/exported_data',
method: 'GET',
params: params
})
}
// 更新数据集
export const updateDatasetApi = async (params: Record<string, any>) => {
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',
})
}

45
src/apis/model/index.ts Normal file
View File

@ -0,0 +1,45 @@
import { request } from '@/utils/request/index.ts';
// 新增
export const addApi = async (data: Record<string, any>, 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<string, any>) => {
return await request({
url: `/train/model/list`,
method: 'GET',
params: params
})
}

View File

0
src/apis/model/train.ts Normal file
View File

25
src/apis/system/config.ts Normal file
View File

@ -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',
})
}

34
src/apis/system/index.ts Normal file
View File

@ -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',
})
}

1
src/apis/system/model.ts Normal file
View File

@ -0,0 +1 @@
// 模型相关

67
src/apis/train/index.ts Normal file
View File

@ -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<string, any>) => {
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'
})
}

View File

@ -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<string, any>, 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<string, any>) => {
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,
})
}

View File

@ -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<string, any>) => {
return await request({
url: `/performance/datasets/${data.datasetName}/models/${data.modelName}/${data.checkpoint_dir}`,
method: 'GET'
})
}

View File

@ -0,0 +1,2 @@
batch_id,label
20250612_960_0,A330
1 batch_id label
2 20250612_960_0 A330

10
src/assets/icons/back.svg Normal file
View File

@ -0,0 +1,10 @@
<svg width="12" height="11" viewBox="0 0 12 11" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="_&#233;&#141;&#165;&#230;&#131;&#167;&#231;&#156;&#176;_1" clip-path="url(#clip0_75_1658)">
<path id="Vector" d="M7.71372 1.83341H3.42832V0L0 2.75027L3.42832 5.50039V3.6662H7.71387C9.17109 3.6662 10.2863 4.85719 10.2863 6.41647C10.2863 7.97575 9.17183 9.16659 7.71387 9.16659H3.42832V11H7.71387C10.0283 11 12 8.98284 12 6.41647C12 3.8501 10.1145 1.83341 7.71372 1.83341Z" fill="currentColor"/>
</g>
<defs>
<clipPath id="clip0_75_1658">
<rect width="12" height="11" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 610 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="18" height="18" viewBox="0 0 18 18"><g><g><g><path d="M1.875,15.375L1.875,2.625Q1.875,2.5511314,1.889411,2.478682Q1.903822,2.406233,1.93209,2.337987Q1.960359,2.269742,2.001398,2.208322Q2.042437,2.146903,2.09467,2.09467Q2.146903,2.042437,2.208322,2.001398Q2.269742,1.960359,2.337987,1.93209Q2.406233,1.903822,2.478682,1.889411Q2.5511314,1.875,2.625,1.875L15.375,1.875Q15.4489,1.875,15.5213,1.889411Q15.5938,1.903822,15.662,1.93209Q15.7303,1.960359,15.7917,2.001398Q15.8531,2.042437,15.9053,2.09467Q15.9576,2.146903,15.9986,2.208322Q16.0396,2.269742,16.0679,2.337987Q16.0962,2.406233,16.110599999999998,2.478682Q16.125,2.5511314,16.125,2.625L16.125,15.375Q16.125,15.4489,16.110599999999998,15.5213Q16.0962,15.5938,16.0679,15.662Q16.0396,15.7303,15.9986,15.7917Q15.9576,15.8531,15.9053,15.9053Q15.8531,15.9576,15.7917,15.9986Q15.7303,16.0396,15.662,16.0679Q15.5938,16.0962,15.5213,16.110599999999998Q15.4489,16.125,15.375,16.125L2.625,16.125Q2.5511314,16.125,2.478682,16.110599999999998Q2.406233,16.0962,2.337987,16.0679Q2.269742,16.0396,2.208322,15.9986Q2.146903,15.9576,2.09467,15.9053Q2.042437,15.8531,2.001398,15.7917Q1.960359,15.7303,1.93209,15.662Q1.903822,15.5938,1.889411,15.5213Q1.875,15.4489,1.875,15.375ZM3.375,3.375L3.375,14.625L14.625,14.625L14.625,3.375L3.375,3.375Z" fill="currentColor" fill-opacity="1"/></g></g><g transform="matrix(0,1,-1,0,15.75,-5.25)"><path d="M10.5,4.5C10.085786,4.5,9.75,4.835786,9.75,5.25C9.75,5.664214,10.085786,6,10.5,6C10.5,6,10.5,4.5,10.5,4.5C10.5,4.5,10.5,4.5,10.5,4.5ZM12.375,6C12.78921,6,13.125,5.664214,13.125,5.25C13.125,4.835786,12.78921,4.5,12.375,4.5C12.375,4.5,12.375,6,12.375,6C12.375,6,12.375,6,12.375,6ZM10.5,6C10.5,6,12.375,6,12.375,6C12.375,6,12.375,4.5,12.375,4.5C12.375,4.5,10.5,4.5,10.5,4.5C10.5,4.5,10.5,6,10.5,6C10.5,6,10.5,6,10.5,6Z" fill="currentColor" fill-opacity="1"/></g><g transform="matrix(0,1,-1,0,13.125,-2.625)"><path d="M7.875,4.5C7.460786,4.5,7.125,4.835786,7.125,5.25C7.125,5.664214,7.460786,6,7.875,6C7.875,6,7.875,4.5,7.875,4.5C7.875,4.5,7.875,4.5,7.875,4.5ZM13.125,6C13.53921,6,13.875,5.664214,13.875,5.25C13.875,4.835786,13.53921,4.5,13.125,4.5C13.125,4.5,13.125,6,13.125,6C13.125,6,13.125,6,13.125,6ZM7.875,6C7.875,6,13.125,6,13.125,6C13.125,6,13.125,4.5,13.125,4.5C13.125,4.5,7.875,4.5,7.875,4.5C7.875,4.5,7.875,6,7.875,6C7.875,6,7.875,6,7.875,6Z" fill="currentColor" fill-opacity="1"/></g><g transform="matrix(0,1,-1,0,10.5,0)"><path d="M5.25,4.5C4.835786,4.5,4.5,4.835786,4.5,5.25C4.5,5.664214,4.835786,6,5.25,6C5.25,6,5.25,4.5,5.25,4.5C5.25,4.5,5.25,4.5,5.25,4.5ZM9,6C9.41421,6,9.75,5.664214,9.75,5.25C9.75,4.835786,9.41421,4.5,9,4.5C9,4.5,9,6,9,6C9,6,9,6,9,6ZM5.25,6C5.25,6,9,6,9,6C9,6,9,4.5,9,4.5C9,4.5,5.25,4.5,5.25,4.5C5.25,4.5,5.25,6,5.25,6C5.25,6,5.25,6,5.25,6Z" fill="currentColor" fill-opacity="1"/></g></g></svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.771 5.17211C12.0314 4.91176 12.0314 4.48965 11.771 4.2293C11.5107 3.96895 11.0886 3.96895 10.8282 4.2293L7.99984 7.0577L5.17136 4.22922C4.91101 3.96887 4.4889 3.96887 4.22855 4.22922C3.9682 4.48956 3.9682 4.91167 4.22855 5.17202L7.05703 8.00051L4.22858 10.829C3.96823 11.0893 3.96823 11.5114 4.22858 11.7718C4.48893 12.0321 4.91104 12.0321 5.17139 11.7718L7.99984 8.94332L10.8282 11.7717C11.0886 12.032 11.5107 12.032 11.771 11.7717C12.0314 11.5113 12.0314 11.0892 11.771 10.8289L8.94265 8.00051L11.771 5.17211Z" fill="#151515"/>
</svg>

After

Width:  |  Height:  |  Size: 686 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="16" height="16" viewBox="0 0 16 16"><g><g></g><g><path d="M13.0895315625,6.013678125Q13.1801615625,5.910921125,13.2286115625,5.782757125Q13.2770515625,5.654592125,13.2770515625,5.517578125Q13.2770515625,5.443709625,13.2626415625,5.371260125Q13.2482315625,5.298811125,13.2199615625,5.230565125Q13.1916915625,5.162320125,13.1506515625,5.100900125Q13.1096115625,5.039481125,13.0573815625,4.987248125Q13.0051515625,4.935015125,12.9437315625,4.893976125Q12.8823115625,4.852937125,12.8140615625,4.824668125Q12.7458215625,4.796400125,12.6733715625,4.781989125Q12.6009215625,4.767578125,12.5270515625,4.767578125Q12.3656215625,4.767578125,12.2184915625,4.833994125Q12.0713515625,4.900409125,11.9645715625,5.021477125L11.9641615625,5.021939125L7.0038015625,10.646018125000001L4.1427655625,7.389058125L4.1425725625,7.3888381249999995Q4.0357755625,7.267258125,3.8883495625,7.200538125Q3.7409235625,7.1338181249999995,3.5791015625,7.1338181249999995Q3.5052330625,7.1338181249999995,3.4327835625,7.148228125Q3.3603345625,7.162638125,3.2920885625,7.190908125Q3.2238435625,7.2191681249999995,3.1624235625,7.260208125Q3.1010045625,7.301248125,3.0487715625,7.353488125Q2.9965385625,7.405718125,2.9554995625,7.467138125Q2.9144605625,7.528558125,2.8861915625,7.596798124999999Q2.8579235625,7.665048125,2.8435125625,7.737498125Q2.8291015625,7.809948125,2.8291015625,7.8838181249999995Q2.8291015625,8.020438125,2.8772815625,8.148288125Q2.9254605625,8.276138125,3.0156305625,8.378788125L6.4391915625,12.276118125Q6.4707315625,12.312018125,6.5065615625,12.343628125Q6.618451562500001,12.442308125,6.7595815625,12.490658125Q6.9007115625,12.539008124999999,7.0496015624999995,12.529678125Q7.1984915625,12.520338125,7.3324815625,12.454738125Q7.4664615625,12.389128125,7.5651415625,12.277248125L13.0895315625,6.013679125L13.0895315625,6.013678125Z" fill-rule="evenodd" fill="currentColor" fill-opacity="1"/></g></g></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.6655 3.31655C14.7611 3.31655 14.8567 3.36691 14.9044 3.41727C14.9522 3.46763 15 3.56835 15 3.66906V4.52518C15 4.6259 14.9522 4.72662 14.9044 4.77698C14.8567 4.82734 14.7611 4.8777 14.6655 4.8777H13.1843V13.4388C13.1843 14.295 12.4676 14.9496 11.6553 15H11.5597H4.48805C3.62799 15 2.91126 14.3957 2.86348 13.5396V13.4388V4.8777H1.38225C1.14334 4.8777 1 4.72662 1 4.52518V3.71942C1 3.61871 1.04778 3.51799 1.09556 3.46763C1.14334 3.41727 1.23891 3.36691 1.33447 3.36691H14.6655V3.31655ZM11.7031 4.8777H4.34471V13.3885C4.39249 13.3885 4.39249 13.4388 4.44027 13.4388H4.48805H11.6075C11.6553 13.4388 11.7031 13.4388 11.7509 13.3885V4.8777H11.7031ZM6.92491 6.84173C7.11604 6.84173 7.30717 6.99281 7.30717 7.2446V11.1223C7.30717 11.223 7.25939 11.3237 7.2116 11.3741C7.16382 11.4245 7.06826 11.4748 6.9727 11.4748H6.25597C6.16041 11.4748 6.06485 11.4245 6.01707 11.3741C5.96928 11.3237 5.9215 11.223 5.9215 11.1223V7.2446C5.9215 7.04317 6.06485 6.84173 6.30375 6.84173H6.92491ZM9.88737 6.84173C9.98294 6.84173 10.0785 6.89209 10.1263 6.94245C10.1741 6.99281 10.2218 7.09353 10.2218 7.19424V11.0719C10.2218 11.1727 10.1741 11.2734 10.1263 11.3237C10.0785 11.3741 9.98294 11.4245 9.88737 11.4245H9.12287C9.0273 11.4245 8.93174 11.3741 8.88396 11.3237C8.83618 11.2734 8.7884 11.1727 8.7884 11.0719V7.19424C8.7884 7.09353 8.83618 6.99281 8.88396 6.94245C8.93174 6.89209 9.0273 6.84173 9.12287 6.84173H9.88737ZM9.88737 1C9.98294 1 10.0785 1.05036 10.1263 1.10072C10.1741 1.15108 10.2218 1.2518 10.2218 1.35252V2.10791C10.2218 2.20863 10.1741 2.30935 10.1263 2.35971C10.0785 2.51079 9.98294 2.56115 9.88737 2.56115H6.20819C6.01707 2.56115 5.82594 2.41007 5.82594 2.15827V1.40288C5.82594 1.20144 5.96928 1 6.20819 1H9.88737Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.32955 2C7.50622 2.00003 7.67564 2.07028 7.80055 2.1953C7.92547 2.32032 7.99564 2.48987 7.99564 2.66667C7.99564 2.84346 7.92547 3.01301 7.80055 3.13804C7.67564 3.26306 7.50622 3.33331 7.32955 3.33333H3.33239V12.6667H12.6591V8.66667L12.6638 8.58867C12.6838 8.41994 12.7675 8.26524 12.8977 8.15618C13.0279 8.04712 13.1948 7.99193 13.3643 8.00189C13.5338 8.01184 13.6931 8.0862 13.8097 8.20975C13.9263 8.33331 13.9913 8.49675 13.9915 8.66667V12.6667C13.9915 13.0203 13.8511 13.3594 13.6012 13.6095C13.3514 13.8595 13.0125 14 12.6591 14H3.33239C2.97902 14 2.64012 13.8595 2.39025 13.6095C2.14038 13.3594 2 13.0203 2 12.6667V3.33333C2 2.97971 2.14038 2.64057 2.39025 2.39052C2.64012 2.14048 2.97902 2 3.33239 2H7.32955ZM13.8049 2.19533C13.9298 2.32035 14 2.48989 14 2.66667C14 2.84344 13.9298 3.01298 13.8049 3.138L7.6813 9.26667C7.55565 9.3881 7.38737 9.4553 7.2127 9.45378C7.03802 9.45226 6.87093 9.38215 6.74742 9.25855C6.6239 9.13494 6.55384 8.96773 6.55232 8.79293C6.5508 8.61814 6.61795 8.44974 6.7393 8.324L12.863 2.196C12.9879 2.07102 13.1573 2.00081 13.3339 2.00081C13.5106 2.00081 13.68 2.07102 13.8049 2.196V2.19533Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,5 @@
<svg width="15" height="12" viewBox="0 0 15 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Vector">
<path id="Vector_2" d="M12.4443 0.825817H1.46413C1.40457 0.825817 1.34645 0.833899 1.29058 0.849615L6.60885 6.66281C6.74584 6.81233 6.96765 6.81233 7.10443 6.66281L12.4443 0.825817ZM13.2023 1.08063L8.91915 5.76275L13.2393 10.4848C13.3286 10.3562 13.3816 10.1954 13.3816 10.0217V1.59229C13.3816 1.39584 13.3138 1.21691 13.2027 1.08086H13.2023V1.08063ZM12.5248 10.7881L8.42378 6.30472L7.60001 7.20522C7.40284 7.42075 7.13523 7.54198 6.85633 7.54198C6.57743 7.54198 6.30982 7.42075 6.11266 7.20522L5.2893 6.30472L1.22918 10.7439C1.3027 10.7724 1.38177 10.7879 1.46433 10.7879H12.5252H12.5248V10.7881ZM0.773852 10.1573L4.79372 5.76253L0.788845 1.38529C0.771593 1.45264 0.762967 1.52224 0.762967 1.59229V10.0219C0.762967 10.0679 0.766459 10.1135 0.773852 10.1575V10.1573ZM1.46413 0.0595703H12.6804C13.0524 0.0595703 13.4089 0.220992 13.6718 0.508587C13.9347 0.795958 14.0826 1.1857 14.0826 1.59229V10.0219C14.0826 10.4285 13.9347 10.8182 13.6718 11.1056C13.4089 11.393 13.0522 11.5546 12.6804 11.5546H1.46413C1.09219 11.5546 0.735652 11.3932 0.472768 11.1056C0.209884 10.8182 0.0620117 10.4285 0.0620117 10.0219V1.59229C0.0620117 1.1857 0.209679 0.795958 0.472768 0.508587C0.735652 0.220992 1.09219 0.0595703 1.46413 0.0595703Z" fill="currentColor"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="16" height="16" viewBox="0 0 16 16"><defs><clipPath id="master_svg0_39_604"><rect x="0" y="0" width="16" height="16" rx="0"/></clipPath></defs><g clip-path="url(#master_svg0_39_604)"><g><path d="M4.59375,0.59375Q4.03125,1.15625,4,2L4,14Q4.03125,14.8438,4.59375,15.4062Q5.15625,15.9688,6,16L14,16Q14.8438,15.9688,15.4062,15.4062Q15.9688,14.8438,16,14L16,5L12,5Q11.5625,5,11.28125,4.71875Q11,4.4375,11,4L11,0L6,0Q5.15625,0.03125,4.59375,0.59375ZM4,9.74951Q4,9.48389,4.21875,9.21826L6.71875,6.71826Q7.25,6.28076,7.78125,6.71826Q8.21875,7.24951,7.78125,7.78076L6.5625,8.99951L12.25,8.99951Q12.9375,9.06201,13,9.74951Q12.9375,10.437,12.25,10.4995L6.5625,10.4995L7.78125,11.7183Q8.21875,12.2495,7.78125,12.7808Q7.25,13.2183,6.71875,12.7808L4.21875,10.2808Q4,10.0151,4,9.74951ZM16,4L12,0L12,4L16,4Z" fill-rule="evenodd" fill="currentColor" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></svg>

After

Width:  |  Height:  |  Size: 1016 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="17" height="17" viewBox="0 0 17 17"><defs><clipPath id="master_svg0_41_5342"><rect x="0" y="0" width="17" height="17" rx="0"/></clipPath></defs><g clip-path="url(#master_svg0_41_5342)"><g><path d="M15.0908015625,14.8915984375L1.9091795624999999,14.8915984375C1.4507405625,14.8915984375,1.0791015625,14.5199984375,1.0791015625,14.0614984375L1.0791015625,2.9384764375C1.0791015625,2.4800374375,1.4507405625,2.1083984375,1.9091795624999999,2.1083984375L8.051761562500001,2.1083984375C8.5102015625,2.1083984375,8.8818315625,2.4800374375,8.8818315625,2.9384764375L8.8818315625,3.9677784375C8.8818315625,4.0594584375,8.9561615625,4.1337884375,9.0478515625,4.1337884375L13.0488015625,4.1337884375C13.5073015625,4.1337884375,13.8789015625,4.5054284375,13.8789015625,4.9638684375L13.8789015625,5.9267584375L15.0908015625,5.9267584375C15.5493015625,5.9267584375,15.9209015625,6.2983984375,15.9209015625,6.7568384375L15.9209015625,14.0614984375C15.9209015625,14.5199984375,15.5493015625,14.8915984375,15.0908015625,14.8915984375ZM8.3837915625,5.1298784375C8.108721562500001,5.1298784375,7.8857415625,4.906898437500001,7.8857415625,4.6318384375L7.8857415625,3.4365184375C7.8857415625,3.2531484375,7.7370915625,3.1044924375000003,7.5537115625,3.1044924375000003L2.4072215625,3.1044924375000003C2.2238515625,3.1044924375000003,2.0751955625000003,3.2531484375,2.0751955625000003,3.4365184375L2.0751955625000003,13.5634984375C2.0751955625000003,13.7468984375,2.2308715625,13.8954984375,2.4142315625,13.8954984375L3.4697215625,13.8954984375C3.6531015625,13.8954984375,3.8017615625,13.7468984375,3.8017615625,13.5634984375L3.8017615625,6.7568384375C3.8017615625,6.2983984375,4.1733915625,5.9267584375,4.6318315625,5.9267584375L12.8828015625,5.9267584375L12.8828015625,5.4619184375C12.8828015625,5.2785384375,12.7342015625,5.1298784375,12.5508015625,5.1298784375L8.8818315625,5.1298784375M14.9248015625,7.2548784375C14.9248015625,7.0715084375,14.7761015625,6.9228484375,14.5928015625,6.9228484375L5.1298815625,6.9228484375C4.9465115625,6.9228484375,4.7978515625,7.0715084375,4.7978515625,7.2548784375L4.7978515625,13.8954984375L14.5928015625,13.8954984375C14.7761015625,13.8954984375,14.9248015625,13.7468984375,14.9248015625,13.5634984375L14.9248015625,7.2548784375ZM10.8019515625,10.2232384375L10.3600515625,9.7827984375L10.3600515625,12.2353984375C10.3600615625,12.5103984375,10.1370715625,12.7333984375,9.8620115625,12.7333984375L9.8601315625,12.7333984375C9.5850715625,12.7333984375,9.3620815625,12.5103984375,9.3620815625,12.2353984375L9.3620815625,9.782888437499999L8.9201715625,10.2232384375C8.7257415625,10.4167784375,8.411431562499999,10.4166984375,8.217111562500001,10.2230584375C8.0227815625,10.0294184375,8.021601562499999,9.7151084375,8.214451562499999,9.5199984375L9.5081815625,8.2305684375C9.7033115625,8.036358437499999,10.0187115625,8.036358437499999,10.2138415625,8.2305684375L11.5076015625,9.5199984375C11.6971015625,9.715628437500001,11.6944015625,10.0272184375,11.5015015625,10.2194984375C11.3085015625,10.4117784375,10.9969415625,10.4134484375,10.8019515625,10.2232384375Z" fill="currentColor" fill-opacity="1"/></g></g></svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="16" height="16" viewBox="0 0 16 16"><defs><clipPath id="master_svg0_1_250"><rect x="0" y="0" width="16" height="16" rx="0"/></clipPath></defs><g clip-path="url(#master_svg0_1_250)"><g><path d="M4,2L4,9L9.4375,9L8.21875,7.78125Q7.78125,7.25,8.21875,6.71875Q8.75,6.28125,9.28125,6.71875L11.78125,9.21875Q12.21875,9.75,11.78125,10.2812L9.28125,12.7812Q8.75,13.2188,8.21875,12.7812Q7.78125,12.25,8.21875,11.7188L9.4375,10.5L4,10.5L4,14Q4.03125,14.8438,4.59375,15.4062Q5.15625,15.9688,6,16L14,16Q14.8438,15.9688,15.4062,15.4062Q15.9688,14.8438,16,14L16,5L12,5Q11.5625,5,11.28125,4.71875Q11,4.4375,11,4L11,0L6,0Q5.15625,0.03125,4.59375,0.59375Q4.03125,1.15625,4,2ZM16,4L12,0L12,4L16,4Z" fill-rule="evenodd" fill="currentColor" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></svg>

After

Width:  |  Height:  |  Size: 904 B

View File

@ -0,0 +1,11 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_4756_1572)">
<path d="M16 8C16 12.4182 12.4182 16 8 16C3.58179 16 0 12.4182 0 8C0 3.58179 3.58179 0 8 0C12.4182 0 16 3.58179 16 8Z" fill="#333"/>
<path d="M5.51721 5.51758H10.4827V10.4831H5.51721V5.51758Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_4756_1572">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 457 B

View File

@ -0,0 +1,6 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Group 114">
<path id="Vector" d="M10.6456 10.0696C10.6456 10.3492 10.4168 10.578 10.1372 10.578H5.86385C5.58305 10.578 5.35547 10.3504 5.35547 10.0696V5.92928C5.35547 5.64848 5.58305 5.4209 5.86385 5.4209H10.1372C10.4168 5.4209 10.6456 5.64967 10.6456 5.92928V10.0696Z" fill="currentColor" />
<path id="Vector_2" d="M16 8.00205C16.0044 5.87857 15.1627 3.84068 13.661 2.33916C12.1595 0.837257 10.1216 -0.00435197 7.99816 1.6925e-05C5.87627 -0.00335904 3.84057 0.838448 2.34085 2.33956C0.837947 3.84008 -0.00465313 5.87817 0.000112932 8.00205C-0.0112065 10.1269 0.828812 12.1682 2.3325 13.6695C3.83282 15.173 5.87309 16.0124 7.99697 15.9999C12.4026 15.9552 15.9998 12.4057 15.9998 8.00205H16ZM0.939822 8.00205C0.938829 6.12938 1.68194 4.33297 3.00571 3.0084C4.32949 1.68383 6.1255 0.939529 7.99816 0.939529C11.9218 0.939529 15.1044 4.1189 15.1044 8.00205C15.1044 11.8852 11.9218 15.0624 7.99816 15.0624C6.12331 15.0709 4.32273 14.3296 2.99717 13.0036C1.67161 11.6777 0.930885 9.8769 0.939822 8.00205Z" fill="currentColor" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,5 @@
<svg width="12" height="15" viewBox="0 0 12 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Vector">
<path id="Vector_2" d="M11.4471 14.4718H1.22356C0.697827 14.5303 0.230469 13.828 0.230469 13.477V6.86365C0.230469 6.16135 0.989884 5.69314 1.39886 5.69314H1.98315V3.9373C1.98315 1.94752 4.08626 0.425781 6.07245 0.425781C8.05864 0.425781 10.1618 1.94752 10.1618 3.9373V5.69314H10.7459C11.2132 5.69314 11.9143 6.10286 11.9143 6.86365V13.477C11.8559 13.828 11.9726 14.4718 11.4469 14.4718H11.4471ZM8.93498 3.9373C8.93498 2.59117 7.3576 1.59629 6.01407 1.59629C4.67055 1.59629 3.09317 2.59117 3.09317 3.9373V5.69314H8.93515V3.9373H8.93498ZM10.6877 6.86348H1.34049V13.3013H10.6877L10.863 13.477L10.6877 6.86365V6.86348ZM6.59819 10.1995V11.5457C6.59819 11.8967 6.36451 12.131 6.01407 12.131C5.66364 12.131 5.42996 11.8384 5.42996 11.5457V10.1995C5.07953 10.0239 4.84585 9.61438 4.84585 9.20466C4.84585 8.56084 5.37175 8.03415 6.01424 8.03415C6.65673 8.03415 7.18264 8.56084 7.18264 9.20466C7.18264 9.61438 6.94896 10.0239 6.59853 10.1995H6.59819Z" fill="currentColor"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

11
src/assets/icons/plus.svg Normal file
View File

@ -0,0 +1,11 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_3949_728)">
<path d="M8 3.3335V12.6668" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3.33331 8H12.6666" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_3949_728">
<rect width="16" height="16" fill="currentColor"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 499 B

View File

@ -0,0 +1,5 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Subtract">
<path id="Subtract_2" d="M16 0C18.2091 0 20 1.79086 20 4V16C20 18.2091 18.2091 20 16 20H4C1.79086 20 3.22133e-08 18.2091 0 16V4C0 1.79086 1.79086 3.22128e-08 4 0H16ZM10.1113 5C9.74314 5 9.44434 5.2988 9.44434 5.66699V9.44531H5.66699C5.2988 9.44531 5 9.74411 5 10.1123C5 10.4805 5.2988 10.7793 5.66699 10.7793H9.44434V14.5557C9.44452 14.9237 9.74325 15.2217 10.1113 15.2217C10.4793 15.2215 10.7772 14.9236 10.7773 14.5557V10.7793H14.5557L14.6895 10.7656C14.9934 10.7036 15.2217 10.4346 15.2217 10.1123C15.2217 9.79004 14.9934 9.52105 14.6895 9.45898L14.5557 9.44531H10.7773V5.66699C10.7773 5.29888 10.4794 5.00013 10.1113 5Z" fill="currentColor"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 773 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="16" height="16" viewBox="0 0 16 16"><defs><clipPath id="master_svg0_41_946"><rect x="0.5" y="0" width="15" height="16" rx="0"/></clipPath></defs><g><rect x="0" y="0" width="16" height="16" rx="0" fill="#000000" fill-opacity="0" style="mix-blend-mode:passthrough"/><g clip-path="url(#master_svg0_41_946)"><g transform="matrix(1,0,0,-1,0,30.6875)"><g><path d="M3.96875,25.87495Q3.15625,25.03125,2.78125,24Q2.625,23.625,2.28125,23.4375Q1.90625,23.28125,1.5,23.40625Q1.125,23.5625,0.9375,23.90625Q0.78125,24.28125,0.90625,24.6875Q1.40625,26.12495,2.5625,27.28125Q3.9375,28.65625,5.71875,29.12495Q7.46875,29.56255,9.25,29.12495Q11.0312,28.68755,12.4062,27.31255L13.7188,28.62495Q14.0938,28.96875,14.5312,28.78125Q14.9688,28.59375,15,28.09375L15,24.09375Q14.9375,23.40625,14.25,23.34375L14,23.34375L10.25,23.34375Q9.75,23.375,9.5625,23.8125Q9.375,24.25,9.71875,24.625L11,25.90625Q9.46875,27.34375,7.46875,27.34375Q5.5,27.34375,3.96875,25.87495ZM0,20.59375Q0.0625,21.28125,0.75,21.34375L1,21.34375L4.75,21.34375Q5.25,21.3125,5.4375,20.875Q5.625,20.4375,5.28125,20.0625L4,18.78125Q5.53125,17.34375,7.53125,17.34375Q9.5,17.34375,11.0312,18.8125Q11.8438,19.65625,12.2188,20.6875Q12.375,21.0625,12.7188,21.25Q13.0938,21.40625,13.5,21.28125Q13.875,21.125,14.0625,20.78125Q14.25,20.40625,14.0938,20Q13.5938,18.5625,12.4375,17.40625Q11.0625,16.03125,9.28125,15.5625Q7.53125,15.125,5.75,15.5625Q3.96875,16,2.59375,17.375L1.28125,16.0625Q0.90625,15.71875,0.46875,15.90625Q0.03125,16.09375,0,16.59375L0,20.34375L0,20.59375Z" fill="currentColor" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></g></g></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="15" height="16" viewBox="-0.984375 0 15 16"><defs><clipPath id="master_svg0_1_255"><rect x="0" y="0" width="14" height="16" rx="0"/></clipPath></defs><g clip-path="url(#master_svg0_1_255)"><g transform="matrix(1,0,0,-1,0,30)"><g><path d="M1.872765,29Q1.089285,28.9688,0.566964,28.4062Q0.0446429,27.8438,0.015625,27L0.015625,17Q0.0446429,16.15625,0.566964,15.59375Q1.089285,15.03125,1.872765,15L11.158525,15Q11.941925,15.03125,12.464325,15.59375Q12.986625,16.15625,13.015625,17L13.015625,24.59375Q13.015625,25.4062,12.464325,26L10.229925,28.4062Q9.678575,29,8.924105,29L1.872765,29ZM1.872765,26Q1.872765,26.4375,2.133925,26.7188Q2.395085,27,2.801335,27L8.372765,27Q8.779015,27,9.040175,26.7188Q9.301335,26.4375,9.301335,26L9.301335,24Q9.301335,23.5625,9.040175,23.28125Q8.779015,23,8.372765,23L2.801335,23Q2.395085,23,2.133925,23.28125Q1.872765,23.5625,1.872765,24L1.872765,26ZM6.515625,21Q7.560265,20.96875,8.111605,20Q8.633925,19,8.111605,18Q7.560265,17.03125,6.515625,17Q5.470985,17.03125,4.919645,18Q4.397325,19,4.919645,20Q5.470985,20.96875,6.515625,21Z" fill="currentColor" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="18" height="18" viewBox="0 0 18 18"><g><rect x="0" y="0" width="18" height="18" rx="0" fill="none" fill-opacity="1"/><g><g><rect x="3" y="3" width="4.5" height="12" rx="0" fill="none" fill-opacity="1"/><path d="M2.25,15L2.25,3Q2.25,2.9261315,2.264411,2.853682Q2.278822,2.781233,2.30709,2.712987Q2.335359,2.644742,2.376398,2.583322Q2.417437,2.521903,2.46967,2.46967Q2.521903,2.417437,2.583322,2.376398Q2.644742,2.335359,2.712987,2.30709Q2.781233,2.278822,2.853682,2.264411Q2.9261315,2.25,3,2.25L7.5,2.25Q7.57387,2.25,7.64632,2.264411Q7.71877,2.278822,7.78701,2.30709Q7.85526,2.335359,7.91668,2.376398Q7.9781,2.417437,8.03033,2.46967Q8.08256,2.521903,8.1236,2.583322Q8.16464,2.644742,8.192910000000001,2.712987Q8.22118,2.781233,8.23559,2.853682Q8.25,2.9261315,8.25,3L8.25,15Q8.25,15.0739,8.23559,15.1463Q8.22118,15.2188,8.192910000000001,15.287Q8.16464,15.3553,8.1236,15.4167Q8.08256,15.4781,8.03033,15.5303Q7.9781,15.5826,7.91668,15.6236Q7.85526,15.6646,7.78701,15.6929Q7.71877,15.7212,7.64632,15.7356Q7.57387,15.75,7.5,15.75L3,15.75Q2.9261315,15.75,2.853682,15.7356Q2.781233,15.7212,2.712987,15.6929Q2.644742,15.6646,2.583322,15.6236Q2.521903,15.5826,2.46967,15.5303Q2.417437,15.4781,2.376398,15.4167Q2.335359,15.3553,2.30709,15.287Q2.278822,15.2188,2.264411,15.1463Q2.25,15.0739,2.25,15ZM3.75,3.75L3.75,14.25L6.75,14.25L6.75,3.75L3.75,3.75Z" fill="currentColor" fill-opacity="1"/></g><g><rect x="10.125" y="3" width="4.875" height="4.875" rx="0" fill="none" fill-opacity="1"/><path d="M9.375,7.875L9.375,3Q9.375,2.9261315,9.389410999999999,2.853682Q9.403822,2.781233,9.43209,2.712987Q9.460359,2.644742,9.501398,2.583322Q9.542437,2.521903,9.59467,2.46967Q9.646903,2.417437,9.708322,2.376398Q9.769742,2.335359,9.837987,2.30709Q9.906233,2.278822,9.978682,2.264411Q10.0511315,2.25,10.125,2.25L15,2.25Q15.07387,2.25,15.14632,2.264411Q15.21877,2.278822,15.28701,2.30709Q15.355260000000001,2.335359,15.41668,2.376398Q15.478100000000001,2.417437,15.53033,2.46967Q15.58256,2.521903,15.6236,2.583322Q15.66464,2.644742,15.692910000000001,2.712987Q15.72118,2.781233,15.73559,2.853682Q15.75,2.9261315,15.75,3L15.75,7.875Q15.75,7.94887,15.73559,8.02132Q15.72118,8.09377,15.692910000000001,8.16201Q15.66464,8.230260000000001,15.6236,8.29168Q15.58256,8.353100000000001,15.53033,8.40533Q15.478100000000001,8.45756,15.41668,8.4986Q15.355260000000001,8.53964,15.28701,8.567910000000001Q15.21877,8.59618,15.14632,8.61059Q15.07387,8.625,15,8.625L10.125,8.625Q10.0511315,8.625,9.978682,8.61059Q9.906233,8.59618,9.837987,8.567910000000001Q9.769742,8.53964,9.708322,8.4986Q9.646903,8.45756,9.59467,8.40533Q9.542437,8.353100000000001,9.501398,8.29168Q9.460359,8.230260000000001,9.43209,8.16201Q9.403822,8.09377,9.389410999999999,8.02132Q9.375,7.94887,9.375,7.875ZM10.875,3.75L10.875,7.125L14.25,7.125L14.25,3.75L10.875,3.75Z" fill="currentColor" fill-opacity="1"/></g><g><rect x="10.125" y="10.125" width="4.875" height="4.875" rx="0" fill="none" fill-opacity="1"/><path d="M9.375,15L9.375,10.125Q9.375,10.0511315,9.389410999999999,9.978682Q9.403822,9.906233,9.43209,9.837987Q9.460359,9.769742,9.501398,9.708322Q9.542437,9.646903,9.59467,9.59467Q9.646903,9.542437,9.708322,9.501398Q9.769742,9.460359,9.837987,9.43209Q9.906233,9.403822,9.978682,9.389410999999999Q10.0511315,9.375,10.125,9.375L15,9.375Q15.07387,9.375,15.14632,9.389410999999999Q15.21877,9.403822,15.28701,9.43209Q15.355260000000001,9.460359,15.41668,9.501398Q15.478100000000001,9.542437,15.53033,9.59467Q15.58256,9.646903,15.6236,9.708322Q15.66464,9.769742,15.692910000000001,9.837987Q15.72118,9.906233,15.73559,9.978682Q15.75,10.0511315,15.75,10.125L15.75,15Q15.75,15.07387,15.73559,15.14632Q15.72118,15.21877,15.692910000000001,15.28701Q15.66464,15.355260000000001,15.6236,15.41668Q15.58256,15.478100000000001,15.53033,15.53033Q15.478100000000001,15.58256,15.41668,15.6236Q15.355260000000001,15.66464,15.28701,15.692910000000001Q15.21877,15.72118,15.14632,15.73559Q15.07387,15.75,15,15.75L10.125,15.75Q10.0511315,15.75,9.978682,15.73559Q9.906233,15.72118,9.837987,15.692910000000001Q9.769742,15.66464,9.708322,15.6236Q9.646903,15.58256,9.59467,15.53033Q9.542437,15.478100000000001,9.501398,15.41668Q9.460359,15.355260000000001,9.43209,15.28701Q9.403822,15.21877,9.389410999999999,15.14632Q9.375,15.07387,9.375,15ZM10.875,10.875L10.875,14.25L14.25,14.25L14.25,10.875L10.875,10.875Z" fill="currentColor" fill-opacity="1"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="12" height="16" viewBox="0 0 12 16"><defs><clipPath id="master_svg0_30_03326"><rect x="0" y="0" width="12" height="16" rx="0"/></clipPath></defs><g clip-path="url(#master_svg0_30_03326)"><g transform="matrix(1,0,0,-1,0,30.70355224609375)"><g><path d="M2.28125,29.141176123046876Q1.53125,29.578676123046876,0.78125,29.172376123046874Q0.03125,28.734876123046874,0,27.859876123046874L0,16.859876123046874Q0.03125,15.984878123046876,0.78125,15.547378123046874Q1.53125,15.141128123046874,2.28125,15.578628123046874L11.2812,21.078626123046874Q11.9688,21.516126123046874,12,22.359876123046874Q11.9688,23.172376123046874,11.2812,23.641126123046874L2.28125,29.141176123046876Z" fill="currentColor" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 876 B

View File

@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.14184 2.19544C3.01669 2.07029 2.84705 2 2.67018 2C2.49331 2 2.32367 2.07029 2.19852 2.19544C2.07353 2.32074 2.00333 2.4906 2.00333 2.66769C2.00333 2.84479 2.07353 3.01464 2.19852 3.13994L3.14184 2.19544ZM5.16195 6.08037C5.2871 6.20552 5.45674 6.27581 5.63361 6.27581C5.81048 6.27581 5.98012 6.20552 6.10527 6.08037C6.23026 5.95507 6.30046 5.78522 6.30046 5.60812C6.30046 5.43103 6.23026 5.26117 6.10527 5.13587L5.16195 6.08037ZM2.19852 3.13994L5.16195 6.08037L6.10527 5.13587L3.14184 2.19544L2.19852 3.13994ZM2.19852 12.8523C2.07225 12.9775 2.00084 13.1479 2.00001 13.3258C1.99917 13.5038 2.06898 13.6748 2.19407 13.8012C2.31916 13.9277 2.48929 13.9992 2.66703 14C2.84477 14.0008 3.01556 13.9309 3.14184 13.8057L2.19852 12.8523ZM6.10527 10.8653C6.23154 10.74 6.30295 10.5697 6.30378 10.3917C6.30461 10.2137 6.23481 10.0427 6.10972 9.9163C5.98462 9.78987 5.8145 9.71837 5.63676 9.71753C5.45902 9.7167 5.28822 9.78659 5.16195 9.91184L6.10527 10.8653ZM3.14184 13.8057L6.10527 10.8653L5.16195 9.91184L2.19852 12.8523L3.14184 13.8057ZM12.8776 13.8057C13.0041 13.9237 13.1714 13.988 13.3443 13.9849C13.5172 13.9819 13.6822 13.9117 13.8045 13.7893C13.9268 13.6669 13.9968 13.5017 13.9999 13.3286C14.0029 13.1554 13.9388 12.9879 13.8209 12.8612L12.8776 13.8057ZM10.8841 9.92075C10.8228 9.85771 10.7494 9.8076 10.6684 9.77338C10.5874 9.73917 10.5004 9.72154 10.4125 9.72154C10.3246 9.72154 10.2375 9.73917 10.1565 9.77338C10.0755 9.8076 10.0022 9.85771 9.94082 9.92075C9.81583 10.0461 9.74562 10.2159 9.74562 10.393C9.74562 10.5701 9.81583 10.74 9.94082 10.8653L10.8841 9.92075ZM13.8209 12.8612L10.8841 9.92075L9.94082 10.8653L12.8776 13.8057L13.8209 12.8612ZM13.7942 3.13994C13.9192 3.01464 13.9894 2.84479 13.9894 2.66769C13.9894 2.4906 13.9192 2.32074 13.7942 2.19544C13.6664 2.07186 13.4957 2.00278 13.3181 2.00278C13.1404 2.00278 12.9697 2.07186 12.842 2.19544L13.7942 3.13994ZM9.90522 5.13587C9.78023 5.26117 9.71003 5.43103 9.71003 5.60812C9.71003 5.78522 9.78023 5.95507 9.90522 6.08037C10.033 6.20396 10.2037 6.27303 10.3813 6.27303C10.559 6.27303 10.7297 6.20396 10.8574 6.08037L9.90522 5.13587ZM12.842 2.19544L9.90522 5.13587L10.8574 6.08037L13.7942 3.13994L12.842 2.19544Z" fill="#151515"/>
<path d="M10.7128 2.20027C10.5394 2.20027 10.373 2.26931 10.2504 2.39218C10.1278 2.51506 10.0589 2.68172 10.0589 2.8555C10.0589 3.02928 10.1278 3.19594 10.2504 3.31881C10.373 3.44169 10.5394 3.51073 10.7128 3.51073V2.20027ZM13.3286 2.8555H13.9826C13.9803 2.68243 13.9107 2.51708 13.7885 2.39469C13.6664 2.27229 13.5013 2.20254 13.3286 2.20027V2.8555ZM12.6747 5.47641C12.6747 5.65018 12.7436 5.81684 12.8662 5.93972C12.9888 6.0626 13.1552 6.13163 13.3286 6.13163C13.5021 6.13163 13.6684 6.0626 13.791 5.93972C13.9137 5.81684 13.9826 5.65018 13.9826 5.47641H12.6747ZM10.7128 3.51073H13.3286V2.20027H10.7128V3.51073ZM12.6921 2.8555V5.47641H14V2.8555H12.6921ZM14 10.7182C14 10.5444 13.9311 10.3778 13.8085 10.2549C13.6858 10.132 13.5195 10.063 13.3461 10.063C13.1726 10.063 13.0063 10.132 12.8836 10.2549C12.761 10.3778 12.6921 10.5444 12.6921 10.7182H14ZM13.3286 13.3391V13.9944C13.5013 13.9921 13.6664 13.9223 13.7885 13.7999C13.9107 13.6775 13.9803 13.5122 13.9826 13.3391H13.3286ZM10.7128 12.6839C10.5394 12.6839 10.373 12.7529 10.2504 12.8758C10.1278 12.9987 10.0589 13.1653 10.0589 13.3391C10.0589 13.5129 10.1278 13.6796 10.2504 13.8024C10.373 13.9253 10.5394 13.9944 10.7128 13.9944V12.6839ZM12.6921 10.7182V13.3391H14V10.7182H12.6921ZM13.3286 12.6839H10.7128V13.9944H13.3286V12.6839ZM5.48122 13.9944C5.65466 13.9944 5.82099 13.9253 5.94363 13.8024C6.06627 13.6796 6.13517 13.5129 6.13517 13.3391C6.13517 13.1653 6.06627 12.9987 5.94363 12.8758C5.82099 12.7529 5.65466 12.6839 5.48122 12.6839V13.9944ZM2.86542 13.3391H2.22891C2.22891 13.5129 2.29781 13.6796 2.42045 13.8024C2.54309 13.9253 2.70942 13.9944 2.88286 13.9944V13.3391H2.86542ZM3.51937 10.7182C3.51937 10.5444 3.45047 10.3778 3.32784 10.2549C3.2052 10.132 3.03886 10.063 2.86542 10.063C2.69199 10.063 2.52565 10.132 2.40301 10.2549C2.28037 10.3778 2.21147 10.5444 2.21147 10.7182H3.51937ZM5.48122 12.6839H2.86542V13.9944H5.48122V12.6839ZM3.53681 13.3391V10.7182H2.22891V13.3391H3.53681ZM2.22891 5.47641C2.22891 5.65018 2.29781 5.81684 2.42045 5.93972C2.54309 6.0626 2.70942 6.13163 2.88286 6.13163C3.0563 6.13163 3.22263 6.0626 3.34527 5.93972C3.46791 5.81684 3.53681 5.65018 3.53681 5.47641H2.22891ZM2.86542 2.8555V2.20027C2.69503 2.20483 2.53314 2.27587 2.41424 2.39825C2.29535 2.52064 2.22885 2.68471 2.22891 2.8555H2.86542ZM5.48122 3.51073C5.65466 3.51073 5.82099 3.44169 5.94363 3.31881C6.06627 3.19594 6.13517 3.02928 6.13517 2.8555C6.13517 2.68172 6.06627 2.51506 5.94363 2.39218C5.82099 2.26931 5.65466 2.20027 5.48122 2.20027V3.51073ZM3.53681 5.47641V2.8555H2.22891V5.47641H3.53681ZM2.86542 3.51073H5.48122V2.20027H2.86542V3.51073Z" fill="#151515"/>
<path d="M10.5013 2.00586C10.3279 2.00586 10.1616 2.07489 10.0389 2.19777C9.91629 2.32065 9.84739 2.48731 9.84739 2.66108C9.84739 2.83486 9.91629 3.00152 10.0389 3.1244C10.1616 3.24728 10.3279 3.31631 10.5013 3.31631V2.00586ZM13.1171 2.66108H13.7711C13.7688 2.48801 13.6992 2.32266 13.5771 2.20027C13.4549 2.07788 13.2899 2.00812 13.1171 2.00586V2.66108ZM12.4632 5.28199C12.4632 5.45577 12.5321 5.62243 12.6547 5.74531C12.7774 5.86818 12.9437 5.93722 13.1171 5.93722C13.2906 5.93722 13.4569 5.86818 13.5796 5.74531C13.7022 5.62243 13.7711 5.45577 13.7711 5.28199H12.4632ZM10.5013 3.31631H13.1171V2.00586H10.5013V3.31631ZM12.4806 2.66108V5.28199H13.7885V2.66108H12.4806ZM13.7885 10.5238C13.7885 10.35 13.7196 10.1834 13.597 10.0605C13.4744 9.93761 13.308 9.86858 13.1346 9.86858C12.9611 9.86858 12.7948 9.93761 12.6722 10.0605C12.5495 10.1834 12.4806 10.35 12.4806 10.5238H13.7885ZM13.1171 13.1447V13.7999C13.2899 13.7977 13.4549 13.7279 13.5771 13.6055C13.6992 13.4831 13.7688 13.3178 13.7711 13.1447H13.1171ZM10.5013 12.4895C10.3279 12.4895 10.1616 12.5585 10.0389 12.6814C9.91629 12.8043 9.84739 12.9709 9.84739 13.1447C9.84739 13.3185 9.91629 13.4851 10.0389 13.608C10.1616 13.7309 10.3279 13.7999 10.5013 13.7999V12.4895ZM12.4806 10.5238V13.1447H13.7885V10.5238H12.4806ZM13.1171 12.4895H10.5013V13.7999H13.1171V12.4895ZM5.26975 13.7999C5.44319 13.7999 5.60952 13.7309 5.73216 13.608C5.8548 13.4851 5.9237 13.3185 5.9237 13.1447C5.9237 12.9709 5.8548 12.8043 5.73216 12.6814C5.60952 12.5585 5.44319 12.4895 5.26975 12.4895V13.7999ZM2.65395 13.1447H2.01744C2.01744 13.3185 2.08634 13.4851 2.20898 13.608C2.33162 13.7309 2.49795 13.7999 2.67139 13.7999V13.1447H2.65395ZM3.3079 10.5238C3.3079 10.35 3.239 10.1834 3.11636 10.0605C2.99372 9.93761 2.82739 9.86858 2.65395 9.86858C2.48051 9.86858 2.31418 9.93761 2.19154 10.0605C2.0689 10.1834 2 10.35 2 10.5238H3.3079ZM5.26975 12.4895H2.65395V13.7999H5.26975V12.4895ZM3.32534 13.1447V10.5238H2.01744V13.1447H3.32534ZM2.01744 5.28199C2.01744 5.45577 2.08634 5.62243 2.20898 5.74531C2.33162 5.86818 2.49795 5.93722 2.67139 5.93722C2.84483 5.93722 3.01116 5.86818 3.1338 5.74531C3.25644 5.62243 3.32534 5.45577 3.32534 5.28199H2.01744ZM2.65395 2.66108V2.00586C2.48355 2.01041 2.32166 2.08145 2.20277 2.20384C2.08388 2.32623 2.01738 2.49029 2.01744 2.66108H2.65395ZM5.26975 3.31631C5.44319 3.31631 5.60952 3.24728 5.73216 3.1244C5.8548 3.00152 5.9237 2.83486 5.9237 2.66108C5.9237 2.48731 5.8548 2.32065 5.73216 2.19777C5.60952 2.07489 5.44319 2.00586 5.26975 2.00586V3.31631ZM3.32534 5.28199V2.66108H2.01744V5.28199H3.32534ZM2.65395 3.31631H5.26975V2.00586H2.65395V3.31631Z" fill="#151515"/>
</svg>

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 898 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 636 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
src/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,49 @@
<script lang="ts">
import { defineComponent, reactive } from "vue";
export default defineComponent({
name: "LoginLayout",
});
</script>
<script setup lang="ts">
import { info } from "@/config/index.ts";
const prefix = "login-layout";
</script>
<template>
<div :class="prefix" :style="`background-image: url(${info.bgImg})`">
<div :class="`${prefix}-form`">
<RouterView />
</div>
</div>
</template>
<style lang="scss" scoped>
$prefix: "login-layout";
.#{$prefix} {
width: 100vw;
height: 100vh;
overflow: hidden;
position: relative;
background-size: 100% auto;
&-form {
position: absolute;
left: 384px;
top: 253px;
width: 447px;
height: 300px;
opacity: 1;
background: #fff;
box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.16);
border-radius: 16px;
// border-radius: 16px 16px 16px 16px;
}
}
.global-login-page {
width: 100%;
height: 100%;
position: relative;
}
</style>

View File

@ -0,0 +1,85 @@
<script lang="ts">
import { defineComponent, defineAsyncComponent } from "vue";
export default defineComponent({
name: "SystemLayoutContent",
});
</script>
<script setup lang="ts">
// import LoginDrawer from '@/components/Service/Login/index/index.vue'
import ErrorLoading from "@/components/Module/AsyncContent/ErrorLoading.vue";
import { useRoute } from "vue-router";
const asyncContent = defineAsyncComponent({
loader: () => import("@/components/Module/AsyncContent/index.vue"),
delay: 2000,
loadingComponent: ErrorLoading,
errorComponent: ErrorLoading,
timeout: 10000,
});
interface propsType {
contentLoading: boolean;
}
let props = withDefaults(defineProps<propsType>(), {
//
contentLoading: true,
});
const route = useRoute();
const prefix = "_system-layout-content";
</script>
<!-- bgImg
SiderImg-->
<template>
<a-layout-content :class="prefix">
<div class="router-view-box">
<div v-if="props.contentLoading">
<Suspense>
<asyncContent>
<RouterView :key="route.path" />
</asyncContent>
<template #fallback>
<div class="empty-box">
<a-spin />
</div>
</template>
</Suspense>
</div>
</div>
<!-- <LoginDrawer /> -->
</a-layout-content>
</template>
<style lang="scss" scoped>
$prefix: "_system-layout-content";
.#{$prefix} {
width: 100%;
// background-color: #f4f6fc;
width: 100%;
height: calc(100vh - 60px);
overflow: hidden;
.router-view-box {
width: 100%;
height: 100%;
// background-color: #f7f8fa;
overflow: auto;
box-sizing: border-box;
> div {
width: 100%;
height: 100%;
}
.empty-box {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
}
}
</style>

View File

@ -0,0 +1,58 @@
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
name: "SystemLayoutHeader",
});
</script>
<script setup lang="ts">
import Info from "@/components/Base/Tools/Info/index.vue";
const prefix = "_system-layout-header";
</script>
<template>
<a-layout-header theme="light" :class="prefix">
<div :class="`${prefix}-wrapper`">
<div :class="`${prefix}-wrapper-grid`">
<Info />
</div>
<div :class="`${prefix}-wrapper-grid`">
<!-- <UserBox /> -->
</div>
</div>
</a-layout-header>
</template>
<style lang="scss" scoped>
$prefix: "_system-layout-header";
.#{$prefix} {
padding: 0;
background-color: #fff;
height: 60px;
position: reactive;
z-index: 20;
box-shadow: 0px 2px 4px 0px rgba(133, 99, 244, 0.2);
box-sizing: border-box;
&-wrapper {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20px;
&-left {
font-size: 16px;
color: #333;
}
&-right {
display: flex;
align-items: center;
justify-content: center;
gap: 20px;
}
}
}
</style>

View File

@ -0,0 +1,44 @@
import { h } from 'vue'
import Icon from '@/components/Module/Icon/index.vue'
export const useMain = () => {
// 路由过滤,只显示要显示的路由
const routerFilter = (menus: Array<Record<string, any>>) => {
return menus.filter((item: Record<string, any>) => {
if (item.children) {
item.children = routerFilter(item.children)
}
return !item.meta.hidden
})
}
// 路由格式化
const routerFormat = (arr: Array<Record<string, any>>) => {
return arr.map((item: Record<string, any>) => {
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
}
}

View File

@ -0,0 +1,242 @@
<script lang="ts">
import { defineComponent } from "vue";
import { computed } from "vue";
export default defineComponent({
name: "SystemLayoutSider",
});
</script>
<script setup lang="ts">
import { reactive, ref, watch, h, onMounted } from "vue";
import type { MenuProps } from "ant-design-vue";
import { router } from "@/router/index.ts";
import UserBox from "@/components/Base/Tools/UserBox/index.vue";
import { useMain } from "@/components/Base/Layout/SystemLayout/Sider/hooks/router.ts";
import { useRouterStore } from "@/stores/modules/async-router.ts";
import type { AppRouter } from "@/router/config/index";
import SiderImg from "@/assets/images/index/sider-radar.png";
const routerStore = useRouterStore();
const routers = routerStore.ROUTERS;
const { routerFilter, routerFormat } = useMain();
//
let menus = ref<Array<AppRouter>>([]);
//
const createMenus = (routerList: Array<AppRouter>) => {
const validRouter = routerFilter(routerList);
const useRouter = routerFormat(validRouter);
return useRouter;
};
// let menuSelectKeys = computed(() => {
// console.log("router.currentRoute", router.currentRoute.value);
// return [router.currentRoute.value.name];
// });
//
const menuSelectKeys = ref<string[]>([]);
//
const handleClick: MenuProps["onClick"] = (e) => {
const { fullPath } = e.item;
console.log("fullPath", e.item, routers[0].children);
console.log("menuSelectKeys", menuSelectKeys);
if (fullPath != router.currentRoute.value.fullPath) {
router.push(e.item.fullPath as string);
}
};
//
const routerOpenKeys = ref<string[]>([]);
const handleOpenChange = (openKeys: string[]) => {};
//
const initMenus = () => {
//@ts-ignorex
menus.value = createMenus(routers[0].children as AppRouter[]);
};
//
const initMenuSelectkeys = () => {
const curRouter = router.currentRoute.value;
console.log("curRouter", curRouter, routers[0]);
let key = curRouter.name as string;
const { hidden } = curRouter.meta;
if (hidden) {
const routerGroup = curRouter.matched[1];
let target = findRouter([routerGroup], key);
console.log("targetRouter", target);
let isSelect = true;
while (isSelect) {
if (target.meta.hidden) {
if (!target.parent) {
isSelect = false;
} else {
target = target.parent;
}
} else {
key = target.name;
isSelect = false;
}
}
}
menuSelectKeys.value = [key];
};
//
const findRouter = (routerList: Array<Record<string, any>>, key: string): any => {
let router = null;
for (let i = 0; i < routerList.length; i++) {
const item = routerList[i];
if (item.name == key) {
router = item;
break;
}
if (item.children) {
router = findRouter(item.children, key);
if (router) {
break;
}
}
}
return router;
};
//
const initStatus = () => {
const { value } = router.currentRoute;
routerOpenKeys.value = (value.meta.parentId ? [value.meta.parentId] : []) as string[];
};
const init = () => {
initMenus();
initStatus();
initMenuSelectkeys();
};
const collapsed = ref(false);
onMounted(() => {
//@ts-ignorex
init();
});
const prefix = "_system-layout-sider";
</script>
<template>
<a-layout-sider
collapsible
v-model:collapsed="collapsed"
theme="light"
:class="prefix"
:trigger="null"
width="240"
>
<a-menu
v-model:openKeys="routerOpenKeys"
v-model:selectedKeys="menuSelectKeys"
mode="inline"
:multiple="false"
:inline-collapsed="collapsed"
:items="menus"
@click="handleClick"
@openChange="handleOpenChange"
>
</a-menu>
<img :src="SiderImg" :class="`${prefix}-marker`" />
</a-layout-sider>
</template>
<style lang="scss" scoped>
$prefix: "_system-layout-sider";
.#{$prefix} {
max-height: 100vh;
overflow: hidden;
// padding: 10px 10px;
box-sizing: border-box;
position: reactive;
z-index: 10;
box-shadow: 0px 2px 4px 0px rgba(133, 99, 244, 0.2);
border-radius: 0px 0px 16px 16px;
background: rgba(255, 255, 255, 0.6);
border: 1px solid rgba(231, 239, 251, 0.5);
background: rgba(255, 255, 255, 0.6);
:deep(.ant-layout-sider-children) {
display: flex;
align-items: start;
justify-content: start;
flex-direction: column;
.ant-menu-light {
background-color: transparent;
margin: 0;
}
.ant-menu-item-selected {
background-color: #e2eaf2;
}
.ant-menu-root.ant-menu-inline {
border-inline-end: none;
}
}
&-header {
padding-bottom: 15px;
&__row {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
.search-row {
margin-top: 5px;
padding: 5px 15px;
border-radius: 40px;
color: #333;
box-sizing: border-box;
:deep(.ant-input) {
background-color: transparent;
}
}
}
&__collapsed {
width: 32px;
height: 32px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
cursor: pointer;
&:hover {
background-color: #f0f0f0;
}
}
}
&-chats {
flex: 1;
width: 100%;
flex-shrink: 0;
align-self: stretch;
}
&-menus {
padding-top: 10px;
width: 100%;
height: 240px;
margin-top: auto;
}
&-footer {
width: 100%;
}
&-marker {
width: 339px;
height: 319px;
position: absolute;
bottom: 0;
left: -121px;
}
}
</style>

View File

@ -0,0 +1,48 @@
<script setup lang="ts">
import { reactive, computed } from "vue";
import LayoutHeader from "./Header/index.vue";
import LayoutSider from "./Sider/index.vue";
import LayoutContent from "./Content/index.vue";
import { router } from "@/router/index.ts";
import { inject } from "vue";
import bgImg from "@/assets/images/index/bg.jpg";
const data = reactive({
loading: true,
});
</script>
<template>
<a-layout class="system-layout">
<layout-header></layout-header>
<a-layout>
<layout-sider v-model:contentLoading="data.loading"> </layout-sider>
<layout-content v-model:contentLoading="data.loading"> </layout-content>
</a-layout>
</a-layout>
</template>
<style lang="scss" scoped>
.system-layout {
width: 100%;
height: 100vh;
position: relative;
background:
linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5)),
url(@/assets/images/index/bg.jpg) no-repeat center center;
background-size: cover;
:deep(.ant-layout) {
background: transparent;
}
&_header {
}
&_content {
// padding-top: 80px;
box-sizing: border-box;
height: 100%;
width: 100%;
// position: relative;
// z-index: 0;
}
}
</style>

View File

@ -0,0 +1,12 @@
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: 'ViewLayout',
})
</script>
<script setup lang="ts"></script>
<template>
<RouterView />
</template>

View File

@ -0,0 +1,56 @@
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
name: "SystemInfo",
});
</script>
<script setup lang="ts">
import { info } from "@/config/index.ts";
const prefix = "_info-box";
</script>
<template>
<div :class="prefix">
<div :class="`${prefix}-icon`">
<img :class="`${prefix}-icon-img`" :src="info.logo" />
</div>
<div :class="`${prefix}-title`">
{{ info.title }}
</div>
</div>
</template>
<style lang="scss" scoped>
$prefix: "_info-box";
.#{$prefix} {
display: flex;
align-items: center;
justify-content: center;
&-icon {
margin-right: 10px;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
&-img {
width: 100%;
height: 100%;
}
}
&-title {
font-family:
Source Han Sans CN,
Source Han Sans CN;
font-weight: 500;
font-size: 20px;
color: #1d2129;
line-height: 28px;
text-align: left;
font-style: normal;
text-transform: none;
}
}
</style>

View File

@ -0,0 +1,81 @@
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
name: "SystemLayoutSider",
});
</script>
<script setup lang="ts">
import Icon from "@/components/Module/Icon/index.vue";
import { ref } from "vue";
interface propsType {
menus: Array<{
name: string;
path: string;
key: string;
icon: string;
}>;
}
let props = withDefaults(defineProps<propsType>(), {
//
menus: () => [],
});
const emits = defineEmits(["click"]);
//
const onHandle = (item: { path: string; key: string }) => {
emits("click", item);
};
const prefix = "_system-menus";
</script>
<template>
<div :class="prefix">
<button :class="`${prefix}-row`" v-for="item in props.menus" @click="onHandle(item)">
<div :class="`${prefix}-row__icon`">
<Icon :name="item.icon" />
</div>
<div :class="`${prefix}-row__label`">{{ item.name }}</div>
</button>
</div>
</template>
<style lang="scss" scoped>
$prefix: "_system-menus";
.#{$prefix} {
width: 100%;
display: flex;
align-items: start;
justify-content: start;
flex-direction: column;
&-row {
width: 100%;
height: 40px;
padding: 5px;
border-radius: 12px;
background-color: transparent;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: start;
gap: 10px;
cursor: pointer;
&:hover {
background-color: #333;
}
&__icon {
margin-right: 8px;
font-size: 16px;
}
&__label {
font-size: 14px;
font-weight: 500;
}
}
}
</style>

View File

@ -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
}
}

View File

@ -0,0 +1,71 @@
<script lang="ts">
import { defineComponent, ref, inject } from "vue";
export default defineComponent({
name: "UserCompBox",
});
</script>
<script lang="ts" setup>
import { useMain } from "@/components/Base/Tools/UserBox/hooks/index.ts";
import Menus from "@/components/Base/Tools/Menus/index.vue";
import Icon from "@/components/Module/Icon/index.vue";
const { menus, operateMenus } = useMain();
const prefix = "_user-box";
</script>
<template>
<a-Dropdown>
<template #overlay>
<div :class="`${prefix}-drop`">
<div :class="`${prefix}-drop-row`">
<Menus :menus="menus" />
</div>
<div :class="`${prefix}-drop-row`">
<Menus :menus="operateMenus" />
</div>
</div>
</template>
<button :class="`${prefix}-avator`">
<icon name="userLine" />
</button>
</a-Dropdown>
</template>
<style lang="scss" scoped>
$prefix: "_user-box";
.#{$prefix} {
&-avator {
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
&:hover {
background-color: #333;
}
}
&-drop {
width: 200px;
display: flex;
align-items: center;
justify-content: center;
padding: 10px;
border-radius: 6px;
background-color: #fff;
box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.16);
&-row {
margin-bottom: 10px;
&:last-child {
padding-top: 10px;
margin-bottom: 0;
border-top: 1px solid #333;
}
}
}
}
</style>

View File

@ -0,0 +1,21 @@
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: 'asyncErrLoadingBox',
})
</script>
<script setup lang="ts"></script>
<template>
<div class="global-comp-loading-box">
<a-spin />
</div>
</template>
<style lang="scss" scoped>
.global-comp-loading-box {
width: 100%;
height: 100%;
}
</style>

View File

@ -0,0 +1,14 @@
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: 'asyncContentBox',
})
</script>
<script setup lang="ts"></script>
<template>
<slot></slot>
</template>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,80 @@
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
name: "SvgIcon",
});
</script>
<script lang="ts" setup>
import { computed } from "vue";
import { getIconContent } from "@/components/Module/Icon/components/icon.ts";
const props = defineProps({
prefix: {
type: String,
default: "icon",
},
name: {
type: String,
required: true,
},
color: {
type: String,
default: "currentColor",
},
className: {
type: String,
default: "",
},
stroke: {
type: String,
default: "currentColor",
},
});
function processSvgForInline(svg: string, fillColor: string) {
let processed = svg.replace(/<\?xml.*?\?>/, "");
if (fillColor) {
processed = processed.replace("<svg", `<svg fill="${fillColor || "currentColor"}" `);
}
return processed;
}
let svgContent: string | null = "";
const processedSvg = computed(() => {
svgContent = getIconContent(props.name);
return svgContent ? processSvgForInline(svgContent, props.color) : "";
});
</script>
<template>
<div class="_svg-container" :style="`--color: ${color ?? 'inherit'};`">
<template v-if="processedSvg">
<div :class="`_svg-icon ${className || ''}`" v-html="processedSvg"></div>
</template>
<template v-else-if="name">
<div :class="`_svg-icon ${className || ''}`">{{ name }}</div>
</template>
</div>
</template>
<style lang="scss" scoped>
._svg-container {
display: flex;
align-items: center;
justify-content: center;
font-size: inherit;
}
._svg-icon {
display: flex;
align-items: center;
justify-content: center;
width: 1em;
height: 1em;
vertical-align: -0.1em;
fill: currentColor;
overflow: hidden;
font-size: inherit;
}
</style>

View File

@ -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<IconMap>({});
const iconContents = ref<IconMap>({});
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
// };
// }

View File

@ -0,0 +1,73 @@
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
name: "CommonIcon",
});
</script>
<script lang="ts" setup>
import * as Icons from "@ant-design/icons-vue";
import SvgIcon from "./components/SvgIcon.vue";
// const { name, color } = defineProps(['name', 'color'])
interface propsType {
name?: string;
color?: string;
}
let props = withDefaults(defineProps<propsType>(), {
name: "",
color: "inherit",
});
const keys = Object.keys(Icons);
const rendIcon = (name: string) => {
const isDefault = keys.find((item) => {
return item == name;
});
return isDefault;
};
</script>
<template>
<!-- {{ props.color }} -->
<div
v-if="rendIcon(props.name)"
class="_global-icon"
:style="{
'--color': props.color ? props.color : 'inherit',
}"
>
<component :is="props.name"></component>
</div>
<div
v-else
class="_global-icon"
:style="{
'--color': props.color ? props.color : 'inherit',
}"
>
<svg-icon :name="props.name" v-if="props.name" :color="props.color" class="_global-svg-icon" />
</div>
</template>
<style lang="scss" scoped>
._global-icon {
color: var(--color);
display: flex;
align-items: center;
justify-content: center;
._global-svg-icon {
display: flex;
align-items: center;
justify-content: center;
color: inherit;
font-size: inherit;
use {
fill: currentColor;
}
}
}
</style>

View File

@ -0,0 +1,171 @@
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
name: "ModalHeader",
});
</script>
<script setup lang="ts">
import { reactive, defineEmits, withDefaults } from "vue";
import { useRoute } from "vue-router";
import Icon from "@/components/Module/Icon/index.vue";
const emits = defineEmits(["onSubmit", "onSubmitAndCreate", "onCancel"]);
interface propsType {
addPermission?: string[] | undefined; //
showIcon?: boolean;
title?: string | null | undefined;
showSubmit?: boolean;
showSubmitAndCreate?: boolean;
loading?: boolean;
// icon
}
const props = withDefaults(defineProps<propsType>(), {
//
addPermission: undefined, //
showSubmit: true,
showSubmitAndCreate: true,
loading: false,
});
//
const route = useRoute();
const pageInfo = reactive({
name: route.meta.title,
icon: route.meta.icon,
});
const typeObj: ExtraObj = {
add: "新增",
edit: "编辑",
};
console.log("typeObj", typeObj, props.type);
const onSubmit = () => {
emits("onSubmit");
};
const onSubmitAndCreate = () => {
emits("onSubmitAndCreate");
};
const onCancel = () => {
emits("onCancel");
};
</script>
<template>
<a-page-header class="_modal-header">
<template #title>
<div class="page-header-title-bar">
<slot name="title">
<slot name="title-start-slot"></slot>
<div class="page-header-title">
<!-- <div class="icon" v-if="showIcon">
<Icon :name="route.meta.icon" />
</div> -->
<div class="type">
{{ typeObj[$attrs.type] || "" }}
</div>
<div class="title">{{ props.title || pageInfo.name }}</div>
</div>
<slot name="title-end-slot"></slot>
</slot>
</div>
</template>
<template #extra>
<!-- 前插槽 -->
<slot name="btn-start-slot"></slot>
<a-button
type="primary"
class="main-color"
@click="onSubmit"
v-if="showSubmit"
:loading="props.loading"
>提交</a-button
>
<a-button
type="primary"
class="main-color"
@click="onSubmitAndCreate"
:loading="props.loading"
v-if="showSubmitAndCreate"
>提交并继续新建</a-button
>
<a-button @click="onCancel">{{ $attrs.cancelText || "取消" }}</a-button>
<!-- 后插槽 -->
<slot name="btn-end-slot"></slot>
</template>
</a-page-header>
</template>
<style lang="scss" scoped>
._modal-header {
padding: 0;
height: 54px;
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid rgba(229, 233, 242, 1);
.page-header-title-bar {
display: flex;
align-items: center;
padding-left: 28px;
position: relative;
&::after {
content: " ";
position: absolute;
left: 0px;
top: 50%;
transform: translateY(-50%);
width: 4px;
height: 32px;
// background: $primary-color-1;
}
.page-header-title {
display: flex;
align-items: center;
.type {
margin-right: 4px;
@include module-title-font;
}
.title {
@include module-title-font;
}
}
}
:deep(.ant-page-header-heading-extra) {
padding-left: 24px;
}
}
.ant-page-header {
// background-color: red !important;
// background-color: red;
:deep(.ant-page-header-heading) {
height: 100%;
width: 100%;
padding-right: 24px;
box-sizing: border-box;
margin: 0px;
}
:deep(.ant-page-header-extra) {
margin: 0px;
display: flex;
align-items: center;
}
}
:deep(.ant-btn) {
padding: 7px 16px;
height: 38px;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
}
</style>

View File

@ -0,0 +1,187 @@
<script lang="ts">
import { defineComponent, defineAsyncComponent } from "vue";
export default defineComponent({
name: "CommonModal",
});
</script>
<script lang="ts" setup>
import { ref, watch } from "vue";
import Icon from "@/components/Module/Icon/index.vue";
const emits = defineEmits(["update:open"]);
const asyncContent = defineAsyncComponent({
loader: () => import("@/components/Module/AsyncContent/index.vue"),
delay: 2000,
});
interface propsType {
open: boolean;
title?: string | null;
zIndex?: number | string;
loading?: boolean;
containDom?: any; // dom
showConfirm?: boolean;
showCancel?: boolean;
confirmText?: string;
cancelText?: string;
onConfirm?: null | (() => void | Promise<void>);
onCancel?: null | (() => void | Promise<void>);
}
let props = withDefaults(defineProps<propsType>(), {
//
open: false,
title: null,
zIndex: 1000,
loading: false,
confirmText: "确定",
cancelText: "取消",
onConfirm: null,
onCancel: null,
showConfirm: true,
showCancel: true,
});
const useOpen = ref<boolean>(props.open);
const onConfirmHandle = () => {
if (props.onConfirm) {
props.onConfirm();
} else {
emits("update:open", false);
useOpen.value = false;
}
};
const onCancel = () => {
if (props.onCancel) {
props.onCancel();
} else {
emits("update:open", false);
useOpen.value = false;
}
};
watch(
() => props.open,
(val) => {
useOpen.value = val;
},
);
let globalModalRef = ref<HTMLElement>();
const prefix = "_global-modal";
</script>
<template>
<div ref="globalModalRef" :class="prefix">
<a-modal
:zIndex="props.zIndex"
v-bind="$attrs"
:getContainer="() => props.containDom || globalModalRef"
:closable="false"
v-model:open="useOpen"
:footer="null"
@cancel="onCancel"
>
<slot name="header">
<div :class="`${prefix}-head`">
<div :class="`${prefix}-head-label`">
{{ props.title }}
</div>
<button :class="`${prefix}-head-close`" @click="onCancel">
<Icon name="close" />
</button>
</div>
</slot>
<Suspense>
<asyncContent>
<a-spin :spinning="props.loading">
<div :class="`${prefix}-container`">
<div class="modal-content-center">
<asyncContent>
<slot></slot>
</asyncContent>
</div>
</div>
</a-spin>
<slot name="footer">
<div :class="`${prefix}-footer`">
<div :class="`${prefix}-footer__grid`" v-if="props.showCancel">
<a-button @click="onCancel">{{ cancelText }}</a-button>
</div>
<div :class="`${prefix}-footer__grid`" v-if="props.showConfirm">
<a-button type="primary" @click="onConfirmHandle" :loading="props.loading">{{
confirmText
}}</a-button>
</div>
</div>
</slot>
</asyncContent>
<template #fallback>
<div class="modal-content load-box">
<a-spin />
</div>
</template>
</Suspense>
</a-modal>
</div>
</template>
<style lang="scss" scoped>
$prefix: "_global-modal";
.#{$prefix} {
&-head {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
border-radius: 16px 16px 0 0;
background-color: #d9d9d9;
margin-bottom: 20px;
&-close {
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
font-weight: 500;
color: #333;
}
}
&-container {
padding: 0 16px;
width: 100%;
flex: 1;
align-self: stretch;
}
&-footer {
padding: 12px 16px;
width: 100%;
display: flex;
align-items: center;
justify-content: end;
gap: 20px;
}
:deep(.ant-modal-content) {
padding: 0;
.ant-modal-body {
padding: 0;
display: flex;
align-items: start;
justify-content: start;
flex-direction: column;
}
}
:deep(.ant-spin-nested-loading) {
width: 100%;
}
}
</style>

View File

@ -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')
})
})

8
src/config/index.ts Normal file
View File

@ -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
}

115
src/core/lazy_use.ts Normal file
View File

@ -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<Element>) {
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<Element>) {
}

8
src/directive/index.ts Normal file
View File

@ -0,0 +1,8 @@
import type { App } from 'vue'
function directive(app: App<Element>) {
// 注册指令
}
export default directive;

36
src/main.ts Normal file
View File

@ -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')

40
src/router/base/index.ts Normal file
View File

@ -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'),
},
]
},
]

View File

@ -0,0 +1,61 @@
import type {
RouteRecordRaw,
RouteMeta
} from 'vue-router'
import type { defineComponent } from 'vue'
export type Component<T = any> = ReturnType<typeof defineComponent> | (() => Promise<typeof import('*.vue')>) | (() => Promise<T>)
// @ts-expect-error
export interface AppRouter extends Omit<RouteRecordRaw, 'meta'> {
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 // 全路径
// }

70
src/router/func/index.ts Normal file
View File

@ -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<AppRouter[]> => {
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
}

View File

@ -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(),
])
}

90
src/router/guard/index.ts Normal file
View File

@ -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()
// }
}
// }
})
}

17
src/router/index.ts Normal file
View File

@ -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<Element>) {
app.use(router)
}

View File

@ -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')),
}

View File

@ -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,
},
}
]

12
src/stores/counter.ts Normal file
View File

@ -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 }
})

10
src/stores/index.ts Normal file
View File

@ -0,0 +1,10 @@
import type { App } from 'vue'
import { createPinia } from 'pinia'
const store = createPinia()
export function setupStore(app: App<Element>) {
app.use(store)
}
export { store }

View File

@ -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<AppRouter[]> {
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)
}

View File

@ -0,0 +1,6 @@
export const ACCESS_TOKEN = 'token'
export const ACCESS_EXPIRES = 'expires'
export const APP_VERSION = 'app_version'

View File

@ -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)
}

View File

@ -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)
}

103
src/stores/modules/user.ts Normal file
View File

@ -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)
}

30
src/styles/index.css Normal file
View File

@ -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;
/* 保持指针样式 */
}

34
src/styles/index.scss Normal file
View File

@ -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; /* 保持指针样式 */
}

View File

@ -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": {}
}
}

37
src/types/global.d.ts vendored Normal file
View File

@ -0,0 +1,37 @@
import type { DefineComponent } from 'vue'
declare global {
type ExtraObj = {
[key?: string]: any
}
type Recordable<T = any> = Record<string, T>
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/*';

37
src/types/module.d.ts vendored Normal file
View File

@ -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';

View File

187
src/utils/download/index.ts Normal file
View File

@ -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() });
}

106
src/utils/request/index.ts Normal file
View File

@ -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<ResponseData | ResponseError> => {
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;
}

View File

@ -0,0 +1,43 @@
export interface Config {
base?: string, // 前缀
url: string // 请求地址
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
data?: string | Record<string, any> // 请求体
dataRaw?: boolean // 请求体是否为原始数据
params?: string | Record<string, any> | URLSearchParams | string[][] // 请求参数
headers?: HeadersInit | undefined // 请求头
controller?: AbortController // 请求控制器
getRaw?: boolean // 是否需要返回原始数据
}
// 请求头
export interface RequestHead {
[key: string]: string
}
// 标准响应
export interface ResponseData<T = any> {
code: number
data: T
[key: string]: any
}
// 错误响应
export interface ResponseError<T = any> {
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
}

Some files were not shown because too many files have changed in this diff Show More