diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 00000000..4c7f8a8e
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,11 @@
+# 🎨 editorconfig.org
+
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+indent_style = space
+indent_size = 2
+trim_trailing_whitespace = true
+insert_final_newline = true
\ No newline at end of file
diff --git a/.env.development b/.env.development
new file mode 100644
index 00000000..e4a07bd0
--- /dev/null
+++ b/.env.development
@@ -0,0 +1,17 @@
+# 历史路径-哈希带井号标识
+VITE_HISTORY_HASH = false
+
+# 历史路径-前缀URL如:/h5
+VITE_HISTORY_BASE_URL = /
+
+# 应用名称
+VITE_APP_NAME = Mask管理系统
+
+# 应用标识
+VITE_APP_CODE = maskAntd
+
+# 应用版本
+VITE_APP_VERSION = '0.2.1'
+
+# 接口基础URL地址-不带/后缀
+VITE_API_BASE_URL = /dev-api
diff --git a/.env.production b/.env.production
new file mode 100644
index 00000000..cb4095cd
--- /dev/null
+++ b/.env.production
@@ -0,0 +1,19 @@
+# 历史路径-哈希带井号标识
+VITE_HISTORY_HASH = true
+
+# 历史路径-前缀URL如:/h5
+VITE_HISTORY_BASE_URL = /mask-antd
+
+# 应用名称
+VITE_APP_NAME = Mask管理系统
+
+# 应用标识
+VITE_APP_CODE = maskAntd
+
+# 应用版本
+VITE_APP_VERSION = '0.2.1'
+
+# 接口基础URL地址-不带/后缀
+# VITE_API_BASE_URL = https://mock.apifox.cn/m1/1551143-0-default
+VITE_API_BASE_URL = http://124.223.91.248:8102/prod-api
+# VITE_API_BASE_URL = http://192.168.56.1:6275
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..a2583a80
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,33 @@
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+*.code-workspace
+
+# Local History for Visual Studio Code
+.history/
+
+.DS_Store
+node_modules/
+dist/
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+**/*.log
+
+tests/**/coverage/
+tests/e2e/reports
+selenium-debug.log
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.local
+
+package-lock.json
+yarn.lock
diff --git a/.prettierrc.json b/.prettierrc.json
new file mode 100644
index 00000000..41e79eac
--- /dev/null
+++ b/.prettierrc.json
@@ -0,0 +1,11 @@
+{
+ "printWidth": 80,
+ "tabWidth": 2,
+ "useTabs": false,
+ "singleQuote": true,
+ "semi": true,
+ "trailingComma": "es5",
+ "bracketSpacing": true,
+ "jsxBracketSameLine": false,
+ "arrowParens": "avoid"
+}
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..59631a78
--- /dev/null
+++ b/README.md
@@ -0,0 +1,18 @@
+# 基于 Ant-Design-Vue + Vue3 的管理系统
+
+[](https://gitee.com/TsMask/mask_antd_vue3/stargazers)
+
+
+
+
+
+## 简介
+
+该项目选择 [RuoYi-Vue3](https://github.com/yangzongzhuan/RuoYi-Vue3) 进行功能适配。
+
+- 系统布局使用 [@ant-design-vue/pro-layout](https://github.com/vueComponent/pro-components)
+- 图标来源 [@ant-design/icons-vue](https://ant.design/components/icon)
+- 菜单图标使用自定义iconfont `font_8d5l8fzk5b87iudi.js`图标文件
+
+> 有任何问题或者建议,可以在 [_Issues_](https://gitee.com/TsMask/mask_api_midwayjs/issues) 或通过QQ群:[_57242844_](https://jq.qq.com/?_wv=1027&k=z6Y4YQcB) 提出想法。
+> 如果觉得项目对您有帮助,可以来个Star ⭐
diff --git a/index.html b/index.html
new file mode 100644
index 00000000..1369a698
--- /dev/null
+++ b/index.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/init.md b/init.md
deleted file mode 100644
index b5754e20..00000000
--- a/init.md
+++ /dev/null
@@ -1 +0,0 @@
-ok
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 00000000..28145b4b
--- /dev/null
+++ b/package.json
@@ -0,0 +1,42 @@
+{
+ "name": "ems_frontend_vue3",
+ "type": "module",
+ "description": "核心网管理系统",
+ "author": "TsMask",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "scripts": {
+ "dev": "vite",
+ "build": "vue-tsc && vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@ant-design-vue/pro-layout": "^3.2.4",
+ "@ant-design/icons-vue": "^6.1.0",
+ "ant-design-vue": "^3.2.20",
+ "dayjs": "^1.11.8",
+ "echarts": "^5.4.2",
+ "file-saver": "^2.0.5",
+ "js-base64": "^3.7.5",
+ "js-cookie": "^3.0.5",
+ "nprogress": "^0.2.0",
+ "pinia": "^2.1.4",
+ "vue": "^3.3.4",
+ "vue-router": "^4.2.2"
+ },
+ "devDependencies": {
+ "@types/file-saver": "^2.0.5",
+ "@types/js-cookie": "^3.0.3",
+ "@types/node": "^18.0.0",
+ "@types/nprogress": "^0.2.0",
+ "@vitejs/plugin-vue": "^4.2.3",
+ "less": "^4.1.3",
+ "typescript": "^5.1.3",
+ "unplugin-vue-components": "^0.25.1",
+ "vite": "^4.3.9",
+ "vite-plugin-compression": "^0.5.1",
+ "vue-i18n": "^9.3.0-beta.27",
+ "vue-tsc": "^1.8.0"
+ }
+}
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 00000000..4814a3f3
Binary files /dev/null and b/public/favicon.ico differ
diff --git a/public/font_8d5l8fzk5b87iudi.js b/public/font_8d5l8fzk5b87iudi.js
new file mode 100644
index 00000000..699f245c
--- /dev/null
+++ b/public/font_8d5l8fzk5b87iudi.js
@@ -0,0 +1 @@
+(function(window){var svgSprite=""+""+''+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+" "+""+'"+""+''+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+' '+""+' '+""+' '+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+' '+""+' '+""+' '+""+' '+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+' '+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+' '+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+' '+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+' '+""+' '+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+' '+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+' '+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+' '+""+' '+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+' '+""+' '+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+' '+""+' '+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+' '+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+' '+""+' '+""+' '+""+' '+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+' '+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+' '+""+' '+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+' '+""+' '+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+' '+""+' '+""+' '+""+' '+""+' '+""+' '+""+' '+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+' '+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+' '+""+' '+""+' '+""+' '+""+' '+""+" "+""+''+""+' '+""+" "+""+''+""+' '+""+' '+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+" "+""+''+""+' '+""+' '+""+" "+""+" ";var script=function(){var scripts=document.getElementsByTagName("script");return scripts[scripts.length-1]}();var shouldInjectCss=script.getAttribute("data-injectcss");var ready=function(fn){if(document.addEventListener){if(~["complete","loaded","interactive"].indexOf(document.readyState)){setTimeout(fn,0)}else{var loadFn=function(){document.removeEventListener("DOMContentLoaded",loadFn,false);fn()};document.addEventListener("DOMContentLoaded",loadFn,false)}}else if(document.attachEvent){IEContentLoaded(window,fn)}function IEContentLoaded(w,fn){var d=w.document,done=false,init=function(){if(!done){done=true;fn()}};var polling=function(){try{d.documentElement.doScroll("left")}catch(e){setTimeout(polling,50);return}init()};polling();d.onreadystatechange=function(){if(d.readyState=="complete"){d.onreadystatechange=null;init()}}}};var before=function(el,target){target.parentNode.insertBefore(el,target)};var prepend=function(el,target){if(target.firstChild){before(el,target.firstChild)}else{target.appendChild(el)}};function appendSvg(){var div,svg;div=document.createElement("div");div.innerHTML=svgSprite;svgSprite=null;svg=div.getElementsByTagName("svg")[0];if(svg){svg.setAttribute("aria-hidden","true");svg.style.position="absolute";svg.style.width=0;svg.style.height=0;svg.style.overflow="hidden";prepend(svg,document.body)}}if(shouldInjectCss&&!window.__iconfont__svg__cssinject__){window.__iconfont__svg__cssinject__=true;try{document.write("")}catch(e){console&&console.log(e)}}ready(appendSvg)})(window)
\ No newline at end of file
diff --git a/public/loading.js b/public/loading.js
new file mode 100644
index 00000000..36664037
--- /dev/null
+++ b/public/loading.js
@@ -0,0 +1,205 @@
+/**
+ * loading 占位
+ * 解决首次加载时白屏的问题
+ */
+(function () {
+ const _app = document.querySelector('#app');
+ if (_app && _app.innerHTML === '') {
+ const styleStr = `
+ `;
+
+ let loadInfo = {
+ title: '正在加载资源',
+ titleSub: '初次加载资源可能需要较多时间',
+ msg: '请耐心等待',
+ };
+ document.title = "管理系统";
+
+ // 判断选择语言
+ const lang = localStorage.getItem('cache:local:i18n') || 'zh_CN';
+ if (lang === 'en_US') {
+ loadInfo = {
+ title: 'Loading Resources',
+ titleSub: 'Loading resources for the first time may take a lot of time',
+ msg: 'Please be patient',
+ };
+ document.title = "Managerial System";
+ }
+
+ const divStr = `
+
+
+
+ ${loadInfo.title}
+
+
+ ${loadInfo.titleSub} ${loadInfo.msg}
+
+
`;
+
+ _app.innerHTML = styleStr + divStr;
+ }
+})();
diff --git a/src/App.vue b/src/App.vue
new file mode 100644
index 00000000..98d5fd18
--- /dev/null
+++ b/src/App.vue
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/api/login.ts b/src/api/login.ts
new file mode 100644
index 00000000..225c9327
--- /dev/null
+++ b/src/api/login.ts
@@ -0,0 +1,59 @@
+import { request } from '@/plugins/http-fetch';
+
+// 登录方法
+export function login(data: Record) {
+ return request({
+ url: '/login',
+ method: 'post',
+ data: data,
+ whithToken: false,
+ });
+}
+
+/**
+ * 注册方法
+ * @param data 注册对象
+ * @returns object
+ */
+export function register(data: Record) {
+ return request({
+ url: '/register',
+ method: 'post',
+ data: data,
+ whithToken: false,
+ });
+}
+
+/**
+ * 获取用户详细信息
+ * @returns object
+ */
+export function getInfo() {
+ return request({
+ url: '/getInfo',
+ method: 'get',
+ });
+}
+
+/**
+ * 退出方法
+ * @returns object
+ */
+export function logout() {
+ return request({
+ url: '/logout',
+ method: 'post',
+ });
+}
+
+/**
+ * 获取验证码
+ * @returns object
+ */
+export function getCaptchaImage() {
+ return request({
+ url: '/captchaImage',
+ method: 'get',
+ whithToken: false,
+ });
+}
diff --git a/src/api/monitor/cache.ts b/src/api/monitor/cache.ts
new file mode 100644
index 00000000..b5fc7479
--- /dev/null
+++ b/src/api/monitor/cache.ts
@@ -0,0 +1,86 @@
+import { request } from '@/plugins/http-fetch';
+
+/**
+ * 查询缓存详细
+ * @returns object
+ */
+export function getCache() {
+ return request({
+ url: '/monitor/cache',
+ method: 'get',
+ });
+}
+
+/**
+ * 查询缓存名称列表
+ * @returns object
+ */
+export function listCacheName() {
+ return request({
+ url: '/monitor/cache/getNames',
+ method: 'get',
+ });
+}
+
+/**
+ * 查询缓存名称下键名列表
+ * @param cacheName 缓存名称列表中得到的缓存名称
+ * @returns object
+ */
+export function listCacheKey(cacheName: string) {
+ return request({
+ url: `/monitor/cache/getKeys/${cacheName}`,
+ method: 'get',
+ });
+}
+
+/**
+ * 查询缓存内容
+ * @param cacheName 键名列表中得到的缓存名称
+ * @param cacheKey 键名列表中得到的缓存键名
+ * @returns object
+ */
+export function getCacheValue(cacheName: string, cacheKey: string) {
+ return request({
+ url: `/monitor/cache/getValue/${cacheName}/${cacheKey}`,
+ method: 'get',
+ });
+}
+
+/**
+ * 删除缓存名称下键名列表
+ * @param cacheName 缓存名称列表中得到的缓存名称
+ * @returns object
+ */
+export function clearCacheName(cacheName: string) {
+ return request({
+ url: `/monitor/cache/clearCacheName/${cacheName}`,
+ method: 'delete',
+ });
+}
+
+/**
+ * 删除缓存键名
+ * @param cacheName 键名列表中得到的缓存名称
+ * @param cacheKey 键名列表中得到的缓存键名
+ * @returns object
+ */
+export function clearCacheKey(cacheName: string, cacheKey: string) {
+ return request({
+ url: `/monitor/cache/clearCacheKey/${cacheName}/${cacheKey}`,
+ method: 'delete',
+ });
+}
+
+/**
+ * 安全清理缓存名称
+ *
+ * 指定可清理的缓存key
+ * @returns object
+ */
+export function clearCacheSafe() {
+ return request({
+ url: '/monitor/cache/clearCacheSafe',
+ method: 'delete',
+ });
+}
diff --git a/src/api/monitor/job.ts b/src/api/monitor/job.ts
new file mode 100644
index 00000000..09ec7d5b
--- /dev/null
+++ b/src/api/monitor/job.ts
@@ -0,0 +1,121 @@
+import { request } from '@/plugins/http-fetch';
+
+/**
+ * 定时任务调度列表导出
+ * @param query 查询参数
+ * @returns bolb
+ */
+export function exportJob(query: Record) {
+ return request({
+ url: '/monitor/job/export',
+ method: 'post',
+ data: query,
+ responseType: 'blob',
+ });
+}
+
+/**
+ * 查询定时任务调度列表
+ * @param query 查询参数
+ * @returns object
+ */
+export function listJob(query: Record) {
+ return request({
+ url: '/monitor/job/list',
+ method: 'get',
+ params: query,
+ });
+}
+
+/**
+ * 查询定时任务调度详细
+ * @param jobId 任务ID
+ * @returns object
+ */
+export function getJob(jobId: string | number) {
+ return request({
+ url: `/monitor/job/${jobId}`,
+ method: 'get',
+ });
+}
+
+/**
+ * 新增定时任务调度
+ * @param data 任务对象
+ * @returns object
+ */
+export function addJob(data: Record) {
+ return request({
+ url: '/monitor/job',
+ method: 'post',
+ data: data,
+ });
+}
+
+/**
+ * 修改定时任务调度
+ * @param data 任务对象
+ * @returns object
+ */
+export function updateJob(data: Record) {
+ return request({
+ url: '/monitor/job',
+ method: 'put',
+ data: data,
+ });
+}
+
+/**
+ * 删除定时任务调度
+ * @param jobId 任务ID
+ * @returns object
+ */
+export function delJob(jobId: string | number) {
+ return request({
+ url: `/monitor/job/${jobId}`,
+ method: 'delete',
+ });
+}
+
+/**
+ * 任务状态修改
+ * @param jobId 任务ID
+ * @param status 变更状态值
+ * @returns
+ */
+export function changeJobStatus(
+ jobId: string | number,
+ status: string | number
+) {
+ return request({
+ url: '/monitor/job/changeStatus',
+ method: 'put',
+ data: {
+ jobId,
+ status,
+ },
+ });
+}
+
+/**
+ * 定时任务立即执行一次
+ * @param jobId 任务ID
+ * @returns object
+ */
+export function runJob(jobId: string) {
+ return request({
+ url: `/monitor/job/run/${jobId}`,
+ method: 'put',
+ });
+}
+
+/**
+ * 重置刷新队列
+ * @returns object
+ */
+export function resetQueueJob() {
+ return request({
+ url: '/monitor/job/resetQueueJob',
+ method: 'put',
+ });
+}
diff --git a/src/api/monitor/jobLog.ts b/src/api/monitor/jobLog.ts
new file mode 100644
index 00000000..ade7b771
--- /dev/null
+++ b/src/api/monitor/jobLog.ts
@@ -0,0 +1,53 @@
+import { request } from '@/plugins/http-fetch';
+
+/**
+ * 定时任务调度日志列表导出
+ * @param query 查询参数
+ * @returns bolb
+ */
+export function exportJobLog(
+ query: Record
+) {
+ return request({
+ url: '/monitor/jobLog/export',
+ method: 'post',
+ data: query,
+ responseType: 'blob',
+ });
+}
+
+/**
+ * 查询调度日志列表
+ * @param query 查询参数
+ * @returns object
+ */
+export function listJobLog(query: Record) {
+ return request({
+ url: '/monitor/jobLog/list',
+ method: 'get',
+ params: query,
+ });
+}
+
+/**
+ * 删除调度日志
+ * @param jobLogId 任务日志Id
+ * @returns object
+ */
+export function delJobLog(jobLogId: string) {
+ return request({
+ url: `/monitor/jobLog/${jobLogId}`,
+ method: 'delete',
+ });
+}
+
+/**
+ * 清空调度日志
+ * @returns object
+ */
+export function cleanJobLog() {
+ return request({
+ url: '/monitor/jobLog/clean',
+ method: 'delete',
+ });
+}
diff --git a/src/api/monitor/logininfor.ts b/src/api/monitor/logininfor.ts
new file mode 100644
index 00000000..8e0388e5
--- /dev/null
+++ b/src/api/monitor/logininfor.ts
@@ -0,0 +1,67 @@
+import { request } from '@/plugins/http-fetch';
+
+/**
+ * 登录日志列表导出
+ * @param query 查询参数
+ * @returns bolb
+ */
+export function exportLogininfor(
+ query: Record
+) {
+ return request({
+ url: '/monitor/logininfor/export',
+ method: 'post',
+ data: query,
+ responseType: 'blob',
+ });
+}
+
+/**
+ * 查询登录日志列表
+ * @param query 查询参数
+ * @returns object
+ */
+export function listLogininfor(
+ query: Record
+) {
+ return request({
+ url: '/monitor/logininfor/list',
+ method: 'get',
+ params: query,
+ });
+}
+
+/**
+ * 删除登录日志
+ * @param infoId 登录日志Id
+ * @returns object
+ */
+export function delLogininfor(infoId: string) {
+ return request({
+ url: `/monitor/logininfor/${infoId}`,
+ method: 'delete',
+ });
+}
+
+/**
+ * 清空登录日志
+ * @returns object
+ */
+export function cleanLogininfor() {
+ return request({
+ url: '/monitor/logininfor/clean',
+ method: 'delete',
+ });
+}
+
+/**
+ * 解锁用户登录状态
+ * @param userName 登录账号
+ * @returns object
+ */
+export function unlockLogininfor(userName: string) {
+ return request({
+ url: `/monitor/logininfor/unlock/${userName}`,
+ method: 'put',
+ });
+}
diff --git a/src/api/monitor/online.ts b/src/api/monitor/online.ts
new file mode 100644
index 00000000..4faa13ea
--- /dev/null
+++ b/src/api/monitor/online.ts
@@ -0,0 +1,26 @@
+import { request } from '@/plugins/http-fetch';
+
+/**
+ * 查询在线用户列表
+ * @param query 查询参数
+ * @returns object
+ */
+export function listOnline(query: Record) {
+ return request({
+ url: '/monitor/online/list',
+ method: 'get',
+ params: query,
+ });
+}
+
+/**
+ * 强退用户
+ * @param tokenId 授权标识
+ * @returns object
+ */
+export function forceLogout(tokenId: string) {
+ return request({
+ url: `/monitor/online/${tokenId}`,
+ method: 'delete',
+ });
+}
diff --git a/src/api/monitor/operlog.ts b/src/api/monitor/operlog.ts
new file mode 100644
index 00000000..04bfb101
--- /dev/null
+++ b/src/api/monitor/operlog.ts
@@ -0,0 +1,55 @@
+import { request } from '@/plugins/http-fetch';
+
+/**
+ * 操作日志列表导出
+ * @param query 查询参数
+ * @returns bolb
+ */
+export function exportOperlog(
+ query: Record
+) {
+ return request({
+ url: '/monitor/operlog/export',
+ method: 'post',
+ data: query,
+ responseType: 'blob',
+ });
+}
+
+/**
+ * 查询操作日志列表
+ * @param query 查询参数
+ * @returns object
+ */
+export function listOperlog(
+ query: Record
+) {
+ return request({
+ url: '/monitor/operlog/list',
+ method: 'get',
+ params: query,
+ });
+}
+
+/**
+ * 删除操作日志
+ * @param operId 操作日志ID
+ * @returns object
+ */
+export function delOperlog(operId: string) {
+ return request({
+ url: `/monitor/operlog/${operId}`,
+ method: 'delete',
+ });
+}
+
+/**
+ * 清空操作日志
+ * @returns object
+ */
+export function cleanOperlog() {
+ return request({
+ url: '/monitor/operlog/clean',
+ method: 'delete',
+ });
+}
diff --git a/src/api/monitor/server.ts b/src/api/monitor/server.ts
new file mode 100644
index 00000000..eea90e35
--- /dev/null
+++ b/src/api/monitor/server.ts
@@ -0,0 +1,9 @@
+import { request } from '@/plugins/http-fetch';
+
+/**获取服务信息 */
+export function getServer() {
+ return request({
+ url: '/monitor/server',
+ method: 'get',
+ });
+}
diff --git a/src/api/profile.ts b/src/api/profile.ts
new file mode 100644
index 00000000..be793283
--- /dev/null
+++ b/src/api/profile.ts
@@ -0,0 +1,56 @@
+import { request } from '@/plugins/http-fetch';
+
+/**
+ * 查询用户个人信息
+ * @returns object
+ */
+export function getUserProfile() {
+ return request({
+ url: '/system/user/profile',
+ method: 'get',
+ });
+}
+
+/**
+ * 修改用户个人信息
+ * @param data 用户对象
+ * @returns object
+ */
+export function updateUserProfile(data: Record) {
+ return request({
+ url: '/system/user/profile',
+ method: 'put',
+ data: data,
+ });
+}
+
+/**
+ * 用户密码重置
+ * @param userId 用户ID
+ * @param status 变更状态值
+ * @returns object
+ */
+export function updateUserPwd(oldPassword: string, newPassword: string) {
+ return request({
+ url: '/system/user/profile/updatePwd',
+ method: 'put',
+ data: {
+ oldPassword,
+ newPassword,
+ },
+ });
+}
+
+/**
+ * 用户头像上传
+ * @param data 表单数据对象
+ * @returns object
+ */
+export function uploadAvatar(data: FormData) {
+ return request({
+ url: '/system/user/profile/avatar',
+ method: 'post',
+ data,
+ dataType: 'form-data',
+ });
+}
diff --git a/src/api/router.ts b/src/api/router.ts
new file mode 100644
index 00000000..a0b810cf
--- /dev/null
+++ b/src/api/router.ts
@@ -0,0 +1,12 @@
+import { request } from '@/plugins/http-fetch';
+
+/**
+ * 获取路由
+ * @returns object
+ */
+export const getRouters = () => {
+ return request({
+ url: '/getRouters',
+ method: 'get',
+ });
+};
diff --git a/src/api/system/config.ts b/src/api/system/config.ts
new file mode 100644
index 00000000..2358eb7c
--- /dev/null
+++ b/src/api/system/config.ts
@@ -0,0 +1,103 @@
+import { request } from '@/plugins/http-fetch';
+
+/**
+ * 参数配置列表导出
+ * @param query 查询参数
+ * @returns bolb
+ */
+export function exportConfig(
+ query: Record
+) {
+ return request({
+ url: '/system/config/export',
+ method: 'post',
+ data: query,
+ responseType: 'blob',
+ });
+}
+
+/**
+ * 查询参数配置列表
+ * @param query 查询参数
+ * @returns object
+ */
+export function listConfig(query: Record) {
+ return request({
+ url: '/system/config/list',
+ method: 'get',
+ params: query,
+ });
+}
+
+/**
+ * 查询参数详细
+ * @param configId 参数配置ID
+ * @returns object
+ */
+export function getConfig(configId: string | number) {
+ return request({
+ url: `/system/config/${configId}`,
+ method: 'get',
+ });
+}
+
+/**
+ * 根据参数键名查询参数值
+ * @param configKey 参数键名
+ * @returns object
+ */
+export function getConfigKey(configKey: string) {
+ return request({
+ url: `/system/config/configKey/${configKey}`,
+ method: 'get',
+ });
+}
+
+/**
+ * 新增参数配置
+ * @param data 参数配置对象
+ * @returns object
+ */
+export function addConfig(data: Record) {
+ return request({
+ url: '/system/config',
+ method: 'post',
+ data: data,
+ });
+}
+
+/**
+ * 修改参数配置
+ * @param data 参数配置对象
+ * @returns object
+ */
+export function updateConfig(data: Record) {
+ return request({
+ url: '/system/config',
+ method: 'put',
+ data: data,
+ });
+}
+
+/**
+ * 删除参数配置
+ * @param configId 参数配置ID
+ * @returns object
+ */
+export function delConfig(configId: string | number) {
+ return request({
+ url: `/system/config/${configId}`,
+ method: 'delete',
+ });
+}
+
+/**
+ * 刷新参数缓存
+ * @returns object
+ */
+export function refreshCache() {
+ return request({
+ url: '/system/config/refreshCache',
+ method: 'put',
+ });
+}
diff --git a/src/api/system/dept.ts b/src/api/system/dept.ts
new file mode 100644
index 00000000..288a16ef
--- /dev/null
+++ b/src/api/system/dept.ts
@@ -0,0 +1,99 @@
+import { request } from '@/plugins/http-fetch';
+
+/**
+ * 查询部门列表
+ * @param query 查询参数
+ * @returns object
+ */
+export function listDept(query: Record) {
+ return request({
+ url: '/system/dept/list',
+ method: 'get',
+ params: query,
+ });
+}
+
+/**
+ * 查询部门列表(排除节点)
+ * @param deptId 部门ID
+ * @returns object
+ */
+export function listDeptExcludeChild(deptId: string | number) {
+ return request({
+ url: `/system/dept/list/exclude/${deptId}`,
+ method: 'get',
+ });
+}
+
+/**
+ * 查询部门详细
+ * @param deptId 部门ID
+ * @returns object
+ */
+export function getDept(deptId: string | number) {
+ return request({
+ url: `/system/dept/${deptId}`,
+ method: 'get',
+ });
+}
+
+/**
+ * 新增部门
+ * @param data 部门对象
+ * @returns object
+ */
+export function addDept(data: Record) {
+ return request({
+ url: '/system/dept',
+ method: 'post',
+ data: data,
+ });
+}
+
+/**
+ * 修改部门
+ * @param data 部门对象
+ * @returns object
+ */
+export function updateDept(data: Record) {
+ return request({
+ url: '/system/dept',
+ method: 'put',
+ data: data,
+ });
+}
+
+/**
+ * 删除部门
+ * @param deptId 部门ID
+ * @returns object
+ */
+export function delDept(deptId: string | number) {
+ return request({
+ url: `/system/dept/${deptId}`,
+ method: 'delete',
+ });
+}
+
+/**
+ * 查询部门下拉树结构
+ * @returns object
+ */
+export function deptTreeSelect() {
+ return request({
+ url: '/system/dept/treeSelect',
+ method: 'get',
+ });
+}
+
+/**
+ * 部门树结构列表(指定角色)
+ * @param roleId 角色ID
+ * @returns object
+ */
+export function roleDeptTreeSelect(roleId: string | number) {
+ return request({
+ url: `/system/dept/roleDeptTreeSelect/${roleId}`,
+ method: 'get',
+ });
+}
diff --git a/src/api/system/dict/data.ts b/src/api/system/dict/data.ts
new file mode 100644
index 00000000..f4fc8df4
--- /dev/null
+++ b/src/api/system/dict/data.ts
@@ -0,0 +1,90 @@
+import { request } from '@/plugins/http-fetch';
+
+/**
+ * 字典数据列表导出
+ * @param query 查询参数
+ * @returns bolb
+ */
+export function exportData(query: Record) {
+ return request({
+ url: '/system/dict/data/export',
+ method: 'post',
+ data: query,
+ responseType: 'blob',
+ });
+}
+
+/**
+ * 查询字典数据列表
+ * @param query 查询值
+ * @returns
+ */
+export function listData(query: Record) {
+ return request({
+ url: '/system/dict/data/list',
+ method: 'get',
+ params: query,
+ });
+}
+
+/**
+ * 查询字典数据详细
+ * @param dictCode 字典代码值
+ * @returns object
+ */
+export function getData(dictCode: string | number) {
+ return request({
+ url: `/system/dict/data/${dictCode}`,
+ method: 'get',
+ });
+}
+
+/**
+ * 新增字典数据
+ * @param data 字典数据对象
+ * @returns object
+ */
+export function addData(data: Record) {
+ return request({
+ url: '/system/dict/data',
+ method: 'post',
+ data: data,
+ });
+}
+
+/**
+ * 修改字典数据
+ * @param data 字典数据对象
+ * @returns object
+ */
+export function updateData(data: Record) {
+ return request({
+ url: '/system/dict/data',
+ method: 'put',
+ data: data,
+ });
+}
+
+/**
+ * 删除字典数据
+ * @param dictCode 字典代码值
+ * @returns object
+ */
+export function delData(dictCode: string | number) {
+ return request({
+ url: `/system/dict/data/${dictCode}`,
+ method: 'delete',
+ });
+}
+
+/**
+ * 字典数据列表(指定字典类型)
+ * @param dictType 字典类型
+ * @returns object
+ */
+export function getDictDataType(dictType: string) {
+ return request({
+ url: `/system/dict/data/type/${dictType}`,
+ method: 'get',
+ });
+}
diff --git a/src/api/system/dict/type.ts b/src/api/system/dict/type.ts
new file mode 100644
index 00000000..040b0329
--- /dev/null
+++ b/src/api/system/dict/type.ts
@@ -0,0 +1,102 @@
+import { request } from '@/plugins/http-fetch';
+
+/**
+ * 字典类型列表导出
+ * @param query 查询参数
+ * @returns bolb
+ */
+export function exportType(query: Record) {
+ return request({
+ url: '/system/dict/type/export',
+ method: 'post',
+ data: query,
+ responseType: 'blob',
+ });
+}
+
+/**
+ * 查询字典类型列表
+ * @param query 查询值
+ * @returns
+ */
+export function listType(query: Record) {
+ return request({
+ url: '/system/dict/type/list',
+ method: 'get',
+ params: query,
+ });
+}
+
+/**
+ * 查询字典类型详细
+ * @param dictId 字典编号
+ * @returns object
+ */
+export function getType(dictId: string | number) {
+ return request({
+ url: `/system/dict/type/${dictId}`,
+ method: 'get',
+ });
+}
+
+/**
+ * 新增字典类型
+ * @param data 字典数据对象
+ * @returns object
+ */
+export function addType(data: Record) {
+ return request({
+ url: '/system/dict/type',
+ method: 'post',
+ data: data,
+ });
+}
+
+/**
+ * 修改字典类型
+ * @param data 字典数据对象
+ * @returns object
+ */
+export function updateType(data: Record) {
+ return request({
+ url: '/system/dict/type',
+ method: 'put',
+ data: data,
+ });
+}
+
+/**
+ * 删除字典类型
+ * @param dictCode 字典代码值
+ * @returns object
+ */
+export function delType(dictId: string | number) {
+ return request({
+ url: `/system/dict/type/${dictId}`,
+ method: 'delete',
+ });
+}
+
+/**
+ * 刷新字典缓存
+ * @param data 字典数据对象
+ * @returns object
+ */
+export function refreshCache() {
+ return request({
+ url: '/system/dict/type/refreshCache',
+ method: 'put',
+ });
+}
+
+/**
+ * 获取字典选择框列表
+ * @param data 字典数据对象
+ * @returns object
+ */
+export function getDictOptionselect() {
+ return request({
+ url: '/system/dict/type/getDictOptionselect',
+ method: 'get',
+ });
+}
diff --git a/src/api/system/menu.ts b/src/api/system/menu.ts
new file mode 100644
index 00000000..4827d770
--- /dev/null
+++ b/src/api/system/menu.ts
@@ -0,0 +1,87 @@
+import { request } from '@/plugins/http-fetch';
+
+/**
+ * 查询菜单列表
+ * @param query 查询参数
+ * @returns object
+ */
+export function listMenu(query?: Record) {
+ return request({
+ url: '/system/menu/list',
+ method: 'get',
+ params: query,
+ });
+}
+
+/**
+ * 查询菜单详细
+ * @param menuId 菜单ID
+ * @returns object
+ */
+export function getMenu(menuId: string | number) {
+ return request({
+ url: `/system/menu/${menuId}`,
+ method: 'get',
+ });
+}
+
+/**
+ * 查询菜单下拉树结构
+ * @returns object
+ */
+export function menuTreeSelect() {
+ return request({
+ url: '/system/menu/treeSelect',
+ method: 'get',
+ });
+}
+
+/**
+ * 根据角色ID查询菜单下拉树结构
+ * @param roleId 角色ID
+ * @returns object
+ */
+export function roleMenuTreeSelect(roleId: string | number) {
+ return request({
+ url: `/system/menu/roleMenuTreeSelect/${roleId}`,
+ method: 'get',
+ });
+}
+
+/**
+ * 新增菜单
+ * @param data 菜单对象
+ * @returns object
+ */
+export function addMenu(data: Record) {
+ return request({
+ url: '/system/menu',
+ method: 'post',
+ data: data,
+ });
+}
+
+/**
+ * 修改菜单
+ * @param data 菜单对象
+ * @returns object
+ */
+export function updateMenu(data: Record) {
+ return request({
+ url: '/system/menu',
+ method: 'put',
+ data: data,
+ });
+}
+
+/**
+ * 删除菜单
+ * @param menuId 菜单ID
+ * @returns object
+ */
+export function delMenu(menuId: string | number) {
+ return request({
+ url: `/system/menu/${menuId}`,
+ method: 'delete',
+ });
+}
diff --git a/src/api/system/notice.ts b/src/api/system/notice.ts
new file mode 100644
index 00000000..d3046fce
--- /dev/null
+++ b/src/api/system/notice.ts
@@ -0,0 +1,64 @@
+import { request } from '@/plugins/http-fetch';
+
+/**
+ * 查询公告列表
+ * @param query 查询参数
+ * @returns object
+ */
+export function listNotice(query: Record) {
+ return request({
+ url: '/system/notice/list',
+ method: 'get',
+ params: query,
+ });
+}
+
+/**
+ * 查询公告详细
+ * @param menuId 公告ID
+ * @returns object
+ */
+export function getNotice(noticeId: string | number) {
+ return request({
+ url: `/system/notice/${noticeId}`,
+ method: 'get',
+ });
+}
+
+/**
+ * 新增公告
+ * @param data 公告对象
+ * @returns object
+ */
+export function addNotice(data: Record) {
+ return request({
+ url: '/system/notice',
+ method: 'post',
+ data: data,
+ });
+}
+
+/**
+ * 修改公告
+ * @param data 公告对象
+ * @returns object
+ */
+export function updateNotice(data: Record) {
+ return request({
+ url: '/system/notice',
+ method: 'put',
+ data: data,
+ });
+}
+
+/**
+ * 删除公告
+ * @param noticeId 公告ID
+ * @returns object
+ */
+export function delNotice(noticeId: string | number) {
+ return request({
+ url: `/system/notice/${noticeId}`,
+ method: 'delete',
+ });
+}
diff --git a/src/api/system/post.ts b/src/api/system/post.ts
new file mode 100644
index 00000000..da63bc92
--- /dev/null
+++ b/src/api/system/post.ts
@@ -0,0 +1,78 @@
+import { request } from '@/plugins/http-fetch';
+
+/**
+ * 岗位列表导出
+ * @param query 查询参数
+ * @returns bolb
+ */
+export function exportPost(query: Record) {
+ return request({
+ url: '/system/post/export',
+ method: 'post',
+ data: query,
+ responseType: 'blob',
+ });
+}
+
+/**
+ * 查询岗位列表
+ * @param query 查询参数
+ * @returns object
+ */
+export function listPost(query: Record) {
+ return request({
+ url: '/system/post/list',
+ method: 'get',
+ params: query,
+ });
+}
+
+/**
+ * 查询岗位详细
+ * @param postId 岗位ID
+ * @returns object
+ */
+export function getPost(postId: string | number) {
+ return request({
+ url: `/system/post/${postId}`,
+ method: 'get',
+ });
+}
+
+/**
+ * 新增岗位
+ * @param data 岗位对象
+ * @returns object
+ */
+export function addPost(data: Record) {
+ return request({
+ url: '/system/post',
+ method: 'post',
+ data: data,
+ });
+}
+
+/**
+ * 修改岗位
+ * @param data 岗位对象
+ * @returns object
+ */
+export function updatePost(data: Record) {
+ return request({
+ url: '/system/post',
+ method: 'put',
+ data: data,
+ });
+}
+
+/**
+ * 删除岗位
+ * @param postId 岗位ID
+ * @returns object
+ */
+export function delPost(postId: string | number) {
+ return request({
+ url: `/system/post/${postId}`,
+ method: 'delete',
+ });
+}
diff --git a/src/api/system/role.ts b/src/api/system/role.ts
new file mode 100644
index 00000000..2f426836
--- /dev/null
+++ b/src/api/system/role.ts
@@ -0,0 +1,134 @@
+import { request } from '@/plugins/http-fetch';
+
+/**
+ * 角色列表导出
+ * @param query 查询参数
+ * @returns bolb
+ */
+export function exportRole(query: Record) {
+ return request({
+ url: '/system/role/export',
+ method: 'post',
+ data: query,
+ responseType: 'blob',
+ });
+}
+
+/**
+ * 查询角色列表
+ * @param query 查询参数
+ * @returns object
+ */
+export function listRole(query: Record) {
+ return request({
+ url: '/system/role/list',
+ method: 'get',
+ params: query,
+ });
+}
+
+/**
+ * 查询角色详细
+ * @param roleId 角色ID
+ * @returns object
+ */
+export function getRole(roleId: string | number) {
+ return request({
+ url: `/system/role/${roleId}`,
+ method: 'get',
+ });
+}
+
+/**
+ * 新增角色
+ * @param data 角色对象
+ * @returns object
+ */
+export function addRole(data: Record) {
+ return request({
+ url: '/system/role',
+ method: 'post',
+ data: data,
+ });
+}
+
+/**
+ * 修改角色
+ * @param data 角色对象
+ * @returns object
+ */
+export function updateRole(data: Record) {
+ return request({
+ url: '/system/role',
+ method: 'put',
+ data: data,
+ });
+}
+
+/**
+ * 删除角色
+ * @param roleId 角色ID
+ * @returns object
+ */
+export function delRole(roleId: string | number) {
+ return request({
+ url: `/system/role/${roleId}`,
+ method: 'delete',
+ });
+}
+
+/**
+ * 角色状态修改
+ * @param roleId 角色ID
+ * @param status 角色状态
+ * @returns object
+ */
+export function changeRoleStatus(roleId: string, status: string | number) {
+ return request({
+ url: '/system/role/changeStatus',
+ method: 'put',
+ data: {
+ roleId,
+ status,
+ },
+ });
+}
+
+/**
+ * 修改角色数据权限
+ * @param data 角色对象
+ * @returns object
+ */
+export function dataScope(data: Record) {
+ return request({
+ url: '/system/role/dataScope',
+ method: 'put',
+ data: data,
+ });
+}
+
+/**
+ * 角色分配用户列表
+ * @param query 查询参数
+ * @returns object
+ */
+export function authUserAllocatedList(query: Record) {
+ return request({
+ url: '/system/role/authUser/allocatedList',
+ method: 'get',
+ params: query,
+ });
+}
+
+/**
+ * 角色分配选择授权
+ * @param data 角色对象
+ * @returns object
+ */
+export function authUserChecked(data: Record) {
+ return request({
+ url: '/system/role/authUser/checked',
+ method: 'put',
+ data: data,
+ });
+}
diff --git a/src/api/system/user.ts b/src/api/system/user.ts
new file mode 100644
index 00000000..febcce9e
--- /dev/null
+++ b/src/api/system/user.ts
@@ -0,0 +1,141 @@
+import { request } from '@/plugins/http-fetch';
+
+/**
+ * 导入用户模板数据
+ * @param data 表单数据对象
+ * @returns object
+ */
+export function importData(data: FormData) {
+ return request({
+ url: '/system/user/importData',
+ method: 'post',
+ data,
+ dataType: 'form-data',
+ });
+}
+
+/**
+ * 导入用户模板下载
+ * @returns bolb
+ */
+export function importTemplate() {
+ return request({
+ url: '/system/user/importTemplate',
+ method: 'get',
+ responseType: 'blob',
+ });
+}
+
+/**
+ * 用户列表导出
+ * @param query 查询参数
+ * @returns bolb
+ */
+export function exportUser(query: Record) {
+ return request({
+ url: '/system/user/export',
+ method: 'post',
+ data: query,
+ responseType: 'blob',
+ });
+}
+
+/**
+ * 查询用户列表
+ * @param query 查询参数
+ * @returns object
+ */
+export function listUser(query: Record) {
+ return request({
+ url: '/system/user/list',
+ method: 'get',
+ params: query,
+ });
+}
+
+/**
+ * 查询用户详细
+ * @param userId 用户ID,新增0
+ * @returns object
+ */
+export function getUser(userId: string | number = '0') {
+ return request({
+ url: `/system/user/${userId}`,
+ method: 'get',
+ });
+}
+
+/**
+ * 新增用户
+ * @param data 用户对象
+ * @returns object
+ */
+export function addUser(data: Record) {
+ return request({
+ url: '/system/user',
+ method: 'post',
+ data: data,
+ });
+}
+
+/**
+ * 修改用户
+ * @param data 用户对象
+ * @returns object
+ */
+export function updateUser(data: Record) {
+ return request({
+ url: '/system/user',
+ method: 'put',
+ data: data,
+ });
+}
+
+/**
+ * 删除用户
+ * @param userId 用户ID
+ * @returns object
+ */
+export function delUser(userId: string | number) {
+ return request({
+ url: `/system/user/${userId}`,
+ method: 'delete',
+ });
+}
+
+/**
+ * 用户密码重置
+ * @param userId 用户ID
+ * @param password 密码
+ * @returns object
+ */
+export function resetUserPwd(userId: string | number, password: string) {
+ return request({
+ url: '/system/user/resetPwd',
+ method: 'put',
+ data: {
+ userId,
+ password,
+ },
+ });
+}
+
+/**
+ * 用户状态修改
+ * @param userId 用户ID
+ * @param status 变更状态值
+ * @returns object
+ */
+export function changeUserStatus(
+ userId: string | number,
+ status: string | number
+) {
+ return request({
+ url: '/system/user/changeStatus',
+ method: 'put',
+ data: {
+ userId,
+ status,
+ },
+ });
+}
diff --git a/src/api/tool/file.ts b/src/api/tool/file.ts
new file mode 100644
index 00000000..6d454b17
--- /dev/null
+++ b/src/api/tool/file.ts
@@ -0,0 +1,195 @@
+import { request } from '@/plugins/http-fetch';
+import { encode } from 'js-base64';
+
+/**
+ * 下载文件
+ * @param filePath 文件路径带/,如:/upload/default/2023/06/xx.png
+ * @param range 断点续传标识,填入字符串 `bytes=${startByte}-${endByte}`
+ * @returns object
+ */
+export async function downloadFile(filePath: string, range?: string) {
+ return request({
+ url: `/file/download/${encode(filePath)}`,
+ method: 'get',
+ headers: range ? { range } : {},
+ responseType: 'blob',
+ });
+}
+
+/**
+ * 下载文件切片
+ * @param filePath 文件路径带/,如:/upload/default/2023/06/xx.png
+ * @param chunkSize 数据块大小MB,默认1MB
+ * @returns bolb
+ */
+export async function downloadFileChunk(
+ filePath: string,
+ chunkSize: number = 1
+): Promise {
+ chunkSize = chunkSize * 1024 * 1024;
+ let start = 0; // 文件块的起始字节
+ let end = chunkSize - 1; // 文件块的结束字节
+ let totalSize = 0; // 文件总大小
+ let downloadedSize = 0; // 已下载的文件大小
+ let filePart: Blob[] = []; // 文件数据块内容
+
+ // 发送带有 Range 请求头的 HTTP 请求
+ async function sendRequest() {
+ const range = `bytes=${start}-${end}`;
+ const res = await downloadFile(filePath, range);
+ if (res.code === 200 && res.status === 206) {
+ // 总大小
+ const contentRange = res.headers.get('content-range') || '0/0';
+ totalSize = parseInt(contentRange.split('/')[1]);
+ // 已下载大小
+ const contentLength = res.headers.get('content-length') || '0';
+ const chunkSize = parseInt(contentLength);
+ // 下一段数据块区间
+ start += chunkSize;
+ end = Math.min(start + chunkSize - 1, totalSize - 1);
+ // 记录下载结果
+ filePart.push(res.data);
+ downloadedSize += chunkSize;
+ // 小于总大小继续下载后续数据
+ if (downloadedSize < totalSize) {
+ await sendRequest();
+ }
+ } else {
+ return res;
+ }
+ }
+
+ await sendRequest();
+ return new Blob(filePart, { type: 'application/octet-stream' });
+}
+
+/**
+ * 上传文件
+ * @param data 表单数据对象
+ * @returns object
+ */
+export function uploadFile(data: FormData) {
+ return request({
+ url: '/file/upload',
+ method: 'post',
+ data,
+ dataType: 'form-data',
+ });
+}
+
+/**
+ * 上传切片文件
+ * @param file 文件对象
+ * @param chunkSize 数据块大小MB,默认1MB
+ * @param subPath 归属子路径, 默认default
+ * @returns
+ */
+export async function uploadFileChunk(
+ fileData: File,
+ chunkSize: number = 1,
+ subPath: string = 'default'
+) {
+ const { name, size } = fileData;
+ const chunkSizeInBytes = chunkSize * 1024 * 1024;
+ // 文件标识使用唯一编码 MD5(文件名+文件大小)
+ const fileIdentifier = `${name}-${size}`;
+ // 文件切分为多少份进行上传
+ const chunksCount = Math.ceil(size / chunkSizeInBytes);
+ // 切块的数据数据用于上传
+ const fileChunks: { index: number; chunk: Blob }[] = [];
+
+ for (let i = 0; i < chunksCount; i++) {
+ const start = i * chunkSizeInBytes;
+ const end = Math.min(start + chunkSizeInBytes, size);
+ fileChunks.push({
+ index: i,
+ chunk: fileData.slice(start, end),
+ });
+ }
+
+ // 检查是否已上传部分数据块
+ const resCheck = await chunkCheck(fileIdentifier, name);
+ if (resCheck.code !== 200) {
+ return resCheck;
+ }
+
+ let uploadedSize = 0;
+ let uploadProgress = 0;
+
+ for (const { index, chunk } of fileChunks) {
+ const chunksIndex = `${index}`;
+ // 跳过已上传块
+ if (resCheck.data.includes(chunksIndex)) {
+ continue;
+ }
+
+ // 上传数据块
+ const formData = new FormData();
+ formData.append('file', chunk, name);
+ formData.append('index', chunksIndex);
+ formData.append('identifier', fileIdentifier);
+
+ const resUpload = await chunkUpload(formData);
+ if (resUpload.code === 200) {
+ uploadedSize += chunk.size;
+ uploadProgress = (uploadedSize / size) * 100;
+ console.log(`上传进度:${uploadProgress}%`);
+ } else {
+ // 上传失败处理
+ break;
+ }
+ }
+
+ // 上传数据完整后合并数据块
+ if (uploadedSize === size) {
+ return await chunkMerge(fileIdentifier, name, subPath);
+ }
+ return { code: 500, msg: '上传出错,请重试' };
+}
+
+/**
+ * 切片文件检查
+ * @param identifier 文件标识
+ * @param fileName 原文件名称
+ * @returns object
+ */
+export function chunkCheck(identifier: string, fileName: string) {
+ return request({
+ url: '/file/chunkCheck',
+ method: 'post',
+ data: { identifier, fileName },
+ });
+}
+
+/**
+ * 切片文件合并
+ * @param identifier 文件标识
+ * @param fileName 原文件名称
+ * @param subPath 文件归属
+ * @returns object
+ */
+export function chunkMerge(
+ identifier: string,
+ fileName: string,
+ subPath: string = 'default'
+) {
+ return request({
+ url: '/file/chunkMerge',
+ method: 'post',
+ data: { identifier, fileName, subPath },
+ });
+}
+
+/**
+ * 切片文件上传
+ * @param data 表单数据对象
+ * @returns object
+ */
+export function chunkUpload(data: FormData) {
+ return request({
+ url: '/file/chunkUpload',
+ method: 'post',
+ data,
+ dataType: 'form-data',
+ });
+}
diff --git a/src/assets/background.svg b/src/assets/background.svg
new file mode 100644
index 00000000..43adc8a6
--- /dev/null
+++ b/src/assets/background.svg
@@ -0,0 +1,69 @@
+
+
+
+ Ant-Design-Pro
+ mask-and-vue3 By TsMask
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/assets/donate.jpg b/src/assets/donate.jpg
new file mode 100644
index 00000000..6d62f6c4
Binary files /dev/null and b/src/assets/donate.jpg differ
diff --git a/src/assets/images/default_avatar.png b/src/assets/images/default_avatar.png
new file mode 100644
index 00000000..89ae8d75
Binary files /dev/null and b/src/assets/images/default_avatar.png differ
diff --git a/src/assets/js/icon_font_8d5l8fzk5b87iudi.ts b/src/assets/js/icon_font_8d5l8fzk5b87iudi.ts
new file mode 100644
index 00000000..794233df
--- /dev/null
+++ b/src/assets/js/icon_font_8d5l8fzk5b87iudi.ts
@@ -0,0 +1,130 @@
+/**
+ * 字体图标文件-静态资源文件路径
+ */
+const baseUrl = import.meta.env.VITE_HISTORY_BASE_URL;
+export const scriptUrl = `${
+ baseUrl.length === 1 && baseUrl.indexOf('/') === 0
+ ? ''
+ : baseUrl.indexOf('/') === -1
+ ? '/' + baseUrl
+ : baseUrl
+}/font_8d5l8fzk5b87iudi.js`;
+
+/**
+ * 读取 font_8d5l8fzk5b87iudi.js 文件内svg图标名称
+ *
+ * JSON.stringify(txt.match(/icon-(\S*)"/gi).map(i=>i.slice(0,-1)))
+ */
+export const iconFonts = [
+ '#',
+ 'icon-alibaba',
+ 'icon-alimama',
+ 'icon-aliyun',
+ 'icon-anzhuo',
+ 'icon-biaoqing',
+ 'icon-chexiao',
+ 'icon-chexiao2',
+ 'icon-daimayingyong',
+ 'icon-daishenhe',
+ 'icon-dashang',
+ 'icon-dianzan',
+ 'icon-dianzan1',
+ 'icon-facebook',
+ 'icon-fangda',
+ 'icon-fangda2',
+ 'icon-fanhui',
+ 'icon-fanhui1',
+ 'icon-fankui1',
+ 'icon-fenxiang',
+ 'icon-fuzhichenggong',
+ 'icon-fuzhidaima',
+ 'icon-fuzhidaima1',
+ 'icon-gengduo',
+ 'icon-gerenzhanghu',
+ 'icon-github',
+ 'icon-gonggao',
+ 'icon-gonggaodayi',
+ 'icon-gongnengjieshao',
+ 'icon-gouwuche',
+ 'icon-gouwuche2',
+ 'icon-guanbi',
+ 'icon-huidingbu',
+ 'icon-huifu',
+ 'icon-huizhiguize',
+ 'icon-iconfont1',
+ 'icon-ios',
+ 'icon-jieshi',
+ 'icon-jinggao',
+ 'icon-lishi',
+ 'icon-morentouxiang',
+ 'icon-paixu',
+ 'icon-pcduan',
+ 'icon-piliang',
+ 'icon-qingchu',
+ 'icon-qq',
+ 'icon-qunzhu',
+ 'icon-right',
+ 'icon-saoyisao',
+ 'icon-shanchu',
+ 'icon-shang',
+ 'icon-shang1',
+ 'icon-shang2',
+ 'icon-shangchuan',
+ 'icon-shenhejujue',
+ 'icon-shenhetongguo',
+ 'icon-shijian',
+ 'icon-shuoming',
+ 'icon-souren',
+ 'icon-sousuo',
+ 'icon-soutubiao',
+ 'icon-suofang',
+ 'icon-suoxiao',
+ 'icon-suoxiao2',
+ 'icon-taobaowang',
+ 'icon-tengxunwang',
+ 'icon-tianjiachengyuan',
+ 'icon-tianmao',
+ 'icon-tubiaohuizhi',
+ 'icon-tubiaoku',
+ 'icon-tuichu',
+ 'icon-twitter',
+ 'icon-weibo',
+ 'icon-weibo1',
+ 'icon-weibo2',
+ 'icon-weijiaru',
+ 'icon-weitijiao',
+ 'icon-weixin',
+ 'icon-wenjian',
+ 'icon-wocanyu',
+ 'icon-wofaqi',
+ 'icon-xia',
+ 'icon-xia2',
+ 'icon-xiangmu',
+ 'icon-xiangmuchengyuan',
+ 'icon-xiangxia',
+ 'icon-xiangxia1',
+ 'icon-xiangxia2',
+ 'icon-xiangyou',
+ 'icon-xiaomi',
+ 'icon-xiazai',
+ 'icon-xinjiantubiaoku',
+ 'icon-yingwen',
+ 'icon-you',
+ 'icon-you1',
+ 'icon-you2',
+ 'icon-youxiang',
+ 'icon-youxuan',
+ 'icon-youxuan2',
+ 'icon-yuzhanghao',
+ 'icon-yuzhanghao1',
+ 'icon-zhifubao',
+ 'icon-zhizuoliucheng',
+ 'icon-zhongguodianxin',
+ 'icon-zhuanrang',
+ 'icon-zhubajie',
+ 'icon-zuo',
+ 'icon-zuo1',
+ 'icon-zuo2',
+ 'icon-zuoxuan',
+ 'icon-zuoxuan2',
+];
diff --git a/src/assets/logo.png b/src/assets/logo.png
new file mode 100644
index 00000000..89ae8d75
Binary files /dev/null and b/src/assets/logo.png differ
diff --git a/src/components/CronModal/components/Day.vue b/src/components/CronModal/components/Day.vue
new file mode 100644
index 00000000..c176ad25
--- /dev/null
+++ b/src/components/CronModal/components/Day.vue
@@ -0,0 +1,141 @@
+
+
+
+
+
+ 每一天
+
+ 每隔
+
+ 天执行一次,从
+
+ 日开始
+
+
+ 周期从
+
+ 到
+
+ 日
+
+ 指定日(可多选)
+
+ 本月最后一天
+
+
+
+
+
diff --git a/src/components/CronModal/components/Hour.vue b/src/components/CronModal/components/Hour.vue
new file mode 100644
index 00000000..9555d3d2
--- /dev/null
+++ b/src/components/CronModal/components/Hour.vue
@@ -0,0 +1,137 @@
+
+
+
+
+
+ 每一小时
+
+ 每隔
+
+ 小时执行一次,从
+
+ 时开始
+
+
+ 周期从
+
+ 到
+
+ 小时
+
+ 指定小时(可多选)
+
+
+
+
+
+
diff --git a/src/components/CronModal/components/Minute.vue b/src/components/CronModal/components/Minute.vue
new file mode 100644
index 00000000..17a6e8c9
--- /dev/null
+++ b/src/components/CronModal/components/Minute.vue
@@ -0,0 +1,139 @@
+
+
+
+
+
+ 每一分钟
+
+ 每隔
+
+ 分钟执行一次,从
+
+ 分钟开始
+
+
+ 周期从
+
+ 到
+
+ 分钟
+
+ 指定分钟(可多选)
+
+
+
+
+
+
diff --git a/src/components/CronModal/components/Month.vue b/src/components/CronModal/components/Month.vue
new file mode 100644
index 00000000..9a782bf3
--- /dev/null
+++ b/src/components/CronModal/components/Month.vue
@@ -0,0 +1,141 @@
+
+
+
+
+
+ 每一月
+
+ 每隔
+
+ 月执行,从
+
+ 月开始
+
+
+ 周期从
+
+ 到
+
+ 月之间的每个月
+
+ 指定月(可多选)
+
+
+
+
+
+
diff --git a/src/components/CronModal/components/Second.vue b/src/components/CronModal/components/Second.vue
new file mode 100644
index 00000000..e4efdf3b
--- /dev/null
+++ b/src/components/CronModal/components/Second.vue
@@ -0,0 +1,138 @@
+
+
+
+
+
+ 每一秒钟
+
+ 每隔
+
+ 秒执行一次,从
+
+ 秒开始
+
+
+ 周期从
+
+ 到
+
+ 秒
+
+ 指定秒数(可多选)
+
+
+
+
+
+
diff --git a/src/components/CronModal/index.vue b/src/components/CronModal/index.vue
new file mode 100644
index 00000000..8d6a723e
--- /dev/null
+++ b/src/components/CronModal/index.vue
@@ -0,0 +1,112 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/DictTag/index.vue b/src/components/DictTag/index.vue
new file mode 100644
index 00000000..a8e8ae03
--- /dev/null
+++ b/src/components/DictTag/index.vue
@@ -0,0 +1,48 @@
+
+
+
+
+
+ {{ item.label }}
+
+
+ {{ item.label }}
+
+
+
+
+
+
diff --git a/src/components/IconFont/index.vue b/src/components/IconFont/index.vue
new file mode 100644
index 00000000..912cd286
--- /dev/null
+++ b/src/components/IconFont/index.vue
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
diff --git a/src/components/LinkiFrame/index.vue b/src/components/LinkiFrame/index.vue
new file mode 100644
index 00000000..483d1cce
--- /dev/null
+++ b/src/components/LinkiFrame/index.vue
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/constants/admin-constants.ts b/src/constants/admin-constants.ts
new file mode 100644
index 00000000..eeca5ec2
--- /dev/null
+++ b/src/constants/admin-constants.ts
@@ -0,0 +1,5 @@
+/**管理员-系统指定角色KEY */
+export const ADMIN_ROLE_KEY = 'admin';
+
+/**管理员-系统指定权限 */
+export const ADMIN_PERMISSION = '*:*:*';
diff --git a/src/constants/app-constants.ts b/src/constants/app-constants.ts
new file mode 100644
index 00000000..cea6637a
--- /dev/null
+++ b/src/constants/app-constants.ts
@@ -0,0 +1,5 @@
+/**应用-请求头-系统标识 */
+export const APP_REQUEST_HEADER_CODE = 'X-App-Code';
+
+/**应用-请求头-系统版本 */
+export const APP_REQUEST_HEADER_VERSION = 'X-App-Version';
diff --git a/src/constants/cache-keys-constants.ts b/src/constants/cache-keys-constants.ts
new file mode 100644
index 00000000..22d7e9a5
--- /dev/null
+++ b/src/constants/cache-keys-constants.ts
@@ -0,0 +1,11 @@
+/**会话缓存-网络请求 */
+export const CACHE_SESSION_FATCH = 'cache:session:fatch';
+
+/**本地缓存-布局设置 */
+export const CACHE_LOCAL_PROCONFIG = 'cache:local:proConfig';
+
+/**本地缓存-主题色 */
+export const CACHE_LOCAL_PRIMARY_COLOR = 'cache:local:primaryColor';
+
+/**本地缓存-多语言 */
+export const CACHE_LOCAL_I18N = 'cache:local:i18n';
diff --git a/src/constants/menu-constants.ts b/src/constants/menu-constants.ts
new file mode 100644
index 00000000..a753ef18
--- /dev/null
+++ b/src/constants/menu-constants.ts
@@ -0,0 +1,20 @@
+/**组件布局类型-基础布局组件标识 */
+export const MENU_COMPONENT_LAYOUT_BASIC = 'BasicLayout';
+
+/**组件布局类型-空白布局组件标识 */
+export const MENU_COMPONENT_LAYOUT_BLANK = 'BlankLayout';
+
+/**组件布局类型-内链接布局组件标识 */
+export const MENU_COMPONENT_LAYOUT_LINK = 'LinkLayout';
+
+/**菜单类型-目录 */
+export const MENU_TYPE_DIR = 'D';
+
+/**菜单类型-菜单 */
+export const MENU_TYPE_MENU = 'M';
+
+/**菜单类型-按钮 */
+export const MENU_TYPE_BUTTON = 'B';
+
+/**菜单内嵌地址标识-带/前缀 */
+export const MENU_PATH_INLINE = '/inline';
diff --git a/src/constants/token-constants.ts b/src/constants/token-constants.ts
new file mode 100644
index 00000000..043273ff
--- /dev/null
+++ b/src/constants/token-constants.ts
@@ -0,0 +1,11 @@
+/**令牌-数据响应字段 */
+export const TOKEN_RESPONSE_FIELD = 'access_token';
+
+/**令牌-请求头标识前缀 */
+export const TOKEN_KEY_PREFIX = 'Bearer ';
+
+/**令牌-请求头标识 */
+export const TOKEN_KEY = 'Authorization';
+
+/**令牌-存放Cookie标识 */
+export const TOKEN_COOKIE = 'AuthAntdv';
diff --git a/src/directive/index.ts b/src/directive/index.ts
new file mode 100644
index 00000000..37c3d042
--- /dev/null
+++ b/src/directive/index.ts
@@ -0,0 +1,10 @@
+import { App } from 'vue';
+import permsDirective from './perms-directive';
+import rolesDirective from './roles-directive';
+
+export default {
+ install: (app: App) => {
+ app.directive('perms', permsDirective);
+ app.directive('roles', rolesDirective);
+ },
+};
diff --git a/src/directive/perms-directive.ts b/src/directive/perms-directive.ts
new file mode 100644
index 00000000..04a00384
--- /dev/null
+++ b/src/directive/perms-directive.ts
@@ -0,0 +1,38 @@
+import { hasPermissions, matchPermissions } from '@/plugins/auth-user';
+import { DirectiveBinding } from 'vue';
+
+/**
+ * perms-权限标识
+ *
+ * 指令值:字符串数组
+ *
+ * 指令的参数:has/math,默认has
+ *
+ * v-perms="['monitor:server:query']"
+ * 等同
+ * v-perms:has="['monitor:server:query']"
+ *
+ * v-perms:math="['style:user:query', 'style:user:edit']"
+ *
+ * @param el 指令绑定到的元素
+ * @param binding 一个对象
+ */
+export default function (el: any, binding: DirectiveBinding) {
+ const value = binding.value;
+ let arg = binding.arg;
+ let ok: boolean = false;
+ if (Array.isArray(value) && value.length > 0) {
+ // 匹配
+ if (arg === 'math') {
+ ok = matchPermissions(value);
+ }
+ // 含有
+ if (!arg || arg === 'has') {
+ ok = hasPermissions(value);
+ }
+ }
+ // 没有权限就移除节点
+ if (!ok) {
+ el.parentNode && el.parentNode.removeChild(el);
+ }
+}
diff --git a/src/directive/roles-directive.ts b/src/directive/roles-directive.ts
new file mode 100644
index 00000000..8ee00980
--- /dev/null
+++ b/src/directive/roles-directive.ts
@@ -0,0 +1,38 @@
+import { hasRoles, matchRoles } from '@/plugins/auth-user';
+import { DirectiveBinding } from 'vue';
+
+/**
+ * roles-权限标识
+ *
+ * 指令值:字符串数组
+ *
+ * 指令的参数:has/math,默认has
+ *
+ * v-roles="['admin']"
+ * 等同
+ * v-roles:has="['admin']"
+ *
+ * v-roles:math="['common', 'user']"
+ *
+ * @param el 指令绑定到的元素
+ * @param binding 一个对象
+ */
+export default function (el: any, binding: DirectiveBinding) {
+ const value = binding.value;
+ let arg = binding.arg;
+ let ok: boolean = false;
+ if (Array.isArray(value) && value.length > 0) {
+ // 匹配
+ if (arg === 'math') {
+ ok = matchRoles(value);
+ }
+ // 含有
+ if (!arg || arg === 'has') {
+ ok = hasRoles(value);
+ }
+ }
+ // 没有权限就移除节点
+ if (!ok) {
+ el.parentNode && el.parentNode.removeChild(el);
+ }
+}
diff --git a/src/hooks/useI18n.ts b/src/hooks/useI18n.ts
new file mode 100644
index 00000000..fc76eba4
--- /dev/null
+++ b/src/hooks/useI18n.ts
@@ -0,0 +1,26 @@
+import { computed } from 'vue';
+import { useI18n } from 'vue-i18n';
+import { localSet } from '@/utils/cache-local-utils';
+import { CACHE_LOCAL_I18N } from '@/constants/cache-keys-constants';
+
+export default function useLocale() {
+ //实例化i18n
+ const i18n = useI18n();
+
+ // 获取当前语言设置
+ const currentLocale = computed(() => {
+ return i18n.locale.value;
+ });
+
+ // 切换语言
+ const changeLocale = (value: string) => {
+ i18n.locale.value = value;
+ localSet(CACHE_LOCAL_I18N, value);
+ };
+
+ return {
+ currentLocale,
+ changeLocale,
+ t: i18n.t,
+ };
+}
diff --git a/src/hooks/useLoading.ts b/src/hooks/useLoading.ts
new file mode 100644
index 00000000..3750f69f
--- /dev/null
+++ b/src/hooks/useLoading.ts
@@ -0,0 +1,16 @@
+import { ref, inject, provide } from 'vue';
+
+const INJECT_LOADING_KEY = Symbol('loading_store');
+
+export const createLoading = (v = false) => {
+ const loading = ref(v);
+ const change = (bool: boolean) => {
+ loading.value = bool;
+ };
+ provide(INJECT_LOADING_KEY, loading);
+ return [loading, change];
+};
+
+export const useLoading = () => {
+ return inject(INJECT_LOADING_KEY);
+};
diff --git a/src/hooks/useTheme.ts b/src/hooks/useTheme.ts
new file mode 100644
index 00000000..8745b805
--- /dev/null
+++ b/src/hooks/useTheme.ts
@@ -0,0 +1,58 @@
+import { onBeforeMount } from 'vue';
+import { ConfigProvider } from 'ant-design-vue/lib';
+import { CACHE_LOCAL_PRIMARY_COLOR } from '@/constants/cache-keys-constants';
+import { localGet, localSet } from '@/utils/cache-local-utils';
+
+/**
+ * 初始主题色
+ */
+export const usePrimaryColor = () => {
+ onBeforeMount(() => {
+ changePrimaryColor(getLocalColor());
+ });
+};
+
+/**
+ * 改变主题色
+ * @param color 颜色
+ */
+export function changePrimaryColor(color?: string) {
+ if (!color) {
+ color = getRandomColor();
+ }
+ ConfigProvider.config({
+ theme: {
+ primaryColor: color,
+ },
+ });
+ localSet(CACHE_LOCAL_PRIMARY_COLOR, color);
+}
+
+/**
+ * 获取主题色-本地缓存
+ * @returns 颜色
+ */
+export function getLocalColor() {
+ return localGet(CACHE_LOCAL_PRIMARY_COLOR) || '#1890ff';
+}
+
+/**
+ * 获取随机颜色范围
+ * @returns 颜色
+ */
+function getRandomColor(): string {
+ const colors: string[] = [
+ '#f5222d',
+ '#fa541c',
+ '#fa8c16',
+ '#a0d911',
+ '#13c2c2',
+ '#1890ff',
+ '#722ed1',
+ '#eb2f96',
+ '#faad14',
+ '#52c41a',
+ ];
+ const i = Math.floor(Math.random() * 10);
+ return colors[i];
+}
diff --git a/src/i18n/index.ts b/src/i18n/index.ts
new file mode 100644
index 00000000..aa344943
--- /dev/null
+++ b/src/i18n/index.ts
@@ -0,0 +1,16 @@
+import { createI18n } from 'vue-i18n';
+import { localGet } from '@/utils/cache-local-utils';
+import { CACHE_LOCAL_I18N } from '@/constants/cache-keys-constants';
+import zhCN from './locales/zh-CN';
+import enUS from './locales/en-US';
+
+const i18n = createI18n({
+ legacy: false, // 使用 Composition API 的方式创建 i18n 实例
+ locale: localGet(CACHE_LOCAL_I18N) || 'zh_CN', // 默认显示语言
+ messages: {
+ zh_CN: zhCN,
+ en_US: enUS,
+ },
+});
+
+export default i18n;
diff --git a/src/i18n/locales/en-US.ts b/src/i18n/locales/en-US.ts
new file mode 100644
index 00000000..0df53e9c
--- /dev/null
+++ b/src/i18n/locales/en-US.ts
@@ -0,0 +1,62 @@
+export default {
+ // 语言
+ i18n: 'English',
+ hello: 'Hello',
+
+ // 通用
+ common: {
+ title: 'Mask Antd Vue3',
+ desc: 'Management system based on ant design vue+vue3',
+ loading: 'Please wait...',
+ tipTitle: 'Prompt',
+ },
+
+ // 全局页脚
+ globalFooter: {
+ help: 'Help',
+ privacy: 'Privacy',
+ term: 'Term',
+ },
+
+ // 校验
+ valid: {
+ userNameReg:
+ 'The account cannot start with a number and can contain uppercase and lowercase letters, numbers, and no less than 5 digits.',
+ userNamePlease: 'Please enter the correct login account',
+ userNameHit: 'Login account',
+ passwordReg:
+ 'The password should contain at least uppercase and lowercase letters, numbers, special symbols, and no less than 6 digits.',
+ passwordPlease: 'Please enter the correct login password',
+ passwordHit: 'Login password',
+ passwordConfirmHit: 'Confirm login password',
+ phoneReg: 'Incorrect phone number',
+ phonePlease: 'Please enter the correct phone number',
+ phoneHit: 'Mobile number',
+ codePlease: 'Please enter the correct verification code',
+ codeHit: 'Verification code',
+ codeText: 'Obtain verification code',
+ codeSmsSend: 'Successfully sent, please pay attention to checking the SMS',
+ },
+
+ // 页面
+ views: {
+ login: {
+ tabPane1: 'Account password login',
+ tabPane2: 'Login with phone number',
+ registerBtn: 'Register an account',
+ loginBtn: 'Login',
+ loginSuccess: 'Login successful',
+ loginMethod: 'Other login methods:',
+ loginMethodWX: 'WeChat Scan Login',
+ loginMethodQQ: 'QQ Scan Code Login',
+ },
+ register: {
+ registerBtn: 'Register',
+ loginBtn: 'Log in with an existing account',
+ passwordErr: 'Please enter the correct confirmation password',
+ passwordConfirmErr: 'The two passwords entered do not match',
+ tipContent: 'Congratulations, {username} account registration succeeded!',
+ tipBtn: 'Go to login',
+ },
+ },
+};
diff --git a/src/i18n/locales/zh-CN.ts b/src/i18n/locales/zh-CN.ts
new file mode 100644
index 00000000..86d95237
--- /dev/null
+++ b/src/i18n/locales/zh-CN.ts
@@ -0,0 +1,60 @@
+export default {
+ // 语言
+ i18n: '中文',
+ hello: '你好',
+
+ // 通用
+ common: {
+ title: 'Mask Antd Vue3',
+ desc: '基于 ant-design-vue + vue3 的管理系统',
+ loading: '请稍等...',
+ tipTitle: '提示',
+ },
+
+ // 全局页脚
+ globalFooter: {
+ help: '帮助',
+ privacy: '隐私',
+ term: '条款',
+ },
+
+ // 校验
+ valid: {
+ userNameReg: '账号不能以数字开头,可包含大写小写字母,数字,且不少于5位',
+ userNamePlease: '请输入正确登录账号',
+ userNameHit: '登录账号',
+ passwordReg: '密码至少包含大小写字母、数字、特殊符号,且不少于6位',
+ passwordPlease: '请输入正确登录密码',
+ passwordHit: '登录密码',
+ passwordConfirmHit: '确认登录密码',
+ phoneReg: '手机号码不正确',
+ phonePlease: '请输入正确的手机号码',
+ phoneHit: '手机号码',
+ codePlease: '请输入正确的验证码',
+ codeHit: '验证码',
+ codeText: '获取验证码',
+ codeSmsSend: '发送成功,请注意查看短信',
+ },
+
+ // 页面
+ views: {
+ login: {
+ tabPane1: '账号密码登录',
+ tabPane2: '手机号登录',
+ registerBtn: '注册账号',
+ loginBtn: '登录',
+ loginSuccess: '登录成功',
+ loginMethod: '其他登录方式:',
+ loginMethodWX: '微信扫一扫登录',
+ loginMethodQQ: 'QQ扫码登录',
+ },
+ register: {
+ registerBtn: '注册',
+ loginBtn: '使用已有账号登录',
+ passwordErr: '请正确输入确认密码',
+ passwordConfirmErr: '两次输入的密码不一致',
+ tipContent: '恭喜您,{username} 账号注册成功!',
+ tipBtn: '前往登录',
+ },
+ },
+};
diff --git a/src/layouts/BasicLayout.vue b/src/layouts/BasicLayout.vue
new file mode 100644
index 00000000..2b290f03
--- /dev/null
+++ b/src/layouts/BasicLayout.vue
@@ -0,0 +1,212 @@
+
+
+
+
+
+
+
+
+
+ {{ appName }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ route.breadcrumbName }}
+
+
+ {{ route.breadcrumbName }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/layouts/BlankLayout.vue b/src/layouts/BlankLayout.vue
new file mode 100644
index 00000000..0c0d640e
--- /dev/null
+++ b/src/layouts/BlankLayout.vue
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/src/layouts/LinkLayout.vue b/src/layouts/LinkLayout.vue
new file mode 100644
index 00000000..7c322a81
--- /dev/null
+++ b/src/layouts/LinkLayout.vue
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/layouts/components/RightContent.vue b/src/layouts/components/RightContent.vue
new file mode 100644
index 00000000..e0a1decd
--- /dev/null
+++ b/src/layouts/components/RightContent.vue
@@ -0,0 +1,155 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Content of Tab 通知
+ 查看更多
+
+
+ Content of Tab 消息
+ 查看更多
+
+
+ Content of Tab 待办
+ 查看更多
+
+
+
+
+
+
+ 开源仓库
+
+
+
+
+
+
+
+
+ 文档手册
+
+
+
+
+
+
+
+
+
+ {{ t('i18n') }}
+
+
+
+
+ 中文
+ English
+
+
+
+
+
+
+
+
+ {{ userStore.nickName }}
+
+
+
+
+
+
+
+
+ 个人中心
+
+
+
+
+
+ 个人设置
+
+
+
+
+
+
+ 退出登录
+
+
+
+
+
+
+
+
diff --git a/src/layouts/components/Tabs.vue b/src/layouts/components/Tabs.vue
new file mode 100644
index 00000000..4c2507c4
--- /dev/null
+++ b/src/layouts/components/Tabs.vue
@@ -0,0 +1,187 @@
+
+
+
+
+
+
fnTabClick(path as string)"
+ @edit="path => fnTabClose(path as string)"
+ >
+
+
+
+
+ {{ tab.title }}
+
+
+
+
+
+
+
+ 刷新当前
+
+
+
+
+
+ 更多选项
+
+
+
+
+
+ fnTabMenu(key)">
+ 关闭当前
+ 关闭其他
+ 关闭全部
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main.ts b/src/main.ts
new file mode 100644
index 00000000..de1255a8
--- /dev/null
+++ b/src/main.ts
@@ -0,0 +1,16 @@
+import { createApp } from 'vue';
+import store from './store';
+import App from './App.vue';
+import router from './router';
+import directive from './directive';
+import i18n from './i18n';
+import '@ant-design-vue/pro-layout/dist/style.css';
+import 'ant-design-vue/dist/antd.variable.min.css';
+
+const app = createApp(App);
+app.use(store);
+app.use(router);
+app.use(directive);
+app.use(i18n);
+
+app.mount('#app');
diff --git a/src/plugins/auth-token.ts b/src/plugins/auth-token.ts
new file mode 100644
index 00000000..2c5bd953
--- /dev/null
+++ b/src/plugins/auth-token.ts
@@ -0,0 +1,17 @@
+import Cookies from 'js-cookie';
+import { TOKEN_COOKIE } from '@/constants/token-constants';
+
+/**获取cookis中Token字符串 */
+export function getToken(): string {
+ return Cookies.get(TOKEN_COOKIE) || '';
+}
+
+/**设置cookis中Token字符串 */
+export function setToken(token: string): void {
+ Cookies.set(TOKEN_COOKIE, token);
+}
+
+/**移除cookis中Token字符串 */
+export function removeToken(): void {
+ Cookies.remove(TOKEN_COOKIE);
+}
diff --git a/src/plugins/auth-user.ts b/src/plugins/auth-user.ts
new file mode 100644
index 00000000..f736c6d3
--- /dev/null
+++ b/src/plugins/auth-user.ts
@@ -0,0 +1,54 @@
+import { ADMIN_PERMISSION, ADMIN_ROLE_KEY } from '@/constants/admin-constants';
+import useUserStore from '@/store/modules/user';
+
+/**
+ * 只需含有其中权限
+ * @param role 权限字符数组
+ * @returns true | false
+ */
+export function hasPermissions(permissions: string[]): boolean {
+ if (!permissions || permissions.length === 0) return false;
+ const userPermissions = useUserStore().permissions;
+ if (!userPermissions || userPermissions.length === 0) return false;
+ if (userPermissions.includes(ADMIN_PERMISSION)) return true;
+ return permissions.some(p => userPermissions.some(up => up === p));
+}
+
+/**
+ * 同时匹配其中权限
+ * @param role 权限字符数组
+ * @returns true | false
+ */
+export function matchPermissions(permissions: string[]): boolean {
+ if (!permissions || permissions.length === 0) return false;
+ const userPermissions = useUserStore().permissions;
+ if (!userPermissions || userPermissions.length === 0) return false;
+ if (userPermissions.includes(ADMIN_PERMISSION)) return true;
+ return permissions.every(p => userPermissions.some(up => up === p));
+}
+
+/**
+ * 只需含有其中角色
+ * @param role 角色字符数组
+ * @returns true | false
+ */
+export function hasRoles(roles: string[]): boolean {
+ if (!roles || roles.length === 0) return false;
+ const userRoles = useUserStore().roles;
+ if (!userRoles || userRoles.length === 0) return false;
+ if (userRoles.includes(ADMIN_ROLE_KEY)) return true;
+ return roles.some(r => userRoles.some(ur => ur === r));
+}
+
+/**
+ * 同时匹配其中角色
+ * @param role 角色字符数组
+ * @returns true | false
+ */
+export function matchRoles(roles: string[]): boolean {
+ if (!roles || roles.length === 0) return false;
+ const userRoles = useUserStore().roles;
+ if (!userRoles || userRoles.length === 0) return false;
+ if (userRoles.includes(ADMIN_ROLE_KEY)) return true;
+ return roles.every(r => userRoles.some(ur => ur === r));
+}
diff --git a/src/plugins/http-fetch.ts b/src/plugins/http-fetch.ts
new file mode 100644
index 00000000..24985d4d
--- /dev/null
+++ b/src/plugins/http-fetch.ts
@@ -0,0 +1,270 @@
+import { getToken, removeToken } from '@/plugins/auth-token';
+import { sessionGetJSON, sessionSetJSON } from '@/utils/cache-session-utils';
+import { TOKEN_KEY, TOKEN_KEY_PREFIX } from '@/constants/token-constants';
+import { CACHE_SESSION_FATCH } from '@/constants/cache-keys-constants';
+import {
+ APP_REQUEST_HEADER_CODE,
+ APP_REQUEST_HEADER_VERSION,
+} from '@/constants/app-constants';
+
+/**响应结果类型 */
+export type ResultType = {
+ /**响应码 */
+ code: number | 200 | 500;
+ /**信息 */
+ msg: string;
+ /**数据 */
+ data?: any;
+ /**未知属性 */
+ [key: string]: any;
+};
+
+/**防止重复提交类型 */
+type RepeatSubmitType = {
+ /**请求地址 */
+ url: string;
+ /**请求数据 */
+ data: string;
+ /**请求时间 */
+ time: number;
+};
+
+/**请求参数类型 */
+type OptionsType = {
+ /**请求地址 */
+ url: string;
+ /**请求方法 */
+ method: 'get' | 'post' | 'put' | 'delete';
+ /**请求头 */
+ headers?: HeadersInit;
+ /**地址栏参数 */
+ params?: Record;
+ /**发送数据 */
+ data?: Record | FormData | object;
+ /**请求数据类型 */
+ dataType?: 'form-data' | 'json';
+ /**响应数据类型 */
+ responseType?: 'text' | 'json' | 'blob' | 'arrayBuffer';
+ /**请求缓存策略 */
+ cache?: RequestCache;
+ /**请求的凭证,如 omit、same-origin、include */
+ credentials?: RequestCredentials;
+ /**请求体 */
+ body?: BodyInit;
+ /**防止数据重复提交 */
+ repeatSubmit?: boolean;
+ /**携带授权Token请求头 */
+ whithToken?: boolean;
+};
+
+/**全局配置类型 */
+type ConfigType = {
+ /**请求的根域名地址-不带/后缀 */
+ baseUrl: string;
+ /**超时时间,毫秒 */
+ timeout: number;
+};
+
+/**默认配置 */
+const FATCH_CONFIG: ConfigType = {
+ baseUrl: import.meta.env.VITE_API_BASE_URL,
+ timeout: 10 * 1000,
+};
+
+/**默认请求参数 */
+const FATCH_OPTIONS: OptionsType = {
+ url: '',
+ method: 'get',
+ headers: {
+ [APP_REQUEST_HEADER_CODE]: import.meta.env.VITE_APP_CODE,
+ [APP_REQUEST_HEADER_VERSION]: import.meta.env.VITE_APP_VERSION,
+ // 使用mock.apifox.cn时开启
+ // apifoxToken: '8zCzh3vipdEwd1ukv9lQEuTekdWIH7xN',
+ },
+ dataType: 'json',
+ responseType: 'json',
+ cache: 'no-cache',
+ credentials: undefined,
+ repeatSubmit: true,
+ whithToken: true,
+};
+
+/**请求前的拦截 */
+function beforeRequest(options: OptionsType): OptionsType | Promise {
+ options.headers = Object.assign({}, options.headers);
+ //console.log('请求前的拦截', options);
+
+ // 给发送数据类型设置请求头
+ if (options.dataType === 'json') {
+ Reflect.set(
+ options.headers,
+ 'content-type',
+ 'application/json;charset=utf-8'
+ );
+ }
+
+ // 是否需要设置 token
+ const token = getToken();
+ if (options.whithToken && token) {
+ Reflect.set(options.headers, TOKEN_KEY, TOKEN_KEY_PREFIX + token);
+ }
+ // 是否需要防止数据重复提交
+ if (
+ options.repeatSubmit &&
+ options.dataType === 'json' &&
+ ['post', 'put'].includes(options.method)
+ ) {
+ const requestObj: RepeatSubmitType = {
+ url: options.url,
+ data: JSON.stringify(options.data),
+ time: Date.now(),
+ };
+ const sessionObj: RepeatSubmitType = sessionGetJSON(CACHE_SESSION_FATCH);
+ if (sessionObj) {
+ const { url, data, time } = sessionObj;
+ const interval = 1000; // 间隔时间(ms),小于此时间视为重复提交
+ if (
+ requestObj.url === url &&
+ requestObj.data === data &&
+ requestObj.time - time < interval
+ ) {
+ const message = '数据正在处理,请勿重复提交';
+ console.warn(`[${url}]: ${message}`);
+ return Promise.reject(message);
+ } else {
+ sessionSetJSON(CACHE_SESSION_FATCH, requestObj);
+ }
+ } else {
+ sessionSetJSON(CACHE_SESSION_FATCH, requestObj);
+ }
+ }
+
+ // get请求拼接地址栏参数
+ if (options.method === 'get' && options.params) {
+ let paramStr = '';
+ const params = options.params;
+ for (const key in params) {
+ const value = params[key];
+ // 空字符或未定义的值不作为参数发送
+ if (value === '' || value === undefined) continue;
+ paramStr += `&${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
+ }
+ if (paramStr && paramStr.startsWith('&')) {
+ options.url = `${options.url}?${paramStr.substring(1)}`;
+ }
+ }
+
+ // 非get参数提交
+ if (options.data instanceof FormData) {
+ options.body = options.data;
+ } else {
+ options.body = JSON.stringify(options.data);
+ }
+ return options;
+}
+
+/**请求后的拦截 */
+function interceptorResponse(res: ResultType): ResultType | Promise {
+ //console.log('请求后的拦截', res);
+ // 登录失效时,移除授权令牌并重新刷新页面
+ if (res.code === 401) {
+ removeToken();
+ window.location.reload();
+ }
+ return res;
+}
+
+/**
+ * 请求http
+ *
+ * @param options 请求参数
+ *
+ * responseType改变响应结果类型
+ * @returns 返回 Promise
+ */
+export async function request(options: OptionsType): Promise {
+ // 请求超时控制请求终止
+ const controller = new AbortController();
+ const { signal } = controller;
+ const timeoutId = setTimeout(() => {
+ controller.abort(); // 终止请求
+ }, FATCH_CONFIG.timeout);
+
+ options = Object.assign({ signal }, FATCH_OPTIONS, options);
+
+ // 检查请求拦截
+ const beforeReq = beforeRequest(options);
+ if (beforeReq instanceof Promise) {
+ return await beforeReq;
+ }
+ options = beforeReq;
+
+ // 判断用户传递的URL是否http或/开头
+ if (!options.url.startsWith('http')) {
+ const uri = options.url.startsWith('/') ? options.url : `/${options.url}`;
+ options.url = FATCH_CONFIG.baseUrl + uri;
+ }
+
+ try {
+ const res = await fetch(options.url, options);
+ // console.log('请求结果:', res);
+ if (res.status === 500) {
+ return {
+ code: 500,
+ msg: '服务器连接出错!',
+ };
+ }
+
+ // 根据响应数据类型返回
+ switch (options.responseType) {
+ case 'text': // 文本数据
+ const str = await res.text();
+ return {
+ code: 200,
+ msg: str,
+ };
+ case 'json': // json格式数据
+ const result = await res.json();
+ // 请求后的拦截
+ const beforeRes = interceptorResponse(result);
+ if (beforeRes instanceof Promise) {
+ return await beforeRes;
+ }
+ return result;
+ case 'blob': // 二进制数据则直接返回
+ case 'arrayBuffer':
+ const contentType = res.headers.get('content-type') || '';
+ if (contentType.startsWith('application/json')) {
+ const result = await res.json();
+ return result as ResultType;
+ }
+ const data =
+ options.responseType === 'blob'
+ ? await res.blob()
+ : await res.arrayBuffer();
+ return {
+ code: 200,
+ msg: '成功',
+ data: data,
+ status: res.status,
+ headers: res.headers,
+ };
+ default:
+ return {
+ code: 500,
+ msg: '未知响应数据类型',
+ };
+ }
+ } catch (error: any) {
+ // 请求被终止时
+ if (error.name === 'AbortError') {
+ return {
+ code: 500,
+ msg: '网络连接超时!',
+ };
+ }
+ throw error;
+ } finally {
+ clearTimeout(timeoutId); // 请求成功,清除超时计时器
+ }
+}
diff --git a/src/router/index.ts b/src/router/index.ts
new file mode 100644
index 00000000..0915ead7
--- /dev/null
+++ b/src/router/index.ts
@@ -0,0 +1,289 @@
+import {
+ createRouter,
+ createWebHistory,
+ createWebHashHistory,
+ RouteRecordRaw,
+} from 'vue-router';
+import NProgress from 'nprogress';
+import 'nprogress/nprogress.css';
+import BasicLayout from '../layouts/BasicLayout.vue';
+import BlankLayout from '../layouts/BlankLayout.vue';
+import LinkLayout from '../layouts/LinkLayout.vue';
+import { encode } from 'js-base64';
+import { getToken } from '@/plugins/auth-token';
+import { validHttp } from '@/utils/regular-utils';
+import useUserStore from '@/store/modules/user';
+import useAppStore from '@/store/modules/app';
+import useRouterStore from '@/store/modules/router';
+
+// NProgress Configuration
+NProgress.configure({ showSpinner: false });
+
+// import { MetaRecord, MenuDataItem } from '@ant-design-vue/pro-layout';
+// mate数据类型 MetaRecord
+// 根据/路径构建菜单列表,列表项类型 MenuDataItem
+// https://github.com/vueComponent/pro-components/blob/a19279f3a28190bf11e8c36f316c92dbd3387a6d/packages/pro-layout/src/typings.ts#L16
+// 菜单图标来源 https://ant.design/components/icon 自定义iconfont
+
+/**公共路由 */
+const constantRoutes: RouteRecordRaw[] = [
+ {
+ path: '/',
+ name: 'Root',
+ meta: { title: '根节点' },
+ component: BasicLayout,
+ redirect: '/index',
+ children: [
+ {
+ path: '/index',
+ name: 'Index',
+ meta: { title: '首页', icon: 'icon-pcduan', cache: true },
+ component: () => import('@/views/index.vue'),
+ },
+ {
+ path: '/dome1',
+ name: 'Dome1',
+ meta: { title: '示例一', icon: 'icon-ios' },
+ component: () => import('@/views/dome/dome1.vue'),
+ },
+ {
+ path: '/dome2',
+ name: 'Dome2',
+ meta: { title: '示例二', icon: 'icon-anzhuo' },
+ component: () => import('@/views/dome/dome2.vue'),
+ },
+ {
+ path: '/dome3',
+ name: 'Dome3',
+ meta: { title: '示例三', icon: 'icon-qunzhu' },
+ component: () => import('@/views/dome/dome3.vue'),
+ },
+ {
+ path: '/domes',
+ name: 'Domes',
+ meta: {
+ title: '示例目录',
+ icon: 'icon-zhizuoliucheng',
+ },
+ component: BlankLayout,
+ redirect: () => ({ name: 'PageInfo' }),
+ children: [
+ {
+ path: 'page-info',
+ name: 'PageInfo',
+ meta: { title: '页面信息', icon: 'icon-huifu' },
+ component: () => import('../views/domes/page-info.vue'),
+ },
+ {
+ path: 'page-typography',
+ name: 'PageTypography',
+ meta: { title: '文本信息', icon: 'icon-huizhiguize' },
+ component: () => import('../views/domes/page-typography.vue'),
+ },
+ {
+ path: 'dynamic-match/:id(\\d+)',
+ name: 'DynamicMatch',
+ // 路由 path 默认参数再 meta.params 里
+ meta: { title: '动态参数页面', params: { id: 1 }, cache: true },
+ component: () => import('../views/domes/dynamic-match.vue'),
+ },
+ {
+ path: 'disabled',
+ name: 'Disabled',
+ meta: { title: '禁止点击', disabled: true },
+ component: () => {},
+ },
+ {
+ path: 'https://github.com/TsMask',
+ name: 'BlankGithubTsMask',
+ meta: {
+ title: 'TsMask-打开新窗',
+ icon: 'icon-github',
+ target: '_blank',
+ },
+ component: () => {},
+ },
+ {
+ path: encode('https://www.antdv.com/components/comment-cn'),
+ name: 'HttpsAntDesignVue',
+ meta: {
+ title: 'Antdv-内嵌窗口',
+ icon: 'icon-morentouxiang',
+ target: null,
+ },
+ component: LinkLayout,
+ },
+ ],
+ },
+ {
+ path: 'https://github.com/',
+ name: 'BlankGithub',
+ meta: {
+ title: 'Github-打开新窗',
+ icon: 'icon-github',
+ target: '_blank',
+ },
+ component: () => {},
+ },
+ {
+ path: 'https://www.antdv.com/components/comment-cn?sdf=12321&id=12&sdnf',
+ name: 'SelfAnt Design Vue',
+ meta: {
+ title: 'Antdv-当前窗口',
+ icon: 'icon-morentouxiang',
+ target: '_self',
+ },
+ component: LinkLayout,
+ },
+ {
+ path: '/account',
+ name: 'Account',
+ meta: {
+ title: '个人中心',
+ },
+ component: BlankLayout,
+ redirect: '/account/profile',
+ children: [
+ {
+ path: 'profile',
+ name: 'Profile',
+ meta: { title: '个人信息', cache: true },
+ component: () => import('@/views/account/profile.vue'),
+ },
+ {
+ path: 'settings',
+ name: 'Settings',
+ meta: { title: '个人设置', cache: true },
+ component: () => import('@/views/account/settings.vue'),
+ },
+ ],
+ },
+ ],
+ },
+ {
+ path: '/login',
+ name: 'Login',
+ meta: { title: '登录' },
+ component: () => import('@/views/login.vue'),
+ },
+ {
+ path: '/register',
+ name: 'Register',
+ meta: { title: '注册' },
+ component: () => import('@/views/register.vue'),
+ },
+ {
+ path: '/403',
+ name: 'NotPermission',
+ meta: { title: '没有访问权限' },
+ component: () => import('@/views/error/403.vue'),
+ },
+ {
+ path: '/redirect',
+ name: 'Redirect',
+ meta: { title: '重定向' },
+ component: BasicLayout,
+ children: [
+ {
+ path: '/redirect/:path(.*)',
+ component: () => import('@/views/redirect/index.vue'),
+ },
+ ],
+ },
+ {
+ path: '/:pathMatch(.*)*',
+ meta: { title: '找不到匹配页面' },
+ component: () => import('@/views/error/404.vue'),
+ },
+];
+
+// 根据.env配置获取是否带井号和基础路径
+const hasHash = import.meta.env.VITE_HISTORY_HASH;
+const bashUrl = import.meta.env.VITE_HISTORY_BASE_URL;
+
+/**全局路由 */
+const router = createRouter({
+ history:
+ hasHash === 'true'
+ ? createWebHashHistory(bashUrl)
+ : createWebHistory(bashUrl),
+ routes: constantRoutes,
+ scrollBehavior(to, from, savedPosition) {
+ if (savedPosition) {
+ return savedPosition;
+ } else {
+ return { top: 0 };
+ }
+ },
+});
+
+/**全局路由-后置守卫 */
+router.afterEach((to, from, failure) => {
+ NProgress.done();
+ // 设置标题
+ if (to.meta?.title) {
+ useAppStore().setTitle(to.meta.title);
+ }
+});
+
+/**无Token可访问页面地址白名单 */
+const WHITE_LIST: string[] = ['/login', '/auth-redirect', '/bind', '/register'];
+
+/**全局路由-前置守卫 */
+router.beforeEach((to, from, next) => {
+ NProgress.start();
+ const token = getToken();
+
+ // 没有token
+ if (!token) {
+ if (WHITE_LIST.includes(to.path)) {
+ // 在免登录白名单,直接进入
+ next();
+ } else {
+ // 否则全部重定向到登录页
+ next(`/login?redirect=${to.fullPath}`);
+ }
+ }
+
+ // 有Token
+ if (token) {
+ // 防止重复访问登录页面
+ if (to.path === '/login') {
+ next({ name: 'Index' });
+ } else {
+ // 判断当前用户是否有角色信息
+ const user = useUserStore();
+ if (user.roles && user.roles.length === 0) {
+ // 获取用户信息
+ user
+ .fnGetInfo()
+ .then(() => {
+ return useRouterStore().generateRoutes();
+ })
+ .then(accessRoutes => {
+ // 根据后台配置生成可访问的路由表
+ if (accessRoutes && accessRoutes.length !== 0) {
+ for (const route of accessRoutes) {
+ // 动态添加可访问路由表,http开头会异常
+ if (!validHttp(route.path)) {
+ router.addRoute(route);
+ }
+ }
+ }
+ // 刷新替换原先路由,确保addRoutes已完成
+ next({ ...to, replace: true });
+ })
+ .catch(e => {
+ console.error(`[${to.path}]: ${e.message}`);
+ user.fnLogOut().finally(() => {
+ next({ name: 'Login' });
+ });
+ });
+ } else {
+ next();
+ }
+ }
+ }
+});
+
+export default router;
diff --git a/src/store/index.ts b/src/store/index.ts
new file mode 100644
index 00000000..02ae96a9
--- /dev/null
+++ b/src/store/index.ts
@@ -0,0 +1,5 @@
+import { createPinia } from 'pinia';
+
+const store = createPinia();
+
+export default store;
diff --git a/src/store/modules/app.ts b/src/store/modules/app.ts
new file mode 100644
index 00000000..eb6d7b2a
--- /dev/null
+++ b/src/store/modules/app.ts
@@ -0,0 +1,31 @@
+import { defineStore } from 'pinia';
+
+/**应用参数类型 */
+type AppStore = {
+ /**应用名称 */
+ appName: string;
+ /**应用标识 */
+ appCode: string;
+ /**应用版本 */
+ appVersion: string;
+};
+
+const useAppStore = defineStore('app', {
+ state: (): AppStore => ({
+ appName: import.meta.env.VITE_APP_NAME,
+ appCode: import.meta.env.VITE_APP_CODE,
+ appVersion: import.meta.env.VITE_APP_VERSION,
+ }),
+ actions: {
+ /**设置网页标题 */
+ setTitle(title?: string) {
+ if (title) {
+ document.title = `${title} - ${this.appName}`;
+ } else {
+ document.title = this.appName;
+ }
+ },
+ },
+});
+
+export default useAppStore;
diff --git a/src/store/modules/dict.ts b/src/store/modules/dict.ts
new file mode 100644
index 00000000..ec183b09
--- /dev/null
+++ b/src/store/modules/dict.ts
@@ -0,0 +1,63 @@
+import { defineStore } from 'pinia';
+import { getDictDataType } from '@/api/system/dict/data';
+
+/**字典参数类型 */
+type DictStore = {
+ /**字典数据 */
+ dicts: Map;
+};
+
+const useDictStore = defineStore('dict', {
+ state: (): DictStore => ({
+ dicts: new Map(),
+ }),
+ actions: {
+ /**清空字典 */
+ clearDict() {
+ this.dicts.clear();
+ },
+ /**删除字典 */
+ removeDict(key: string) {
+ if (!key) return;
+ return this.dicts.delete(key);
+ },
+ /**
+ * 处理字典数据对象用于回显标签
+ * @param data 字典数据项
+ * @returns
+ */
+ parseDataDict(data: Record) {
+ return [
+ {
+ label: data.dictLabel,
+ value: data.dictValue,
+ elTagType: data.tagType,
+ elTagClass: data.tagClass,
+ },
+ ];
+ },
+ /**获取字典 */
+ async getDict(key: string) {
+ if (!key) return [];
+ let disct = this.dicts.get(key);
+ if (disct === undefined || disct.length === 0) {
+ const res = await getDictDataType(key);
+ if (res.code === 200 && Array.isArray(res.data)) {
+ const dictData: DictType[] = res.data.map(d => ({
+ label: d.dictLabel,
+ value: d.dictValue,
+ elTagType: d.tagType,
+ elTagClass: d.tagClass,
+ }));
+ this.dicts.set(key, dictData);
+ disct = dictData;
+ } else {
+ disct = [];
+ }
+ }
+ return disct;
+ },
+ },
+});
+
+export default useDictStore;
diff --git a/src/store/modules/layout.ts b/src/store/modules/layout.ts
new file mode 100644
index 00000000..0fb8fa0f
--- /dev/null
+++ b/src/store/modules/layout.ts
@@ -0,0 +1,91 @@
+import { CACHE_LOCAL_PROCONFIG } from '@/constants/cache-keys-constants';
+import { localGetJSON, localSetJSON } from '@/utils/cache-local-utils';
+import { defineStore } from 'pinia';
+
+/**布局参数类型 */
+type LayoutStore = {
+ /**布局设置抽屉显示 */
+ visible: boolean;
+ /**布局配置 */
+ proConfig: {
+ /**导航布局 */
+ layout: 'side' | 'top' | 'mix';
+ /**导航菜单主题色 */
+ navTheme: 'dark' | 'light';
+ /**顶部导航主题,仅导航布局为mix时生效 */
+ headerTheme: 'dark' | 'light';
+ /**固定顶部栏 */
+ fixedHeader: boolean;
+ /**固定菜单栏 */
+ fixSiderbar: boolean;
+ /**自动分割菜单 */
+ splitMenus: boolean;
+ /**内容区域-顶栏 */
+ headerRender: any | boolean | undefined;
+ /**内容区域-页脚 */
+ footerRender: any | boolean | undefined;
+ /**内容区域-菜单头 */
+ menuHeaderRender: any | boolean | undefined;
+ /**内容区域-导航标签项 */
+ tabRender: any | boolean | undefined;
+ };
+ /**水印内容 */
+ waterMarkContent: string;
+};
+
+/**判断是否关闭内容区域 */
+const proRender = (render: any) => (render === false ? false : undefined);
+
+/**本地缓存-布局配置设置 */
+const proConfigLocal: LayoutStore['proConfig'] = localGetJSON(
+ CACHE_LOCAL_PROCONFIG
+) || {
+ layout: 'side',
+ headerTheme: 'light',
+ navTheme: 'light',
+ fixSiderbar: true,
+ fixedHeader: true,
+ splitMenus: true,
+};
+
+const useLayoutStore = defineStore('layout', {
+ state: (): LayoutStore => ({
+ visible: false,
+ proConfig: {
+ layout: proConfigLocal.layout,
+ navTheme: proConfigLocal.navTheme,
+ headerTheme: proConfigLocal.headerTheme,
+ fixedHeader: Boolean(proConfigLocal.fixedHeader),
+ fixSiderbar: Boolean(proConfigLocal.fixSiderbar),
+ splitMenus: Boolean(proConfigLocal.splitMenus),
+ headerRender: proRender(proConfigLocal.headerRender),
+ footerRender: proRender(proConfigLocal.footerRender),
+ menuHeaderRender: proRender(proConfigLocal.menuHeaderRender),
+ tabRender: proRender(proConfigLocal.tabRender),
+ },
+ waterMarkContent: import.meta.env.VITE_APP_NAME,
+ }),
+ actions: {
+ /**改变显示状态 */
+ changeVisibleLayoutSetting() {
+ this.visible = !this.visible;
+ },
+ /**修改水印文字 */
+ changeWaterMark(text: string) {
+ this.waterMarkContent = text;
+ },
+ /**修改布局设置 */
+ changeConf(key: string, value: boolean | string | number | undefined) {
+ if (Reflect.has(this.proConfig, key)) {
+ // 同时修改mix混合菜单的导航主题
+ if (key === 'navTheme') {
+ Reflect.set(this.proConfig, 'headerTheme', value);
+ }
+ Reflect.set(this.proConfig, key, value);
+ localSetJSON(CACHE_LOCAL_PROCONFIG, this.proConfig);
+ }
+ },
+ },
+});
+
+export default useLayoutStore;
diff --git a/src/store/modules/router.ts b/src/store/modules/router.ts
new file mode 100644
index 00000000..41f9ff87
--- /dev/null
+++ b/src/store/modules/router.ts
@@ -0,0 +1,152 @@
+import { defineStore } from 'pinia';
+import {
+ RouteComponent,
+ RouteLocationRaw,
+ RouteMeta,
+ RouteRecordRaw,
+} from 'vue-router';
+import { getRouters } from '@/api/router';
+import BasicLayout from '@/layouts/BasicLayout.vue';
+import BlankLayout from '@/layouts/BlankLayout.vue';
+import LinkLayout from '@/layouts/LinkLayout.vue';
+import {
+ MENU_COMPONENT_LAYOUT_BASIC,
+ MENU_COMPONENT_LAYOUT_BLANK,
+ MENU_COMPONENT_LAYOUT_LINK,
+} from '@/constants/menu-constants';
+
+/**路由构建参数类型 */
+type RouterStore = {
+ /**初始的根路由数据 */
+ rootRouterData: RouteRecordRaw[];
+ /**动态路由数据 */
+ buildRouterData: RouteRecordRaw[];
+};
+
+const useRouterStore = defineStore('router', {
+ state: (): RouterStore => ({
+ rootRouterData: [],
+ buildRouterData: [],
+ }),
+ actions: {
+ /**
+ * 记录初始根节点菜单数据
+ * @param data 初始数据
+ * @returns 初始数据
+ */
+ setRootRouterData(data: RouteRecordRaw[]) {
+ if (this.rootRouterData.length <= 0) {
+ this.rootRouterData = data;
+ }
+ return this.rootRouterData;
+ },
+ /**
+ * 动态路由列表数据生成
+ * @returns 生成的路由菜单
+ */
+ async generateRoutes() {
+ const res = await getRouters();
+ if (res.code === 200 && Array.isArray(res.data)) {
+ const buildRoutes = buildRouters(res.data.concat());
+ this.buildRouterData = buildRoutes;
+ return buildRoutes;
+ }
+ return [];
+ },
+ },
+});
+
+/**异步路由类型 */
+type RecordRaws = {
+ path: string;
+ name: string;
+ meta: RouteMeta;
+ redirect: RouteLocationRaw;
+ component: string;
+ children: RecordRaws[];
+};
+
+/**
+ * 构建动态路由
+ *
+ * 遍历后台配置的路由菜单,转换为组件路由菜单
+ *
+ * @param recordRaws 异步路由列表
+ * @returns 可添加的路由列表
+ */
+function buildRouters(recordRaws: RecordRaws[]): RouteRecordRaw[] {
+ const routers: RouteRecordRaw[] = [];
+ for (const item of recordRaws) {
+ // 路由页面组件
+ let component: RouteComponent = {};
+ if (item.component) {
+ const comp = item.component;
+ if (comp === MENU_COMPONENT_LAYOUT_BASIC) {
+ component = BasicLayout;
+ } else if (comp === MENU_COMPONENT_LAYOUT_BLANK) {
+ component = BlankLayout;
+ } else if (comp === MENU_COMPONENT_LAYOUT_LINK) {
+ component = LinkLayout;
+ } else {
+ // 指定页面视图,一般用于显示子菜单
+ component = findView(comp);
+ }
+ }
+
+ // 有子菜单进行递归
+ let children: RouteRecordRaw[] = [];
+ if (item.children && item.children.length > 0) {
+ children = buildRouters(item.children);
+ }
+
+ // 对元数据特殊参数进行处理
+ let metaIcon = (item.meta?.icon as string) || '';
+ if (!metaIcon.startsWith('icon-')) {
+ metaIcon = '';
+ }
+ item.meta = Object.assign(item.meta, {
+ icon: metaIcon,
+ });
+
+ // 构建路由
+ const router: RouteRecordRaw = {
+ path: item.path,
+ name: item.name,
+ meta: item.meta,
+ redirect: item.redirect,
+ component: component,
+ children: children,
+ };
+ routers.push(router);
+ }
+ return routers;
+}
+
+/**匹配views里面所有的.vue或.tsx文件 */
+const views = import.meta.glob('./../../views/**/*.{vue,tsx}');
+
+/**
+ * 查找页面模块
+ *
+ * 查找 `/views/system/menu/index.vue` 或 `/views/system/menu/index.tsx`
+ *
+ * 参数值为 `system/menu/index`
+ *
+ * @param dirName 组件路径
+ * @returns 路由懒加载函数
+ */
+function findView(dirName: string) {
+ for (const dir in views) {
+ let viewDirName = '';
+ const component = dir.match(/views\/(.+)\.(vue|tsx)/);
+ if (component && component.length === 3) {
+ viewDirName = component[1];
+ }
+ if (viewDirName === dirName) {
+ return () => views[dir]();
+ }
+ }
+ return () => import('@/views/error/404.vue');
+}
+
+export default useRouterStore;
diff --git a/src/store/modules/tabs.ts b/src/store/modules/tabs.ts
new file mode 100644
index 00000000..3ab22d82
--- /dev/null
+++ b/src/store/modules/tabs.ts
@@ -0,0 +1,189 @@
+import { defineStore } from 'pinia';
+import type { LocationQuery, RouteLocationNormalizedLoaded } from 'vue-router';
+
+/**导航标签栏类型 */
+type TabsStore = {
+ /**标签列表 */
+ tabs: TabType[];
+ /**激活标签项 */
+ activePath: string;
+ /**缓存页面路由名称 */
+ caches: Set;
+};
+
+/**标签信息类型 */
+type TabType = {
+ path: string;
+ query: LocationQuery;
+ name: string;
+ title: string;
+ icon?: any;
+ cache?: boolean;
+};
+
+const useTabsStore = defineStore('tabs', {
+ state: (): TabsStore => ({
+ tabs: [],
+ activePath: '',
+ caches: new Set(),
+ }),
+ getters: {
+ /**获取导航标签栏列表 */
+ getTabs(state) {
+ return state.tabs;
+ },
+ /**获取缓存页面名 */
+ getCaches(state) {
+ return [...state.caches];
+ },
+ },
+ actions: {
+ /**清空标签项和缓存项列表 */
+ clear() {
+ this.tabs = [];
+ this.caches.clear();
+ },
+ /**
+ * 删除标签项
+ * @param path 当期标签路由地址
+ * @returns 布尔 true/false
+ */
+ remove(path: string) {
+ if (!path) return false;
+ const tabIndex = this.tabs.findIndex(tab => tab.path === path);
+ if (tabIndex === -1) return false;
+ // 同名称标签只剩一个时,才移除缓存
+ const name = this.tabs[tabIndex].name;
+ const tabs = this.tabs.filter(tab => tab.name === name);
+ if (tabs.length <= 1) {
+ this.cacheDelete(name);
+ }
+ this.tabs.splice(tabIndex, 1);
+ return true;
+ },
+ /**
+ * 添加标签项
+ * @param tab 标签信息对象
+ * @param index 插入指定位置,默认加到最后
+ * @returns 布尔 true/false
+ */
+ add(tab: TabType, index?: number) {
+ const { path, query, name, title, icon, cache } = tab;
+ // 是否缓存
+ if (cache) {
+ this.cacheAdd(name);
+ }
+ // 获取没有才添加
+ let tabIndex = this.tabs.findIndex(tab => tab.path === path);
+ if (tabIndex >= 0) return false;
+ const idx = index ? index : this.tabs.length;
+ this.tabs.splice(idx, 0, { path, query, name, title, icon });
+ return true;
+ },
+ /**添加缓存项
+ * @param name 路由名称
+ * @returns 布尔 true/false
+ */
+ cacheAdd(name: string) {
+ if (!name) return;
+ const has = this.caches.has(name);
+ if (has) return;
+ this.caches.add(name);
+ },
+ /**
+ * 删除缓存项
+ * @param name 路由名称
+ * @returns 布尔 true/false
+ */
+ cacheDelete(name: string) {
+ if (!name) return false;
+ const has = this.caches.has(name);
+ if (!has) return false;
+ return this.caches.delete(name);
+ },
+
+ /**
+ * 打开标签
+ *
+ * 动态参数会开新标签,这是考虑多信息查看才没用同一个标签打开。
+ * @param raw 跳转的路由信息
+ * @returns 无
+ */
+ tabOpen(raw: RouteLocationNormalizedLoaded) {
+ // 刷新是重定向不记录
+ if (raw.path.startsWith('/redirect')) return;
+ // 标签缓存使用路由名称
+ const name = (raw.name && raw.name.toString()) || '-';
+ // 新增到当期标签后面打开,获取当期标签下标
+ const tabIndex = this.tabs.findIndex(tab => tab.path === this.activePath);
+ this.add(
+ {
+ path: raw.path,
+ query: raw.query,
+ name: name,
+ title: raw.meta.title || '-',
+ icon: raw.meta.icon || '#',
+ cache: Boolean(raw.meta.cache),
+ },
+ tabIndex + 1
+ );
+ // 设置激活项
+ this.activePath = raw.path;
+ },
+ /**
+ * 关闭标签
+ * @param path 当期标签路由地址
+ * @returns 新跳转push路由参数
+ */
+ tabClose(path: string) {
+ if (!path) return null;
+ // 获取当前项和最后项下标
+ const tabIndex = this.tabs.findIndex(tab => tab.path === path);
+ if (tabIndex === -1) return null;
+ const lastIndex = this.tabs.length - 1;
+ let to = null;
+ // 只有一项默认跳首页
+ if (lastIndex === 0) {
+ to = {
+ path: '/index',
+ query: {},
+ };
+ }
+ // 关闭当期标签,操作第一项跳后一项
+ else if (path === this.activePath && tabIndex === 0) {
+ const tab = this.tabs[tabIndex + 1];
+ to = {
+ path: tab.path,
+ query: tab.query,
+ };
+ }
+ // 关闭当期标签,默认跳前一项
+ else if (path === this.activePath && tabIndex <= lastIndex) {
+ const tab = this.tabs[tabIndex - 1];
+ to = {
+ path: tab.path,
+ query: tab.query,
+ };
+ }
+ // 移除标签
+ this.remove(path);
+ return to;
+ },
+ /**
+ * 跳转标签
+ * @param path 当期标签路由地址
+ * @returns 新跳转push路由参数
+ */
+ tabGoto(path: string) {
+ if (!path) return null;
+ const tab = this.tabs.find(tab => tab.path === path);
+ if (!tab) return null;
+ return {
+ path: tab.path,
+ query: tab.query,
+ };
+ },
+ },
+});
+
+export default useTabsStore;
diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts
new file mode 100644
index 00000000..6930cf58
--- /dev/null
+++ b/src/store/modules/user.ts
@@ -0,0 +1,171 @@
+import defaultAvatar from '@/assets/images/default_avatar.png';
+import useLayoutStore from './layout';
+import { login, logout, getInfo } from '@/api/login';
+import { getToken, setToken, removeToken } from '@/plugins/auth-token';
+import { defineStore } from 'pinia';
+import { TOKEN_RESPONSE_FIELD } from '@/constants/token-constants';
+import { validHttp } from '@/utils/regular-utils';
+
+/**用户信息类型 */
+type UserInfo = {
+ /**授权凭证 */
+ token: string;
+ /**登录账号 */
+ userName: string;
+ /**用户角色 字符串数组 */
+ roles: string[];
+ /**用户权限 字符串数组 */
+ permissions: string[];
+ /**用户头像 */
+ avatar: string;
+ /**用户昵称 */
+ nickName: string;
+ /**用户手机号 */
+ phonenumber: string;
+ /**用户邮箱 */
+ email: string;
+ /**用户性别 */
+ sex: string | undefined;
+};
+
+/**
+ * 格式解析头像地址
+ * @param avatar 头像路径
+ * @returns url地址
+ */
+function parseAvatar(avatar: string): string {
+ if (!avatar) {
+ return defaultAvatar;
+ }
+ if (validHttp(avatar)) {
+ return avatar;
+ }
+ const baseApi = import.meta.env.VITE_API_BASE_URL;
+ return `${baseApi}${avatar}`;
+}
+
+const useUserStore = defineStore('user', {
+ state: (): UserInfo => ({
+ token: getToken(),
+ userName: '',
+ roles: [],
+ permissions: [],
+ avatar: '',
+ nickName: '',
+ phonenumber: '',
+ email: '',
+ sex: undefined,
+ }),
+ getters: {
+ /**
+ * 获取正确头像地址
+ * @param state 内部属性不用传入
+ * @returns 头像地址url
+ */
+ getAvatar(state) {
+ return parseAvatar(state.avatar);
+ },
+ /**
+ * 获取基础信息属性
+ * @param state 内部属性不用传入
+ * @returns 基础信息
+ */
+ getBaseInfo(state) {
+ return {
+ nickName: state.nickName,
+ phonenumber: state.phonenumber,
+ email: state.email,
+ sex: state.sex,
+ };
+ },
+ },
+ actions: {
+ /**
+ * 更新基础信息属性
+ * @param data 变更信息
+ */
+ setBaseInfo(data: Record) {
+ this.nickName = data.nickName;
+ this.phonenumber = data.phonenumber;
+ this.email = data.email;
+ this.sex = data.sex;
+ },
+ /**
+ * 更新头像
+ * @param avatar 上传后的地址
+ */
+ setAvatar(avatar: string) {
+ this.avatar = avatar;
+ },
+ /**
+ * 获取正确头像地址
+ * @param avatar
+ */
+ fnAvatar(avatar: string) {
+ return parseAvatar(avatar);
+ },
+ // 登录
+ async fnLogin(loginBody: Record) {
+ const res = await login(loginBody);
+ if (res.code === 200 && res.data) {
+ const token = res.data[TOKEN_RESPONSE_FIELD];
+ setToken(token);
+ this.token = token;
+ }
+ return res;
+ },
+ // 获取用户信息
+ async fnGetInfo() {
+ const res = await getInfo();
+ if (res.code === 200 && res.data) {
+ const { user, roles, permissions } = res.data;
+ // 登录账号
+ this.userName = user.userName;
+ // 用户头像
+ this.avatar = user.avatar;
+ // 基础信息
+ this.nickName = user.nickName;
+ this.phonenumber = user.phonenumber;
+ this.email = user.email;
+ this.sex = user.sex;
+
+ // 验证返回的roles是否是一个非空数组
+ if (Array.isArray(roles) && roles.length > 0) {
+ this.roles = roles;
+ this.permissions = permissions;
+ } else {
+ this.roles = ['ROLE_DEFAULT'];
+ this.permissions = [];
+ }
+
+ // 水印文字信息=用户昵称 手机号
+ let waterMarkContent = this.nickName;
+ if (this.phonenumber) {
+ waterMarkContent = `${this.nickName} ${this.phonenumber}`;
+ }
+ useLayoutStore().changeWaterMark(waterMarkContent);
+ }
+ // 网络错误时退出登录状态
+ if (res.code === 500) {
+ removeToken();
+ window.location.reload();
+ }
+ return res;
+ },
+ // 退出系统
+ async fnLogOut() {
+ try {
+ await logout();
+ } catch (error) {
+ throw error;
+ } finally {
+ this.token = '';
+ this.roles = [];
+ this.permissions = [];
+ removeToken();
+ }
+ },
+ },
+});
+
+export default useUserStore;
diff --git a/src/typings/components.d.ts b/src/typings/components.d.ts
new file mode 100644
index 00000000..9c949291
--- /dev/null
+++ b/src/typings/components.d.ts
@@ -0,0 +1,54 @@
+/* eslint-disable */
+/* prettier-ignore */
+// @ts-nocheck
+// Generated by unplugin-vue-components
+// Read more: https://github.com/vuejs/core/pull/3399
+export {}
+
+declare module 'vue' {
+ export interface GlobalComponents {
+ AAvatar: typeof import('ant-design-vue/lib')['Avatar']
+ ABadge: typeof import('ant-design-vue/lib')['Badge']
+ AButton: typeof import('ant-design-vue/lib')['Button']
+ ACol: typeof import('ant-design-vue/lib')['Col']
+ ADropdown: typeof import('ant-design-vue/lib')['Dropdown']
+ AForm: typeof import('ant-design-vue/lib')['Form']
+ AFormItem: typeof import('ant-design-vue/lib')['FormItem']
+ AImage: typeof import('ant-design-vue/lib')['Image']
+ AInput: typeof import('ant-design-vue/lib')['Input']
+ AInputPassword: typeof import('ant-design-vue/lib')['InputPassword']
+ AMenu: typeof import('ant-design-vue/lib')['Menu']
+ AMenuDivider: typeof import('ant-design-vue/lib')['MenuDivider']
+ AMenuItem: typeof import('ant-design-vue/lib')['MenuItem']
+ APopover: typeof import('ant-design-vue/lib')['Popover']
+ ARow: typeof import('ant-design-vue/lib')['Row']
+ ASpace: typeof import('ant-design-vue/lib')['Space']
+ ATabPane: typeof import('ant-design-vue/lib')['TabPane']
+ ATabs: typeof import('ant-design-vue/lib')['Tabs']
+ ATooltip: typeof import('ant-design-vue/lib')['Tooltip']
+ BellOutlined: typeof import('@ant-design/icons-vue')['BellOutlined']
+ CronModal: typeof import('./../components/CronModal/index.vue')['default']
+ Day: typeof import('./../components/CronModal/components/Day.vue')['default']
+ DictTag: typeof import('./../components/DictTag/index.vue')['default']
+ DownOutlined: typeof import('@ant-design/icons-vue')['DownOutlined']
+ GithubOutlined: typeof import('@ant-design/icons-vue')['GithubOutlined']
+ Hour: typeof import('./../components/CronModal/components/Hour.vue')['default']
+ IconFont: typeof import('./../components/IconFont/index.vue')['default']
+ LinkiFrame: typeof import('./../components/LinkiFrame/index.vue')['default']
+ LockOutlined: typeof import('@ant-design/icons-vue')['LockOutlined']
+ LogoutOutlined: typeof import('@ant-design/icons-vue')['LogoutOutlined']
+ Minute: typeof import('./../components/CronModal/components/Minute.vue')['default']
+ MobileOutlined: typeof import('@ant-design/icons-vue')['MobileOutlined']
+ Month: typeof import('./../components/CronModal/components/Month.vue')['default']
+ QqOutlined: typeof import('@ant-design/icons-vue')['QqOutlined']
+ QuestionCircleOutlined: typeof import('@ant-design/icons-vue')['QuestionCircleOutlined']
+ ReloadOutlined: typeof import('@ant-design/icons-vue')['ReloadOutlined']
+ RobotOutlined: typeof import('@ant-design/icons-vue')['RobotOutlined']
+ RouterLink: typeof import('vue-router')['RouterLink']
+ RouterView: typeof import('vue-router')['RouterView']
+ Second: typeof import('./../components/CronModal/components/Second.vue')['default']
+ SettingOutlined: typeof import('@ant-design/icons-vue')['SettingOutlined']
+ UserOutlined: typeof import('@ant-design/icons-vue')['UserOutlined']
+ WechatOutlined: typeof import('@ant-design/icons-vue')['WechatOutlined']
+ }
+}
diff --git a/src/typings/dict.d.ts b/src/typings/dict.d.ts
new file mode 100644
index 00000000..38e52f7d
--- /dev/null
+++ b/src/typings/dict.d.ts
@@ -0,0 +1,7 @@
+/**字段类型 */
+type DictType = {
+ label: string;
+ value: string;
+ elTagType: string;
+ elTagClass: string;
+};
diff --git a/src/typings/router.d.ts b/src/typings/router.d.ts
new file mode 100644
index 00000000..69983d0d
--- /dev/null
+++ b/src/typings/router.d.ts
@@ -0,0 +1,13 @@
+import 'vue-router';
+import { MetaRecord, MenuDataItem } from '@ant-design-vue/pro-layout';
+
+declare module 'vue-router' {
+ interface RouteMeta extends MetaRecord {
+ /**请求授权 */
+ requiresAuth?: boolean;
+ /**权限 */
+ permissions?: string[];
+ /**角色 */
+ roles?: string[];
+ }
+}
diff --git a/src/typings/vite-env.d.ts b/src/typings/vite-env.d.ts
new file mode 100644
index 00000000..a735ff23
--- /dev/null
+++ b/src/typings/vite-env.d.ts
@@ -0,0 +1,8 @@
+///
+
+declare module '*.vue' {
+ import { DefineComponent } from 'vue';
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
+ const component: DefineComponent<{}, {}, any>;
+ export default component;
+}
diff --git a/src/utils/cache-local-utils.ts b/src/utils/cache-local-utils.ts
new file mode 100644
index 00000000..f3d51906
--- /dev/null
+++ b/src/utils/cache-local-utils.ts
@@ -0,0 +1,40 @@
+/**长期级缓存设置 */
+export function localSet(key: string, value: string) {
+ if (!localStorage || key == null || value == null) {
+ return;
+ }
+ localStorage.setItem(key, value);
+}
+
+/**长期级缓存获取 */
+export function localGet(key: string) {
+ if (!localStorage || key == null) {
+ return null;
+ }
+ return localStorage.getItem(key);
+}
+
+/**长期级缓存移除 */
+export function localRemove(key: string) {
+ if (!localStorage || key == null) {
+ return null;
+ }
+ return localStorage.removeItem(key);
+}
+
+/**长期级缓存设置JSON */
+export function localSetJSON(key: string, jsonValue: object) {
+ if (key == null || jsonValue == null) {
+ return null;
+ }
+ localSet(key, JSON.stringify(jsonValue));
+}
+
+/**长期级缓存获取JSON */
+export function localGetJSON(key: string) {
+ const value = localGet(key);
+ if (value == null) {
+ return null;
+ }
+ return JSON.parse(value);
+}
diff --git a/src/utils/cache-session-utils.ts b/src/utils/cache-session-utils.ts
new file mode 100644
index 00000000..f3fe5022
--- /dev/null
+++ b/src/utils/cache-session-utils.ts
@@ -0,0 +1,40 @@
+/**会话级缓存设置 */
+export function sessionSet(key: string, value: string) {
+ if (!sessionStorage || key == null || value == null) {
+ return;
+ }
+ sessionStorage.setItem(key, value);
+}
+
+/**会话级缓存获取 */
+export function sessionGet(key: string) {
+ if (!sessionStorage || key == null) {
+ return null;
+ }
+ return sessionStorage.getItem(key);
+}
+
+/**会话级缓存移除 */
+export function sessionRemove(key: string) {
+ if (!sessionStorage || key == null) {
+ return null;
+ }
+ return sessionStorage.removeItem(key);
+}
+
+/**会话级缓存设置JSON */
+export function sessionSetJSON(key: string, jsonValue: object) {
+ if (key == null || jsonValue == null) {
+ return null;
+ }
+ sessionSet(key, JSON.stringify(jsonValue));
+}
+
+/**会话级缓存获取JSON */
+export function sessionGetJSON(key: string) {
+ const value = sessionGet(key);
+ if (value == null) {
+ return null;
+ }
+ return JSON.parse(value);
+}
diff --git a/src/utils/date-utils.ts b/src/utils/date-utils.ts
new file mode 100644
index 00000000..166669e2
--- /dev/null
+++ b/src/utils/date-utils.ts
@@ -0,0 +1,72 @@
+// 依赖来源 https://github.com/iamkun/dayjs
+import dayjs from 'dayjs';
+
+// 导入本地化语言并设为默认使用
+import('dayjs/locale/zh-cn');
+dayjs.locale('zh-cn');
+
+/**年 列如:2022 */
+export const YYYY = 'YYYY';
+
+/**年-月 列如:2022-12 */
+export const YYYY_MM = 'YYYY-MM';
+
+/**年-月-日 列如:2022-12-30 */
+export const YYYY_MM_DD = 'YYYY-MM-DD';
+
+/**年月日时分秒 列如:20221230010159 */
+export const YYYYMMDDHHMMSS = 'YYYYMMDDHHmmss';
+
+/**年-月-日 时:分:秒 列如:2022-12-30 01:01:59 */
+export const YYYY_MM_DD_HH_MM_SS = 'YYYY-MM-DD HH:mm:ss';
+
+/**
+ * 格式时间字符串
+ * @param dateStr 时间字符串
+ * @param formatStr 时间格式 默认YYYY-MM-DD HH:mm:ss
+ * @returns Date对象
+ */
+export function parseStrToDate(
+ dateStr: string,
+ formatStr: string = YYYY_MM_DD_HH_MM_SS
+): Date {
+ return dayjs(dateStr, formatStr).toDate();
+}
+
+/**
+ * 格式时间
+ * @param date 可转的Date对象
+ * @param formatStr 时间格式 默认YYYY-MM-DD HH:mm:ss
+ * @returns 时间格式字符串
+ */
+export function parseDateToStr(
+ date: string | number | Date,
+ formatStr: string = YYYY_MM_DD_HH_MM_SS
+): string {
+ return dayjs(date).format(formatStr);
+}
+
+/**
+ * 格式时间成日期路径
+ *
+ * 年/月 列如:2022/12
+ * @returns 时间格式字符串 YYYY/MM
+ */
+export function parseDatePath(date: number | Date = Date.now()): string {
+ return dayjs(date).format('YYYY/MM');
+}
+
+/**
+ * 判断两次时间差
+ * @param endDate 结束时间
+ * @param startDate 开始时间
+ * @returns 单位秒
+ */
+export function diffSeconds(
+ endDate: number | Date,
+ startDate: number | Date
+): number {
+ const value = Math.ceil(dayjs(endDate).diff(startDate, 'seconds'));
+ if (Number.isNaN(value)) return 0;
+ return value;
+}
diff --git a/src/utils/parse-tree-utils.ts b/src/utils/parse-tree-utils.ts
new file mode 100644
index 00000000..50866442
--- /dev/null
+++ b/src/utils/parse-tree-utils.ts
@@ -0,0 +1,188 @@
+/**
+ * 解析数据层级转树结构
+ *
+ * @param data 数组数据
+ * @param fieldId 读取节点字段 默认 'id'
+ * @param fieldParentId 读取节点父节点字段 默认 'parentId'
+ * @param fieldChildren 设置子节点字段 默认 'children'
+ * @returns 层级数组
+ */
+export function parseDataToTree(
+ data: Record[],
+ fieldId: string = 'id',
+ fieldParentId: string = 'parentId',
+ fieldChildren: string = 'children'
+) {
+ // 节点分组
+ let map: Map[]> = new Map();
+ // 节点id
+ let treeIds: string[] = [];
+ // 树节点
+ let tree: Record[] = [];
+
+ for (const item of data) {
+ let parentId = item[fieldParentId];
+ // 分组
+ let mapItem = map.get(parentId) ?? [];
+ mapItem.push(item);
+ map.set(parentId, mapItem);
+ // 记录节点id
+ treeIds.push(item[fieldId]);
+ }
+
+ for (const [key, value] of map) {
+ // 选择不是节点id的作为树节点
+ if (!treeIds.includes(key)) {
+ tree.push(...value);
+ }
+ }
+
+ for (const iterator of tree) {
+ componet(iterator);
+ }
+
+ /**闭包递归函数 */
+ function componet(iterator: Record) {
+ let id = iterator[fieldId];
+ let item = map.get(id);
+ if (item) {
+ iterator[fieldChildren] = item;
+ }
+ if (iterator[fieldChildren]) {
+ for (let i of iterator[fieldChildren]) {
+ componet(i);
+ }
+ }
+ }
+ return tree;
+}
+
+/**
+ * 解析数据层级转树结构-排除节点
+ *
+ * @param data 数组数据
+ * @param excludeField 排除节点字段 默认 'type'
+ * @param excludeValue 排除节点值 默认 '0'
+ * @param fieldId 读取节点字段 默认 'id'
+ * @param fieldParentId 读取节点父节点字段 默认 'parentId'
+ * @param fieldChildren 设置子节点字段 默认 'children'
+ * @returns 层级数组
+ */
+export function parseDataToTreeExclude(
+ data: Record[],
+ excludeField = 'type',
+ excludeValue = '0',
+ fieldId: string = 'id',
+ fieldParentId: string = 'parentId',
+ fieldChildren: string = 'children'
+) {
+ // 节点分组
+ let map: Map[]> = new Map();
+ // 节点id
+ let treeIds: string[] = [];
+ // 树节点
+ let tree: Record[] = [];
+
+ for (const item of data) {
+ // 排除值跳过
+ let exclude = item[excludeField];
+ if (exclude && exclude === excludeValue) {
+ continue;
+ }
+ let parentId = item[fieldParentId];
+ // 分组
+ let mapItem = map.get(parentId) ?? [];
+ mapItem.push(item);
+ map.set(parentId, mapItem);
+ // 记录节点id
+ treeIds.push(item[fieldId]);
+ }
+
+ for (const [key, value] of map) {
+ // 选择不是节点id的作为树节点
+ if (!treeIds.includes(key)) {
+ tree.push(...value);
+ }
+ }
+
+ for (const iterator of tree) {
+ componet(iterator);
+ }
+
+ /**闭包递归函数 */
+ function componet(iterator: Record) {
+ let id = iterator[fieldId];
+ let item = map.get(id);
+ if (item) {
+ iterator[fieldChildren] = item;
+ }
+ if (iterator[fieldChildren]) {
+ for (let i of iterator[fieldChildren]) {
+ componet(i);
+ }
+ }
+ }
+ return tree;
+}
+
+/**
+ * 解析树结构数据转出一维id数组
+ *
+ * @param data 数组数据
+ * @param fieldId 读取节点字段 默认 'id'
+ * @param fieldChildren 读取子节点字段 默认 'children'
+ * @returns 层级数组
+ */
+export function parseTreeKeys(
+ data: Record[],
+ fieldId: string = 'id',
+ fieldChildren: string = 'children'
+) {
+ // 节点id
+ let treeIds: string[] | number[] = [];
+ componet(data);
+ /**闭包递归函数 */
+ function componet(data: Record[]) {
+ if (data.length <= 0) return;
+ for (const iterator of data) {
+ let id = iterator[fieldId];
+ if (id) {
+ treeIds.push(id as never);
+ }
+ if (Array.isArray(iterator[fieldChildren])) {
+ componet(iterator[fieldChildren]);
+ }
+ }
+ }
+ return treeIds;
+}
+
+/**
+ * 解析树结构数据转出含子节点的一维id数组
+ *
+ * @param data 数组数据
+ * @param fieldId 读取节点字段 默认 'id'
+ * @param fieldChildren 读取子节点字段 默认 'children'
+ * @returns 层级数组
+ */
+export function parseTreeNodeKeys(
+ data: Record[],
+ fieldId: string = 'id',
+ fieldChildren: string = 'children'
+) {
+ // 节点id
+ let treeIds: string[] | number[] = [];
+ componet(data);
+ /**闭包递归函数 */
+ function componet(data: Record[]) {
+ if (data.length <= 0) return;
+ for (const iterator of data) {
+ let nodes = iterator[fieldChildren];
+ if (Array.isArray(nodes) && nodes.length > 0) {
+ treeIds.push(iterator[fieldId] as never);
+ componet(iterator[fieldChildren]);
+ }
+ }
+ }
+ return treeIds;
+}
diff --git a/src/utils/regular-utils.ts b/src/utils/regular-utils.ts
new file mode 100644
index 00000000..a7463473
--- /dev/null
+++ b/src/utils/regular-utils.ts
@@ -0,0 +1,67 @@
+/**
+ * 有效账号格式
+ *
+ * 账号不能以数字开头,可包含大写小写字母,数字,且不少于5位
+ */
+export const regExpUserName = /^[a-zA-Z][a-z0-9A-Z]{5,}$/;
+
+/**
+ * 有效密码格式
+ *
+ * 密码至少包含大小写字母、数字、特殊符号,且不少于6位
+ */
+export const regExpPasswd =
+ /^(?![A-Za-z0-9]+$)(?![a-z0-9\W]+$)(?![A-Za-z\W]+$)(?![A-Z0-9\W]+$)[a-zA-Z0-9\W]{6,}$/;
+
+/**
+ * 有效手机号格式
+ */
+export const regExpMobile = /^1[3|4|5|6|7|8|9][0-9]\d{8}$/;
+
+/**
+ * 有效邮箱格式
+ */
+export const regExpEmail =
+ /^(([^<>()\\.,;:\s@"]+(\.[^<>()\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+\.)+[a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]{2,}))$/;
+
+/**
+ * 有效用户昵称格式
+ *
+ * 用户昵称只能包含字母、数字、中文和下划线,且不少于2位
+ */
+export const regExpNick = /^[\w\u4e00-\u9fa5-]{2,}$/;
+
+/**
+ * 是否为http(s)://开头
+ */
+export const regExpHttp = /^http(s)?:\/\/+/;
+
+/**
+ * 判断是否为http(s)://开头
+ * @param link 网络链接
+ * @returns true | false
+ */
+export function validHttp(link: string): boolean {
+ if (!link) return false;
+ return regExpHttp.test(link);
+}
+
+/**
+ * 判断是否为有效手机号格式
+ * @param mobile 手机号字符串
+ * @returns true | false
+ */
+export function validMobile(mobile: string): boolean {
+ if (!mobile) return false;
+ return regExpMobile.test(mobile);
+}
+
+/**
+ * 判断是否为有效邮箱格式
+ * @param email 邮箱字符串
+ * @returns true | false
+ */
+export function validEmail(email: string): boolean {
+ if (!email) return false;
+ return regExpEmail.test(email);
+}
diff --git a/src/views/account/components/base-info.vue b/src/views/account/components/base-info.vue
new file mode 100644
index 00000000..5a282759
--- /dev/null
+++ b/src/views/account/components/base-info.vue
@@ -0,0 +1,248 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 确认修改
+
+
+ 重置
+
+
+
+
+
+
+ 请选择等比大小图片作为头像,如200x200、400x400
+
+
+ 上传/变更图片
+
+
+
+
+
+
+
+
+
diff --git a/src/views/account/components/reset-passwd.vue b/src/views/account/components/reset-passwd.vue
new file mode 100644
index 00000000..a4657735
--- /dev/null
+++ b/src/views/account/components/reset-passwd.vue
@@ -0,0 +1,158 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 提交修改
+
+
+
+
+
+
diff --git a/src/views/account/components/style-layout.vue b/src/views/account/components/style-layout.vue
new file mode 100644
index 00000000..c159050d
--- /dev/null
+++ b/src/views/account/components/style-layout.vue
@@ -0,0 +1,165 @@
+
+
+
+ 布局属性
+
+
+ 整体布局
+ 导航模式模块设置
+
+ changeConf('layout', e.target.value)"
+ >
+ 左侧菜单布局
+ 顶部菜单布局
+ 混合菜单布局
+
+
+
+
+ 风格配色
+ 整体风格配色设置
+
+
+
+ 随机
+
+
+
+
+
+
+ 深色菜单
+ 只能改变导航模式的菜单
+
+ changeConf('navTheme', checked ? 'dark' : 'light')
+ "
+ >
+
+
+
+ 固定顶部导航栏
+ 顶部导航栏是否固定,不随滚动条移动
+
+ changeConf('fixedHeader', checked)"
+ >
+
+
+
+ 固定左侧菜单
+ 左侧菜单是否固定,仅左侧菜单布局时有效
+
+ changeConf('fixSiderbar', checked)"
+ >
+
+
+
+ 自动分割菜单
+
+ 顶部有多级菜单时显示左侧菜单,仅混合菜单布局时有效
+
+
+ changeConf('splitMenus', checked)"
+ >
+
+
+
+ 内容区域
+
+
+ 顶栏
+ 是否显示顶部导航栏
+
+ changeConf('headerRender', checked === true && undefined)
+ "
+ >
+
+
+
+ 页脚
+ 是否显示底部导航栏
+
+ changeConf('footerRender', checked === true && undefined)
+ "
+ >
+
+
+
+ 菜单头
+ 是否显示左侧菜单栏顶部LOGO区域
+
+ changeConf('menuHeaderRender', checked === true && undefined)
+ "
+ >
+
+
+
+ 导航标签项
+ 是否显示顶部Tab导航标签项
+
+ changeConf('tabRender', checked === true && undefined)
+ "
+ >
+
+
+
+
+
+
diff --git a/src/views/account/profile.vue b/src/views/account/profile.vue
new file mode 100644
index 00000000..cc2696ce
--- /dev/null
+++ b/src/views/account/profile.vue
@@ -0,0 +1,198 @@
+
+
+
+
+
+
+
+
+
+
No:{{ state.user.userId }}
+
+
+ {{ state.user.nickName }}
+
+
+
+
+
+ {{ state.user.phonenumber || '-' }}
+
+
+ {{ state.user.email || '-' }}
+
+
+ {{ state.user.dept?.deptName || '-' }}
+
+
+ -
+
+ {{ v }}
+
+
+
+ -
+
+ {{ v }}
+
+
+
+ {{ state.user.loginIp || '-' }}
+
+
+
+ {{ parseDateToStr(+state.user.loginDate) }}
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ item.title }}
+
+
+ {{ item.description }}
+
+
+ {{ item.id }}
+
+
+
+
+
+
+
+
+ 暂无数据,尝试刷新看看
+ 刷新
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/account/settings.vue b/src/views/account/settings.vue
new file mode 100644
index 00000000..6b12f7c2
--- /dev/null
+++ b/src/views/account/settings.vue
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/dome/dome1.vue b/src/views/dome/dome1.vue
new file mode 100644
index 00000000..09380429
--- /dev/null
+++ b/src/views/dome/dome1.vue
@@ -0,0 +1,27 @@
+
+
+
+
+ Back Home
+
+
+
+
+
+
diff --git a/src/views/dome/dome2.vue b/src/views/dome/dome2.vue
new file mode 100644
index 00000000..f3fdd5c9
--- /dev/null
+++ b/src/views/dome/dome2.vue
@@ -0,0 +1,61 @@
+
+
+
+
+
+ nav 1
+ nav 2
+ nav 3
+
+
+
+
+ Home
+ List
+ App
+
+
+ Content {{ selectedKeys }}
+
+
+
+ Ant Design ©2018 Created by Ant UED
+
+
+
+
+
+
+
diff --git a/src/views/dome/dome3.vue b/src/views/dome/dome3.vue
new file mode 100644
index 00000000..3174c70d
--- /dev/null
+++ b/src/views/dome/dome3.vue
@@ -0,0 +1,30 @@
+
+
+
+ Content Area
+
+
+ Extra Area
+
+
+ ExtraContent Area
+
+
+ Tag1
+ Tag2
+
+
+
+ text block...
+ {{ i }}
+
+
+
+
+
+
diff --git a/src/views/domes/dynamic-match.vue b/src/views/domes/dynamic-match.vue
new file mode 100644
index 00000000..5249bdca
--- /dev/null
+++ b/src/views/domes/dynamic-match.vue
@@ -0,0 +1,87 @@
+
+
+
+
+ 张三
+
+ 421421
+
+ 2017-01-10
+ 2017-10-10
+
+ 中国浙江省杭州市西湖区古翠路
+
+
+
+
+ 操作
+ 操作
+ 主操作
+
+
+
+
+
+
+
+
+
+
+
+
+
+
路由参数联动 分页器 组件
+
+ 跳转上一页
+ 跳转下一页
+
+
+
+
+
+
+
+
diff --git a/src/views/domes/page-info.vue b/src/views/domes/page-info.vue
new file mode 100644
index 00000000..82119502
--- /dev/null
+++ b/src/views/domes/page-info.vue
@@ -0,0 +1,58 @@
+
+
+
+
+ 张三
+
+ 421421
+
+ 2017-01-10
+ 2017-10-10
+
+ 中国浙江省杭州市西湖区古翠路
+
+
+
+
+ 操作
+ 操作
+ 主操作
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/domes/page-typography.vue b/src/views/domes/page-typography.vue
new file mode 100644
index 00000000..89f895aa
--- /dev/null
+++ b/src/views/domes/page-typography.vue
@@ -0,0 +1,102 @@
+
+
+
+ Introduction
+
+ In the process of internal desktop applications development, many
+ different design specs and implementations would be involved, which
+ might cause designers and developers difficulties and duplication and
+ reduce the efficiency of development.
+
+
+ After massive project practice and summaries, Ant Design, a design
+ language for background applications, is refined by Ant UED Team, which
+ aims to
+
+ uniform the user interface specs for internal background projects,
+ lower the unnecessary cost of design differences and implementation
+ and liberate the resources of design and front-end development.
+
+
+ Guidelines and Resources
+
+ We supply a series of design principles, practical patterns and high
+ quality design resources (
+ Sketch
+ and
+ Axure
+ ), to help people create their product prototypes beautifully and
+ efficiently.
+
+
+
+
+
+ Resource Download
+
+
+
+
+
+ Press
+ Esc
+ to exit...
+
+
+
+
+ 介绍
+
+ 蚂蚁的企业级产品是一个庞大且复杂的体系。这类产品不仅量级巨大且功能复杂,而且变动和并发频繁,常常需要设计与开发能够快速的做出响应。同时这类产品中有存在很多类似的页面以及组件,可以通过抽象得到一些稳定且高复用性的内容。
+
+
+ 随着商业化的趋势,越来越多的企业级产品对更好的用户体验有了进一步的要求。带着这样的一个终极目标,我们(蚂蚁金服体验技术部)经过大量的项目实践和总结,逐步打磨出一个服务于企业级产品的设计体系
+ Ant Design。基于
+ 『确定』和『自然』
+ 的设计价值观,通过模块化的解决方案,降低冗余的生产成本,让设计者专注于
+ 更好的用户体验
+ 。
+
+ 设计资源
+
+ 我们提供完善的设计原则、最佳实践和设计资源文件(
+ Sketch
+ 和
+ Axure
+ ),来帮助业务快速设计出高质量的产品原型。
+
+
+
+
+
+
+
+ {{ blockContent }}
+ {{ blockContent }}
+
+
+
+ 按
+ Esc
+ 键退出阅读……
+
+
+
+
+
diff --git a/src/views/error/403.vue b/src/views/error/403.vue
new file mode 100644
index 00000000..13c215c4
--- /dev/null
+++ b/src/views/error/403.vue
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+ 返回首页
+
+ router.back()"> 返回
+
+
+
+
+
diff --git a/src/views/error/404.vue b/src/views/error/404.vue
new file mode 100644
index 00000000..14b1e60e
--- /dev/null
+++ b/src/views/error/404.vue
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+ 返回首页
+
+
+
+ 找不到网页?
+
+ 1. 尝试检查URL的错误,然后按浏览器上的刷新按钮。
+
+
+ 2. 尝试在我们的应用程序中找到其他内容。
+
+
+
+
+
+
diff --git a/src/views/index.vue b/src/views/index.vue
new file mode 100644
index 00000000..a2ed7b40
--- /dev/null
+++ b/src/views/index.vue
@@ -0,0 +1,123 @@
+
+
+
+
+
+ 当前版本:{{ appVersion }}
+ 免费开源
+
+
+ 开源仓库
+ 提些建议
+
+
+
+
+
+ {{ userStore.nickName }} ,想必你那里一切安好吧。
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Vue3
+ 技术组合,支持按钮及数据权限,可自定义部门数据权限。
+
+
+ 内置模块如:部门管理、角色用户、菜单及按钮授权、数据权限、系统参数、日志管理等,支持在线定时任务配置。
+
+
+ 使用 Ant-Design-Vue
+ 组件库,搭建的前后端分离极速后台管理系统。
+
+
+
+
+
+
+
+
+
+ 开发手册
+
+
+
+
+ 接口文档
+
+
+
+
+ Node后端
+
+
+
+ 相关待定
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/login.vue b/src/views/login.vue
new file mode 100644
index 00000000..307f8921
--- /dev/null
+++ b/src/views/login.vue
@@ -0,0 +1,469 @@
+
+
+
+
+
+
+
{{ t('common.desc') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ router.push({ name: 'Register' })"
+ >
+ {{ t('views.login.registerBtn') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{
+ smsState.click
+ ? `${smsState.time} s`
+ : t('valid.codeText')
+ }}
+
+
+
+
+
+
+
+
+ {{ t('views.login.loginBtn') }}
+
+
+
+
+ {{ t('views.login.loginMethod') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ t('i18n') }}
+
+
+
+
+ 中文
+ English
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/monitor/cache/index.vue b/src/views/monitor/cache/index.vue
new file mode 100644
index 00000000..4421250d
--- /dev/null
+++ b/src/views/monitor/cache/index.vue
@@ -0,0 +1,498 @@
+
+
+
+
+
+
+ 系统在缓存
+ Redis
+ 应用程序中的可控的缓存信息
+
+
+
+
+
+
+
+
+
+ 刷新
+
+
+
+
+
+ 安全清理
+
+
+
+
+
+
+
+
+
+
+
+
setSelectedKeys(e.target.value ? [e.target.value] : [])
+ "
+ @pressEnter="confirm()"
+ />
+
+ 过滤
+
+
+ 重置
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 刷新
+
+
+
+
+
+
+
+
+
setSelectedKeys(e.target.value ? [e.target.value] : [])
+ "
+ @pressEnter="confirm()"
+ />
+
+ 过滤
+
+
+ 重置
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ cacheKeyInfo.data.cacheName }}
+
+
+ {{ cacheKeyInfo.data.cacheKey }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/monitor/cache/info.vue b/src/views/monitor/cache/info.vue
new file mode 100644
index 00000000..18c78a34
--- /dev/null
+++ b/src/views/monitor/cache/info.vue
@@ -0,0 +1,222 @@
+
+
+
+
+
+
+ 缓存
+ Redis
+ 应用程序的信息
+
+
+
+
+
+
+ {{ cache.info.server.redis_version }}
+
+
+ {{ cache.info.server.redis_mode == 'standalone' ? '单机' : '集群' }}
+
+
+ {{ cache.info.server.tcp_port }}
+
+
+ {{ cache.info.clients.connected_clients }}
+
+
+ {{ cache.info.server.uptime_in_days }}
+
+
+ {{ cache.info.memory.used_memory_human }}
+
+
+ {{ parseFloat(cache.info.cpu.used_cpu_user_children).toFixed(2) }}
+
+
+ {{ cache.info.memory.maxmemory_human }}
+
+
+ {{ cache.info.persistence.aof_enabled == '0' ? '否' : '是' }}
+
+
+ {{ cache.info.persistence.rdb_last_bgsave_status }}
+
+
+ {{ cache.dbSize }}
+
+
+ {{ cache.info.stats.instantaneous_input_kbps }} kps /
+ {{ cache.info.stats.instantaneous_output_kbps }} kps
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/monitor/job/index.vue b/src/views/monitor/job/index.vue
new file mode 100644
index 00000000..aa4c5094
--- /dev/null
+++ b/src/views/monitor/job/index.vue
@@ -0,0 +1,1087 @@
+
+
+
+
+
+
+ Nodejs
+ 使用
+ Bull
+ 基于
+ Redis
+ 的任务队列。
+ Golang
+ 使用
+ Cron
+ 定时任务管理。
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+
+
+ 重置
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 新建
+
+
+
+ 删除
+
+
+
+ 导出
+
+
+
+ 日志
+
+
+
+ 重置队列
+
+
+
+
+
+
+
+
+ 搜索栏
+
+
+
+ 表格斑马纹
+
+
+
+ 刷新
+
+
+
+
+
+ 密度
+
+
+
+
+
+
+ 默认
+ 中等
+ 紧凑
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 查看详情
+
+
+
+
+
+ 编辑
+
+
+
+
+
+ 删除
+
+
+
+
+
+ 执行一次
+
+
+
+
+
+ 任务日志
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ modalState.from.jobName }}
+
+
+
+
+ {{
+ ['立即执行', '执行一次', '放弃执行'][
+ +modalState.from.misfirePolicy - 1
+ ]
+ }}
+
+
+
+
+ {{ ['禁止', '允许'][+modalState.from.concurrent] }}
+
+
+
+
+
+
+
+ {{ modalState.from.invokeTarget }}
+
+
+
+
+
+
+
+
+
+ {{ ['暂停', '正常'][+modalState.from.status] }}
+
+
+
+
+
+
+
+ {{
+ modalState.from.cronExpression
+ }}
+
+
+
+
+
+ {{ parseDateToStr(+modalState.from.createTime) }}
+
+
+
+
+
+
+ {{ modalState.from.targetParams }}
+
+
+
+ {{ modalState.from.remark }}
+
+
+
+ 关闭
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 立即执行
+ 执行一次
+ 放弃执行
+
+
+
+
+
+
+ 允许
+ 禁止
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Processor调用示例:simple
+ 定义任务处理器示例:src\modules\monitor\processor
+ 参数说明:支持预设传入参数,在处理器中进行序列化处理参数
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 表达式示例:0/20 * * * * ?
+ 示例说明:每20秒执行任务
+
+
+
+
+
+
+
+
+ 生成表达式
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/monitor/job/log.vue b/src/views/monitor/job/log.vue
new file mode 100644
index 00000000..15481346
--- /dev/null
+++ b/src/views/monitor/job/log.vue
@@ -0,0 +1,682 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+
+
+
+ 重置
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 关闭
+
+
+
+ 删除
+
+
+
+ 清空
+
+
+
+ 导出
+
+
+
+
+
+
+
+
+ 搜索栏
+
+
+
+ 表格斑马纹
+
+
+
+ 刷新
+
+
+
+
+
+ 密度
+
+
+
+
+
+
+ 默认
+ 中等
+ 紧凑
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ ['失败', '正常'][+record.status] }}
+
+
+
+
+
+ 查看详情
+
+
+ 详情
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ modalState.from.jobLogId }}
+
+
+
+
+
+ {{ ['失败', '正常'][+modalState.from.status] }}
+
+
+
+
+
+
+
+ {{ modalState.from.jobName }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ modalState.from.invokeTarget }}
+
+
+
+
+
+ {{ parseDateToStr(+modalState.from.createTime) }}
+
+
+
+
+
+
+
+
+
+
+
+
+ 关闭
+
+
+
+
+
+
diff --git a/src/views/monitor/logininfor/index.vue b/src/views/monitor/logininfor/index.vue
new file mode 100644
index 00000000..69b0a303
--- /dev/null
+++ b/src/views/monitor/logininfor/index.vue
@@ -0,0 +1,546 @@
+
+
+
+
+
+
+ 对登录进行日志收集,登录锁定的信息存入
+ Redis
+ 可对登录账号进行解锁。
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+
+
+ 重置
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 解锁
+
+
+
+ 删除
+
+
+
+ 清空
+
+
+
+ 导出
+
+
+
+
+
+
+
+
+ 搜索栏
+
+
+
+ 表格斑马纹
+
+
+
+ 刷新
+
+
+
+
+
+ 密度
+
+
+
+
+
+
+ 默认
+ 中等
+ 紧凑
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/monitor/online/index.vue b/src/views/monitor/online/index.vue
new file mode 100644
index 00000000..e151ff27
--- /dev/null
+++ b/src/views/monitor/online/index.vue
@@ -0,0 +1,338 @@
+
+
+
+
+
+
+ 登录用户
+ Token
+ 授权标识记录,存储在
+ Redis
+ 中,可撤销对用户的授权,拒绝用户请求并强制退出。
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+
+
+
+ 重置
+
+
+
+
+
+
+
+
+
+
+
+ {{ title }}
+
+
+
+
+
+
+ 搜索栏
+
+
+
+ 表格斑马纹
+
+
+
+ 刷新
+
+
+
+
+
+ 密度
+
+
+
+
+
+
+ 默认
+ 中等
+ 紧凑
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 强退
+
+
+
+
+
+
+
+
+
diff --git a/src/views/monitor/operlog/index.vue b/src/views/monitor/operlog/index.vue
new file mode 100644
index 00000000..faf3e0e0
--- /dev/null
+++ b/src/views/monitor/operlog/index.vue
@@ -0,0 +1,692 @@
+
+
+
+
+
+
+ 对接口请求进行日志收集,统计高频接口分析优化等操作。
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+
+
+ 重置
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 删除
+
+
+
+ 清空
+
+
+
+ 导出
+
+
+
+
+
+
+
+
+ 搜索栏
+
+
+
+ 表格斑马纹
+
+
+
+ 刷新
+
+
+
+
+
+ 密度
+
+
+
+
+
+
+ 默认
+ 中等
+ 紧凑
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 查看详情
+
+
+ 详情
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ modalState.from.operId }}
+
+
+
+
+
+ {{ ['失败', '正常'][+modalState.from.status] }}
+
+
+
+
+
+
+
+ {{ modalState.from.title }} /
+
+
+
+
+
+ {{ modalState.from.operName }} / {{ modalState.from.operIp }} /
+ {{ modalState.from.operLocation }}
+
+
+
+
+
+
+ {{ modalState.from.requestMethod }} -
+ {{ modalState.from.operUrl }}
+
+
+
+
+
+ {{ parseDateToStr(+modalState.from.operTime) }}
+
+
+
+
+
+
+
+ {{ modalState.from.costTime }} ms
+
+
+
+
+ {{ modalState.from.method }}
+
+
+
+
+
+
+
+
+
+
+
+ 关闭
+
+
+
+
+
+
diff --git a/src/views/monitor/server/info.vue b/src/views/monitor/server/info.vue
new file mode 100644
index 00000000..16aa9721
--- /dev/null
+++ b/src/views/monitor/server/info.vue
@@ -0,0 +1,329 @@
+
+
+
+
+
+ 服务器与应用程序的信息
+
+
+
+
+
+ {{ server.project.name }}
+
+
+ {{ server.project.version }}
+
+
+
+ {{ server.project.env }}
+
+
+ {{ server.project.appDir }}
+
+
+
+ {{ name }}:{{ value }}
+
+
+
+
+
+
+
+
+ {{ server.system.go }}
+
+
+ {{ server.system.node }}
+
+
+ {{ server.system.v8 }}
+
+
+ {{ server.system.processId }}
+
+
+ {{ server.system.platform }}
+
+
+ {{ server.system.arch }}
+
+
+ {{ server.system.uname }}
+
+
+ {{ server.system.release }}
+
+
+ {{ server.system.hostname }}
+
+
+ {{ server.system.homeDir }}
+
+
+ {{ server.system.cmd }}
+
+
+ {{ server.system.execCommand }}
+
+
+
+
+
+
+
+ {{ server.cpu.model }}
+
+
+ {{ server.cpu.speed }}
+
+
+ {{ server.cpu.core }}
+
+
+ {{ server.cpu.coreUsed }}
+
+
+
+
+
+
+
+ {{ server.memory.totalmem }}
+
+
+ {{ server.memory.freemem }}
+
+
+ {{ server.memory.usage }}
+
+
+ {{ server.memory.rss }}
+
+
+ {{ server.memory.heapTotal }}
+
+
+ {{ server.memory.heapUsed }}
+
+
+ {{ server.memory.external }}
+
+
+
+
+
+
+
+ {{ server.time.timezone }}
+
+
+ {{ server.time.current }}
+
+
+ {{ server.time.timezoneName }}
+
+
+ {{ server.time.uptime }}
+
+
+
+
+
+
+
+ {{ value }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/redirect/index.vue b/src/views/redirect/index.vue
new file mode 100644
index 00000000..0eaf77c1
--- /dev/null
+++ b/src/views/redirect/index.vue
@@ -0,0 +1,11 @@
+
+
+
+ 稍等...
+
diff --git a/src/views/register.vue b/src/views/register.vue
new file mode 100644
index 00000000..fb66e29e
--- /dev/null
+++ b/src/views/register.vue
@@ -0,0 +1,313 @@
+
+
+
+
+
+
+
{{ t('common.desc') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ t('views.register.registerBtn') }}
+
+ router.push({ name: 'Login' })"
+ >
+ {{ t('views.register.loginBtn') }}
+
+
+
+
+
+
+
diff --git a/src/views/system/config/index.vue b/src/views/system/config/index.vue
new file mode 100644
index 00000000..5b72c333
--- /dev/null
+++ b/src/views/system/config/index.vue
@@ -0,0 +1,809 @@
+
+
+
+
+
+
+ 系统内可配置的参数变量。
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+
+
+ 重置
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 新建
+
+
+
+ 删除
+
+
+
+ 刷新缓存
+
+
+
+ 导出
+
+
+
+
+
+
+
+
+ 搜索栏
+
+
+
+ 表格斑马纹
+
+
+
+ 刷新
+
+
+
+
+
+ 密度
+
+
+
+
+
+
+ 默认
+ 中等
+ 紧凑
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 查看详情
+
+
+
+
+
+ 编辑
+
+
+
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ modalState.from.configName }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ modalState.from.configKey }}
+
+
+
+
+ {{ modalState.from.configValue }}
+
+
+
+
+ {{ modalState.from.remark }}
+
+
+
+ 关闭
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/dept/index.vue b/src/views/system/dept/index.vue
new file mode 100644
index 00000000..da6fa754
--- /dev/null
+++ b/src/views/system/dept/index.vue
@@ -0,0 +1,800 @@
+
+
+
+
+
+ 给予用户部门标记
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+
+
+ 重置
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 新建
+
+
+
+
+
+
+
+
+ 展开/折叠
+
+
+
+ 搜索栏
+
+
+
+ 表格斑马纹
+
+
+
+ 刷新
+
+
+
+
+
+ 密度
+
+
+
+
+
+
+ 默认
+ 中等
+ 紧凑
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 查看详情
+
+
+
+
+
+ 编辑
+
+
+
+
+
+ 删除
+
+
+
+
+
+ 新增子部门
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ modalState.from.orderNum }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ modalState.from.deptId }}
+
+
+
+
+
+
+ {{ modalState.from.deptName }}
+
+
+
+
+ {{ modalState.from.leader }}
+
+
+
+
+
+
+ {{ modalState.from.phone }}
+
+
+
+
+ {{ modalState.from.email }}
+
+
+
+
+
+ 关闭
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/dict/data.vue b/src/views/system/dict/data.vue
new file mode 100644
index 00000000..4085c4e7
--- /dev/null
+++ b/src/views/system/dict/data.vue
@@ -0,0 +1,883 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+
+
+ 重置
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 关闭
+
+
+
+ 新增
+
+
+
+ 删除
+
+
+
+ 导出
+
+
+
+
+
+
+
+
+ 搜索栏
+
+
+
+ 表格斑马纹
+
+
+
+ 刷新
+
+
+
+
+
+ 密度
+
+
+
+
+
+
+ 默认
+ 中等
+ 紧凑
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 查看详情
+
+
+
+
+
+ 编辑
+
+
+
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{
+ dict.sysDictType.find(
+ item => item.value === modalState.from.dictType
+ )?.label
+ }}
+
+
+
+
+
+ {{ parseDateToStr(+modalState.from.createTime) }}
+
+
+
+
+
+
+
+ {{ modalState.from.dictCode }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ modalState.from.dictLabel }}
+
+
+
+
+ {{ modalState.from.dictValue }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ modalState.from.tagClass }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ modalState.from.dictSort }}
+
+
+
+
+ {{ modalState.from.remark }}
+
+
+
+ 关闭
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/dict/index.vue b/src/views/system/dict/index.vue
new file mode 100644
index 00000000..a895ba30
--- /dev/null
+++ b/src/views/system/dict/index.vue
@@ -0,0 +1,809 @@
+
+
+
+
+
+
+ 数据字典类型,数据名称对应的代码值映射数据。
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+
+
+ 重置
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 新建
+
+
+
+ 删除
+
+
+
+ 字典数据
+
+
+
+ 刷新缓存
+
+
+
+ 导出
+
+
+
+
+
+
+
+
+ 搜索栏
+
+
+
+ 表格斑马纹
+
+
+
+ 刷新
+
+
+
+
+
+ 密度
+
+
+
+
+
+
+ 默认
+ 中等
+ 紧凑
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 查看详情
+
+
+
+
+
+ 编辑
+
+
+
+
+
+ 删除
+
+
+
+
+
+ 字典数据
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ modalState.from.dictId }}
+
+
+
+
+
+
+
+
+
+
+ {{ modalState.from.dictName }}
+
+
+ {{ modalState.from.dictType }}
+
+
+ {{ modalState.from.remark }}
+
+
+
+ 关闭
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/menu/index.vue b/src/views/system/menu/index.vue
new file mode 100644
index 00000000..7d29d392
--- /dev/null
+++ b/src/views/system/menu/index.vue
@@ -0,0 +1,1103 @@
+
+
+
+
+
+
+ 动态路由菜单,根节点下不要创建菜单哦
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+
+
+ 重置
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 新建
+
+
+
+
+
+
+
+
+ 展开/折叠
+
+
+
+ 搜索栏
+
+
+
+ 表格斑马纹
+
+
+
+ 刷新
+
+
+
+
+
+ 密度
+
+
+
+
+
+
+ 默认
+ 中等
+ 紧凑
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ ['隐藏', '显示'][+record.visible] }}
+
+
+
+
+
+
+
+
+ 查看详情
+
+
+
+
+
+ 编辑
+
+
+
+
+
+ 删除
+
+
+
+
+
+ 新增子菜单
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ modalState.from.menuId }}
+
+
+
+
+ {{ modalState.from.menuSort }}
+
+
+
+
+
+
+ {{ modalState.from.menuName }}
+
+
+
+
+
+ 目录
+
+
+ 菜单
+
+
+ 按钮
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ modalState.from.path }}
+
+
+
+
+ {{ modalState.from.component }}
+
+
+
+
+
+
+
+
+ {{ ['否', '是'][+modalState.from.isFrame] }}
+
+
+
+
+
+
+ {{ ['不缓存', '缓存'][+modalState.from.isCache] }}
+
+
+
+
+
+
+ {{ ['隐藏', '显示'][+modalState.from.visible] }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ modalState.from.perms }}
+
+
+
+ {{ modalState.from.remark }}
+
+
+
+ 关闭
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 目录
+
+
+ 菜单
+
+
+ 按钮
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ label }}
+
+
+
+
+
+
+
+
+
+
+
+
+ 访问的路由地址,如:user、/auth
+
+ 1. 如网络地址需内部访问 则以 http(s):// 开头
+ 菜单行为(根节点):当前窗口打开
+ 菜单行为(非根节点):内嵌窗口
+
+ 2. 如网络地址需外部访问 则将内部地址选项设为否
+ 菜单行为:打开新标签
+
+ 3. 如内嵌子页面需要隐藏页面 则将显示状态选项设为隐藏
+ 地址拼接以内嵌路由地址
+ {{ MENU_PATH_INLINE }}/子页面地址
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 否
+ 是
+
+
+
+
+
+
+ 不缓存
+ 缓存
+
+
+
+
+
+
+ 隐藏
+ 显示
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 页面组件目录 views
+ 访问的组件路径,如:system/user/index
+ 注意:不带 .vue 文件后缀
+ 路由地址是网络地址可填入链接
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 权限标识示例:monitor:server:query
+ 后端控制器中使用权限标识,如:
+ @PreAuthorize({ hasPermissions: ['monitor:server:query'] })
+
+ 前端vue页面中使用权限标识,如:
+ v-perms:has="['monitor:server:query']"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/notice/index.vue b/src/views/system/notice/index.vue
new file mode 100644
index 00000000..da1372ee
--- /dev/null
+++ b/src/views/system/notice/index.vue
@@ -0,0 +1,734 @@
+
+
+
+
+
+
+ 发布公告给內部用户的通知。
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+
+
+ 重置
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 新建
+
+
+
+ 删除
+
+
+
+
+
+
+
+
+ 搜索栏
+
+
+
+ 表格斑马纹
+
+
+
+ 刷新
+
+
+
+
+
+ 密度
+
+
+
+
+
+
+ 默认
+ 中等
+ 紧凑
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 查看详情
+
+
+
+
+
+ 编辑
+
+
+
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ modalState.from.noticeTitle }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ modalState.from.noticeContent }}
+
+
+
+ 关闭
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/post/index.vue b/src/views/system/post/index.vue
new file mode 100644
index 00000000..92e93a35
--- /dev/null
+++ b/src/views/system/post/index.vue
@@ -0,0 +1,759 @@
+
+
+
+
+
+ 给予用户岗位标记
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+
+
+ 重置
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 新建
+
+
+
+ 删除
+
+
+
+ 导出
+
+
+
+
+
+
+
+
+ 搜索栏
+
+
+
+ 表格斑马纹
+
+
+
+ 刷新
+
+
+
+
+
+ 密度
+
+
+
+
+
+
+ 默认
+ 中等
+ 紧凑
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 查看详情
+
+
+
+
+
+ 编辑
+
+
+
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ modalState.from.postId }}
+
+
+
+
+
+ {{ parseDateToStr(+modalState.from.createTime) }}
+
+
+
+
+
+
+
+ {{ modalState.from.postSort }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ modalState.from.postCode }}
+
+
+
+
+ {{ modalState.from.postName }}
+
+
+
+
+ {{ modalState.from.remark }}
+
+
+
+ 关闭
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/role/auth-user.vue b/src/views/system/role/auth-user.vue
new file mode 100644
index 00000000..722872f0
--- /dev/null
+++ b/src/views/system/role/auth-user.vue
@@ -0,0 +1,508 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+
+
+ 重置
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 关闭
+
+
+
+ 分配用户
+
+
+
+ 批量取消授权
+
+
+
+
+
+
+
+
+ 搜索栏
+
+
+
+ 表格斑马纹
+
+
+
+ 刷新
+
+
+
+
+
+ 密度
+
+
+
+
+
+
+ 默认
+ 中等
+ 紧凑
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 取消授权
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/role/components/auth-user-select.vue b/src/views/system/role/components/auth-user-select.vue
new file mode 100644
index 00000000..19746b36
--- /dev/null
+++ b/src/views/system/role/components/auth-user-select.vue
@@ -0,0 +1,301 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+
+
+ 重置
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/role/index.vue b/src/views/system/role/index.vue
new file mode 100644
index 00000000..25a1e796
--- /dev/null
+++ b/src/views/system/role/index.vue
@@ -0,0 +1,1281 @@
+
+
+
+
+
+
+ 给予用户角色标记,可分配给用户多个角色,分配数据权限需要关联部门数据表进行相关配置生效。
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+
+
+
+ 重置
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 新建
+
+
+
+ 删除
+
+
+
+ 导出
+
+
+
+
+
+
+
+
+ 搜索栏
+
+
+
+ 表格斑马纹
+
+
+
+ 刷新
+
+
+
+
+
+ 密度
+
+
+
+
+
+
+ 默认
+ 中等
+ 紧凑
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 查看详情
+
+
+
+
+
+ 编辑
+
+
+
+
+
+ 删除
+
+
+
+
+
+ 分配数据权限
+
+
+
+
+
+ 分配用户
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ modalState.from.roleId }}
+
+
+
+
+
+ {{ parseDateToStr(+modalState.from.createTime) }}
+
+
+
+
+
+
+
+ {{ modalState.from.roleSort }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ modalState.from.roleName }}
+
+
+
+
+ {{ modalState.from.roleKey }}
+
+
+
+
+ {{ modalState.from.remark }}
+
+
+
+
+
+
+
+
+ 关闭
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 权限标识示例:dba
+ 控制器中使用权限标识,如:
+ @PreAuthorize({ hasRoles: ['dba'] })
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ fnModalTreeChecked(checked, info, 'menu')
+ "
+ v-model:expanded-keys="modalState.menuTree.expandedKeys"
+ v-model:checked-keys="modalState.menuTree.checkedKeys"
+ :check-strictly="modalState.from.menuCheckStrictly === '0'"
+ :tree-data="modalState.menuTree.treeData"
+ :field-names="{ children: 'children', title: 'label', key: 'id' }"
+ :height="256"
+ style="border: 1px solid #d9d9d9; margin-top: 4px"
+ >
+
+
+
+
+
+
+
+
+
+
+
+ {{ modalState.from.roleId }}
+
+
+
+
+
+ {{ parseDateToStr(+modalState.from.createTime) }}
+
+
+
+
+
+
+
+ {{ modalState.from.roleSort }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ modalState.from.roleName }}
+
+
+
+
+ {{ modalState.from.roleKey }}
+
+
+
+
+ {{ modalState.from.remark }}
+
+
+
+
+
+
+
+
+ fnModalExpandedKeys(e.target.checked, 'dept')"
+ >
+ 展开/折叠
+
+ fnModalCheckedKeys(e.target.checked, 'dept')"
+ >
+ 全选/全不选
+
+ fnModalCheckStrictly(e.target.checked, 'dept')"
+ >
+ 父子联动
+
+
+ fnModalTreeChecked(checked, info, 'dept')
+ "
+ v-model:expanded-keys="modalState.deptTree.expandedKeys"
+ v-model:checked-keys="modalState.deptTree.checkedKeys"
+ :check-strictly="modalState.from.deptCheckStrictly === '0'"
+ :tree-data="modalState.deptTree.treeData"
+ :field-names="{ children: 'children', title: 'label', key: 'id' }"
+ :height="256"
+ style="border: 1px solid #d9d9d9; margin-top: 4px"
+ >
+
+
+
+
+
+
+
+
diff --git a/src/views/system/user/components/UploadXlsxImport.vue b/src/views/system/user/components/UploadXlsxImport.vue
new file mode 100644
index 00000000..66164491
--- /dev/null
+++ b/src/views/system/user/components/UploadXlsxImport.vue
@@ -0,0 +1,185 @@
+
+
+
+
+
+
+
+
+
+ 点击选择或将文件拖入边框区域进行上传
+
+ 仅允许导入
+ {{ props.fileExt.join('、') }}
+ 格式文件,上传文件大小
+ {{ props.fileSize }}
+ MB。
+
+
+
+
+
+ 是否更新已经存在的数据
+
+
+
+
+ 下载模板
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/user/index.vue b/src/views/system/user/index.vue
new file mode 100644
index 00000000..9d58f15a
--- /dev/null
+++ b/src/views/system/user/index.vue
@@ -0,0 +1,1433 @@
+
+
+
+
+
+ 所有系统用户管理列表
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+
+
+
+ 重置
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 新建
+
+
+
+ 删除
+
+
+
+ 导入
+
+
+
+ 导出
+
+
+
+
+
+
+
+
+ 搜索栏
+
+
+
+ 表格斑马纹
+
+
+
+ 刷新
+
+
+
+
+
+ 密度
+
+
+
+
+
+
+ 默认
+ 中等
+ 紧凑
+
+
+
+
+
+
+
+
+
+
+
+ {{ record.dept?.deptName }}
+
+
+
+
+
+
+
+
+ 查看详情
+
+
+
+
+
+ 编辑
+
+
+
+
+
+ 删除
+
+
+
+
+
+ 重置密码
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ modalState.from.userId }}
+
+
+
+
+
+ {{ parseDateToStr(+modalState.from.createTime) }}
+
+
+
+
+
+
+
+ {{ modalState.from.loginIp }}
+
+
+
+
+
+ {{ parseDateToStr(+modalState.from.loginDate) }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ modalState.from.userName }}
+
+
+
+
+
+
+ {{ modalState.from.nickName }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ modalState.from.phonenumber }}
+
+
+
+
+ {{ modalState.from.email }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ modalState.from.remark }}
+
+
+
+ 关闭
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/tool/build/index.vue b/src/views/tool/build/index.vue
new file mode 100644
index 00000000..a66e68de
--- /dev/null
+++ b/src/views/tool/build/index.vue
@@ -0,0 +1,11 @@
+
+
+
+ {{ msg }}
+
+
+
diff --git a/src/views/tool/swagger/index.vue b/src/views/tool/swagger/index.vue
new file mode 100644
index 00000000..c7703370
--- /dev/null
+++ b/src/views/tool/swagger/index.vue
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
diff --git a/src/views/tool/upload/index.vue b/src/views/tool/upload/index.vue
new file mode 100644
index 00000000..edd1ed9c
--- /dev/null
+++ b/src/views/tool/upload/index.vue
@@ -0,0 +1,236 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ 普通下载
+
+
+
+
+
+
+ 分片下载
+
+
+
+
+
+
+
+
+ 选择文件
+
+
+
+
+
+
+
+
+
+
+
+ 选择文件
+
+
+
+
+
+
+
+
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 00000000..11702116
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,24 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "useDefineForClassFields": true,
+ "module": "ESNext",
+ "moduleResolution": "Node",
+ "strict": true,
+ "jsx": "preserve",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "esModuleInterop": true,
+ "lib": ["ESNext", "DOM"],
+ "skipLibCheck": true,
+ "noEmit": true,
+ "typeRoots": ["src/typings", "./node_modules/@types"],
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["src/*"]
+ }
+ },
+ "exclude": ["dist", "node_modules", "test", "script"],
+ "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
+ "references": [{ "path": "./tsconfig.node.json" }]
+}
diff --git a/tsconfig.node.json b/tsconfig.node.json
new file mode 100644
index 00000000..9d31e2ae
--- /dev/null
+++ b/tsconfig.node.json
@@ -0,0 +1,9 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "module": "ESNext",
+ "moduleResolution": "Node",
+ "allowSyntheticDefaultImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 00000000..ac6c2a93
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,76 @@
+import vue from '@vitejs/plugin-vue';
+import { defineConfig, loadEnv } from 'vite';
+import Components from 'unplugin-vue-components/vite';
+import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers';
+import Compression from 'vite-plugin-compression';
+import path from 'path';
+
+// https://vitejs.dev/config/
+export default defineConfig(({ mode }) => {
+ // 读取环境配置变量,指定前缀
+ const env = loadEnv(mode, process.cwd(), 'VITE_');
+ return {
+ // 访问基础路径
+ base: env.VITE_HISTORY_BASE_URL,
+ // 本地开发服务配置
+ server: {
+ port: 6269, // 端口
+ host: true, // 暴露到网络地址
+ open: false, // 完成后自动跳转浏览器打开
+ proxy: {
+ // https://cn.vitejs.dev/config/#server-proxy
+ [env.VITE_API_BASE_URL]: {
+ target: 'http://192.168.56.1:6275',
+ changeOrigin: true,
+ rewrite: p => p.replace(/^\/dev-api/, ''),
+ },
+ },
+ },
+ resolve: {
+ // 资源别名
+ alias: {
+ '@': path.resolve(__dirname, './src'),
+ },
+ extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'],
+ },
+ css: {
+ preprocessorOptions: {
+ less: {
+ // DO NOT REMOVE THIS LINE
+ javascriptEnabled: true,
+ modifyVars: {
+ // hack: `true; @import 'ant-design-vue/dist/antd.variable.less'`,
+ // '@primary-color': '#eb2f96', // 全局主色
+ },
+ },
+ },
+ },
+ optimizeDeps: {
+ include: ['@ant-design/icons-vue', 'ant-design-vue'],
+ },
+ plugins: [
+ vue(),
+ // Vue文件中自动导入组件,dirs目录和antd库
+ Components({
+ dts: 'src/typings/components.d.ts',
+ deep: true,
+ dirs: ['src/components'],
+ extensions: ['vue', 'tsx'],
+ resolvers: [
+ AntDesignVueResolver({
+ importStyle: false,
+ resolveIcons: true,
+ cjs: true, // 避免es模块打包缺失
+ }),
+ ],
+ }),
+ // gzip静态压缩文件
+ Compression({
+ verbose: false,
+ algorithm: 'gzip',
+ ext: '.gz',
+ disable: false, // 是否禁用
+ }),
+ ],
+ };
+});