Compare commits

...

29 Commits

Author SHA1 Message Date
vben
46540a7329 chore: release v5.3.2 2024-10-03 15:43:15 +08:00
Vben
13fd0ea16c fix: try to fix the error reported by the stub command in the window system (#4560) 2024-10-03 15:34:29 +08:00
Vben
f7016466ee feat: add examples of asynchronous form verification and verification time (#4559)
* feat: add examples of asynchronous form verification and verification time
2024-10-03 15:15:50 +08:00
Vben
0cd865e211 fix: fixed an error in the form onChange within Naive (#4558)
* fix: fixed an error in the form onChange within Naive

* chore: update
2024-10-03 14:22:18 +08:00
Squall2017
64428b9b11 feat: add form field autofocus configuration (#4550)
* feat: add form field autofocus configuration
2024-10-03 13:10:21 +08:00
LinaBell
aed31a5a4e perf: [antd] default placeholder for input and select components (#4551)
* chore: demo of customizing form layout using tailwind

* perf: default placeholder for input and select components

* chore: update ts type

* chore: extract public methods
2024-10-03 13:04:19 +08:00
dependabot[bot]
b3e196f001 chore(deps-dev): bump the non-breaking-changes group across 1 directory with 3 updates (#4557)
* chore(deps-dev): bump the non-breaking-changes group across 1 directory with 3 updates

Bumps the non-breaking-changes group with 3 updates in the / directory: [turbo](https://github.com/vercel/turborepo), [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) and [rollup](https://github.com/rollup/rollup).


Updates `turbo` from 2.1.2 to 2.1.3
- [Release notes](https://github.com/vercel/turborepo/releases)
- [Changelog](https://github.com/vercel/turborepo/blob/main/release.md)
- [Commits](https://github.com/vercel/turborepo/compare/v2.1.2...v2.1.3)

Updates `vitest` from 2.1.1 to 2.1.2
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v2.1.2/packages/vitest)

Updates `rollup` from 4.22.5 to 4.24.0
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.22.5...v4.24.0)

---
updated-dependencies:
- dependency-name: turbo
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: non-breaking-changes
- dependency-name: vitest
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: non-breaking-changes
- dependency-name: rollup
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: non-breaking-changes
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore: update deps

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-03 12:54:04 +08:00
LinaBell
b2c117f544 chore: demo of customizing form layout using tailwind (#4549) 2024-09-30 09:47:16 +08:00
vben
01391ee5a1 chore: release v5.3.1 2024-09-29 22:25:20 +08:00
Vben
3572ce1538 fix: when multiple pop-ups exist at the same time, clicking will close all (#4548) 2024-09-29 22:15:43 +08:00
Vben
d1e1256202 chore: disable sorting of non-core folder object fields (#4547)
* chore: disable sorting of non-core folder object fields

* chore: ci error
2024-09-29 22:03:17 +08:00
Squall2017
b7776c5148 fix: fix abnormal display of scroll bar on lock screen page (#4546)
*  fix abnormal display of scroll bar on lock screen page
2024-09-29 21:45:56 +08:00
dependabot[bot]
2d1519eca7 chore(deps): bump the non-breaking-changes group across 1 directory with 4 updates (#4537)
* chore(deps): bump the non-breaking-changes group across 1 directory with 4 updates

Bumps the non-breaking-changes group with 4 updates in the / directory: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node), [vue](https://github.com/vuejs/core), [rollup](https://github.com/rollup/rollup) and [@vue/shared](https://github.com/vuejs/core/tree/HEAD/packages/shared).


Updates `@types/node` from 22.7.2 to 22.7.4
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `vue` from 3.5.8 to 3.5.10
- [Release notes](https://github.com/vuejs/core/releases)
- [Changelog](https://github.com/vuejs/core/blob/main/CHANGELOG.md)
- [Commits](https://github.com/vuejs/core/compare/v3.5.8...v3.5.10)

Updates `rollup` from 4.22.4 to 4.22.5
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.22.4...v4.22.5)

Updates `@vue/shared` from 3.5.8 to 3.5.10
- [Release notes](https://github.com/vuejs/core/releases)
- [Changelog](https://github.com/vuejs/core/blob/main/CHANGELOG.md)
- [Commits](https://github.com/vuejs/core/commits/v3.5.10/packages/shared)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: non-breaking-changes
- dependency-name: vue
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-breaking-changes
- dependency-name: rollup
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: non-breaking-changes
- dependency-name: "@vue/shared"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-breaking-changes
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore: update deps

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-28 16:10:26 +08:00
jinmao88
93b5618b52 feat(@vben/stores): export defineStore to support pinia persistence within apps (#4483)
Co-authored-by: Li Kui <90845831+likui628@users.noreply.github.com>
2024-09-28 16:01:04 +08:00
Vben
639d2e1525 feat: add pagination component (#4522)
* feat: add pagination component

* chore: update
2024-09-26 23:09:17 +08:00
Vben
26646d42f7 fix: when modal and drawer exist at the same time, click Close All (#4521) 2024-09-26 22:50:37 +08:00
Vben
17fa8eb93b fix: improve ant design button icon style (#4520) 2024-09-26 22:40:23 +08:00
Vben
8250894a50 fix: improve input browser backfilling style (#4519) 2024-09-26 22:31:20 +08:00
vince
a72b8acaf9 fix: window system clean script execution problems (#4513)
* fix: fix window system clean script execution problems

* fix: lint error

* chore: remove test code
2024-09-26 11:59:19 +08:00
Vben
a46c85d77d chore: update documentation and deps (#4510)
* chore: update docs

* chore: update deps

* chore: update action

* fix: reset after preferences are refreshed

* fix: ci error
2024-09-25 23:09:48 +08:00
Squall2017
fdc5b02c30 feat(form): add merge form functionality (#4495)
* feat: captcha example

* fix: fix lint errors

* chore: event handling and methods

* chore: add accessibility features ARIA labels and roles

* refactor: refactor code structure and improve captcha demo page

* feat: add captcha internationalization

* chore: 适配时间戳国际化展示

* fix: 1. 添加点击位置边界校验,防止点击外部导致x,y误差。2. 演示页面宽度过长添加滚动条。3. 添加hooks

* feat: sync test

* feat: 添加合并表单功能

* fix: 修复上一步不展示问题

---------

Co-authored-by: vince <vince292007@gmail.com>
2024-09-25 18:11:02 +08:00
Netfan
476aa068d7 fix: stripe table style for element plus, fixed: #4501 (#4503) 2024-09-25 17:33:24 +08:00
LinaBell
bb6057cac3 perf: setValues method of the form supports assigning values only to keys that exist in the schema (#4508)
* fix: hover border style same as antd style when validate error

* fix: hover border style same as antd style when validate error

* feat(@vben-core/form-ui): Default form validation rules applicable to selector components

* fix: Missing the default required label style for components such as select

* fix: the focus style and antd of the input box validation failure should be consistent

* fix: the focus style and antd of the input box validation failure should be consistent

* fix: some antd components failed to verify styles

* perf: setValues method of the form supports assigning values only to keys that exist in the schema

* docs: update form component docs

---------

Co-authored-by: vince <vince292007@gmail.com>
2024-09-25 17:09:38 +08:00
Fifteen
abbbbfb955 fix(docs): fix the selected state of the top navigation bar (#4499)
* fix(@vben/docs): fix the selected state of the top navigation bar

* style(@vben/docs): navigation bar selected item style
2024-09-25 09:53:55 +08:00
handsomeFu
79c87c9f46 chore(@vben/playground): Add new slider captcha element and adjust references (#4490)
Add a new slider captcha action reference (`el6`) to support additional functionality and update related template and event handler to use this new reference.
2024-09-24 14:15:41 +08:00
Vben
f815dcf3ae fix: after deleting the form item, you will also get the form value (#4481)
* fix: after deleting the form item, you will also get the form value
2024-09-23 22:59:58 +08:00
neo.dowithless
1197efea26 fix: wrong style when breadcrumbs display background (#4472) 2024-09-23 14:15:46 +08:00
Vben
2a83f1d666 feat: add playwright e2e testing framework (#4468)
* feat: add playwright e2e testing framework
2024-09-22 21:35:40 +08:00
aonoa
4b3d2d21ed fix: Clear the input box when closing the search (#4467)
Signed-off-by: aonoa <1991849113@qq.com>
2024-09-22 20:38:01 +08:00
167 changed files with 3770 additions and 2195 deletions

View File

@@ -19,6 +19,7 @@ permissions:
jobs:
post-update:
if: github.repository == 'vbenjs/vue-vben-admin'
# if: ${{ github.actor == 'dependabot[bot]' }}
runs-on: ${{ matrix.os }}
strategy:

View File

@@ -18,7 +18,7 @@ env:
jobs:
version:
if: (github.event.pull_request.merged || github.event_name == 'workflow_dispatch') && github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
if: (github.event.pull_request.merged || github.event_name == 'workflow_dispatch') && github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && github.repository == 'vbenjs/vue-vben-admin'
# if: github.repository == 'vbenjs/vue-vben-admin'
timeout-minutes: 15
runs-on: ubuntu-latest

View File

@@ -17,6 +17,7 @@ env:
jobs:
test:
name: Test
if: github.repository == 'vbenjs/vue-vben-admin'
runs-on: ${{ matrix.os }}
strategy:
matrix:
@@ -55,6 +56,7 @@ jobs:
lint:
name: Lint
if: github.repository == 'vbenjs/vue-vben-admin'
runs-on: ${{ matrix.os }}
strategy:
matrix:
@@ -77,6 +79,7 @@ jobs:
check:
name: Check
if: github.repository == 'vbenjs/vue-vben-admin'
runs-on: ${{ matrix.os }}
timeout-minutes: 20
strategy:
@@ -106,6 +109,7 @@ jobs:
ci-ok:
name: CI OK
if: github.repository == 'vbenjs/vue-vben-admin'
runs-on: ubuntu-latest
needs: [test, check, lint]
env:

View File

@@ -22,6 +22,7 @@ on:
jobs:
analyze:
name: Analyze (${{ matrix.language }})
if: github.repository == 'vbenjs/vue-vben-admin'
# Runner size impacts CodeQL analysis time. To learn more, please see:
# - https://gh.io/recommended-hardware-resources-for-running-codeql
# - https://gh.io/supported-runners-and-hardware-resources

View File

@@ -8,7 +8,7 @@ on:
jobs:
deploy-playground-ftp:
name: Deploy Push Playground Ftp
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && github.repository == 'vbenjs/vue-vben-admin'
runs-on: ubuntu-latest
steps:
- name: Checkout code
@@ -39,7 +39,7 @@ jobs:
deploy-docs-ftp:
name: Deploy Push Docs Ftp
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && github.repository == 'vbenjs/vue-vben-admin'
runs-on: ubuntu-latest
steps:
- name: Checkout code
@@ -63,7 +63,7 @@ jobs:
deploy-antd-ftp:
name: Deploy Push Antd Ftp
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && github.repository == 'vbenjs/vue-vben-admin'
runs-on: ubuntu-latest
steps:
- name: Checkout code
@@ -94,7 +94,7 @@ jobs:
deploy-ele-ftp:
name: Deploy Push Element Ftp
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && github.repository == 'vbenjs/vue-vben-admin'
runs-on: ubuntu-latest
steps:
- name: Checkout code
@@ -125,7 +125,7 @@ jobs:
deploy-naive-ftp:
name: Deploy Push Naive Ftp
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && github.repository == 'vbenjs/vue-vben-admin'
runs-on: ubuntu-latest
steps:
- name: Checkout code

View File

@@ -17,6 +17,7 @@ jobs:
# write permission is required for autolabeler
# otherwise, read permission is required at least
pull-requests: write
if: github.repository == 'vbenjs/vue-vben-admin'
runs-on: ubuntu-latest
steps:
- uses: release-drafter/release-drafter@v6

View File

@@ -3,23 +3,29 @@ name: Issue Close Require
# 触发条件:每天零点
on:
workflow_dispatch:
schedule:
- cron: '0 0 * * *'
permissions:
pull-requests: write
contents: write
issues: write
jobs:
close-issues:
if: github.repository == 'vbenjs/vue-vben-admin'
runs-on: ubuntu-latest
steps:
# 步骤1关闭未活动的 Issues
# 关闭未活动的 Issues
- name: Close Inactive Issues
uses: actions-cool/issues-helper@v3
uses: actions/stale@v9
with:
actions: 'close-issues' # 执行动作:关闭 Issues
token: ${{ secrets.GITHUB_TOKEN }} # GitHub Token用于认证
labels: 'needs reproduction' # 目标标签
inactive-day: 3 # 未活动天数阈值
days-before-stale: -1 # Issues and PR will never be flagged stale automatically.
stale-issue-label: needs-reproduction # Label that flags an issue as stale.
only-labels: needs-reproduction # Only process these issues
days-before-issue-close: 3
ignore-updates: true
remove-stale-when-updated: false
close-issue-message: This issue was closed because it was open for 3 days without a valid reproduction.
close-issue-label: closed-by-action

View File

@@ -13,6 +13,7 @@ permissions:
jobs:
reply-labeled:
if: github.repository == 'vbenjs/vue-vben-admin'
runs-on: ubuntu-latest
steps:
- name: remove enhancement pending

View File

@@ -11,12 +11,13 @@ permissions:
jobs:
action:
if: github.repository == 'vbenjs/vue-vben-admin'
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v5
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
issue-inactive-days: '30'
issue-inactive-days: '14'
issue-lock-reason: ''
pr-inactive-days: '30'
pr-lock-reason: ''

View File

@@ -15,6 +15,7 @@ permissions:
jobs:
build:
name: Create Release
if: github.repository == 'vbenjs/vue-vben-admin'
runs-on: ubuntu-latest
strategy:
matrix:

View File

@@ -9,8 +9,9 @@ on:
jobs:
main:
runs-on: ubuntu-latest
name: Semantic Pull Request
if: github.repository == 'vbenjs/vue-vben-admin'
runs-on: ubuntu-latest
steps:
- name: Validate PR title
uses: amannn/action-semantic-pull-request@v5

View File

@@ -6,6 +6,7 @@ on:
jobs:
stale:
if: github.repository == 'vbenjs/vue-vben-admin'
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9

View File

@@ -3,4 +3,4 @@ ports:
onOpen: open-preview
tasks:
- init: corepack enable && pnpm install
command: pnpm run dev
command: pnpm run dev:play

View File

@@ -125,6 +125,10 @@ pnpm build
[@Vben](https://github.com/anncwb)
## スター歴史
[![Star History Chart](https://api.star-history.com/svg?repos=vbenjs/vue-vben-admin&type=Date)](https://star-history.com/#vbenjs/vue-vben-admin&Date)
## 寄付
このプロジェクトが役に立つと思われた場合、作者にコーヒーを一杯おごってサポートを示すことができます!

View File

@@ -124,6 +124,10 @@ Support modern browsers, not IE
[@Vben](https://github.com/anncwb)
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=vbenjs/vue-vben-admin&type=Date)](https://star-history.com/#vbenjs/vue-vben-admin&Date)
## Donate
If you think this project is helpful to you, you can help the author buy a cup of coffee to show your support!

View File

@@ -77,6 +77,10 @@ pnpm dev
pnpm build
```
## 更新日志
[CHANGELOG](https://github.com/vbenjs/vue-vben-admin/releases)
## 如何贡献
非常欢迎你的加入![提一个 Issue](https://github.com/anncwb/vue-vben-admin/issues/new/choose) 或者提交一个 Pull Request。
@@ -120,6 +124,10 @@ pnpm build
[@Vben](https://github.com/anncwb)
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=vbenjs/vue-vben-admin&type=Date)](https://star-history.com/#vbenjs/vue-vben-admin&Date)
## 捐赠
如果你觉得这个项目对你有帮助,你可以帮作者买一杯咖啡表示支持!
@@ -128,10 +136,6 @@ pnpm build
<a style="display: block;width: 100px;height: 50px;line-height: 50px; color: #fff;text-align: center; background: #408aed;border-radius: 4px;" href="https://www.paypal.com/paypalme/cvvben">Paypal Me</a>
## 更新日志
[CHANGELOG](https://github.com/vbenjs/vue-vben-admin/releases)
## Contributor
<a href="https://github.com/vbenjs/vue-vben-admin/graphs/contributors">

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/web-antd",
"version": "5.3.0",
"version": "5.3.2",
"homepage": "https://vben.pro",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -4,6 +4,7 @@ import type {
VbenFormProps,
} from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue';
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
@@ -57,6 +58,16 @@ export type FormComponentType =
| 'Upload'
| BaseFormComponentType;
const withDefaultPlaceholder = <T extends Component>(
component: T,
type: 'input' | 'select',
) => {
return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
const placeholder = props?.placeholder || $t(`placeholder.${type}`);
return h(component, { ...props, ...attrs, placeholder }, slots);
};
};
// 初始化表单组件并注册到form组件内部
setupVbenForm<FormComponentType>({
components: {
@@ -73,20 +84,20 @@ setupVbenForm<FormComponentType>({
return h(Button, { ...props, attrs, type: 'primary' }, slots);
},
Divider,
Input,
InputNumber,
InputPassword,
Mentions,
Input: withDefaultPlaceholder(Input, 'input'),
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
Mentions: withDefaultPlaceholder(Mentions, 'input'),
Radio,
RadioGroup,
RangePicker,
Rate,
Select,
Select: withDefaultPlaceholder(Select, 'select'),
Space,
Switch,
Textarea,
Textarea: withDefaultPlaceholder(Textarea, 'input'),
TimePicker,
TreeSelect,
TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
Upload,
},
config: {

View File

@@ -10,10 +10,6 @@ export namespace AuthApi {
/** 登录接口返回值 */
export interface LoginResult {
accessToken: string;
desc: string;
realName: string;
userId: string;
username: string;
}
export interface RefreshTokenResult {

View File

@@ -1,6 +1,8 @@
/**
* 该文件可自行根据业务逻辑进行调整
*/
import type { HttpResponse } from '@vben/request';
import { useAppConfig } from '@vben/hooks';
import { preferences } from '@vben/preferences';
import {
@@ -68,7 +70,7 @@ function createRequestClient(baseURL: string) {
});
// response数据解构
client.addResponseInterceptor({
client.addResponseInterceptor<HttpResponse>({
fulfilled: (response) => {
const { data: responseData, status } = response;
@@ -93,7 +95,10 @@ function createRequestClient(baseURL: string) {
// 通用的错误处理,如果没有进入上面的错误处理逻辑,就会进入这里
client.addResponseInterceptor(
errorMessageResponseInterceptor((msg: string) => message.error(msg)),
errorMessageResponseInterceptor((msg: string, _error) => {
// 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg
message.error(msg);
}),
);
return client;

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/web-ele",
"version": "5.3.0",
"version": "5.3.2",
"homepage": "https://vben.pro",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -4,6 +4,7 @@ import type {
VbenFormProps,
} from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue';
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
@@ -42,6 +43,16 @@ export type FormComponentType =
| 'Upload'
| BaseFormComponentType;
const withDefaultPlaceholder = <T extends Component>(
component: T,
type: 'input' | 'select',
) => {
return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
const placeholder = props?.placeholder || $t(`placeholder.${type}`);
return h(component, { ...props, ...attrs, placeholder }, slots);
};
};
// 初始化表单组件并注册到form组件内部
setupVbenForm<FormComponentType>({
components: {
@@ -56,14 +67,14 @@ setupVbenForm<FormComponentType>({
return h(ElButton, { ...props, attrs, type: 'primary' }, slots);
},
Divider: ElDivider,
Input: ElInput,
InputNumber: ElInputNumber,
Input: withDefaultPlaceholder(ElInput, 'input'),
InputNumber: withDefaultPlaceholder(ElInputNumber, 'input'),
RadioGroup: ElRadioGroup,
Select: ElSelect,
Select: withDefaultPlaceholder(ElSelect, 'select'),
Space: ElSpace,
Switch: ElSwitch,
TimePicker: ElTimePicker,
TreeSelect: ElTreeSelect,
TreeSelect: withDefaultPlaceholder(ElTreeSelect, 'select'),
Upload: ElUpload,
},
config: {

View File

@@ -10,10 +10,6 @@ export namespace AuthApi {
/** 登录接口返回值 */
export interface LoginResult {
accessToken: string;
desc: string;
realName: string;
userId: string;
username: string;
}
export interface RefreshTokenResult {

View File

@@ -1,6 +1,8 @@
/**
* 该文件可自行根据业务逻辑进行调整
*/
import type { HttpResponse } from '@vben/request';
import { useAppConfig } from '@vben/hooks';
import { preferences } from '@vben/preferences';
import {
@@ -68,7 +70,7 @@ function createRequestClient(baseURL: string) {
});
// response数据解构
client.addResponseInterceptor({
client.addResponseInterceptor<HttpResponse>({
fulfilled: (response) => {
const { data: responseData, status } = response;
@@ -93,7 +95,10 @@ function createRequestClient(baseURL: string) {
// 通用的错误处理,如果没有进入上面的错误处理逻辑,就会进入这里
client.addResponseInterceptor(
errorMessageResponseInterceptor((msg: string) => ElMessage.error(msg)),
errorMessageResponseInterceptor((msg: string, _error) => {
// 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg
ElMessage.error(msg);
}),
);
return client;

View File

@@ -7,6 +7,7 @@ import {
ElMessage,
ElNotification,
ElSpace,
ElTable,
} from 'element-plus';
type NotificationType = 'error' | 'info' | 'success' | 'warning';
@@ -38,6 +39,14 @@ function notify(type: NotificationType) {
type,
});
}
const tableData = [
{ prop1: '1', prop2: 'A' },
{ prop1: '2', prop2: 'B' },
{ prop1: '3', prop2: 'C' },
{ prop1: '4', prop2: 'D' },
{ prop1: '5', prop2: 'E' },
{ prop1: '6', prop2: 'F' },
];
</script>
<template>
@@ -74,5 +83,11 @@ function notify(type: NotificationType) {
<ElButton type="success" @click="notify('success')"> 成功 </ElButton>
</ElSpace>
</ElCard>
<ElCard class="mb-5">
<ElTable :data="tableData" stripe>
<ElTable.TableColumn label="测试列1" prop="prop1" />
<ElTable.TableColumn label="测试列2" prop="prop2" />
</ElTable>
</ElCard>
</Page>
</template>

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/web-naive",
"version": "5.3.0",
"version": "5.3.2",
"homepage": "https://vben.pro",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -4,6 +4,7 @@ import type {
VbenFormProps,
} from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue';
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
@@ -43,6 +44,16 @@ export type FormComponentType =
| 'Upload'
| BaseFormComponentType;
const withDefaultPlaceholder = <T extends Component>(
component: T,
type: 'input' | 'select',
) => {
return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
const placeholder = props?.placeholder || $t(`placeholder.${type}`);
return h(component, { ...props, ...attrs, placeholder }, slots);
};
};
// 初始化表单组件并注册到form组件内部
setupVbenForm<FormComponentType>({
components: {
@@ -62,17 +73,18 @@ setupVbenForm<FormComponentType>({
);
},
Divider: NDivider,
Input: NInput,
InputNumber: NInputNumber,
Input: withDefaultPlaceholder(NInput, 'input'),
InputNumber: withDefaultPlaceholder(NInputNumber, 'input'),
RadioGroup: NRadioGroup,
Select: NSelect,
Select: withDefaultPlaceholder(NSelect, 'select'),
Space: NSpace,
Switch: NSwitch,
TimePicker: NTimePicker,
TreeSelect: NTreeSelect,
TreeSelect: withDefaultPlaceholder(NTreeSelect, 'select'),
Upload: NUpload,
},
config: {
disabledOnChangeListener: true,
baseModelPropName: 'value',
modelPropNameMap: {
Checkbox: 'checked',

View File

@@ -10,10 +10,6 @@ export namespace AuthApi {
/** 登录接口返回值 */
export interface LoginResult {
accessToken: string;
desc: string;
realName: string;
userId: string;
username: string;
}
export interface RefreshTokenResult {

View File

@@ -1,6 +1,8 @@
/**
* 该文件可自行根据业务逻辑进行调整
*/
import type { HttpResponse } from '@vben/request';
import { useAppConfig } from '@vben/hooks';
import { preferences } from '@vben/preferences';
import {
@@ -67,7 +69,7 @@ function createRequestClient(baseURL: string) {
});
// response数据解构
client.addResponseInterceptor({
client.addResponseInterceptor<HttpResponse>({
fulfilled: (response) => {
const { data: responseData, status } = response;
@@ -92,7 +94,10 @@ function createRequestClient(baseURL: string) {
// 通用的错误处理,如果没有进入上面的错误处理逻辑,就会进入这里
client.addResponseInterceptor(
errorMessageResponseInterceptor((msg: string) => message.error(msg)),
errorMessageResponseInterceptor((msg: string, _error) => {
// 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg
message.error(msg);
}),
);
return client;

View File

@@ -25,7 +25,6 @@ const routes: RouteRecordRaw[] = [
},
{
meta: {
icon: 'mdi:shield-key-outline',
title: $t('page.demos.table'),
},
name: 'Table',

View File

@@ -133,12 +133,19 @@ function sidebarCommercial(): DefaultTheme.SidebarItem[] {
function nav(): DefaultTheme.NavItem[] {
return [
{
activeMatch: '^/en/(guide|components)/',
text: 'Doc',
items: [
{
activeMatch: '^/en/guide/',
link: '/en/guide/introduction/vben',
text: 'Guide',
},
// {
// activeMatch: '^/en/components/',
// link: '/en/components/introduction',
// text: 'Components',
// },
{
text: 'Historical Versions',
items: [

View File

@@ -176,13 +176,16 @@ function sidebarComponents(): DefaultTheme.SidebarItem[] {
function nav(): DefaultTheme.NavItem[] {
return [
{
activeMatch: '^/(guide|components)/',
text: '文档',
items: [
{
activeMatch: '^/guide/',
link: '/guide/introduction/vben',
text: '指南',
},
{
activeMatch: '^/components/',
link: '/components/introduction',
text: '组件',
},

View File

@@ -9,3 +9,14 @@ html.dark {
.form-valid-error p {
margin: 0;
}
/* 顶部导航栏选中项样式 */
.VPNavBarMenuLink,
.VPNavBarMenuGroup {
border-bottom: 1px solid transparent;
}
.VPNavBarMenuLink.active,
.VPNavBarMenuGroup.active {
border-bottom-color: var(--vp-c-brand-1);
}

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/docs",
"version": "5.3.0",
"version": "5.3.2",
"private": true,
"scripts": {
"build": "vitepress build",

View File

@@ -31,6 +31,7 @@ import type {
VbenFormProps,
} from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue';
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
@@ -84,6 +85,16 @@ export type FormComponentType =
| 'Upload'
| BaseFormComponentType;
const withDefaultPlaceholder = <T extends Component>(
component: T,
type: 'input' | 'select',
) => {
return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
const placeholder = props?.placeholder || $t(`placeholder.${type}`);
return h(component, { ...props, ...attrs, placeholder }, slots);
};
};
// 初始化表单组件并注册到form组件内部
setupVbenForm<FormComponentType>({
components: {
@@ -100,26 +111,27 @@ setupVbenForm<FormComponentType>({
return h(Button, { ...props, attrs, type: 'primary' }, slots);
},
Divider,
Input,
InputNumber,
InputPassword,
Mentions,
Input: withDefaultPlaceholder(Input, 'input'),
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
Mentions: withDefaultPlaceholder(Mentions, 'input'),
Radio,
RadioGroup,
RangePicker,
Rate,
Select,
Select: withDefaultPlaceholder(Select, 'select'),
Space,
Switch,
Textarea,
Textarea: withDefaultPlaceholder(Textarea, 'input'),
TimePicker,
TreeSelect,
TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
Upload,
},
config: {
// 是否禁用onChange事件监听naive ui组件库默认不需要监听onChange事件否则会在控制台报错
disabledOnChangeListener: true,
// ant design vue组件库默认都是 v-model:value
baseModelPropName: 'value',
// 一些组件是 v-model:checked 或者 v-model:fileList
modelPropNameMap: {
Checkbox: 'checked',
@@ -229,7 +241,7 @@ useVbenForm 返回的第二个参数,是一个对象,包含了一些表单
| --- | --- | --- |
| submitForm | 提交表单 | `(e:Event)=>Promise<Record<string,any>>` |
| resetForm | 重置表单 | `()=>Promise<void>` |
| setValues | 设置表单值 | `()=>Promise<Record<string,any>>` |
| setValues | 设置表单值, 默认会过滤不在schema中定义的field, 可通过filterFields形参关闭过滤 | `(fields: Record<string, any>, filterFields?: boolean, shouldValidate?: boolean) => Promise<void>` |
| getValues | 获取表单值 | `(fields:Record<string, any>,shouldValidate: boolean = false)=>Promise<void>` |
| validate | 表单校验 | `()=>Promise<void>` |
| resetValidate | 重置表单校验 | `()=>Promise<void>` |

View File

@@ -46,8 +46,6 @@ The execution command is: `pnpm run [script]` or `npm run [script]`.
```json
{
"scripts": {
// Install dependencies
"bootstrap": "pnpm install",
// Build the project
"build": "cross-env NODE_OPTIONS=--max-old-space-size=8192 turbo build",
// Build the project with analysis
@@ -77,7 +75,7 @@ The execution command is: `pnpm run [script]` or `npm run [script]`.
// Check types
"check:type": "turbo run typecheck",
// Clean the project (delete node_modules, dist, .turbo, etc.)
"clean": "vsh clean",
"clean": "node ./scripts/clean.mjs",
// Commit code
"commit": "czg",
// Start the project (by default, the dev scripts of all packages in the entire repository will run)
@@ -97,7 +95,7 @@ The execution command is: `pnpm run [script]` or `npm run [script]`.
// Lint code
"lint": "vsh lint",
// After installing dependencies, execute the stub script for all packages
"postinstall": "turbo run stub",
"postinstall": "pnpm -r run stub --if-present",
// Only allow using pnpm
"preinstall": "npx only-allow pnpm",
// Install husky
@@ -107,9 +105,9 @@ The execution command is: `pnpm run [script]` or `npm run [script]`.
// Package specification check
"publint": "vsh publint",
// Delete all node_modules, yarn.lock, package.lock.json, and reinstall dependencies
"reinstall": "pnpm clean --del-lock && pnpm bootstrap",
"reinstall": "pnpm clean --del-lock && pnpm install",
// Run vitest unit tests
"test:unit": "vitest",
"test:unit": "vitest run --dom",
// Update project dependencies
"update:deps": " pnpm update --latest --recursive",
// Changeset generation and versioning

View File

@@ -163,6 +163,8 @@ The `src/api/request.ts` within the application can be configured according to t
/**
* This file can be adjusted according to business logic
*/
import type { HttpResponse } from '@vben/request';
import { useAppConfig } from '@vben/hooks';
import { preferences } from '@vben/preferences';
import {
@@ -227,7 +229,7 @@ function createRequestClient(baseURL: string) {
});
// Deal Response Data
client.addResponseInterceptor({
client.addResponseInterceptor<HttpResponse>({
fulfilled: (response) => {
const { data: responseData, status } = response;
@@ -253,7 +255,10 @@ function createRequestClient(baseURL: string) {
// Generic error handling; if none of the above error handling logic is triggered, it will fall back to this.
client.addResponseInterceptor(
errorMessageResponseInterceptor((msg: string) => message.error(msg)),
errorMessageResponseInterceptor((msg: string, _error) => {
// 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg
message.error(msg);
}),
);
return client;

View File

@@ -37,8 +37,6 @@ If you want to adjust the content of the login form, you can configure the `Auth
```vue
<AuthenticationLogin
:loading="authStore.loginLoading"
password-placeholder="123456"
username-placeholder="vben"
@submit="authStore.authLogin"
/>
```

View File

@@ -42,23 +42,6 @@ Check the dependency situation of the entire project and output `unused dependen
pnpm vsh check-dep
```
### vsh clean
Delete the project's `node_modules`, `dist`, `.turbo` directories, etc., to clean the project.
#### Usage
```bash
pnpm vsh clean
```
#### Options
| Option | Description |
| --- | --- |
| `-r,--recursive` | Recursively delete the entire project, default `true` |
| `--del-lock` | Whether to delete the `pnpm-lock.yaml` file, default `true` |
### vsh lint
Lint checks the project to see if the code in the project conforms to standards.

View File

@@ -46,8 +46,6 @@ npm 脚本是项目常见的配置,用于执行一些常见的任务,比如
```json
{
"scripts": {
// 安装依赖
"bootstrap": "pnpm install",
// 构建项目
"build": "cross-env NODE_OPTIONS=--max-old-space-size=8192 turbo build",
// 构建项目并分析
@@ -77,7 +75,7 @@ npm 脚本是项目常见的配置,用于执行一些常见的任务,比如
// 检查类型
"check:type": "turbo run typecheck",
// 清理项目删除node_modules、dist、.turbo等目录
"clean": "vsh clean",
"clean": "node ./scripts/clean.mjs",
// 提交代码
"commit": "czg",
// 启动项目默认会运行整个仓库所有包的dev脚本
@@ -97,7 +95,7 @@ npm 脚本是项目常见的配置,用于执行一些常见的任务,比如
// lint 代码
"lint": "vsh lint",
// 依赖安装完成之后执行所有包的stub脚本
"postinstall": "turbo run stub",
"postinstall": "pnpm -r run stub --if-present",
// 只允许使用pnpm
"preinstall": "npx only-allow pnpm",
// husky的安装
@@ -107,9 +105,9 @@ npm 脚本是项目常见的配置,用于执行一些常见的任务,比如
// 包规范检查
"publint": "vsh publint",
// 删除所有的node_modules、yarn.lock、package.lock.json重新安装依赖
"reinstall": "pnpm clean --del-lock && pnpm bootstrap",
"reinstall": "pnpm clean --del-lock && pnpm install",
// 运行 vitest 单元测试
"test:unit": "vitest",
"test:unit": "vitest run --dom",
// 更新项目依赖
"update:deps": " pnpm update --latest --recursive",
// changeset生成提交集

View File

@@ -163,6 +163,8 @@ export async function deleteUserApi(user: UserInfo) {
/**
* 该文件可自行根据业务逻辑进行调整
*/
import type { HttpResponse } from '@vben/request';
import { useAppConfig } from '@vben/hooks';
import { preferences } from '@vben/preferences';
import {
@@ -230,7 +232,7 @@ function createRequestClient(baseURL: string) {
});
// response数据解构
client.addResponseInterceptor({
client.addResponseInterceptor<HttpResponse>({
fulfilled: (response) => {
const { data: responseData, status } = response;
@@ -256,7 +258,10 @@ function createRequestClient(baseURL: string) {
// 通用的错误处理,如果没有进入上面的错误处理逻辑,就会进入这里
client.addResponseInterceptor(
errorMessageResponseInterceptor((msg: string) => message.error(msg)),
errorMessageResponseInterceptor((msg: string, _error) => {
// 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg
message.error(msg);
}),
);
return client;

View File

@@ -1,10 +1,14 @@
---
outline: deep
---
# 登录
本文介绍如何去改造自己的应用程序登录页。
本文介绍如何去改造自己的应用程序登录页以及如何快速的对接登录页面接口
## 登录页面调整
如果你想调整登录页面的标题、描述和图标以及工具栏,你可以通过配置 `AuthPageLayout` 组件的 `props` 参数来实现。
如果你想调整登录页面的标题、描述和图标以及工具栏,你可以通过配置 `AuthPageLayout` 组件的参数来实现。
![login](/guide/login.png)
@@ -30,8 +34,6 @@
```vue
<AuthenticationLogin
:loading="authStore.loginLoading"
password-placeholder="123456"
username-placeholder="vben"
@submit="authStore.authLogin"
/>
```
@@ -108,8 +110,111 @@
:::
::: tip
::: tip Note
如果这些配置不能满足你的需求,你可以自行实现登录表单及相关登录逻辑。
如果这些配置不能满足你的需求,你可以自行实现登录表单及相关登录逻辑或者给我们提交 `PR`
:::
## 接口对接流程
这里将会快速的介绍如何快速对接自己的后端。
### 前置条件
- 首先文档用的后端服务,接口返回的格式统一如下:
```ts
interface HttpResponse<T = any> {
/**
* 0 表示成功 其他表示失败
* 0 means success, others means fail
*/
code: number;
data: T;
message: string;
}
```
如果你不符合这个格式,你需要先阅读 [服务端交互](../essentials/server.md) 文档,改造你的`request.ts`配置。
- 其次你需要在先将本地代理地址改为你的真实后端地址,你可以在应用下的 `vite.config.mts` 内配置:
```ts
import { defineConfig } from '@vben/vite-config';
export default defineConfig(async () => {
return {
vite: {
server: {
proxy: {
'/api': {
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
// 这里改为你的真实接口地址
target: 'http://localhost:5320/api',
ws: true,
},
},
},
},
};
});
```
### 登录接口
为了能正常登录,你的后端最少需要提供 `2-3` 个接口:
- 登录接口
接口地址可在应用下的 `src/api/core/auth` 内修改,以下为默认接口地址:
```ts
/**
* 登录
*/
export async function loginApi(data: AuthApi.LoginParams) {
return requestClient.post<AuthApi.LoginResult>('/auth/login', data);
}
/** 只需要保证登录接口返回值有 `accessToken` 字段即可 */
export interface LoginResult {
accessToken: string;
}
```
- 获取用户信息接口
接口地址可在应用下的 `src/api/core/user` 内修改,以下为默认接口地址:
```ts
export async function getUserInfoApi() {
return requestClient.get<UserInfo>('/user/info');
}
/** 只需要保证登录接口返回值有以下字段即可,多的字段可以自行使用 */
export interface UserInfo {
roles: string[];
realName: string;
}
```
- 获取权限码 (可选)
这个接口用于获取用户的权限码,权限码是用于控制用户的权限的,接口地址可在应用下的 `src/api/core/auth` 内修改,以下为默认接口地址:
```ts
export async function getAccessCodesApi() {
return requestClient.get<string[]>('/auth/codes');
}
```
如果你不需要这个权限,你只需要把代码改为返回一个空数组即可。
```ts {2}
export async function getAccessCodesApi() {
// 这里返回一个空数组即可
return [];
}
```

View File

@@ -1,3 +1,7 @@
---
outline: deep
---
# 精简版本
`5.0` 版本开始,我们不再提供精简的仓库或者分支。我们的目标是提供一个更加一致的开发体验,同时减少维护成本。在这里,我们将如何介绍自己的项目,如何去精简以及移除不需要的功能。
@@ -74,3 +78,17 @@ pnpm install
- `.github` 文件夹用于存放 GitHub 的配置文件
- `.vscode` 文件夹用于存放 VSCode 的配置文件,如果你使用其他编辑器,可以删除
- `./scripts/deploy` 文件夹用于存放部署脚本如果你不需要docker部署可以删除
## 应用精简
当你确定了某个应用,你还可以进一步精简:
### 删除不需要的路由及页面
- 在应用的 `src/router/routes` 文件中,你可以删除不需要的路由。其中 `core` 文件夹内,如果只需要登录和忘记密码,你可以删除其他路由,如忘记密码、注册等。路由删除后,你可以删除对应的页面文件,在 `src/views/_core` 文件夹中。
- 在应用的 `src/router/routes` 文件中,你可以按需求删除不需要的路由,如`demos``vben` 目录等。路由删除后,你可以删除对应的页面文件,在 `src/views` 文件夹中。
### 删除不需要的组件
- 在应用的 `packages/effects/common-ui/src/ui` 文件夹中,你可以删除不需要的组件,如`about``dashboard` 目录等。删除之前请先确保你的路由中没有引用到这些组件。

View File

@@ -42,16 +42,6 @@ pnpm vsh check-circular
pnpm vsh check-dep
```
### vsh clean
删除项目的`node_modules``dist``.turbo`等目录,清理项目。
#### 用法
```bash
pnpm vsh clean
```
#### 选项
| 选项 | 说明 |

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/commitlint-config",
"version": "5.3.0",
"version": "5.3.2",
"private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",

View File

@@ -24,6 +24,7 @@ export async function node(): Promise<Linter.Config[]> {
'vite',
'@vue/test-utils',
'@vben/tailwind-config',
'@playwright/test',
],
},
],

View File

@@ -15,10 +15,17 @@ const customConfig: Linter.Config[] = [
},
},
{
files: ['packages/effects/**/**', 'packages/types/**/**'],
files: [
'apps/**/**',
'packages/effects/**/**',
'packages/utils/**/**',
'packages/types/**/**',
'packages/locales/**/**',
],
ignores: restrictedImportIgnores,
rules: {
'perfectionist/sort-interfaces': 'off',
'perfectionist/sort-objects': 'off',
},
},
{
@@ -135,7 +142,15 @@ const customConfig: Linter.Config[] = [
},
},
{
files: ['internal/**/**'],
files: ['**/**/playwright.config.ts'],
rules: {
'n/prefer-global/buffer': 'off',
'n/prefer-global/process': 'off',
'no-console': 'off',
},
},
{
files: ['internal/**/**', 'scripts/**/**'],
rules: {
'no-console': 'off',
},

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/stylelint-config",
"version": "5.3.0",
"version": "5.3.2",
"private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/node-utils",
"version": "5.3.0",
"version": "5.3.2",
"private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/tailwind-config",
"version": "5.3.0",
"version": "5.3.2",
"private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",

View File

@@ -29,6 +29,7 @@ const shadcnUiColors = {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))',
hover: 'hsl(var(--accent-hover))',
lighter: 'has(val(--accent-lighter))',
},
background: {
deep: 'hsl(var(--background-deep))',

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/tsconfig",
"version": "5.3.0",
"version": "5.3.2",
"private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/vite-config",
"version": "5.3.0",
"version": "5.3.2",
"private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",

View File

@@ -1,6 +1,6 @@
{
"name": "vben-admin-pro",
"version": "5.3.0",
"version": "5.3.2",
"private": true,
"keywords": [
"monorepo",
@@ -25,11 +25,10 @@
},
"type": "module",
"scripts": {
"bootstrap": "pnpm install",
"build": "cross-env NODE_OPTIONS=--max-old-space-size=8192 turbo build",
"build:analyze": "turbo build:analyze",
"build:docker": "./build-local-docker-image.sh",
"build:antd": "pnpm run build --filter=@vben/web-antd",
"build:docker": "./build-local-docker-image.sh",
"build:docs": "pnpm run build --filter=@vben/docs",
"build:ele": "pnpm run build --filter=@vben/web-ele",
"build:naive": "pnpm run build --filter=@vben/web-naive",
@@ -40,7 +39,7 @@
"check:cspell": "cspell lint **/*.ts **/README.md .changeset/*.md --no-progress",
"check:dep": "vsh check-dep",
"check:type": "turbo run typecheck",
"clean": "vsh clean",
"clean": "node ./scripts/clean.mjs",
"commit": "czg",
"dev": "turbo-run dev",
"dev:antd": "pnpm -F @vben/web-antd run dev",
@@ -50,20 +49,21 @@
"dev:play": "pnpm -F @vben/playground run dev",
"format": "vsh lint --format",
"lint": "vsh lint",
"postinstall": "turbo run stub",
"postinstall": "pnpm -r run stub --if-present",
"preinstall": "npx only-allow pnpm",
"prepare": "is-ci || husky",
"preview": "turbo-run preview",
"publint": "vsh publint",
"reinstall": "pnpm clean --del-lock && pnpm bootstrap",
"test:unit": "vitest",
"reinstall": "pnpm clean --del-lock && pnpm install",
"test:unit": "vitest run --dom",
"test:e2e": "turbo run test:e2e",
"update:deps": "pnpm update --latest --recursive",
"version": "pnpm exec changeset version && pnpm install --no-frozen-lockfile"
},
"devDependencies": {
"@changesets/changelog-github": "catalog:",
"@changesets/cli": "catalog:",
"@types/jsdom": "catalog:",
"@playwright/test": "catalog:",
"@types/node": "catalog:",
"@vben/commitlint-config": "workspace:*",
"@vben/eslint-config": "workspace:*",
@@ -80,10 +80,11 @@
"autoprefixer": "catalog:",
"cross-env": "catalog:",
"cspell": "catalog:",
"happy-dom": "catalog:",
"husky": "catalog:",
"is-ci": "catalog:",
"jsdom": "catalog:",
"lint-staged": "catalog:",
"playwright": "catalog:",
"rimraf": "catalog:",
"tailwindcss": "catalog:",
"turbo": "catalog:",
@@ -98,7 +99,7 @@
"node": ">=20.10.0",
"pnpm": ">=9.5.0"
},
"packageManager": "pnpm@9.11.0",
"packageManager": "pnpm@9.12.0",
"pnpm": {
"peerDependencyRules": {
"allowedVersions": {
@@ -109,12 +110,11 @@
"@ctrl/tinycolor": "4.1.0",
"clsx": "2.1.1",
"pinia": "2.2.2",
"vue": "3.5.7"
"vue": "3.5.10"
},
"neverBuiltDependencies": [
"canvas",
"node-gyp",
"playwright"
"node-gyp"
]
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@vben-core/design",
"version": "5.3.0",
"version": "5.3.2",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -82,11 +82,11 @@
@apply opacity-100;
}
input:-webkit-autofill {
/* input:-webkit-autofill {
@apply border-none;
box-shadow: 0 0 0 1000px transparent inset;
}
} */
input[type='number']::-webkit-inner-spin-button,
input[type='number']::-webkit-outer-spin-button {

View File

@@ -53,6 +53,7 @@
/* Used for accents such as hover effects on <DropdownMenuItem>, <SelectItem>...etc */
--accent: 216 5% 19%;
--accent-lighter: 216 5% 11%;
--accent-hover: 216 5% 24%;
--accent-foreground: 0 0% 98%;

View File

@@ -53,6 +53,7 @@
/* Used for accents such as hover effects on <DropdownMenuItem>, <SelectItem>...etc */
--accent: 240 5% 96%;
--accent-lighter: 240 0% 98%;
--accent-hover: 200deg 10% 90%;
--accent-foreground: 240 6% 10%;

View File

@@ -1,6 +1,6 @@
{
"name": "@vben-core/icons",
"version": "5.3.0",
"version": "5.3.2",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@vben-core/shared",
"version": "5.3.0",
"version": "5.3.2",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest';
import { bindMethods } from '../util';
import { bindMethods, getNestedValue } from '../util';
class TestClass {
public value: string;
@@ -78,3 +78,79 @@ describe('bindMethods', () => {
expect(value).toBe('test');
});
});
describe('getNestedValue', () => {
interface UserProfile {
age: number;
name: string;
}
interface UserSettings {
theme: string;
}
interface Data {
user: {
profile: UserProfile;
settings: UserSettings;
};
}
const data: Data = {
user: {
profile: {
age: 25,
name: 'Alice',
},
settings: {
theme: 'dark',
},
},
};
it('should get a nested value when the path is valid', () => {
const result = getNestedValue(data, 'user.profile.name');
expect(result).toBe('Alice');
});
it('should return undefined for non-existent property', () => {
const result = getNestedValue(data, 'user.profile.gender');
expect(result).toBeUndefined();
});
it('should return undefined when accessing a non-existent deep path', () => {
const result = getNestedValue(data, 'user.nonexistent.field');
expect(result).toBeUndefined();
});
it('should return undefined if a middle level is undefined', () => {
const result = getNestedValue({ user: undefined }, 'user.profile.name');
expect(result).toBeUndefined();
});
it('should return the correct value for a nested setting', () => {
const result = getNestedValue(data, 'user.settings.theme');
expect(result).toBe('dark');
});
it('should work for a single-level path', () => {
const result = getNestedValue({ a: 1, b: 2 }, 'b');
expect(result).toBe(2);
});
it('should return the entire object if path is empty', () => {
expect(() => getNestedValue(data, '')()).toThrow();
});
it('should handle paths with array indexes', () => {
const complexData = { list: [{ name: 'Item1' }, { name: 'Item2' }] };
const result = getNestedValue(complexData, 'list.1.name');
expect(result).toBe('Item2');
});
it('should return undefined when accessing an out-of-bounds array index', () => {
const complexData = { list: [{ name: 'Item1' }] };
const result = getNestedValue(complexData, 'list.2.name');
expect(result).toBeUndefined();
});
});

View File

@@ -1 +1,10 @@
import { createDefu } from 'defu';
export { createDefu as createMerge, defu as merge } from 'defu';
export const mergeWithArrayOverride = createDefu((originObj, key, updates) => {
if (Array.isArray(originObj[key]) && Array.isArray(updates)) {
originObj[key] = updates;
return true;
}
});

View File

@@ -17,3 +17,28 @@ export function bindMethods<T extends object>(instance: T): void {
}
});
}
/**
* 获取嵌套对象的字段值
* @param obj - 要查找的对象
* @param path - 用于查找字段的路径,使用小数点分隔
* @returns 字段值,或者未找到时返回 undefined
*/
export function getNestedValue<T>(obj: T, path: string): any {
if (typeof path !== 'string' || path.length === 0) {
throw new Error('Path must be a non-empty string');
}
// 把路径字符串按 "." 分割成数组
const keys = path.split('.') as (number | string)[];
let current: any = obj;
for (const key of keys) {
if (current === null || current === undefined) {
return undefined;
}
current = current[key as keyof typeof current];
}
return current;
}

View File

@@ -1,6 +1,6 @@
{
"name": "@vben-core/typings",
"version": "5.3.0",
"version": "5.3.2",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@vben-core/composables",
"version": "5.3.0",
"version": "5.3.2",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -2,8 +2,8 @@ import { getScrollbarWidth, needsScrollbar } from '@vben-core/shared/utils';
import {
useScrollLock as _useScrollLock,
tryOnBeforeMount,
tryOnBeforeUnmount,
tryOnMounted,
} from '@vueuse/core';
export const SCROLL_FIXED_CLASS = `_scroll__fixed_`;
@@ -12,7 +12,7 @@ export function useScrollLock() {
const isLocked = _useScrollLock(document.body);
const scrollbarWidth = getScrollbarWidth();
tryOnBeforeMount(() => {
tryOnMounted(() => {
if (!needsScrollbar()) {
return;
}

View File

@@ -1,6 +1,6 @@
{
"name": "@vben-core/preferences",
"version": "5.3.0",
"version": "5.3.2",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -171,8 +171,9 @@ class PreferenceManager {
// 加载并合并当前存储的偏好设置
const mergedPreference = merge(
{},
overrides,
this.loadCachedPreferences() || defaultPreferences,
// overrides,
this.loadCachedPreferences() || {},
this.initialPreferences,
);
// 更新偏好设置

View File

@@ -1,24 +1,7 @@
// 假设这个文件为 FormApi.ts
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { FormApi } from '../src/form-api';
vi.mock('@vben-core/shared/utils', () => ({
bindMethods: vi.fn(),
createMerge: vi.fn((mergeFn) => {
return (stateOrFn: any, prev: any) => {
mergeFn(prev, 'key', stateOrFn);
return { ...prev, ...stateOrFn };
};
}),
isFunction: (fn: any) => typeof fn === 'function',
StateHandler: vi.fn().mockImplementation(() => ({
reset: vi.fn(),
setConditionTrue: vi.fn(),
waitForCondition: vi.fn().mockResolvedValue(true),
})),
}));
describe('formApi', () => {
let formApi: FormApi;
@@ -128,7 +111,6 @@ describe('formApi', () => {
it('should unmount form and reset state', () => {
formApi.unmounted();
expect(formApi.isMounted).toBe(false);
expect(formApi.stateHandler.reset).toHaveBeenCalled();
});
it('should validate form', async () => {

View File

@@ -1,4 +1,8 @@
import type { BaseFormComponentType, VbenFormAdapterOptions } from './types';
import type {
BaseFormComponentType,
FormCommonConfig,
VbenFormAdapterOptions,
} from './types';
import type { Component } from 'vue';
import { h } from 'vue';
@@ -16,6 +20,8 @@ import { defineRule } from 'vee-validate';
const DEFAULT_MODEL_PROP_NAME = 'modelValue';
export const DEFAULT_FORM_COMMON_CONFIG: FormCommonConfig = {};
export const COMPONENT_MAP: Record<BaseFormComponentType, Component> = {
DefaultResetActionButton: h(VbenButton, { size: 'sm', variant: 'outline' }),
DefaultSubmitActionButton: h(VbenButton, { size: 'sm', variant: 'default' }),
@@ -37,6 +43,9 @@ export function setupVbenForm<
>(options: VbenFormAdapterOptions<T>) {
const { components, config, defineRules } = options;
DEFAULT_FORM_COMMON_CONFIG.disabledOnChangeListener =
config?.disabledOnChangeListener ?? false;
if (defineRules) {
for (const key of Object.keys(defineRules)) {
defineRule(key, defineRules[key as never]);

View File

@@ -12,17 +12,12 @@ import { toRaw } from 'vue';
import { Store } from '@vben-core/shared/store';
import {
bindMethods,
createMerge,
isFunction,
mergeWithArrayOverride,
StateHandler,
} from '@vben-core/shared/utils';
const merge = createMerge((originObj, key, updates) => {
if (Array.isArray(originObj[key]) && Array.isArray(updates)) {
originObj[key] = updates;
return true;
}
});
import { objectPick } from '@vueuse/core';
function getDefaultState(): VbenFormProps {
return {
@@ -43,11 +38,11 @@ function getDefaultState(): VbenFormProps {
}
export class FormApi {
private prevState: null | VbenFormProps = null;
// private api: Pick<VbenFormProps, 'handleReset' | 'handleSubmit'>;
public form = {} as FormActions;
isMounted = false;
// private prevState!: ModalState;
isMounted = false;
public state: null | VbenFormProps = null;
stateHandler: StateHandler;
@@ -66,7 +61,9 @@ export class FormApi {
},
{
onUpdate: () => {
this.prevState = this.state;
this.state = this.store.state;
this.updateState();
},
},
);
@@ -87,6 +84,24 @@ export class FormApi {
return this.form;
}
private updateState() {
const currentSchema = this.state?.schema ?? [];
const prevSchema = this.prevState?.schema ?? [];
// 进行了删除schema操作
if (currentSchema.length < prevSchema.length) {
const currentFields = new Set(
currentSchema.map((item) => item.fieldName),
);
const deletedSchema = prevSchema.filter(
(item) => !currentFields.has(item.fieldName),
);
for (const schema of deletedSchema) {
this.form?.setFieldValue(schema.fieldName, undefined);
}
}
}
// 如果需要多次更新状态,可以使用 batch 方法
batchStore(cb: () => void) {
this.store.batch(cb);
@@ -101,6 +116,47 @@ export class FormApi {
return form.values;
}
merge(formApi: FormApi) {
const chain = [this, formApi];
const proxy = new Proxy(formApi, {
get(target: any, prop: any) {
if (prop === 'merge') {
return (nextFormApi: FormApi) => {
chain.push(nextFormApi);
return proxy;
};
}
if (prop === 'submitAllForm') {
return async (needMerge: boolean = true) => {
try {
const results = await Promise.all(
chain.map(async (api) => {
const form = await api.getForm();
const validateResult = await api.validate();
if (!validateResult.valid) {
return;
}
const rawValues = toRaw(form.values || {});
return rawValues;
}),
);
if (needMerge) {
const mergedResults = Object.assign({}, ...results);
return mergedResults;
}
return results;
} catch (error) {
console.error('Validation error:', error);
}
};
}
return target[prop];
},
});
return proxy;
}
mount(formActions: FormActions) {
if (!this.isMounted) {
Object.assign(this.form, formActions);
@@ -155,19 +211,32 @@ export class FormApi {
) {
if (isFunction(stateOrFn)) {
this.store.setState((prev) => {
return merge(stateOrFn(prev), prev);
return mergeWithArrayOverride(stateOrFn(prev), prev);
});
} else {
this.store.setState((prev) => merge(stateOrFn, prev));
this.store.setState((prev) => mergeWithArrayOverride(stateOrFn, prev));
}
}
/**
* 设置表单值
* @param fields record
* @param filterFields 过滤不在schema中定义的字段 默认为true
* @param shouldValidate
*/
async setValues(
fields: Record<string, any>,
filterFields: boolean = true,
shouldValidate: boolean = false,
) {
const form = await this.getForm();
form.setValues(fields, shouldValidate);
if (!filterFields) {
form.setValues(fields, shouldValidate);
return;
}
const fieldNames = this.state?.schema?.map((item) => item.fieldName) ?? [];
const filteredFields = objectPick(fields, fieldNames);
form.setValues(filteredFields, shouldValidate);
}
async submitForm(e?: Event) {
@@ -211,7 +280,10 @@ export class FormApi {
currentSchema.forEach((schema, index) => {
const updatedData = updatedMap[schema.fieldName];
if (updatedData) {
currentSchema[index] = merge(updatedData, schema) as FormSchema;
currentSchema[index] = mergeWithArrayOverride(
updatedData,
schema,
) as FormSchema;
}
});
this.setState({ schema: currentSchema });

View File

@@ -3,7 +3,7 @@ import type { ZodType } from 'zod';
import type { FormSchema, MaybeComponentProps } from '../types';
import { computed } from 'vue';
import { computed, nextTick, useTemplateRef, watch } from 'vue';
import {
FormControl,
@@ -32,6 +32,7 @@ const {
dependencies,
description,
disabled,
disabledOnChangeListener,
fieldName,
formFieldProps,
label,
@@ -49,6 +50,7 @@ const { componentBindEventMap, componentMap, isVertical } = useFormContext();
const formRenderProps = injectRenderFormProps();
const values = useFormValues();
const errors = useFieldError(fieldName);
const fieldComponentRef = useTemplateRef<HTMLInputElement>('fieldComponentRef');
const formApi = formRenderProps.form;
const isInValid = computed(() => errors.value?.length > 0);
@@ -156,6 +158,18 @@ const computedProps = computed(() => {
};
});
watch(
() => computedProps.value?.autofocus,
(value) => {
if (value === true) {
nextTick(() => {
autofocus();
});
}
},
{ immediate: true },
);
const shouldDisabled = computed(() => {
return isDisabled.value || disabled || computedProps.value?.disabled;
});
@@ -177,7 +191,7 @@ const fieldProps = computed(() => {
keepValue: true,
label,
...(rules ? { rules } : {}),
...formFieldProps,
...(formFieldProps as Record<string, any>),
};
});
@@ -200,15 +214,16 @@ function fieldBindEvent(slotProps: Record<string, any>) {
return {
[`onUpdate:${bindEventField}`]: handler,
[bindEventField]: value,
onChange: (e: Record<string, any>) => {
const shouldUnwrap = isEventObjectLike(e);
const onChange = slotProps?.componentField?.onChange;
if (!shouldUnwrap) {
return onChange?.(e);
}
return onChange?.(e?.target?.[bindEventField] ?? e);
},
onChange: disabledOnChangeListener
? undefined
: (e: Record<string, any>) => {
const shouldUnwrap = isEventObjectLike(e);
const onChange = slotProps?.componentField?.onChange;
if (!shouldUnwrap) {
return onChange?.(e);
}
return onChange?.(e?.target?.[bindEventField] ?? e);
},
onInput: () => {},
};
}
@@ -226,6 +241,17 @@ function createComponentProps(slotProps: Record<string, any>) {
return binds;
}
function autofocus() {
if (
fieldComponentRef.value &&
isFunction(fieldComponentRef.value.focus) &&
// 检查当前是否有元素被聚焦
document.activeElement !== fieldComponentRef.value
) {
fieldComponentRef.value?.focus?.();
}
}
</script>
<template>
@@ -275,6 +301,7 @@ function createComponentProps(slotProps: Record<string, any>) {
>
<component
:is="fieldComponent"
ref="fieldComponentRef"
:class="{
'border-destructive focus:border-destructive hover:border-destructive/80 focus:shadow-[0_0_0_2px_rgba(255,38,5,0.06)]':
isInValid,

View File

@@ -1,12 +1,17 @@
<script setup lang="ts">
import type { ZodTypeAny } from 'zod';
import type { FormRenderProps, FormSchema, FormShape } from '../types';
import type {
FormCommonConfig,
FormRenderProps,
FormSchema,
FormShape,
} from '../types';
import { computed } from 'vue';
import { Form } from '@vben-core/shadcn-ui';
import { cn, isString } from '@vben-core/shared/utils';
import { cn, isString, mergeWithArrayOverride } from '@vben-core/shared/utils';
import { type GenericObject } from 'vee-validate';
@@ -17,12 +22,16 @@ import { getBaseRules, getDefaultValueInZodStack } from './helper';
interface Props extends FormRenderProps {}
const props = withDefaults(defineProps<Props>(), {
collapsedRows: 1,
commonConfig: () => ({}),
showCollapseButton: false,
wrapperClass: 'grid-cols-1 sm:grid-cols-2 md:grid-cols-3',
});
const props = withDefaults(
defineProps<{ globalCommonConfig?: FormCommonConfig } & Props>(),
{
collapsedRows: 1,
commonConfig: () => ({}),
globalCommonConfig: () => ({}),
showCollapseButton: false,
wrapperClass: 'grid-cols-1 sm:grid-cols-2 md:grid-cols-3',
},
);
const emits = defineEmits<{
submit: [event: any];
@@ -77,6 +86,7 @@ const computedSchema = computed(
componentProps = {},
controlClass = '',
disabled,
disabledOnChangeListener = false,
formFieldProps = {},
formItemClass = '',
hideLabel = false,
@@ -84,7 +94,7 @@ const computedSchema = computed(
labelClass = '',
labelWidth = 100,
wrapperClass = '',
} = props.commonConfig;
} = mergeWithArrayOverride(props.commonConfig, props.globalCommonConfig);
return (props.schema || []).map((schema, index) => {
const keepIndex = keepFormItemIndex.value;
@@ -96,6 +106,7 @@ const computedSchema = computed(
return {
disabled,
disabledOnChangeListener,
hideLabel,
hideRequiredMark,
labelWidth,

View File

@@ -1,5 +1,5 @@
import type { VbenButtonProps } from '@vben-core/shadcn-ui';
import type { Field, FormContext, GenericObject } from 'vee-validate';
import type { FieldOptions, FormContext, GenericObject } from 'vee-validate';
import type { ZodTypeAny } from 'zod';
import type { FormApi } from './form-api';
@@ -33,6 +33,15 @@ export type FormItemClassType =
| (Record<never, never> & string)
| WrapperClassType;
export type FormFieldOptions = Partial<
{
validateOnBlur?: boolean;
validateOnChange?: boolean;
validateOnInput?: boolean;
validateOnModelUpdate?: boolean;
} & FieldOptions
>;
export interface FormShape {
/** 默认值 */
default?: any;
@@ -139,11 +148,16 @@ export interface FormCommonConfig {
* @default false
*/
disabled?: boolean;
/**
* 是否禁用所有表单项的change事件监听
* @default false
*/
disabledOnChangeListener?: boolean;
/**
* 所有表单项的控件样式
* @default {}
*/
formFieldProps?: Partial<typeof Field>;
formFieldProps?: FormFieldOptions;
/**
* 所有表单项的栅格布局
* @default ""
@@ -317,6 +331,7 @@ export interface VbenFormAdapterOptions<
components: Partial<Record<T, Component>>;
config?: {
baseModelPropName?: string;
disabledOnChangeListener?: boolean;
modelPropNameMap?: Partial<Record<T, string>>;
};
defineRules?: {

View File

@@ -6,7 +6,11 @@ import { ref, watchEffect } from 'vue';
import { useForwardPropsEmits } from '@vben-core/composables';
import FormActions from './components/form-actions.vue';
import { COMPONENT_BIND_EVENT_MAP, COMPONENT_MAP } from './config';
import {
COMPONENT_BIND_EVENT_MAP,
COMPONENT_MAP,
DEFAULT_FORM_COMMON_CONFIG,
} from './config';
import { Form } from './form-render';
import { provideFormProps, useFormInitial } from './use-form-context';
@@ -51,6 +55,7 @@ watchEffect(() => {
:component-bind-event-map="COMPONENT_BIND_EVENT_MAP"
:component-map="COMPONENT_MAP"
:form="form"
:global-common-config="DEFAULT_FORM_COMMON_CONFIG"
>
<template
v-for="slotName in delegatedSlots"

View File

@@ -4,10 +4,13 @@ import type { ExtendedFormApi, VbenFormProps } from './types';
import { useForwardPriorityValues } from '@vben-core/composables';
import FormActions from './components/form-actions.vue';
import { COMPONENT_BIND_EVENT_MAP, COMPONENT_MAP } from './config';
import {
COMPONENT_BIND_EVENT_MAP,
COMPONENT_MAP,
DEFAULT_FORM_COMMON_CONFIG,
} from './config';
import { Form } from './form-render';
import { provideFormProps, useFormInitial } from './use-form-context';
// 通过 extends 会导致热更新卡死,所以重复写了一遍
interface Props extends VbenFormProps {
formApi: ExtendedFormApi;
@@ -36,6 +39,7 @@ const handleUpdateCollapsed = (value: boolean) => {
:component-bind-event-map="COMPONENT_BIND_EVENT_MAP"
:component-map="COMPONENT_MAP"
:form="form"
:global-common-config="DEFAULT_FORM_COMMON_CONFIG"
>
<template
v-for="slotName in delegatedSlots"

View File

@@ -1,6 +1,6 @@
{
"name": "@vben-core/layout-ui",
"version": "5.3.0",
"version": "5.3.2",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@vben-core/menu-ui",
"version": "5.3.0",
"version": "5.3.2",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -1,7 +1,7 @@
<script lang="ts" setup>
import type { DrawerProps, ExtendedDrawerApi } from './drawer';
import { ref, watch } from 'vue';
import { provide, ref, useId, watch } from 'vue';
import {
useIsMobile,
@@ -33,9 +33,13 @@ const props = withDefaults(defineProps<Props>(), {
drawerApi: undefined,
});
const id = useId();
provide('DISMISSABLE_DRAWER_ID', id);
const wrapperRef = ref<HTMLElement>();
const { $t } = useSimpleLocale();
const { isMobile } = useIsMobile();
const state = props.drawerApi?.useStore?.();
const {
@@ -83,8 +87,8 @@ function escapeKeyDown(e: KeyboardEvent) {
// pointer-down-outside
function pointerDownOutside(e: Event) {
const target = e.target as HTMLElement;
const isDismissableModal = !!target?.dataset.dismissableModal;
if (!closeOnClickModal.value || !isDismissableModal) {
const dismissableDrawer = target?.dataset.dismissableDrawer;
if (!closeOnClickModal.value || dismissableDrawer !== id) {
e.preventDefault();
}
}

View File

@@ -1,7 +1,7 @@
<script lang="ts" setup>
import type { ExtendedModalApi, ModalProps } from './modal';
import { computed, nextTick, ref, watch } from 'vue';
import { computed, nextTick, provide, ref, useId, watch } from 'vue';
import {
useIsMobile,
@@ -40,6 +40,10 @@ const dialogRef = ref();
const headerRef = ref();
const footerRef = ref();
const id = useId();
provide('DISMISSABLE_MODAL_ID', id);
const { $t } = useSimpleLocale();
const { isMobile } = useIsMobile();
const state = props.modalApi?.useStore?.();
@@ -141,8 +145,8 @@ function handerOpenAutoFocus(e: Event) {
// pointer-down-outside
function pointerDownOutside(e: Event) {
const target = e.target as HTMLElement;
const isDismissableModal = !!target?.dataset.dismissableModal;
if (!closeOnClickModal.value || !isDismissableModal) {
const isDismissableModal = target?.dataset.dismissableModal;
if (!closeOnClickModal.value || isDismissableModal !== id) {
e.preventDefault();
e.stopPropagation();
}

View File

@@ -1,6 +1,8 @@
{
"name": "@vben-core/shadcn-ui",
"version": "5.3.0",
"version": "5.3.2",
"#main": "./dist/index.mjs",
"#module": "./dist/index.mjs",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {
@@ -20,16 +22,14 @@
"sideEffects": [
"**/*.css"
],
"#main": "./dist/index.mjs",
"main": "./src/index.ts",
"#module": "./dist/index.mjs",
"module": "./src/index.ts",
"exports": {
".": {
"types": "./src/index.ts",
"development": "./src/index.ts",
"//default": "./dist/index.mjs",
"default": "./src/index.ts"
"default": "./src/index.ts",
"//default": "./dist/index.mjs"
}
},
"publishConfig": {
@@ -40,7 +40,6 @@
}
},
"dependencies": {
"@radix-icons/vue": "catalog:",
"@vben-core/composables": "workspace:*",
"@vben-core/icons": "workspace:*",
"@vben-core/shared": "workspace:*",

View File

@@ -83,7 +83,7 @@ li:last-child a::after {
li a::before,
li a::after {
@apply border-accent absolute top-0 h-0 w-0 border-[14px] border-solid content-[''];
@apply border-accent absolute top-0 h-0 w-0 border-[.875rem] border-solid content-[''];
}
li a::before {

View File

@@ -14,6 +14,7 @@ export * from './input-password';
export * from './link';
export * from './logo';
export * from './menu-badge';
export * from './pagination';
export * from './pin-input';
export * from './popover';
export * from './render-content';
@@ -38,6 +39,7 @@ export * from './ui/hover-card';
export * from './ui/input';
export * from './ui/label';
export * from './ui/number-field';
export * from './ui/pagination';
export * from './ui/pin-input';
export * from './ui/popover';
export * from './ui/radio-group';

View File

@@ -0,0 +1,2 @@
export type { PaginationProps as VbenPaginationProps } from './pagination';
export { default as VbenPagination } from './pagination.vue';

View File

@@ -0,0 +1,41 @@
export interface PaginationProps {
/**
* 是否禁用
*/
disabled?: boolean;
/**
* 每页记录数选项
*/
pageSizeOptions?: number[];
/**
* 当 时true始终显示第一页、最后一页和省略号
*/
showEdges?: boolean;
/**
* 显示当前页选择下拉框
*/
showRowsPerPage?: boolean;
/**
* 显示总条数文本
*/
showTotalText?: boolean;
/**
* 当前页面周围应显示的兄弟页面数量
*/
siblingCount?: number;
/**
* 组件尺寸
*/
size?: 'default' | 'large' | 'small';
/**
* 总条数
*/
total?: number;
}
export const SIZE_CLASS_MAP = {
default: 'size-8',
large: 'size-9',
small: 'size-7',
};

View File

@@ -0,0 +1,111 @@
<script setup lang="ts">
import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { Button } from '../ui/button';
import {
Pagination,
PaginationEllipsis,
PaginationFirst,
PaginationLast,
PaginationList,
PaginationListItem,
PaginationNext,
PaginationPrev,
} from '../ui/pagination';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '../ui/select';
import { type PaginationProps, SIZE_CLASS_MAP } from './pagination';
interface Props extends PaginationProps {}
const {
disabled = false,
pageSizeOptions = [10, 20, 30, 50, 100, 200],
showEdges = true,
showRowsPerPage = true,
showTotalText = true,
siblingCount = 1,
size = 'default',
total = 500,
} = defineProps<Props>();
const currentPage = defineModel<number>('currentPage', { default: 1 });
const itemPerPage = defineModel<number>('itemPerPage', { default: 20 });
const itemSize = computed(() => {
return SIZE_CLASS_MAP[size];
});
const options = computed(() => {
return pageSizeOptions.map((item) => ({
label: `${item} 条/页`,
value: `${item}`,
}));
});
function handleUpdateModelValue(value: string) {
itemPerPage.value = Number(value);
}
</script>
<template>
<Pagination
v-model:page="currentPage"
:disabled="disabled"
:items-per-page="itemPerPage"
:show-edges="showEdges"
:sibling-count="siblingCount"
:total="total"
>
<PaginationList
v-slot="{ items }"
class="flex w-full items-center justify-end gap-1"
>
<span v-if="showTotalText" class="mr-2"> {{ total }} </span>
<Select
v-if="showRowsPerPage"
:model-value="`${itemPerPage}`"
@update:model-value="handleUpdateModelValue"
>
<SelectTrigger class="w-30 mr-auto h-8">
<SelectValue />
</SelectTrigger>
<SelectContent>
<template v-for="item in options" :key="item.value">
<SelectItem :value="item.value"> {{ item.label }} </SelectItem>
</template>
</SelectContent>
</Select>
<PaginationFirst :class="cn('size-8', itemSize)" />
<PaginationPrev :class="cn('size-8', itemSize)" />
<template v-for="(item, index) in items">
<PaginationListItem
v-if="item.type === 'page'"
:key="index"
:value="item.value"
as-child
>
<Button
:class="cn('size-8 p-0 shadow-none', itemSize)"
:variant="item.value === currentPage ? 'default' : 'outline'"
>
{{ item.value }}
</Button>
</PaginationListItem>
<PaginationEllipsis v-else :key="item.type" :index="index" />
</template>
<PaginationNext :class="cn('size-8', itemSize)" />
<PaginationLast :class="cn('size-8', itemSize)" />
</PaginationList>
</Pagination>
</template>

View File

@@ -3,7 +3,7 @@ import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { ChevronDownIcon } from '@radix-icons/vue';
import { ChevronDown } from 'lucide-vue-next';
import {
AccordionHeader,
AccordionTrigger,
@@ -32,7 +32,7 @@ const delegatedProps = computed(() => {
>
<slot></slot>
<slot name="icon">
<ChevronDownIcon
<ChevronDown
class="text-muted-foreground h-4 w-4 shrink-0 transition-transform duration-200"
/>
</slot>

View File

@@ -1,7 +1,7 @@
<script lang="ts" setup>
import { cn } from '@vben-core/shared/utils';
import { DotsHorizontalIcon } from '@radix-icons/vue';
import { MoreHorizontal } from 'lucide-vue-next';
const props = defineProps<{
class?: any;
@@ -15,7 +15,7 @@ const props = defineProps<{
role="presentation"
>
<slot>
<DotsHorizontalIcon class="h-4 w-4" />
<MoreHorizontal class="h-4 w-4" />
</slot>
<span class="sr-only">More</span>
</span>

View File

@@ -1,7 +1,7 @@
<script lang="ts" setup>
import { cn } from '@vben-core/shared/utils';
import { ChevronRightIcon } from '@radix-icons/vue';
import { ChevronRight } from 'lucide-vue-next';
const props = defineProps<{
class?: any;
@@ -15,7 +15,7 @@ const props = defineProps<{
role="presentation"
>
<slot>
<ChevronRightIcon />
<ChevronRight />
</slot>
</li>
</template>

View File

@@ -10,7 +10,7 @@ import { buttonVariants } from './button';
interface Props extends PrimitiveProps {
class?: any;
size?: ButtonVariantSize;
variant?: 'heavy' & ButtonVariants;
variant?: ButtonVariants;
}
const props = withDefaults(defineProps<Props>(), {

View File

@@ -5,7 +5,7 @@ import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { CheckIcon } from '@radix-icons/vue';
import { Check } from 'lucide-vue-next';
import {
CheckboxIndicator,
CheckboxRoot,
@@ -38,7 +38,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
class="flex h-full w-full items-center justify-center text-current"
>
<slot>
<CheckIcon class="h-4 w-4" />
<Check class="h-4 w-4" />
</slot>
</CheckboxIndicator>
</CheckboxRoot>

View File

@@ -3,7 +3,7 @@ import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { CheckIcon } from '@radix-icons/vue';
import { Check } from 'lucide-vue-next';
import {
ContextMenuCheckboxItem,
type ContextMenuCheckboxItemEmits,
@@ -36,7 +36,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
>
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<ContextMenuItemIndicator>
<CheckIcon class="h-4 w-4" />
<Check class="h-4 w-4" />
</ContextMenuItemIndicator>
</span>
<slot></slot>

View File

@@ -3,7 +3,7 @@ import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { DotFilledIcon } from '@radix-icons/vue';
import { Circle } from 'lucide-vue-next';
import {
ContextMenuItemIndicator,
ContextMenuRadioItem,
@@ -36,7 +36,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
>
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<ContextMenuItemIndicator>
<DotFilledIcon class="h-4 w-4 fill-current" />
<Circle class="h-2 w-2 fill-current" />
</ContextMenuItemIndicator>
</span>
<slot></slot>

View File

@@ -3,7 +3,7 @@ import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { ChevronRightIcon } from '@radix-icons/vue';
import { ChevronRight } from 'lucide-vue-next';
import {
ContextMenuSubTrigger,
type ContextMenuSubTriggerProps,
@@ -38,6 +38,6 @@ const forwardedProps = useForwardProps(delegatedProps);
"
>
<slot></slot>
<ChevronRightIcon class="ml-auto h-4 w-4" />
<ChevronRight class="ml-auto h-4 w-4" />
</ContextMenuSubTrigger>
</template>

View File

@@ -3,7 +3,7 @@ import { computed, ref } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { Cross2Icon } from '@radix-icons/vue';
import { X } from 'lucide-vue-next';
import {
DialogClose,
DialogContent,
@@ -77,7 +77,7 @@ defineExpose({
"
@click="() => emits('close')"
>
<Cross2Icon class="h-4 w-4" />
<X class="h-4 w-4" />
</DialogClose>
</DialogContent>
</DialogPortal>

View File

@@ -1,11 +1,14 @@
<script setup lang="ts">
import { inject } from 'vue';
import { useScrollLock } from '@vben-core/composables';
useScrollLock();
const id = inject('DISMISSABLE_MODAL_ID');
</script>
<template>
<div
:data-dismissable-modal="id"
class="bg-overlay fixed inset-0 z-[1000]"
data-dismissable-modal="true"
></div>
</template>

View File

@@ -3,6 +3,7 @@ import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { X } from 'lucide-vue-next';
import {
DialogClose,
DialogContent,
@@ -56,7 +57,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
<DialogClose
class="hover:bg-secondary absolute right-4 top-4 rounded-md p-0.5 transition-colors"
>
<Cross2Icon class="h-4 w-4" />
<X class="h-4 w-4" />
<span class="sr-only">Close</span>
</DialogClose>
</DialogContent>

View File

@@ -3,7 +3,7 @@ import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { CheckIcon } from '@radix-icons/vue';
import { Check } from 'lucide-vue-next';
import {
DropdownMenuCheckboxItem,
type DropdownMenuCheckboxItemEmits,
@@ -36,7 +36,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
>
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuItemIndicator>
<CheckIcon class="h-4 w-4" />
<Check class="h-4 w-4" />
</DropdownMenuItemIndicator>
</span>
<slot></slot>

View File

@@ -3,7 +3,7 @@ import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { DotFilledIcon } from '@radix-icons/vue';
import { Circle } from 'lucide-vue-next';
import {
DropdownMenuItemIndicator,
DropdownMenuRadioItem,
@@ -37,7 +37,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
>
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuItemIndicator>
<DotFilledIcon class="h-4 w-4 fill-current" />
<Circle class="h-2 w-2 fill-current" />
</DropdownMenuItemIndicator>
</span>
<slot></slot>

View File

@@ -3,7 +3,7 @@ import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { ChevronRightIcon } from '@radix-icons/vue';
import { ChevronRight } from 'lucide-vue-next';
import {
DropdownMenuSubTrigger,
type DropdownMenuSubTriggerProps,
@@ -32,6 +32,6 @@ const forwardedProps = useForwardProps(delegatedProps);
"
>
<slot></slot>
<ChevronRightIcon class="ml-auto h-4 w-4" />
<ChevronRight class="ml-auto h-4 w-4" />
</DropdownMenuSubTrigger>
</template>

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