8 Commits

Author SHA1 Message Date
003c43140b chore(projects): 更换图标 2025-08-16 13:34:25 +08:00
6850347b89 Merge branch 'master' into tauri 2025-08-16 11:56:14 +08:00
fb5a59d81b Merge branch 'master' into tauri
# Conflicts:
#	package.json
#	pnpm-lock.yaml
2025-07-24 17:49:12 +08:00
8147f1652e chore(release): release tauri v1.1.0 2025-07-01 10:53:41 +08:00
d5be9dc08e Merge branch 'master' into tauri 2025-06-05 22:16:58 +08:00
2f9576f53d Merge branch 'master' into tauri 2025-05-21 11:02:20 +08:00
ba7395ac18 Merge branch 'dev' into tauri 2025-05-17 20:17:20 +08:00
d49728a796 feat: 新增 tauri 适配 2025-05-16 17:50:23 +08:00
101 changed files with 13240 additions and 2188 deletions

View File

@ -15,8 +15,6 @@ 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 私钥与后端加密公钥对应 如更换需前后端一同更换

View File

@ -12,8 +12,6 @@ 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 私钥与后端加密公钥对应 如更换需前后端一同更换

View File

@ -12,8 +12,6 @@ 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 私钥与后端加密公钥对应 如更换需前后端一同更换

View File

@ -31,8 +31,7 @@
"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"]
} }

View File

@ -1,4 +1,4 @@
import type { ProxyOptions } from 'vite'; import type { HttpProxy, 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, options) => { configure: (_proxy: HttpProxy.Server, options: ProxyOptions) => {
_proxy.on('proxyReq', (_proxyReq, req, _res) => { _proxy.on('proxyReq', (_proxyReq, req, _res) => {
if (!enableLog) return; if (!enableLog) return;

View File

@ -10,7 +10,6 @@ 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;
@ -59,7 +58,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", StrUtil.toSymbolCase(genTable.getModuleName(), '-')); velocityContext.put("moduleName", 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()));
@ -125,12 +124,11 @@ 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");
DataBaseType dataBaseType = DataBaseHelper.getDataBaseType(); if (DataBaseHelper.isOracle()) {
if (dataBaseType.isOracle()) {
templates.add("vm/sql/oracle/sql.vm"); templates.add("vm/sql/oracle/sql.vm");
} else if (dataBaseType.isPostgreSql()) { } else if (DataBaseHelper.isPostgerSql()) {
templates.add("vm/sql/postgres/sql.vm"); templates.add("vm/sql/postgres/sql.vm");
} else if (dataBaseType.isSqlServer()) { } else if (DataBaseHelper.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");
@ -165,7 +163,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);
} }
@ -188,17 +186,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, soybeanModuleName, StrUtil.toSymbolCase(businessName, '-')); fileName = StringUtils.format("{}/views/{}/{}/index.vue", soybeanPath, moduleName, 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, soybeanModuleName, StrUtil.toSymbolCase(businessName, '-')); fileName = StringUtils.format("{}/views/{}/{}/index.vue", soybeanPath, moduleName, 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, soybeanModuleName, StrUtil.toSymbolCase(businessName, '-')); fileName = StringUtils.format("{}/typings/api/{}.{}.api.d.ts", soybeanPath, moduleName, StrUtil.toSymbolCase(businessName, '-'));
} else if (template.contains("api.ts.vm")) { } else if (template.contains("api.ts.vm")) {
fileName = StringUtils.format("{}/service/api/{}/{}.ts", soybeanPath, soybeanModuleName, StrUtil.toSymbolCase(businessName, '-')); fileName = StringUtils.format("{}/service/api/{}/{}.ts", soybeanPath, moduleName, 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, soybeanModuleName, StrUtil.toSymbolCase(businessName, '-'), StrUtil.toSymbolCase(businessName, '-')); fileName = StringUtils.format("{}/views/{}/{}/modules/{}-search.vue", soybeanPath, moduleName, 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, soybeanModuleName, StrUtil.toSymbolCase(businessName, '-'), StrUtil.toSymbolCase(businessName, '-')); fileName = StringUtils.format("{}/views/{}/{}/modules/{}-operate-drawer.vue", soybeanPath, moduleName, StrUtil.toSymbolCase(businessName, '-'), StrUtil.toSymbolCase(businessName, '-'));
} }
return fileName; return fileName;
} }

