mirror of
https://gitee.com/dapppp/ruoyi-plus-vben5.git
synced 2026-04-02 14:03:24 +08:00
Compare commits
245 Commits
1.3.1-back
...
1.4.1-back
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
656d53a059 | ||
|
|
190c8c586e | ||
|
|
5767af5269 | ||
|
|
d3f4b936fb | ||
|
|
b78bc65ce7 | ||
|
|
b1fb623113 | ||
|
|
de14908fd3 | ||
|
|
5c3972196a | ||
|
|
3230781538 | ||
|
|
e7fd0e3b6a | ||
|
|
fb64d9f87a | ||
|
|
c4e3ff14b3 | ||
|
|
04796449da | ||
|
|
6ced4a44c8 | ||
|
|
3ebf0ac7df | ||
|
|
47ae02c571 | ||
|
|
f16bfe2cd0 | ||
|
|
383756c0aa | ||
|
|
2f7d1f009d | ||
|
|
946f91f387 | ||
|
|
445e6011da | ||
|
|
afc2a3de58 | ||
|
|
cb83bca12d | ||
|
|
f7ae821dc2 | ||
|
|
b737fa940a | ||
|
|
986eacae9a | ||
|
|
ce6867994a | ||
|
|
e10ddb421c | ||
|
|
af0bb9bd66 | ||
|
|
aec123a834 | ||
|
|
c09c089265 | ||
|
|
97b8e28a2b | ||
|
|
4baa0aed8b | ||
|
|
b2d3cf10aa | ||
|
|
63d2b38fd1 | ||
|
|
78cd6677c3 | ||
|
|
c0962fec18 | ||
|
|
d38093ca7d | ||
|
|
687c33ec29 | ||
|
|
8ba7bdf2bd | ||
|
|
b015fbc9fc | ||
|
|
b69320c070 | ||
|
|
dcccc213ce | ||
|
|
c0e601c020 | ||
|
|
017ed1a9e1 | ||
|
|
598f371568 | ||
|
|
ca2aadaf4a | ||
|
|
616db1c127 | ||
|
|
08de1a6f19 | ||
|
|
006370798b | ||
|
|
831700660c | ||
|
|
a53b9382f5 | ||
|
|
703586123a | ||
|
|
0295418f79 | ||
|
|
14b0d9b50f | ||
|
|
b9aef618fe | ||
|
|
4102cc2211 | ||
|
|
ea776aa710 | ||
|
|
feb96dc8ea | ||
|
|
470fd43b49 | ||
|
|
76d106e474 | ||
|
|
78c3c9da6f | ||
|
|
8b7d717b21 | ||
|
|
081d08a7f8 | ||
|
|
0da75418d0 | ||
|
|
55f0da3085 | ||
|
|
3849800388 | ||
|
|
f913955259 | ||
|
|
8d6ef40d3e | ||
|
|
2141c93399 | ||
|
|
906502f49b | ||
|
|
96a10ca83f | ||
|
|
1f68fd31b7 | ||
|
|
f31360ba4e | ||
|
|
4eb16d6d3a | ||
|
|
9db6ade1ed | ||
|
|
2569e1da0d | ||
|
|
2de9cd2334 | ||
|
|
739e04816a | ||
|
|
d9c57dfb61 | ||
|
|
2217c96cd9 | ||
|
|
752c1ac3ed | ||
|
|
53304514b6 | ||
|
|
cf913f8b8d | ||
|
|
ad9c465622 | ||
|
|
bea7c1a094 | ||
|
|
95859e36a2 | ||
|
|
8bfc482a7f | ||
|
|
6fbf1387f5 | ||
|
|
c3edbec3f0 | ||
|
|
e5c937396d | ||
|
|
45de9b7547 | ||
|
|
6daedd1de5 | ||
|
|
c45eed90d9 | ||
|
|
845719d951 | ||
|
|
4ef974ca4e | ||
|
|
af186f878d | ||
|
|
2dc7e564b2 | ||
|
|
97894a940e | ||
|
|
48d70182b4 | ||
|
|
a1091bad46 | ||
|
|
9f9be21e2a | ||
|
|
a2bdcd6e49 | ||
|
|
a38f2de982 | ||
|
|
d039c53053 | ||
|
|
11b2b5bcc2 | ||
|
|
ebef2c91e2 | ||
|
|
0c3edb10b0 | ||
|
|
801514dbe3 | ||
|
|
2dce7718d6 | ||
|
|
8a8e090792 | ||
|
|
6fc2c4e3cc | ||
|
|
8ac97688da | ||
|
|
c89ec0088b | ||
|
|
0a076f5e6e | ||
|
|
4fd68bc083 | ||
|
|
2efacb3e5b | ||
|
|
dae46abb71 | ||
|
|
5ee2a74e2d | ||
|
|
79d89005b6 | ||
|
|
d0b8349a2d | ||
|
|
34c4ecb047 | ||
|
|
3d9dba965f | ||
|
|
96b8ae94fd | ||
|
|
024c01d350 | ||
|
|
10b8b81954 | ||
|
|
2adb8acd80 | ||
|
|
a23bc4cb5c | ||
|
|
cf17a45d8d | ||
|
|
b46ebe756e | ||
|
|
1f50c95c66 | ||
|
|
cd4706b717 | ||
|
|
769aceb55f | ||
|
|
e89cf400c0 | ||
|
|
9e67929ee7 | ||
|
|
7926865bf9 | ||
|
|
51fbfcedd2 | ||
|
|
8f71d6a5d9 | ||
|
|
90625782c0 | ||
|
|
12d0ba24e5 | ||
|
|
540f24ed43 | ||
|
|
c57d3f32b5 | ||
|
|
b52f3ba0c5 | ||
|
|
84ef207d9c | ||
|
|
e68fff58e8 | ||
|
|
63c06e02b2 | ||
|
|
bf70539221 | ||
|
|
5949c73a30 | ||
|
|
cc6c9bf7a0 | ||
|
|
6b1aab9c67 | ||
|
|
8f4d3d418d | ||
|
|
aa086a2800 | ||
|
|
3b3f8e4e44 | ||
|
|
b0763d6429 | ||
|
|
f94ca10adf | ||
|
|
4471bc7a5d | ||
|
|
5689ac60ff | ||
|
|
045bc4e5ee | ||
|
|
17a18fc9ba | ||
|
|
41152d1722 | ||
|
|
f1af9f8f6e | ||
|
|
0517a7014f | ||
|
|
3e6d608a2f | ||
|
|
5de954baa4 | ||
|
|
add1e61b6f | ||
|
|
9f978cc9b0 | ||
|
|
89dd4b8131 | ||
|
|
a10a981fab | ||
|
|
20c15f352f | ||
|
|
8aa7dabeff | ||
|
|
78c7c1589a | ||
|
|
dd833ca56b | ||
|
|
681c1dc267 | ||
|
|
4545422ee0 | ||
|
|
5f26f5662e | ||
|
|
ca94ca906f | ||
|
|
76de450c71 | ||
|
|
dd2b1ed580 | ||
|
|
baec89f896 | ||
|
|
7c7051a11e | ||
|
|
aa27a2f7a1 | ||
|
|
9ee6d06d50 | ||
|
|
0cc1cb5a7b | ||
|
|
e662681ce2 | ||
|
|
0a9fc4e02d | ||
|
|
be840460d8 | ||
|
|
cb45987fe2 | ||
|
|
5ffd7db8e0 | ||
|
|
14377705e7 | ||
|
|
23503778d4 | ||
|
|
f54fab0bae | ||
|
|
b985ff0584 | ||
|
|
eff2f2a0b1 | ||
|
|
664fa800cd | ||
|
|
5dc4448c01 | ||
|
|
ccfe992779 | ||
|
|
583504495d | ||
|
|
7fb4bf3431 | ||
|
|
b148b8ec92 | ||
|
|
79de6bcbf7 | ||
|
|
14bd6dd25d | ||
|
|
9b577261e2 | ||
|
|
7f269e0d69 | ||
|
|
4baec83db5 | ||
|
|
7d8416890b | ||
|
|
2e2ffcd59e | ||
|
|
2046bfa846 | ||
|
|
0446adf778 | ||
|
|
f7a4d13a4c | ||
|
|
e587256425 | ||
|
|
0936861da1 | ||
|
|
3318d76bab | ||
|
|
8f3881eabf | ||
|
|
5252480b09 | ||
|
|
f096dfc6e6 | ||
|
|
d18f56177c | ||
|
|
333998b518 | ||
|
|
3fb4fba1cb | ||
|
|
c7e6210c8d | ||
|
|
d864085c13 | ||
|
|
fcdc1a1602 | ||
|
|
bf7496f0d5 | ||
|
|
9700150653 | ||
|
|
f0e9e55af2 | ||
|
|
ff88274554 | ||
|
|
afce9dc5c0 | ||
|
|
b5700bd0b1 | ||
|
|
e085083e42 | ||
|
|
a47910f650 | ||
|
|
a8c4786311 | ||
|
|
2971ccc0b7 | ||
|
|
4ead56eaf1 | ||
|
|
4fad8d77de | ||
|
|
9db1087d32 | ||
|
|
4a2c7b313f | ||
|
|
0f5fc5f54c | ||
|
|
76108e7b8f | ||
|
|
6018817906 | ||
|
|
7e4bdf7bd6 | ||
|
|
32117574f6 | ||
|
|
a48dfa1de2 | ||
|
|
36bf6fc149 | ||
|
|
f46ec30995 | ||
|
|
9bd5a190c2 | ||
|
|
86da3cedc2 |
@@ -2,5 +2,5 @@ ports:
|
||||
- port: 5555
|
||||
onOpen: open-preview
|
||||
tasks:
|
||||
- init: corepack enable && pnpm install
|
||||
- init: npm i -g corepack && pnpm install
|
||||
command: pnpm run dev:play
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
echo Start running commit-msg hook...
|
||||
|
||||
# Check whether the git commit information is standardized
|
||||
pnpm exec commitlint --edit "$1"
|
||||
|
||||
echo Run commit-msg hook done.
|
||||
@@ -1,3 +0,0 @@
|
||||
# 每次 git pull 之后, 安装依赖
|
||||
|
||||
pnpm install
|
||||
@@ -1,7 +0,0 @@
|
||||
# update `.vscode/vben-admin.code-workspace` file
|
||||
pnpm vsh code-workspace --auto-commit
|
||||
|
||||
# Format and submit code according to lintstagedrc.js configuration
|
||||
pnpm exec lint-staged
|
||||
|
||||
echo Run pre-commit hook done.
|
||||
@@ -1,20 +0,0 @@
|
||||
export default {
|
||||
'*.md': ['prettier --cache --ignore-unknown --write'],
|
||||
'*.vue': [
|
||||
'prettier --write',
|
||||
'eslint --cache --fix',
|
||||
'stylelint --fix --allow-empty-input',
|
||||
],
|
||||
'*.{js,jsx,ts,tsx}': [
|
||||
'prettier --cache --ignore-unknown --write',
|
||||
'eslint --cache --fix',
|
||||
],
|
||||
'*.{scss,less,styl,html,vue,css}': [
|
||||
'prettier --cache --ignore-unknown --write',
|
||||
'stylelint --fix --allow-empty-input',
|
||||
],
|
||||
'package.json': ['prettier --cache --write'],
|
||||
'{!(package)*.json,*.code-snippets,.!(browserslist)*rc}': [
|
||||
'prettier --cache --write--parser json',
|
||||
],
|
||||
};
|
||||
@@ -1 +1 @@
|
||||
20.14.0
|
||||
22.1.0
|
||||
|
||||
2
.npmrc
2
.npmrc
@@ -1,5 +1,5 @@
|
||||
registry = "https://registry.npmmirror.com"
|
||||
public-hoist-pattern[]=husky
|
||||
public-hoist-pattern[]=lefthook
|
||||
public-hoist-pattern[]=eslint
|
||||
public-hoist-pattern[]=prettier
|
||||
public-hoist-pattern[]=prettier-plugin-tailwindcss
|
||||
|
||||
9
.vscode/settings.json
vendored
9
.vscode/settings.json
vendored
@@ -14,7 +14,7 @@
|
||||
"editor.tabSize": 2,
|
||||
"editor.detectIndentation": false,
|
||||
"editor.cursorBlinking": "expand",
|
||||
"editor.largeFileOptimizations": false,
|
||||
"editor.largeFileOptimizations": true,
|
||||
"editor.accessibilitySupport": "off",
|
||||
"editor.cursorSmoothCaretAnimation": "on",
|
||||
"editor.guides.bracketPairs": "active",
|
||||
@@ -91,6 +91,7 @@
|
||||
"**/bower_components": true,
|
||||
"**/.turbo": true,
|
||||
"**/.idea": true,
|
||||
"**/.vitepress": true,
|
||||
"**/tmp": true,
|
||||
"**/.git": true,
|
||||
"**/.svn": true,
|
||||
@@ -113,6 +114,8 @@
|
||||
"**/yarn.lock": true
|
||||
},
|
||||
|
||||
"typescript.tsserver.exclude": ["**/node_modules", "**/dist", "**/.turbo"],
|
||||
|
||||
// search
|
||||
"search.searchEditor.singleClickBehaviour": "peekDefinition",
|
||||
"search.followSymlinks": false,
|
||||
@@ -217,7 +220,7 @@
|
||||
"*.env": "$(capture).env.*",
|
||||
"README.md": "README*,CHANGELOG*,LICENSE,CNAME",
|
||||
"package.json": "pnpm-lock.yaml,pnpm-workspace.yaml,.gitattributes,.gitignore,.gitpod.yml,.npmrc,.browserslistrc,.node-version,.git*,.tazerc.json",
|
||||
"eslint.config.mjs": ".eslintignore,.prettierignore,.stylelintignore,.commitlintrc.*,.prettierrc.*,stylelint.config.*,.lintstagedrc.mjs,cspell.json",
|
||||
"eslint.config.mjs": ".eslintignore,.prettierignore,.stylelintignore,.commitlintrc.*,.prettierrc.*,stylelint.config.*,.lintstagedrc.mjs,cspell.json,lefthook.yml",
|
||||
"tailwind.config.mjs": "postcss.*"
|
||||
},
|
||||
"commentTranslate.hover.enabled": false,
|
||||
@@ -232,12 +235,14 @@
|
||||
"cSpell.words": [
|
||||
"archiver",
|
||||
"axios",
|
||||
"Cascader",
|
||||
"dotenv",
|
||||
"isequal",
|
||||
"jspm",
|
||||
"napi",
|
||||
"nolebase",
|
||||
"rollup",
|
||||
"tinymce",
|
||||
"vitest"
|
||||
]
|
||||
}
|
||||
|
||||
77
CHANGELOG.md
77
CHANGELOG.md
@@ -1,3 +1,80 @@
|
||||
# 1.4.1
|
||||
|
||||
**FEATURES**
|
||||
|
||||
- Tinymce添加在antd原生表单/useVbenForm下的校验样式
|
||||
- useVbenForm 增加 Cascader(级联选择器) 组件
|
||||
|
||||
**BUG FIX**
|
||||
|
||||
- 菜单管理 路由地址的必填项不生效
|
||||
- withDefaultPlaceholder中placeholder 在keepalive & 语言切换 & tab切换 显示不变的问题
|
||||
|
||||
**REFACTOR**
|
||||
|
||||
- 字典接口抛出异常(为什么会抛出异常?)无限调用接口 兼容处理
|
||||
- 代码生成 字典下拉加载 改为每次进入编辑页面都加载
|
||||
- ~~个人中心 账号绑定 样式/逻辑重构~~(回滚了 既要又要的问题)
|
||||
- ~~个人中心 下拉卡片 昵称超长省略显示~~(回滚了 既要又要的问题)
|
||||
|
||||
# 1.4.0
|
||||
|
||||
**FEATURES**
|
||||
|
||||
- 菜单管理(通用方法) 保存表格滚动/展开状态并执行回调 用于树表在执行 新增/编辑/删除等操作后 依然在当前位置(体验优化)
|
||||
-
|
||||
- 菜单管理 级联删除 删除菜单和children
|
||||
|
||||
**REFACTOR**
|
||||
|
||||
- 除个人中心外所有本地路由改为从后端返回(需要执行更新sql)
|
||||
- 流程图预览改为logicflow预览而非图片 ...然后后端又更新了 又改成iframe了
|
||||
- 菜单管理 新增角色校验(与后端权限保持一致) 只有superadmin可进行增删改
|
||||
|
||||
# 1.3.6
|
||||
|
||||
**BUG FIX**
|
||||
|
||||
- oss配置switch切换 导致报错`存储类型找不到`
|
||||
- 文件上传无法正确清除(innerList)
|
||||
|
||||
# 1.3.5
|
||||
|
||||
**BUG FIX**
|
||||
|
||||
- 某些带Vxe表格弹窗 关闭后没有正常清理表格数据的问题
|
||||
|
||||
# 1.3.4
|
||||
|
||||
**BUG FIX**
|
||||
|
||||
- 文件上传多次触发导致数据不一致 https://gitee.com/dapppp/ruoyi-plus-vben5/issues/IC3BK6
|
||||
|
||||
**PREFORMANCE**
|
||||
|
||||
- 浏览器返回按钮/手势操作时 弹窗不会被关闭(keepAlive导致)
|
||||
|
||||
# 1.3.3
|
||||
|
||||
**BUG FIX**
|
||||
|
||||
- 工作流list展示在开启缩放会有误差导致触底逻辑不会触发
|
||||
|
||||
**OTHER**
|
||||
|
||||
- 代码生成预览对模板的提示...(下载都懒得点一下吗)
|
||||
|
||||
# 1.3.2
|
||||
|
||||
**REFACTOR**
|
||||
|
||||
- 所有表格操作列宽度调整为'auto', 这样会根据子元素宽度适配(比如没有分配权限的情况)
|
||||
- 菜单图标更新了一部分 sql同步更新
|
||||
|
||||
**OTHER**
|
||||
|
||||
- 暂时锁死vite依赖 i18n会报错
|
||||
|
||||
# 1.3.1
|
||||
|
||||
**REFACTOR**
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
<div align="center"> <a href="https://github.com/anncwb/vue-vben-admin"> <img alt="VbenAdmin Logo" width="215" src="https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp"> </a> <br> <br>
|
||||
<div align="center">
|
||||
<a href="https://github.com/anncwb/vue-vben-admin">
|
||||
<img alt="VbenAdmin Logo" width="215" src="https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp">
|
||||
</a>
|
||||
<br>
|
||||
<br>
|
||||
|
||||
[](LICENSE)
|
||||
|
||||
<h1>Vue Vben Admin</h1>
|
||||
<h1>Vue Vben Admin</h1>
|
||||
</div>
|
||||
|
||||
[](https://sonarcloud.io/summary/new_code?id=vbenjs_vue-vben-admin)    
|
||||
@@ -15,27 +20,27 @@ Vue Vben Adminは、最新の`vue3`、`vite`、`TypeScript`などの主流技術
|
||||
|
||||
## アップグレード通知
|
||||
|
||||
これは最新バージョン5.0であり、以前のバージョンとは互換性がありません。新しいプロジェクトを開始する場合は、最新バージョンを使用することをお勧めします。古いバージョンを表示したい場合は、[v2ブランチ](https://github.com/vbenjs/vue-vben-admin/tree/v2)を使用してください。
|
||||
これは最新バージョン `5.0` であり、以前のバージョンとは互換性がありません。新しいプロジェクトを開始する場合は、最新バージョンを使用することをお勧めします。古いバージョンを表示したい場合は、[v2ブランチ](https://github.com/vbenjs/vue-vben-admin/tree/v2)を使用してください。
|
||||
|
||||
## 特徴
|
||||
|
||||
- **最新技術スタック**: Vue 3やViteなどの最先端フロントエンド技術で開発
|
||||
- **TypeScript**: アプリケーション規模のJavaScriptのための言語
|
||||
- **テーマ**: 複数のテーマカラーが利用可能で、カスタマイズオプションも豊富
|
||||
- **国際化**: 完全な内蔵国際化サポート
|
||||
- **権限管理**: 動的ルートベースの権限生成ソリューションを内蔵
|
||||
- **最新技術スタック**:Vue 3やViteなどの最先端フロントエンド技術で開発
|
||||
- **TypeScript**:アプリケーション規模のJavaScriptのための言語
|
||||
- **テーマ**:複数のテーマカラーが利用可能で、カスタマイズオプションも豊富
|
||||
- **国際化**:完全な内蔵国際化サポート
|
||||
- **権限管理**:動的ルートベースの権限生成ソリューションを内蔵
|
||||
|
||||
## プレビュー
|
||||
|
||||
- [Vben Admin](https://vben.pro/) - フルバージョンの中国語サイト
|
||||
|
||||
テストアカウント: vben/123456
|
||||
テストアカウント:vben/123456
|
||||
|
||||
<p align="center">
|
||||
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview1.png">
|
||||
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview2.png">
|
||||
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview3.png">
|
||||
</p>
|
||||
<div align="center">
|
||||
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview1.png">
|
||||
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview2.png">
|
||||
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview3.png">
|
||||
</div>
|
||||
|
||||
### Gitpodを使用
|
||||
|
||||
@@ -49,30 +54,27 @@ Gitpod(GitHub用の無料オンライン開発環境)でプロジェクト
|
||||
|
||||
## インストールと使用
|
||||
|
||||
- プロジェクトコードを取得
|
||||
1. プロジェクトコードを取得
|
||||
|
||||
```bash
|
||||
git clone https://github.com/vbenjs/vue-vben-admin.git
|
||||
```
|
||||
|
||||
- 依存関係のインストール
|
||||
2. 依存関係のインストール
|
||||
|
||||
```bash
|
||||
cd vue-vben-admin
|
||||
|
||||
corepack enable
|
||||
|
||||
npm i -g corepack
|
||||
pnpm install
|
||||
|
||||
```
|
||||
|
||||
- 実行
|
||||
3. 実行
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
- ビルド
|
||||
4. ビルド
|
||||
|
||||
```bash
|
||||
pnpm build
|
||||
@@ -86,40 +88,39 @@ pnpm build
|
||||
|
||||
ご参加をお待ちしておりますするか、Pull Requestを送信してください。
|
||||
|
||||
**Pull Request:**
|
||||
**Pull Request プロセス:**
|
||||
|
||||
1. コードをフォーク!
|
||||
2. 自分のブランチを作成: `git checkout -b feat/xxxx`
|
||||
3. 変更をコミット: `git commit -am 'feat(function): add xxxxx'`
|
||||
4. ブランチをプッシュ: `git push origin feat/xxxx`
|
||||
1. コードをフォーク
|
||||
2. 自分のブランチを作成:`git checkout -b feat/xxxx`
|
||||
3. 変更をコミット:`git commit -am 'feat(function): add xxxxx'`
|
||||
4. ブランチをプッシュ:`git push origin feat/xxxx`
|
||||
5. `pull request`を送信
|
||||
|
||||
## Git貢献提出規則
|
||||
|
||||
- 参考 [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) 規則 ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular))
|
||||
参考 [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) 規則 ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular))
|
||||
|
||||
- `feat` 新機能の追加
|
||||
- `fix` 問題/バグの修正
|
||||
- `style` コードスタイルに関連し、実行結果に影響しない
|
||||
- `perf` 最適化/パフォーマンス向上
|
||||
- `refactor` リファクタリング
|
||||
- `revert` 変更の取り消し
|
||||
- `test` テスト関連
|
||||
- `docs` ドキュメント/注釈
|
||||
- `chore` 依存関係の更新/スキャフォールディング設定の変更など
|
||||
- `ci` 継続的インテグレーション
|
||||
- `types` 型定義ファイルの変更
|
||||
- `wip` 開発中
|
||||
- `feat` 新機能の追加
|
||||
- `fix` 問題/バグの修正
|
||||
- `style` コードスタイルに関連し、実行結果に影響しない
|
||||
- `perf` 最適化/パフォーマンス向上
|
||||
- `refactor` リファクタリング
|
||||
- `revert` 変更の取り消し
|
||||
- `test` テスト関連
|
||||
- `docs` ドキュメント/注釈
|
||||
- `chore` 依存関係の更新/スキャフォールディング設定の変更など
|
||||
- `ci` 継続的インテグレーション
|
||||
- `types` 型定義ファイルの変更
|
||||
|
||||
## ブラウザサポート
|
||||
|
||||
ローカル開発には`Chrome 80+`ブラウザを推奨します
|
||||
ローカル開発には `Chrome 80+` ブラウザを推奨します
|
||||
|
||||
モダンブラウザをサポートし、IEはサポートしません
|
||||
|
||||
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
|
||||
| :-: | :-: | :-: | :-: | :-: |
|
||||
| サポートしない | 最新2バージョン | 最新2バージョン | 最新2バージョン | 最新2バージョン |
|
||||
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
|
||||
| :-: | :-: | :-: | :-: |
|
||||
| 最新2バージョン | 最新2バージョン | 最新2バージョン | 最新2バージョン |
|
||||
|
||||
## メンテナー
|
||||
|
||||
@@ -140,8 +141,7 @@ pnpm build
|
||||
## 貢献者
|
||||
|
||||
<a href="https://github.com/vbenjs/vue-vben-admin/graphs/contributors">
|
||||
<img alt="Contributors"
|
||||
src="https://opencollective.com/vbenjs/contributors.svg?button=false" />
|
||||
<img alt="Contributors" src="https://opencollective.com/vbenjs/contributors.svg?button=false" />
|
||||
</a>
|
||||
|
||||
## Discord
|
||||
|
||||
87
README.md
87
README.md
@@ -1,8 +1,13 @@
|
||||
<div align="center"> <a href="https://github.com/anncwb/vue-vben-admin"> <img alt="VbenAdmin Logo" width="215" src="https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp"> </a> <br> <br>
|
||||
<div align="center">
|
||||
<a href="https://github.com/anncwb/vue-vben-admin">
|
||||
<img alt="VbenAdmin Logo" width="215" src="https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp">
|
||||
</a>
|
||||
<br>
|
||||
<br>
|
||||
|
||||
[](LICENSE)
|
||||
|
||||
<h1>Vue Vben Admin</h1>
|
||||
<h1>Vue Vben Admin</h1>
|
||||
</div>
|
||||
|
||||
[](https://sonarcloud.io/summary/new_code?id=vbenjs_vue-vben-admin)    
|
||||
@@ -17,7 +22,7 @@ Vue Vben Admin is a free and open source middle and back-end template. Using the
|
||||
|
||||
This is the latest version, 5.0, and it is not compatible with previous versions. If you are starting a new project, it is recommended to use the latest version. If you wish to view the old version, please use the [v2 branch](https://github.com/vbenjs/vue-vben-admin/tree/v2).
|
||||
|
||||
## Feature
|
||||
## Features
|
||||
|
||||
- **Latest Technology Stack**: Developed with cutting-edge front-end technologies like Vue 3 and Vite
|
||||
- **TypeScript**: A language for application-scale JavaScript
|
||||
@@ -31,11 +36,11 @@ This is the latest version, 5.0, and it is not compatible with previous versions
|
||||
|
||||
Test Account: vben/123456
|
||||
|
||||
<p align="center">
|
||||
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview1.png">
|
||||
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview2.png">
|
||||
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview3.png">
|
||||
</p>
|
||||
<div align="center">
|
||||
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview1.png">
|
||||
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview2.png">
|
||||
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview3.png">
|
||||
</div>
|
||||
|
||||
### Use Gitpod
|
||||
|
||||
@@ -47,31 +52,29 @@ Open the project in Gitpod (free online dev environment for GitHub) and start co
|
||||
|
||||
[Document](https://doc.vben.pro/)
|
||||
|
||||
## Install and use
|
||||
## Install and Use
|
||||
|
||||
- Get the project code
|
||||
1. Get the project code
|
||||
|
||||
```bash
|
||||
git clone https://github.com/vbenjs/vue-vben-admin.git
|
||||
```
|
||||
|
||||
- Installation dependencies
|
||||
2. Install dependencies
|
||||
|
||||
```bash
|
||||
cd vue-vben-admin
|
||||
|
||||
corepack enable
|
||||
|
||||
npm i -g corepack
|
||||
pnpm install
|
||||
```
|
||||
|
||||
- run
|
||||
3. Run
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
- build
|
||||
4. Build
|
||||
|
||||
```bash
|
||||
pnpm build
|
||||
@@ -81,44 +84,43 @@ pnpm build
|
||||
|
||||
[CHANGELOG](https://github.com/vbenjs/vue-vben-admin/releases)
|
||||
|
||||
## How to contribute
|
||||
## How to Contribute
|
||||
|
||||
You are very welcome to join Or submit a Pull Request。
|
||||
You are very welcome to join! [Raise an issue](https://github.com/anncwb/vue-vben-admin/issues/new/choose) or submit a Pull Request.
|
||||
|
||||
**Pull Request:**
|
||||
**Pull Request Process:**
|
||||
|
||||
1. Fork code!
|
||||
2. Create your own branch: `git checkout -b feat/xxxx`
|
||||
1. Fork the code
|
||||
2. Create your branch: `git checkout -b feat/xxxx`
|
||||
3. Submit your changes: `git commit -am 'feat(function): add xxxxx'`
|
||||
4. Push your branch: `git push origin feat/xxxx`
|
||||
5. submit`pull request`
|
||||
5. Submit `pull request`
|
||||
|
||||
## Git Contribution submission specification
|
||||
## Git Contribution Submission Specification
|
||||
|
||||
- reference [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) specification ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular))
|
||||
Reference [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) specification ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular))
|
||||
|
||||
- `feat` Add new features
|
||||
- `fix` Fix the problem/BUG
|
||||
- `style` The code style is related and does not affect the running result
|
||||
- `perf` Optimization/performance improvement
|
||||
- `refactor` Refactor
|
||||
- `revert` Undo edit
|
||||
- `test` Test related
|
||||
- `docs` Documentation/notes
|
||||
- `chore` Dependency update/scaffolding configuration modification etc.
|
||||
- `ci` Continuous integration
|
||||
- `types` Type definition file changes
|
||||
- `wip` In development
|
||||
- `feat` Add new features
|
||||
- `fix` Fix the problem/BUG
|
||||
- `style` The code style is related and does not affect the running result
|
||||
- `perf` Optimization/performance improvement
|
||||
- `refactor` Refactor
|
||||
- `revert` Undo edit
|
||||
- `test` Test related
|
||||
- `docs` Documentation/notes
|
||||
- `chore` Dependency update/scaffolding configuration modification etc.
|
||||
- `ci` Continuous integration
|
||||
- `types` Type definition file changes
|
||||
|
||||
## Browser support
|
||||
## Browser Support
|
||||
|
||||
The `Chrome 80+` browser is recommended for local development
|
||||
|
||||
Support modern browsers, not IE
|
||||
|
||||
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
|
||||
| :-: | :-: | :-: | :-: | :-: |
|
||||
| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
|
||||
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
|
||||
| :-: | :-: | :-: | :-: |
|
||||
| last 2 versions | last 2 versions | last 2 versions | last 2 versions |
|
||||
|
||||
## Maintainer
|
||||
|
||||
@@ -136,11 +138,10 @@ If you think this project is helpful to you, you can help the author buy a cup o
|
||||
|
||||
<a style="display: block;width: 100px;height: 50px;line-height: 50px; color: #fff;text-align: center; background: #408aee;border-radius: 4px;" href="https://www.paypal.com/paypalme/cvvben">Paypal Me</a>
|
||||
|
||||
## Contributor
|
||||
## Contributors
|
||||
|
||||
<a href="https://github.com/vbenjs/vue-vben-admin/graphs/contributors">
|
||||
<img alt="Contributors"
|
||||
src="https://opencollective.com/vbenjs/contributors.svg?button=false" />
|
||||
<img alt="Contributors" src="https://opencollective.com/vbenjs/contributors.svg?button=false" />
|
||||
</a>
|
||||
|
||||
## Discord
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
v5版本采用分仓(包)目录结构, 具体开发路径为: `根目录/apps/web-antd`
|
||||
|
||||
目前对应后端版本: **分布式5.3.1/微服务2.3.0**
|
||||
目前对应后端版本: **分布式5.4.0/微服务2.4.0**
|
||||
|
||||
V1.1.0版本已支持离线图标
|
||||
|
||||
@@ -18,7 +18,7 @@ V1.2.0版本对接warmflow工作流
|
||||
|
||||
| 组件/框架 | 版本 |
|
||||
| :------------- | :----- |
|
||||
| vben | 5.5.4 |
|
||||
| vben | 5.5.6 |
|
||||
| ant-design-vue | 4.2.6 |
|
||||
| vue | 3.5.13 |
|
||||
|
||||
@@ -46,14 +46,6 @@ admin 账号: admin admin123
|
||||
|
||||
[RuoYi-Plus 文档地址](https://plus-doc.dromara.org/#/)
|
||||
|
||||
## 关于表单
|
||||
|
||||
如果你觉得`useVbenForm`难度很大, 完全可以**使用原生antd表单**进行开发, 不一定非得用`useVbenForm`进行开发
|
||||
|
||||
`apps/web-antd/src/views/system/notice/notice-modal.vue`即`通知公告modal`使用**原生antd form**进行(反向🤔)重构, 不想用`useVbenForm`可参考该页面进行表单开发
|
||||
|
||||
复杂表单(如各种联动, 需要自定义样式布局, 需要自定义组件)**优先使用原生表单**(反正说了也没人听听😅)
|
||||
|
||||
## 预览图
|
||||
|
||||
        
|
||||
@@ -76,7 +68,7 @@ admin 账号: admin admin123
|
||||
git clone https://gitee.com/dapppp/ruoyi-plus-vben5.git
|
||||
```
|
||||
|
||||
- 安装依赖
|
||||
2. 安装依赖
|
||||
|
||||
```bash
|
||||
cd ruoyi-plus-vben5
|
||||
@@ -150,7 +142,7 @@ VITE_GLOB_WEBSOCKET_ENABLE=false
|
||||
pnpm dev:antd
|
||||
```
|
||||
|
||||
- 打包
|
||||
4. 打包
|
||||
|
||||
```bash
|
||||
pnpm build:antd
|
||||
@@ -164,21 +156,21 @@ pnpm build:antd
|
||||
|
||||
## Git 贡献提交规范
|
||||
|
||||
- 参考 [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) 规范 ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular))
|
||||
参考 [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) 规范 ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular))
|
||||
|
||||
- `feat` 增加新功能
|
||||
- `fix` 修复问题/BUG
|
||||
- `style` 代码风格相关无影响运行结果的
|
||||
- `perf` 优化/性能提升
|
||||
- `refactor` 重构
|
||||
- `revert` 撤销修改
|
||||
- `test` 测试相关
|
||||
- `docs` 文档/注释
|
||||
- `chore` 依赖更新/脚手架配置修改等
|
||||
- `workflow` 工作流改进
|
||||
- `ci` 持续集成
|
||||
- `types` 类型定义文件更改
|
||||
- `wip` 开发中
|
||||
- `feat` 增加新功能
|
||||
- `fix` 修复问题/BUG
|
||||
- `style` 代码风格相关无影响运行结果的
|
||||
- `perf` 优化/性能提升
|
||||
- `refactor` 重构
|
||||
- `revert` 撤销修改
|
||||
- `test` 测试相关
|
||||
- `docs` 文档/注释
|
||||
- `chore` 依赖更新/脚手架配置修改等
|
||||
- `workflow` 工作流改进
|
||||
- `ci` 持续集成
|
||||
- `types` 类型定义文件更改
|
||||
- `wip` 开发中
|
||||
|
||||
## 浏览器支持
|
||||
|
||||
@@ -186,7 +178,7 @@ pnpm build:antd
|
||||
|
||||
本地开发推荐使用`Chrome` 最新版本浏览器
|
||||
|
||||
支持现代浏览器, 不支持 IE
|
||||
支持现代浏览器,不支持 IE
|
||||
|
||||
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
|
||||
| :-: | :-: | :-: | :-: | :-: |
|
||||
|
||||
28
apps/backend-mock/api/demo/bigint.ts
Normal file
28
apps/backend-mock/api/demo/bigint.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
export default eventHandler(async (event) => {
|
||||
const userinfo = verifyAccessToken(event);
|
||||
if (!userinfo) {
|
||||
return unAuthorizedResponse(event);
|
||||
}
|
||||
const data = `
|
||||
{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": [
|
||||
{
|
||||
"id": 123456789012345678901234567890123456789012345678901234567890,
|
||||
"name": "John Doe",
|
||||
"age": 30,
|
||||
"email": "john-doe@demo.com"
|
||||
},
|
||||
{
|
||||
"id": 987654321098765432109876543210987654321098765432109876543210,
|
||||
"name": "Jane Smith",
|
||||
"age": 25,
|
||||
"email": "jane@demo.com"
|
||||
}
|
||||
]
|
||||
}
|
||||
`;
|
||||
setHeader(event, 'Content-Type', 'application/json');
|
||||
return data;
|
||||
});
|
||||
13
apps/backend-mock/api/upload.ts
Normal file
13
apps/backend-mock/api/upload.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||
import { unAuthorizedResponse } from '~/utils/response';
|
||||
|
||||
export default eventHandler((event) => {
|
||||
const userinfo = verifyAccessToken(event);
|
||||
if (!userinfo) {
|
||||
return unAuthorizedResponse(event);
|
||||
}
|
||||
return useResponseSuccess({
|
||||
url: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp',
|
||||
});
|
||||
// return useResponseError("test")
|
||||
});
|
||||
@@ -7,6 +7,7 @@ export default defineEventHandler(() => {
|
||||
<li><a href="/api/menu">/api/menu/all</a></li>
|
||||
<li><a href="/api/auth/codes">/api/auth/codes</a></li>
|
||||
<li><a href="/api/auth/login">/api/auth/login</a></li>
|
||||
<li><a href="/api/upload">/api/upload</a></li>
|
||||
</ul>
|
||||
`;
|
||||
});
|
||||
|
||||
@@ -3,3 +3,6 @@ VITE_APP_TITLE=Plus Admin
|
||||
|
||||
# 应用命名空间,用于缓存、store等功能的前缀,确保隔离
|
||||
VITE_APP_NAMESPACE=vben-web-antd
|
||||
|
||||
# 对store进行加密的密钥,在将store持久化到localStorage时会使用该密钥进行加密
|
||||
VITE_APP_STORE_SECURE_KEY=please-replace-me-with-your-own-key
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/web-antd",
|
||||
"version": "1.3.0",
|
||||
"version": "1.4.1",
|
||||
"homepage": "https://vben.pro",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
||||
@@ -40,6 +40,9 @@ const AutoComplete = defineAsyncComponent(
|
||||
() => import('ant-design-vue/es/auto-complete'),
|
||||
);
|
||||
const Button = defineAsyncComponent(() => import('ant-design-vue/es/button'));
|
||||
const Cascader = defineAsyncComponent(
|
||||
() => import('ant-design-vue/es/cascader'),
|
||||
);
|
||||
const Checkbox = defineAsyncComponent(
|
||||
() => import('ant-design-vue/es/checkbox'),
|
||||
);
|
||||
@@ -88,13 +91,11 @@ const withDefaultPlaceholder = <T extends Component>(
|
||||
componentProps: Recordable<any> = {},
|
||||
) => {
|
||||
return defineComponent({
|
||||
inheritAttrs: false,
|
||||
name: component.name,
|
||||
inheritAttrs: false,
|
||||
setup: (props: any, { attrs, expose, slots }) => {
|
||||
/**
|
||||
* 需要使用computed 否则后续updateSchema更新的placeholder无法显示(响应式问题)
|
||||
*/
|
||||
const placeholder = computed(
|
||||
// 改为placeholder 解决在keepalive & 语言切换 & tab切换 显示不变的问题
|
||||
const computedPlaceholder = computed(
|
||||
() =>
|
||||
props?.placeholder ||
|
||||
attrs?.placeholder ||
|
||||
@@ -118,7 +119,7 @@ const withDefaultPlaceholder = <T extends Component>(
|
||||
component,
|
||||
{
|
||||
...componentProps,
|
||||
placeholder: placeholder.value,
|
||||
placeholder: computedPlaceholder.value,
|
||||
...props,
|
||||
...attrs,
|
||||
ref: innerRef,
|
||||
@@ -134,6 +135,7 @@ export type ComponentType =
|
||||
| 'ApiSelect'
|
||||
| 'ApiTreeSelect'
|
||||
| 'AutoComplete'
|
||||
| 'Cascader'
|
||||
| 'Checkbox'
|
||||
| 'CheckboxGroup'
|
||||
| 'DatePicker'
|
||||
@@ -168,21 +170,36 @@ async function initComponentAdapter() {
|
||||
// 如果你的组件体积比较大,可以使用异步加载
|
||||
// Button: () =>
|
||||
// import('xxx').then((res) => res.Button),
|
||||
ApiSelect: withDefaultPlaceholder(ApiComponent, 'select', {
|
||||
component: Select,
|
||||
loadingSlot: 'suffixIcon',
|
||||
visibleEvent: 'onDropdownVisibleChange',
|
||||
modelPropName: 'value',
|
||||
}),
|
||||
ApiTreeSelect: withDefaultPlaceholder(ApiComponent, 'select', {
|
||||
component: TreeSelect,
|
||||
fieldNames: { label: 'label', value: 'value', children: 'children' },
|
||||
loadingSlot: 'suffixIcon',
|
||||
modelPropName: 'value',
|
||||
optionsPropName: 'treeData',
|
||||
visibleEvent: 'onVisibleChange',
|
||||
}),
|
||||
ApiSelect: withDefaultPlaceholder(
|
||||
{
|
||||
...ApiComponent,
|
||||
name: 'ApiSelect',
|
||||
},
|
||||
'select',
|
||||
{
|
||||
component: Select,
|
||||
loadingSlot: 'suffixIcon',
|
||||
visibleEvent: 'onDropdownVisibleChange',
|
||||
modelPropName: 'value',
|
||||
},
|
||||
),
|
||||
ApiTreeSelect: withDefaultPlaceholder(
|
||||
{
|
||||
...ApiComponent,
|
||||
name: 'ApiTreeSelect',
|
||||
},
|
||||
'select',
|
||||
{
|
||||
component: TreeSelect,
|
||||
fieldNames: { label: 'label', value: 'value', children: 'children' },
|
||||
loadingSlot: 'suffixIcon',
|
||||
modelPropName: 'value',
|
||||
optionsPropName: 'treeData',
|
||||
visibleEvent: 'onVisibleChange',
|
||||
},
|
||||
),
|
||||
AutoComplete,
|
||||
Cascader: withDefaultPlaceholder(Cascader, 'select'),
|
||||
Checkbox,
|
||||
CheckboxGroup,
|
||||
DatePicker,
|
||||
|
||||
@@ -10,44 +10,46 @@ import { $t } from '@vben/locales';
|
||||
|
||||
import { isArray } from 'lodash-es';
|
||||
|
||||
setupVbenForm<ComponentType>({
|
||||
config: {
|
||||
// ant design vue组件库默认都是 v-model:value
|
||||
baseModelPropName: 'value',
|
||||
async function initSetupVbenForm() {
|
||||
setupVbenForm<ComponentType>({
|
||||
config: {
|
||||
// ant design vue组件库默认都是 v-model:value
|
||||
baseModelPropName: 'value',
|
||||
|
||||
// 一些组件是 v-model:checked 或者 v-model:fileList
|
||||
modelPropNameMap: {
|
||||
Checkbox: 'checked',
|
||||
Radio: 'checked',
|
||||
RichTextarea: 'modelValue',
|
||||
Switch: 'checked',
|
||||
Upload: 'fileList',
|
||||
// 一些组件是 v-model:checked 或者 v-model:fileList
|
||||
modelPropNameMap: {
|
||||
Checkbox: 'checked',
|
||||
Radio: 'checked',
|
||||
RichTextarea: 'modelValue',
|
||||
Switch: 'checked',
|
||||
Upload: 'fileList',
|
||||
},
|
||||
},
|
||||
},
|
||||
defineRules: {
|
||||
// 输入项目必填国际化适配
|
||||
required: (value, _params, ctx) => {
|
||||
if (value === undefined || value === null || value.length === 0) {
|
||||
return $t('ui.formRules.required', [ctx.label]);
|
||||
}
|
||||
return true;
|
||||
defineRules: {
|
||||
// 输入项目必填国际化适配
|
||||
required: (value, _params, ctx) => {
|
||||
if (value === undefined || value === null || value.length === 0) {
|
||||
return $t('ui.formRules.required', [ctx.label]);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
// 选择项目必填国际化适配
|
||||
selectRequired: (value, _params, ctx) => {
|
||||
if (
|
||||
[false, null, undefined].includes(value) ||
|
||||
(isArray(value) && value.length === 0)
|
||||
) {
|
||||
return $t('ui.formRules.selectRequired', [ctx.label]);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
// 选择项目必填国际化适配
|
||||
selectRequired: (value, _params, ctx) => {
|
||||
if (
|
||||
[false, null, undefined].includes(value) ||
|
||||
(isArray(value) && value.length === 0)
|
||||
) {
|
||||
return $t('ui.formRules.selectRequired', [ctx.label]);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const useVbenForm = useForm<ComponentType>;
|
||||
|
||||
export { useVbenForm, z };
|
||||
export { initSetupVbenForm, useVbenForm, z };
|
||||
|
||||
export type VbenFormSchema = FormSchema<ComponentType>;
|
||||
export type { VbenFormProps };
|
||||
|
||||
@@ -50,13 +50,15 @@ setupVbenVxeTable({
|
||||
// 右上角工具栏
|
||||
toolbarConfig: {
|
||||
// 自定义列
|
||||
custom: {
|
||||
custom: true,
|
||||
customOptions: {
|
||||
icon: 'vxe-icon-setting',
|
||||
},
|
||||
// 最大化
|
||||
zoom: true,
|
||||
// 刷新
|
||||
refresh: {
|
||||
refresh: true,
|
||||
refreshOptions: {
|
||||
// 默认为reload 修改为在当前页刷新
|
||||
code: 'query',
|
||||
},
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import type { GrantType } from '@vben/common-ui';
|
||||
import type { HttpResponse } from '@vben/request';
|
||||
|
||||
import { h } from 'vue';
|
||||
|
||||
import { useAppConfig } from '@vben/hooks';
|
||||
|
||||
import { Modal } from 'ant-design-vue';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
const { clientId, sseEnable } = useAppConfig(
|
||||
@@ -90,8 +95,32 @@ export async function loginApi(data: AuthApi.LoginParams) {
|
||||
* 用户登出
|
||||
* @returns void
|
||||
*/
|
||||
export function doLogout() {
|
||||
return requestClient.post<void>('/auth/logout');
|
||||
export async function doLogout() {
|
||||
const resp = await requestClient.post<HttpResponse<void>>(
|
||||
'/auth/logout',
|
||||
null,
|
||||
{
|
||||
isTransformResponse: false,
|
||||
},
|
||||
);
|
||||
// 无奈之举 对错误用法的提示
|
||||
if (resp.code === 401 && import.meta.env.DEV) {
|
||||
Modal.destroyAll();
|
||||
Modal.warn({
|
||||
title: '后端配置出现错误',
|
||||
centered: true,
|
||||
content: h('div', { class: 'flex flex-col gap-2' }, [
|
||||
`检测到你的logout接口返回了401, 导致前端一直进入循环逻辑???`,
|
||||
...Array.from({ length: 3 }, () =>
|
||||
h(
|
||||
'span',
|
||||
{ class: 'font-bold text-red-500 text-[18px]' },
|
||||
'去检查你的后端配置!别盯着前端找问题了!这不是前端问题!',
|
||||
),
|
||||
),
|
||||
]),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,4 +7,5 @@ export interface OnlineUser {
|
||||
browser: string;
|
||||
os: string;
|
||||
loginTime: number;
|
||||
deviceType: string;
|
||||
}
|
||||
|
||||
@@ -39,6 +39,11 @@ const { apiURL, clientId, enableEncrypt } = useAppConfig(
|
||||
*/
|
||||
let isLogoutProcessing = false;
|
||||
|
||||
/**
|
||||
* 定义一个401专用异常 用于可能会用到的区分场景?
|
||||
*/
|
||||
export class UnauthorizedException extends Error {}
|
||||
|
||||
function createRequestClient(baseURL: string) {
|
||||
const client = new RequestClient({
|
||||
// 后端地址
|
||||
@@ -228,7 +233,7 @@ function createRequestClient(baseURL: string) {
|
||||
case 401: {
|
||||
// 已经在登出过程中 不再执行
|
||||
if (isLogoutProcessing) {
|
||||
throw new Error(timeoutMsg);
|
||||
throw new UnauthorizedException(timeoutMsg);
|
||||
}
|
||||
isLogoutProcessing = true;
|
||||
const _msg = $t('http.loginTimeout');
|
||||
@@ -238,7 +243,7 @@ function createRequestClient(baseURL: string) {
|
||||
isLogoutProcessing = false;
|
||||
});
|
||||
// 不再执行下面逻辑
|
||||
throw new Error(_msg);
|
||||
throw new UnauthorizedException(_msg);
|
||||
}
|
||||
default: {
|
||||
if (msg) {
|
||||
|
||||
@@ -81,3 +81,12 @@ export function tenantPackageMenuTreeSelect(packageId: ID) {
|
||||
`${Api.tenantPackageMenuTreeselect}/${packageId}`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除菜单
|
||||
* @param menuIds 菜单ids
|
||||
* @returns void
|
||||
*/
|
||||
export function menuCascadeRemove(menuIds: IDS) {
|
||||
return requestClient.deleteWithMsg<void>(`${Api.root}/cascade/${menuIds}`);
|
||||
}
|
||||
|
||||
@@ -37,9 +37,10 @@ export function ossConfigRemove(ossConfigIds: IDS) {
|
||||
|
||||
// 更改OSS配置的状态
|
||||
export function ossConfigChangeStatus(data: any) {
|
||||
const requestData = {
|
||||
const requestData: Partial<OssConfig> = {
|
||||
ossConfigId: data.ossConfigId,
|
||||
status: data.status,
|
||||
configKey: data.configKey,
|
||||
};
|
||||
return requestClient.putWithMsg(Api.ossConfigChangeStatus, requestData);
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ export function pageByCurrent(params?: PageQuery) {
|
||||
*/
|
||||
export function flowInfo(businessId: string) {
|
||||
return requestClient.get<FlowInfoResponse>(
|
||||
`/workflow/instance/flowImage/${businessId}`,
|
||||
`/workflow/instance/flowHisTaskList/${businessId}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,6 @@ export interface Flow {
|
||||
}
|
||||
|
||||
export interface FlowInfoResponse {
|
||||
image: string;
|
||||
instanceId: string;
|
||||
list: Flow[];
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ import { App, ConfigProvider, theme } from 'ant-design-vue';
|
||||
|
||||
import { antdLocale } from '#/locales';
|
||||
|
||||
import { useUploadTip } from './upload-tip';
|
||||
|
||||
defineOptions({ name: 'App' });
|
||||
|
||||
const { isDark } = usePreferences();
|
||||
@@ -28,6 +30,8 @@ const tokenTheme = computed(() => {
|
||||
token: tokens,
|
||||
};
|
||||
});
|
||||
|
||||
useUploadTip();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -13,6 +13,7 @@ import { setupGlobalComponent } from '#/components/global';
|
||||
import { $t, setupI18n } from '#/locales';
|
||||
|
||||
import { initComponentAdapter } from './adapter/component';
|
||||
import { initSetupVbenForm } from './adapter/form';
|
||||
import App from './app.vue';
|
||||
import { router } from './router';
|
||||
|
||||
@@ -20,6 +21,9 @@ async function bootstrap(namespace: string) {
|
||||
// 初始化组件适配器
|
||||
await initComponentAdapter();
|
||||
|
||||
// 初始化表单组件
|
||||
await initSetupVbenForm();
|
||||
|
||||
// // 设置弹窗的默认配置
|
||||
// setDefaultModalProps({
|
||||
// fullscreenButton: false,
|
||||
|
||||
@@ -202,13 +202,31 @@ const events = computed(() => {
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
/***
|
||||
由于modal/drawer的zIndex升级后为2000
|
||||
这里会造成遮挡 修改为更高的zIndex
|
||||
*/
|
||||
// 展开层元素z-index
|
||||
$dropdown-index: 2025;
|
||||
|
||||
@mixin tinymce-valid-fail($color) {
|
||||
.app-tinymce {
|
||||
// 最外层的tinymce容器
|
||||
.tox-tinymce {
|
||||
border-color: $color;
|
||||
}
|
||||
// focus样式
|
||||
.tox .tox-edit-area::before {
|
||||
border-color: $color;
|
||||
border-right: none;
|
||||
border-left: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tox.tox-silver-sink.tox-tinymce-aux {
|
||||
/** 该样式默认为1300的zIndex */
|
||||
z-index: 2025;
|
||||
z-index: $dropdown-index;
|
||||
}
|
||||
|
||||
.tox-fullscreen .tox.tox-tinymce-aux {
|
||||
z-index: $dropdown-index !important;
|
||||
}
|
||||
|
||||
.app-tinymce {
|
||||
@@ -218,5 +236,29 @@ const events = computed(() => {
|
||||
.tox-promotion {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/** 保持focus时与primary色一致 */
|
||||
.tox .tox-edit-area::before {
|
||||
border-color: hsl(var(--primary));
|
||||
}
|
||||
}
|
||||
|
||||
// antd原生表单 校验失败样式
|
||||
.ant-form-item:has(.ant-form-item-explain-error) {
|
||||
$error-color: #ff3860;
|
||||
|
||||
@include tinymce-valid-fail($error-color);
|
||||
}
|
||||
|
||||
// useVbenForm 校验失败样式
|
||||
.form-valid-error {
|
||||
$error-color: hsl(var(--destructive));
|
||||
|
||||
@include tinymce-valid-fail($error-color);
|
||||
}
|
||||
|
||||
// 全屏下样式处理 不去掉transform位置会异常
|
||||
div[role='dialog']:has(.tox.tox-tinymce.tox-fullscreen) {
|
||||
transform: none !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -98,7 +98,7 @@ Upload.Dragger只会影响样式
|
||||
{{ $t('component.upload.upload') }}
|
||||
</a-button>
|
||||
</div>
|
||||
<div v-if="enableDragUpload && innerFileList?.length < maxCount">
|
||||
<div v-if="enableDragUpload">
|
||||
<p class="ant-upload-drag-icon">
|
||||
<InboxOutlined />
|
||||
</p>
|
||||
|
||||
@@ -160,6 +160,8 @@ export function useUpload(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// 用来标识是否为上传 这样在watch内部不需要请求api
|
||||
let isUpload = false;
|
||||
function handleChange(info: UploadChangeParam) {
|
||||
/**
|
||||
* 移除当前文件
|
||||
@@ -199,7 +201,8 @@ export function useUpload(
|
||||
currentFile.fileName = transformFilename(cb);
|
||||
currentFile.name = transformFilename(cb);
|
||||
currentFile.thumbUrl = transformThumbUrl(cb);
|
||||
|
||||
// 标记为上传 watch根据值做处理
|
||||
isUpload = true;
|
||||
// ossID添加 单个文件会被当做string
|
||||
if (props.maxCount === 1) {
|
||||
bindValue.value = ossId;
|
||||
@@ -319,8 +322,18 @@ export function useUpload(
|
||||
() => bindValue.value,
|
||||
async (value) => {
|
||||
if (value.length === 0) {
|
||||
// 清空绑定值时,同时清空innerFileList,避免外部使用时还能读取到
|
||||
innerFileList.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// 上传完毕 不需要调用获取信息接口
|
||||
if (isUpload) {
|
||||
// 清理 使下一次状态可用
|
||||
isUpload = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const resp = await ossInfo(value);
|
||||
function transformFile(info: OssFile) {
|
||||
const cb = { type: 'info', response: info } as const;
|
||||
|
||||
@@ -138,8 +138,8 @@ watch(
|
||||
:avatar
|
||||
:menus
|
||||
:text="userStore.userInfo?.realName"
|
||||
description="ann.vben@gmail.com"
|
||||
tag-text="Pro"
|
||||
:description="userStore.userInfo?.email || '未设置邮箱'"
|
||||
:tag-text="userStore.userInfo?.username"
|
||||
@logout="handleLogout"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -20,6 +20,9 @@ export const overridesPreferences = defineOverridesPreferences({
|
||||
* 这里可以设置默认头像 url链接或vite导入的图片链接
|
||||
*/
|
||||
// defaultAvatar: '',
|
||||
/**
|
||||
* 在这里设置应用标题
|
||||
*/
|
||||
name: import.meta.env.VITE_APP_TITLE,
|
||||
/**
|
||||
* 不支持modal模式 需要改动的地方太多
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type {
|
||||
ComponentRecordType,
|
||||
GenerateMenuAndRoutesOptions,
|
||||
RouteMeta,
|
||||
RouteRecordStringComponent,
|
||||
} from '@vben/types';
|
||||
|
||||
@@ -21,6 +22,37 @@ import { localMenuList } from './routes/local';
|
||||
const forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue');
|
||||
const NotFoundComponent = () => import('#/views/_core/fallback/not-found.vue');
|
||||
|
||||
/**
|
||||
* 后端返回的meta有时候不包括需要的信息 比如activePath等
|
||||
* 在这里定义映射
|
||||
*/
|
||||
const routeMetaMapping: Record<string, Omit<RouteMeta, 'title'>> = {
|
||||
'/system/role-auth/user/:roleId': {
|
||||
activePath: '/system/role',
|
||||
requireHomeRedirect: true,
|
||||
},
|
||||
|
||||
'/system/oss-config/index': {
|
||||
activePath: '/system/oss',
|
||||
requireHomeRedirect: true,
|
||||
},
|
||||
|
||||
'/tool/gen-edit/index/:tableId': {
|
||||
activePath: '/tool/gen',
|
||||
requireHomeRedirect: true,
|
||||
},
|
||||
|
||||
'/workflow/design/index': {
|
||||
activePath: '/workflow/processDefinition',
|
||||
requireHomeRedirect: true,
|
||||
},
|
||||
|
||||
'/workflow/leaveEdit/index': {
|
||||
activePath: '/demo/leave',
|
||||
requireHomeRedirect: true,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* 后台路由转vben路由
|
||||
* @param menuList 后台菜单
|
||||
@@ -98,6 +130,17 @@ function backMenuToVbenMenu(
|
||||
path: menu.path,
|
||||
};
|
||||
|
||||
// 处理meta映射
|
||||
if (Object.keys(routeMetaMapping).includes(vbenRoute.path)) {
|
||||
const routeMeta = routeMetaMapping[vbenRoute.path];
|
||||
if (routeMeta) {
|
||||
vbenRoute.meta = {
|
||||
...vbenRoute.meta,
|
||||
...(routeMeta as RouteMeta),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 添加路由参数信息
|
||||
if (menu.query) {
|
||||
try {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Router } from 'vue-router';
|
||||
|
||||
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
|
||||
import { LOGIN_PATH } from '@vben/constants';
|
||||
import { preferences } from '@vben/preferences';
|
||||
import { useAccessStore, useUserStore } from '@vben/stores';
|
||||
import { startProgress, stopProgress } from '@vben/utils';
|
||||
@@ -18,7 +18,7 @@ function setupCommonGuard(router: Router) {
|
||||
// 记录已经加载的页面
|
||||
const loadedPaths = new Set<string>();
|
||||
|
||||
router.beforeEach(async (to) => {
|
||||
router.beforeEach((to) => {
|
||||
to.meta.loaded = loadedPaths.has(to.path);
|
||||
|
||||
// 页面加载进度条
|
||||
@@ -56,7 +56,7 @@ function setupAccessGuard(router: Router) {
|
||||
return decodeURIComponent(
|
||||
(to.query?.redirect as string) ||
|
||||
userStore.userInfo?.homePath ||
|
||||
DEFAULT_HOME_PATH,
|
||||
preferences.app.defaultHomePath,
|
||||
);
|
||||
}
|
||||
return true;
|
||||
@@ -75,7 +75,7 @@ function setupAccessGuard(router: Router) {
|
||||
path: LOGIN_PATH,
|
||||
// 如不需要,直接删除 query
|
||||
query:
|
||||
to.fullPath === DEFAULT_HOME_PATH
|
||||
to.fullPath === preferences.app.defaultHomePath
|
||||
? {}
|
||||
: { redirect: encodeURIComponent(to.fullPath) },
|
||||
// 携带当前跳转的页面,登录后重新跳转该页面
|
||||
@@ -108,8 +108,8 @@ function setupAccessGuard(router: Router) {
|
||||
accessStore.setAccessRoutes(accessibleRoutes);
|
||||
accessStore.setIsAccessChecked(true);
|
||||
const redirectPath = (from.query.redirect ??
|
||||
(to.path === DEFAULT_HOME_PATH
|
||||
? userInfo.homePath || DEFAULT_HOME_PATH
|
||||
(to.path === preferences.app.defaultHomePath
|
||||
? userInfo.homePath || preferences.app.defaultHomePath
|
||||
: to.fullPath)) as string;
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
|
||||
import { LOGIN_PATH } from '@vben/constants';
|
||||
import { preferences } from '@vben/preferences';
|
||||
|
||||
import { $t } from '#/locales';
|
||||
|
||||
@@ -34,7 +35,7 @@ const coreRoutes: RouteRecordRaw[] = [
|
||||
},
|
||||
name: 'Root',
|
||||
path: '/',
|
||||
redirect: DEFAULT_HOME_PATH,
|
||||
redirect: preferences.app.defaultHomePath,
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -2,8 +2,14 @@ import type { RouteRecordStringComponent } from '@vben/types';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
const {
|
||||
version,
|
||||
// vite inject-metadata 插件注入的全局变量
|
||||
} = __VBEN_ADMIN_METADATA__ || {};
|
||||
|
||||
/**
|
||||
* 该文件放非后台返回的路由 比如个人中心 等需要跳转显示的页面
|
||||
* 也可以直接在菜单管理配置
|
||||
*/
|
||||
const localRoutes: RouteRecordStringComponent[] = [
|
||||
{
|
||||
@@ -17,69 +23,6 @@ const localRoutes: RouteRecordStringComponent[] = [
|
||||
name: 'Profile',
|
||||
path: '/profile',
|
||||
},
|
||||
{
|
||||
component: '/system/oss-config/index',
|
||||
meta: {
|
||||
activePath: '/system/oss',
|
||||
icon: 'ant-design:setting-outlined',
|
||||
title: 'oss配置',
|
||||
hideInMenu: true,
|
||||
requireHomeRedirect: true,
|
||||
},
|
||||
name: 'OssConfig',
|
||||
path: '/system/oss-config',
|
||||
},
|
||||
{
|
||||
component: '/tool/gen/edit-gen',
|
||||
meta: {
|
||||
activePath: '/tool/gen',
|
||||
icon: 'tabler:code',
|
||||
title: '生成配置',
|
||||
hideInMenu: true,
|
||||
requireHomeRedirect: true,
|
||||
},
|
||||
name: 'GenConfig',
|
||||
path: '/code-gen/edit/:tableId',
|
||||
},
|
||||
{
|
||||
component: '/system/role-assign/index',
|
||||
meta: {
|
||||
activePath: '/system/role',
|
||||
icon: 'eos-icons:role-binding-outlined',
|
||||
title: '分配角色',
|
||||
hideInMenu: true,
|
||||
requireHomeRedirect: true,
|
||||
},
|
||||
name: 'RoleAssign',
|
||||
path: '/system/role-assign/:roleId',
|
||||
},
|
||||
{
|
||||
component: '/workflow/components/flow-designer',
|
||||
meta: {
|
||||
activePath: '/workflow/processDefinition',
|
||||
icon: 'fluent-mdl2:flow',
|
||||
title: '流程设计',
|
||||
hideInMenu: true,
|
||||
requireHomeRedirect: true,
|
||||
},
|
||||
name: 'WorkflowDesigner',
|
||||
path: '/workflow/designer',
|
||||
},
|
||||
/**
|
||||
* 需要添加iframe路由 同目录的./workflow-iframe.ts
|
||||
*/
|
||||
{
|
||||
component: 'workflow/leave/leave-form',
|
||||
meta: {
|
||||
icon: 'flat-color-icons:leave',
|
||||
title: '请假申请',
|
||||
activePath: '/demo/leave',
|
||||
hideInMenu: true,
|
||||
requireHomeRedirect: true,
|
||||
},
|
||||
name: 'WorkflowLeaveIndex',
|
||||
path: '/workflow/leaveEdit/index',
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -134,8 +77,8 @@ export const localMenuList: RouteRecordStringComponent[] = [
|
||||
icon: 'lucide:book-open-text',
|
||||
keepAlive: true,
|
||||
title: '更新记录',
|
||||
badge: '1.3.0',
|
||||
badgeVariants: '#CC0033',
|
||||
badge: `当前: ${version}`,
|
||||
badgeVariants: 'bg-primary',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -4,7 +4,8 @@ import type { UserInfo } from '@vben/types';
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
|
||||
import { LOGIN_PATH } from '@vben/constants';
|
||||
import { preferences } from '@vben/preferences';
|
||||
import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores';
|
||||
|
||||
import { notification } from 'ant-design-vue';
|
||||
@@ -55,7 +56,9 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
if (accessStore.loginExpired) {
|
||||
accessStore.setLoginExpired(false);
|
||||
} else {
|
||||
onSuccess ? await onSuccess?.() : await router.push(DEFAULT_HOME_PATH);
|
||||
onSuccess
|
||||
? await onSuccess?.()
|
||||
: await router.push(preferences.app.defaultHomePath);
|
||||
}
|
||||
|
||||
if (userInfo?.realName) {
|
||||
@@ -115,6 +118,7 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
roles,
|
||||
userId: user.userId,
|
||||
username: user.userName,
|
||||
email: user.email ?? '',
|
||||
};
|
||||
userStore.setUserInfo(userInfo);
|
||||
/**
|
||||
|
||||
36
apps/web-antd/src/upload-tip.ts
Normal file
36
apps/web-antd/src/upload-tip.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { onMounted } from 'vue';
|
||||
|
||||
import { useLocalStorage } from '@vueuse/core';
|
||||
import { Modal } from 'ant-design-vue';
|
||||
|
||||
export function useUploadTip() {
|
||||
const readTip = useLocalStorage<boolean>('__upload_tip_read_5.4.0', false);
|
||||
onMounted(() => {
|
||||
if (readTip.value || !import.meta.env.DEV) {
|
||||
return;
|
||||
}
|
||||
const modalInstance = Modal.info({
|
||||
title: '提示',
|
||||
centered: true,
|
||||
content:
|
||||
'如果你的版本是从低版本升级到后端>5.4.0, 记得执行升级sql, 否则跳转页面(如oss 代码生成配置)等会404',
|
||||
okButtonProps: { disabled: true },
|
||||
onOk() {
|
||||
modalInstance.destroy();
|
||||
readTip.value = true;
|
||||
},
|
||||
});
|
||||
|
||||
let time = 3;
|
||||
const interval = setInterval(() => {
|
||||
modalInstance.update({
|
||||
okText: time === 0 ? '我知道了, 不再弹出' : `${time}秒后关闭`,
|
||||
okButtonProps: { disabled: time > 0 },
|
||||
});
|
||||
if (time <= 0) {
|
||||
clearInterval(interval);
|
||||
}
|
||||
time--;
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { UnauthorizedException } from '#/api/request';
|
||||
import { dictDataInfo } from '#/api/system/dict/dict-data';
|
||||
import { useDictStore } from '#/store/dict';
|
||||
|
||||
@@ -27,9 +28,16 @@ function fetchAndCacheDictData<T>(
|
||||
// 内部处理了push的逻辑 这里不用push
|
||||
setDictInfo(dictName, resp, formatNumber);
|
||||
})
|
||||
.catch(() => {
|
||||
// 401时 移除字典缓存 下次登录重新获取
|
||||
dictRequestCache.delete(dictName);
|
||||
.catch((error) => {
|
||||
/**
|
||||
* 需要判断是否为401抛出的特定异常 401清除缓存
|
||||
* 其他error清除缓存会导致无限循环调用字典接口 则不做处理
|
||||
*/
|
||||
if (error instanceof UnauthorizedException) {
|
||||
// 401时 移除字典缓存 下次登录重新获取
|
||||
dictRequestCache.delete(dictName);
|
||||
}
|
||||
// 其他不做处理
|
||||
})
|
||||
.finally(() => {
|
||||
// 移除请求状态缓存
|
||||
|
||||
@@ -18,7 +18,7 @@ import { useAuthStore } from '#/store';
|
||||
defineOptions({ name: 'CodeLogin' });
|
||||
|
||||
const loading = ref(false);
|
||||
const CODE_LENGTH = 6;
|
||||
const CODE_LENGTH = 4;
|
||||
|
||||
const tenantInfo = ref<TenantResp>({
|
||||
tenantEnabled: false,
|
||||
@@ -85,8 +85,8 @@ const formSchema = computed((): VbenFormSchema[] => {
|
||||
: $t('authentication.sendCode');
|
||||
return text;
|
||||
},
|
||||
// 验证码长度 在这设置
|
||||
codeLength: 4,
|
||||
// 验证码长度
|
||||
codeLength: CODE_LENGTH,
|
||||
placeholder: $t('authentication.code'),
|
||||
handleSendCode: async () => {
|
||||
const { valid, value } = await form.validateField('phoneNumber');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Component, CSSProperties } from 'vue';
|
||||
|
||||
import { ref } from 'vue';
|
||||
import { markRaw, ref } from 'vue';
|
||||
|
||||
import { DEFAULT_TENANT_ID } from '@vben/constants';
|
||||
import {
|
||||
@@ -69,32 +69,32 @@ export async function handleAuthBinding(source: string) {
|
||||
*/
|
||||
export const accountBindList: BindItem[] = [
|
||||
{
|
||||
avatar: GiteeIcon,
|
||||
avatar: markRaw(GiteeIcon),
|
||||
description: '绑定Gitee账号',
|
||||
source: 'gitee',
|
||||
title: 'Gitee',
|
||||
style: { color: '#c71d23' },
|
||||
},
|
||||
{
|
||||
avatar: GithubOAuthIcon,
|
||||
avatar: markRaw(GithubOAuthIcon),
|
||||
description: '绑定Github账号',
|
||||
source: 'github',
|
||||
title: 'Github',
|
||||
},
|
||||
{
|
||||
avatar: SvgMaxKeyIcon,
|
||||
avatar: markRaw(SvgMaxKeyIcon),
|
||||
description: '绑定MaxKey账号',
|
||||
source: 'maxkey',
|
||||
title: 'MaxKey',
|
||||
},
|
||||
{
|
||||
avatar: SvgTopiamIcon,
|
||||
avatar: markRaw(SvgTopiamIcon),
|
||||
description: '绑定topiam账号',
|
||||
source: 'topiam',
|
||||
title: 'Topiam',
|
||||
},
|
||||
{
|
||||
avatar: SvgWechatIcon,
|
||||
avatar: markRaw(SvgWechatIcon),
|
||||
description: '绑定wechat账号',
|
||||
source: 'wechat',
|
||||
title: 'Wechat',
|
||||
|
||||
@@ -1,174 +1,142 @@
|
||||
<script setup lang="tsx">
|
||||
import type { VxeGridProps } from '@vben/plugins/vxe-table';
|
||||
|
||||
import type { BindItem } from '../../oauth-common';
|
||||
|
||||
import { computed, ref, unref } from 'vue';
|
||||
import type { SocialInfo } from '#/api/system/social/model';
|
||||
|
||||
import { useVbenVxeGrid } from '@vben/plugins/vxe-table';
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { Alert, Avatar, Card, List, ListItem, Modal } from 'ant-design-vue';
|
||||
import { Alert, Avatar, Card, Empty, Modal, Tooltip } from 'ant-design-vue';
|
||||
|
||||
import { authUnbinding } from '#/api';
|
||||
import { socialList } from '#/api/system/social';
|
||||
|
||||
import { accountBindList, handleAuthBinding } from '../../oauth-common';
|
||||
|
||||
function buttonText(item: BindItem) {
|
||||
return item.bound ? '已绑定' : '绑定';
|
||||
interface BindItemWithInfo extends BindItem {
|
||||
info?: SocialInfo;
|
||||
bind?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 已经绑定的平台
|
||||
*/
|
||||
const boundPlatformsList = ref<string[]>([]);
|
||||
const bindList = computed<BindItem[]>(() => {
|
||||
const list = [...accountBindList];
|
||||
const bindList = ref<BindItemWithInfo[]>([]);
|
||||
|
||||
async function loadData() {
|
||||
const resp = await socialList();
|
||||
|
||||
const list: BindItemWithInfo[] = [...accountBindList];
|
||||
list.forEach((item) => {
|
||||
item.bound = !!unref(boundPlatformsList).includes(item.source);
|
||||
/**
|
||||
* 平台转小写
|
||||
*/
|
||||
item.bound = resp
|
||||
.map((social) => social.source.toLowerCase())
|
||||
.includes(item.source.toLowerCase());
|
||||
/**
|
||||
* 添加info信息
|
||||
*/
|
||||
if (item.bound) {
|
||||
item.info = resp.find(
|
||||
(social) => social.source.toLowerCase() === item.source,
|
||||
);
|
||||
}
|
||||
});
|
||||
return list;
|
||||
});
|
||||
|
||||
const gridOptions: VxeGridProps = {
|
||||
columns: [
|
||||
{
|
||||
field: 'source',
|
||||
title: '绑定平台',
|
||||
},
|
||||
{
|
||||
slots: {
|
||||
default: ({ row }) => {
|
||||
return <Avatar src={row.avatar} />;
|
||||
},
|
||||
},
|
||||
field: 'avatar',
|
||||
title: '头像',
|
||||
},
|
||||
{
|
||||
align: 'center',
|
||||
field: 'userName',
|
||||
title: '账号',
|
||||
},
|
||||
{
|
||||
align: 'center',
|
||||
slots: {
|
||||
default: 'action',
|
||||
},
|
||||
title: '操作',
|
||||
},
|
||||
],
|
||||
height: 220,
|
||||
keepSource: true,
|
||||
pagerConfig: {
|
||||
enabled: false,
|
||||
},
|
||||
toolbarConfig: {
|
||||
enabled: false,
|
||||
},
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async () => {
|
||||
const resp = await socialList();
|
||||
/**
|
||||
* 平台转小写
|
||||
* 已经绑定的平台
|
||||
*/
|
||||
boundPlatformsList.value = resp.map((item) =>
|
||||
item.source.toLowerCase(),
|
||||
);
|
||||
return {
|
||||
rows: resp,
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
rowConfig: {
|
||||
isCurrent: false,
|
||||
keyField: 'id',
|
||||
},
|
||||
id: 'profile-bind-table',
|
||||
};
|
||||
|
||||
const [BasicTable, tableApi] = useVbenVxeGrid({
|
||||
gridOptions,
|
||||
});
|
||||
bindList.value = list;
|
||||
}
|
||||
onMounted(loadData);
|
||||
|
||||
/**
|
||||
* 解绑账号
|
||||
*/
|
||||
function handleUnbind(record: Record<string, any>) {
|
||||
function handleUnbind(record: BindItemWithInfo) {
|
||||
if (!record.info) {
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
content: `确定解绑[${record.source}]平台的[${record.userName}]账号吗?`,
|
||||
content: `确定解绑[${record.source}]平台的[${record.info.userName}]账号吗?`,
|
||||
async onOk() {
|
||||
await authUnbinding(record.id);
|
||||
await tableApi.reload();
|
||||
await authUnbinding(record.info!.id);
|
||||
await loadData();
|
||||
},
|
||||
title: '提示',
|
||||
type: 'warning',
|
||||
});
|
||||
}
|
||||
|
||||
const simpleImage = Empty.PRESENTED_IMAGE_SIMPLE;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-[16px]">
|
||||
<BasicTable>
|
||||
<template #action="{ row }">
|
||||
<a-button type="link" @click="handleUnbind(row)">解绑</a-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
<div class="pb-3">
|
||||
<List
|
||||
:data-source="bindList"
|
||||
:grid="{ gutter: 8, xs: 1, sm: 1, md: 2, lg: 3, xl: 3, xxl: 3 }"
|
||||
<div class="flex flex-col gap-4 pb-4">
|
||||
<div
|
||||
v-if="bindList.length > 0"
|
||||
class="grid grid-cols-1 gap-4 lg:grid-cols-2 2xl:grid-cols-3"
|
||||
>
|
||||
<Card
|
||||
class="transition-shadow duration-300 hover:shadow-md"
|
||||
v-for="item in bindList"
|
||||
:key="item.source"
|
||||
>
|
||||
<template #renderItem="{ item }">
|
||||
<ListItem>
|
||||
<Card>
|
||||
<div class="flex w-full items-center gap-4">
|
||||
<component
|
||||
:is="item.avatar"
|
||||
v-if="item.avatar"
|
||||
:style="item?.style ?? {}"
|
||||
class="size-[40px]"
|
||||
/>
|
||||
<div class="flex flex-1 items-center justify-between">
|
||||
<div class="flex flex-col">
|
||||
<h4
|
||||
class="mb-[4px] text-[14px] text-black/85 dark:text-white/85"
|
||||
>
|
||||
{{ item.title }}
|
||||
</h4>
|
||||
<span class="text-black/45 dark:text-white/45">
|
||||
{{ item.description }}
|
||||
</span>
|
||||
</div>
|
||||
<a-button
|
||||
:disabled="item.bound"
|
||||
size="small"
|
||||
type="link"
|
||||
@click="handleAuthBinding(item.source)"
|
||||
>
|
||||
{{ buttonText(item) }}
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</ListItem>
|
||||
</template>
|
||||
</List>
|
||||
<Alert message="说明" type="info">
|
||||
<template #description>
|
||||
<p>
|
||||
需要添加第三方账号在
|
||||
<span class="font-bold">
|
||||
apps\web-antd\src\views\_core\oauth-common.ts
|
||||
</span>
|
||||
中accountBindList按模板添加
|
||||
</p>
|
||||
</template>
|
||||
</Alert>
|
||||
<div class="flex w-full items-center gap-4">
|
||||
<component
|
||||
:is="item.avatar"
|
||||
v-if="item.avatar"
|
||||
:style="item?.style ?? {}"
|
||||
class="size-[40px]"
|
||||
/>
|
||||
<div class="flex flex-1 items-center justify-between">
|
||||
<div class="flex flex-col">
|
||||
<h4 class="mb-[4px] text-[14px] text-black/85 dark:text-white/85">
|
||||
{{ item.title }}
|
||||
</h4>
|
||||
<span class="text-black/45 dark:text-white/45">
|
||||
<template v-if="!item.bound">
|
||||
{{ item.description }}
|
||||
</template>
|
||||
<template v-if="item.bound && item.info">
|
||||
<Tooltip>
|
||||
<template #title>
|
||||
<div class="flex flex-col items-center gap-2 p-2">
|
||||
<Avatar :size="36" :src="item.info.avatar" />
|
||||
<div>绑定时间: {{ item.info.createTime }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="cursor-pointer">
|
||||
已绑定: {{ item.info.nickName }}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</template>
|
||||
</span>
|
||||
</div>
|
||||
<!-- TODO: 这里有优化空间? -->
|
||||
<a-button
|
||||
size="small"
|
||||
:type="item.bound ? 'default' : 'link'"
|
||||
@click="
|
||||
item.bound ? handleUnbind(item) : handleAuthBinding(item.source)
|
||||
"
|
||||
>
|
||||
{{ item.bound ? '取消绑定' : '绑定' }}
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
<div
|
||||
v-if="bindList.length === 0"
|
||||
class="flex items-center justify-center rounded-lg border py-4"
|
||||
>
|
||||
<Empty :image="simpleImage" description="暂无可绑定的第三方账户" />
|
||||
</div>
|
||||
<Alert message="说明" type="info">
|
||||
<template #description>
|
||||
<p>
|
||||
需要添加第三方账号在
|
||||
<span class="font-bold">
|
||||
apps\web-antd\src\views\_core\oauth-common.ts
|
||||
</span>
|
||||
中accountBindList按模板添加
|
||||
</p>
|
||||
</template>
|
||||
</Alert>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@ import type { AuthApi } from '#/api';
|
||||
import { onMounted } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
import { DEFAULT_HOME_PATH, DEFAULT_TENANT_ID } from '@vben/constants';
|
||||
import { DEFAULT_TENANT_ID } from '@vben/constants';
|
||||
import { preferences } from '@vben/preferences';
|
||||
import { useAccessStore } from '@vben/stores';
|
||||
|
||||
import { message } from 'ant-design-vue';
|
||||
@@ -70,7 +71,7 @@ onMounted(async () => {
|
||||
// 500 你还没有绑定第三方账号,绑定后才可以登录!
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
router.push(DEFAULT_HOME_PATH);
|
||||
router.push(preferences.app.defaultHomePath);
|
||||
}, 1500);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -51,7 +51,8 @@ export const columns: VxeGridProps['columns'] = [
|
||||
fixed: 'right',
|
||||
slots: { default: 'action' },
|
||||
title: '操作',
|
||||
width: 180,
|
||||
resizable: false,
|
||||
width: 'auto',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -60,7 +60,8 @@ export const columns: VxeGridProps['columns'] = [
|
||||
fixed: 'right',
|
||||
slots: { default: 'action' },
|
||||
title: '操作',
|
||||
width: 180,
|
||||
resizable: false,
|
||||
width: 'auto',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -79,6 +79,7 @@ export const columns: VxeGridProps['columns'] = [
|
||||
fixed: 'right',
|
||||
slots: { default: 'action' },
|
||||
title: '操作',
|
||||
width: 120,
|
||||
resizable: false,
|
||||
width: 'auto',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -86,6 +86,7 @@ export const columns: VxeGridProps['columns'] = [
|
||||
fixed: 'right',
|
||||
slots: { default: 'action' },
|
||||
title: '操作',
|
||||
width: 120,
|
||||
resizable: false,
|
||||
width: 'auto',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -95,7 +95,8 @@ export const columns: VxeGridProps['columns'] = [
|
||||
fixed: 'right',
|
||||
slots: { default: 'action' },
|
||||
title: '操作',
|
||||
width: 180,
|
||||
resizable: false,
|
||||
width: 'auto',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -71,7 +71,8 @@ export const columns: VxeGridProps['columns'] = [
|
||||
fixed: 'right',
|
||||
slots: { default: 'action' },
|
||||
title: '操作',
|
||||
width: 180,
|
||||
resizable: false,
|
||||
width: 'auto',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -30,7 +30,6 @@ export const columns: VxeGridProps['columns'] = [
|
||||
field: 'deptName',
|
||||
title: '部门名称',
|
||||
treeNode: true,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
field: 'deptCategory',
|
||||
@@ -39,11 +38,9 @@ export const columns: VxeGridProps['columns'] = [
|
||||
{
|
||||
field: 'orderNum',
|
||||
title: '排序',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
width: 180,
|
||||
title: '状态',
|
||||
slots: {
|
||||
default: ({ row }) => {
|
||||
@@ -60,7 +57,8 @@ export const columns: VxeGridProps['columns'] = [
|
||||
fixed: 'right',
|
||||
slots: { default: 'action' },
|
||||
title: '操作',
|
||||
width: 200,
|
||||
resizable: false,
|
||||
width: 'auto',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -97,6 +95,7 @@ export const drawerSchema: FormSchemaGetter = () => [
|
||||
fieldName: 'orderNum',
|
||||
label: '显示排序',
|
||||
rules: 'required',
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
|
||||
6
apps/web-antd/src/views/system/dict/data.vue
Normal file
6
apps/web-antd/src/views/system/dict/data.vue
Normal file
@@ -0,0 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
ele版本会使用这个文件 只是为了不报错`未找到对应组件`才新建的这个文件
|
||||
无实际意义
|
||||
</div>
|
||||
</template>
|
||||
@@ -45,7 +45,8 @@ export const columns: VxeGridProps['columns'] = [
|
||||
fixed: 'right',
|
||||
slots: { default: 'action' },
|
||||
title: '操作',
|
||||
width: 180,
|
||||
resizable: false,
|
||||
width: 'auto',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -98,6 +99,7 @@ export const drawerSchema: FormSchemaGetter = () => [
|
||||
fieldName: 'dictSort',
|
||||
label: '显示排序',
|
||||
rules: 'required',
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
component: 'Textarea',
|
||||
|
||||
@@ -77,7 +77,7 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({
|
||||
|
||||
const { dictCode, dictType } = drawerApi.getData() as DrawerProps;
|
||||
isUpdate.value = !!dictCode;
|
||||
formApi.setFieldValue('dictType', dictType);
|
||||
await formApi.setFieldValue('dictType', dictType);
|
||||
|
||||
if (dictCode && isUpdate.value) {
|
||||
const record = await dictDetailInfo(dictCode);
|
||||
|
||||
@@ -39,7 +39,8 @@ export const columns: VxeGridProps['columns'] = [
|
||||
fixed: 'right',
|
||||
slots: { default: 'action' },
|
||||
title: '操作',
|
||||
width: 180,
|
||||
resizable: false,
|
||||
width: 'auto',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
281
apps/web-antd/src/views/system/dict/type/index-refactor.vue
Normal file
281
apps/web-antd/src/views/system/dict/type/index-refactor.vue
Normal file
@@ -0,0 +1,281 @@
|
||||
<!-- 使用vxe实现成本最小 且自带虚拟滚动 -->
|
||||
<script setup lang="ts">
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
import type { DictType } from '#/api/system/dict/dict-type-model';
|
||||
|
||||
import { h, ref, shallowRef, watch } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
import { cn } from '@vben/utils';
|
||||
|
||||
import {
|
||||
DeleteOutlined,
|
||||
EditOutlined,
|
||||
ExportOutlined,
|
||||
PlusOutlined,
|
||||
SyncOutlined,
|
||||
} from '@ant-design/icons-vue';
|
||||
import {
|
||||
Alert,
|
||||
Input,
|
||||
Modal,
|
||||
Popconfirm,
|
||||
Space,
|
||||
Tooltip,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import {
|
||||
dictTypeExport,
|
||||
dictTypeList,
|
||||
dictTypeRemove,
|
||||
refreshDictTypeCache,
|
||||
} from '#/api/system/dict/dict-type';
|
||||
import { commonDownloadExcel } from '#/utils/file/download';
|
||||
|
||||
import { emitter } from '../mitt';
|
||||
import dictTypeModal from './dict-type-modal.vue';
|
||||
|
||||
const tableAllData = shallowRef<DictType[]>([]);
|
||||
const gridOptions: VxeGridProps = {
|
||||
columns: [
|
||||
{
|
||||
title: 'name',
|
||||
field: 'render',
|
||||
slots: { default: 'render' },
|
||||
},
|
||||
],
|
||||
height: 'auto',
|
||||
keepSource: true,
|
||||
pagerConfig: {
|
||||
enabled: false,
|
||||
},
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async () => {
|
||||
const resp = await dictTypeList();
|
||||
|
||||
total.value = resp.total;
|
||||
tableAllData.value = resp.rows;
|
||||
return resp;
|
||||
},
|
||||
},
|
||||
},
|
||||
rowConfig: {
|
||||
keyField: 'dictId',
|
||||
// 高亮当前行
|
||||
isCurrent: true,
|
||||
},
|
||||
cellConfig: {
|
||||
height: 60,
|
||||
},
|
||||
showHeader: false,
|
||||
toolbarConfig: {
|
||||
enabled: false,
|
||||
},
|
||||
// 开启虚拟滚动
|
||||
scrollY: {
|
||||
enabled: false,
|
||||
gt: 0,
|
||||
},
|
||||
rowClassName: 'cursor-pointer',
|
||||
id: 'system-dict-data-index',
|
||||
};
|
||||
|
||||
const [BasicTable, tableApi] = useVbenVxeGrid({
|
||||
gridOptions,
|
||||
gridEvents: {
|
||||
cellClick: ({ row }) => {
|
||||
handleRowClick(row);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const [DictTypeModal, modalApi] = useVbenModal({
|
||||
connectedComponent: dictTypeModal,
|
||||
});
|
||||
|
||||
function handleAdd() {
|
||||
modalApi.setData({});
|
||||
modalApi.open();
|
||||
}
|
||||
|
||||
async function handleEdit(record: DictType) {
|
||||
modalApi.setData({ id: record.dictId });
|
||||
modalApi.open();
|
||||
}
|
||||
|
||||
async function handleDelete(row: DictType) {
|
||||
await dictTypeRemove([row.dictId]);
|
||||
await tableApi.query();
|
||||
}
|
||||
|
||||
async function handleReset() {
|
||||
currentRowId.value = '';
|
||||
searchValue.value = '';
|
||||
await tableApi.query();
|
||||
}
|
||||
|
||||
function handleDownloadExcel() {
|
||||
commonDownloadExcel(dictTypeExport, '字典类型数据');
|
||||
}
|
||||
|
||||
function handleRefreshCache() {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确认刷新字典类型缓存吗?',
|
||||
okButtonProps: {
|
||||
danger: true,
|
||||
},
|
||||
onOk: async () => {
|
||||
await refreshDictTypeCache();
|
||||
await tableApi.query();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const lastDictType = ref<string>('');
|
||||
const currentRowId = ref<null | number | string>(null);
|
||||
function handleRowClick(row: DictType) {
|
||||
if (lastDictType.value === row.dictType) {
|
||||
return;
|
||||
}
|
||||
currentRowId.value = row.dictId;
|
||||
emitter.emit('rowClick', row.dictType);
|
||||
}
|
||||
|
||||
const searchValue = ref('');
|
||||
const total = ref(0);
|
||||
watch(searchValue, (value) => {
|
||||
if (!tableApi) {
|
||||
return;
|
||||
}
|
||||
if (value) {
|
||||
const names = tableAllData.value.filter((item) =>
|
||||
item.dictName.includes(searchValue.value),
|
||||
);
|
||||
const types = tableAllData.value.filter((item) =>
|
||||
item.dictType.includes(searchValue.value),
|
||||
);
|
||||
const filtered = [...new Set([...names, ...types])];
|
||||
total.value = filtered.length;
|
||||
tableApi.grid.loadData(filtered);
|
||||
} else {
|
||||
total.value = tableAllData.value.length;
|
||||
tableApi.grid.loadData(tableAllData.value);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="
|
||||
cn(
|
||||
'bg-background flex max-h-[100vh] w-[360px] flex-col overflow-y-hidden',
|
||||
'rounded-lg',
|
||||
'dict-type-card',
|
||||
)
|
||||
"
|
||||
>
|
||||
<div :class="cn('flex items-center justify-between', 'border-b px-4 py-2')">
|
||||
<span class="font-semibold">字典项列表</span>
|
||||
<Space>
|
||||
<Tooltip title="刷新缓存">
|
||||
<a-button
|
||||
v-access:code="['system:dict:edit']"
|
||||
:icon="h(SyncOutlined)"
|
||||
@click="handleRefreshCache"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip :title="$t('pages.common.export')">
|
||||
<a-button
|
||||
v-access:code="['system:dict:export']"
|
||||
:icon="h(ExportOutlined)"
|
||||
@click="handleDownloadExcel"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip :title="$t('pages.common.add')">
|
||||
<a-button
|
||||
v-access:code="['system:dict:add']"
|
||||
:icon="h(PlusOutlined)"
|
||||
@click="handleAdd"
|
||||
/>
|
||||
</Tooltip>
|
||||
</Space>
|
||||
</div>
|
||||
<div class="flex flex-1 flex-col overflow-y-hidden p-4">
|
||||
<Alert
|
||||
class="mb-4"
|
||||
show-icon
|
||||
message="如果你的数据量大 自行开启虚拟滚动"
|
||||
/>
|
||||
<Input
|
||||
placeholder="搜索字典项名称/类型"
|
||||
v-model:value="searchValue"
|
||||
allow-clear
|
||||
>
|
||||
<template #addonAfter>
|
||||
<Tooltip title="重置/刷新">
|
||||
<SyncOutlined
|
||||
v-access:code="['system:dict:edit']"
|
||||
@click="handleReset"
|
||||
/>
|
||||
</Tooltip>
|
||||
</template>
|
||||
</Input>
|
||||
<BasicTable class="flex-1 overflow-hidden">
|
||||
<template #render="{ row: item }">
|
||||
<div :class="cn('flex items-center justify-between px-2 py-2')">
|
||||
<div class="flex flex-col items-baseline overflow-hidden">
|
||||
<span class="font-medium">{{ item.dictName }}</span>
|
||||
<div
|
||||
class="max-w-full overflow-hidden text-ellipsis whitespace-nowrap"
|
||||
>
|
||||
{{ item.dictType }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-3 text-[17px]">
|
||||
<EditOutlined
|
||||
class="text-primary"
|
||||
v-access:code="['system:dict:edit']"
|
||||
@click.stop="handleEdit(item)"
|
||||
/>
|
||||
<Popconfirm
|
||||
placement="left"
|
||||
:title="`确认删除 [${item.dictName}]?`"
|
||||
@confirm="handleDelete(item)"
|
||||
>
|
||||
<DeleteOutlined
|
||||
v-access:code="['system:dict:remove']"
|
||||
class="text-destructive"
|
||||
@click.stop=""
|
||||
/>
|
||||
</Popconfirm>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</div>
|
||||
<div class="border-t px-4 py-3">共 {{ total }} 条数据</div>
|
||||
<DictTypeModal @reload="tableApi.query()" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.dict-type-card {
|
||||
.vxe-grid {
|
||||
padding: 12px 0 0;
|
||||
|
||||
.vxe-body--row {
|
||||
&.row--current {
|
||||
// 选中行背景色
|
||||
background-color: hsl(var(--accent-hover)) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-alert {
|
||||
padding: 6px 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -145,7 +145,8 @@ export const columns: VxeGridProps['columns'] = [
|
||||
fixed: 'right',
|
||||
slots: { default: 'action' },
|
||||
title: '操作',
|
||||
width: 200,
|
||||
resizable: false,
|
||||
width: 'auto',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -218,6 +219,7 @@ export const drawerSchema: FormSchemaGetter = () => [
|
||||
fieldName: 'orderNum',
|
||||
help: '排序, 数字越小越靠前',
|
||||
label: '显示排序',
|
||||
defaultValue: 0,
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
@@ -236,6 +238,7 @@ export const drawerSchema: FormSchemaGetter = () => [
|
||||
if (model.isFrame !== '0') {
|
||||
return z
|
||||
.string({ message: '请输入路由地址' })
|
||||
.min(1, '请输入路由地址')
|
||||
.refine((val) => !val.startsWith('/'), {
|
||||
message: '路由地址不需要带/',
|
||||
});
|
||||
|
||||
@@ -4,16 +4,17 @@ import type { VbenFormProps } from '@vben/common-ui';
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
import type { Menu } from '#/api/system/menu/model';
|
||||
|
||||
import { computed } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { useAccess } from '@vben/access';
|
||||
import { Fallback, Page, useVbenDrawer } from '@vben/common-ui';
|
||||
import { eachTree, getVxePopupContainer } from '@vben/utils';
|
||||
import { $t } from '@vben/locales';
|
||||
import { eachTree, getVxePopupContainer, treeToList } from '@vben/utils';
|
||||
|
||||
import { Popconfirm, Space } from 'ant-design-vue';
|
||||
import { Popconfirm, Space, Switch, Tooltip } from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { menuList, menuRemove } from '#/api/system/menu';
|
||||
import { menuCascadeRemove, menuList, menuRemove } from '#/api/system/menu';
|
||||
|
||||
import { columns, querySchema } from './data';
|
||||
import menuDrawer from './menu-drawer.vue';
|
||||
@@ -67,6 +68,8 @@ const gridOptions: VxeGridProps = {
|
||||
rowField: 'menuId',
|
||||
// 自动转换为tree 由vxe处理 无需手动转换
|
||||
transform: true,
|
||||
// 刷新接口后 记录展开行的情况
|
||||
reserve: true,
|
||||
},
|
||||
id: 'system-menu-index',
|
||||
};
|
||||
@@ -111,11 +114,41 @@ async function handleEdit(record: Menu) {
|
||||
drawerApi.open();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否级联删除
|
||||
*/
|
||||
const cascadingDeletion = ref(false);
|
||||
async function handleDelete(row: Menu) {
|
||||
await menuRemove([row.menuId]);
|
||||
if (cascadingDeletion.value) {
|
||||
// 级联删除
|
||||
const menuAndChildren: Menu[] = treeToList([row], { id: 'menuId' });
|
||||
await menuCascadeRemove(menuAndChildren.map((item) => item.menuId));
|
||||
} else {
|
||||
// 单删除
|
||||
await menuRemove([row.menuId]);
|
||||
}
|
||||
await tableApi.query();
|
||||
}
|
||||
|
||||
function removeConfirmTitle(row: Menu) {
|
||||
const menuName = $t(row.menuName);
|
||||
if (!cascadingDeletion.value) {
|
||||
return `是否确认删除 [${menuName}] ?`;
|
||||
}
|
||||
const menuAndChildren = treeToList([row], { id: 'menuId' });
|
||||
if (menuAndChildren.length === 1) {
|
||||
return `是否确认删除 [${menuName}] ?`;
|
||||
}
|
||||
return `是否确认删除 [${menuName}] 及 [${menuAndChildren.length - 1}]个子项目 ?`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑/添加成功后刷新表格
|
||||
*/
|
||||
async function afterEditOrAdd() {
|
||||
tableApi.query();
|
||||
}
|
||||
|
||||
/**
|
||||
* 全部展开/折叠
|
||||
* @param expand 是否展开
|
||||
@@ -128,6 +161,9 @@ function setExpandOrCollapse(expand: boolean) {
|
||||
/**
|
||||
* 与后台逻辑相同
|
||||
* 只有租户管理和超级管理能访问菜单管理
|
||||
* 注意: 只有超管才能对菜单进行`增删改`操作
|
||||
* 注意: 只有超管才能对菜单进行`增删改`操作
|
||||
* 注意: 只有超管才能对菜单进行`增删改`操作
|
||||
*/
|
||||
const { hasAccessByRoles } = useAccess();
|
||||
const isAdmin = computed(() => {
|
||||
@@ -140,6 +176,16 @@ const isAdmin = computed(() => {
|
||||
<BasicTable table-title="菜单列表" table-title-help="双击展开/收起子菜单">
|
||||
<template #toolbar-tools>
|
||||
<Space>
|
||||
<Tooltip title="删除菜单以及子菜单">
|
||||
<div
|
||||
v-access:role="['superadmin']"
|
||||
v-access:code="['system:menu:remove']"
|
||||
class="flex items-center"
|
||||
>
|
||||
<span class="mr-2 text-sm text-[#666666]">级联删除</span>
|
||||
<Switch v-model:checked="cascadingDeletion" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
<a-button @click="setExpandOrCollapse(false)">
|
||||
{{ $t('pages.common.collapse') }}
|
||||
</a-button>
|
||||
@@ -149,6 +195,7 @@ const isAdmin = computed(() => {
|
||||
<a-button
|
||||
type="primary"
|
||||
v-access:code="['system:menu:add']"
|
||||
v-access:role="['superadmin']"
|
||||
@click="handleAdd"
|
||||
>
|
||||
{{ $t('pages.common.add') }}
|
||||
@@ -159,6 +206,7 @@ const isAdmin = computed(() => {
|
||||
<Space>
|
||||
<ghost-button
|
||||
v-access:code="['system:menu:edit']"
|
||||
v-access:role="['superadmin']"
|
||||
@click="handleEdit(row)"
|
||||
>
|
||||
{{ $t('pages.common.edit') }}
|
||||
@@ -168,6 +216,7 @@ const isAdmin = computed(() => {
|
||||
v-if="row.menuType !== 'F'"
|
||||
class="btn-success"
|
||||
v-access:code="['system:menu:add']"
|
||||
v-access:role="['superadmin']"
|
||||
@click="handleSubAdd(row)"
|
||||
>
|
||||
{{ $t('pages.common.add') }}
|
||||
@@ -175,12 +224,13 @@ const isAdmin = computed(() => {
|
||||
<Popconfirm
|
||||
:get-popup-container="getVxePopupContainer"
|
||||
placement="left"
|
||||
title="确认删除?"
|
||||
:title="removeConfirmTitle(row)"
|
||||
@confirm="handleDelete(row)"
|
||||
>
|
||||
<ghost-button
|
||||
danger
|
||||
v-access:code="['system:menu:remove']"
|
||||
v-access:role="['superadmin']"
|
||||
@click.stop=""
|
||||
>
|
||||
{{ $t('pages.common.delete') }}
|
||||
@@ -189,7 +239,7 @@ const isAdmin = computed(() => {
|
||||
</Space>
|
||||
</template>
|
||||
</BasicTable>
|
||||
<MenuDrawer @reload="tableApi.query()" />
|
||||
<MenuDrawer @reload="afterEditOrAdd" />
|
||||
</Page>
|
||||
<Fallback v-else description="您没有菜单管理的访问权限" status="403" />
|
||||
</template>
|
||||
|
||||
@@ -69,7 +69,8 @@ export const columns: VxeGridProps['columns'] = [
|
||||
fixed: 'right',
|
||||
slots: { default: 'action' },
|
||||
title: '操作',
|
||||
width: 180,
|
||||
resizable: false,
|
||||
width: 'auto',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -28,7 +28,10 @@ export const querySchema: FormSchemaGetter = () => [
|
||||
{
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
options: getDictOptions(DictEnum.SYS_YES_NO),
|
||||
options: [
|
||||
{ label: '是', value: '0' },
|
||||
{ label: '否', value: '1' },
|
||||
],
|
||||
},
|
||||
fieldName: 'status',
|
||||
label: '是否默认',
|
||||
@@ -81,7 +84,8 @@ export const columns: VxeGridProps['columns'] = [
|
||||
fixed: 'right',
|
||||
slots: { default: 'action' },
|
||||
title: '操作',
|
||||
width: 180,
|
||||
resizable: false,
|
||||
width: 'auto',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -131,6 +131,8 @@ const { hasAccessByCodes } = useAccess();
|
||||
v-model:value="row.status"
|
||||
:api="() => ossConfigChangeStatus(row)"
|
||||
:disabled="!hasAccessByCodes(['system:ossConfig:edit'])"
|
||||
checked-text="是"
|
||||
un-checked-text="否"
|
||||
@reload="tableApi.query()"
|
||||
/>
|
||||
</template>
|
||||
|
||||
11
apps/web-antd/src/views/system/oss/config.vue
Normal file
11
apps/web-antd/src/views/system/oss/config.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<!--
|
||||
后端版本>=5.4.0 这个从本地路由变为从后台返回
|
||||
未修改文件名 而是新加了这个文件
|
||||
-->
|
||||
<script setup lang="ts">
|
||||
import OssConfigPage from '#/views/system/oss-config/index.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<OssConfigPage />
|
||||
</template>
|
||||
2
apps/web-antd/src/views/system/oss/constant.ts
Normal file
2
apps/web-antd/src/views/system/oss/constant.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
/** 支持的图片列表 */
|
||||
export const supportImageList = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
|
||||
@@ -69,12 +69,7 @@ export const columns: VxeGridProps['columns'] = [
|
||||
fixed: 'right',
|
||||
slots: { default: 'action' },
|
||||
title: '操作',
|
||||
width: 180,
|
||||
resizable: false,
|
||||
width: 'auto',
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* 图片加载失败的fallback
|
||||
*/
|
||||
export const fallbackImageBase64 =
|
||||
'data:image/png;base64,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';
|
||||
|
||||
1
apps/web-antd/src/views/system/oss/fallback-image.txt
Normal file
1
apps/web-antd/src/views/system/oss/fallback-image.txt
Normal file
@@ -0,0 +1 @@
|
||||
data:image/png;base64,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
|
||||
@@ -31,7 +31,11 @@ const [BasicModal, modalApi] = useVbenModal({
|
||||
title="文件上传"
|
||||
>
|
||||
<div class="flex flex-col gap-4">
|
||||
<FileUpload v-model:value="fileList" :enable-drag-upload="true" />
|
||||
<FileUpload
|
||||
v-model:value="fileList"
|
||||
:enable-drag-upload="true"
|
||||
:max-count="3"
|
||||
/>
|
||||
</div>
|
||||
</BasicModal>
|
||||
</template>
|
||||
|
||||
@@ -31,7 +31,7 @@ const [BasicModal, modalApi] = useVbenModal({
|
||||
title="图片上传"
|
||||
>
|
||||
<div class="flex flex-col gap-4">
|
||||
<ImageUpload v-model:value="fileList" />
|
||||
<ImageUpload v-model:value="fileList" :max-count="3" />
|
||||
</div>
|
||||
</BasicModal>
|
||||
</template>
|
||||
|
||||
@@ -33,7 +33,9 @@ import { ossDownload, ossList, ossRemove } from '#/api/system/oss';
|
||||
import { calculateFileSize } from '#/utils/file';
|
||||
import { downloadByData } from '#/utils/file/download';
|
||||
|
||||
import { columns, fallbackImageBase64, querySchema } from './data';
|
||||
import { supportImageList } from './constant';
|
||||
import { columns, querySchema } from './data';
|
||||
import fallbackImageBase64 from './fallback-image.txt?raw';
|
||||
import fileUploadModal from './file-upload-modal.vue';
|
||||
import imageUploadModal from './image-upload-modal.vue';
|
||||
|
||||
@@ -154,7 +156,7 @@ function handleMultiDelete() {
|
||||
|
||||
const router = useRouter();
|
||||
function handleToSettings() {
|
||||
router.push('/system/oss-config');
|
||||
router.push('/system/oss-config/index');
|
||||
}
|
||||
|
||||
const preview = ref(false);
|
||||
@@ -163,10 +165,32 @@ onMounted(async () => {
|
||||
preview.value = previewStr === 'true';
|
||||
});
|
||||
|
||||
/**
|
||||
* 根据拓展名判断是否是图片
|
||||
* @param ext 拓展名
|
||||
*/
|
||||
function isImageFile(ext: string) {
|
||||
const supportList = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
|
||||
return supportList.some((item) => ext.toLocaleLowerCase().includes(item));
|
||||
return supportImageList.some((item) =>
|
||||
ext.toLocaleLowerCase().includes(item),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否是pdf文件
|
||||
* @param ext 扩展名
|
||||
*/
|
||||
function isPdfFile(ext: string) {
|
||||
return ext.toLocaleLowerCase().includes('pdf');
|
||||
}
|
||||
|
||||
/**
|
||||
* pdf预览 使用浏览器接管
|
||||
* @param url 文件地址
|
||||
*/
|
||||
function pdfPreview(url: string) {
|
||||
window.open(url);
|
||||
}
|
||||
|
||||
const [ImageUploadModal, imageUploadApi] = useVbenModal({
|
||||
connectedComponent: imageUploadModal,
|
||||
});
|
||||
@@ -230,6 +254,12 @@ const [FileUploadModal, fileUploadApi] = useVbenModal({
|
||||
</div>
|
||||
</template>
|
||||
</Image>
|
||||
<!-- pdf预览 使用浏览器开新窗口 -->
|
||||
<span
|
||||
v-else-if="preview && isPdfFile(row.url)"
|
||||
class="icon-[vscode-icons--file-type-pdf2] size-10 cursor-pointer"
|
||||
@click.stop="pdfPreview(row.url)"
|
||||
></span>
|
||||
<span v-else>{{ row.url }}</span>
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
|
||||
@@ -65,7 +65,8 @@ export const columns: VxeGridProps['columns'] = [
|
||||
fixed: 'right',
|
||||
slots: { default: 'action' },
|
||||
title: '操作',
|
||||
width: 180,
|
||||
resizable: false,
|
||||
width: 'auto',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -110,6 +111,7 @@ export const drawerSchema: FormSchemaGetter = () => [
|
||||
fieldName: 'postSort',
|
||||
label: '岗位排序',
|
||||
rules: 'required',
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
component: 'RadioGroup',
|
||||
|
||||
@@ -8,6 +8,7 @@ import { addFullName, cloneDeep } from '@vben/utils';
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import { postAdd, postInfo, postUpdate } from '#/api/system/post';
|
||||
import { getDeptTree } from '#/api/system/user';
|
||||
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
|
||||
|
||||
import { drawerSchema } from './data';
|
||||
|
||||
@@ -50,8 +51,16 @@ async function setupDeptSelect() {
|
||||
]);
|
||||
}
|
||||
|
||||
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
|
||||
{
|
||||
initializedGetter: defaultFormValueGetter(formApi),
|
||||
currentGetter: defaultFormValueGetter(formApi),
|
||||
},
|
||||
);
|
||||
|
||||
const [BasicDrawer, drawerApi] = useVbenDrawer({
|
||||
onCancel: handleCancel,
|
||||
onBeforeClose,
|
||||
onClosed: handleClosed,
|
||||
onConfirm: handleConfirm,
|
||||
async onOpenChange(isOpen) {
|
||||
if (!isOpen) {
|
||||
@@ -67,31 +76,33 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({
|
||||
const record = await postInfo(id);
|
||||
await formApi.setValues(record);
|
||||
}
|
||||
await markInitialized();
|
||||
drawerApi.drawerLoading(false);
|
||||
},
|
||||
});
|
||||
|
||||
async function handleConfirm() {
|
||||
try {
|
||||
drawerApi.drawerLoading(true);
|
||||
drawerApi.lock(true);
|
||||
const { valid } = await formApi.validate();
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
const data = cloneDeep(await formApi.getValues());
|
||||
await (isUpdate.value ? postUpdate(data) : postAdd(data));
|
||||
resetInitialized();
|
||||
emit('reload');
|
||||
await handleCancel();
|
||||
drawerApi.close();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
drawerApi.drawerLoading(false);
|
||||
drawerApi.lock(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCancel() {
|
||||
drawerApi.close();
|
||||
async function handleClosed() {
|
||||
await formApi.resetForm();
|
||||
resetInitialized();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ export const columns: VxeGridProps['columns'] = [
|
||||
fixed: 'right',
|
||||
slots: { default: 'action' },
|
||||
title: '操作',
|
||||
width: 180,
|
||||
resizable: false,
|
||||
width: 'auto',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -17,6 +17,7 @@ const emit = defineEmits<{ reload: [] }>();
|
||||
const [BasicDrawer, drawerApi] = useVbenDrawer({
|
||||
onConfirm: handleSubmit,
|
||||
onCancel: handleReset,
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
11
apps/web-antd/src/views/system/role/authUser.vue
Normal file
11
apps/web-antd/src/views/system/role/authUser.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<!--
|
||||
后端版本>=5.4.0 这个从本地路由变为从后台返回
|
||||
未修改文件名 而是新加了这个文件
|
||||
-->
|
||||
<script setup lang="ts">
|
||||
import RoleAssignPage from '#/views/system/role-assign/index.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RoleAssignPage />
|
||||
</template>
|
||||
@@ -94,7 +94,8 @@ export const columns: VxeGridProps['columns'] = [
|
||||
fixed: 'right',
|
||||
slots: { default: 'action' },
|
||||
title: '操作',
|
||||
width: 180,
|
||||
resizable: false,
|
||||
width: 'auto',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -126,6 +127,7 @@ export const drawerSchema: FormSchemaGetter = () => [
|
||||
fieldName: 'roleSort',
|
||||
label: '角色排序',
|
||||
rules: 'required',
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
component: 'Select',
|
||||
|
||||
@@ -11,14 +11,7 @@ import { useAccess } from '@vben/access';
|
||||
import { Page, useVbenDrawer, useVbenModal } from '@vben/common-ui';
|
||||
import { getVxePopupContainer } from '@vben/utils';
|
||||
|
||||
import {
|
||||
Dropdown,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Modal,
|
||||
Popconfirm,
|
||||
Space,
|
||||
} from 'ant-design-vue';
|
||||
import { Modal, Popconfirm, Space } from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid, vxeCheckboxChecked } from '#/adapter/vxe-table';
|
||||
import {
|
||||
@@ -142,7 +135,7 @@ function handleAuthEdit(record: Role) {
|
||||
|
||||
const router = useRouter();
|
||||
function handleAssignRole(record: Role) {
|
||||
router.push(`/system/role-assign/${record.roleId}`);
|
||||
router.push(`/system/role-auth/user/${record.roleId}`);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -200,6 +193,18 @@ function handleAssignRole(record: Role) {
|
||||
>
|
||||
{{ $t('pages.common.edit') }}
|
||||
</ghost-button>
|
||||
<ghost-button
|
||||
v-access:code="['system:role:edit']"
|
||||
@click.stop="handleAuthEdit(row)"
|
||||
>
|
||||
权限
|
||||
</ghost-button>
|
||||
<ghost-button
|
||||
v-access:code="['system:role:edit']"
|
||||
@click.stop="handleAssignRole(row)"
|
||||
>
|
||||
分配
|
||||
</ghost-button>
|
||||
<Popconfirm
|
||||
:get-popup-container="getVxePopupContainer"
|
||||
placement="left"
|
||||
@@ -215,21 +220,6 @@ function handleAssignRole(record: Role) {
|
||||
</ghost-button>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
<Dropdown placement="bottomRight">
|
||||
<template #overlay>
|
||||
<Menu>
|
||||
<MenuItem key="1" @click="handleAuthEdit(row)">
|
||||
数据权限
|
||||
</MenuItem>
|
||||
<MenuItem key="2" @click="handleAssignRole(row)">
|
||||
分配用户
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</template>
|
||||
<a-button size="small" type="link">
|
||||
{{ $t('pages.common.more') }}
|
||||
</a-button>
|
||||
</Dropdown>
|
||||
</template>
|
||||
</template>
|
||||
</BasicTable>
|
||||
|
||||
@@ -52,7 +52,7 @@ const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
|
||||
const [BasicModal, modalApi] = useVbenModal({
|
||||
fullscreenButton: false,
|
||||
onBeforeClose,
|
||||
onCancel: handleClosed,
|
||||
onClosed: handleClosed,
|
||||
onConfirm: handleConfirm,
|
||||
onOpenChange: async (isOpen) => {
|
||||
if (!isOpen) {
|
||||
|
||||
@@ -85,6 +85,7 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({
|
||||
onBeforeClose,
|
||||
onClosed: handleClosed,
|
||||
onConfirm: handleConfirm,
|
||||
destroyOnClose: true,
|
||||
async onOpenChange(isOpen) {
|
||||
if (!isOpen) {
|
||||
return null;
|
||||
|
||||
@@ -68,7 +68,8 @@ export const columns: VxeGridProps['columns'] = [
|
||||
fixed: 'right',
|
||||
slots: { default: 'action' },
|
||||
title: '操作',
|
||||
width: 200,
|
||||
resizable: false,
|
||||
width: 'auto',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -29,7 +29,8 @@ export const columns: VxeGridProps['columns'] = [
|
||||
fixed: 'right',
|
||||
slots: { default: 'action' },
|
||||
title: '操作',
|
||||
width: 180,
|
||||
resizable: false,
|
||||
width: 'auto',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { cloneDeep, eachTree } from '@vben/utils';
|
||||
import { omit } from 'lodash-es';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import { menuTreeSelect, tenantPackageMenuTreeSelect } from '#/api/system/menu';
|
||||
import { tenantPackageMenuTreeSelect } from '#/api/system/menu';
|
||||
import {
|
||||
packageAdd,
|
||||
packageInfo,
|
||||
@@ -40,30 +40,18 @@ const [BasicForm, formApi] = useVbenForm({
|
||||
|
||||
const menuTree = ref<MenuOption[]>([]);
|
||||
async function setupMenuTree(id?: number | string) {
|
||||
if (id) {
|
||||
const resp = await tenantPackageMenuTreeSelect(id);
|
||||
const menus = resp.menus;
|
||||
// i18n处理
|
||||
eachTree(menus, (node) => {
|
||||
node.label = $t(node.label);
|
||||
});
|
||||
// 设置菜单信息
|
||||
menuTree.value = resp.menus;
|
||||
// keys依赖于menu 需要先加载menu
|
||||
await nextTick();
|
||||
await formApi.setFieldValue('menuIds', resp.checkedKeys);
|
||||
} else {
|
||||
const resp = await menuTreeSelect();
|
||||
// i18n处理
|
||||
eachTree(resp, (node) => {
|
||||
node.label = $t(node.label);
|
||||
});
|
||||
// 设置菜单信息
|
||||
menuTree.value = resp;
|
||||
// keys依赖于menu 需要先加载menu
|
||||
await nextTick();
|
||||
await formApi.setFieldValue('menuIds', []);
|
||||
}
|
||||
// 0为新增使用 获取除了`租户管理`的所有菜单
|
||||
const resp = await tenantPackageMenuTreeSelect(id ?? 0);
|
||||
const menus = resp.menus;
|
||||
// i18n处理
|
||||
eachTree(menus, (node) => {
|
||||
node.label = $t(node.label);
|
||||
});
|
||||
// 设置菜单信息
|
||||
menuTree.value = menus;
|
||||
// keys依赖于menu 需要先加载menu
|
||||
await nextTick();
|
||||
await formApi.setFieldValue('menuIds', resp.checkedKeys);
|
||||
}
|
||||
|
||||
async function customFormValueGetter() {
|
||||
@@ -85,6 +73,7 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({
|
||||
onBeforeClose,
|
||||
onClosed: handleClosed,
|
||||
onConfirm: handleConfirm,
|
||||
destroyOnClose: true,
|
||||
async onOpenChange(isOpen) {
|
||||
if (!isOpen) {
|
||||
return null;
|
||||
|
||||
6
apps/web-antd/src/views/system/user/authRole.vue
Normal file
6
apps/web-antd/src/views/system/user/authRole.vue
Normal file
@@ -0,0 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
ele版本会使用这个文件 只是为了不报错`未找到对应组件`才新建的这个文件
|
||||
无实际意义
|
||||
</div>
|
||||
</template>
|
||||
@@ -87,7 +87,7 @@ export const columns: VxeGridProps['columns'] = [
|
||||
slots: { default: 'action' },
|
||||
title: '操作',
|
||||
resizable: false,
|
||||
width: 180,
|
||||
width: 'auto',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -80,6 +80,7 @@ onMounted(loadTree);
|
||||
v-model:value="searchValue"
|
||||
:placeholder="$t('pages.common.search')"
|
||||
size="small"
|
||||
allow-clear
|
||||
>
|
||||
<template #enterButton>
|
||||
<a-button @click="handleReload">
|
||||
@@ -102,9 +103,9 @@ onMounted(loadTree);
|
||||
@select="$emit('select')"
|
||||
>
|
||||
<template #title="{ label }">
|
||||
<span v-if="label.indexOf(searchValue) > -1">
|
||||
<span v-if="label.includes(searchValue)">
|
||||
{{ label.substring(0, label.indexOf(searchValue)) }}
|
||||
<span style="color: #f50">{{ searchValue }}</span>
|
||||
<span class="text-primary">{{ searchValue }}</span>
|
||||
{{
|
||||
label.substring(
|
||||
label.indexOf(searchValue) + searchValue.length,
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
} from '@vben/icons';
|
||||
|
||||
import { useClipboard } from '@vueuse/core';
|
||||
import { Skeleton, Tree } from 'ant-design-vue';
|
||||
import { Alert, Skeleton, Tree } from 'ant-design-vue';
|
||||
|
||||
import { previewCode } from '#/api/tool/gen';
|
||||
|
||||
@@ -185,6 +185,11 @@ const { copy } = useClipboard({ legacy: true });
|
||||
</div>
|
||||
</template>
|
||||
</Tree>
|
||||
<Alert
|
||||
class="mt-2"
|
||||
show-icon
|
||||
message="👆显示的名称为模板的文件名,非最终下载文件名..."
|
||||
/>
|
||||
</div>
|
||||
<CodeMirror
|
||||
v-model="codeContent"
|
||||
|
||||
@@ -4,9 +4,10 @@ import type { Ref } from 'vue';
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
import type { GenInfo } from '#/api/tool/gen/model';
|
||||
|
||||
import { inject } from 'vue';
|
||||
import { inject, onMounted, reactive } from 'vue';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { dictOptionSelectList } from '#/api/system/dict/dict-type';
|
||||
|
||||
import { validRules, vxeTableColumns } from './gen-data';
|
||||
|
||||
@@ -15,8 +16,26 @@ import { validRules, vxeTableColumns } from './gen-data';
|
||||
*/
|
||||
const genInfoData = inject('genInfoData') as Ref<GenInfo['info']>;
|
||||
|
||||
const dictOptions = reactive<{ label: string; value: string }[]>([
|
||||
{ label: '未设置', value: '' },
|
||||
]);
|
||||
|
||||
/**
|
||||
* 加载字典下拉数据
|
||||
*/
|
||||
onMounted(async () => {
|
||||
const resp = await dictOptionSelectList();
|
||||
|
||||
const options = resp.map((dict) => ({
|
||||
label: `${dict.dictName} | ${dict.dictType}`,
|
||||
value: dict.dictType,
|
||||
}));
|
||||
|
||||
dictOptions.push(...options);
|
||||
});
|
||||
|
||||
const gridOptions: VxeGridProps = {
|
||||
columns: vxeTableColumns,
|
||||
columns: vxeTableColumns(dictOptions),
|
||||
keepSource: true,
|
||||
editConfig: { trigger: 'click', mode: 'cell', showStatus: true },
|
||||
editRules: validRules,
|
||||
|
||||
@@ -2,14 +2,10 @@ import type { Recordable } from '@vben/types';
|
||||
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
|
||||
import { reactive } from 'vue';
|
||||
|
||||
import { getPopupContainer } from '@vben/utils';
|
||||
|
||||
import { Checkbox, Input, Select } from 'ant-design-vue';
|
||||
|
||||
import { dictOptionSelectList } from '#/api/system/dict/dict-type';
|
||||
|
||||
const JavaTypes: string[] = [
|
||||
'Long',
|
||||
'String',
|
||||
@@ -45,24 +41,6 @@ const componentsOptions = [
|
||||
{ label: '富文本', value: 'editor' },
|
||||
];
|
||||
|
||||
const dictOptions = reactive<{ label: string; value: string }[]>([
|
||||
{ label: '未设置', value: '' },
|
||||
]);
|
||||
/**
|
||||
* 在这里初始化字典下拉框
|
||||
*/
|
||||
(async function init() {
|
||||
const ret = await dictOptionSelectList();
|
||||
|
||||
ret.forEach((dict) => {
|
||||
const option = {
|
||||
label: `${dict.dictName} | ${dict.dictType}`,
|
||||
value: dict.dictType,
|
||||
};
|
||||
dictOptions.push(option);
|
||||
});
|
||||
})();
|
||||
|
||||
function renderBooleanTag(row: Recordable<any>, field: string) {
|
||||
const value = row[field] ? '是' : '否';
|
||||
const className = row[field] ? 'text-green-500' : 'text-red-500';
|
||||
@@ -78,7 +56,10 @@ export const validRules: VxeGridProps['editRules'] = {
|
||||
javaField: [{ required: true, message: '请输入' }],
|
||||
};
|
||||
|
||||
export const vxeTableColumns: VxeGridProps['columns'] = [
|
||||
// 内部依赖的字典从外部通过函数传入
|
||||
export const vxeTableColumns: (
|
||||
dictOptions: { label: string; value: string }[],
|
||||
) => VxeGridProps['columns'] = (dictOptions) => [
|
||||
{
|
||||
title: '序号',
|
||||
type: 'seq',
|
||||
|
||||
10
apps/web-antd/src/views/tool/gen/editTable.vue
Normal file
10
apps/web-antd/src/views/tool/gen/editTable.vue
Normal file
@@ -0,0 +1,10 @@
|
||||
<!--
|
||||
后端版本>=5.4.0 这个从本地路由变为从后台返回
|
||||
-->
|
||||
<script setup lang="ts">
|
||||
import EditGenPage from './edit-gen.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<EditGenPage />
|
||||
</template>
|
||||
@@ -110,7 +110,7 @@ function handlePreview(record: Recordable<any>) {
|
||||
|
||||
const router = useRouter();
|
||||
function handleEdit(record: Recordable<any>) {
|
||||
router.push(`/code-gen/edit/${record.tableId}`);
|
||||
router.push(`/tool/gen-edit/index/${record.tableId}`);
|
||||
}
|
||||
|
||||
async function handleSync(record: Recordable<any>) {
|
||||
|
||||
@@ -86,11 +86,13 @@ const gridOptions: VxeGridProps = {
|
||||
},
|
||||
},
|
||||
rowConfig: {
|
||||
keyField: 'tableId',
|
||||
keyField: 'tableName',
|
||||
},
|
||||
toolbarConfig: {
|
||||
enabled: false,
|
||||
},
|
||||
id: 'import-table-modal',
|
||||
cellClassName: 'cursor-pointer',
|
||||
};
|
||||
|
||||
const [BasicTable, tableApi] = useVbenVxeGrid({ formOptions, gridOptions });
|
||||
|
||||
@@ -33,7 +33,8 @@ export const columns: VxeGridProps['columns'] = [
|
||||
fixed: 'right',
|
||||
slots: { default: 'action' },
|
||||
title: '操作',
|
||||
width: 200,
|
||||
resizable: false,
|
||||
width: 'auto',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ import { renderDict } from '#/utils/render';
|
||||
|
||||
import { approvalModal, approvalRejectionModal, flowInterfereModal } from '.';
|
||||
import ApprovalDetails from './approval-details.vue';
|
||||
import FlowPreview from './flow-preview.vue';
|
||||
import { approveWithReasonModal } from './helper';
|
||||
import userSelectModal from './user-select-modal.vue';
|
||||
|
||||
@@ -442,10 +443,7 @@ async function handleCopy(text: string) {
|
||||
/>
|
||||
</TabPane>
|
||||
<TabPane key="2" tab="审批流程图">
|
||||
<img
|
||||
:src="`data:image/png;base64,${currentFlowInfo.image}`"
|
||||
class="rounded-lg border"
|
||||
/>
|
||||
<FlowPreview :instance-id="currentFlowInfo.instanceId" />
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { stringify } from '@vben/request';
|
||||
import { useAccessStore } from '@vben/stores';
|
||||
|
||||
import { useEventListener } from '@vueuse/core';
|
||||
import { Alert } from 'ant-design-vue';
|
||||
|
||||
defineOptions({ name: 'FlowDesigner' });
|
||||
|
||||
@@ -48,5 +49,13 @@ useEventListener('message', messageHandler);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<iframe :src="url" class="size-full"></iframe>
|
||||
<div class="size-full">
|
||||
<Alert
|
||||
class="mx-4 my-2"
|
||||
type="warning"
|
||||
:show-icon="true"
|
||||
message="这是iframe页面! iframe页面! iframe页面! 不是我写的真服了"
|
||||
/>
|
||||
<iframe :src="url" class="size-full"></iframe>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
32
apps/web-antd/src/views/workflow/components/flow-preview.vue
Normal file
32
apps/web-antd/src/views/workflow/components/flow-preview.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<script setup lang="ts">
|
||||
import { useAppConfig } from '@vben/hooks';
|
||||
import { stringify } from '@vben/request';
|
||||
import { useAccessStore } from '@vben/stores';
|
||||
|
||||
import { useWarmflowIframe } from './hook';
|
||||
|
||||
defineOptions({ name: 'FlowPreview' });
|
||||
|
||||
const props = defineProps<{ instanceId: string }>();
|
||||
|
||||
const { clientId } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
||||
|
||||
const accessStore = useAccessStore();
|
||||
const params = {
|
||||
Authorization: `Bearer ${accessStore.accessToken}`,
|
||||
id: props.instanceId,
|
||||
clientid: clientId,
|
||||
type: 'FlowChart',
|
||||
};
|
||||
|
||||
/**
|
||||
* iframe地址
|
||||
*/
|
||||
const url = `${import.meta.env.VITE_GLOB_API_URL}/warm-flow-ui/index.html?${stringify(params)}`;
|
||||
|
||||
const { iframeRef } = useWarmflowIframe();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<iframe ref="iframeRef" :src="url" class="h-[500px] w-full border"></iframe>
|
||||
</template>
|
||||
37
apps/web-antd/src/views/workflow/components/hook.ts
Normal file
37
apps/web-antd/src/views/workflow/components/hook.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { onMounted, useTemplateRef, watch } from 'vue';
|
||||
|
||||
import { usePreferences } from '@vben/preferences';
|
||||
|
||||
/**
|
||||
* warmflow ref相关操作
|
||||
* @returns hook
|
||||
*/
|
||||
export function useWarmflowIframe() {
|
||||
const iframeRef = useTemplateRef<HTMLIFrameElement>('iframeRef');
|
||||
const { isDark } = usePreferences();
|
||||
|
||||
onMounted(() => {
|
||||
/**
|
||||
* load只是iframe加载完 而非vue加载完
|
||||
*/
|
||||
iframeRef.value?.addEventListener('load', async () => {
|
||||
/**
|
||||
* TODO: 这里可以优化 因为拿不到内部vue的mount状态
|
||||
*/
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
const theme = isDark.value ? 'theme-dark' : 'theme-light';
|
||||
iframeRef.value?.contentWindow?.postMessage({ type: theme });
|
||||
});
|
||||
});
|
||||
|
||||
// 监听主题切换 通知iframe切换
|
||||
watch(isDark, (dark) => {
|
||||
if (!iframeRef.value) {
|
||||
return;
|
||||
}
|
||||
const theme = dark ? 'theme-dark' : 'theme-light';
|
||||
iframeRef.value.contentWindow?.postMessage({ type: theme });
|
||||
});
|
||||
|
||||
return { iframeRef };
|
||||
}
|
||||
@@ -91,7 +91,8 @@ export const columns: VxeGridProps['columns'] = [
|
||||
fixed: 'right',
|
||||
slots: { default: 'action' },
|
||||
title: '操作',
|
||||
width: 210,
|
||||
resizable: false,
|
||||
width: 'auto',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
11
apps/web-antd/src/views/workflow/leave/leaveEdit.vue
Normal file
11
apps/web-antd/src/views/workflow/leave/leaveEdit.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<!--
|
||||
后端版本>=5.4.0 这个从本地路由变为从后台返回
|
||||
未修改文件名 而是新加了这个文件
|
||||
-->
|
||||
<script setup lang="ts">
|
||||
import LeaveFormPage from './leave-form.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LeaveFormPage />
|
||||
</template>
|
||||
@@ -1,7 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import type { PropType } from 'vue';
|
||||
|
||||
import type { CategoryTree } from '#/api/workflow/category/model';
|
||||
|
||||
import { onMounted, type PropType, ref } from 'vue';
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { SyncOutlined } from '@ant-design/icons-vue';
|
||||
import { InputSearch, Skeleton, Tree } from 'ant-design-vue';
|
||||
@@ -71,6 +73,7 @@ onMounted(loadTree);
|
||||
v-model:value="searchValue"
|
||||
:placeholder="$t('pages.common.search')"
|
||||
size="small"
|
||||
allow-clear
|
||||
>
|
||||
<template #enterButton>
|
||||
<a-button @click="handleReload">
|
||||
@@ -93,9 +96,9 @@ onMounted(loadTree);
|
||||
@select="$emit('select')"
|
||||
>
|
||||
<template #title="{ label }">
|
||||
<span v-if="label.indexOf(searchValue) > -1">
|
||||
<span v-if="label.includes(searchValue)">
|
||||
{{ label.substring(0, label.indexOf(searchValue)) }}
|
||||
<span style="color: #f50">{{ searchValue }}</span>
|
||||
<span class="text-primary">{{ searchValue }}</span>
|
||||
{{
|
||||
label.substring(
|
||||
label.indexOf(searchValue) + searchValue.length,
|
||||
|
||||
@@ -63,7 +63,7 @@ export const columns: VxeGridProps['columns'] = [
|
||||
slots: { default: 'action' },
|
||||
title: '操作',
|
||||
resizable: false,
|
||||
width: 200,
|
||||
width: 'auto',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<!--
|
||||
后端版本>=5.4.0 这个从本地路由变为从后台返回
|
||||
未修改文件名 而是新加了这个文件
|
||||
-->
|
||||
<script setup lang="ts">
|
||||
import FlowDesignerPage from '../components/flow-designer.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FlowDesignerPage />
|
||||
</template>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user