Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev
This commit is contained in:
commit
b0763d6429
@ -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',
|
|
||||||
],
|
|
||||||
};
|
|
2
.npmrc
2
.npmrc
@ -1,5 +1,5 @@
|
|||||||
registry = "https://registry.npmmirror.com"
|
registry = "https://registry.npmmirror.com"
|
||||||
public-hoist-pattern[]=husky
|
public-hoist-pattern[]=lefthook
|
||||||
public-hoist-pattern[]=eslint
|
public-hoist-pattern[]=eslint
|
||||||
public-hoist-pattern[]=prettier
|
public-hoist-pattern[]=prettier
|
||||||
public-hoist-pattern[]=prettier-plugin-tailwindcss
|
public-hoist-pattern[]=prettier-plugin-tailwindcss
|
||||||
|
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@ -220,7 +220,7 @@
|
|||||||
"*.env": "$(capture).env.*",
|
"*.env": "$(capture).env.*",
|
||||||
"README.md": "README*,CHANGELOG*,LICENSE,CNAME",
|
"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",
|
"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.*"
|
"tailwind.config.mjs": "postcss.*"
|
||||||
},
|
},
|
||||||
"commentTranslate.hover.enabled": false,
|
"commentTranslate.hover.enabled": false,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import type { Router } from 'vue-router';
|
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 { preferences } from '@vben/preferences';
|
||||||
import { useAccessStore, useUserStore } from '@vben/stores';
|
import { useAccessStore, useUserStore } from '@vben/stores';
|
||||||
import { startProgress, stopProgress } from '@vben/utils';
|
import { startProgress, stopProgress } from '@vben/utils';
|
||||||
@ -56,7 +56,7 @@ function setupAccessGuard(router: Router) {
|
|||||||
return decodeURIComponent(
|
return decodeURIComponent(
|
||||||
(to.query?.redirect as string) ||
|
(to.query?.redirect as string) ||
|
||||||
userStore.userInfo?.homePath ||
|
userStore.userInfo?.homePath ||
|
||||||
DEFAULT_HOME_PATH,
|
preferences.app.defaultHomePath,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@ -75,7 +75,7 @@ function setupAccessGuard(router: Router) {
|
|||||||
path: LOGIN_PATH,
|
path: LOGIN_PATH,
|
||||||
// 如不需要,直接删除 query
|
// 如不需要,直接删除 query
|
||||||
query:
|
query:
|
||||||
to.fullPath === DEFAULT_HOME_PATH
|
to.fullPath === preferences.app.defaultHomePath
|
||||||
? {}
|
? {}
|
||||||
: { redirect: encodeURIComponent(to.fullPath) },
|
: { redirect: encodeURIComponent(to.fullPath) },
|
||||||
// 携带当前跳转的页面,登录后重新跳转该页面
|
// 携带当前跳转的页面,登录后重新跳转该页面
|
||||||
@ -108,8 +108,8 @@ function setupAccessGuard(router: Router) {
|
|||||||
accessStore.setAccessRoutes(accessibleRoutes);
|
accessStore.setAccessRoutes(accessibleRoutes);
|
||||||
accessStore.setIsAccessChecked(true);
|
accessStore.setIsAccessChecked(true);
|
||||||
const redirectPath = (from.query.redirect ??
|
const redirectPath = (from.query.redirect ??
|
||||||
(to.path === DEFAULT_HOME_PATH
|
(to.path === preferences.app.defaultHomePath
|
||||||
? userInfo.homePath || DEFAULT_HOME_PATH
|
? userInfo.homePath || preferences.app.defaultHomePath
|
||||||
: to.fullPath)) as string;
|
: to.fullPath)) as string;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import type { RouteRecordRaw } from 'vue-router';
|
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';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
@ -34,7 +35,7 @@ const coreRoutes: RouteRecordRaw[] = [
|
|||||||
},
|
},
|
||||||
name: 'Root',
|
name: 'Root',
|
||||||
path: '/',
|
path: '/',
|
||||||
redirect: DEFAULT_HOME_PATH,
|
redirect: preferences.app.defaultHomePath,
|
||||||
children: [],
|
children: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -4,7 +4,8 @@ import type { UserInfo } from '@vben/types';
|
|||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
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 { resetAllStores, useAccessStore, useUserStore } from '@vben/stores';
|
||||||
|
|
||||||
import { notification } from 'ant-design-vue';
|
import { notification } from 'ant-design-vue';
|
||||||
@ -55,7 +56,9 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
if (accessStore.loginExpired) {
|
if (accessStore.loginExpired) {
|
||||||
accessStore.setLoginExpired(false);
|
accessStore.setLoginExpired(false);
|
||||||
} else {
|
} else {
|
||||||
onSuccess ? await onSuccess?.() : await router.push(DEFAULT_HOME_PATH);
|
onSuccess
|
||||||
|
? await onSuccess?.()
|
||||||
|
: await router.push(preferences.app.defaultHomePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userInfo?.realName) {
|
if (userInfo?.realName) {
|
||||||
|
@ -78,7 +78,7 @@ const [Drawer, drawerApi] = useVbenDrawer({
|
|||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| appendToMain | 是否挂载到内容区域(默认挂载到body) | `boolean` | `false` |
|
| appendToMain | 是否挂载到内容区域(默认挂载到body) | `boolean` | `false` |
|
||||||
| connectedComponent | 连接另一个Modal组件 | `Component` | - |
|
| connectedComponent | 连接另一个Modal组件 | `Component` | - |
|
||||||
| destroyOnClose | 关闭时销毁`connectedComponent` | `boolean` | `false` |
|
| destroyOnClose | 关闭时销毁 | `boolean` | `false` |
|
||||||
| title | 标题 | `string\|slot` | - |
|
| title | 标题 | `string\|slot` | - |
|
||||||
| titleTooltip | 标题提示信息 | `string\|slot` | - |
|
| titleTooltip | 标题提示信息 | `string\|slot` | - |
|
||||||
| description | 描述信息 | `string\|slot` | - |
|
| description | 描述信息 | `string\|slot` | - |
|
||||||
|
@ -98,8 +98,8 @@ The execution command is: `pnpm run [script]` or `npm run [script]`.
|
|||||||
"postinstall": "pnpm -r run stub --if-present",
|
"postinstall": "pnpm -r run stub --if-present",
|
||||||
// Only allow using pnpm
|
// Only allow using pnpm
|
||||||
"preinstall": "npx only-allow pnpm",
|
"preinstall": "npx only-allow pnpm",
|
||||||
// Install husky
|
// Install lefthook
|
||||||
"prepare": "is-ci || husky",
|
"prepare": "is-ci || lefthook install",
|
||||||
// Preview the application
|
// Preview the application
|
||||||
"preview": "turbo-run preview",
|
"preview": "turbo-run preview",
|
||||||
// Package specification check
|
// Package specification check
|
||||||
|
@ -164,6 +164,7 @@ const defaultPreferences: Preferences = {
|
|||||||
contentCompact: 'wide',
|
contentCompact: 'wide',
|
||||||
defaultAvatar:
|
defaultAvatar:
|
||||||
'https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp',
|
'https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp',
|
||||||
|
defaultHomePath: '/analytics',
|
||||||
dynamicTitle: true,
|
dynamicTitle: true,
|
||||||
enableCheckUpdates: true,
|
enableCheckUpdates: true,
|
||||||
enablePreferences: true,
|
enablePreferences: true,
|
||||||
@ -289,6 +290,8 @@ interface AppPreferences {
|
|||||||
contentCompact: ContentCompactType;
|
contentCompact: ContentCompactType;
|
||||||
// /** Default application avatar */
|
// /** Default application avatar */
|
||||||
defaultAvatar: string;
|
defaultAvatar: string;
|
||||||
|
/** Default homepage path */
|
||||||
|
defaultHomePath: string;
|
||||||
// /** Enable dynamic title */
|
// /** Enable dynamic title */
|
||||||
dynamicTitle: boolean;
|
dynamicTitle: boolean;
|
||||||
/** Whether to enable update checks */
|
/** Whether to enable update checks */
|
||||||
|
@ -18,7 +18,7 @@ If you encounter a problem, you can start looking from the following aspects:
|
|||||||
|
|
||||||
## Dependency Issues
|
## 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
|
## About Cache Update Issues
|
||||||
|
|
||||||
|
@ -33,8 +33,8 @@ The project integrates the following code verification tools:
|
|||||||
- [Prettier](https://prettier.io/) for code formatting
|
- [Prettier](https://prettier.io/) for code formatting
|
||||||
- [Commitlint](https://commitlint.js.org/) for checking the standard of git commit messages
|
- [Commitlint](https://commitlint.js.org/) for checking the standard of git commit messages
|
||||||
- [Publint](https://publint.dev/) for checking the standard of npm packages
|
- [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
|
- [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
|
## 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.
|
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
|
||||||
|
@ -98,8 +98,8 @@ npm 脚本是项目常见的配置,用于执行一些常见的任务,比如
|
|||||||
"postinstall": "pnpm -r run stub --if-present",
|
"postinstall": "pnpm -r run stub --if-present",
|
||||||
// 只允许使用pnpm
|
// 只允许使用pnpm
|
||||||
"preinstall": "npx only-allow pnpm",
|
"preinstall": "npx only-allow pnpm",
|
||||||
// husky的安装
|
// lefthook的安装
|
||||||
"prepare": "is-ci || husky",
|
"prepare": "is-ci || lefthook install",
|
||||||
// 预览应用
|
// 预览应用
|
||||||
"preview": "turbo-run preview",
|
"preview": "turbo-run preview",
|
||||||
// 包规范检查
|
// 包规范检查
|
||||||
|
@ -187,6 +187,7 @@ const defaultPreferences: Preferences = {
|
|||||||
contentCompact: 'wide',
|
contentCompact: 'wide',
|
||||||
defaultAvatar:
|
defaultAvatar:
|
||||||
'https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp',
|
'https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp',
|
||||||
|
defaultHomePath: '/analytics',
|
||||||
dynamicTitle: true,
|
dynamicTitle: true,
|
||||||
enableCheckUpdates: true,
|
enableCheckUpdates: true,
|
||||||
enablePreferences: true,
|
enablePreferences: true,
|
||||||
@ -312,6 +313,8 @@ interface AppPreferences {
|
|||||||
contentCompact: ContentCompactType;
|
contentCompact: ContentCompactType;
|
||||||
// /** 应用默认头像 */
|
// /** 应用默认头像 */
|
||||||
defaultAvatar: string;
|
defaultAvatar: string;
|
||||||
|
/** 默认首页地址 */
|
||||||
|
defaultHomePath: string;
|
||||||
// /** 开启动态标题 */
|
// /** 开启动态标题 */
|
||||||
dynamicTitle: boolean;
|
dynamicTitle: boolean;
|
||||||
/** 是否开启检查更新 */
|
/** 是否开启检查更新 */
|
||||||
|
@ -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/) 用于代码格式化
|
- [Prettier](https://prettier.io/) 用于代码格式化
|
||||||
- [Commitlint](https://commitlint.js.org/) 用于检查 git 提交信息的规范
|
- [Commitlint](https://commitlint.js.org/) 用于检查 git 提交信息的规范
|
||||||
- [Publint](https://publint.dev/) 用于检查 npm 包的规范
|
- [Publint](https://publint.dev/) 用于检查 npm 包的规范
|
||||||
- [Lint Staged](https://github.com/lint-staged/lint-staged) 用于在 git 提交前运行代码校验
|
|
||||||
- [Cspell](https://cspell.org/) 用于检查拼写错误
|
- [Cspell](https://cspell.org/) 用于检查拼写错误
|
||||||
|
- [lefthook](https://github.com/evilmartians/lefthook) 用于管理 Git hooks,在提交前自动运行代码校验和格式化
|
||||||
|
|
||||||
## ESLint
|
## ESLint
|
||||||
|
|
||||||
@ -148,18 +148,66 @@ cspell 配置文件为 `cspell.json`,可以根据项目需求进行修改。
|
|||||||
|
|
||||||
git hook 一般结合各种 lint,在 git 提交代码的时候进行代码风格校验,如果校验没通过,则不会进行提交。需要开发者自行修改后再次进行提交
|
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}`: 表示暂存的文件列表
|
||||||
|
@ -70,7 +70,7 @@ export async function perfectionist(): Promise<Linter.Config[]> {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
'perfectionist/sort-objects': [
|
'perfectionist/sort-objects': [
|
||||||
'error',
|
'off',
|
||||||
{
|
{
|
||||||
customGroups: {
|
customGroups: {
|
||||||
items: 'items',
|
items: 'items',
|
||||||
|
@ -28,6 +28,13 @@ const customConfig: Linter.Config[] = [
|
|||||||
'perfectionist/sort-objects': 'off',
|
'perfectionist/sort-objects': 'off',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
files: ['**/**.vue'],
|
||||||
|
ignores: restrictedImportIgnores,
|
||||||
|
rules: {
|
||||||
|
'perfectionist/sort-objects': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
// apps内部的一些基础规则
|
// apps内部的一些基础规则
|
||||||
files: ['apps/**/**'],
|
files: ['apps/**/**'],
|
||||||
|
76
lefthook.yml
Normal file
76
lefthook.yml
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
# EXAMPLE USAGE:
|
||||||
|
#
|
||||||
|
# Refer for explanation to following link:
|
||||||
|
# https://lefthook.dev/configuration/
|
||||||
|
#
|
||||||
|
# pre-push:
|
||||||
|
# jobs:
|
||||||
|
# - name: packages audit
|
||||||
|
# tags:
|
||||||
|
# - frontend
|
||||||
|
# - security
|
||||||
|
# run: yarn audit
|
||||||
|
#
|
||||||
|
# - name: gems audit
|
||||||
|
# tags:
|
||||||
|
# - backend
|
||||||
|
# - security
|
||||||
|
# run: bundle audit
|
||||||
|
#
|
||||||
|
# pre-commit:
|
||||||
|
# parallel: true
|
||||||
|
# jobs:
|
||||||
|
# - run: yarn eslint {staged_files}
|
||||||
|
# glob: "*.{js,ts,jsx,tsx}"
|
||||||
|
#
|
||||||
|
# - name: rubocop
|
||||||
|
# glob: "*.rb"
|
||||||
|
# exclude:
|
||||||
|
# - config/application.rb
|
||||||
|
# - config/routes.rb
|
||||||
|
# run: bundle exec rubocop --force-exclusion {all_files}
|
||||||
|
#
|
||||||
|
# - name: govet
|
||||||
|
# files: git ls-files -m
|
||||||
|
# glob: "*.go"
|
||||||
|
# run: go vet {files}
|
||||||
|
#
|
||||||
|
# - script: "hello.js"
|
||||||
|
# runner: node
|
||||||
|
#
|
||||||
|
# - script: "hello.go"
|
||||||
|
# runner: go run
|
||||||
|
|
||||||
|
pre-commit:
|
||||||
|
parallel: true
|
||||||
|
commands:
|
||||||
|
code-workspace:
|
||||||
|
run: pnpm vsh code-workspace --auto-commit
|
||||||
|
lint-md:
|
||||||
|
run: pnpm prettier --cache --ignore-unknown --write {staged_files}
|
||||||
|
glob: '*.md'
|
||||||
|
lint-vue:
|
||||||
|
run: pnpm prettier --write {staged_files} && pnpm eslint --cache --fix {staged_files} && pnpm stylelint --fix --allow-empty-input {staged_files}
|
||||||
|
glob: '*.vue'
|
||||||
|
lint-js:
|
||||||
|
run: pnpm prettier --cache --ignore-unknown --write {staged_files} && pnpm eslint --cache --fix {staged_files}
|
||||||
|
glob: '*.{js,jsx,ts,tsx}'
|
||||||
|
lint-style:
|
||||||
|
run: pnpm prettier --cache --ignore-unknown --write {staged_files} && pnpm stylelint --fix --allow-empty-input {staged_files}
|
||||||
|
glob: '*.{scss,less,styl,html,vue,css}'
|
||||||
|
lint-package:
|
||||||
|
run: pnpm prettier --cache --write {staged_files}
|
||||||
|
glob: 'package.json'
|
||||||
|
lint-json:
|
||||||
|
run: pnpm prettier --cache --write --parser json {staged_files}
|
||||||
|
glob: '{!(package)*.json,*.code-snippets,.!(browserslist)*rc}'
|
||||||
|
|
||||||
|
post-merge:
|
||||||
|
commands:
|
||||||
|
install:
|
||||||
|
run: pnpm install
|
||||||
|
|
||||||
|
commit-msg:
|
||||||
|
commands:
|
||||||
|
commitlint:
|
||||||
|
run: pnpm exec commitlint --edit $1
|
@ -48,14 +48,14 @@
|
|||||||
"lint": "vsh lint",
|
"lint": "vsh lint",
|
||||||
"postinstall": "pnpm -r run stub --if-present",
|
"postinstall": "pnpm -r run stub --if-present",
|
||||||
"preinstall": "npx only-allow pnpm",
|
"preinstall": "npx only-allow pnpm",
|
||||||
"prepare": "is-ci || husky",
|
|
||||||
"preview": "turbo-run preview",
|
"preview": "turbo-run preview",
|
||||||
"publint": "vsh publint",
|
"publint": "vsh publint",
|
||||||
"reinstall": "pnpm clean --del-lock && pnpm install",
|
"reinstall": "pnpm clean --del-lock && pnpm install",
|
||||||
"test:unit": "vitest run --dom",
|
"test:unit": "vitest run --dom",
|
||||||
"test:e2e": "turbo run test:e2e",
|
"test:e2e": "turbo run test:e2e",
|
||||||
"update:deps": "npx taze -r -w",
|
"update:deps": "npx taze -r -w",
|
||||||
"version": "pnpm exec changeset version && pnpm install --no-frozen-lockfile"
|
"version": "pnpm exec changeset version && pnpm install --no-frozen-lockfile",
|
||||||
|
"catalog": "pnpx codemod pnpm/catalog"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@changesets/changelog-github": "catalog:",
|
"@changesets/changelog-github": "catalog:",
|
||||||
@ -78,9 +78,8 @@
|
|||||||
"cross-env": "catalog:",
|
"cross-env": "catalog:",
|
||||||
"cspell": "catalog:",
|
"cspell": "catalog:",
|
||||||
"happy-dom": "catalog:",
|
"happy-dom": "catalog:",
|
||||||
"husky": "catalog:",
|
|
||||||
"is-ci": "catalog:",
|
"is-ci": "catalog:",
|
||||||
"lint-staged": "catalog:",
|
"lefthook": "catalog:",
|
||||||
"playwright": "catalog:",
|
"playwright": "catalog:",
|
||||||
"rimraf": "catalog:",
|
"rimraf": "catalog:",
|
||||||
"tailwindcss": "catalog:",
|
"tailwindcss": "catalog:",
|
||||||
|
@ -11,6 +11,7 @@ exports[`defaultPreferences immutability test > should not modify the config obj
|
|||||||
"compact": false,
|
"compact": false,
|
||||||
"contentCompact": "wide",
|
"contentCompact": "wide",
|
||||||
"defaultAvatar": "https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp",
|
"defaultAvatar": "https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp",
|
||||||
|
"defaultHomePath": "/analytics",
|
||||||
"dynamicTitle": true,
|
"dynamicTitle": true,
|
||||||
"enableCheckUpdates": true,
|
"enableCheckUpdates": true,
|
||||||
"enablePreferences": true,
|
"enablePreferences": true,
|
||||||
|
@ -11,6 +11,7 @@ const defaultPreferences: Preferences = {
|
|||||||
contentCompact: 'wide',
|
contentCompact: 'wide',
|
||||||
defaultAvatar:
|
defaultAvatar:
|
||||||
'https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp',
|
'https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp',
|
||||||
|
defaultHomePath: '/analytics',
|
||||||
dynamicTitle: true,
|
dynamicTitle: true,
|
||||||
enableCheckUpdates: true,
|
enableCheckUpdates: true,
|
||||||
enablePreferences: true,
|
enablePreferences: true,
|
||||||
|
@ -35,6 +35,8 @@ interface AppPreferences {
|
|||||||
contentCompact: ContentCompactType;
|
contentCompact: ContentCompactType;
|
||||||
// /** 应用默认头像 */
|
// /** 应用默认头像 */
|
||||||
defaultAvatar: string;
|
defaultAvatar: string;
|
||||||
|
/** 默认首页地址 */
|
||||||
|
defaultHomePath: string;
|
||||||
// /** 开启动态标题 */
|
// /** 开启动态标题 */
|
||||||
dynamicTitle: boolean;
|
dynamicTitle: boolean;
|
||||||
/** 是否开启检查更新 */
|
/** 是否开启检查更新 */
|
||||||
|
@ -38,6 +38,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vben-core/composables": "workspace:*",
|
"@vben-core/composables": "workspace:*",
|
||||||
|
"@vben-core/icons": "workspace:*",
|
||||||
"@vben-core/shadcn-ui": "workspace:*",
|
"@vben-core/shadcn-ui": "workspace:*",
|
||||||
"@vben-core/shared": "workspace:*",
|
"@vben-core/shared": "workspace:*",
|
||||||
"@vben-core/typings": "workspace:*",
|
"@vben-core/typings": "workspace:*",
|
||||||
|
@ -5,6 +5,7 @@ import type { FormSchema, MaybeComponentProps } from '../types';
|
|||||||
|
|
||||||
import { computed, nextTick, onUnmounted, useTemplateRef, watch } from 'vue';
|
import { computed, nextTick, onUnmounted, useTemplateRef, watch } from 'vue';
|
||||||
|
|
||||||
|
import { CircleAlert } from '@vben-core/icons';
|
||||||
import {
|
import {
|
||||||
FormControl,
|
FormControl,
|
||||||
FormDescription,
|
FormDescription,
|
||||||
@ -12,6 +13,7 @@ import {
|
|||||||
FormItem,
|
FormItem,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
VbenRenderContent,
|
VbenRenderContent,
|
||||||
|
VbenTooltip,
|
||||||
} from '@vben-core/shadcn-ui';
|
} from '@vben-core/shadcn-ui';
|
||||||
import { cn, isFunction, isObject, isString } from '@vben-core/shared/utils';
|
import { cn, isFunction, isObject, isString } from '@vben-core/shared/utils';
|
||||||
|
|
||||||
@ -354,6 +356,24 @@ onUnmounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
<!-- <slot></slot> -->
|
<!-- <slot></slot> -->
|
||||||
</component>
|
</component>
|
||||||
|
<VbenTooltip
|
||||||
|
v-if="compact && isInValid"
|
||||||
|
:delay-duration="300"
|
||||||
|
side="left"
|
||||||
|
>
|
||||||
|
<template #trigger>
|
||||||
|
<slot name="trigger">
|
||||||
|
<CircleAlert
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
'text-foreground/80 hover:text-foreground inline-flex size-5 cursor-pointer',
|
||||||
|
)
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</slot>
|
||||||
|
</template>
|
||||||
|
<FormMessage />
|
||||||
|
</VbenTooltip>
|
||||||
</slot>
|
</slot>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<!-- 自定义后缀 -->
|
<!-- 自定义后缀 -->
|
||||||
@ -365,7 +385,7 @@ onUnmounted(() => {
|
|||||||
</FormDescription>
|
</FormDescription>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Transition name="slide-up">
|
<Transition name="slide-up" v-if="!compact">
|
||||||
<FormMessage class="absolute bottom-1" />
|
<FormMessage class="absolute bottom-1" />
|
||||||
</Transition>
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
|
@ -31,8 +31,8 @@ export function useVbenForm<
|
|||||||
h(VbenUseForm, { ...props, ...attrs, formApi: extendedApi }, slots);
|
h(VbenUseForm, { ...props, ...attrs, formApi: extendedApi }, slots);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
inheritAttrs: false,
|
|
||||||
name: 'VbenUseForm',
|
name: 'VbenUseForm',
|
||||||
|
inheritAttrs: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
// Add reactivity support
|
// Add reactivity support
|
||||||
|
@ -31,6 +31,7 @@ import {
|
|||||||
createSubMenuContext,
|
createSubMenuContext,
|
||||||
useMenuStyle,
|
useMenuStyle,
|
||||||
} from '../hooks';
|
} from '../hooks';
|
||||||
|
import { useMenuScroll } from '../hooks/use-menu-scroll';
|
||||||
import { flattedChildren } from '../utils';
|
import { flattedChildren } from '../utils';
|
||||||
import SubMenu from './sub-menu.vue';
|
import SubMenu from './sub-menu.vue';
|
||||||
|
|
||||||
@ -44,6 +45,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
mode: 'vertical',
|
mode: 'vertical',
|
||||||
rounded: true,
|
rounded: true,
|
||||||
theme: 'dark',
|
theme: 'dark',
|
||||||
|
scrollToActive: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@ -206,15 +208,19 @@ function handleResize() {
|
|||||||
isFirstTimeRender = false;
|
isFirstTimeRender = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getActivePaths() {
|
const enableScroll = computed(
|
||||||
const activeItem = activePath.value && items.value[activePath.value];
|
() => props.scrollToActive && props.mode === 'vertical' && !props.collapse,
|
||||||
|
);
|
||||||
|
|
||||||
if (!activeItem || props.mode === 'horizontal' || props.collapse) {
|
const { scrollToActiveItem } = useMenuScroll(activePath, {
|
||||||
return [];
|
enable: enableScroll,
|
||||||
}
|
delay: 320,
|
||||||
|
});
|
||||||
|
|
||||||
return activeItem.parentPaths;
|
// 监听 activePath 变化,自动滚动到激活项
|
||||||
}
|
watch(activePath, () => {
|
||||||
|
scrollToActiveItem();
|
||||||
|
});
|
||||||
|
|
||||||
// 默认展开菜单
|
// 默认展开菜单
|
||||||
function initMenu() {
|
function initMenu() {
|
||||||
@ -318,6 +324,16 @@ function removeSubMenu(subMenu: MenuItemRegistered) {
|
|||||||
function removeMenuItem(item: MenuItemRegistered) {
|
function removeMenuItem(item: MenuItemRegistered) {
|
||||||
Reflect.deleteProperty(items.value, item.path);
|
Reflect.deleteProperty(items.value, item.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getActivePaths() {
|
||||||
|
const activeItem = activePath.value && items.value[activePath.value];
|
||||||
|
|
||||||
|
if (!activeItem || props.mode === 'horizontal' || props.collapse) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return activeItem.parentPaths;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<ul
|
<ul
|
||||||
|
46
packages/@core/ui-kit/menu-ui/src/hooks/use-menu-scroll.ts
Normal file
46
packages/@core/ui-kit/menu-ui/src/hooks/use-menu-scroll.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import type { Ref } from 'vue';
|
||||||
|
|
||||||
|
import { watch } from 'vue';
|
||||||
|
|
||||||
|
import { useDebounceFn } from '@vueuse/core';
|
||||||
|
|
||||||
|
interface UseMenuScrollOptions {
|
||||||
|
delay?: number;
|
||||||
|
enable?: boolean | Ref<boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useMenuScroll(
|
||||||
|
activePath: Ref<string | undefined>,
|
||||||
|
options: UseMenuScrollOptions = {},
|
||||||
|
) {
|
||||||
|
const { enable = true, delay = 320 } = options;
|
||||||
|
|
||||||
|
function scrollToActiveItem() {
|
||||||
|
const isEnabled = typeof enable === 'boolean' ? enable : enable.value;
|
||||||
|
if (!isEnabled) return;
|
||||||
|
|
||||||
|
const activeElement = document.querySelector(
|
||||||
|
`aside li[role=menuitem].is-active`,
|
||||||
|
);
|
||||||
|
if (activeElement) {
|
||||||
|
activeElement.scrollIntoView({
|
||||||
|
behavior: 'smooth',
|
||||||
|
block: 'center',
|
||||||
|
inline: 'center',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const debouncedScroll = useDebounceFn(scrollToActiveItem, delay);
|
||||||
|
|
||||||
|
watch(activePath, () => {
|
||||||
|
const isEnabled = typeof enable === 'boolean' ? enable : enable.value;
|
||||||
|
if (!isEnabled) return;
|
||||||
|
|
||||||
|
debouncedScroll();
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
scrollToActiveItem,
|
||||||
|
};
|
||||||
|
}
|
@ -18,15 +18,9 @@ defineOptions({
|
|||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
collapse: false,
|
collapse: false,
|
||||||
// theme: 'dark',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const forward = useForwardProps(props);
|
const forward = useForwardProps(props);
|
||||||
|
|
||||||
// const emit = defineEmits<{
|
|
||||||
// 'update:openKeys': [key: Key[]];
|
|
||||||
// 'update:selectedKeys': [key: Key[]];
|
|
||||||
// }>();
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -41,6 +41,12 @@ interface MenuProps {
|
|||||||
*/
|
*/
|
||||||
rounded?: boolean;
|
rounded?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @zh_CN 是否自动滚动到激活的菜单项
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
scrollToActive?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @zh_CN 菜单主题
|
* @zh_CN 菜单主题
|
||||||
* @default dark
|
* @default dark
|
||||||
|
@ -35,7 +35,7 @@ interface Props extends DrawerProps {
|
|||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
appendToMain: false,
|
appendToMain: false,
|
||||||
closeIconPlacement: 'right',
|
closeIconPlacement: 'right',
|
||||||
destroyOnClose: true,
|
destroyOnClose: false,
|
||||||
drawerApi: undefined,
|
drawerApi: undefined,
|
||||||
submitting: false,
|
submitting: false,
|
||||||
zIndex: 1000,
|
zIndex: 1000,
|
||||||
|
@ -21,9 +21,7 @@ import VbenDrawer from './drawer.vue';
|
|||||||
|
|
||||||
const USER_DRAWER_INJECT_KEY = Symbol('VBEN_DRAWER_INJECT');
|
const USER_DRAWER_INJECT_KEY = Symbol('VBEN_DRAWER_INJECT');
|
||||||
|
|
||||||
const DEFAULT_DRAWER_PROPS: Partial<DrawerProps> = {
|
const DEFAULT_DRAWER_PROPS: Partial<DrawerProps> = {};
|
||||||
destroyOnClose: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
export function setDefaultDrawerProps(props: Partial<DrawerProps>) {
|
export function setDefaultDrawerProps(props: Partial<DrawerProps>) {
|
||||||
Object.assign(DEFAULT_DRAWER_PROPS, props);
|
Object.assign(DEFAULT_DRAWER_PROPS, props);
|
||||||
@ -66,9 +64,10 @@ export function useVbenDrawer<
|
|||||||
slots,
|
slots,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
// eslint-disable-next-line vue/one-component-per-file
|
||||||
{
|
{
|
||||||
inheritAttrs: false,
|
|
||||||
name: 'VbenParentDrawer',
|
name: 'VbenParentDrawer',
|
||||||
|
inheritAttrs: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
return [Drawer, extendedApi as ExtendedDrawerApi] as const;
|
return [Drawer, extendedApi as ExtendedDrawerApi] as const;
|
||||||
@ -107,9 +106,10 @@ export function useVbenDrawer<
|
|||||||
return () =>
|
return () =>
|
||||||
h(VbenDrawer, { ...props, ...attrs, drawerApi: extendedApi }, slots);
|
h(VbenDrawer, { ...props, ...attrs, drawerApi: extendedApi }, slots);
|
||||||
},
|
},
|
||||||
|
// eslint-disable-next-line vue/one-component-per-file
|
||||||
{
|
{
|
||||||
inheritAttrs: false,
|
|
||||||
name: 'VbenDrawer',
|
name: 'VbenDrawer',
|
||||||
|
inheritAttrs: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
injectData.extendApi?.(extendedApi);
|
injectData.extendApi?.(extendedApi);
|
||||||
|
@ -34,7 +34,7 @@ interface Props extends ModalProps {
|
|||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
appendToMain: false,
|
appendToMain: false,
|
||||||
destroyOnClose: true,
|
destroyOnClose: false,
|
||||||
modalApi: undefined,
|
modalApi: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -17,9 +17,7 @@ import VbenModal from './modal.vue';
|
|||||||
|
|
||||||
const USER_MODAL_INJECT_KEY = Symbol('VBEN_MODAL_INJECT');
|
const USER_MODAL_INJECT_KEY = Symbol('VBEN_MODAL_INJECT');
|
||||||
|
|
||||||
const DEFAULT_MODAL_PROPS: Partial<ModalProps> = {
|
const DEFAULT_MODAL_PROPS: Partial<ModalProps> = {};
|
||||||
destroyOnClose: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
export function setDefaultModalProps(props: Partial<ModalProps>) {
|
export function setDefaultModalProps(props: Partial<ModalProps>) {
|
||||||
Object.assign(DEFAULT_MODAL_PROPS, props);
|
Object.assign(DEFAULT_MODAL_PROPS, props);
|
||||||
@ -65,9 +63,10 @@ export function useVbenModal<TParentModalProps extends ModalProps = ModalProps>(
|
|||||||
slots,
|
slots,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
// eslint-disable-next-line vue/one-component-per-file
|
||||||
{
|
{
|
||||||
inheritAttrs: false,
|
|
||||||
name: 'VbenParentModal',
|
name: 'VbenParentModal',
|
||||||
|
inheritAttrs: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
return [Modal, extendedApi as ExtendedModalApi] as const;
|
return [Modal, extendedApi as ExtendedModalApi] as const;
|
||||||
@ -114,9 +113,10 @@ export function useVbenModal<TParentModalProps extends ModalProps = ModalProps>(
|
|||||||
slots,
|
slots,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
// eslint-disable-next-line vue/one-component-per-file
|
||||||
{
|
{
|
||||||
inheritAttrs: false,
|
|
||||||
name: 'VbenModal',
|
name: 'VbenModal',
|
||||||
|
inheritAttrs: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
injectData.extendApi?.(extendedApi);
|
injectData.extendApi?.(extendedApi);
|
||||||
|
@ -21,6 +21,7 @@ interface Props extends PopoverRootProps {
|
|||||||
class?: ClassType;
|
class?: ClassType;
|
||||||
contentClass?: ClassType;
|
contentClass?: ClassType;
|
||||||
contentProps?: PopoverContentProps;
|
contentProps?: PopoverContentProps;
|
||||||
|
triggerClass?: ClassType;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {});
|
const props = withDefaults(defineProps<Props>(), {});
|
||||||
@ -32,6 +33,7 @@ const delegatedProps = computed(() => {
|
|||||||
class: _cls,
|
class: _cls,
|
||||||
contentClass: _,
|
contentClass: _,
|
||||||
contentProps: _cProps,
|
contentProps: _cProps,
|
||||||
|
triggerClass: _tClass,
|
||||||
...delegated
|
...delegated
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
@ -43,7 +45,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<PopoverRoot v-bind="forwarded">
|
<PopoverRoot v-bind="forwarded">
|
||||||
<PopoverTrigger>
|
<PopoverTrigger :class="triggerClass">
|
||||||
<slot name="trigger"></slot>
|
<slot name="trigger"></slot>
|
||||||
|
|
||||||
<PopoverContent
|
<PopoverContent
|
||||||
|
@ -12,7 +12,6 @@ interface Props extends TabsProps {}
|
|||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'VbenTabsChrome',
|
name: 'VbenTabsChrome',
|
||||||
// eslint-disable-next-line perfectionist/sort-objects
|
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ interface Props extends TabsProps {}
|
|||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'VbenTabs',
|
name: 'VbenTabs',
|
||||||
// eslint-disable-next-line perfectionist/sort-objects
|
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
});
|
});
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
@ -15,5 +15,5 @@ pnpm add @vben/constants
|
|||||||
### 使用
|
### 使用
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { DEFAULT_HOME_PATH } from '@vben/constants';
|
import { LOGIN_PATH } from '@vben/constants';
|
||||||
```
|
```
|
||||||
|
@ -3,11 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const LOGIN_PATH = '/auth/login';
|
export const LOGIN_PATH = '/auth/login';
|
||||||
|
|
||||||
/**
|
|
||||||
* @zh_CN 默认首页地址
|
|
||||||
*/
|
|
||||||
export const DEFAULT_HOME_PATH = '/analytics';
|
|
||||||
|
|
||||||
export interface LanguageOption {
|
export interface LanguageOption {
|
||||||
label: string;
|
label: string;
|
||||||
value: 'en-US' | 'zh-CN';
|
value: 'en-US' | 'zh-CN';
|
||||||
|
@ -74,7 +74,7 @@ async function generateAccessible(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 生成菜单
|
// 生成菜单
|
||||||
const accessibleMenus = await generateMenus(accessibleRoutes, options.router);
|
const accessibleMenus = generateMenus(accessibleRoutes, options.router);
|
||||||
|
|
||||||
return { accessibleMenus, accessibleRoutes };
|
return { accessibleMenus, accessibleRoutes };
|
||||||
}
|
}
|
||||||
|
@ -165,13 +165,18 @@ const searchInputProps = computed(() => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function updateCurrentSelect(v: string) {
|
||||||
|
currentSelect.value = v;
|
||||||
|
}
|
||||||
|
|
||||||
defineExpose({ toggleOpenState, open, close });
|
defineExpose({ toggleOpenState, open, close });
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<VbenPopover
|
<VbenPopover
|
||||||
v-model:open="visible"
|
v-model:open="visible"
|
||||||
:content-props="{ align: 'end', alignOffset: -11, sideOffset: 8 }"
|
:content-props="{ align: 'end', alignOffset: -11, sideOffset: 8 }"
|
||||||
content-class="p-0 pt-3"
|
content-class="p-0 pt-3 w-full"
|
||||||
|
trigger-class="w-full"
|
||||||
>
|
>
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<template v-if="props.type === 'input'">
|
<template v-if="props.type === 'input'">
|
||||||
@ -183,6 +188,7 @@ defineExpose({ toggleOpenState, open, close });
|
|||||||
role="combobox"
|
role="combobox"
|
||||||
:aria-label="$t('ui.iconPicker.placeholder')"
|
:aria-label="$t('ui.iconPicker.placeholder')"
|
||||||
aria-expanded="visible"
|
aria-expanded="visible"
|
||||||
|
:[`onUpdate:${modelValueProp}`]="updateCurrentSelect"
|
||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
>
|
>
|
||||||
<template #[iconSlot]>
|
<template #[iconSlot]>
|
||||||
|
@ -35,6 +35,16 @@ const getZIndex = computed(() => {
|
|||||||
return props.zIndex || calcZIndex();
|
return props.zIndex || calcZIndex();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排除ant-message和loading:9999的z-index
|
||||||
|
*/
|
||||||
|
const zIndexExcludeClass = ['ant-message', 'loading'];
|
||||||
|
function isZIndexExcludeClass(element: Element) {
|
||||||
|
return zIndexExcludeClass.some((className) =>
|
||||||
|
element.classList.contains(className),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取最大的zIndex值
|
* 获取最大的zIndex值
|
||||||
*/
|
*/
|
||||||
@ -44,7 +54,11 @@ function calcZIndex() {
|
|||||||
[...elements].forEach((element) => {
|
[...elements].forEach((element) => {
|
||||||
const style = window.getComputedStyle(element);
|
const style = window.getComputedStyle(element);
|
||||||
const zIndex = style.getPropertyValue('z-index');
|
const zIndex = style.getPropertyValue('z-index');
|
||||||
if (zIndex && !Number.isNaN(Number.parseInt(zIndex))) {
|
if (
|
||||||
|
zIndex &&
|
||||||
|
!Number.isNaN(Number.parseInt(zIndex)) &&
|
||||||
|
!isZIndexExcludeClass(element)
|
||||||
|
) {
|
||||||
maxZ = Math.max(maxZ, Number.parseInt(zIndex));
|
maxZ = Math.max(maxZ, Number.parseInt(zIndex));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -37,6 +37,7 @@ function handleMenuOpen(key: string, path: string[]) {
|
|||||||
:menus="menus"
|
:menus="menus"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
:rounded="rounded"
|
:rounded="rounded"
|
||||||
|
scroll-to-active
|
||||||
:theme="theme"
|
:theme="theme"
|
||||||
@open="handleMenuOpen"
|
@open="handleMenuOpen"
|
||||||
@select="handleMenuSelect"
|
@select="handleMenuSelect"
|
||||||
|
@ -6,17 +6,37 @@ import { isHttpUrl, openRouteInNewWindow, openWindow } from '@vben/utils';
|
|||||||
|
|
||||||
function useNavigation() {
|
function useNavigation() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const routes = router.getRoutes();
|
|
||||||
|
|
||||||
const routeMetaMap = new Map<string, RouteRecordNormalized>();
|
const routeMetaMap = new Map<string, RouteRecordNormalized>();
|
||||||
|
|
||||||
|
// 初始化路由映射
|
||||||
|
const initRouteMetaMap = () => {
|
||||||
|
const routes = router.getRoutes();
|
||||||
routes.forEach((route) => {
|
routes.forEach((route) => {
|
||||||
routeMetaMap.set(route.path, route);
|
routeMetaMap.set(route.path, route);
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
initRouteMetaMap();
|
||||||
|
|
||||||
|
// 监听路由变化
|
||||||
|
router.afterEach(() => {
|
||||||
|
initRouteMetaMap();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 检查是否应该在新窗口打开
|
||||||
|
const shouldOpenInNewWindow = (path: string): boolean => {
|
||||||
|
if (isHttpUrl(path)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const route = routeMetaMap.get(path);
|
||||||
|
return route?.meta?.openInNewWindow ?? false;
|
||||||
|
};
|
||||||
|
|
||||||
const navigation = async (path: string) => {
|
const navigation = async (path: string) => {
|
||||||
|
try {
|
||||||
const route = routeMetaMap.get(path);
|
const route = routeMetaMap.get(path);
|
||||||
const { openInNewWindow = false, query = {} } = route?.meta ?? {};
|
const { openInNewWindow = false, query = {} } = route?.meta ?? {};
|
||||||
|
|
||||||
if (isHttpUrl(path)) {
|
if (isHttpUrl(path)) {
|
||||||
openWindow(path, { target: '_blank' });
|
openWindow(path, { target: '_blank' });
|
||||||
} else if (openInNewWindow) {
|
} else if (openInNewWindow) {
|
||||||
@ -27,18 +47,14 @@ function useNavigation() {
|
|||||||
query,
|
query,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Navigation failed:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const willOpenedByWindow = (path: string) => {
|
const willOpenedByWindow = (path: string) => {
|
||||||
const route = routeMetaMap.get(path);
|
return shouldOpenInNewWindow(path);
|
||||||
const { openInNewWindow = false } = route?.meta ?? {};
|
|
||||||
if (isHttpUrl(path)) {
|
|
||||||
return true;
|
|
||||||
} else if (openInNewWindow) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return { navigation, willOpenedByWindow };
|
return { navigation, willOpenedByWindow };
|
||||||
|
@ -11,7 +11,8 @@ defineOptions({
|
|||||||
name: 'LanguageToggle',
|
name: 'LanguageToggle',
|
||||||
});
|
});
|
||||||
|
|
||||||
async function handleUpdate(value: string) {
|
async function handleUpdate(value: string | undefined) {
|
||||||
|
if (!value) return;
|
||||||
const locale = value as SupportedLanguagesType;
|
const locale = value as SupportedLanguagesType;
|
||||||
updatePreferences({
|
updatePreferences({
|
||||||
app: {
|
app: {
|
||||||
|
@ -36,7 +36,8 @@ const menus = computed((): VbenDropdownMenuItem[] => [
|
|||||||
|
|
||||||
const { authPanelCenter, authPanelLeft, authPanelRight } = usePreferences();
|
const { authPanelCenter, authPanelLeft, authPanelRight } = usePreferences();
|
||||||
|
|
||||||
function handleUpdate(value: string) {
|
function handleUpdate(value: string | undefined) {
|
||||||
|
if (!value) return;
|
||||||
updatePreferences({
|
updatePreferences({
|
||||||
app: {
|
app: {
|
||||||
authPageLayout: value as AuthPageLayoutType,
|
authPageLayout: value as AuthPageLayoutType,
|
||||||
|
@ -79,14 +79,14 @@ const handleCheckboxChange = () => {
|
|||||||
</SwitchItem>
|
</SwitchItem>
|
||||||
<CheckboxItem
|
<CheckboxItem
|
||||||
:items="[
|
:items="[
|
||||||
{ label: '收缩按钮', value: 'collapsed' },
|
{ label: $t('preferences.sidebar.buttonCollapsed'), value: 'collapsed' },
|
||||||
{ label: '固定按钮', value: 'fixed' },
|
{ label: $t('preferences.sidebar.buttonFixed'), value: 'fixed' },
|
||||||
]"
|
]"
|
||||||
multiple
|
multiple
|
||||||
v-model="sidebarButtons"
|
v-model="sidebarButtons"
|
||||||
:on-btn-click="handleCheckboxChange"
|
:on-btn-click="handleCheckboxChange"
|
||||||
>
|
>
|
||||||
按钮配置
|
{{ $t('preferences.sidebar.buttons') }}
|
||||||
</CheckboxItem>
|
</CheckboxItem>
|
||||||
<NumberFieldItem
|
<NumberFieldItem
|
||||||
v-model="sidebarWidth"
|
v-model="sidebarWidth"
|
||||||
|
@ -24,7 +24,7 @@ withDefaults(defineProps<{ shouldOnHover?: boolean }>(), {
|
|||||||
shouldOnHover: false,
|
shouldOnHover: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleChange(isDark: boolean) {
|
function handleChange(isDark: boolean | undefined) {
|
||||||
updatePreferences({
|
updatePreferences({
|
||||||
theme: { mode: isDark ? 'dark' : 'light' },
|
theme: { mode: isDark ? 'dark' : 'light' },
|
||||||
});
|
});
|
||||||
|
@ -45,6 +45,9 @@
|
|||||||
"fixed": "Fixed"
|
"fixed": "Fixed"
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar": {
|
||||||
|
"buttons": "Show Buttons",
|
||||||
|
"buttonFixed": "Fixed",
|
||||||
|
"buttonCollapsed": "Collapsed",
|
||||||
"title": "Sidebar",
|
"title": "Sidebar",
|
||||||
"width": "Width",
|
"width": "Width",
|
||||||
"visible": "Show Sidebar",
|
"visible": "Show Sidebar",
|
||||||
|
@ -45,6 +45,9 @@
|
|||||||
"fixed": "固定"
|
"fixed": "固定"
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar": {
|
||||||
|
"buttons": "显示按钮",
|
||||||
|
"buttonFixed": "固定按钮",
|
||||||
|
"buttonCollapsed": "折叠按钮",
|
||||||
"title": "侧边栏",
|
"title": "侧边栏",
|
||||||
"width": "宽度",
|
"width": "宽度",
|
||||||
"visible": "显示侧边栏",
|
"visible": "显示侧边栏",
|
||||||
|
@ -69,7 +69,7 @@ describe('generateMenus', () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const menus = await generateMenus(mockRoutes, mockRouter as any);
|
const menus = generateMenus(mockRoutes, mockRouter as any);
|
||||||
expect(menus).toEqual(expectedMenus);
|
expect(menus).toEqual(expectedMenus);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -82,7 +82,7 @@ describe('generateMenus', () => {
|
|||||||
},
|
},
|
||||||
] as RouteRecordRaw[];
|
] as RouteRecordRaw[];
|
||||||
|
|
||||||
const menus = await generateMenus(mockRoutesWithMeta, mockRouter as any);
|
const menus = generateMenus(mockRoutesWithMeta, mockRouter as any);
|
||||||
expect(menus).toEqual([
|
expect(menus).toEqual([
|
||||||
{
|
{
|
||||||
badge: undefined,
|
badge: undefined,
|
||||||
@ -109,7 +109,7 @@ describe('generateMenus', () => {
|
|||||||
},
|
},
|
||||||
] as RouteRecordRaw[];
|
] as RouteRecordRaw[];
|
||||||
|
|
||||||
const menus = await generateMenus(mockRoutesWithParams, mockRouter as any);
|
const menus = generateMenus(mockRoutesWithParams, mockRouter as any);
|
||||||
expect(menus).toEqual([
|
expect(menus).toEqual([
|
||||||
{
|
{
|
||||||
badge: undefined,
|
badge: undefined,
|
||||||
@ -141,10 +141,7 @@ describe('generateMenus', () => {
|
|||||||
},
|
},
|
||||||
] as RouteRecordRaw[];
|
] as RouteRecordRaw[];
|
||||||
|
|
||||||
const menus = await generateMenus(
|
const menus = generateMenus(mockRoutesWithRedirect, mockRouter as any);
|
||||||
mockRoutesWithRedirect,
|
|
||||||
mockRouter as any,
|
|
||||||
);
|
|
||||||
expect(menus).toEqual([
|
expect(menus).toEqual([
|
||||||
// Assuming your generateMenus function excludes redirect routes from the menu
|
// Assuming your generateMenus function excludes redirect routes from the menu
|
||||||
{
|
{
|
||||||
@ -195,7 +192,7 @@ describe('generateMenus', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should generate menu list with correct order', async () => {
|
it('should generate menu list with correct order', async () => {
|
||||||
const menus = await generateMenus(routes, router);
|
const menus = generateMenus(routes, router);
|
||||||
const expectedMenus = [
|
const expectedMenus = [
|
||||||
{
|
{
|
||||||
badge: undefined,
|
badge: undefined,
|
||||||
@ -230,7 +227,7 @@ describe('generateMenus', () => {
|
|||||||
|
|
||||||
it('should handle empty routes', async () => {
|
it('should handle empty routes', async () => {
|
||||||
const emptyRoutes: any[] = [];
|
const emptyRoutes: any[] = [];
|
||||||
const menus = await generateMenus(emptyRoutes, router);
|
const menus = generateMenus(emptyRoutes, router);
|
||||||
expect(menus).toEqual([]);
|
expect(menus).toEqual([]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,30 +1,38 @@
|
|||||||
import type { Router, RouteRecordRaw } from 'vue-router';
|
import type { Router, RouteRecordRaw } from 'vue-router';
|
||||||
|
|
||||||
import type { ExRouteRecordRaw, MenuRecordRaw } from '@vben-core/typings';
|
import type {
|
||||||
|
ExRouteRecordRaw,
|
||||||
|
MenuRecordRaw,
|
||||||
|
RouteMeta,
|
||||||
|
} from '@vben-core/typings';
|
||||||
|
|
||||||
import { filterTree, mapTree } from '@vben-core/shared/utils';
|
import { filterTree, mapTree } from '@vben-core/shared/utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据 routes 生成菜单列表
|
* 根据 routes 生成菜单列表
|
||||||
* @param routes
|
* @param routes - 路由配置列表
|
||||||
|
* @param router - Vue Router 实例
|
||||||
|
* @returns 生成的菜单列表
|
||||||
*/
|
*/
|
||||||
async function generateMenus(
|
function generateMenus(
|
||||||
routes: RouteRecordRaw[],
|
routes: RouteRecordRaw[],
|
||||||
router: Router,
|
router: Router,
|
||||||
): Promise<MenuRecordRaw[]> {
|
): MenuRecordRaw[] {
|
||||||
// 将路由列表转换为一个以 name 为键的对象映射
|
// 将路由列表转换为一个以 name 为键的对象映射
|
||||||
// 获取所有router最终的path及name
|
|
||||||
const finalRoutesMap: { [key: string]: string } = Object.fromEntries(
|
const finalRoutesMap: { [key: string]: string } = Object.fromEntries(
|
||||||
router.getRoutes().map(({ name, path }) => [name, path]),
|
router.getRoutes().map(({ name, path }) => [name, path]),
|
||||||
);
|
);
|
||||||
|
|
||||||
let menus = mapTree<ExRouteRecordRaw, MenuRecordRaw>(routes, (route) => {
|
let menus = mapTree<ExRouteRecordRaw, MenuRecordRaw>(routes, (route) => {
|
||||||
// 路由表的路径写法有多种,这里从router获取到最终的path并赋值
|
// 获取最终的路由路径
|
||||||
const path = finalRoutesMap[route.name as string] ?? route.path;
|
const path = finalRoutesMap[route.name as string] ?? route.path ?? '';
|
||||||
|
|
||||||
// 转换为菜单结构
|
const {
|
||||||
// const path = matchRoute?.path ?? route.path;
|
meta = {} as RouteMeta,
|
||||||
const { meta, name: routeName, redirect, children } = route;
|
name: routeName,
|
||||||
|
redirect,
|
||||||
|
children = [],
|
||||||
|
} = route;
|
||||||
const {
|
const {
|
||||||
activeIcon,
|
activeIcon,
|
||||||
badge,
|
badge,
|
||||||
@ -35,24 +43,27 @@ async function generateMenus(
|
|||||||
link,
|
link,
|
||||||
order,
|
order,
|
||||||
title = '',
|
title = '',
|
||||||
} = meta || {};
|
} = meta;
|
||||||
|
|
||||||
|
// 确保菜单名称不为空
|
||||||
const name = (title || routeName || '') as string;
|
const name = (title || routeName || '') as string;
|
||||||
|
|
||||||
// 隐藏子菜单
|
// 处理子菜单
|
||||||
const resultChildren = hideChildrenInMenu
|
const resultChildren = hideChildrenInMenu
|
||||||
? []
|
? []
|
||||||
: (children as MenuRecordRaw[]);
|
: (children as MenuRecordRaw[]);
|
||||||
|
|
||||||
// 将菜单的所有父级和父级菜单记录到菜单项内
|
// 设置子菜单的父子关系
|
||||||
if (resultChildren && resultChildren.length > 0) {
|
if (resultChildren.length > 0) {
|
||||||
resultChildren.forEach((child) => {
|
resultChildren.forEach((child) => {
|
||||||
child.parents = [...(route.parents || []), path];
|
child.parents = [...(route.parents ?? []), path];
|
||||||
child.parent = path;
|
child.parent = path;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// 隐藏子菜单
|
|
||||||
|
// 确定最终路径
|
||||||
const resultPath = hideChildrenInMenu ? redirect || path : link || path;
|
const resultPath = hideChildrenInMenu ? redirect || path : link || path;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
activeIcon,
|
activeIcon,
|
||||||
badge,
|
badge,
|
||||||
@ -63,19 +74,17 @@ async function generateMenus(
|
|||||||
order,
|
order,
|
||||||
parent: route.parent,
|
parent: route.parent,
|
||||||
parents: route.parents,
|
parents: route.parents,
|
||||||
path: resultPath as string,
|
path: resultPath,
|
||||||
show: !route?.meta?.hideInMenu,
|
show: !meta.hideInMenu,
|
||||||
children: resultChildren || [],
|
children: resultChildren,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// 对菜单进行排序,避免order=0时被替换成999的问题
|
// 对菜单进行排序,避免order=0时被替换成999的问题
|
||||||
menus = menus.sort((a, b) => (a?.order ?? 999) - (b?.order ?? 999));
|
menus = menus.sort((a, b) => (a?.order ?? 999) - (b?.order ?? 999));
|
||||||
|
|
||||||
const finalMenus = filterTree(menus, (menu) => {
|
// 过滤掉隐藏的菜单项
|
||||||
return !!menu.show;
|
return filterTree(menus, (menu) => !!menu.show);
|
||||||
});
|
|
||||||
return finalMenus;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export { generateMenus };
|
export { generateMenus };
|
||||||
|
@ -73,8 +73,8 @@ const withDefaultPlaceholder = <T extends Component>(
|
|||||||
componentProps: Recordable<any> = {},
|
componentProps: Recordable<any> = {},
|
||||||
) => {
|
) => {
|
||||||
return defineComponent({
|
return defineComponent({
|
||||||
inheritAttrs: false,
|
|
||||||
name: component.name,
|
name: component.name,
|
||||||
|
inheritAttrs: false,
|
||||||
setup: (props: any, { attrs, expose, slots }) => {
|
setup: (props: any, { attrs, expose, slots }) => {
|
||||||
const placeholder =
|
const placeholder =
|
||||||
props?.placeholder ||
|
props?.placeholder ||
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import type { Router } from 'vue-router';
|
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 { preferences } from '@vben/preferences';
|
||||||
import { useAccessStore, useUserStore } from '@vben/stores';
|
import { useAccessStore, useUserStore } from '@vben/stores';
|
||||||
import { startProgress, stopProgress } from '@vben/utils';
|
import { startProgress, stopProgress } from '@vben/utils';
|
||||||
@ -54,7 +54,7 @@ function setupAccessGuard(router: Router) {
|
|||||||
return decodeURIComponent(
|
return decodeURIComponent(
|
||||||
(to.query?.redirect as string) ||
|
(to.query?.redirect as string) ||
|
||||||
userStore.userInfo?.homePath ||
|
userStore.userInfo?.homePath ||
|
||||||
DEFAULT_HOME_PATH,
|
preferences.app.defaultHomePath,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@ -73,7 +73,7 @@ function setupAccessGuard(router: Router) {
|
|||||||
path: LOGIN_PATH,
|
path: LOGIN_PATH,
|
||||||
// 如不需要,直接删除 query
|
// 如不需要,直接删除 query
|
||||||
query:
|
query:
|
||||||
to.fullPath === DEFAULT_HOME_PATH
|
to.fullPath === preferences.app.defaultHomePath
|
||||||
? {}
|
? {}
|
||||||
: { redirect: encodeURIComponent(to.fullPath) },
|
: { redirect: encodeURIComponent(to.fullPath) },
|
||||||
// 携带当前跳转的页面,登录后重新跳转该页面
|
// 携带当前跳转的页面,登录后重新跳转该页面
|
||||||
@ -106,8 +106,8 @@ function setupAccessGuard(router: Router) {
|
|||||||
accessStore.setAccessRoutes(accessibleRoutes);
|
accessStore.setAccessRoutes(accessibleRoutes);
|
||||||
accessStore.setIsAccessChecked(true);
|
accessStore.setIsAccessChecked(true);
|
||||||
const redirectPath = (from.query.redirect ??
|
const redirectPath = (from.query.redirect ??
|
||||||
(to.path === DEFAULT_HOME_PATH
|
(to.path === preferences.app.defaultHomePath
|
||||||
? userInfo.homePath || DEFAULT_HOME_PATH
|
? userInfo.homePath || preferences.app.defaultHomePath
|
||||||
: to.fullPath)) as string;
|
: to.fullPath)) as string;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import type { RouteRecordRaw } from 'vue-router';
|
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';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
@ -34,7 +35,7 @@ const coreRoutes: RouteRecordRaw[] = [
|
|||||||
},
|
},
|
||||||
name: 'Root',
|
name: 'Root',
|
||||||
path: '/',
|
path: '/',
|
||||||
redirect: DEFAULT_HOME_PATH,
|
redirect: preferences.app.defaultHomePath,
|
||||||
children: [],
|
children: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -3,7 +3,8 @@ import type { Recordable, UserInfo } from '@vben/types';
|
|||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
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 { resetAllStores, useAccessStore, useUserStore } from '@vben/stores';
|
||||||
|
|
||||||
import { notification } from 'ant-design-vue';
|
import { notification } from 'ant-design-vue';
|
||||||
@ -55,7 +56,9 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
} else {
|
} else {
|
||||||
onSuccess
|
onSuccess
|
||||||
? await onSuccess?.()
|
? await onSuccess?.()
|
||||||
: await router.push(userInfo.homePath || DEFAULT_HOME_PATH);
|
: await router.push(
|
||||||
|
userInfo.homePath || preferences.app.defaultHomePath,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userInfo?.realName) {
|
if (userInfo?.realName) {
|
||||||
|
@ -21,22 +21,22 @@ catalog:
|
|||||||
'@commitlint/cli': ^19.8.0
|
'@commitlint/cli': ^19.8.0
|
||||||
'@commitlint/config-conventional': ^19.8.0
|
'@commitlint/config-conventional': ^19.8.0
|
||||||
'@ctrl/tinycolor': ^4.1.0
|
'@ctrl/tinycolor': ^4.1.0
|
||||||
'@eslint/js': ^9.25.1
|
'@eslint/js': ^9.26.0
|
||||||
'@faker-js/faker': ^9.7.0
|
'@faker-js/faker': ^9.7.0
|
||||||
'@iconify/json': ^2.2.332
|
'@iconify/json': ^2.2.334
|
||||||
'@iconify/tailwind': ^1.2.0
|
'@iconify/tailwind': ^1.2.0
|
||||||
'@iconify/vue': ^4.3.0
|
'@iconify/vue': ^5.0.0
|
||||||
'@intlify/core-base': ^11.1.3
|
'@intlify/core-base': ^11.1.3
|
||||||
'@intlify/unplugin-vue-i18n': ^6.0.8
|
'@intlify/unplugin-vue-i18n': ^6.0.8
|
||||||
'@jspm/generator': ^2.5.1
|
'@jspm/generator': ^2.5.1
|
||||||
'@manypkg/get-packages': ^2.2.2
|
'@manypkg/get-packages': ^3.0.0
|
||||||
'@nolebase/vitepress-plugin-git-changelog': ^2.17.0
|
'@nolebase/vitepress-plugin-git-changelog': ^2.17.0
|
||||||
'@playwright/test': ^1.52.0
|
'@playwright/test': ^1.52.0
|
||||||
'@pnpm/workspace.read-manifest': ^1000.1.4
|
'@pnpm/workspace.read-manifest': ^1000.1.4
|
||||||
'@stylistic/stylelint-plugin': ^3.1.2
|
'@stylistic/stylelint-plugin': ^3.1.2
|
||||||
'@tailwindcss/nesting': 0.0.0-insiders.565cd3e
|
'@tailwindcss/nesting': 0.0.0-insiders.565cd3e
|
||||||
'@tailwindcss/typography': ^0.5.16
|
'@tailwindcss/typography': ^0.5.16
|
||||||
'@tanstack/vue-query': ^5.74.7
|
'@tanstack/vue-query': ^5.75.1
|
||||||
'@tanstack/vue-store': ^0.7.0
|
'@tanstack/vue-store': ^0.7.0
|
||||||
'@types/archiver': ^6.0.3
|
'@types/archiver': ^6.0.3
|
||||||
'@types/eslint': ^9.6.1
|
'@types/eslint': ^9.6.1
|
||||||
@ -46,14 +46,14 @@ catalog:
|
|||||||
'@types/lodash.get': ^4.4.9
|
'@types/lodash.get': ^4.4.9
|
||||||
'@types/lodash.isequal': ^4.5.8
|
'@types/lodash.isequal': ^4.5.8
|
||||||
'@types/lodash.set': ^4.3.9
|
'@types/lodash.set': ^4.3.9
|
||||||
'@types/node': ^22.15.2
|
'@types/node': ^22.15.3
|
||||||
'@types/nprogress': ^0.2.3
|
'@types/nprogress': ^0.2.3
|
||||||
'@types/postcss-import': ^14.0.3
|
'@types/postcss-import': ^14.0.3
|
||||||
'@types/qrcode': ^1.5.5
|
'@types/qrcode': ^1.5.5
|
||||||
'@types/qs': ^6.9.18
|
'@types/qs': ^6.9.18
|
||||||
'@types/sortablejs': ^1.15.8
|
'@types/sortablejs': ^1.15.8
|
||||||
'@typescript-eslint/eslint-plugin': ^8.31.0
|
'@typescript-eslint/eslint-plugin': ^8.31.1
|
||||||
'@typescript-eslint/parser': ^8.31.0
|
'@typescript-eslint/parser': ^8.31.1
|
||||||
'@vee-validate/zod': ^4.15.0
|
'@vee-validate/zod': ^4.15.0
|
||||||
'@vite-pwa/vitepress': ^1.0.0
|
'@vite-pwa/vitepress': ^1.0.0
|
||||||
'@vitejs/plugin-vue': ^5.2.3
|
'@vitejs/plugin-vue': ^5.2.3
|
||||||
@ -62,8 +62,8 @@ catalog:
|
|||||||
'@vue/shared': ^3.5.13
|
'@vue/shared': ^3.5.13
|
||||||
'@vue/test-utils': ^2.4.6
|
'@vue/test-utils': ^2.4.6
|
||||||
'@vueuse/core': ^13.1.0
|
'@vueuse/core': ^13.1.0
|
||||||
'@vueuse/motion': ^3.0.3
|
|
||||||
'@vueuse/integrations': ^13.1.0
|
'@vueuse/integrations': ^13.1.0
|
||||||
|
'@vueuse/motion': ^3.0.3
|
||||||
ant-design-vue: ^4.2.6
|
ant-design-vue: ^4.2.6
|
||||||
archiver: ^7.0.1
|
archiver: ^7.0.1
|
||||||
autoprefixer: ^10.4.21
|
autoprefixer: ^10.4.21
|
||||||
@ -88,7 +88,7 @@ catalog:
|
|||||||
dotenv: ^16.5.0
|
dotenv: ^16.5.0
|
||||||
echarts: ^5.6.0
|
echarts: ^5.6.0
|
||||||
element-plus: ^2.9.9
|
element-plus: ^2.9.9
|
||||||
eslint: ^9.25.1
|
eslint: ^9.26.0
|
||||||
eslint-config-turbo: ^2.5.2
|
eslint-config-turbo: ^2.5.2
|
||||||
eslint-plugin-command: ^3.2.0
|
eslint-plugin-command: ^3.2.0
|
||||||
eslint-plugin-eslint-comments: ^3.2.0
|
eslint-plugin-eslint-comments: ^3.2.0
|
||||||
@ -103,24 +103,23 @@ catalog:
|
|||||||
eslint-plugin-unicorn: ^59.0.0
|
eslint-plugin-unicorn: ^59.0.0
|
||||||
eslint-plugin-unused-imports: ^4.1.4
|
eslint-plugin-unused-imports: ^4.1.4
|
||||||
eslint-plugin-vitest: ^0.5.4
|
eslint-plugin-vitest: ^0.5.4
|
||||||
eslint-plugin-vue: ^10.0.0
|
eslint-plugin-vue: ^10.1.0
|
||||||
execa: ^9.5.2
|
execa: ^9.5.2
|
||||||
find-up: ^7.0.0
|
find-up: ^7.0.0
|
||||||
get-port: ^7.1.0
|
get-port: ^7.1.0
|
||||||
globals: ^16.0.0
|
globals: ^16.0.0
|
||||||
h3: ^1.15.3
|
h3: ^1.15.3
|
||||||
happy-dom: ^17.4.4
|
happy-dom: ^17.4.6
|
||||||
html-minifier-terser: ^7.2.0
|
html-minifier-terser: ^7.2.0
|
||||||
husky: ^9.1.7
|
|
||||||
is-ci: ^4.1.0
|
is-ci: ^4.1.0
|
||||||
jsonc-eslint-parser: ^2.4.0
|
jsonc-eslint-parser: ^2.4.0
|
||||||
jsonwebtoken: ^9.0.2
|
jsonwebtoken: ^9.0.2
|
||||||
lint-staged: ^15.5.1
|
lefthook: ^1.11.12
|
||||||
lodash.clonedeep: ^4.5.0
|
lodash.clonedeep: ^4.5.0
|
||||||
lodash.get: ^4.4.2
|
lodash.get: ^4.4.2
|
||||||
lodash.set: ^4.3.2
|
|
||||||
lodash.isequal: ^4.5.0
|
lodash.isequal: ^4.5.0
|
||||||
lucide-vue-next: ^0.503.0
|
lodash.set: ^4.3.2
|
||||||
|
lucide-vue-next: ^0.507.0
|
||||||
medium-zoom: ^1.1.0
|
medium-zoom: ^1.1.0
|
||||||
naive-ui: ^2.41.0
|
naive-ui: ^2.41.0
|
||||||
nitropack: ^2.11.11
|
nitropack: ^2.11.11
|
||||||
@ -144,7 +143,7 @@ catalog:
|
|||||||
radix-vue: ^1.9.17
|
radix-vue: ^1.9.17
|
||||||
resolve.exports: ^2.0.3
|
resolve.exports: ^2.0.3
|
||||||
rimraf: ^6.0.1
|
rimraf: ^6.0.1
|
||||||
rollup: ^4.40.0
|
rollup: ^4.40.1
|
||||||
rollup-plugin-visualizer: ^5.14.0
|
rollup-plugin-visualizer: ^5.14.0
|
||||||
sass: ^1.87.0
|
sass: ^1.87.0
|
||||||
secure-ls: ^2.0.0
|
secure-ls: ^2.0.0
|
||||||
@ -168,7 +167,7 @@ catalog:
|
|||||||
unbuild: ^3.5.0
|
unbuild: ^3.5.0
|
||||||
unplugin-element-plus: ^0.10.0
|
unplugin-element-plus: ^0.10.0
|
||||||
vee-validate: ^4.15.0
|
vee-validate: ^4.15.0
|
||||||
vite: ^6.3.3
|
vite: ^6.3.4
|
||||||
vite-plugin-compression: ^0.5.1
|
vite-plugin-compression: ^0.5.1
|
||||||
vite-plugin-dts: ^4.5.3
|
vite-plugin-dts: ^4.5.3
|
||||||
vite-plugin-html: ^3.2.2
|
vite-plugin-html: ^3.2.2
|
||||||
@ -184,9 +183,9 @@ catalog:
|
|||||||
vue-json-viewer: ^3.0.4
|
vue-json-viewer: ^3.0.4
|
||||||
vue-router: ^4.5.1
|
vue-router: ^4.5.1
|
||||||
vue-tippy: ^6.7.0
|
vue-tippy: ^6.7.0
|
||||||
vue-tsc: 2.1.10
|
vue-tsc: 2.2.10
|
||||||
vxe-pc-ui: ^4.5.14
|
vxe-pc-ui: ^4.5.35
|
||||||
vxe-table: ^4.13.14
|
vxe-table: ^4.13.16
|
||||||
watermark-js-plus: ^1.6.0
|
watermark-js-plus: ^1.6.0
|
||||||
zod: ^3.24.3
|
zod: ^3.24.3
|
||||||
zod-defaults: ^0.1.3
|
zod-defaults: ^0.1.3
|
||||||
|
@ -22,7 +22,6 @@ const DEFAULT_CONFIG = {
|
|||||||
'@vben/backend-mock',
|
'@vben/backend-mock',
|
||||||
'@vben/commitlint-config',
|
'@vben/commitlint-config',
|
||||||
'@vben/eslint-config',
|
'@vben/eslint-config',
|
||||||
'@vben/lint-staged-config',
|
|
||||||
'@vben/node-utils',
|
'@vben/node-utils',
|
||||||
'@vben/prettier-config',
|
'@vben/prettier-config',
|
||||||
'@vben/stylelint-config',
|
'@vben/stylelint-config',
|
||||||
|
Loading…
Reference in New Issue
Block a user