View File

@ -41,18 +41,21 @@
"scripts": { "scripts": {
"build": "vite build --mode prod", "build": "vite build --mode prod",
"build:dev": "vite build --mode dev", "build:dev": "vite build --mode dev",
"build:tauri": "pnpm tauri build",
"build:test": "vite build --mode test", "build:test": "vite build --mode test",
"cleanup": "sa cleanup", "cleanup": "sa cleanup",
"commit": "sa git-commit", "commit": "sa git-commit",
"commit:zh": "sa git-commit -l=zh-cn", "commit:zh": "sa git-commit -l=zh-cn",
"dev": "vite --mode dev", "dev": "vite --mode dev",
"dev:prod": "vite --mode prod", "dev:prod": "vite --mode prod",
"dev:tauri": "pnpm tauri dev",
"dev:test": "vite --mode test", "dev:test": "vite --mode test",
"gen-route": "sa gen-route", "gen-route": "sa gen-route",
"lint": "eslint . --fix", "lint": "eslint . --fix",
"prepare": "simple-git-hooks", "prepare": "simple-git-hooks",
"preview": "vite preview", "preview": "vite preview",
"release": "sa release", "release": "sa release",
"tauri-icon": "pnpm tauri icon ./public/logo.png",
"typecheck": "vue-tsc --noEmit --skipLibCheck", "typecheck": "vue-tsc --noEmit --skipLibCheck",
"update-pkg": "sa update-pkg" "update-pkg": "sa update-pkg"
}, },
@ -65,12 +68,13 @@
"@sa/materials": "workspace:*", "@sa/materials": "workspace:*",
"@sa/tinymce": "workspace:*", "@sa/tinymce": "workspace:*",
"@sa/utils": "workspace:*", "@sa/utils": "workspace:*",
"@tauri-apps/api": "2.5.0",
"@types/streamsaver": "^2.0.5", "@types/streamsaver": "^2.0.5",
"@vueuse/core": "13.8.0", "@vueuse/core": "13.5.0",
"clipboard": "2.0.11", "clipboard": "2.0.11",
"dayjs": "1.11.14", "dayjs": "1.11.13",
"defu": "6.1.4", "defu": "6.1.4",
"echarts": "6.0.0", "echarts": "5.6.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,47 +84,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.20", "vue": "3.5.17",
"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.11", "vue-i18n": "11.1.9",
"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.378", "@iconify/json": "2.2.357",
"@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.3.0", "@tauri-apps/cli": "2.5.0",
"@types/node": "24.0.13",
"@types/nprogress": "0.2.3", "@types/nprogress": "0.2.3",
"@unocss/eslint-config": "66.4.2", "@unocss/eslint-config": "66.3.3",
"@unocss/preset-icons": "66.4.2", "@unocss/preset-icons": "66.3.3",
"@unocss/preset-uno": "66.4.2", "@unocss/preset-uno": "66.3.3",
"@unocss/transformer-directives": "66.4.2", "@unocss/transformer-directives": "66.3.3",
"@unocss/transformer-variant-group": "66.4.2", "@unocss/transformer-variant-group": "66.3.3",
"@unocss/vite": "66.4.2", "@unocss/vite": "66.3.3",
"@vitejs/plugin-vue": "6.0.1", "@vitejs/plugin-vue": "6.0.0",
"@vitejs/plugin-vue-jsx": "5.1.0", "@vitejs/plugin-vue-jsx": "5.0.1",
"consola": "3.4.2", "consola": "3.4.2",
"eslint": "9.34.0", "eslint": "9.31.0",
"eslint-plugin-vue": "10.4.0", "eslint-plugin-vue": "10.3.0",
"kolorist": "1.8.0", "kolorist": "1.8.0",
"sass": "1.91.0", "sass": "1.89.2",
"simple-git-hooks": "2.13.1", "simple-git-hooks": "2.13.0",
"tsx": "4.20.5", "tsx": "4.20.3",
"typescript": "5.9.2", "typescript": "5.8.3",
"unplugin-icons": "22.2.0", "unplugin-icons": "22.1.0",
"unplugin-vue-components": "29.0.0", "unplugin-vue-components": "28.8.0",
"vite": "7.1.3", "vite": "7.0.4",
"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": "8.0.1", "vite-plugin-vue-devtools": "7.7.7",
"vue-eslint-parser": "10.2.0", "vue-eslint-parser": "10.2.0",
"vue-tsc": "3.0.6" "vue-tsc": "3.0.1"
}, },
"simple-git-hooks": { "simple-git-hooks": {
"commit-msg": "pnpm sa git-commit-verify", "commit-msg": "pnpm sa git-commit-verify",

View File

@ -11,7 +11,7 @@
}, },
"dependencies": { "dependencies": {
"@sa/utils": "workspace:*", "@sa/utils": "workspace:*",
"axios": "1.11.0", "axios": "1.10.0",
"axios-retry": "4.5.0", "axios-retry": "4.5.0",
"qs": "6.14.0" "qs": "6.14.0"
}, },

