Compare commits
136 Commits
1.3.2-back
...
main
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f913955259 | ||
![]() |
8d6ef40d3e | ||
![]() |
2141c93399 | ||
![]() |
906502f49b | ||
![]() |
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 |
@@ -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
|
||||
|
7
.vscode/settings.json
vendored
7
.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,
|
||||
|
47
CHANGELOG.md
47
CHANGELOG.md
@@ -1,3 +1,50 @@
|
||||
# 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**
|
||||
|
@@ -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.2",
|
||||
"version": "1.4.0",
|
||||
"homepage": "https://vben.pro",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -87,8 +87,8 @@ const withDefaultPlaceholder = <T extends Component>(
|
||||
componentProps: Recordable<any> = {},
|
||||
) => {
|
||||
return defineComponent({
|
||||
inheritAttrs: false,
|
||||
name: component.name,
|
||||
inheritAttrs: false,
|
||||
setup: (props: any, { attrs, expose, slots }) => {
|
||||
const placeholder =
|
||||
props?.placeholder ||
|
||||
@@ -162,20 +162,34 @@ 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,
|
||||
Checkbox,
|
||||
CheckboxGroup,
|
||||
|
@@ -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 };
|
||||
|
@@ -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]' },
|
||||
'去检查你的后端配置!别盯着前端找问题了!这不是前端问题!',
|
||||
),
|
||||
),
|
||||
]),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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,
|
||||
|
@@ -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;
|
||||
|
@@ -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(\\d+)': {
|
||||
activePath: '/system/role',
|
||||
requireHomeRedirect: true,
|
||||
},
|
||||
|
||||
'/system/oss-config/index': {
|
||||
activePath: '/system/oss',
|
||||
requireHomeRedirect: true,
|
||||
},
|
||||
|
||||
'/tool/gen-edit/index/:tableId(\\d+)': {
|
||||
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: [],
|
||||
},
|
||||
{
|
||||
|
@@ -9,6 +9,7 @@ const {
|
||||
|
||||
/**
|
||||
* 该文件放非后台返回的路由 比如个人中心 等需要跳转显示的页面
|
||||
* 也可以直接在菜单管理配置
|
||||
*/
|
||||
const localRoutes: RouteRecordStringComponent[] = [
|
||||
{
|
||||
@@ -22,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',
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
|
@@ -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) {
|
||||
|
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);
|
||||
});
|
||||
}
|
@@ -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');
|
||||
|
@@ -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);
|
||||
}
|
||||
});
|
||||
|
@@ -30,7 +30,6 @@ export const columns: VxeGridProps['columns'] = [
|
||||
field: 'deptName',
|
||||
title: '部门名称',
|
||||
treeNode: true,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
field: 'deptCategory',
|
||||
@@ -39,13 +38,9 @@ export const columns: VxeGridProps['columns'] = [
|
||||
{
|
||||
field: 'orderNum',
|
||||
title: '排序',
|
||||
resizable: false,
|
||||
width: 'auto',
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
resizable: false,
|
||||
width: 'auto',
|
||||
title: '状态',
|
||||
slots: {
|
||||
default: ({ row }) => {
|
||||
@@ -62,7 +57,8 @@ export const columns: VxeGridProps['columns'] = [
|
||||
fixed: 'right',
|
||||
slots: { default: 'action' },
|
||||
title: '操作',
|
||||
width: 200,
|
||||
resizable: false,
|
||||
width: 'auto',
|
||||
},
|
||||
];
|
||||
|
||||
|
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>
|
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>
|
@@ -219,6 +219,7 @@ export const drawerSchema: FormSchemaGetter = () => [
|
||||
fieldName: 'orderNum',
|
||||
help: '排序, 数字越小越靠前',
|
||||
label: '显示排序',
|
||||
defaultValue: 0,
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
|
25
apps/web-antd/src/views/system/menu/helper.ts
Normal file
25
apps/web-antd/src/views/system/menu/helper.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import type { MaybePromise } from '@vben/types';
|
||||
|
||||
import type { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
|
||||
/**
|
||||
* 保存表格滚动/展开状态并执行回调 用于树表在执行 新增/编辑/删除等操作后 依然在当前位置(体验优化)
|
||||
*
|
||||
* @param tableApi 表格api
|
||||
* @param callback 回调
|
||||
*/
|
||||
export async function preserveTreeTableState(
|
||||
tableApi: ReturnType<typeof useVbenVxeGrid>[1],
|
||||
callback: () => MaybePromise<void>,
|
||||
) {
|
||||
// 保存当前状态
|
||||
const scrollState = tableApi.grid.getScroll();
|
||||
const expandRecords = tableApi.grid.getTreeExpandRecords();
|
||||
|
||||
// 执行回调
|
||||
await callback();
|
||||
|
||||
// 恢复状态
|
||||
tableApi.grid.setTreeExpand(expandRecords, true);
|
||||
tableApi.grid.scrollTo(scrollState.scrollLeft, scrollState.scrollTop);
|
||||
}
|
@@ -4,18 +4,20 @@ 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 { preserveTreeTableState } from './helper';
|
||||
import menuDrawer from './menu-drawer.vue';
|
||||
|
||||
/**
|
||||
@@ -111,9 +113,41 @@ async function handleEdit(record: Menu) {
|
||||
drawerApi.open();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否级联删除
|
||||
*/
|
||||
const cascadingDeletion = ref(false);
|
||||
async function handleDelete(row: Menu) {
|
||||
await menuRemove([row.menuId]);
|
||||
await tableApi.query();
|
||||
await preserveTreeTableState(tableApi, async () => {
|
||||
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() {
|
||||
await preserveTreeTableState(tableApi, () => tableApi.query());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -128,6 +162,9 @@ function setExpandOrCollapse(expand: boolean) {
|
||||
/**
|
||||
* 与后台逻辑相同
|
||||
* 只有租户管理和超级管理能访问菜单管理
|
||||
* 注意: 只有超管才能对菜单进行`增删改`操作
|
||||
* 注意: 只有超管才能对菜单进行`增删改`操作
|
||||
* 注意: 只有超管才能对菜单进行`增删改`操作
|
||||
*/
|
||||
const { hasAccessByRoles } = useAccess();
|
||||
const isAdmin = computed(() => {
|
||||
@@ -140,6 +177,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 +196,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 +207,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 +217,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 +225,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 +240,7 @@ const isAdmin = computed(() => {
|
||||
</Space>
|
||||
</template>
|
||||
</BasicTable>
|
||||
<MenuDrawer @reload="tableApi.query()" />
|
||||
<MenuDrawer @reload="afterEditOrAdd" />
|
||||
</Page>
|
||||
<Fallback v-else description="您没有菜单管理的访问权限" status="403" />
|
||||
</template>
|
||||
|
@@ -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: '是否默认',
|
||||
|
@@ -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'];
|
@@ -73,9 +73,3 @@ export const columns: VxeGridProps['columns'] = [
|
||||
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 }">
|
||||
|
@@ -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>
|
@@ -142,7 +142,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>
|
||||
|
||||
|
@@ -85,6 +85,7 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({
|
||||
onBeforeClose,
|
||||
onClosed: handleClosed,
|
||||
onConfirm: handleConfirm,
|
||||
destroyOnClose: true,
|
||||
async onOpenChange(isOpen) {
|
||||
if (!isOpen) {
|
||||
return null;
|
||||
|
@@ -85,6 +85,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>
|
@@ -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"
|
||||
|
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>) {
|
||||
|
@@ -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>
|
||||
|
28
apps/web-antd/src/views/workflow/components/flow-preview.vue
Normal file
28
apps/web-antd/src/views/workflow/components/flow-preview.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<script setup lang="ts">
|
||||
import { useAppConfig } from '@vben/hooks';
|
||||
import { stringify } from '@vben/request';
|
||||
import { useAccessStore } from '@vben/stores';
|
||||
|
||||
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)}`;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<iframe :src="url" class="h-[500px] w-full border"></iframe>
|
||||
</template>
|
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>
|
@@ -0,0 +1,11 @@
|
||||
<!--
|
||||
后端版本>=5.4.0 这个从本地路由变为从后台返回
|
||||
未修改文件名 而是新加了这个文件
|
||||
-->
|
||||
<script setup lang="ts">
|
||||
import FlowDesignerPage from '../components/flow-designer.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FlowDesignerPage />
|
||||
</template>
|
@@ -154,7 +154,7 @@ const router = useRouter();
|
||||
*/
|
||||
function handleDesign(row: any, disabled: boolean) {
|
||||
router.push({
|
||||
path: '/workflow/designer',
|
||||
path: '/workflow/design/index',
|
||||
query: { definitionId: row.id, disabled: String(disabled) },
|
||||
});
|
||||
}
|
||||
|
@@ -28,6 +28,7 @@ import { categoryTree } from '#/api/workflow/category';
|
||||
import { pageByAllTaskFinish, pageByAllTaskWait } from '#/api/workflow/task';
|
||||
|
||||
import { ApprovalCard, ApprovalPanel, CopyComponent } from '../components';
|
||||
import { bottomOffset } from './constant';
|
||||
|
||||
const emptyImage = Empty.PRESENTED_IMAGE_SIMPLE;
|
||||
|
||||
@@ -140,7 +141,9 @@ const handleScroll = debounce(async (e: Event) => {
|
||||
// e.target.scrollHeight 是元素的总高度。
|
||||
const { scrollTop, clientHeight, scrollHeight } = e.target as HTMLElement;
|
||||
// 判断是否滚动到底部
|
||||
const isBottom = scrollTop + clientHeight >= scrollHeight;
|
||||
const isBottom = scrollTop + clientHeight >= scrollHeight - bottomOffset;
|
||||
console.log('scrollTop + clientHeight', scrollTop + clientHeight);
|
||||
console.log('scrollHeight', scrollHeight);
|
||||
|
||||
// 滚动到底部且没有加载完成
|
||||
if (isBottom && !isLoadComplete.value) {
|
||||
|
7
apps/web-antd/src/views/workflow/task/constant.ts
Normal file
7
apps/web-antd/src/views/workflow/task/constant.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* 底部偏移量
|
||||
* 在缩放时会差大概0.5px 导致触底逻辑不会触发
|
||||
* 在这里设置手动补偿
|
||||
* @see https://gitee.com/dapppp/ruoyi-plus-vben5/issues/IC28RE#note_40175381
|
||||
*/
|
||||
export const bottomOffset = 2;
|
@@ -24,6 +24,7 @@ import { cloneDeep, debounce } from 'lodash-es';
|
||||
import { pageByCurrent } from '#/api/workflow/instance';
|
||||
|
||||
import { ApprovalCard, ApprovalPanel } from '../components';
|
||||
import { bottomOffset } from './constant';
|
||||
|
||||
const emptyImage = Empty.PRESENTED_IMAGE_SIMPLE;
|
||||
|
||||
@@ -95,7 +96,7 @@ const handleScroll = debounce(async (e: Event) => {
|
||||
// e.target.scrollHeight 是元素的总高度。
|
||||
const { scrollTop, clientHeight, scrollHeight } = e.target as HTMLElement;
|
||||
// 判断是否滚动到底部
|
||||
const isBottom = scrollTop + clientHeight >= scrollHeight;
|
||||
const isBottom = scrollTop + clientHeight >= scrollHeight - bottomOffset;
|
||||
|
||||
// 滚动到底部且没有加载完成
|
||||
if (isBottom && !isLoadComplete.value) {
|
||||
|
@@ -26,6 +26,7 @@ import { categoryTree } from '#/api/workflow/category';
|
||||
import { pageByTaskCopy } from '#/api/workflow/task';
|
||||
|
||||
import { ApprovalCard, ApprovalPanel, CopyComponent } from '../components';
|
||||
import { bottomOffset } from './constant';
|
||||
|
||||
const emptyImage = Empty.PRESENTED_IMAGE_SIMPLE;
|
||||
|
||||
@@ -99,7 +100,7 @@ const handleScroll = debounce(async (e: Event) => {
|
||||
// e.target.scrollHeight 是元素的总高度。
|
||||
const { scrollTop, clientHeight, scrollHeight } = e.target as HTMLElement;
|
||||
// 判断是否滚动到底部
|
||||
const isBottom = scrollTop + clientHeight >= scrollHeight;
|
||||
const isBottom = scrollTop + clientHeight >= scrollHeight - bottomOffset;
|
||||
|
||||
// 滚动到底部且没有加载完成
|
||||
if (isBottom && !isLoadComplete.value) {
|
||||
|
@@ -26,6 +26,7 @@ import { categoryTree } from '#/api/workflow/category';
|
||||
import { pageByTaskFinish } from '#/api/workflow/task';
|
||||
|
||||
import { ApprovalCard, ApprovalPanel, CopyComponent } from '../components';
|
||||
import { bottomOffset } from './constant';
|
||||
|
||||
const emptyImage = Empty.PRESENTED_IMAGE_SIMPLE;
|
||||
|
||||
@@ -99,7 +100,7 @@ const handleScroll = debounce(async (e: Event) => {
|
||||
// e.target.scrollHeight 是元素的总高度。
|
||||
const { scrollTop, clientHeight, scrollHeight } = e.target as HTMLElement;
|
||||
// 判断是否滚动到底部
|
||||
const isBottom = scrollTop + clientHeight >= scrollHeight;
|
||||
const isBottom = scrollTop + clientHeight >= scrollHeight - bottomOffset;
|
||||
|
||||
// 滚动到底部且没有加载完成
|
||||
if (isBottom && !isLoadComplete.value) {
|
||||
|
@@ -27,6 +27,7 @@ import { categoryTree } from '#/api/workflow/category';
|
||||
import { pageByTaskWait } from '#/api/workflow/task';
|
||||
|
||||
import { ApprovalCard, ApprovalPanel, CopyComponent } from '../components';
|
||||
import { bottomOffset } from './constant';
|
||||
|
||||
const emptyImage = Empty.PRESENTED_IMAGE_SIMPLE;
|
||||
|
||||
@@ -100,7 +101,7 @@ const handleScroll = debounce(async (e: Event) => {
|
||||
// e.target.scrollHeight 是元素的总高度。
|
||||
const { scrollTop, clientHeight, scrollHeight } = e.target as HTMLElement;
|
||||
// 判断是否滚动到底部
|
||||
const isBottom = scrollTop + clientHeight >= scrollHeight;
|
||||
const isBottom = scrollTop + clientHeight >= scrollHeight - bottomOffset;
|
||||
|
||||
// 滚动到底部且没有加载完成
|
||||
if (isBottom && !isLoadComplete.value) {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/docs",
|
||||
"version": "5.5.4",
|
||||
"version": "5.5.6",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "vitepress build",
|
||||
|
@@ -78,7 +78,7 @@ const [Drawer, drawerApi] = useVbenDrawer({
|
||||
| --- | --- | --- | --- |
|
||||
| appendToMain | 是否挂载到内容区域(默认挂载到body) | `boolean` | `false` |
|
||||
| connectedComponent | 连接另一个Modal组件 | `Component` | - |
|
||||
| destroyOnClose | 关闭时销毁`connectedComponent` | `boolean` | `false` |
|
||||
| destroyOnClose | 关闭时销毁 | `boolean` | `false` |
|
||||
| title | 标题 | `string\|slot` | - |
|
||||
| titleTooltip | 标题提示信息 | `string\|slot` | - |
|
||||
| description | 描述信息 | `string\|slot` | - |
|
||||
|
@@ -26,6 +26,12 @@ outline: deep
|
||||
|
||||
<DemoPreview dir="demos/vben-ellipsis-text/tooltip" />
|
||||
|
||||
## 自动显示 tooltip
|
||||
|
||||
通过`tooltip-when-ellipsis`设置,仅在文本长度超出导致省略号出现时才触发 tooltip。
|
||||
|
||||
<DemoPreview dir="demos/vben-ellipsis-text/auto-display" />
|
||||
|
||||
## API
|
||||
|
||||
### Props
|
||||
@@ -37,6 +43,8 @@ outline: deep
|
||||
| maxWidth | 文本区域最大宽度 | `number \| string` | `'100%'` |
|
||||
| placement | 提示浮层的位置 | `'bottom'\|'left'\|'right'\|'top'` | `'top'` |
|
||||
| tooltip | 启用文本提示 | `boolean` | `true` |
|
||||
| tooltipWhenEllipsis | 内容超出,自动启用文本提示 | `boolean` | `false` |
|
||||
| ellipsisThreshold | 设置 tooltipWhenEllipsis 后才生效,文本截断检测的像素差异阈值,越大则判断越严格,如果碰见异常情况可以自己设置阈值 | `number` | `3` |
|
||||
| tooltipBackgroundColor | 提示文本的背景颜色 | `string` | - |
|
||||
| tooltipColor | 提示文本的颜色 | `string` | - |
|
||||
| tooltipFontSize | 提示文本的大小 | `string` | - |
|
||||
|
@@ -59,7 +59,7 @@ Modal 内的内容一般业务中,会比较复杂,所以我们可以将 moda
|
||||
::: info 注意
|
||||
|
||||
- `VbenModal` 组件对与参数的处理优先级是 `slot` > `props` > `state`(通过api更新的状态以及useVbenModal参数)。如果你已经传入了 `slot` 或者 `props`,那么 `setState` 将不会生效,这种情况下你可以通过 `slot` 或者 `props` 来更新状态。
|
||||
- 如果你使用到了 `connectedComponent` 参数,那么会存在 2 个`useVbenModal`, 此时,如果同时设置了相同的参数,那么以内部为准(也就是没有设置 connectedComponent 的代码)。比如 同时设置了 `onConfirm`,那么以内部的 `onConfirm` 为准。`onOpenChange`事件除外,内外都会触发。
|
||||
- 如果你使用到了 `connectedComponent` 参数,那么会存在 2 个`useVbenModal`, 此时,如果同时设置了相同的参数,那么以内部为准(也就是没有设置 connectedComponent 的代码)。比如 同时设置了 `onConfirm`,那么以内部的 `onConfirm` 为准。`onOpenChange`事件除外,内外都会触发。另外,如果设置了`destroyOnClose`,内部Modal及其子组件会在被关闭后<b>完全销毁</b>。
|
||||
- 如果弹窗的默认行为不符合你的预期,可以在`src\bootstrap.ts`中修改`setDefaultModalProps`的参数来设置默认的属性,如默认隐藏全屏按钮,修改默认ZIndex等。
|
||||
|
||||
:::
|
||||
|
16
docs/src/demos/vben-ellipsis-text/auto-display/index.vue
Normal file
16
docs/src/demos/vben-ellipsis-text/auto-display/index.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script lang="ts" setup>
|
||||
import { EllipsisText } from '@vben/common-ui';
|
||||
|
||||
const text = `
|
||||
Vben Admin 是一个基于 Vue3.0、Vite、 TypeScript 的后台解决方案,目标是为开发中大型项目提供开箱即用的解决方案。
|
||||
`;
|
||||
</script>
|
||||
<template>
|
||||
<EllipsisText :line="2" :tooltip-when-ellipsis="true">
|
||||
{{ text }}
|
||||
</EllipsisText>
|
||||
|
||||
<EllipsisText :line="3" :tooltip-when-ellipsis="true">
|
||||
{{ text }}
|
||||
</EllipsisText>
|
||||
</template>
|
@@ -98,8 +98,8 @@ The execution command is: `pnpm run [script]` or `npm run [script]`.
|
||||
"postinstall": "pnpm -r run stub --if-present",
|
||||
// Only allow using pnpm
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
// Install husky
|
||||
"prepare": "is-ci || husky",
|
||||
// Install lefthook
|
||||
"prepare": "is-ci || lefthook install",
|
||||
// Preview the application
|
||||
"preview": "turbo-run preview",
|
||||
// Package specification check
|
||||
|
@@ -60,6 +60,29 @@ VITE_INJECT_APP_LOADING=true
|
||||
VITE_ARCHIVER=true
|
||||
```
|
||||
|
||||
```bash [.env.production]
|
||||
# Public Path for Resources, must start and end with /
|
||||
VITE_BASE=/
|
||||
|
||||
# API URL
|
||||
VITE_GLOB_API_URL=https://mock-napi.vben.pro/api
|
||||
|
||||
# Whether to enable compression, can be set to none, brotli, gzip
|
||||
VITE_COMPRESS=gzip
|
||||
|
||||
# Whether to enable PWA
|
||||
VITE_PWA=false
|
||||
|
||||
# vue-router mode
|
||||
VITE_ROUTER_HISTORY=hash
|
||||
|
||||
# Whether to inject global loading
|
||||
VITE_INJECT_APP_LOADING=true
|
||||
|
||||
# Whether to generate dist.zip after packaging
|
||||
VITE_ARCHIVER=true
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Dynamic Configuration in Production Environment
|
||||
@@ -142,6 +165,7 @@ import { defineOverridesPreferences } from '@vben/preferences';
|
||||
/**
|
||||
* @description Project configuration file
|
||||
* Only a part of the configuration in the project needs to be covered, and unnecessary configurations do not need to be covered. The default configuration will be automatically used
|
||||
* !!! Please clear the cache after changing the configuration, otherwise it may not take effect
|
||||
*/
|
||||
export const overridesPreferences = defineOverridesPreferences({
|
||||
// overrides
|
||||
@@ -162,8 +186,15 @@ const defaultPreferences: Preferences = {
|
||||
colorWeakMode: false,
|
||||
compact: false,
|
||||
contentCompact: 'wide',
|
||||
contentCompactWidth: 1200,
|
||||
contentPadding: 0,
|
||||
contentPaddingBottom: 0,
|
||||
contentPaddingLeft: 0,
|
||||
contentPaddingRight: 0,
|
||||
contentPaddingTop: 0,
|
||||
defaultAvatar:
|
||||
'https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp',
|
||||
defaultHomePath: '/analytics',
|
||||
dynamicTitle: true,
|
||||
enableCheckUpdates: true,
|
||||
enablePreferences: true,
|
||||
@@ -171,10 +202,11 @@ const defaultPreferences: Preferences = {
|
||||
isMobile: false,
|
||||
layout: 'sidebar-nav',
|
||||
locale: 'zh-CN',
|
||||
loginExpiredMode: 'modal',
|
||||
loginExpiredMode: 'page',
|
||||
name: 'Vben Admin',
|
||||
preferencesButtonPosition: 'auto',
|
||||
watermark: false,
|
||||
zIndex: 200,
|
||||
},
|
||||
breadcrumb: {
|
||||
enable: true,
|
||||
@@ -190,18 +222,23 @@ const defaultPreferences: Preferences = {
|
||||
enable: true,
|
||||
icp: '',
|
||||
icpLink: '',
|
||||
settingShow: true,
|
||||
},
|
||||
footer: {
|
||||
enable: true,
|
||||
enable: false,
|
||||
fixed: false,
|
||||
height: 32,
|
||||
},
|
||||
header: {
|
||||
enable: true,
|
||||
height: 50,
|
||||
hidden: false,
|
||||
menuAlign: 'start',
|
||||
mode: 'fixed',
|
||||
},
|
||||
logo: {
|
||||
enable: true,
|
||||
fit: 'contain',
|
||||
source: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp',
|
||||
},
|
||||
navigation: {
|
||||
@@ -219,23 +256,31 @@ const defaultPreferences: Preferences = {
|
||||
sidebar: {
|
||||
autoActivateChild: false,
|
||||
collapsed: false,
|
||||
collapsedButton: true,
|
||||
collapsedShowTitle: false,
|
||||
collapseWidth: 60,
|
||||
enable: true,
|
||||
expandOnHover: true,
|
||||
extraCollapse: true,
|
||||
extraCollapse: false,
|
||||
extraCollapsedWidth: 60,
|
||||
fixedButton: true,
|
||||
hidden: false,
|
||||
width: 230,
|
||||
mixedWidth: 80,
|
||||
width: 224,
|
||||
},
|
||||
tabbar: {
|
||||
draggable: true,
|
||||
enable: true,
|
||||
height: 36,
|
||||
height: 38,
|
||||
keepAlive: true,
|
||||
maxCount: 0,
|
||||
middleClickToClose: false,
|
||||
persist: true,
|
||||
showIcon: true,
|
||||
showMaximize: true,
|
||||
showMore: true,
|
||||
styleType: 'chrome',
|
||||
wheelable: true,
|
||||
},
|
||||
theme: {
|
||||
builtinType: 'default',
|
||||
@@ -246,7 +291,7 @@ const defaultPreferences: Preferences = {
|
||||
mode: 'dark',
|
||||
radius: '0.5',
|
||||
semiDarkHeader: false,
|
||||
semiDarkSidebar: true,
|
||||
semiDarkSidebar: false,
|
||||
},
|
||||
transition: {
|
||||
enable: true,
|
||||
@@ -260,9 +305,9 @@ const defaultPreferences: Preferences = {
|
||||
languageToggle: true,
|
||||
lockScreen: true,
|
||||
notification: true,
|
||||
refresh: true,
|
||||
sidebarToggle: true,
|
||||
themeToggle: true,
|
||||
refresh: true,
|
||||
},
|
||||
};
|
||||
```
|
||||
@@ -287,8 +332,22 @@ interface AppPreferences {
|
||||
compact: boolean;
|
||||
/** Whether to enable content compact mode */
|
||||
contentCompact: ContentCompactType;
|
||||
/** Content compact width */
|
||||
contentCompactWidth: number;
|
||||
/** Content padding */
|
||||
contentPadding: number;
|
||||
/** Content bottom padding */
|
||||
contentPaddingBottom: number;
|
||||
/** Content left padding */
|
||||
contentPaddingLeft: number;
|
||||
/** Content right padding */
|
||||
contentPaddingRight: number;
|
||||
/** Content top padding */
|
||||
contentPaddingTop: number;
|
||||
// /** Default application avatar */
|
||||
defaultAvatar: string;
|
||||
/** Default homepage path */
|
||||
defaultHomePath: string;
|
||||
// /** Enable dynamic title */
|
||||
dynamicTitle: boolean;
|
||||
/** Whether to enable update checks */
|
||||
@@ -315,6 +374,8 @@ interface AppPreferences {
|
||||
* @zh_CN Whether to enable watermark
|
||||
*/
|
||||
watermark: boolean;
|
||||
/** z-index */
|
||||
zIndex: number;
|
||||
}
|
||||
interface BreadcrumbPreferences {
|
||||
/** Whether breadcrumbs are enabled */
|
||||
@@ -342,6 +403,8 @@ interface CopyrightPreferences {
|
||||
icp: string;
|
||||
/** Link to the ICP */
|
||||
icpLink: string;
|
||||
/** Whether to show in settings panel */
|
||||
settingShow?: boolean;
|
||||
}
|
||||
|
||||
interface FooterPreferences {
|
||||
@@ -349,13 +412,19 @@ interface FooterPreferences {
|
||||
enable: boolean;
|
||||
/** Whether the footer is fixed */
|
||||
fixed: boolean;
|
||||
/** Footer height */
|
||||
height: number;
|
||||
}
|
||||
|
||||
interface HeaderPreferences {
|
||||
/** Whether the header is enabled */
|
||||
enable: boolean;
|
||||
/** Header height */
|
||||
height: number;
|
||||
/** Whether the header is hidden, css-hidden */
|
||||
hidden: boolean;
|
||||
/** Header menu alignment */
|
||||
menuAlign: LayoutHeaderMenuAlignType;
|
||||
/** Header display mode */
|
||||
mode: LayoutHeaderModeType;
|
||||
}
|
||||
@@ -363,6 +432,8 @@ interface HeaderPreferences {
|
||||
interface LogoPreferences {
|
||||
/** Whether the logo is visible */
|
||||
enable: boolean;
|
||||
/** Logo image fitting method */
|
||||
fit: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down';
|
||||
/** Logo URL */
|
||||
source: string;
|
||||
}
|
||||
@@ -376,18 +447,30 @@ interface NavigationPreferences {
|
||||
styleType: NavigationStyleType;
|
||||
}
|
||||
interface SidebarPreferences {
|
||||
/** Automatically activate child menu when clicking on directory */
|
||||
autoActivateChild: boolean;
|
||||
/** Whether the sidebar is collapsed */
|
||||
collapsed: boolean;
|
||||
/** Whether the sidebar collapse button is visible */
|
||||
collapsedButton: boolean;
|
||||
/** Whether to show title when sidebar is collapsed */
|
||||
collapsedShowTitle: boolean;
|
||||
/** Sidebar collapse width */
|
||||
collapseWidth: number;
|
||||
/** Whether the sidebar is visible */
|
||||
enable: boolean;
|
||||
/** Menu auto-expand state */
|
||||
expandOnHover: boolean;
|
||||
/** Whether the sidebar extension area is collapsed */
|
||||
extraCollapse: boolean;
|
||||
/** Sidebar extension area collapse width */
|
||||
extraCollapsedWidth: number;
|
||||
/** Whether the sidebar fixed button is visible */
|
||||
fixedButton: boolean;
|
||||
/** Whether the sidebar is hidden - css */
|
||||
hidden: boolean;
|
||||
/** Mixed sidebar width */
|
||||
mixedWidth: number;
|
||||
/** Sidebar width */
|
||||
width: number;
|
||||
}
|
||||
@@ -414,6 +497,10 @@ interface TabbarPreferences {
|
||||
height: number;
|
||||
/** Whether tab caching is enabled */
|
||||
keepAlive: boolean;
|
||||
/** Maximum number of tabs */
|
||||
maxCount: number;
|
||||
/** Whether to close tab when middle-clicked */
|
||||
middleClickToClose: boolean;
|
||||
/** Whether tabs are persistent */
|
||||
persist: boolean;
|
||||
/** Whether icons in multiple tabs are enabled */
|
||||
@@ -424,6 +511,8 @@ interface TabbarPreferences {
|
||||
showMore: boolean;
|
||||
/** Tab style */
|
||||
styleType: TabsStyleType;
|
||||
/** Whether mouse wheel response is enabled */
|
||||
wheelable: boolean;
|
||||
}
|
||||
interface ThemePreferences {
|
||||
/** Built-in theme name */
|
||||
@@ -511,5 +600,6 @@ interface Preferences {
|
||||
|
||||
- The `overridesPreferences` method only needs to override a part of the configurations in the project. There's no need to override configurations that are not needed; they will automatically use the default settings.
|
||||
- Any configuration item can be overridden. You just need to override it within the `overridesPreferences` method. Do not modify the default configuration file.
|
||||
- Please clear the cache after changing the configuration, otherwise it may not take effect.
|
||||
|
||||
:::
|
||||
|
@@ -4,10 +4,11 @@ outline: deep
|
||||
|
||||
# Access Control
|
||||
|
||||
The framework has built-in two types of access control methods:
|
||||
The framework has built-in three types of access control methods:
|
||||
|
||||
- Determining whether a menu or button can be accessed based on user roles
|
||||
- Determining whether a menu or button can be accessed through an API
|
||||
- Mixed mode: Using both frontend and backend access control simultaneously
|
||||
|
||||
## Frontend Access Control
|
||||
|
||||
@@ -151,6 +152,43 @@ const dashboardMenus = [
|
||||
|
||||
At this point, the configuration is complete. You need to ensure that after logging in, the format of the menu returned by the interface is correct; otherwise, access will not be possible.
|
||||
|
||||
## Mixed Access Control
|
||||
|
||||
**Implementation Principle**: Mixed mode combines both frontend access control and backend access control methods. The system processes frontend fixed route permissions and backend dynamic menu data in parallel, ultimately merging both parts of routes to provide a more flexible access control solution.
|
||||
|
||||
**Advantages**: Combines the performance advantages of frontend control with the flexibility of backend control, suitable for complex business scenarios requiring permission management.
|
||||
|
||||
### Steps
|
||||
|
||||
- Ensure the current mode is set to mixed access control
|
||||
|
||||
Adjust `preferences.ts` in the corresponding application directory to ensure `accessMode='mixed'`.
|
||||
|
||||
```ts
|
||||
import { defineOverridesPreferences } from '@vben/preferences';
|
||||
|
||||
export const overridesPreferences = defineOverridesPreferences({
|
||||
// overrides
|
||||
app: {
|
||||
accessMode: 'mixed',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
- Configure frontend route permissions
|
||||
|
||||
Same as the route permission configuration method in [Frontend Access Control](#frontend-access-control) mode.
|
||||
|
||||
- Configure backend menu interface
|
||||
|
||||
Same as the interface configuration method in [Backend Access Control](#backend-access-control) mode.
|
||||
|
||||
- Ensure roles and permissions match
|
||||
|
||||
Must satisfy both frontend route permission configuration and backend menu data return requirements, ensuring user roles match the permission configurations of both modes.
|
||||
|
||||
At this point, the configuration is complete. Mixed mode will automatically merge frontend and backend routes, providing complete access control functionality.
|
||||
|
||||
## Fine-grained Control of Buttons
|
||||
|
||||
In some cases, we need to control the display of buttons with fine granularity. We can control the display of buttons through interfaces or roles.
|
||||
|
@@ -58,7 +58,7 @@ Open a terminal in your code directory and execute the following commands:
|
||||
cd vue-vben-admin
|
||||
|
||||
# Enable the project-specified version of pnpm
|
||||
corepack enable
|
||||
npm i -g corepack
|
||||
|
||||
# Install dependencies
|
||||
pnpm install
|
||||
|
@@ -18,7 +18,7 @@ If you encounter a problem, you can start looking from the following aspects:
|
||||
|
||||
## Dependency Issues
|
||||
|
||||
In a `Monorepo` project, it is necessary to develop the habit of executing `pnpm install` every time you `git pull` the code, as new dependency packages are often added. The project has already configured automatic execution of `pnpm install` in `.husky/git-merge`, but sometimes there might be issues. If it does not execute automatically, it is recommended to execute it manually once.
|
||||
In a `Monorepo` project, it's important to get into the habit of running `pnpm install` after every `git pull` because new dependencies are often added. The project has configured automatic execution of `pnpm install` in `lefthook.yml`, but sometimes there might be issues. If it does not execute automatically, it is recommended to execute it manually once.
|
||||
|
||||
## About Cache Update Issues
|
||||
|
||||
|
@@ -33,8 +33,8 @@ The project integrates the following code verification tools:
|
||||
- [Prettier](https://prettier.io/) for code formatting
|
||||
- [Commitlint](https://commitlint.js.org/) for checking the standard of git commit messages
|
||||
- [Publint](https://publint.dev/) for checking the standard of npm packages
|
||||
- [Lint Staged](https://github.com/lint-staged/lint-staged) for running code verification before git commits
|
||||
- [Cspell](https://cspell.org/) for checking spelling errors
|
||||
- [lefthook](https://github.com/evilmartians/lefthook) for managing Git hooks, automatically running code checks and formatting before commits
|
||||
|
||||
## ESLint
|
||||
|
||||
@@ -148,18 +148,66 @@ The cspell configuration file is `cspell.json`, which can be modified according
|
||||
|
||||
Git hooks are generally combined with various lints to check code style during git commits. If the check fails, the commit will not proceed. Developers need to modify and resubmit.
|
||||
|
||||
### husky
|
||||
### lefthook
|
||||
|
||||
One issue is that the check will verify all code, but we only want to check the code we are committing. This is where husky comes in.
|
||||
One issue is that the check will verify all code, but we only want to check the code we are committing. This is where lefthook comes in.
|
||||
|
||||
The most effective solution is to perform Lint checks locally before committing. A common practice is to use husky or pre-commit to perform a Lint check before local submission.
|
||||
The most effective solution is to perform Lint checks locally before committing. A common practice is to use lefthook to perform a Lint check before local submission.
|
||||
|
||||
The project defines corresponding hooks inside `.husky`.
|
||||
The project defines corresponding hooks inside `lefthook.yml`:
|
||||
|
||||
#### How to Disable Husky
|
||||
- `pre-commit`: Runs before commit, used for code formatting and checking
|
||||
|
||||
If you want to disable Husky, simply delete the .husky directory.
|
||||
- `code-workspace`: Updates VSCode workspace configuration
|
||||
- `lint-md`: Formats Markdown files
|
||||
- `lint-vue`: Formats and checks Vue files
|
||||
- `lint-js`: Formats and checks JavaScript/TypeScript files
|
||||
- `lint-style`: Formats and checks style files
|
||||
- `lint-package`: Formats package.json
|
||||
- `lint-json`: Formats other JSON files
|
||||
|
||||
### lint-staged
|
||||
- `post-merge`: Runs after merge, used for automatic dependency installation
|
||||
|
||||
Used for automatically fixing style issues of committed files. Its configuration file is `.lintstagedrc.mjs`, which can be modified according to project needs.
|
||||
- `install`: Runs `pnpm install` to install new dependencies
|
||||
|
||||
- `commit-msg`: Runs during commit, used for checking commit message format
|
||||
- `commitlint`: Uses commitlint to check commit messages
|
||||
|
||||
#### How to Disable lefthook
|
||||
|
||||
If you want to disable lefthook, there are two ways:
|
||||
|
||||
::: code-group
|
||||
|
||||
```bash [Temporary disable]
|
||||
git commit -m 'feat: add home page' --no-verify
|
||||
```
|
||||
|
||||
```bash [Permanent disable]
|
||||
# Simply delete the lefthook.yml file
|
||||
rm lefthook.yml
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
#### How to Modify lefthook Configuration
|
||||
|
||||
If you want to modify lefthook's configuration, you can edit the `lefthook.yml` file. For example:
|
||||
|
||||
```yaml
|
||||
pre-commit:
|
||||
parallel: true # Execute tasks in parallel
|
||||
jobs:
|
||||
- name: lint-js
|
||||
run: pnpm prettier --cache --ignore-unknown --write {staged_files}
|
||||
glob: '*.{js,jsx,ts,tsx}'
|
||||
```
|
||||
|
||||
Where:
|
||||
|
||||
- `parallel`: Whether to execute tasks in parallel
|
||||
- `jobs`: Defines the list of tasks to execute
|
||||
- `name`: Task name
|
||||
- `run`: Command to execute
|
||||
- `glob`: File pattern to match
|
||||
- `{staged_files}`: Represents the list of staged files
|
||||
|
@@ -214,7 +214,7 @@ server {
|
||||
|
||||
使用 nginx 处理项目部署后的跨域问题
|
||||
|
||||
1. 配置前端项目接口地址,在项目目录下的``.env.production`文件中配置:
|
||||
1. 配置前端项目接口地址,在项目目录下的`.env.production`文件中配置:
|
||||
|
||||
```bash
|
||||
VITE_GLOB_API_URL=/api
|
||||
|
@@ -98,8 +98,8 @@ npm 脚本是项目常见的配置,用于执行一些常见的任务,比如
|
||||
"postinstall": "pnpm -r run stub --if-present",
|
||||
// 只允许使用pnpm
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
// husky的安装
|
||||
"prepare": "is-ci || husky",
|
||||
// lefthook的安装
|
||||
"prepare": "is-ci || lefthook install",
|
||||
// 预览应用
|
||||
"preview": "turbo-run preview",
|
||||
// 包规范检查
|
||||
|
@@ -339,6 +339,10 @@ interface RouteMeta {
|
||||
| 'success'
|
||||
| 'warning'
|
||||
| string;
|
||||
/**
|
||||
* 路由的完整路径作为key(默认true)
|
||||
*/
|
||||
fullPathKey?: boolean;
|
||||
/**
|
||||
* 当前路由的子级在菜单中不展现
|
||||
* @default false
|
||||
@@ -502,6 +506,13 @@ interface RouteMeta {
|
||||
|
||||
用于配置页面的徽标颜色。
|
||||
|
||||
### fullPathKey
|
||||
|
||||
- 类型:`boolean`
|
||||
- 默认值:`true`
|
||||
|
||||
是否将路由的完整路径作为tab key(默认true)
|
||||
|
||||
### activePath
|
||||
|
||||
- 类型:`string`
|
||||
@@ -602,3 +613,32 @@ const { refresh } = useRefresh();
|
||||
refresh();
|
||||
</script>
|
||||
```
|
||||
|
||||
## 标签页与路由控制
|
||||
|
||||
在某些场景下,需要单个路由打开多个标签页,或者修改路由的query不打开新的标签页
|
||||
|
||||
每个标签页Tab使用唯一的key标识,设置Tab key有三种方式,优先级由高到低:
|
||||
|
||||
- 使用路由query参数pageKey
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router';
|
||||
// 跳转路由
|
||||
const router = useRouter();
|
||||
router.push({
|
||||
path: 'path',
|
||||
query: {
|
||||
pageKey: 'key',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
- 路由的完整路径作为key
|
||||
|
||||
`meta` 属性中的 `fullPathKey`不为false,则使用路由`fullPath`作为key
|
||||
|
||||
- 路由的path作为key
|
||||
|
||||
`meta` 属性中的 `fullPathKey`为false,则使用路由`path`作为key
|
||||
|
@@ -185,8 +185,15 @@ const defaultPreferences: Preferences = {
|
||||
colorWeakMode: false,
|
||||
compact: false,
|
||||
contentCompact: 'wide',
|
||||
contentCompactWidth: 1200,
|
||||
contentPadding: 0,
|
||||
contentPaddingBottom: 0,
|
||||
contentPaddingLeft: 0,
|
||||
contentPaddingRight: 0,
|
||||
contentPaddingTop: 0,
|
||||
defaultAvatar:
|
||||
'https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp',
|
||||
defaultHomePath: '/analytics',
|
||||
dynamicTitle: true,
|
||||
enableCheckUpdates: true,
|
||||
enablePreferences: true,
|
||||
@@ -194,10 +201,11 @@ const defaultPreferences: Preferences = {
|
||||
isMobile: false,
|
||||
layout: 'sidebar-nav',
|
||||
locale: 'zh-CN',
|
||||
loginExpiredMode: 'modal',
|
||||
loginExpiredMode: 'page',
|
||||
name: 'Vben Admin',
|
||||
preferencesButtonPosition: 'auto',
|
||||
watermark: false,
|
||||
zIndex: 200,
|
||||
},
|
||||
breadcrumb: {
|
||||
enable: true,
|
||||
@@ -213,18 +221,23 @@ const defaultPreferences: Preferences = {
|
||||
enable: true,
|
||||
icp: '',
|
||||
icpLink: '',
|
||||
settingShow: true,
|
||||
},
|
||||
footer: {
|
||||
enable: true,
|
||||
enable: false,
|
||||
fixed: false,
|
||||
height: 32,
|
||||
},
|
||||
header: {
|
||||
enable: true,
|
||||
height: 50,
|
||||
hidden: false,
|
||||
menuAlign: 'start',
|
||||
mode: 'fixed',
|
||||
},
|
||||
logo: {
|
||||
enable: true,
|
||||
fit: 'contain',
|
||||
source: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp',
|
||||
},
|
||||
navigation: {
|
||||
@@ -242,23 +255,31 @@ const defaultPreferences: Preferences = {
|
||||
sidebar: {
|
||||
autoActivateChild: false,
|
||||
collapsed: false,
|
||||
collapsedButton: true,
|
||||
collapsedShowTitle: false,
|
||||
collapseWidth: 60,
|
||||
enable: true,
|
||||
expandOnHover: true,
|
||||
extraCollapse: true,
|
||||
extraCollapse: false,
|
||||
extraCollapsedWidth: 60,
|
||||
fixedButton: true,
|
||||
hidden: false,
|
||||
width: 230,
|
||||
mixedWidth: 80,
|
||||
width: 224,
|
||||
},
|
||||
tabbar: {
|
||||
draggable: true,
|
||||
enable: true,
|
||||
height: 36,
|
||||
height: 38,
|
||||
keepAlive: true,
|
||||
maxCount: 0,
|
||||
middleClickToClose: false,
|
||||
persist: true,
|
||||
showIcon: true,
|
||||
showMaximize: true,
|
||||
showMore: true,
|
||||
styleType: 'chrome',
|
||||
wheelable: true,
|
||||
},
|
||||
theme: {
|
||||
builtinType: 'default',
|
||||
@@ -269,7 +290,7 @@ const defaultPreferences: Preferences = {
|
||||
mode: 'dark',
|
||||
radius: '0.5',
|
||||
semiDarkHeader: false,
|
||||
semiDarkSidebar: true,
|
||||
semiDarkSidebar: false,
|
||||
},
|
||||
transition: {
|
||||
enable: true,
|
||||
@@ -310,8 +331,22 @@ interface AppPreferences {
|
||||
compact: boolean;
|
||||
/** 是否开启内容紧凑模式 */
|
||||
contentCompact: ContentCompactType;
|
||||
/** 内容紧凑宽度 */
|
||||
contentCompactWidth: number;
|
||||
/** 内容内边距 */
|
||||
contentPadding: number;
|
||||
/** 内容底部内边距 */
|
||||
contentPaddingBottom: number;
|
||||
/** 内容左侧内边距 */
|
||||
contentPaddingLeft: number;
|
||||
/** 内容右侧内边距 */
|
||||
contentPaddingRight: number;
|
||||
/** 内容顶部内边距 */
|
||||
contentPaddingTop: number;
|
||||
// /** 应用默认头像 */
|
||||
defaultAvatar: string;
|
||||
/** 默认首页地址 */
|
||||
defaultHomePath: string;
|
||||
// /** 开启动态标题 */
|
||||
dynamicTitle: boolean;
|
||||
/** 是否开启检查更新 */
|
||||
@@ -338,6 +373,8 @@ interface AppPreferences {
|
||||
* @zh_CN 是否开启水印
|
||||
*/
|
||||
watermark: boolean;
|
||||
/** z-index */
|
||||
zIndex: number;
|
||||
}
|
||||
|
||||
interface BreadcrumbPreferences {
|
||||
@@ -366,6 +403,8 @@ interface CopyrightPreferences {
|
||||
icp: string;
|
||||
/** 备案号链接 */
|
||||
icpLink: string;
|
||||
/** 设置面板是否显示*/
|
||||
settingShow?: boolean;
|
||||
}
|
||||
|
||||
interface FooterPreferences {
|
||||
@@ -373,13 +412,19 @@ interface FooterPreferences {
|
||||
enable: boolean;
|
||||
/** 底栏是否固定 */
|
||||
fixed: boolean;
|
||||
/** 底栏高度 */
|
||||
height: number;
|
||||
}
|
||||
|
||||
interface HeaderPreferences {
|
||||
/** 顶栏是否启用 */
|
||||
enable: boolean;
|
||||
/** 顶栏高度 */
|
||||
height: number;
|
||||
/** 顶栏是否隐藏,css-隐藏 */
|
||||
hidden: boolean;
|
||||
/** 顶栏菜单位置 */
|
||||
menuAlign: LayoutHeaderMenuAlignType;
|
||||
/** header显示模式 */
|
||||
mode: LayoutHeaderModeType;
|
||||
}
|
||||
@@ -387,6 +432,8 @@ interface HeaderPreferences {
|
||||
interface LogoPreferences {
|
||||
/** logo是否可见 */
|
||||
enable: boolean;
|
||||
/** logo图片适应方式 */
|
||||
fit: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down';
|
||||
/** logo地址 */
|
||||
source: string;
|
||||
}
|
||||
@@ -401,18 +448,30 @@ interface NavigationPreferences {
|
||||
}
|
||||
|
||||
interface SidebarPreferences {
|
||||
/** 点击目录时自动激活子菜单 */
|
||||
autoActivateChild: boolean;
|
||||
/** 侧边栏是否折叠 */
|
||||
collapsed: boolean;
|
||||
/** 侧边栏折叠按钮是否可见 */
|
||||
collapsedButton: boolean;
|
||||
/** 侧边栏折叠时,是否显示title */
|
||||
collapsedShowTitle: boolean;
|
||||
/** 侧边栏折叠宽度 */
|
||||
collapseWidth: number;
|
||||
/** 侧边栏是否可见 */
|
||||
enable: boolean;
|
||||
/** 菜单自动展开状态 */
|
||||
expandOnHover: boolean;
|
||||
/** 侧边栏扩展区域是否折叠 */
|
||||
extraCollapse: boolean;
|
||||
/** 侧边栏扩展区域折叠宽度 */
|
||||
extraCollapsedWidth: number;
|
||||
/** 侧边栏固定按钮是否可见 */
|
||||
fixedButton: boolean;
|
||||
/** 侧边栏是否隐藏 - css */
|
||||
hidden: boolean;
|
||||
/** 混合侧边栏宽度 */
|
||||
mixedWidth: number;
|
||||
/** 侧边栏宽度 */
|
||||
width: number;
|
||||
}
|
||||
@@ -439,6 +498,10 @@ interface TabbarPreferences {
|
||||
height: number;
|
||||
/** 开启标签页缓存功能 */
|
||||
keepAlive: boolean;
|
||||
/** 限制最大数量 */
|
||||
maxCount: number;
|
||||
/** 是否点击中键时关闭标签 */
|
||||
middleClickToClose: boolean;
|
||||
/** 是否持久化标签 */
|
||||
persist: boolean;
|
||||
/** 是否开启多标签页图标 */
|
||||
@@ -449,6 +512,8 @@ interface TabbarPreferences {
|
||||
showMore: boolean;
|
||||
/** 标签页风格 */
|
||||
styleType: TabsStyleType;
|
||||
/** 是否开启鼠标滚轮响应 */
|
||||
wheelable: boolean;
|
||||
}
|
||||
|
||||
interface ThemePreferences {
|
||||
|
@@ -4,10 +4,11 @@ outline: deep
|
||||
|
||||
# 权限
|
||||
|
||||
框架内置了两种权限控制方式:
|
||||
框架内置了三种权限控制方式:
|
||||
|
||||
- 通过用户角色来判断菜单或者按钮是否可以访问
|
||||
- 通过接口来判断菜单或者按钮是否可以访问
|
||||
- 混合模式:同时使用前端和后端权限控制
|
||||
|
||||
## 前端访问控制
|
||||
|
||||
@@ -114,8 +115,6 @@ async function generateAccess(options: GenerateMenuAndRoutesOptions) {
|
||||
```ts
|
||||
const dashboardMenus = [
|
||||
{
|
||||
// 这里固定写死 BasicLayout,不可更改
|
||||
component: 'BasicLayout',
|
||||
meta: {
|
||||
order: -1,
|
||||
title: 'page.dashboard.title',
|
||||
@@ -144,6 +143,16 @@ const dashboardMenus = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Test',
|
||||
path: '/test',
|
||||
component: '/test/index',
|
||||
meta: {
|
||||
title: 'page.test',
|
||||
// 部分特殊页面如果不需要基础布局(页面顶部和侧边栏),可将noBasicLayout设置为true
|
||||
noBasicLayout: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
@@ -151,6 +160,43 @@ const dashboardMenus = [
|
||||
|
||||
到这里,就已经配置完成,你需要确保登录后,接口返回的菜单格式正确,否则无法访问。
|
||||
|
||||
## 混合访问控制
|
||||
|
||||
**实现原理**: 混合模式同时结合了前端访问控制和后端访问控制两种方式。系统会并行处理前端固定路由权限和后端动态菜单数据,最终将两部分路由合并,提供更灵活的权限控制方案。
|
||||
|
||||
**优点**: 兼具前端控制的性能优势和后端控制的灵活性,适合复杂业务场景下的权限管理。
|
||||
|
||||
### 步骤
|
||||
|
||||
- 确保当前模式为混合访问控制模式
|
||||
|
||||
调整对应应用目录下的`preferences.ts`,确保`accessMode='mixed'`。
|
||||
|
||||
```ts
|
||||
import { defineOverridesPreferences } from '@vben/preferences';
|
||||
|
||||
export const overridesPreferences = defineOverridesPreferences({
|
||||
// overrides
|
||||
app: {
|
||||
accessMode: 'mixed',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
- 配置前端路由权限
|
||||
|
||||
同[前端访问控制](#前端访问控制)模式的路由权限配置方式。
|
||||
|
||||
- 配置后端菜单接口
|
||||
|
||||
同[后端访问控制](#后端访问控制)模式的接口配置方式。
|
||||
|
||||
- 确保角色和权限匹配
|
||||
|
||||
需要同时满足前端路由权限配置和后端菜单数据返回的要求,确保用户角色与两种模式的权限配置都匹配。
|
||||
|
||||
到这里,就已经配置完成,混合模式会自动合并前端和后端的路由,提供完整的权限控制功能。
|
||||
|
||||
## 按钮细粒度控制
|
||||
|
||||
在某些情况下,我们需要对按钮进行细粒度的控制,我们可以借助接口或者角色来控制按钮的显示。
|
||||
|
@@ -58,7 +58,7 @@ git clone https://gitee.com/annsion/vue-vben-admin.git
|
||||
cd vue-vben-admin
|
||||
|
||||
# 使用项目指定的pnpm版本进行依赖安装
|
||||
corepack enable
|
||||
npm i -g corepack
|
||||
|
||||
# 安装依赖
|
||||
pnpm install
|
||||
|
@@ -18,7 +18,7 @@
|
||||
|
||||
## 依赖问题
|
||||
|
||||
在 `Monorepo` 项目下,需要养成每次 `git pull`代码都要执行`pnpm install`的习惯,因为经常会有新的依赖包加入,项目在`.husky/git-merge`已经配置了自动执行`pnpm install`,但是有时候会出现问题,如果没有自动执行,建议手动执行一次。
|
||||
在 `Monorepo` 项目下,需要养成每次 `git pull`代码都要执行`pnpm install`的习惯,因为经常会有新的依赖包加入,项目在`lefthook.yml`已经配置了自动执行`pnpm install`,但是有时候会出现问题,如果没有自动执行,建议手动执行一次。
|
||||
|
||||
## 关于缓存更新问题
|
||||
|
||||
|
@@ -33,8 +33,8 @@
|
||||
- [Prettier](https://prettier.io/) 用于代码格式化
|
||||
- [Commitlint](https://commitlint.js.org/) 用于检查 git 提交信息的规范
|
||||
- [Publint](https://publint.dev/) 用于检查 npm 包的规范
|
||||
- [Lint Staged](https://github.com/lint-staged/lint-staged) 用于在 git 提交前运行代码校验
|
||||
- [Cspell](https://cspell.org/) 用于检查拼写错误
|
||||
- [lefthook](https://github.com/evilmartians/lefthook) 用于管理 Git hooks,在提交前自动运行代码校验和格式化
|
||||
|
||||
## ESLint
|
||||
|
||||
@@ -148,18 +148,66 @@ cspell 配置文件为 `cspell.json`,可以根据项目需求进行修改。
|
||||
|
||||
git hook 一般结合各种 lint,在 git 提交代码的时候进行代码风格校验,如果校验没通过,则不会进行提交。需要开发者自行修改后再次进行提交
|
||||
|
||||
### husky
|
||||
### lefthook
|
||||
|
||||
有一个问题就是校验会校验全部代码,但是我们只想校验我们自己提交的代码,这个时候就可以使用 husky。
|
||||
有一个问题就是校验会校验全部代码,但是我们只想校验我们自己提交的代码,这个时候就可以使用 lefthook。
|
||||
|
||||
最有效的解决方案就是将 Lint 校验放到本地,常见做法是使用 husky 或者 pre-commit 在本地提交之前先做一次 Lint 校验。
|
||||
最有效的解决方案就是将 Lint 校验放到本地,常见做法是使用 lefthook 在本地提交之前先做一次 Lint 校验。
|
||||
|
||||
项目在 `.husky` 内部定义了相应的 hooks
|
||||
项目在 `lefthook.yml` 内部定义了相应的 hooks:
|
||||
|
||||
#### 如何关闭 Husky
|
||||
- `pre-commit`: 在提交前运行,用于代码格式化和检查
|
||||
|
||||
如果你想关闭 Husky,直接删除 `.husky` 目录即可。
|
||||
- `code-workspace`: 更新 VSCode 工作区配置
|
||||
- `lint-md`: 格式化 Markdown 文件
|
||||
- `lint-vue`: 格式化并检查 Vue 文件
|
||||
- `lint-js`: 格式化并检查 JavaScript/TypeScript 文件
|
||||
- `lint-style`: 格式化并检查样式文件
|
||||
- `lint-package`: 格式化 package.json
|
||||
- `lint-json`: 格式化其他 JSON 文件
|
||||
|
||||
### lint-staged
|
||||
- `post-merge`: 在合并后运行,用于自动安装依赖
|
||||
|
||||
用于自动修复提交文件风格问题,其配置文件为 `.lintstagedrc.mjs`,可以根据项目需求进行修改。
|
||||
- `install`: 运行 `pnpm install` 安装新依赖
|
||||
|
||||
- `commit-msg`: 在提交时运行,用于检查提交信息格式
|
||||
- `commitlint`: 使用 commitlint 检查提交信息
|
||||
|
||||
#### 如何关闭 lefthook
|
||||
|
||||
如果你想关闭 lefthook,有两种方式:
|
||||
|
||||
::: code-group
|
||||
|
||||
```bash [临时关闭]
|
||||
git commit -m 'feat: add home page' --no-verify
|
||||
```
|
||||
|
||||
```bash [永久关闭]
|
||||
# 删除 lefthook.yml 文件即可
|
||||
rm lefthook.yml
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
#### 如何修改 lefthook 配置
|
||||
|
||||
如果你想修改 lefthook 的配置,可以编辑 `lefthook.yml` 文件。例如:
|
||||
|
||||
```yaml
|
||||
pre-commit:
|
||||
parallel: true # 并行执行任务
|
||||
jobs:
|
||||
- name: lint-js
|
||||
run: pnpm prettier --cache --ignore-unknown --write {staged_files}
|
||||
glob: '*.{js,jsx,ts,tsx}'
|
||||
```
|
||||
|
||||
其中:
|
||||
|
||||
- `parallel`: 是否并行执行任务
|
||||
- `jobs`: 定义要执行的任务列表
|
||||
- `name`: 任务名称
|
||||
- `run`: 要执行的命令
|
||||
- `glob`: 匹配的文件模式
|
||||
- `{staged_files}`: 表示暂存的文件列表
|
||||
|
@@ -11,3 +11,7 @@
|
||||
当前只有对应的包下面存在 `tailwind.config.mjs` 文件才会启用 tailwindcss 的编译,否则不会启用 tailwindcss。如果你是纯粹的 SDK 包,不需要使用 tailwindcss,可以不用创建 `tailwind.config.mjs` 文件。
|
||||
|
||||
:::
|
||||
|
||||
## 提示
|
||||
|
||||
现`tailwindcss`已至v4.x版本,使用方法与`tailwindcss: ^3.4.17`有差异,v4.0无法与v3.x版本兼容,在开发前请确认`package.json`中的`tailwindcss`版本。
|
||||
|
@@ -20,6 +20,9 @@ hero:
|
||||
- theme: alt
|
||||
text: 在 GitHub 查看
|
||||
link: https://github.com/vbenjs/vue-vben-admin
|
||||
- theme: alt
|
||||
text: DeepWiki 文档
|
||||
link: https://deepwiki.com/vbenjs/vue-vben-admin
|
||||
|
||||
features:
|
||||
- icon: 🚀
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/commitlint-config",
|
||||
"version": "5.5.4",
|
||||
"version": "5.5.6",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
|
@@ -70,7 +70,7 @@ export async function perfectionist(): Promise<Linter.Config[]> {
|
||||
},
|
||||
],
|
||||
'perfectionist/sort-objects': [
|
||||
'error',
|
||||
'off',
|
||||
{
|
||||
customGroups: {
|
||||
items: 'items',
|
||||
|
@@ -10,7 +10,15 @@ export async function vue(): Promise<Linter.Config[]> {
|
||||
interopDefault(import('@typescript-eslint/parser')),
|
||||
] as const);
|
||||
|
||||
const flatEssential = pluginVue.configs?.['flat/essential'] || [];
|
||||
const flatStronglyRecommended =
|
||||
pluginVue.configs?.['flat/strongly-recommended'] || [];
|
||||
const flatRecommended = pluginVue.configs?.['flat/recommended'] || [];
|
||||
|
||||
return [
|
||||
...flatEssential,
|
||||
...flatStronglyRecommended,
|
||||
...flatRecommended,
|
||||
{
|
||||
files: ['**/*.vue'],
|
||||
languageOptions: {
|
||||
@@ -43,12 +51,9 @@ export async function vue(): Promise<Linter.Config[]> {
|
||||
plugins: {
|
||||
vue: pluginVue,
|
||||
},
|
||||
processor: pluginVue.processors['.vue'],
|
||||
processor: pluginVue.processors?.['.vue'],
|
||||
rules: {
|
||||
...pluginVue.configs.base.rules,
|
||||
...pluginVue.configs['vue3-essential'].rules,
|
||||
...pluginVue.configs['vue3-strongly-recommended'].rules,
|
||||
...pluginVue.configs['vue3-recommended'].rules,
|
||||
...pluginVue.configs?.base?.rules,
|
||||
|
||||
'vue/attribute-hyphenation': [
|
||||
'error',
|
||||
@@ -131,7 +136,6 @@ export async function vue(): Promise<Linter.Config[]> {
|
||||
'vue/require-default-prop': 'error',
|
||||
'vue/require-explicit-emits': 'error',
|
||||
'vue/require-prop-types': 'off',
|
||||
'vue/script-setup-uses-vars': 'error',
|
||||
'vue/singleline-html-element-content-newline': 'off',
|
||||
'vue/space-infix-ops': 'error',
|
||||
'vue/space-unary-ops': ['error', { nonwords: false, words: true }],
|
||||
|
@@ -28,6 +28,13 @@ const customConfig: Linter.Config[] = [
|
||||
'perfectionist/sort-objects': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/**.vue'],
|
||||
ignores: restrictedImportIgnores,
|
||||
rules: {
|
||||
'perfectionist/sort-objects': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
// apps内部的一些基础规则
|
||||
files: ['apps/**/**'],
|
||||
|
@@ -43,6 +43,7 @@ export default {
|
||||
'stylelint-scss',
|
||||
],
|
||||
rules: {
|
||||
'at-rule-no-deprecated': null,
|
||||
'at-rule-no-unknown': [
|
||||
true,
|
||||
{
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/stylelint-config",
|
||||
"version": "5.5.4",
|
||||
"version": "5.5.6",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/node-utils",
|
||||
"version": "5.5.4",
|
||||
"version": "5.5.6",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/tailwind-config",
|
||||
"version": "5.5.4",
|
||||
"version": "5.5.6",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
@@ -12,7 +12,7 @@
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"stub": "pnpm unbuild"
|
||||
"stub": "pnpm unbuild --stub"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user