mirror of
https://github.com/m-xlsea/ruoyi-plus-soybean.git
synced 2025-09-23 23:39:47 +08:00
Compare commits
27 Commits
v1.1.2
...
7f2f3bd088
Author | SHA1 | Date | |
---|---|---|---|
7f2f3bd088 | |||
5ef1c5de98 | |||
90a14e338a | |||
4e625111ce | |||
8524ae7666 | |||
d6ae85d218 | |||
89719abe34 | |||
8d7f91dccf | |||
33ade53904 | |||
ab9c84d831 | |||
7651816495 | |||
4539fe01fb | |||
4e9839bd48 | |||
a15b683b1d | |||
9df8d2f55f | |||
710374398a | |||
52318c106d | |||
9027632bef | |||
b96c46baa9 | |||
2d31d7dc62 | |||
e538355f2b | |||
f89835578c | |||
4eb77eac78 | |||
adca2e26be | |||
ff576f3f42 | |||
8fcc70d73d | |||
923eb98a5c |
13
.vscode/settings.json
vendored
13
.vscode/settings.json
vendored
@ -4,7 +4,18 @@
|
||||
"source.organizeImports": "never"
|
||||
},
|
||||
"editor.formatOnSave": false,
|
||||
"eslint.validate": ["html", "css", "scss", "json", "jsonc"],
|
||||
"eslint.validate": [
|
||||
"html",
|
||||
"css",
|
||||
"scss",
|
||||
"json",
|
||||
"jsonc",
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"typescript",
|
||||
"typescriptreact",
|
||||
"vue"
|
||||
],
|
||||
"i18n-ally.displayLanguage": "zh-cn",
|
||||
"i18n-ally.enabledParsers": ["ts"],
|
||||
"i18n-ally.enabledFrameworks": ["vue"],
|
||||
|
39
CHANGELOG.md
39
CHANGELOG.md
@ -1,5 +1,44 @@
|
||||
# 更新日志
|
||||
|
||||
## [v1.1.3](https://gitee.com/xlsea/ruoyi-plus-soybean/compare/v1.1.2...v1.1.3) (2025-08-16)
|
||||
|
||||
### 🐞 Bug 修复
|
||||
|
||||
- **hooks**:
|
||||
- 非安全环境下不使用流式下载 - by @m-xlsea [<samp>(f8983)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/f8983557)
|
||||
- 修复oss下载时未转码问题 - by **AN** [<samp>(2d31d)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/2d31d7dc)
|
||||
- **project**:
|
||||
- 关闭多租户功能后仍然遍历租户列表导致控制台报错的问题 - by **wang_rui** [<samp>(b96c4)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/b96c46ba)
|
||||
- 关闭多租户功能后仍然遍历租户列表导致控制台报错的问题 Merge pull request !25 from littleghost2016/dev - by **不寻俗** [<samp>(90276)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/9027632b)
|
||||
- **projects**:
|
||||
- 修复一级菜单隐藏失效问题 - by **AN** [<samp>(8fcc7)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/8fcc70d7)
|
||||
- 修复日期搜索条件清除问题 - by **AN** [<samp>(52318)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/52318c10)
|
||||
- 修复登录过期事件监听未被重置 - by @m-xlsea [<samp>(71037)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/71037439)
|
||||
- 修复用户新增时角色下拉包含超级管理员问题 - by **AN** [<samp>(a15b6)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/a15b683b)
|
||||
- 修复用户导入功能无法更新问题 - by **AN** [<samp>(4e983)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/4e9839bd)
|
||||
- Fix the icon size in the image preview toolbar - by @m-xlsea [<samp>(4539f)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/4539fe01)
|
||||
- 修复新增用户未查询角色列表问题 - by **AN** [<samp>(d6ae8)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/d6ae85d2)
|
||||
- **readme**:
|
||||
- update GitHub stars and forks links for gitee - by @soybeanjs [<samp>(923eb)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/923eb98a)
|
||||
|
||||
### 💅 重构
|
||||
|
||||
- **menu**:
|
||||
- 菜单管理中隐藏的菜单显示灰色 - by **NicholasLD** [<samp>(adca2)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/adca2e26)
|
||||
- 菜单管理中隐藏的菜单显示灰色 Merge pull request !24 from NicholasLD/N/A - by **不寻俗** [<samp>(4eb77)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/4eb77eac)
|
||||
- **projects**:
|
||||
- 菜单列表新增禁用菜单样式 - by @m-xlsea [<samp>(e5383)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/e538355f)
|
||||
|
||||
### 🏡 杂项
|
||||
|
||||
- **other**: update the ESLint validation configuration to support more file types. - by **Azir-11** [<samp>(8d7f9)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/8d7f91dc)
|
||||
- **readme**: remove DartNode sponsorship badge from README files - by @soybeanjs [<samp>(33ade)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/33ade539)
|
||||
|
||||
### ❤️ 贡献者
|
||||
|
||||
[](https://github.com/soybeanjs) [](https://github.com/m-xlsea) [](https://gitee.com/elio-an) [](https://github.com/Azir-11) [](https://github.com/NicholasLD)
|
||||
[wang_rui](mailto:wrr1996@163.com)
|
||||
|
||||
## [v1.1.2](https://gitee.com/xlsea/ruoyi-plus-soybean/compare/v1.1.1...v1.1.2) (2025-07-24)
|
||||
|
||||
### 🐞 Bug 修复
|
||||
|
@ -22,7 +22,7 @@
|
||||
|
||||
# 📢 重要通知
|
||||
|
||||
1.1.2 版本已经正式发布,工作流版本迎来首个版本(请切换 [flow](https://gitee.com/xlsea/ruoyi-plus-soybean/tree/flow/) 分支查看),但仍然建议:
|
||||
1.1.3 版本已经正式发布(工作流版本请切换 [flow](https://gitee.com/xlsea/ruoyi-plus-soybean/tree/flow/) 分支查看),但仍然建议:
|
||||
- 在生产环境使用前进行充分测试
|
||||
- 关注项目更新,及时获取最新版本
|
||||
- 积极反馈问题,帮助我们快速迭代
|
||||
@ -40,6 +40,11 @@
|
||||
|
||||
<p style="font-weight: bold; font-size: 24px;">后端需要替换代码生成模板与菜单 SQL,详细请看 <a href="#代码生成与菜单更新">代码生成与菜单更新</a></p>
|
||||
|
||||
# 💎 友情链接
|
||||
|
||||
- [Snail Job Pro](https://pro.snailjob.opensnail.com/home) - 灵活,可靠和快速的分布式任务重试和分布式任务调度平台
|
||||
- [AiZuDa - 爱组搭(飞龙工作流企业版)](https://naiveui.aizuda.com) - 像搭积木一样进行低代码甚至零代码快速构建应用
|
||||
|
||||
## 📋 项目概述
|
||||
|
||||
RuoYi-Plus-Soybean 是一个现代化的企业级多租户管理系统,它结合了 RuoYi-Vue-Plus 的强大后端功能和 Soybean Admin 的现代化前端特性,为开发者提供了完整的企业管理解决方案。
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "ruoyi-vue-plus",
|
||||
"type": "module",
|
||||
"version": "1.1.2",
|
||||
"version": "1.1.3",
|
||||
"description": "结合了 RuoYi-Vue-Plus 的强大后端功能和 Soybean Admin 的现代化前端特性,为开发者提供了完整的企业管理解决方案。",
|
||||
"author": {
|
||||
"name": "xlsea",
|
||||
@ -84,7 +84,8 @@
|
||||
"vue-advanced-cropper": "^2.8.9",
|
||||
"vue-draggable-plus": "0.6.0",
|
||||
"vue-i18n": "11.1.9",
|
||||
"vue-router": "4.5.1"
|
||||
"vue-router": "4.5.1",
|
||||
"xlsx": "0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@elegant-router/vue": "0.3.8",
|
||||
|
72
pnpm-lock.yaml
generated
72
pnpm-lock.yaml
generated
@ -92,6 +92,9 @@ importers:
|
||||
vue-router:
|
||||
specifier: 4.5.1
|
||||
version: 4.5.1(vue@3.5.17(typescript@5.8.3))
|
||||
xlsx:
|
||||
specifier: 0.18.5
|
||||
version: 0.18.5
|
||||
devDependencies:
|
||||
'@elegant-router/vue':
|
||||
specifier: 0.3.8
|
||||
@ -1547,6 +1550,10 @@ packages:
|
||||
engines: {node: '>=0.4.0'}
|
||||
hasBin: true
|
||||
|
||||
adler-32@1.3.1:
|
||||
resolution: {integrity: sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==}
|
||||
engines: {node: '>=0.8'}
|
||||
|
||||
ajv@6.12.6:
|
||||
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
|
||||
|
||||
@ -1752,6 +1759,10 @@ packages:
|
||||
caniuse-lite@1.0.30001727:
|
||||
resolution: {integrity: sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==}
|
||||
|
||||
cfb@1.2.2:
|
||||
resolution: {integrity: sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==}
|
||||
engines: {node: '>=0.8'}
|
||||
|
||||
chalk@1.1.3:
|
||||
resolution: {integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@ -1801,6 +1812,10 @@ packages:
|
||||
resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==}
|
||||
engines: {node: '>=0.8'}
|
||||
|
||||
codepage@1.15.0:
|
||||
resolution: {integrity: sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==}
|
||||
engines: {node: '>=0.8'}
|
||||
|
||||
collection-visit@1.0.0:
|
||||
resolution: {integrity: sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@ -1871,6 +1886,11 @@ packages:
|
||||
resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
||||
crc-32@1.2.2:
|
||||
resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==}
|
||||
engines: {node: '>=0.8'}
|
||||
hasBin: true
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||
engines: {node: '>= 8'}
|
||||
@ -2393,6 +2413,10 @@ packages:
|
||||
resolution: {integrity: sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
frac@1.1.2:
|
||||
resolution: {integrity: sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==}
|
||||
engines: {node: '>=0.8'}
|
||||
|
||||
fragment-cache@0.2.1:
|
||||
resolution: {integrity: sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@ -3652,6 +3676,10 @@ packages:
|
||||
resolution: {integrity: sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
ssf@0.11.2:
|
||||
resolution: {integrity: sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==}
|
||||
engines: {node: '>=0.8'}
|
||||
|
||||
stable-hash-x@0.2.0:
|
||||
resolution: {integrity: sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
@ -4168,10 +4196,18 @@ packages:
|
||||
engines: {node: '>= 8'}
|
||||
hasBin: true
|
||||
|
||||
wmf@1.0.2:
|
||||
resolution: {integrity: sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==}
|
||||
engines: {node: '>=0.8'}
|
||||
|
||||
word-wrap@1.2.5:
|
||||
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
word@0.3.0:
|
||||
resolution: {integrity: sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==}
|
||||
engines: {node: '>=0.8'}
|
||||
|
||||
wrap-ansi@7.0.0:
|
||||
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
|
||||
engines: {node: '>=10'}
|
||||
@ -4184,6 +4220,11 @@ packages:
|
||||
resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
xlsx@0.18.5:
|
||||
resolution: {integrity: sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==}
|
||||
engines: {node: '>=0.8'}
|
||||
hasBin: true
|
||||
|
||||
xml-name-validator@4.0.0:
|
||||
resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==}
|
||||
engines: {node: '>=12'}
|
||||
@ -5465,6 +5506,8 @@ snapshots:
|
||||
|
||||
acorn@8.15.0: {}
|
||||
|
||||
adler-32@1.3.1: {}
|
||||
|
||||
ajv@6.12.6:
|
||||
dependencies:
|
||||
fast-deep-equal: 3.1.3
|
||||
@ -5691,6 +5734,11 @@ snapshots:
|
||||
|
||||
caniuse-lite@1.0.30001727: {}
|
||||
|
||||
cfb@1.2.2:
|
||||
dependencies:
|
||||
adler-32: 1.3.1
|
||||
crc-32: 1.2.2
|
||||
|
||||
chalk@1.1.3:
|
||||
dependencies:
|
||||
ansi-styles: 2.2.1
|
||||
@ -5757,6 +5805,8 @@ snapshots:
|
||||
|
||||
clone@2.1.2: {}
|
||||
|
||||
codepage@1.15.0: {}
|
||||
|
||||
collection-visit@1.0.0:
|
||||
dependencies:
|
||||
map-visit: 1.0.0
|
||||
@ -5811,6 +5861,8 @@ snapshots:
|
||||
object-assign: 4.1.1
|
||||
vary: 1.1.2
|
||||
|
||||
crc-32@1.2.2: {}
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
dependencies:
|
||||
path-key: 3.1.1
|
||||
@ -6469,6 +6521,8 @@ snapshots:
|
||||
hasown: 2.0.2
|
||||
mime-types: 2.1.35
|
||||
|
||||
frac@1.1.2: {}
|
||||
|
||||
fragment-cache@0.2.1:
|
||||
dependencies:
|
||||
map-cache: 0.2.2
|
||||
@ -7742,6 +7796,10 @@ snapshots:
|
||||
dependencies:
|
||||
extend-shallow: 3.0.2
|
||||
|
||||
ssf@0.11.2:
|
||||
dependencies:
|
||||
frac: 1.1.2
|
||||
|
||||
stable-hash-x@0.2.0: {}
|
||||
|
||||
stable@0.1.8: {}
|
||||
@ -8363,8 +8421,12 @@ snapshots:
|
||||
dependencies:
|
||||
isexe: 2.0.0
|
||||
|
||||
wmf@1.0.2: {}
|
||||
|
||||
word-wrap@1.2.5: {}
|
||||
|
||||
word@0.3.0: {}
|
||||
|
||||
wrap-ansi@7.0.0:
|
||||
dependencies:
|
||||
ansi-styles: 4.3.0
|
||||
@ -8381,6 +8443,16 @@ snapshots:
|
||||
dependencies:
|
||||
is-wsl: 3.1.0
|
||||
|
||||
xlsx@0.18.5:
|
||||
dependencies:
|
||||
adler-32: 1.3.1
|
||||
cfb: 1.2.2
|
||||
codepage: 1.15.0
|
||||
crc-32: 1.2.2
|
||||
ssf: 0.11.2
|
||||
wmf: 1.0.2
|
||||
word: 0.3.0
|
||||
|
||||
xml-name-validator@4.0.0: {}
|
||||
|
||||
y18n@5.0.8: {}
|
||||
|
@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, useAttrs } from 'vue';
|
||||
import type { TagProps } from 'naive-ui';
|
||||
import { jsonClone } from '@sa/utils';
|
||||
import { useDict } from '@/hooks/business/dict';
|
||||
import { isNotNull } from '@/utils/common';
|
||||
import { $t } from '@/locales';
|
||||
@ -28,7 +29,7 @@ const { transformDictData } = useDict(props.dictCode, props.immediate);
|
||||
|
||||
const dictTagData = computed<Api.System.DictData[]>(() => {
|
||||
if (props.dictData) {
|
||||
const dictData = props.dictData;
|
||||
const dictData = jsonClone(props.dictData);
|
||||
if (dictData.dictLabel?.startsWith(`dict.${dictData.dictType}.`)) {
|
||||
dictData.dictLabel = $t(dictData.dictLabel as App.I18n.I18nKey);
|
||||
}
|
||||
|
@ -21,27 +21,27 @@ const attrs: SelectProps = useAttrs();
|
||||
|
||||
const { loading: postLoading, startLoading: startPostLoading, endLoading: endPostLoading } = useLoading();
|
||||
|
||||
/** the enabled role options */
|
||||
const roleOptions = ref<CommonType.Option<CommonType.IdType>[]>([]);
|
||||
/** the enabled post options */
|
||||
const postOptions = ref<CommonType.Option<CommonType.IdType>[]>([]);
|
||||
|
||||
watch(
|
||||
() => props.deptId,
|
||||
() => {
|
||||
if (!props.deptId) {
|
||||
roleOptions.value = [];
|
||||
postOptions.value = [];
|
||||
return;
|
||||
}
|
||||
getRoleOptions();
|
||||
getPostOptions();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
async function getRoleOptions() {
|
||||
async function getPostOptions() {
|
||||
startPostLoading();
|
||||
const { error, data } = await fetchGetPostSelect(props.deptId!);
|
||||
|
||||
if (!error) {
|
||||
roleOptions.value = data.map(item => ({
|
||||
postOptions.value = data.map(item => ({
|
||||
label: item.postName,
|
||||
value: item.postId
|
||||
}));
|
||||
@ -54,7 +54,7 @@ async function getRoleOptions() {
|
||||
<NSelect
|
||||
v-model:value="value"
|
||||
:loading="postLoading"
|
||||
:options="roleOptions"
|
||||
:options="postOptions"
|
||||
v-bind="attrs"
|
||||
placeholder="请选择岗位"
|
||||
/>
|
||||
|
@ -16,6 +16,12 @@ export function useDownload() {
|
||||
const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y';
|
||||
const { baseURL } = getServiceBaseURL(import.meta.env, isHttpProxy);
|
||||
|
||||
const isHttps = () => {
|
||||
const protocol = document.location.protocol;
|
||||
const hostname = document.location.hostname;
|
||||
return protocol === 'https' || hostname === 'localhost' || hostname === '127.0.0.1';
|
||||
};
|
||||
|
||||
/** 获取通用请求头 */
|
||||
const getCommonHeaders = (contentType = 'application/octet-stream') => ({
|
||||
Authorization: `Bearer ${localStg.get('token')}`,
|
||||
@ -107,11 +113,16 @@ export function useDownload() {
|
||||
|
||||
const response = await fetch(fullUrl, requestOptions);
|
||||
|
||||
if (response.status !== 200) {
|
||||
throw new Error(errorCodeRecord.default);
|
||||
}
|
||||
|
||||
await handleResponse(response);
|
||||
|
||||
const finalFilename = filename || response.headers.get('Download-Filename') || `download-${timestamp}`;
|
||||
const rawHeader = response.headers.get('Download-Filename');
|
||||
const finalFilename = filename || (rawHeader ? decodeURIComponent(rawHeader) : null) || `download-${timestamp}`;
|
||||
|
||||
if (response.body) {
|
||||
if (response.body && isHttps()) {
|
||||
const contentLength = Number(response.headers.get('Content-Length'));
|
||||
await downloadByStream(response.body, finalFilename, contentLength);
|
||||
return;
|
||||
|
@ -77,7 +77,7 @@ export const request = createFlatRequest<App.Service.Response, RequestInstanceSt
|
||||
// when the backend response code is in `modalLogoutCodes`, it means the user will be logged out by displaying a modal
|
||||
const modalLogoutCodes = import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES?.split(',') || [];
|
||||
if (modalLogoutCodes.includes(responseCode) && isLogin) {
|
||||
const isExist = request.state.errMsgStack && request.state.errMsgStack.includes(response.data.msg);
|
||||
const isExist = request.state.errMsgStack?.includes(response.data.msg);
|
||||
if (isExist) {
|
||||
return null;
|
||||
}
|
||||
@ -85,23 +85,24 @@ export const request = createFlatRequest<App.Service.Response, RequestInstanceSt
|
||||
logoutAndCleanup();
|
||||
return null;
|
||||
}
|
||||
|
||||
request.state.errMsgStack = [...(request.state.errMsgStack || []), response.data.msg];
|
||||
// prevent the user from refreshing the page
|
||||
window.addEventListener('beforeunload', handleLogout);
|
||||
|
||||
window.$dialog?.warning({
|
||||
title: '系统提示',
|
||||
content: '登录状态已过期,您可以继续留在该页面,或者重新登录',
|
||||
content: '登录状态已过期,请重新登录',
|
||||
positiveText: '重新登录',
|
||||
negativeText: '取消',
|
||||
maskClosable: false,
|
||||
closeOnEsc: false,
|
||||
onAfterEnter() {
|
||||
// prevent the user from refreshing the page
|
||||
window.addEventListener('beforeunload', handleLogout);
|
||||
},
|
||||
onPositiveClick() {
|
||||
logoutAndCleanup();
|
||||
},
|
||||
onClose() {
|
||||
window.removeEventListener('beforeunload', handleLogout);
|
||||
request.state.errMsgStack = request.state.errMsgStack.filter(msg => msg !== response.data.msg);
|
||||
logoutAndCleanup();
|
||||
}
|
||||
});
|
||||
request.cancelAllRequest();
|
||||
|
@ -67,6 +67,8 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
|
||||
if (authRouteMode.value === 'dynamic') {
|
||||
if (route.path === '/' && route.children?.length) {
|
||||
const child = route.children[0];
|
||||
// @ts-expect-error no hidden field
|
||||
child.hidden = route.hidden;
|
||||
parseRouter(child);
|
||||
child.name = Math.random().toString(36).slice(2, 12);
|
||||
Object.assign(route, child);
|
||||
@ -123,7 +125,6 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
|
||||
} else if (!isNotNull(route.meta.icon)) {
|
||||
route.meta.icon = defaultIcon;
|
||||
}
|
||||
|
||||
// @ts-expect-error no hidden field
|
||||
route.meta.hideInMenu = route.hidden;
|
||||
if (route.meta.hideInMenu && parent) {
|
||||
|
@ -13,6 +13,13 @@
|
||||
border-color: var(--un-default-border-color, #e5e7eb); /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
* [Naive UI] Fix the icon size in the image preview toolbar
|
||||
*/
|
||||
.n-image-preview-toolbar .n-base-icon {
|
||||
box-sizing: unset !important;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Use a consistent sensible line-height in all browsers.
|
||||
2. Prevent adjustments of font size after orientation changes in iOS.
|
||||
|
2
src/typings/api/system.api.d.ts
vendored
2
src/typings/api/system.api.d.ts
vendored
@ -163,6 +163,8 @@ declare namespace Api {
|
||||
postIds: string[];
|
||||
/** user role ids */
|
||||
roleIds: string[];
|
||||
/** roles */
|
||||
roles: Role[];
|
||||
};
|
||||
|
||||
/** user list */
|
||||
|
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);
|
||||
}
|
@ -53,12 +53,14 @@ async function handleFetchTenantList() {
|
||||
const { data, error } = await fetchTenantList();
|
||||
if (error) return;
|
||||
tenantEnabled.value = data.tenantEnabled;
|
||||
tenantOption.value = data.voList.map(tenant => {
|
||||
return {
|
||||
label: tenant.companyName,
|
||||
value: tenant.tenantId
|
||||
};
|
||||
});
|
||||
if (data.tenantEnabled) {
|
||||
tenantOption.value = data.voList.map(tenant => {
|
||||
return {
|
||||
label: tenant.companyName,
|
||||
value: tenant.tenantId
|
||||
};
|
||||
});
|
||||
}
|
||||
endTenantLoading();
|
||||
}
|
||||
|
||||
|
@ -21,9 +21,12 @@ const dateRangeCreateTime = ref<[string, string] | null>(null);
|
||||
const model = defineModel<Api.System.ConfigSearchParams>('model', { required: true });
|
||||
|
||||
function onDateRangeCreateTimeUpdate(value: [string, string] | null) {
|
||||
if (value?.length) {
|
||||
model.value.params!.beginTime = value[0];
|
||||
model.value.params!.endTime = value[1];
|
||||
const params = model.value.params!;
|
||||
if (value && value.length === 2) {
|
||||
[params.beginTime, params.endTime] = value;
|
||||
} else {
|
||||
params.beginTime = undefined;
|
||||
params.endTime = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -113,6 +113,24 @@ function renderLabel({ option }: { option: TreeOption }) {
|
||||
if (label?.startsWith('route.') || label?.startsWith('menu.')) {
|
||||
label = $t(label as App.I18n.I18nKey);
|
||||
}
|
||||
// 禁用的菜单显示红色
|
||||
if (option.status === '1') {
|
||||
return (
|
||||
<div class="flex items-center gap-4px text-error-200">
|
||||
{label}
|
||||
<SvgIcon icon="ri:prohibited-line" class="text-16px" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// 隐藏的菜单显示灰色
|
||||
if (option.visible === '1') {
|
||||
return (
|
||||
<div class="flex items-center gap-4px text-gray-400">
|
||||
{label}
|
||||
<SvgIcon icon="codex:hidden" class="text-21px" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return <div>{label}</div>;
|
||||
}
|
||||
|
||||
|
@ -21,9 +21,12 @@ const dateRangeCreateTime = ref<[string, string] | null>(null);
|
||||
const model = defineModel<Api.System.OssSearchParams>('model', { required: true });
|
||||
|
||||
function onDateRangeCreateTimeUpdate(value: [string, string] | null) {
|
||||
if (value?.length) {
|
||||
model.value.params!.beginCreateTime = value[0];
|
||||
model.value.params!.endCreateTime = value[1];
|
||||
const params = model.value.params!;
|
||||
if (value && value.length === 2) {
|
||||
[params.beginTime, params.endTime] = value;
|
||||
} else {
|
||||
params.beginTime = undefined;
|
||||
params.endTime = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -168,9 +168,12 @@ const dateRangeCreateTime = ref<[string, string] | null>(null);
|
||||
const datePickerRef = ref<InstanceType<typeof NDatePicker>>();
|
||||
|
||||
function onDateRangeCreateTimeUpdate(value: [string, string] | null) {
|
||||
if (value?.length) {
|
||||
searchParams.params!.beginTime = value[0];
|
||||
searchParams.params!.endTime = value[1];
|
||||
const params = searchParams.params!;
|
||||
if (value && value.length === 2) {
|
||||
[params.beginTime, params.endTime] = value;
|
||||
} else {
|
||||
params.beginTime = undefined;
|
||||
params.endTime = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,9 +24,12 @@ const model = defineModel<Api.System.RoleSearchParams>('model', { required: true
|
||||
const { options: sysNormalDisableOptions } = useDict('sys_normal_disable', false);
|
||||
|
||||
function onDateRangeCreateTimeUpdate(value: [string, string] | null) {
|
||||
if (value?.length) {
|
||||
model.value.params!.beginTime = `${value[0]} 00:00:00`;
|
||||
model.value.params!.endTime = `${value[1]} 23:59:59`;
|
||||
const params = model.value.params!;
|
||||
if (value && value.length === 2) {
|
||||
[params.beginTime, params.endTime] = value;
|
||||
} else {
|
||||
params.beginTime = undefined;
|
||||
params.endTime = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -91,6 +91,7 @@ function handleDownloadTemplate() {
|
||||
|
||||
watch(visible, () => {
|
||||
if (visible.value) {
|
||||
data.value.updateSupport = false;
|
||||
fileList.value = [];
|
||||
success.value = false;
|
||||
message.value = '';
|
||||
@ -140,7 +141,7 @@ watch(visible, () => {
|
||||
</NUploadDragger>
|
||||
</NUpload>
|
||||
<div class="flex-center">
|
||||
<NCheckbox v-model="data.updateSupport">{{ $t('common.updateExisting') }}</NCheckbox>
|
||||
<NCheckbox v-model:checked="data.updateSupport">{{ $t('common.updateExisting') }}</NCheckbox>
|
||||
</div>
|
||||
|
||||
<NAlert v-if="message" :title="$t('common.importResult')" :type="success ? 'success' : 'error'" :bordered="false">
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, watch } from 'vue';
|
||||
import { computed, reactive, ref, watch } from 'vue';
|
||||
import { useLoading } from '@sa/hooks';
|
||||
import { fetchCreateUser, fetchGetUserInfo, fetchUpdateUser } from '@/service/api/system';
|
||||
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
|
||||
@ -49,6 +49,8 @@ type Model = Api.System.UserOperateParams;
|
||||
|
||||
const model: Model = reactive(createDefaultModel());
|
||||
|
||||
const roleOptions = ref<CommonType.Option<CommonType.IdType>[]>([]);
|
||||
|
||||
function createDefaultModel(): Model {
|
||||
return {
|
||||
deptId: null,
|
||||
@ -76,18 +78,23 @@ const rules: Record<RuleKey, App.Global.FormRule[]> = {
|
||||
roleIds: [{ ...createRequiredRule('请选择角色'), type: 'array' }]
|
||||
};
|
||||
|
||||
async function getUserInfo() {
|
||||
async function getUserInfo(id: CommonType.IdType = '') {
|
||||
startLoading();
|
||||
const { error, data } = await fetchGetUserInfo(props.rowData?.userId);
|
||||
const { error, data } = await fetchGetUserInfo(id);
|
||||
if (!error) {
|
||||
model.roleIds = data.roleIds;
|
||||
model.postIds = data.postIds;
|
||||
roleOptions.value = data.roles.map(role => ({
|
||||
label: role.roleName,
|
||||
value: role.roleId
|
||||
}));
|
||||
}
|
||||
endLoading();
|
||||
}
|
||||
|
||||
function handleUpdateModelWhenEdit() {
|
||||
if (props.operateType === 'add') {
|
||||
getUserInfo();
|
||||
Object.assign(model, createDefaultModel());
|
||||
model.deptId = props.deptId;
|
||||
return;
|
||||
@ -97,7 +104,7 @@ function handleUpdateModelWhenEdit() {
|
||||
startDeptLoading();
|
||||
Object.assign(model, props.rowData);
|
||||
model.password = '';
|
||||
getUserInfo();
|
||||
getUserInfo(props.rowData.userId);
|
||||
endDeptLoading();
|
||||
}
|
||||
}
|
||||
@ -209,7 +216,14 @@ watch(visible, () => {
|
||||
<PostSelect v-model:value="model.postIds" :dept-id="model.deptId" multiple clearable />
|
||||
</NFormItem>
|
||||
<NFormItem :label="$t('page.system.user.roleIds')" path="roleIds">
|
||||
<RoleSelect v-model:value="model.roleIds" multiple clearable />
|
||||
<NSelect
|
||||
v-model:value="model.roleIds"
|
||||
:loading="loading"
|
||||
:options="roleOptions"
|
||||
multiple
|
||||
clearable
|
||||
placeholder="请选择角色"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem :label="$t('page.system.user.status')" path="status">
|
||||
<DictRadio v-model:value="model.status" dict-code="sys_normal_disable" />
|
||||
|
@ -24,9 +24,12 @@ const datePickerRef = ref<InstanceType<typeof NDatePicker>>();
|
||||
const model = defineModel<Api.System.UserSearchParams>('model', { required: true });
|
||||
|
||||
function onDateRangeCreateTimeUpdate(value: [string, string] | null) {
|
||||
if (value?.length) {
|
||||
model.value.params!.beginTime = value[0];
|
||||
model.value.params!.endTime = value[1];
|
||||
const params = model.value.params!;
|
||||
if (value && value.length === 2) {
|
||||
[params.beginTime, params.endTime] = value;
|
||||
} else {
|
||||
params.beginTime = undefined;
|
||||
params.endTime = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user