View File

@ -14,14 +14,14 @@
}, },
"devDependencies": { "devDependencies": {
"@soybeanjs/changelog": "0.3.24", "@soybeanjs/changelog": "0.3.24",
"bumpp": "10.2.3", "bumpp": "10.2.0",
"c12": "3.2.0", "c12": "3.0.4",
"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.3", "npm-check-updates": "18.0.1",
"rimraf": "6.0.1" "rimraf": "6.0.1"
} }
} }

2652
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

BIN
public/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

3
src-tauri/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
# Generated by Cargo
# will have compiled files and executables
/target/

4580
src-tauri/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

26
src-tauri/Cargo.toml Normal file
View File

@ -0,0 +1,26 @@
[package]
name = "app"
version = "0.1.0"
description = "A Tauri App"
authors = ["you"]
license = ""
repository = ""
default-run = "app"
edition = "2021"
rust-version = "1.60"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
tauri-build = { version = "2", features = [] }
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
tauri = { version = "2", features = [] }
[features]
# this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled.
# If you use cargo directly instead of tauri's cli you can use this feature flag to switch between tauri's `dev` and `build` modes.
# DO NOT REMOVE!!
custom-protocol = [ "tauri/custom-protocol" ]

3
src-tauri/build.rs Normal file
View File

@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}

View File

@ -0,0 +1,7 @@
{
"identifier": "migrated",
"description": "permissions that were migrated from v1",
"local": true,
"windows": ["main"],
"permissions": ["core:default"]
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,9 @@
{
"migrated": {
"identifier": "migrated",
"description": "permissions that were migrated from v1",
"local": true,
"windows": ["main"],
"permissions": ["core:default"]
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

BIN
src-tauri/icons/128x128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
src-tauri/icons/32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
src-tauri/icons/64x64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
src-tauri/icons/icon.icns Normal file

Binary file not shown.

BIN
src-tauri/icons/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

BIN
src-tauri/icons/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

8
src-tauri/src/main.rs Normal file
View File

@ -0,0 +1,8 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
tauri::Builder::default()
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

57
src-tauri/tauri.conf.json Normal file
View File

@ -0,0 +1,57 @@
{
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"build": {
"beforeBuildCommand": "npm run build",
"beforeDevCommand": "npm run dev",
"frontendDist": "../dist",
"devUrl": "http://localhost:9527"
},
"bundle": {
"active": true,
"category": "DeveloperTool",
"copyright": "RuoYi-Plus-Soybean",
"targets": "all",
"externalBin": [],
"icon": ["icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico"],
"windows": {
"certificateThumbprint": null,
"digestAlgorithm": "sha256",
"timestampUrl": ""
},
"longDescription": "",
"macOS": {
"entitlements": null,
"exceptionDomain": "",
"frameworks": [],
"providerShortName": null,
"signingIdentity": null
},
"resources": [],
"shortDescription": "",
"linux": {
"deb": {
"depends": []
}
}
},
"productName": "RuoYi-Plus-Soybean",
"mainBinaryName": "RuoYi-Plus-Soybean",
"version": "1.0.0",
"identifier": "org.dromara.admin",
"plugins": {},
"app": {
"windows": [
{
"fullscreen": false,
"height": 768,
"resizable": true,
"title": "RuoYi-Plus-Soybean",
"width": 1366,
"useHttpsScheme": true
}
],
"security": {
"csp": null
}
}
}

View File

@ -1,7 +1,6 @@
<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';
@ -29,7 +28,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 = jsonClone(props.dictData); const dictData = 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);
} }

View File

