mirror of
https://github.com/m-xlsea/ruoyi-plus-soybean.git
synced 2025-09-24 07:49:47 +08:00
Compare commits
26 Commits
8a453f5852
...
dev
Author | SHA1 | Date | |
---|---|---|---|
2c248d82f2 | |||
28101cb2f1 | |||
4e27f3b5a5 | |||
e623b560e4 | |||
8aeb73627a | |||
3f148a4e62 | |||
34ab7d5da2 | |||
513dc31eaa | |||
dc2fbbd556 | |||
56fd5434ca | |||
ad207255bb | |||
3146c039f0 | |||
d5bbc37dec | |||
2f794c4b73 | |||
378aa869bf | |||
4a4244b5c4 | |||
ecad1c3e78 | |||
9ef0bd416e | |||
25ee32074a | |||
8412a8db16 | |||
e230b0da81 | |||
12b25e0d58 | |||
e33f944a74 | |||
7f2f3bd088 | |||
5ef1c5de98 | |||
90a14e338a |
@ -16,7 +16,7 @@
|
|||||||
},
|
},
|
||||||
"playwright": {
|
"playwright": {
|
||||||
"command": "npx",
|
"command": "npx",
|
||||||
"args": ["@playwright/mcp@0.0.29"]
|
"args": ["@playwright/mcp@latest"]
|
||||||
},
|
},
|
||||||
"mcp-server-time": {
|
"mcp-server-time": {
|
||||||
"command": "uvx",
|
"command": "uvx",
|
||||||
@ -26,14 +26,21 @@
|
|||||||
"command": "npx",
|
"command": "npx",
|
||||||
"args": ["-y", "mcp-shrimp-task-manager"],
|
"args": ["-y", "mcp-shrimp-task-manager"],
|
||||||
"env": {
|
"env": {
|
||||||
"DATA_DIR": "D:/workspace/mcp-shrimp-task-manager/data",
|
"DATA_DIR": "D:/workspace/tools/mcp-shrimp-task-manager/data",
|
||||||
"TEMPLATES_USE": "en",
|
"TEMPLATES_USE": "en",
|
||||||
"ENABLE_GUI": "false"
|
"ENABLE_GUI": "true"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mcp-deepwiki": {
|
"mcp-deepwiki": {
|
||||||
"command": "npx",
|
"command": "npx",
|
||||||
"args": ["-y", "mcp-deepwiki@latest"]
|
"args": ["-y", "mcp-deepwiki@latest"]
|
||||||
|
},
|
||||||
|
"memory": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": ["-y", "@modelcontextprotocol/server-memory"],
|
||||||
|
"env": {
|
||||||
|
"MEMORY_FILE_PATH": "D:/workspace/tools/server-memory/memory.json"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
---
|
---
|
||||||
description:
|
|
||||||
globs:
|
|
||||||
alwaysApply: false
|
alwaysApply: false
|
||||||
---
|
---
|
||||||
|
|
||||||
**# RIPER-5 + 多维度思维 + 代理执行协议 (v4.9.1 - MCP工具驱动版)**
|
**# RIPER-5 + 多维度思维 + 代理执行协议 (v4.9.1 - MCP工具驱动版)**
|
||||||
|
|
||||||
**元指令:** 此协议旨在最大化你的战略规划与执行效率。你的核心任务是**指挥和利用MCP工具集**来驱动项目进展。严格遵守核心原则,利用 `mcp-shrimp-task-manager` 进行项目规划与追踪,使用 `deepwiki-mcp` 进行深度研究。主动管理 `/project_document` 作为知识库。**每轮主要响应后,调用 `mcp.feedback_enhanced` 进行交互或通知。**
|
**元指令:** 此协议旨在最大化你的战略规划与执行效率。你的核心任务是**指挥和利用MCP工具集**来驱动项目进展。严格遵守核心原则,利用 `mcp-shrimp-task-manager` 进行项目规划与追踪,使用 `deepwiki-mcp` 进行深度研究。主动管理 `/project_document` 作为知识库。**每轮主要响应后,调用 `mcp.feedback_enhanced` 进行交互或通知。**
|
||||||
@ -167,5 +164,4 @@ alwaysApply: false
|
|||||||
|
|
||||||
* **极致效率:** AI应最大限度地减少手动干预,让MCP工具处理所有可以自动化的工作。
|
* **极致效率:** AI应最大限度地减少手动干预,让MCP工具处理所有可以自动化的工作。
|
||||||
* **战略聚焦:** 将AI的“思考”集中在无法被工具替代的领域:战略决策、创新构想、复杂问题诊断 (`mcp.sequential_thinking`) 和最终质量把关。
|
* **战略聚焦:** 将AI的“思考”集中在无法被工具替代的领域:战略决策、创新构想、复杂问题诊断 (`mcp.sequential_thinking`) 和最终质量把关。
|
||||||
|
|
||||||
* **无缝集成:** 期望AI能流畅地在不同MCP工具之间传递信息,形成一个高度整合的自动化工作流。
|
* **无缝集成:** 期望AI能流畅地在不同MCP工具之间传递信息,形成一个高度整合的自动化工作流。
|
||||||
|
2
.env.dev
2
.env.dev
@ -15,6 +15,8 @@ VITE_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e
|
|||||||
|
|
||||||
# 接口加密功能开关(如需关闭 后端也必须对应关闭)
|
# 接口加密功能开关(如需关闭 后端也必须对应关闭)
|
||||||
VITE_APP_ENCRYPT=Y
|
VITE_APP_ENCRYPT=Y
|
||||||
|
# AES 加密头标识
|
||||||
|
VITE_HEADER_FLAG=encrypt-key
|
||||||
# 接口加密传输 RSA 公钥与后端解密私钥对应 如更换需前后端一同更换
|
# 接口加密传输 RSA 公钥与后端解密私钥对应 如更换需前后端一同更换
|
||||||
VITE_APP_RSA_PUBLIC_KEY='MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='
|
VITE_APP_RSA_PUBLIC_KEY='MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='
|
||||||
# 接口响应解密 RSA 私钥与后端加密公钥对应 如更换需前后端一同更换
|
# 接口响应解密 RSA 私钥与后端加密公钥对应 如更换需前后端一同更换
|
||||||
|
@ -12,6 +12,8 @@ VITE_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e
|
|||||||
|
|
||||||
# 接口加密功能开关(如需关闭 后端也必须对应关闭)
|
# 接口加密功能开关(如需关闭 后端也必须对应关闭)
|
||||||
VITE_APP_ENCRYPT=Y
|
VITE_APP_ENCRYPT=Y
|
||||||
|
# AES 加密头标识
|
||||||
|
VITE_HEADER_FLAG=encrypt-key
|
||||||
# 接口加密传输 RSA 公钥与后端解密私钥对应 如更换需前后端一同更换
|
# 接口加密传输 RSA 公钥与后端解密私钥对应 如更换需前后端一同更换
|
||||||
VITE_APP_RSA_PUBLIC_KEY='MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='
|
VITE_APP_RSA_PUBLIC_KEY='MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='
|
||||||
# 接口响应解密 RSA 私钥与后端加密公钥对应 如更换需前后端一同更换
|
# 接口响应解密 RSA 私钥与后端加密公钥对应 如更换需前后端一同更换
|
||||||
|
@ -12,6 +12,8 @@ VITE_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e
|
|||||||
|
|
||||||
# 接口加密功能开关(如需关闭 后端也必须对应关闭)
|
# 接口加密功能开关(如需关闭 后端也必须对应关闭)
|
||||||
VITE_APP_ENCRYPT=Y
|
VITE_APP_ENCRYPT=Y
|
||||||
|
# AES 加密头标识
|
||||||
|
VITE_HEADER_FLAG=encrypt-key
|
||||||
# 接口加密传输 RSA 公钥与后端解密私钥对应 如更换需前后端一同更换
|
# 接口加密传输 RSA 公钥与后端解密私钥对应 如更换需前后端一同更换
|
||||||
VITE_APP_RSA_PUBLIC_KEY='MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='
|
VITE_APP_RSA_PUBLIC_KEY='MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='
|
||||||
# 接口响应解密 RSA 私钥与后端加密公钥对应 如更换需前后端一同更换
|
# 接口响应解密 RSA 私钥与后端加密公钥对应 如更换需前后端一同更换
|
||||||
|
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -31,7 +31,8 @@
|
|||||||
"vue.server.hybridMode": true,
|
"vue.server.hybridMode": true,
|
||||||
"files.exclude": { "/docs": true },
|
"files.exclude": { "/docs": true },
|
||||||
"search.exclude": {
|
"search.exclude": {
|
||||||
"/docs": true
|
"/docs": true,
|
||||||
|
"**/dist/**": true
|
||||||
},
|
},
|
||||||
"cSpell.words": ["Axios", "tinymce"]
|
"cSpell.words": ["Axios", "tinymce"]
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import type { HttpProxy, ProxyOptions } from 'vite';
|
import type { ProxyOptions } from 'vite';
|
||||||
import { bgRed, bgYellow, green, lightBlue } from 'kolorist';
|
import { bgRed, bgYellow, green, lightBlue } from 'kolorist';
|
||||||
import { consola } from 'consola';
|
import { consola } from 'consola';
|
||||||
import { createServiceConfig } from '../../src/utils/service';
|
import { createServiceConfig } from '../../src/utils/service';
|
||||||
@ -34,7 +34,7 @@ function createProxyItem(item: App.Service.ServiceConfigItem, enableLog: boolean
|
|||||||
target: item.baseURL,
|
target: item.baseURL,
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
ws: item.ws,
|
ws: item.ws,
|
||||||
configure: (_proxy: HttpProxy.Server, options: ProxyOptions) => {
|
configure: (_proxy, options) => {
|
||||||
_proxy.on('proxyReq', (_proxyReq, req, _res) => {
|
_proxy.on('proxyReq', (_proxyReq, req, _res) => {
|
||||||
if (!enableLog) return;
|
if (!enableLog) return;
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ import org.apache.velocity.VelocityContext;
|
|||||||
import org.dromara.common.core.utils.DateUtils;
|
import org.dromara.common.core.utils.DateUtils;
|
||||||
import org.dromara.common.core.utils.StringUtils;
|
import org.dromara.common.core.utils.StringUtils;
|
||||||
import org.dromara.common.json.utils.JsonUtils;
|
import org.dromara.common.json.utils.JsonUtils;
|
||||||
|
import org.dromara.common.mybatis.enums.DataBaseType;
|
||||||
import org.dromara.common.mybatis.helper.DataBaseHelper;
|
import org.dromara.common.mybatis.helper.DataBaseHelper;
|
||||||
import org.dromara.generator.constant.GenConstants;
|
import org.dromara.generator.constant.GenConstants;
|
||||||
import org.dromara.generator.domain.GenTable;
|
import org.dromara.generator.domain.GenTable;
|
||||||
@ -58,7 +59,7 @@ public class VelocityUtils {
|
|||||||
velocityContext.put("functionName", StringUtils.isNotEmpty(functionName) ? functionName : "【请填写功能名称】");
|
velocityContext.put("functionName", StringUtils.isNotEmpty(functionName) ? functionName : "【请填写功能名称】");
|
||||||
velocityContext.put("ClassName", genTable.getClassName());
|
velocityContext.put("ClassName", genTable.getClassName());
|
||||||
velocityContext.put("className", StringUtils.uncapitalize(genTable.getClassName()));
|
velocityContext.put("className", StringUtils.uncapitalize(genTable.getClassName()));
|
||||||
velocityContext.put("moduleName", genTable.getModuleName());
|
velocityContext.put("moduleName", StrUtil.toSymbolCase(genTable.getModuleName(), '-'));
|
||||||
velocityContext.put("BusinessName", StringUtils.capitalize(genTable.getBusinessName()));
|
velocityContext.put("BusinessName", StringUtils.capitalize(genTable.getBusinessName()));
|
||||||
velocityContext.put("businessName", genTable.getBusinessName());
|
velocityContext.put("businessName", genTable.getBusinessName());
|
||||||
velocityContext.put("business_name", StrUtil.toUnderlineCase(genTable.getBusinessName()));
|
velocityContext.put("business_name", StrUtil.toUnderlineCase(genTable.getBusinessName()));
|
||||||
@ -124,11 +125,12 @@ public class VelocityUtils {
|
|||||||
templates.add("vm/java/serviceImpl.java.vm");
|
templates.add("vm/java/serviceImpl.java.vm");
|
||||||
templates.add("vm/java/controller.java.vm");
|
templates.add("vm/java/controller.java.vm");
|
||||||
templates.add("vm/xml/mapper.xml.vm");
|
templates.add("vm/xml/mapper.xml.vm");
|
||||||
if (DataBaseHelper.isOracle()) {
|
DataBaseType dataBaseType = DataBaseHelper.getDataBaseType();
|
||||||
|
if (dataBaseType.isOracle()) {
|
||||||
templates.add("vm/sql/oracle/sql.vm");
|
templates.add("vm/sql/oracle/sql.vm");
|
||||||
} else if (DataBaseHelper.isPostgerSql()) {
|
} else if (dataBaseType.isPostgreSql()) {
|
||||||
templates.add("vm/sql/postgres/sql.vm");
|
templates.add("vm/sql/postgres/sql.vm");
|
||||||
} else if (DataBaseHelper.isSqlServer()) {
|
} else if (dataBaseType.isSqlServer()) {
|
||||||
templates.add("vm/sql/sqlserver/sql.vm");
|
templates.add("vm/sql/sqlserver/sql.vm");
|
||||||
} else {
|
} else {
|
||||||
templates.add("vm/sql/sql.vm");
|
templates.add("vm/sql/sql.vm");
|
||||||
@ -163,7 +165,7 @@ public class VelocityUtils {
|
|||||||
String javaPath = PROJECT_PATH + "/" + StringUtils.replace(packageName, ".", "/");
|
String javaPath = PROJECT_PATH + "/" + StringUtils.replace(packageName, ".", "/");
|
||||||
String mybatisPath = MYBATIS_PATH + "/" + moduleName;
|
String mybatisPath = MYBATIS_PATH + "/" + moduleName;
|
||||||
String soybeanPath = "soy";
|
String soybeanPath = "soy";
|
||||||
|
String soybeanModuleName = StrUtil.toSymbolCase(moduleName, '-');
|
||||||
if (template.contains("domain.java.vm")) {
|
if (template.contains("domain.java.vm")) {
|
||||||
fileName = StringUtils.format("{}/domain/{}.java", javaPath, className);
|
fileName = StringUtils.format("{}/domain/{}.java", javaPath, className);
|
||||||
}
|
}
|
||||||
@ -186,17 +188,17 @@ public class VelocityUtils {
|
|||||||
} else if (template.contains("sql.vm")) {
|
} else if (template.contains("sql.vm")) {
|
||||||
fileName = businessName + "Menu.sql";
|
fileName = businessName + "Menu.sql";
|
||||||
} else if (template.contains("index.vue.vm")) {
|
} else if (template.contains("index.vue.vm")) {
|
||||||
fileName = StringUtils.format("{}/views/{}/{}/index.vue", soybeanPath, moduleName, StrUtil.toSymbolCase(businessName, '-'));
|
fileName = StringUtils.format("{}/views/{}/{}/index.vue", soybeanPath, soybeanModuleName, StrUtil.toSymbolCase(businessName, '-'));
|
||||||
} else if (template.contains("index-tree.vue.vm")) {
|
} else if (template.contains("index-tree.vue.vm")) {
|
||||||
fileName = StringUtils.format("{}/views/{}/{}/index.vue", soybeanPath, moduleName, StrUtil.toSymbolCase(businessName, '-'));
|
fileName = StringUtils.format("{}/views/{}/{}/index.vue", soybeanPath, soybeanModuleName, StrUtil.toSymbolCase(businessName, '-'));
|
||||||
} else if (template.contains("api.d.ts.vm")) {
|
} else if (template.contains("api.d.ts.vm")) {
|
||||||
fileName = StringUtils.format("{}/typings/api/{}.{}.api.d.ts", soybeanPath, moduleName, StrUtil.toSymbolCase(businessName, '-'));
|
fileName = StringUtils.format("{}/typings/api/{}.{}.api.d.ts", soybeanPath, soybeanModuleName, StrUtil.toSymbolCase(businessName, '-'));
|
||||||
} else if (template.contains("api.ts.vm")) {
|
} else if (template.contains("api.ts.vm")) {
|
||||||
fileName = StringUtils.format("{}/service/api/{}/{}.ts", soybeanPath, moduleName, StrUtil.toSymbolCase(businessName, '-'));
|
fileName = StringUtils.format("{}/service/api/{}/{}.ts", soybeanPath, soybeanModuleName, StrUtil.toSymbolCase(businessName, '-'));
|
||||||
} else if (template.contains("search.vue.vm")) {
|
} else if (template.contains("search.vue.vm")) {
|
||||||
fileName = StringUtils.format("{}/views/{}/{}/modules/{}-search.vue", soybeanPath, moduleName, StrUtil.toSymbolCase(businessName, '-'), StrUtil.toSymbolCase(businessName, '-'));
|
fileName = StringUtils.format("{}/views/{}/{}/modules/{}-search.vue", soybeanPath, soybeanModuleName, StrUtil.toSymbolCase(businessName, '-'), StrUtil.toSymbolCase(businessName, '-'));
|
||||||
} else if (template.contains("operate-drawer.vue.vm")) {
|
} else if (template.contains("operate-drawer.vue.vm")) {
|
||||||
fileName = StringUtils.format("{}/views/{}/{}/modules/{}-operate-drawer.vue", soybeanPath, moduleName, StrUtil.toSymbolCase(businessName, '-'), StrUtil.toSymbolCase(businessName, '-'));
|
fileName = StringUtils.format("{}/views/{}/{}/modules/{}-operate-drawer.vue", soybeanPath, soybeanModuleName, StrUtil.toSymbolCase(businessName, '-'), StrUtil.toSymbolCase(businessName, '-'));
|
||||||
}
|
}
|
||||||
return fileName;
|
return fileName;
|
||||||
}
|
}
|
||||||
|
55
package.json
55
package.json
@ -66,11 +66,11 @@
|
|||||||
"@sa/tinymce": "workspace:*",
|
"@sa/tinymce": "workspace:*",
|
||||||
"@sa/utils": "workspace:*",
|
"@sa/utils": "workspace:*",
|
||||||
"@types/streamsaver": "^2.0.5",
|
"@types/streamsaver": "^2.0.5",
|
||||||
"@vueuse/core": "13.5.0",
|
"@vueuse/core": "13.8.0",
|
||||||
"clipboard": "2.0.11",
|
"clipboard": "2.0.11",
|
||||||
"dayjs": "1.11.13",
|
"dayjs": "1.11.14",
|
||||||
"defu": "6.1.4",
|
"defu": "6.1.4",
|
||||||
"echarts": "5.6.0",
|
"echarts": "6.0.0",
|
||||||
"highlight.js": "^11.11.1",
|
"highlight.js": "^11.11.1",
|
||||||
"jsencrypt": "^3.3.2",
|
"jsencrypt": "^3.3.2",
|
||||||
"json5": "2.2.3",
|
"json5": "2.2.3",
|
||||||
@ -80,46 +80,47 @@
|
|||||||
"pinia": "3.0.3",
|
"pinia": "3.0.3",
|
||||||
"streamsaver": "^2.0.6",
|
"streamsaver": "^2.0.6",
|
||||||
"tailwind-merge": "3.3.1",
|
"tailwind-merge": "3.3.1",
|
||||||
"vue": "3.5.17",
|
"vue": "3.5.20",
|
||||||
"vue-advanced-cropper": "^2.8.9",
|
"vue-advanced-cropper": "^2.8.9",
|
||||||
"vue-draggable-plus": "0.6.0",
|
"vue-draggable-plus": "0.6.0",
|
||||||
"vue-i18n": "11.1.9",
|
"vue-i18n": "11.1.11",
|
||||||
"vue-router": "4.5.1"
|
"vue-router": "4.5.1",
|
||||||
|
"xlsx": "0.18.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@elegant-router/vue": "0.3.8",
|
"@elegant-router/vue": "0.3.8",
|
||||||
"@iconify/json": "2.2.357",
|
"@iconify/json": "2.2.378",
|
||||||
"@sa/scripts": "workspace:*",
|
"@sa/scripts": "workspace:*",
|
||||||
"@sa/uno-preset": "workspace:*",
|
"@sa/uno-preset": "workspace:*",
|
||||||
"@soybeanjs/eslint-config": "1.7.1",
|
"@soybeanjs/eslint-config": "1.7.1",
|
||||||
"@types/node": "24.0.13",
|
"@types/node": "24.3.0",
|
||||||
"@types/nprogress": "0.2.3",
|
"@types/nprogress": "0.2.3",
|
||||||
"@unocss/eslint-config": "66.3.3",
|
"@unocss/eslint-config": "66.4.2",
|
||||||
"@unocss/preset-icons": "66.3.3",
|
"@unocss/preset-icons": "66.4.2",
|
||||||
"@unocss/preset-uno": "66.3.3",
|
"@unocss/preset-uno": "66.4.2",
|
||||||
"@unocss/transformer-directives": "66.3.3",
|
"@unocss/transformer-directives": "66.4.2",
|
||||||
"@unocss/transformer-variant-group": "66.3.3",
|
"@unocss/transformer-variant-group": "66.4.2",
|
||||||
"@unocss/vite": "66.3.3",
|
"@unocss/vite": "66.4.2",
|
||||||
"@vitejs/plugin-vue": "6.0.0",
|
"@vitejs/plugin-vue": "6.0.1",
|
||||||
"@vitejs/plugin-vue-jsx": "5.0.1",
|
"@vitejs/plugin-vue-jsx": "5.1.0",
|
||||||
"consola": "3.4.2",
|
"consola": "3.4.2",
|
||||||
"eslint": "9.31.0",
|
"eslint": "9.34.0",
|
||||||
"eslint-plugin-vue": "10.3.0",
|
"eslint-plugin-vue": "10.4.0",
|
||||||
"kolorist": "1.8.0",
|
"kolorist": "1.8.0",
|
||||||
"sass": "1.89.2",
|
"sass": "1.91.0",
|
||||||
"simple-git-hooks": "2.13.0",
|
"simple-git-hooks": "2.13.1",
|
||||||
"tsx": "4.20.3",
|
"tsx": "4.20.5",
|
||||||
"typescript": "5.8.3",
|
"typescript": "5.9.2",
|
||||||
"unplugin-icons": "22.1.0",
|
"unplugin-icons": "22.2.0",
|
||||||
"unplugin-vue-components": "28.8.0",
|
"unplugin-vue-components": "29.0.0",
|
||||||
"vite": "7.0.4",
|
"vite": "7.1.3",
|
||||||
"vite-plugin-monaco-editor": "^1.1.0",
|
"vite-plugin-monaco-editor": "^1.1.0",
|
||||||
"vite-plugin-progress": "0.0.7",
|
"vite-plugin-progress": "0.0.7",
|
||||||
"vite-plugin-static-copy": "^3.1.0",
|
"vite-plugin-static-copy": "^3.1.0",
|
||||||
"vite-plugin-svg-icons": "2.0.1",
|
"vite-plugin-svg-icons": "2.0.1",
|
||||||
"vite-plugin-vue-devtools": "7.7.7",
|
"vite-plugin-vue-devtools": "8.0.1",
|
||||||
"vue-eslint-parser": "10.2.0",
|
"vue-eslint-parser": "10.2.0",
|
||||||
"vue-tsc": "3.0.1"
|
"vue-tsc": "3.0.6"
|
||||||
},
|
},
|
||||||
"simple-git-hooks": {
|
"simple-git-hooks": {
|
||||||
"commit-msg": "pnpm sa git-commit-verify",
|
"commit-msg": "pnpm sa git-commit-verify",
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sa/utils": "workspace:*",
|
"@sa/utils": "workspace:*",
|
||||||
"axios": "1.10.0",
|
"axios": "1.11.0",
|
||||||
"axios-retry": "4.5.0",
|
"axios-retry": "4.5.0",
|
||||||
"qs": "6.14.0"
|
"qs": "6.14.0"
|
||||||
},
|
},
|
||||||
|
@ -14,14 +14,14 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@soybeanjs/changelog": "0.3.24",
|
"@soybeanjs/changelog": "0.3.24",
|
||||||
"bumpp": "10.2.0",
|
"bumpp": "10.2.3",
|
||||||
"c12": "3.0.4",
|
"c12": "3.2.0",
|
||||||
"cac": "6.7.14",
|
"cac": "6.7.14",
|
||||||
"consola": "3.4.2",
|
"consola": "3.4.2",
|
||||||
"enquirer": "2.4.1",
|
"enquirer": "2.4.1",
|
||||||
"execa": "9.6.0",
|
"execa": "9.6.0",
|
||||||
"kolorist": "1.8.0",
|
"kolorist": "1.8.0",
|
||||||
"npm-check-updates": "18.0.1",
|
"npm-check-updates": "18.0.3",
|
||||||
"rimraf": "6.0.1"
|
"rimraf": "6.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -220,7 +220,7 @@ const events = computed(() => {
|
|||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.tox.tox-silver-sink.tox-tinymce-aux {
|
.tox.tox-silver-sink.tox-tinymce-aux {
|
||||||
/** 该样式默认为1300的zIndex */
|
/** 该样式默认为1300的zIndex */
|
||||||
z-index: 2025;
|
z-index: 2025 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-tinymce {
|
.app-tinymce {
|
||||||
|
2514
pnpm-lock.yaml
generated
2514
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, useAttrs } from 'vue';
|
import { computed, useAttrs } from 'vue';
|
||||||
import type { TagProps } from 'naive-ui';
|
import type { TagProps } from 'naive-ui';
|
||||||
|
import { jsonClone } from '@sa/utils';
|
||||||
import { useDict } from '@/hooks/business/dict';
|
import { useDict } from '@/hooks/business/dict';
|
||||||
import { isNotNull } from '@/utils/common';
|
import { isNotNull } from '@/utils/common';
|
||||||
import { $t } from '@/locales';
|
import { $t } from '@/locales';
|
||||||
@ -28,7 +29,7 @@ const { transformDictData } = useDict(props.dictCode, props.immediate);
|
|||||||
|
|
||||||
const dictTagData = computed<Api.System.DictData[]>(() => {
|
const dictTagData = computed<Api.System.DictData[]>(() => {
|
||||||
if (props.dictData) {
|
if (props.dictData) {
|
||||||
const dictData = props.dictData;
|
const dictData = jsonClone(props.dictData);
|
||||||
if (dictData.dictLabel?.startsWith(`dict.${dictData.dictType}.`)) {
|
if (dictData.dictLabel?.startsWith(`dict.${dictData.dictType}.`)) {
|
||||||
dictData.dictLabel = $t(dictData.dictLabel as App.I18n.I18nKey);
|
dictData.dictLabel = $t(dictData.dictLabel as App.I18n.I18nKey);
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import hljs from 'highlight.js/lib/core';
|
import hljs from 'highlight.js/lib/core';
|
||||||
import json from 'highlight.js/lib/languages/json';
|
import json from 'highlight.js/lib/languages/json';
|
||||||
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
hljs.registerLanguage('json', json);
|
hljs.registerLanguage('json', json);
|
||||||
|
|
||||||
@ -10,15 +11,19 @@ defineOptions({
|
|||||||
});
|
});
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
class?: string;
|
||||||
code?: string;
|
code?: string;
|
||||||
showLineNumbers?: boolean;
|
showLineNumbers?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
class: '',
|
||||||
code: '',
|
code: '',
|
||||||
showLineNumbers: false
|
showLineNumbers: false
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const DEFAULT_CLASS = 'max-h-500px';
|
||||||
|
|
||||||
/** 格式化JSON数据 */
|
/** 格式化JSON数据 */
|
||||||
const jsonData = computed<string>(() => {
|
const jsonData = computed<string>(() => {
|
||||||
if (!props.code) return '';
|
if (!props.code) return '';
|
||||||
@ -33,9 +38,9 @@ const jsonData = computed<string>(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="json-preview">
|
<NScrollbar :class="twMerge(DEFAULT_CLASS, props.class)">
|
||||||
<NCode :code="jsonData" :hljs="hljs" language="json" :show-line-numbers="showLineNumbers" />
|
<NCode :code="jsonData" :hljs="hljs" language="json" :show-line-numbers="showLineNumbers" :word-wrap="true" />
|
||||||
</div>
|
</NScrollbar>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@ -44,18 +49,4 @@ html[class='dark'] {
|
|||||||
background-color: #7c7777;
|
background-color: #7c7777;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.json-preview {
|
|
||||||
width: 100%;
|
|
||||||
max-height: 500px;
|
|
||||||
overflow-y: auto;
|
|
||||||
@include scrollbar();
|
|
||||||
.empty-data {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
height: 100%;
|
|
||||||
color: #999;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,61 +1,524 @@
|
|||||||
|
<!-- Copyright By https://github.com/Daymychen/art-design-pro/blob/main/src/components/core/views/login/LoginLeftView.vue -->
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from 'vue';
|
import { useThemeStore } from '@/store/modules/theme';
|
||||||
import { getPaletteColorByNumber } from '@sa/color';
|
|
||||||
|
|
||||||
defineOptions({ name: 'WaveBg' });
|
defineOptions({ name: 'WaveBg' });
|
||||||
|
|
||||||
interface Props {
|
const themeStore = useThemeStore();
|
||||||
/** Theme color */
|
|
||||||
themeColor: string;
|
function toggleThemeScheme() {
|
||||||
|
if (themeStore.darkMode) {
|
||||||
|
themeStore.setThemeScheme('light');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
themeStore.setThemeScheme('dark');
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<Props>();
|
|
||||||
|
|
||||||
const lightColor = computed(() => getPaletteColorByNumber(props.themeColor, 200));
|
|
||||||
const darkColor = computed(() => getPaletteColorByNumber(props.themeColor, 500));
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="absolute-lt z-1 size-full overflow-hidden">
|
<div class="wave-bg">
|
||||||
<div class="absolute -right-300px -top-900px lt-sm:(-right-100px -top-1170px)">
|
<!-- 几何装饰元素 -->
|
||||||
<svg height="1337" width="1337">
|
<div class="geometric-decorations">
|
||||||
<defs>
|
<!-- 基础几何形状 -->
|
||||||
<path
|
<div class="geo-element circle-outline animate-fade-in-up animate-delay-0s"></div>
|
||||||
id="path-1"
|
<div class="geo-element square-rotated animate-fade-in-left animate-delay-0s"></div>
|
||||||
opacity="1"
|
<div class="geo-element circle-small animate-fade-in-up animate-delay-0.3s"></div>
|
||||||
fill-rule="evenodd"
|
|
||||||
d="M1337,668.5 C1337,1037.455193874239 1037.455193874239,1337 668.5,1337 C523.6725684305388,1337 337,1236 370.50000000000006,1094 C434.03835568300906,824.6732385973953 6.906089672974592e-14,892.6277623047779 0,668.5000000000001 C0,299.5448061257611 299.5448061257609,1.1368683772161603e-13 668.4999999999999,0 C1037.455193874239,0 1337,299.544806125761 1337,668.5Z"
|
<div class="geo-element square-bottom-right animate-fade-in-right animate-delay-0s"></div>
|
||||||
/>
|
|
||||||
<linearGradient id="linearGradient-2" x1="0.79" y1="0.62" x2="0.21" y2="0.86">
|
<!-- 背景泡泡 -->
|
||||||
<stop offset="0" :stop-color="lightColor" stop-opacity="1" />
|
<div class="geo-element bg-bubble animate-scale-in animate-delay-0.5s"></div>
|
||||||
<stop offset="1" :stop-color="darkColor" stop-opacity="1" />
|
|
||||||
</linearGradient>
|
<!-- 太阳/月亮 -->
|
||||||
</defs>
|
<div
|
||||||
<g opacity="1">
|
class="geo-element circle-top-right animate-fade-in-down animate-delay-0.5s"
|
||||||
<use xlink:href="#path-1" fill="url(#linearGradient-2)" fill-opacity="1" />
|
@click="toggleThemeScheme"
|
||||||
</g>
|
></div>
|
||||||
</svg>
|
|
||||||
</div>
|
<!-- 装饰点 -->
|
||||||
<div class="absolute -bottom-400px -left-200px lt-sm:(-bottom-760px -left-100px)">
|
<div class="geo-element dot dot-top-left animate-bounce-in animate-delay-0s"></div>
|
||||||
<svg height="896" width="967.8852157128662">
|
<div class="geo-element dot dot-top-right animate-bounce-in animate-delay-0s"></div>
|
||||||
<defs>
|
<div class="geo-element dot dot-center-right animate-bounce-in animate-delay-0s"></div>
|
||||||
<path
|
|
||||||
id="path-2"
|
<!-- 叠加方块组 -->
|
||||||
opacity="1"
|
<div class="squares-group">
|
||||||
fill-rule="evenodd"
|
<i class="geo-element square square-blue animate-fade-in-left-rotated-blue animate-delay-0.2s"></i>
|
||||||
d="M896,448 C1142.6325445712241,465.5747656464056 695.2579309733121,896 448,896 C200.74206902668806,896 5.684341886080802e-14,695.2579309733121 0,448.0000000000001 C0,200.74206902668806 200.74206902668791,5.684341886080802e-14 447.99999999999994,0 C695.2579309733121,0 475,418 896,448Z"
|
<i class="geo-element square square-pink animate-fade-in-left-rotated-pink animate-delay-0.4s"></i>
|
||||||
/>
|
<i class="geo-element square square-purple animate-fade-in-left-no-rotation animate-delay-0.6s"></i>
|
||||||
<linearGradient id="linearGradient-3" x1="0.5" y1="0" x2="0.5" y2="1">
|
</div>
|
||||||
<stop offset="0" :stop-color="darkColor" stop-opacity="1" />
|
|
||||||
<stop offset="1" :stop-color="lightColor" stop-opacity="1" />
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
<g opacity="1">
|
|
||||||
<use xlink:href="#path-2" fill="url(#linearGradient-3)" fill-opacity="1" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style lang="scss" scoped>
|
||||||
|
// 颜色变量定义
|
||||||
|
$primary-light-7: rgb(var(--primary-50-color));
|
||||||
|
$primary-light-8: rgb(var(--primary-100-color));
|
||||||
|
$primary-light-9: rgb(var(--primary-200-color));
|
||||||
|
$primary-base: rgb(var(--primary-color));
|
||||||
|
$main-bg: rgb(var(--primary-50-color));
|
||||||
|
|
||||||
|
// 混合颜色函数
|
||||||
|
$bg-mix-light-9: color-mix(in srgb, $primary-light-9 100%, $main-bg);
|
||||||
|
$bg-mix-light-8: color-mix(in srgb, $primary-light-8 80%, $main-bg);
|
||||||
|
$bg-mix-light-7: color-mix(in srgb, $primary-light-7 80%, $main-bg);
|
||||||
|
|
||||||
|
.wave-bg {
|
||||||
|
.geometric-decorations {
|
||||||
|
.geo-element {
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
animation-duration: 0.8s;
|
||||||
|
animation-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 动画 mixin
|
||||||
|
@mixin fadeAnimation($direction: '', $rotation: 0deg) {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
|
@if $direction == 'up' {
|
||||||
|
transform: translateY(30px) rotate($rotation);
|
||||||
|
} @else if $direction == 'down' {
|
||||||
|
transform: translateY(-30px) rotate($rotation);
|
||||||
|
} @else if $direction == 'left' {
|
||||||
|
transform: translateX(-30px) rotate($rotation);
|
||||||
|
} @else if $direction == 'right' {
|
||||||
|
transform: translateX(30px) rotate($rotation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
|
||||||
|
@if $direction == 'up' or $direction == 'down' {
|
||||||
|
transform: translateY(0) rotate($rotation);
|
||||||
|
} @else {
|
||||||
|
transform: translateX(0) rotate($rotation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 动画定义
|
||||||
|
@keyframes fadeInUp {
|
||||||
|
@include fadeAnimation('up');
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInDown {
|
||||||
|
@include fadeAnimation('down');
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInLeft {
|
||||||
|
@include fadeAnimation('left');
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInLeftRotated {
|
||||||
|
@include fadeAnimation('left', -25deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInRight {
|
||||||
|
@include fadeAnimation('right');
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInRightRotated {
|
||||||
|
@include fadeAnimation('right', 45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInLeftRotatedBlue {
|
||||||
|
@include fadeAnimation('left', -10deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInLeftRotatedPink {
|
||||||
|
@include fadeAnimation('left', 10deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInLeftNoRotation {
|
||||||
|
@include fadeAnimation('left');
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes scaleIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bounceIn {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
70% {
|
||||||
|
transform: scale(0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes lineGrow {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideInLeft {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-30px);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 动画类
|
||||||
|
.animate-fade-in-up {
|
||||||
|
animation-name: fadeInUp;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-fade-in-down {
|
||||||
|
animation-name: fadeInDown;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-fade-in-left {
|
||||||
|
animation-name: fadeInLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-fade-in-right {
|
||||||
|
animation-name: fadeInRight;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-scale-in {
|
||||||
|
animation-name: scaleIn;
|
||||||
|
animation-duration: 1.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-bounce-in {
|
||||||
|
animation-name: bounceIn;
|
||||||
|
animation-duration: 0.6s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-fade-in-left-rotated-blue {
|
||||||
|
animation-name: fadeInLeftRotatedBlue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-fade-in-left-rotated-pink {
|
||||||
|
animation-name: fadeInLeftRotatedPink;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-fade-in-left-no-rotation {
|
||||||
|
animation-name: fadeInLeftNoRotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 基础几何形状
|
||||||
|
.circle-outline {
|
||||||
|
top: 10%;
|
||||||
|
left: 25%;
|
||||||
|
width: 42px;
|
||||||
|
height: 42px;
|
||||||
|
border: 2px solid $primary-light-8;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.square-rotated {
|
||||||
|
top: 50%;
|
||||||
|
left: 16%;
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
background-color: $bg-mix-light-8;
|
||||||
|
|
||||||
|
&.animate-fade-in-left {
|
||||||
|
animation-name: fadeInLeftRotated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle-small {
|
||||||
|
bottom: 26%;
|
||||||
|
left: 30%;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
background-color: $primary-light-8;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 太阳/月亮效果
|
||||||
|
.circle-top-right {
|
||||||
|
top: 3%;
|
||||||
|
right: 3%;
|
||||||
|
z-index: 100;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
cursor: pointer;
|
||||||
|
background: $bg-mix-light-7;
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: all 0.3s;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
content: '';
|
||||||
|
background: linear-gradient(to right, #fcbb04, #fffc00);
|
||||||
|
border-radius: 50%;
|
||||||
|
opacity: 0;
|
||||||
|
transition: all 0.5s;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0 0 36px #fffc00;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.square-bottom-right {
|
||||||
|
right: 10%;
|
||||||
|
bottom: 10%;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
background-color: $primary-light-8;
|
||||||
|
|
||||||
|
&.animate-fade-in-right {
|
||||||
|
animation-name: fadeInRightRotated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 背景泡泡
|
||||||
|
.bg-bubble {
|
||||||
|
top: -120px;
|
||||||
|
right: -120px;
|
||||||
|
width: 360px;
|
||||||
|
height: 360px;
|
||||||
|
background-color: $bg-mix-light-8;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 装饰点
|
||||||
|
.dot {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
background-color: $primary-light-7;
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
&.dot-top-left {
|
||||||
|
top: 140px;
|
||||||
|
left: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.dot-top-right {
|
||||||
|
top: 140px;
|
||||||
|
right: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.dot-center-right {
|
||||||
|
top: 46%;
|
||||||
|
right: 22%;
|
||||||
|
background-color: $primary-light-8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 叠加方块组
|
||||||
|
.squares-group {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 18px;
|
||||||
|
left: 20px;
|
||||||
|
width: 140px;
|
||||||
|
height: 140px;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
.square {
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 8px 24px rgb(64 87 167 / 12%);
|
||||||
|
|
||||||
|
&.square-blue {
|
||||||
|
top: 12px;
|
||||||
|
left: 30px;
|
||||||
|
z-index: 2;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
background-color: rgb(from $primary-base r g b / 30%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.square-pink {
|
||||||
|
top: 30px;
|
||||||
|
left: 48px;
|
||||||
|
z-index: 1;
|
||||||
|
width: 70px;
|
||||||
|
height: 70px;
|
||||||
|
background-color: rgb(from $primary-base r g b / 15%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.square-purple {
|
||||||
|
top: 66px;
|
||||||
|
left: 86px;
|
||||||
|
z-index: 3;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
background-color: rgb(from $primary-base r g b / 45%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 装饰线条
|
||||||
|
&::after {
|
||||||
|
position: absolute;
|
||||||
|
top: 86px;
|
||||||
|
left: 72px;
|
||||||
|
width: 80px;
|
||||||
|
height: 1px;
|
||||||
|
content: '';
|
||||||
|
background: linear-gradient(90deg, var(--el-color-primary-light-6), transparent);
|
||||||
|
opacity: 0;
|
||||||
|
transform: rotate(50deg);
|
||||||
|
animation: lineGrow 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
|
||||||
|
animation-delay: 1.2s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 1600px) {
|
||||||
|
width: 60vw;
|
||||||
|
|
||||||
|
.text-wrap {
|
||||||
|
bottom: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 1280px) {
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
padding: 0;
|
||||||
|
// 隐藏背景和其他内容,只保留 logo
|
||||||
|
background: transparent;
|
||||||
|
|
||||||
|
.left-img,
|
||||||
|
.text-wrap,
|
||||||
|
.geometric-decorations {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
position: fixed;
|
||||||
|
top: 15px;
|
||||||
|
left: 25px;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暗色主题
|
||||||
|
.dark .wave-bg {
|
||||||
|
background-color: color-mix(in srgb, $primary-light-9 60%, #070707);
|
||||||
|
|
||||||
|
@media only screen and (max-width: 1280px) {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.geometric-decorations {
|
||||||
|
// 月亮效果
|
||||||
|
.circle-top-right {
|
||||||
|
background-color: $bg-mix-light-8;
|
||||||
|
box-shadow: 0 0 25px #333 inset;
|
||||||
|
transition: all 0.3s ease-in-out 0.1s;
|
||||||
|
rotate: -48deg;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 15px;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
content: '';
|
||||||
|
background-color: $bg-mix-light-9;
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: transparent;
|
||||||
|
box-shadow: 0 40px 25px #ddd inset;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
left: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-bubble {
|
||||||
|
background-color: $bg-mix-light-9;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 其他元素颜色调整
|
||||||
|
.square-rotated {
|
||||||
|
background-color: $bg-mix-light-9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle-small,
|
||||||
|
.dot {
|
||||||
|
background-color: $primary-light-8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.square-bottom-right {
|
||||||
|
background-color: $primary-light-9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot.dot-top-right {
|
||||||
|
background-color: $primary-light-8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 方块组暗色调整
|
||||||
|
.squares-group {
|
||||||
|
.square {
|
||||||
|
box-shadow: none;
|
||||||
|
|
||||||
|
&.square-blue {
|
||||||
|
background-color: rgb(from $primary-base r g b / 18%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.square-pink {
|
||||||
|
background-color: rgb(from $primary-base r g b / 10%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.square-purple {
|
||||||
|
background-color: rgb(from $primary-base r g b / 20%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
background: linear-gradient(90deg, $primary-light-8, transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -4,5 +4,6 @@ export enum SetupStoreId {
|
|||||||
Auth = 'auth-store',
|
Auth = 'auth-store',
|
||||||
Route = 'route-store',
|
Route = 'route-store',
|
||||||
Tab = 'tab-store',
|
Tab = 'tab-store',
|
||||||
Notice = 'notice-store'
|
Notice = 'notice-store',
|
||||||
|
Dict = 'dict-store'
|
||||||
}
|
}
|
||||||
|
@ -113,6 +113,10 @@ export function useDownload() {
|
|||||||
|
|
||||||
const response = await fetch(fullUrl, requestOptions);
|
const response = await fetch(fullUrl, requestOptions);
|
||||||
|
|
||||||
|
if (response.status !== 200) {
|
||||||
|
throw new Error(errorCodeRecord.default);
|
||||||
|
}
|
||||||
|
|
||||||
await handleResponse(response);
|
await handleResponse(response);
|
||||||
|
|
||||||
const rawHeader = response.headers.get('Download-Filename');
|
const rawHeader = response.headers.get('Download-Filename');
|
||||||
|
@ -33,7 +33,7 @@ const toGitee = () => {
|
|||||||
</NBadge>
|
</NBadge>
|
||||||
</NButton>
|
</NButton>
|
||||||
</template>
|
</template>
|
||||||
消息
|
{{ $t('page.home.message') }}
|
||||||
</NTooltip>
|
</NTooltip>
|
||||||
</template>
|
</template>
|
||||||
<NCard
|
<NCard
|
||||||
|
@ -328,6 +328,8 @@ const local: App.I18n.Schema = {
|
|||||||
page: {
|
page: {
|
||||||
login: {
|
login: {
|
||||||
common: {
|
common: {
|
||||||
|
title: 'Modern enterprise-level multi-tenant management system',
|
||||||
|
subTitle: 'Provides developers with a complete enterprise management solution',
|
||||||
loginOrRegister: 'Login / Register',
|
loginOrRegister: 'Login / Register',
|
||||||
register: 'Register',
|
register: 'Register',
|
||||||
userNamePlaceholder: 'Please enter user name',
|
userNamePlaceholder: 'Please enter user name',
|
||||||
|
@ -328,6 +328,8 @@ const local: App.I18n.Schema = {
|
|||||||
page: {
|
page: {
|
||||||
login: {
|
login: {
|
||||||
common: {
|
common: {
|
||||||
|
title: '现代化的企业级多租户管理系统',
|
||||||
|
subTitle: '为开发者提供了完整的企业管理解决方案',
|
||||||
loginOrRegister: '登录 / 注册',
|
loginOrRegister: '登录 / 注册',
|
||||||
register: '注册',
|
register: '注册',
|
||||||
userNamePlaceholder: '请输入用户名',
|
userNamePlaceholder: '请输入用户名',
|
||||||
|
@ -43,3 +43,10 @@ export function fetchGetPostSelect(deptId?: CommonType.IdType, postIds?: CommonT
|
|||||||
params: { postIds, deptId }
|
params: { postIds, deptId }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
/** 获取部门选择框列表 */
|
||||||
|
export function fetchGetPostDeptSelect() {
|
||||||
|
return request<Api.Common.CommonTreeRecord>({
|
||||||
|
url: '/system/post/deptTree',
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -8,7 +8,7 @@ import { decrypt, encrypt } from '@/utils/jsencrypt';
|
|||||||
import { getAuthorization, handleExpiredRequest, showErrorMsg } from './shared';
|
import { getAuthorization, handleExpiredRequest, showErrorMsg } from './shared';
|
||||||
import type { RequestInstanceState } from './type';
|
import type { RequestInstanceState } from './type';
|
||||||
|
|
||||||
const encryptHeader = 'encrypt-key';
|
const encryptHeader = import.meta.env.VITE_HEADER_FLAG || 'encrypt-key';
|
||||||
const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y';
|
const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y';
|
||||||
const { baseURL } = getServiceBaseURL(import.meta.env, isHttpProxy);
|
const { baseURL } = getServiceBaseURL(import.meta.env, isHttpProxy);
|
||||||
|
|
||||||
@ -48,6 +48,14 @@ export const request = createFlatRequest<App.Service.Response, RequestInstanceSt
|
|||||||
isBackendSuccess(response) {
|
isBackendSuccess(response) {
|
||||||
// when the backend response code is "0000"(default), it means the request is success
|
// when the backend response code is "0000"(default), it means the request is success
|
||||||
// to change this logic by yourself, you can modify the `VITE_SERVICE_SUCCESS_CODE` in `.env` file
|
// to change this logic by yourself, you can modify the `VITE_SERVICE_SUCCESS_CODE` in `.env` file
|
||||||
|
if (import.meta.env.VITE_APP_ENCRYPT === 'Y' && response.headers[encryptHeader]) {
|
||||||
|
const keyStr = response.headers[encryptHeader];
|
||||||
|
const data = String(response.data);
|
||||||
|
const base64Str = decrypt(keyStr);
|
||||||
|
const aesKey = decryptBase64(base64Str.toString());
|
||||||
|
const decryptData = decryptWithAes(data, aesKey);
|
||||||
|
response.data = JSON.parse(decryptData);
|
||||||
|
}
|
||||||
return String(response.data.code) === import.meta.env.VITE_SERVICE_SUCCESS_CODE;
|
return String(response.data.code) === import.meta.env.VITE_SERVICE_SUCCESS_CODE;
|
||||||
},
|
},
|
||||||
async onBackendFail(response, instance) {
|
async onBackendFail(response, instance) {
|
||||||
@ -125,23 +133,6 @@ export const request = createFlatRequest<App.Service.Response, RequestInstanceSt
|
|||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
transformBackendResponse(response) {
|
transformBackendResponse(response) {
|
||||||
if (import.meta.env.VITE_APP_ENCRYPT === 'Y') {
|
|
||||||
// 加密后的 AES 秘钥
|
|
||||||
const keyStr = response.headers[encryptHeader];
|
|
||||||
// 加密
|
|
||||||
if (keyStr && keyStr !== '') {
|
|
||||||
const data = String(response.data);
|
|
||||||
// 请求体 AES 解密
|
|
||||||
const base64Str = decrypt(keyStr);
|
|
||||||
// base64 解码 得到请求头的 AES 秘钥
|
|
||||||
const aesKey = decryptBase64(base64Str.toString());
|
|
||||||
// aesKey 解码 data
|
|
||||||
const decryptData = decryptWithAes(data, aesKey);
|
|
||||||
// 将结果 (得到的是 JSON 字符串) 转为 JSON
|
|
||||||
response.data = JSON.parse(decryptData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 二进制数据则直接返回
|
// 二进制数据则直接返回
|
||||||
if (response.request.responseType === 'blob' || response.request.responseType === 'arraybuffer') {
|
if (response.request.responseType === 'blob' || response.request.responseType === 'arraybuffer') {
|
||||||
return response.data;
|
return response.data;
|
||||||
|
@ -8,6 +8,7 @@ import { localStg } from '@/utils/storage';
|
|||||||
import { SetupStoreId } from '@/enum';
|
import { SetupStoreId } from '@/enum';
|
||||||
import { useRouteStore } from '../route';
|
import { useRouteStore } from '../route';
|
||||||
import { useTabStore } from '../tab';
|
import { useTabStore } from '../tab';
|
||||||
|
import useNoticeStore from '../notice';
|
||||||
import { clearAuthStorage, getToken } from './shared';
|
import { clearAuthStorage, getToken } from './shared';
|
||||||
|
|
||||||
export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
|
export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
|
||||||
@ -15,6 +16,7 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
|
|||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
const routeStore = useRouteStore();
|
const routeStore = useRouteStore();
|
||||||
const tabStore = useTabStore();
|
const tabStore = useTabStore();
|
||||||
|
const noticeStore = useNoticeStore();
|
||||||
const { toLogin, redirectFromLogin } = useRouterPush(false);
|
const { toLogin, redirectFromLogin } = useRouterPush(false);
|
||||||
const { loading: loginLoading, startLoading, endLoading } = useLoading();
|
const { loading: loginLoading, startLoading, endLoading } = useLoading();
|
||||||
|
|
||||||
@ -48,6 +50,7 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
|
|||||||
await toLogin();
|
await toLogin();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
noticeStore.clearNotice();
|
||||||
tabStore.cacheTabs();
|
tabStore.cacheTabs();
|
||||||
routeStore.resetStore();
|
routeStore.resetStore();
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { $t } from '@/locales';
|
import { $t } from '@/locales';
|
||||||
|
import { SetupStoreId } from '@/enum';
|
||||||
|
|
||||||
export const useDictStore = defineStore('dict', () => {
|
export const useDictStore = defineStore(SetupStoreId.Dict, () => {
|
||||||
const dictData = ref<{ [key: string]: Api.System.DictData[] }>({});
|
const dictData = ref<{ [key: string]: Api.System.DictData[] }>({});
|
||||||
|
|
||||||
const getDict = (key: string) => {
|
const getDict = (key: string) => {
|
||||||
|
@ -127,7 +127,7 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
|
|||||||
}
|
}
|
||||||
// @ts-expect-error no hidden field
|
// @ts-expect-error no hidden field
|
||||||
route.meta.hideInMenu = route.hidden;
|
route.meta.hideInMenu = route.hidden;
|
||||||
if (route.meta.hideInMenu && parent) {
|
if (route.meta.hideInMenu && parent && !route.meta.activeMenu) {
|
||||||
// @ts-expect-error parent.name is activeMenu type
|
// @ts-expect-error parent.name is activeMenu type
|
||||||
route.meta.activeMenu = parent.name;
|
route.meta.activeMenu = parent.name;
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ html,
|
|||||||
body,
|
body,
|
||||||
#app {
|
#app {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
font-family: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
|
@ -44,7 +44,7 @@ export const themeSettings: App.Theme.ThemeSetting = {
|
|||||||
fixedHeaderAndTab: true,
|
fixedHeaderAndTab: true,
|
||||||
sider: {
|
sider: {
|
||||||
inverted: false,
|
inverted: false,
|
||||||
width: 220,
|
width: 230,
|
||||||
collapsedWidth: 64,
|
collapsedWidth: 64,
|
||||||
mixWidth: 90,
|
mixWidth: 90,
|
||||||
mixCollapsedWidth: 64,
|
mixCollapsedWidth: 64,
|
||||||
|
2
src/typings/app.d.ts
vendored
2
src/typings/app.d.ts
vendored
@ -487,6 +487,8 @@ declare namespace App {
|
|||||||
};
|
};
|
||||||
login: {
|
login: {
|
||||||
common: {
|
common: {
|
||||||
|
title: string;
|
||||||
|
subTitle: string;
|
||||||
loginOrRegister: string;
|
loginOrRegister: string;
|
||||||
register: string;
|
register: string;
|
||||||
userNamePlaceholder: string;
|
userNamePlaceholder: string;
|
||||||
|
1
src/typings/vite-env.d.ts
vendored
1
src/typings/vite-env.d.ts
vendored
@ -114,6 +114,7 @@ declare namespace Env {
|
|||||||
readonly VITE_DEVTOOLS_LAUNCH_EDITOR?: import('vite-plugin-vue-devtools').VitePluginVueDevToolsOptions['launchEditor'];
|
readonly VITE_DEVTOOLS_LAUNCH_EDITOR?: import('vite-plugin-vue-devtools').VitePluginVueDevToolsOptions['launchEditor'];
|
||||||
readonly VITE_APP_CLIENT_ID?: string;
|
readonly VITE_APP_CLIENT_ID?: string;
|
||||||
readonly VITE_APP_ENCRYPT?: CommonType.YesOrNo;
|
readonly VITE_APP_ENCRYPT?: CommonType.YesOrNo;
|
||||||
|
readonly VITE_HEADER_FLAG?: string;
|
||||||
readonly VITE_APP_RSA_PUBLIC_KEY?: string;
|
readonly VITE_APP_RSA_PUBLIC_KEY?: string;
|
||||||
readonly VITE_APP_RSA_PRIVATE_KEY?: string;
|
readonly VITE_APP_RSA_PRIVATE_KEY?: string;
|
||||||
readonly VITE_APP_WEBSOCKET: CommonType.YesOrNo;
|
readonly VITE_APP_WEBSOCKET: CommonType.YesOrNo;
|
||||||
|
71
src/utils/export.ts
Normal file
71
src/utils/export.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import { utils, writeFile } from 'xlsx';
|
||||||
|
import { isNotNull } from '@/utils/common';
|
||||||
|
import { $t } from '@/locales';
|
||||||
|
|
||||||
|
export interface ExportExcelProps<T> {
|
||||||
|
columns: NaiveUI.TableColumn<NaiveUI.TableDataWithIndex<T>>[];
|
||||||
|
data: NaiveUI.TableDataWithIndex<T>[];
|
||||||
|
filename: string;
|
||||||
|
ignoreKeys?: (keyof NaiveUI.TableDataWithIndex<T> | NaiveUI.CustomColumnKey)[];
|
||||||
|
dicts?: Record<keyof NaiveUI.TableDataWithIndex<T>, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function exportExcel<T>({
|
||||||
|
columns,
|
||||||
|
data,
|
||||||
|
filename,
|
||||||
|
dicts,
|
||||||
|
ignoreKeys = ['index', 'operate']
|
||||||
|
}: ExportExcelProps<T>) {
|
||||||
|
const exportColumns = columns.filter(col => isTableColumnHasKey(col) && !ignoreKeys?.includes(col.key));
|
||||||
|
|
||||||
|
const excelList = data.map(item => exportColumns.map(col => getTableValue(col, item, dicts)));
|
||||||
|
|
||||||
|
const titleList = exportColumns.map(col => (isTableColumnHasTitle(col) && col.title) || null);
|
||||||
|
|
||||||
|
excelList.unshift(titleList);
|
||||||
|
|
||||||
|
const workBook = utils.book_new();
|
||||||
|
|
||||||
|
const workSheet = utils.aoa_to_sheet(excelList);
|
||||||
|
|
||||||
|
workSheet['!cols'] = exportColumns.map(item => ({
|
||||||
|
width: Math.round(Number(item.width) / 10 || 20)
|
||||||
|
}));
|
||||||
|
|
||||||
|
utils.book_append_sheet(workBook, workSheet, filename);
|
||||||
|
|
||||||
|
writeFile(workBook, `${filename}.xlsx`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTableValue<T>(
|
||||||
|
col: NaiveUI.TableColumn<NaiveUI.TableDataWithIndex<T>>,
|
||||||
|
item: NaiveUI.TableDataWithIndex<T>,
|
||||||
|
dicts?: Record<keyof NaiveUI.TableDataWithIndex<T>, string>
|
||||||
|
) {
|
||||||
|
if (!isTableColumnHasKey(col)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { key } = col;
|
||||||
|
|
||||||
|
if (key === 'operate') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNotNull(dicts?.[key]) && isNotNull(item[key])) {
|
||||||
|
return $t(item[key] as App.I18n.I18nKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
return item[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
function isTableColumnHasKey<T>(column: NaiveUI.TableColumn<T>): column is NaiveUI.TableColumnWithKey<T> {
|
||||||
|
return Boolean((column as NaiveUI.TableColumnWithKey<T>).key);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isTableColumnHasTitle<T>(column: NaiveUI.TableColumn<T>): column is NaiveUI.TableColumnWithKey<T> & {
|
||||||
|
title: string;
|
||||||
|
} {
|
||||||
|
return Boolean((column as NaiveUI.TableColumnWithKey<T>).title);
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import { watch } from 'vue';
|
import { watch } from 'vue';
|
||||||
import { useEventSource } from '@vueuse/core';
|
import { useEventSource } from '@vueuse/core';
|
||||||
import useNoticeStore from '@/store/modules/notice';
|
import useNoticeStore from '@/store/modules/notice';
|
||||||
|
import { $t } from '@/locales';
|
||||||
import { localStg } from './storage';
|
import { localStg } from './storage';
|
||||||
|
|
||||||
// 初始化
|
// 初始化
|
||||||
@ -34,9 +35,14 @@ export const initSSE = (url: any) => {
|
|||||||
read: false,
|
read: false,
|
||||||
time: new Date().toLocaleString()
|
time: new Date().toLocaleString()
|
||||||
});
|
});
|
||||||
|
let content = data.value;
|
||||||
|
const noticeType = content.match(/\[dict\.(.*?)\]/)?.[1];
|
||||||
|
if (noticeType) {
|
||||||
|
content = content.replace(`dict.${noticeType}`, $t(`dict.${noticeType}` as App.I18n.I18nKey));
|
||||||
|
}
|
||||||
window.$notification?.create({
|
window.$notification?.create({
|
||||||
title: '消息',
|
title: '消息',
|
||||||
content: data.value,
|
content,
|
||||||
type: 'success',
|
type: 'success',
|
||||||
duration: 3000
|
duration: 3000
|
||||||
});
|
});
|
||||||
|
@ -39,46 +39,69 @@ const activeModule = computed(() => moduleMap[props.module || 'pwd-login']);
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="relative min-h-screen w-full flex flex-wrap">
|
<div class="scroll box-border size-full flex">
|
||||||
<div class="hidden min-h-screen w-50% bg-primary-100 lg:block dark:bg-primary-800">
|
<div class="relative box-border hidden h-full w-65vw overflow-hidden bg-primary-50 xl:block dark:bg-primary-900">
|
||||||
<div class="size-full flex-center">
|
<div class="relative z-100 flex items-center pl-30px pt-30px">
|
||||||
<img class="w-60% sm:w-80%" :src="loginBackground" />
|
<SystemLogo class="text-32px text-primary" />
|
||||||
|
<h3 class="ml-10px text-20px font-400">{{ $t('system.title') }}</h3>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="absolute inset-x-0 inset-b-10.5% inset-t-0 z-10 m-auto w-40%">
|
||||||
|
<img class="size-full" :src="loginBackground" />
|
||||||
|
</div>
|
||||||
|
<div class="absolute bottom-80px w-full text-center">
|
||||||
|
<h1 class="text-24px font-400">{{ $t('page.login.common.title') }}</h1>
|
||||||
|
<p class="mt-8px text-14px color-gray-500">{{ $t('page.login.common.subTitle') }}</p>
|
||||||
|
</div>
|
||||||
|
<WaveBg />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full flex-col-center px-24px py-32px lg:w-50%">
|
<div class="relative h-full flex-1 xl:m-auto sm:!w-full">
|
||||||
<div class="mx-auto max-w-464px w-full">
|
<header class="flex-y-center justify-between px-30px pt-30px xl:justify-end">
|
||||||
<header class="flex-y-center justify-between">
|
<div class="relative z-100 block flex items-center xl:hidden">
|
||||||
<div class="flex-y-center gap-16px">
|
<SystemLogo class="text-32px text-primary" />
|
||||||
<SystemLogo class="text-30px text-primary sm:text-42px" />
|
<h3 class="ml-10px text-20px font-400">{{ $t('system.title') }}</h3>
|
||||||
<h3 class="text-24px text-primary font-500 sm:text-32px">{{ $t('system.title') }}</h3>
|
</div>
|
||||||
</div>
|
<div class="flex items-center justify-end">
|
||||||
<div class="flex-y-center">
|
<ThemeSchemaSwitch
|
||||||
<ThemeSchemaSwitch
|
:theme-schema="themeStore.themeScheme"
|
||||||
:theme-schema="themeStore.themeScheme"
|
:show-tooltip="false"
|
||||||
:show-tooltip="false"
|
class="text-20px lt-sm:text-18px"
|
||||||
class="text-20px lt-sm:text-18px"
|
@switch="themeStore.toggleThemeScheme"
|
||||||
@switch="themeStore.toggleThemeScheme"
|
/>
|
||||||
/>
|
<LangSwitch
|
||||||
<LangSwitch
|
v-if="themeStore.header.multilingual.visible"
|
||||||
v-if="themeStore.header.multilingual.visible"
|
:lang="appStore.locale"
|
||||||
:lang="appStore.locale"
|
:lang-options="appStore.localeOptions"
|
||||||
:lang-options="appStore.localeOptions"
|
:show-tooltip="false"
|
||||||
:show-tooltip="false"
|
class="text-20px lt-sm:text-18px"
|
||||||
class="text-20px lt-sm:text-18px"
|
@change-lang="appStore.changeLocale"
|
||||||
@change-lang="appStore.changeLocale"
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
</header>
|
||||||
</header>
|
<main
|
||||||
<main class="pt-24px">
|
class="m-auto mt-10% h-630px max-w-450px w-full rounded-5px bg-cover px-24px xl:absolute xl:inset-0 lg:mt-15% xl:mt-auto"
|
||||||
<div>
|
>
|
||||||
<Transition :name="themeStore.page.animateMode" mode="out-in" appear>
|
<Transition :name="themeStore.page.animateMode" mode="out-in" appear>
|
||||||
<component :is="activeModule.component" />
|
<component :is="activeModule.component" />
|
||||||
</Transition>
|
</Transition>
|
||||||
</div>
|
</main>
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped>
|
||||||
|
.scroll {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll {
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll {
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -40,6 +40,10 @@ async function handleSubmit() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<div class="mb-5px text-32px text-black font-600 sm:text-30px dark:text-white">
|
||||||
|
{{ $t('page.login.codeLogin.title') }}
|
||||||
|
</div>
|
||||||
|
<div class="pb-18px text-16px text-#858585">请输入您的手机号,我们将发送验证码到您的手机</div>
|
||||||
<NForm ref="formRef" :model="model" :rules="rules" size="large" :show-label="false" @keyup.enter="handleSubmit">
|
<NForm ref="formRef" :model="model" :rules="rules" size="large" :show-label="false" @keyup.enter="handleSubmit">
|
||||||
<NFormItem path="phone">
|
<NFormItem path="phone">
|
||||||
<NInput v-model:value="model.phone" :placeholder="$t('page.login.common.phonePlaceholder')" />
|
<NInput v-model:value="model.phone" :placeholder="$t('page.login.common.phonePlaceholder')" />
|
||||||
@ -52,15 +56,32 @@ async function handleSubmit() {
|
|||||||
</NButton>
|
</NButton>
|
||||||
</div>
|
</div>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NSpace vertical :size="18" class="w-full">
|
<NSpace vertical :size="20" class="w-full">
|
||||||
<NButton type="primary" size="large" round block @click="handleSubmit">
|
<NButton type="primary" size="large" block @click="handleSubmit">
|
||||||
{{ $t('common.confirm') }}
|
{{ $t('page.login.codeLogin.title') }}
|
||||||
</NButton>
|
</NButton>
|
||||||
<NButton size="large" round block @click="toggleLoginModule('pwd-login')">
|
<NButton size="large" block @click="toggleLoginModule('pwd-login')">
|
||||||
{{ $t('page.login.common.back') }}
|
{{ $t('page.login.common.back') }}
|
||||||
</NButton>
|
</NButton>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
</NForm>
|
</NForm>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped>
|
||||||
|
:deep(.n-base-selection),
|
||||||
|
:deep(.n-input) {
|
||||||
|
--n-height: 42px !important;
|
||||||
|
--n-font-size: 16px !important;
|
||||||
|
--n-border-radius: 8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.n-base-selection-label) {
|
||||||
|
padding: 0 6px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.n-button) {
|
||||||
|
--n-height: 42px !important;
|
||||||
|
--n-font-size: 18px !important;
|
||||||
|
--n-border-radius: 8px !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -124,8 +124,8 @@ async function handleSocialLogin(type: Api.System.SocialSource) {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="mb-12px text-24px text-black font-500 sm:text-30px dark:text-white">登录到您的账户</div>
|
<div class="mb-5px text-32px text-black font-600 dark:text-white">登录到您的账户</div>
|
||||||
<div class="pb-24px text-18px text-#858585">欢迎回来!请输入您的账户信息</div>
|
<div class="pb-18px text-16px text-#858585">欢迎回来!请输入您的账户信息</div>
|
||||||
<NForm
|
<NForm
|
||||||
ref="formRef"
|
ref="formRef"
|
||||||
:model="model"
|
:model="model"
|
||||||
@ -156,16 +156,16 @@ async function handleSocialLogin(type: Api.System.SocialSource) {
|
|||||||
<NFormItem v-if="captchaEnabled" path="code">
|
<NFormItem v-if="captchaEnabled" path="code">
|
||||||
<div class="w-full flex-y-center gap-16px">
|
<div class="w-full flex-y-center gap-16px">
|
||||||
<NInput v-model:value="model.code" :placeholder="$t('page.login.common.codePlaceholder')" />
|
<NInput v-model:value="model.code" :placeholder="$t('page.login.common.codePlaceholder')" />
|
||||||
<NSpin :show="codeLoading" :size="28" class="h-52px">
|
<NSpin :show="codeLoading" :size="28" class="h-42px">
|
||||||
<NButton :focusable="false" class="login-code h-52px w-136px" @click="handleFetchCaptchaCode">
|
<NButton :focusable="false" class="login-code h-42px w-136px" @click="handleFetchCaptchaCode">
|
||||||
<img v-if="codeUrl" :src="codeUrl" />
|
<img v-if="codeUrl" :src="codeUrl" />
|
||||||
<NEmpty v-else :show-icon="false" description="暂无验证码" />
|
<NEmpty v-else :show-icon="false" description="暂无验证码" />
|
||||||
</NButton>
|
</NButton>
|
||||||
</NSpin>
|
</NSpin>
|
||||||
</div>
|
</div>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NSpace vertical :size="16" class="mb-8px">
|
<NSpace vertical :size="12" class="mb-8px">
|
||||||
<div class="mx-6px mb-10px flex-y-center justify-between">
|
<div class="mx-6px mb-8px flex-y-center justify-between">
|
||||||
<NCheckbox v-model:checked="remberMe" size="large">{{ $t('page.login.pwdLogin.rememberMe') }}</NCheckbox>
|
<NCheckbox v-model:checked="remberMe" size="large">{{ $t('page.login.pwdLogin.rememberMe') }}</NCheckbox>
|
||||||
<NA type="primary" class="text-18px" @click="toggleLoginModule('reset-pwd')">
|
<NA type="primary" class="text-18px" @click="toggleLoginModule('reset-pwd')">
|
||||||
{{ $t('page.login.pwdLogin.forgetPassword') }}
|
{{ $t('page.login.pwdLogin.forgetPassword') }}
|
||||||
@ -199,7 +199,7 @@ async function handleSocialLogin(type: Api.System.SocialSource) {
|
|||||||
</NButton>
|
</NButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-32px w-full text-center text-18px text-#858585">
|
<div class="mt-24px w-full text-center text-18px text-#858585">
|
||||||
您还没有账户?
|
您还没有账户?
|
||||||
<NA type="primary" class="text-18px" @click="toggleLoginModule('register')">
|
<NA type="primary" class="text-18px" @click="toggleLoginModule('register')">
|
||||||
{{ $t('page.login.common.register') }}
|
{{ $t('page.login.common.register') }}
|
||||||
@ -216,13 +216,13 @@ async function handleSocialLogin(type: Api.System.SocialSource) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
height: 52px;
|
height: 42px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.n-base-selection),
|
:deep(.n-base-selection),
|
||||||
:deep(.n-input) {
|
:deep(.n-input) {
|
||||||
--n-height: 52px !important;
|
--n-height: 42px !important;
|
||||||
--n-font-size: 16px !important;
|
--n-font-size: 16px !important;
|
||||||
--n-border-radius: 8px !important;
|
--n-border-radius: 8px !important;
|
||||||
}
|
}
|
||||||
@ -237,7 +237,7 @@ async function handleSocialLogin(type: Api.System.SocialSource) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
:deep(.n-button) {
|
:deep(.n-button) {
|
||||||
--n-height: 52px !important;
|
--n-height: 42px !important;
|
||||||
--n-font-size: 18px !important;
|
--n-font-size: 18px !important;
|
||||||
--n-border-radius: 8px !important;
|
--n-border-radius: 8px !important;
|
||||||
}
|
}
|
||||||
|
@ -104,8 +104,8 @@ handleFetchCaptchaCode();
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="mb-12px text-24px text-black font-500 sm:text-30px dark:text-white">注册新账户</div>
|
<div class="mb-5px text-32px text-black font-600 sm:text-30px dark:text-white">注册新账户</div>
|
||||||
<div class="pb-24px text-18px text-#858585">欢迎注册!请输入您的账户信息</div>
|
<div class="pb-18px text-16px text-#858585">欢迎注册!请输入您的账户信息</div>
|
||||||
<NForm
|
<NForm
|
||||||
ref="formRef"
|
ref="formRef"
|
||||||
:model="model"
|
:model="model"
|
||||||
@ -147,14 +147,14 @@ handleFetchCaptchaCode();
|
|||||||
</NSpin>
|
</NSpin>
|
||||||
</div>
|
</div>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NSpace vertical :size="18" class="w-full pt-6px">
|
<NSpace vertical :size="18" class="w-full">
|
||||||
<NButton type="primary" size="large" block :loading="registerLoading" @click="handleSubmit">
|
<NButton type="primary" size="large" block :loading="registerLoading" @click="handleSubmit">
|
||||||
{{ $t('page.login.common.register') }}
|
{{ $t('page.login.common.register') }}
|
||||||
</NButton>
|
</NButton>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
</NForm>
|
</NForm>
|
||||||
|
|
||||||
<div class="mt-32px w-full text-center text-18px text-#858585">
|
<div class="mt-24px w-full text-center text-18px text-#858585">
|
||||||
您已有账户?
|
您已有账户?
|
||||||
<NA type="primary" class="text-18px" @click="toggleLoginModule('pwd-login')">
|
<NA type="primary" class="text-18px" @click="toggleLoginModule('pwd-login')">
|
||||||
{{ $t('common.login') }}
|
{{ $t('common.login') }}
|
||||||
@ -177,7 +177,7 @@ handleFetchCaptchaCode();
|
|||||||
|
|
||||||
:deep(.n-base-selection),
|
:deep(.n-base-selection),
|
||||||
:deep(.n-input) {
|
:deep(.n-input) {
|
||||||
--n-height: 52px !important;
|
--n-height: 42px !important;
|
||||||
--n-font-size: 16px !important;
|
--n-font-size: 16px !important;
|
||||||
--n-border-radius: 8px !important;
|
--n-border-radius: 8px !important;
|
||||||
}
|
}
|
||||||
@ -187,7 +187,7 @@ handleFetchCaptchaCode();
|
|||||||
}
|
}
|
||||||
|
|
||||||
:deep(.n-button) {
|
:deep(.n-button) {
|
||||||
--n-height: 52px !important;
|
--n-height: 42px !important;
|
||||||
--n-font-size: 18px !important;
|
--n-font-size: 18px !important;
|
||||||
--n-border-radius: 8px !important;
|
--n-border-radius: 8px !important;
|
||||||
}
|
}
|
||||||
|
@ -46,10 +46,10 @@ async function handleSubmit() {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="mb-12px text-24px text-black font-500 sm:text-30px dark:text-white">
|
<div class="mb-5px text-32px text-black font-600 sm:text-30px dark:text-white">
|
||||||
{{ $t('page.login.resetPwd.title') }}
|
{{ $t('page.login.resetPwd.title') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="pb-24px text-18px text-#858585">请输入您的手机号,我们将发送验证码到您的手机</div>
|
<div class="pb-18px text-16px text-#858585">请输入您的手机号,我们将发送验证码到您的手机</div>
|
||||||
<NForm ref="formRef" :model="model" :rules="rules" size="large" :show-label="false" @keyup.enter="handleSubmit">
|
<NForm ref="formRef" :model="model" :rules="rules" size="large" :show-label="false" @keyup.enter="handleSubmit">
|
||||||
<NFormItem path="phone">
|
<NFormItem path="phone">
|
||||||
<NInput v-model:value="model.phone" :placeholder="$t('page.login.common.phonePlaceholder')" />
|
<NInput v-model:value="model.phone" :placeholder="$t('page.login.common.phonePlaceholder')" />
|
||||||
@ -73,7 +73,7 @@ async function handleSubmit() {
|
|||||||
:placeholder="$t('page.login.common.confirmPasswordPlaceholder')"
|
:placeholder="$t('page.login.common.confirmPasswordPlaceholder')"
|
||||||
/>
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NSpace vertical :size="18" class="w-full">
|
<NSpace vertical :size="20" class="w-full">
|
||||||
<NButton type="primary" size="large" block @click="handleSubmit">
|
<NButton type="primary" size="large" block @click="handleSubmit">
|
||||||
{{ $t('page.login.resetPwd.title') }}
|
{{ $t('page.login.resetPwd.title') }}
|
||||||
</NButton>
|
</NButton>
|
||||||
@ -88,7 +88,7 @@ async function handleSubmit() {
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
:deep(.n-base-selection),
|
:deep(.n-base-selection),
|
||||||
:deep(.n-input) {
|
:deep(.n-input) {
|
||||||
--n-height: 52px !important;
|
--n-height: 42px !important;
|
||||||
--n-font-size: 16px !important;
|
--n-font-size: 16px !important;
|
||||||
--n-border-radius: 8px !important;
|
--n-border-radius: 8px !important;
|
||||||
}
|
}
|
||||||
@ -98,7 +98,7 @@ async function handleSubmit() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
:deep(.n-button) {
|
:deep(.n-button) {
|
||||||
--n-height: 52px !important;
|
--n-height: 42px !important;
|
||||||
--n-font-size: 18px !important;
|
--n-font-size: 18px !important;
|
||||||
--n-border-radius: 8px !important;
|
--n-border-radius: 8px !important;
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,7 @@ const deptData = ref<Api.System.Dept[]>([]);
|
|||||||
const userOptions = ref<CommonType.Option<CommonType.IdType>[]>([]);
|
const userOptions = ref<CommonType.Option<CommonType.IdType>[]>([]);
|
||||||
const placeholder = ref<string>($t('page.system.dept.placeholder.defaultLeaderPlaceHolder'));
|
const placeholder = ref<string>($t('page.system.dept.placeholder.defaultLeaderPlaceHolder'));
|
||||||
const disabled = ref<boolean>(false);
|
const disabled = ref<boolean>(false);
|
||||||
|
const expandedKeys = ref<CommonType.IdType[]>([]);
|
||||||
|
|
||||||
const title = computed(() => {
|
const title = computed(() => {
|
||||||
const titles: Record<NaiveUI.TableOperateType, string> = {
|
const titles: Record<NaiveUI.TableOperateType, string> = {
|
||||||
@ -55,7 +56,7 @@ const model: Model = reactive(createDefaultModel());
|
|||||||
|
|
||||||
function createDefaultModel(): Model {
|
function createDefaultModel(): Model {
|
||||||
return {
|
return {
|
||||||
parentId: '',
|
parentId: props.rowData?.deptId || '',
|
||||||
deptName: '',
|
deptName: '',
|
||||||
deptCategory: '',
|
deptCategory: '',
|
||||||
orderNum: null,
|
orderNum: null,
|
||||||
@ -80,7 +81,6 @@ const rules: Record<RuleKey, App.Global.FormRule> = {
|
|||||||
function handleUpdateModelWhenEdit() {
|
function handleUpdateModelWhenEdit() {
|
||||||
if (props.operateType === 'add') {
|
if (props.operateType === 'add') {
|
||||||
Object.assign(model, createDefaultModel());
|
Object.assign(model, createDefaultModel());
|
||||||
model.parentId = props.rowData?.deptId || 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.operateType === 'edit' && props.rowData) {
|
if (props.operateType === 'edit' && props.rowData) {
|
||||||
@ -144,6 +144,7 @@ async function getDeptData() {
|
|||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
deptData.value = handleTree(data, { idField: 'deptId' });
|
deptData.value = handleTree(data, { idField: 'deptId' });
|
||||||
|
expandedKeys.value = [deptData.value[0].deptId];
|
||||||
}
|
}
|
||||||
endDeptLoading();
|
endDeptLoading();
|
||||||
}
|
}
|
||||||
@ -186,15 +187,15 @@ watch(visible, () => {
|
|||||||
<NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
|
<NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
|
||||||
<NDrawerContent :title="title" :native-scrollbar="false" closable>
|
<NDrawerContent :title="title" :native-scrollbar="false" closable>
|
||||||
<NForm ref="formRef" :model="model" :rules="rules">
|
<NForm ref="formRef" :model="model" :rules="rules">
|
||||||
<NFormItem v-if="model.parentId != 0" :label="$t('page.system.dept.parentId')" path="parentId">
|
<NFormItem v-if="model.parentId !== 0" :label="$t('page.system.dept.parentId')" path="parentId">
|
||||||
<NTreeSelect
|
<NTreeSelect
|
||||||
v-model:value="model.parentId"
|
v-model:value="model.parentId"
|
||||||
|
v-model:expanded-keys="expandedKeys"
|
||||||
:loading="deptLoading"
|
:loading="deptLoading"
|
||||||
clearable
|
clearable
|
||||||
:options="deptData"
|
:options="deptData"
|
||||||
label-field="deptName"
|
label-field="deptName"
|
||||||
key-field="deptId"
|
key-field="deptId"
|
||||||
default-expand-all
|
|
||||||
:placeholder="$t('page.system.dept.form.parentId.required')"
|
:placeholder="$t('page.system.dept.form.parentId.required')"
|
||||||
/>
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
|
@ -38,6 +38,8 @@ const visible = defineModel<boolean>('visible', {
|
|||||||
default: false
|
default: false
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const defaultIcon = import.meta.env.VITE_MENU_ICON;
|
||||||
|
|
||||||
const iconType = ref<Api.System.IconType>('1');
|
const iconType = ref<Api.System.IconType>('1');
|
||||||
const { formRef, validate, restoreValidation } = useNaiveForm();
|
const { formRef, validate, restoreValidation } = useNaiveForm();
|
||||||
const { createRequiredRule, createNumberRequiredRule } = useFormRules();
|
const { createRequiredRule, createNumberRequiredRule } = useFormRules();
|
||||||
@ -69,7 +71,7 @@ function createDefaultModel(): Model {
|
|||||||
visible: '0',
|
visible: '0',
|
||||||
status: '0',
|
status: '0',
|
||||||
perms: '',
|
perms: '',
|
||||||
icon: undefined,
|
icon: defaultIcon,
|
||||||
remark: ''
|
remark: ''
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -118,6 +120,7 @@ const localIconOptions = localIcons.map<SelectOption>(item => ({
|
|||||||
|
|
||||||
function handleInitModel() {
|
function handleInitModel() {
|
||||||
queryList.value = [];
|
queryList.value = [];
|
||||||
|
iconType.value = '1';
|
||||||
Object.assign(model, createDefaultModel());
|
Object.assign(model, createDefaultModel());
|
||||||
|
|
||||||
if (props.operateType === 'edit' && props.rowData) {
|
if (props.operateType === 'edit' && props.rowData) {
|
||||||
@ -208,7 +211,7 @@ async function handleSubmit() {
|
|||||||
visible: menuVisible,
|
visible: menuVisible,
|
||||||
status,
|
status,
|
||||||
perms,
|
perms,
|
||||||
icon,
|
icon: icon || defaultIcon,
|
||||||
component: processComponent(component),
|
component: processComponent(component),
|
||||||
remark
|
remark
|
||||||
};
|
};
|
||||||
|
@ -2,8 +2,7 @@
|
|||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { NButton, NDivider } from 'naive-ui';
|
import { NButton, NDivider } from 'naive-ui';
|
||||||
import { useLoading } from '@sa/hooks';
|
import { useLoading } from '@sa/hooks';
|
||||||
import { fetchBatchDeletePost, fetchGetPostList } from '@/service/api/system/post';
|
import { fetchBatchDeletePost, fetchGetPostDeptSelect, fetchGetPostList } from '@/service/api/system/post';
|
||||||
import { fetchGetDeptTree } from '@/service/api/system';
|
|
||||||
import { useAppStore } from '@/store/modules/app';
|
import { useAppStore } from '@/store/modules/app';
|
||||||
import { useAuth } from '@/hooks/business/auth';
|
import { useAuth } from '@/hooks/business/auth';
|
||||||
import { useDownload } from '@/hooks/business/download';
|
import { useDownload } from '@/hooks/business/download';
|
||||||
@ -191,16 +190,17 @@ const { loading: treeLoading, startLoading: startTreeLoading, endLoading: endTre
|
|||||||
const deptPattern = ref<string>();
|
const deptPattern = ref<string>();
|
||||||
const deptData = ref<Api.Common.CommonTreeRecord>([]);
|
const deptData = ref<Api.Common.CommonTreeRecord>([]);
|
||||||
const selectedKeys = ref<string[]>([]);
|
const selectedKeys = ref<string[]>([]);
|
||||||
async function getTreeData() {
|
|
||||||
|
async function getDeptOptions() {
|
||||||
|
// 加载
|
||||||
startTreeLoading();
|
startTreeLoading();
|
||||||
const { data: tree, error } = await fetchGetDeptTree();
|
const { data: tree, error } = await fetchGetPostDeptSelect();
|
||||||
if (!error) {
|
if (!error) {
|
||||||
deptData.value = tree;
|
deptData.value = tree;
|
||||||
}
|
}
|
||||||
endTreeLoading();
|
endTreeLoading();
|
||||||
}
|
}
|
||||||
|
getDeptOptions();
|
||||||
getTreeData();
|
|
||||||
|
|
||||||
function handleClickTree(keys: string[]) {
|
function handleClickTree(keys: string[]) {
|
||||||
searchParams.belongDeptId = keys.length ? keys[0] : null;
|
searchParams.belongDeptId = keys.length ? keys[0] : null;
|
||||||
@ -210,7 +210,7 @@ function handleClickTree(keys: string[]) {
|
|||||||
|
|
||||||
function handleResetTreeData() {
|
function handleResetTreeData() {
|
||||||
deptPattern.value = undefined;
|
deptPattern.value = undefined;
|
||||||
getTreeData();
|
getDeptOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleResetSearch() {
|
function handleResetSearch() {
|
||||||
@ -323,11 +323,11 @@ function handleResetSearch() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
:deep(.n-tree-node) {
|
:deep(.n-tree-node) {
|
||||||
height: 33px;
|
height: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.n-tree-node-switcher) {
|
:deep(.n-tree-node-switcher) {
|
||||||
height: 33px;
|
height: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.n-tree-node-switcher__icon) {
|
:deep(.n-tree-node-switcher__icon) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<script setup lang="tsx">
|
<script setup lang="tsx">
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { NButton, NDivider } from 'naive-ui';
|
import { NAvatar, NButton, NDivider, NEllipsis } from 'naive-ui';
|
||||||
import { useBoolean, useLoading } from '@sa/hooks';
|
import { useBoolean, useLoading } from '@sa/hooks';
|
||||||
import { jsonClone } from '@sa/utils';
|
import { jsonClone } from '@sa/utils';
|
||||||
import { fetchBatchDeleteUser, fetchGetDeptTree, fetchGetUserList, fetchUpdateUserStatus } from '@/service/api/system';
|
import { fetchBatchDeleteUser, fetchGetDeptTree, fetchGetUserList, fetchUpdateUserStatus } from '@/service/api/system';
|
||||||
@ -12,6 +12,7 @@ import { useDownload } from '@/hooks/business/download';
|
|||||||
import ButtonIcon from '@/components/custom/button-icon.vue';
|
import ButtonIcon from '@/components/custom/button-icon.vue';
|
||||||
import { $t } from '@/locales';
|
import { $t } from '@/locales';
|
||||||
import StatusSwitch from '@/components/custom/status-switch.vue';
|
import StatusSwitch from '@/components/custom/status-switch.vue';
|
||||||
|
import DictTag from '@/components/custom/dict-tag.vue';
|
||||||
import UserOperateDrawer from './modules/user-operate-drawer.vue';
|
import UserOperateDrawer from './modules/user-operate-drawer.vue';
|
||||||
import UserImportModal from './modules/user-import-modal.vue';
|
import UserImportModal from './modules/user-import-modal.vue';
|
||||||
import UserPasswordDrawer from './modules/user-password-drawer.vue';
|
import UserPasswordDrawer from './modules/user-password-drawer.vue';
|
||||||
@ -65,41 +66,64 @@ const {
|
|||||||
key: 'index',
|
key: 'index',
|
||||||
title: $t('common.index'),
|
title: $t('common.index'),
|
||||||
align: 'center',
|
align: 'center',
|
||||||
width: 64
|
width: 48
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'userName',
|
key: 'userName',
|
||||||
title: $t('page.system.user.userName'),
|
title: $t('page.system.user.userName'),
|
||||||
align: 'center',
|
align: 'left',
|
||||||
minWidth: 120,
|
width: 200,
|
||||||
ellipsis: true
|
ellipsis: true,
|
||||||
|
render: row => {
|
||||||
|
return (
|
||||||
|
<div class="flex items-center justify-center gap-2">
|
||||||
|
<NAvatar src={row.avatar} class="bg-primary">
|
||||||
|
{row.avatar ? undefined : row.nickName.charAt(0)}
|
||||||
|
</NAvatar>
|
||||||
|
<div class="max-w-160px flex flex-col">
|
||||||
|
<NEllipsis>{row.userName}</NEllipsis>
|
||||||
|
<NEllipsis>{row.nickName}</NEllipsis>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'nickName',
|
key: 'sex',
|
||||||
title: $t('page.system.user.nickName'),
|
title: $t('page.system.user.sex'),
|
||||||
align: 'center',
|
align: 'center',
|
||||||
minWidth: 120,
|
width: 80,
|
||||||
ellipsis: true
|
ellipsis: true,
|
||||||
|
render(row) {
|
||||||
|
return <DictTag value={row.sex} dictCode="sys_user_sex" />;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'deptName',
|
key: 'deptName',
|
||||||
title: $t('page.system.user.deptName'),
|
title: $t('page.system.user.deptName'),
|
||||||
align: 'center',
|
align: 'center',
|
||||||
minWidth: 120,
|
width: 120,
|
||||||
|
ellipsis: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'email',
|
||||||
|
title: $t('page.system.user.email'),
|
||||||
|
align: 'center',
|
||||||
|
width: 120,
|
||||||
ellipsis: true
|
ellipsis: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'phonenumber',
|
key: 'phonenumber',
|
||||||
title: $t('page.system.user.phonenumber'),
|
title: $t('page.system.user.phonenumber'),
|
||||||
align: 'center',
|
align: 'center',
|
||||||
minWidth: 120,
|
width: 120,
|
||||||
ellipsis: true
|
ellipsis: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'status',
|
key: 'status',
|
||||||
title: $t('page.system.user.status'),
|
title: $t('page.system.user.status'),
|
||||||
align: 'center',
|
align: 'center',
|
||||||
minWidth: 80,
|
width: 80,
|
||||||
render(row) {
|
render(row) {
|
||||||
return (
|
return (
|
||||||
<StatusSwitch
|
<StatusSwitch
|
||||||
@ -115,7 +139,7 @@ const {
|
|||||||
key: 'createTime',
|
key: 'createTime',
|
||||||
title: $t('page.system.user.createTime'),
|
title: $t('page.system.user.createTime'),
|
||||||
align: 'center',
|
align: 'center',
|
||||||
minWidth: 120
|
width: 120
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'operate',
|
key: 'operate',
|
||||||
@ -341,7 +365,7 @@ function handleResetSearch() {
|
|||||||
:data="data"
|
:data="data"
|
||||||
size="small"
|
size="small"
|
||||||
:flex-height="!appStore.isMobile"
|
:flex-height="!appStore.isMobile"
|
||||||
:scroll-x="962"
|
:scroll-x="1200"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
remote
|
remote
|
||||||
:row-key="row => row.userId"
|
:row-key="row => row.userId"
|
||||||
@ -391,11 +415,11 @@ function handleResetSearch() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
:deep(.n-tree-node) {
|
:deep(.n-tree-node) {
|
||||||
height: 25px;
|
height: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.n-tree-node-switcher) {
|
:deep(.n-tree-node-switcher) {
|
||||||
height: 25px;
|
height: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.n-tree-node-switcher__icon) {
|
:deep(.n-tree-node-switcher__icon) {
|
||||||
|
@ -356,7 +356,7 @@ const columns: NaiveUI.TableColumn<Api.Tool.GenTableColumn>[] = [
|
|||||||
<NFormItemGi span="24 s:12" path="moduleName">
|
<NFormItemGi span="24 s:12" path="moduleName">
|
||||||
<template #label>
|
<template #label>
|
||||||
<div class="flex-center">
|
<div class="flex-center">
|
||||||
<FormTip content="可理解为子系统名,例如 system" />
|
<FormTip content="可理解为子系统名,例如 system,flow-instance。避免驼峰命名" />
|
||||||
<span class="pl-3px">生成模块名</span>
|
<span class="pl-3px">生成模块名</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
Reference in New Issue
Block a user