@ -2,7 +2,6 @@
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);
@ -11,19 +10,15 @@ 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 '';
@ -38,9 +33,9 @@ const jsonData = computed<string>(() => {
</script> </script>
<template> <template>
<NScrollbar :class="twMerge(DEFAULT_CLASS, props.class)"> <div class="json-preview">
<NCode :code="jsonData" :hljs="hljs" language="json" :show-line-numbers="showLineNumbers" :word-wrap="true" /> <NCode :code="jsonData" :hljs="hljs" language="json" :show-line-numbers="showLineNumbers" />
</NScrollbar> </div>
</template> </template>
<style lang="scss"> <style lang="scss">
@ -49,4 +44,18 @@ 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>

View File

@ -1,524 +1,61 @@
<!-- 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 { useThemeStore } from '@/store/modules/theme'; import { computed } from 'vue';
import { getPaletteColorByNumber } from '@sa/color';
defineOptions({ name: 'WaveBg' }); defineOptions({ name: 'WaveBg' });
const themeStore = useThemeStore(); interface Props {
/** Theme color */
function toggleThemeScheme() { themeColor: string;
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="wave-bg"> <div class="absolute-lt z-1 size-full overflow-hidden">
<!-- 几何装饰元素 --> <div class="absolute -right-300px -top-900px lt-sm:(-right-100px -top-1170px)">
<div class="geometric-decorations"> <svg height="1337" width="1337">
<!-- 基础几何形状 --> <defs>
<div class="geo-element circle-outline animate-fade-in-up animate-delay-0s"></div> <path
<div class="geo-element square-rotated animate-fade-in-left animate-delay-0s"></div> id="path-1"
<div class="geo-element circle-small animate-fade-in-up animate-delay-0.3s"></div> opacity="1"
fill-rule="evenodd"
<div class="geo-element square-bottom-right animate-fade-in-right animate-delay-0s"></div> 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"
/>
<!-- 背景泡泡 --> <linearGradient id="linearGradient-2" x1="0.79" y1="0.62" x2="0.21" y2="0.86">
<div class="geo-element bg-bubble animate-scale-in animate-delay-0.5s"></div> <stop offset="0" :stop-color="lightColor" stop-opacity="1" />
<stop offset="1" :stop-color="darkColor" stop-opacity="1" />
<!-- 太阳/月亮 --> </linearGradient>
<div </defs>
class="geo-element circle-top-right animate-fade-in-down animate-delay-0.5s" <g opacity="1">
@click="toggleThemeScheme" <use xlink:href="#path-1" fill="url(#linearGradient-2)" fill-opacity="1" />
></div> </g>
</svg>
<!-- 装饰点 --> </div>
<div class="geo-element dot dot-top-left animate-bounce-in animate-delay-0s"></div> <div class="absolute -bottom-400px -left-200px lt-sm:(-bottom-760px -left-100px)">
<div class="geo-element dot dot-top-right animate-bounce-in animate-delay-0s"></div> <svg height="896" width="967.8852157128662">
<div class="geo-element dot dot-center-right animate-bounce-in animate-delay-0s"></div> <defs>
<path
<!-- 叠加方块组 --> id="path-2"
<div class="squares-group"> opacity="1"
<i class="geo-element square square-blue animate-fade-in-left-rotated-blue animate-delay-0.2s"></i> fill-rule="evenodd"
<i class="geo-element square square-pink animate-fade-in-left-rotated-pink animate-delay-0.4s"></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-purple animate-fade-in-left-no-rotation animate-delay-0.6s"></i> />
</div> <linearGradient id="linearGradient-3" x1="0.5" y1="0" x2="0.5" y2="1">
<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 lang="scss" scoped> <style scoped></style>
// 颜色变量定义
$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>

View File

@ -4,6 +4,5 @@ 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'
} }

View File

@ -113,10 +113,6 @@ 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');

View File

@ -33,7 +33,7 @@ const toGitee = () => {
</NBadge> </NBadge>
</NButton> </NButton>
</template> </template>
{{ $t('page.home.message') }} 消息
</NTooltip> </NTooltip>
</template> </template>
<NCard <NCard

View File

@ -328,8 +328,6 @@ 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',

View File

@ -328,8 +328,6 @@ const local: App.I18n.Schema = {
page: { page: {
login: { login: {
common: { common: {
title: '现代化的企业级多租户管理系统',
subTitle: '为开发者提供了完整的企业管理解决方案',
loginOrRegister: '登录 / 注册', loginOrRegister: '登录 / 注册',
register: '注册', register: '注册',
userNamePlaceholder: '请输入用户名', userNamePlaceholder: '请输入用户名',

View File

@ -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 = import.meta.env.VITE_HEADER_FLAG || 'encrypt-key'; const encryptHeader = '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,14 +48,6 @@ 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) {
@ -133,6 +125,23 @@ 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;

View File

@ -8,7 +8,6 @@ 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, () => {
@ -16,7 +15,6 @@ 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();
@ -50,7 +48,6 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
await toLogin(); await toLogin();
} }
noticeStore.clearNotice();
tabStore.cacheTabs(); tabStore.cacheTabs();
routeStore.resetStore(); routeStore.resetStore();
} }

View File

@ -1,9 +1,8 @@
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(SetupStoreId.Dict, () => { export const useDictStore = defineStore('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) => {

View File

@ -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 && !route.meta.activeMenu) { if (route.meta.hideInMenu && parent) {
// @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;
} }

View File

@ -6,7 +6,6 @@ html,
body, body,
#app { #app {
height: 100%; height: 100%;
font-family: inherit;
} }
html { html {

View File

@ -44,7 +44,7 @@ export const themeSettings: App.Theme.ThemeSetting = {
fixedHeaderAndTab: true, fixedHeaderAndTab: true,
sider: { sider: {
inverted: false, inverted: false,
width: 230, width: 220,
collapsedWidth: 64, collapsedWidth: 64,
mixWidth: 90, mixWidth: 90,
mixCollapsedWidth: 64, mixCollapsedWidth: 64,

View File

@ -487,8 +487,6 @@ declare namespace App {
}; };
login: { login: {
common: { common: {
title: string;
subTitle: string;
loginOrRegister: string; loginOrRegister: string;
register: string; register: string;
userNamePlaceholder: string; userNamePlaceholder: string;

View File

@ -114,7 +114,6 @@ 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;

View File

@ -1,71 +0,0 @@
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);
}

View File

@ -1,7 +1,6 @@
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';
// 初始化 // 初始化
@ -35,14 +34,9 @@ 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, content: data.value,
type: 'success', type: 'success',
duration: 3000 duration: 3000
}); });

View File

@ -39,67 +39,46 @@ const activeModule = computed(() => moduleMap[props.module || 'pwd-login']);
</script> </script>
<template> <template>
<div class="scroll box-border size-full flex"> <div class="relative min-h-screen w-full flex flex-wrap">
<div class="relative box-border hidden h-full w-65vw overflow-hidden bg-primary-50 xl:block dark:bg-primary-900"> <div class="hidden min-h-screen w-50% bg-primary-100 lg:block dark:bg-primary-800">
<div class="relative z-100 flex items-center pl-30px pt-30px"> <div class="size-full flex-center">
<SystemLogo class="text-32px text-primary" /> <img class="w-60% sm:w-80%" :src="loginBackground" />
<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="relative h-full flex-1 xl:m-auto sm:!w-full"> <div class="w-full flex-col-center px-24px py-32px lg:w-50%">
<header class="flex-y-center justify-between px-30px pt-30px xl:justify-end"> <div class="mx-auto max-w-464px w-full">
<div class="relative z-100 block flex items-center xl:hidden"> <header class="flex-y-center justify-between">
<SystemLogo class="text-32px text-primary" /> <div class="flex-y-center gap-16px">
<h3 class="ml-10px text-20px font-400">{{ $t('system.title') }}</h3> <SystemLogo class="text-30px text-primary sm:text-42px" />
</div> <h3 class="text-24px text-primary font-500 sm:text-32px">{{ $t('system.title') }}</h3>
<div class="flex items-center justify-end"> </div>
<ThemeSchemaSwitch <div class="flex-y-center">
:theme-schema="themeStore.themeScheme" <ThemeSchemaSwitch
:show-tooltip="false" :theme-schema="themeStore.themeScheme"
class="text-20px lt-sm:text-18px" :show-tooltip="false"
@switch="themeStore.toggleThemeScheme" class="text-20px lt-sm:text-18px"
/> @switch="themeStore.toggleThemeScheme"
<LangSwitch />
v-if="themeStore.header.multilingual.visible" <LangSwitch
:lang="appStore.locale" v-if="themeStore.header.multilingual.visible"
:lang-options="appStore.localeOptions" :lang="appStore.locale"
:show-tooltip="false" :lang-options="appStore.localeOptions"
class="text-20px lt-sm:text-18px" :show-tooltip="false"
@change-lang="appStore.changeLocale" class="text-20px lt-sm:text-18px"
/> @change-lang="appStore.changeLocale"
</div> />
</header> </div>
<main class="mt-10% max-w-450px w-full rounded-5px bg-cover px-24px xl:absolute xl:m-auto"> </header>
<Transition :name="themeStore.page.animateMode" mode="out-in" appear> <main class="pt-24px">
<component :is="activeModule.component" /> <div>
</Transition> <Transition :name="themeStore.page.animateMode" mode="out-in" appear>
</main> <component :is="activeModule.component" />
</Transition>
</div>
</main>
</div>
</div> </div>
</div> </div>
</template> </template>
<style scoped> <style scoped></style>
.scroll {
overflow: auto;
}
.scroll::-webkit-scrollbar {
display: none;
}
.scroll {
-ms-overflow-style: none;
}
.scroll {
scrollbar-width: none;
}
</style>

View File

@ -40,10 +40,6 @@ 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')" />
@ -56,32 +52,15 @@ async function handleSubmit() {
</NButton> </NButton>
</div> </div>
</NFormItem> </NFormItem>
<NSpace vertical :size="20" class="w-full"> <NSpace vertical :size="18" class="w-full">
<NButton type="primary" size="large" block @click="handleSubmit"> <NButton type="primary" size="large" round block @click="handleSubmit">
{{ $t('page.login.codeLogin.title') }} {{ $t('common.confirm') }}
</NButton> </NButton>
<NButton size="large" block @click="toggleLoginModule('pwd-login')"> <NButton size="large" round 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 scoped></style>
: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>

View File

@ -124,8 +124,8 @@ async function handleSocialLogin(type: Api.System.SocialSource) {
<template> <template>
<div> <div>
<div class="mb-5px text-32px text-black font-600 dark:text-white">登录到您的账户</div> <div class="mb-12px text-24px text-black font-500 sm:text-30px dark:text-white">登录到您的账户</div>
<div class="pb-18px text-16px text-#858585">欢迎回来请输入您的账户信息</div> <div class="pb-24px text-18px 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-42px"> <NSpin :show="codeLoading" :size="28" class="h-52px">
<NButton :focusable="false" class="login-code h-42px w-136px" @click="handleFetchCaptchaCode"> <NButton :focusable="false" class="login-code h-52px 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="12" class="mb-8px"> <NSpace vertical :size="16" class="mb-8px">
<div class="mx-6px mb-8px flex-y-center justify-between"> <div class="mx-6px mb-10px 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-24px w-full text-center text-18px text-#858585"> <div class="mt-32px 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: 42px; height: 52px;
} }
} }
:deep(.n-base-selection), :deep(.n-base-selection),
:deep(.n-input) { :deep(.n-input) {
--n-height: 42px !important; --n-height: 52px !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: 42px !important; --n-height: 52px !important;
--n-font-size: 18px !important; --n-font-size: 18px !important;
--n-border-radius: 8px !important; --n-border-radius: 8px !important;
} }

View File

@ -104,8 +104,8 @@ handleFetchCaptchaCode();
<template> <template>
<div> <div>
<div class="mb-5px text-32px text-black font-600 sm:text-30px dark:text-white">注册新账户</div> <div class="mb-12px text-24px text-black font-500 sm:text-30px dark:text-white">注册新账户</div>
<div class="pb-18px text-16px text-#858585">欢迎注册请输入您的账户信息</div> <div class="pb-24px text-18px 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"> <NSpace vertical :size="18" class="w-full pt-6px">
<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-24px w-full text-center text-18px text-#858585"> <div class="mt-32px 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: 42px !important; --n-height: 52px !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: 42px !important; --n-height: 52px !important;
--n-font-size: 18px !important; --n-font-size: 18px !important;
--n-border-radius: 8px !important; --n-border-radius: 8px !important;
} }

View File

@ -46,10 +46,10 @@ async function handleSubmit() {
<template> <template>
<div> <div>
<div class="mb-5px text-32px text-black font-600 sm:text-30px dark:text-white"> <div class="mb-12px text-24px text-black font-500 sm:text-30px dark:text-white">
{{ $t('page.login.resetPwd.title') }} {{ $t('page.login.resetPwd.title') }}
</div> </div>
<div class="pb-18px text-16px text-#858585">请输入您的手机号我们将发送验证码到您的手机</div> <div class="pb-24px text-18px 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="20" class="w-full"> <NSpace vertical :size="18" 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: 42px !important; --n-height: 52px !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: 42px !important; --n-height: 52px !important;
--n-font-size: 18px !important; --n-font-size: 18px !important;
--n-border-radius: 8px !important; --n-border-radius: 8px !important;
} }

View File

@ -40,7 +40,6 @@ 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> = {
@ -56,7 +55,7 @@ const model: Model = reactive(createDefaultModel());
function createDefaultModel(): Model { function createDefaultModel(): Model {
return { return {
parentId: props.rowData?.deptId || '', parentId: '',
deptName: '', deptName: '',
deptCategory: '', deptCategory: '',
orderNum: null, orderNum: null,
@ -81,6 +80,7 @@ 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,7 +144,6 @@ 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();
} }
@ -187,15 +186,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>

View File

@ -38,8 +38,6 @@ 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();
@ -71,7 +69,7 @@ function createDefaultModel(): Model {
visible: '0', visible: '0',
status: '0', status: '0',
perms: '', perms: '',
icon: defaultIcon, icon: undefined,
remark: '' remark: ''
}; };
} }
@ -120,7 +118,6 @@ 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) {
@ -211,7 +208,7 @@ async function handleSubmit() {
visible: menuVisible, visible: menuVisible,
status, status,
perms, perms,
icon: icon || defaultIcon, icon,
component: processComponent(component), component: processComponent(component),
remark remark
}; };

View File

@ -323,11 +323,11 @@ function handleResetSearch() {
} }
:deep(.n-tree-node) { :deep(.n-tree-node) {
height: 30px; height: 33px;
} }
:deep(.n-tree-node-switcher) { :deep(.n-tree-node-switcher) {
height: 30px; height: 33px;
} }
:deep(.n-tree-node-switcher__icon) { :deep(.n-tree-node-switcher__icon) {

View File

@ -1,6 +1,6 @@
<script setup lang="tsx"> <script setup lang="tsx">
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import { NAvatar, NButton, NDivider, NEllipsis } from 'naive-ui'; import { NButton, NDivider } 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,7 +12,6 @@ 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';
@ -66,64 +65,41 @@ const {
key: 'index', key: 'index',
title: $t('common.index'), title: $t('common.index'),
align: 'center', align: 'center',
width: 48 width: 64
}, },
{ {
key: 'userName', key: 'userName',
title: $t('page.system.user.userName'), title: $t('page.system.user.userName'),
align: 'left', align: 'center',
width: 200, minWidth: 120,
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: 'sex', key: 'nickName',
title: $t('page.system.user.sex'), title: $t('page.system.user.nickName'),
align: 'center', align: 'center',
width: 80, minWidth: 120,
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',
width: 120, minWidth: 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',
width: 120, minWidth: 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',
width: 80, minWidth: 80,
render(row) { render(row) {
return ( return (
<StatusSwitch <StatusSwitch
@ -139,7 +115,7 @@ const {
key: 'createTime', key: 'createTime',
title: $t('page.system.user.createTime'), title: $t('page.system.user.createTime'),
align: 'center', align: 'center',
width: 120 minWidth: 120
}, },
{ {
key: 'operate', key: 'operate',
@ -365,7 +341,7 @@ function handleResetSearch() {
:data="data" :data="data"
size="small" size="small"
:flex-height="!appStore.isMobile" :flex-height="!appStore.isMobile"
:scroll-x="1200" :scroll-x="962"
:loading="loading" :loading="loading"
remote remote
:row-key="row => row.userId" :row-key="row => row.userId"
@ -415,11 +391,11 @@ function handleResetSearch() {
} }
:deep(.n-tree-node) { :deep(.n-tree-node) {
height: 30px; height: 25px;
} }
:deep(.n-tree-node-switcher) { :deep(.n-tree-node-switcher) {
height: 30px; height: 25px;
} }
:deep(.n-tree-node-switcher__icon) { :deep(.n-tree-node-switcher__icon) {

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