Compare commits

..

21 Commits

Author SHA1 Message Date
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
151 changed files with 2546 additions and 1540 deletions

View File

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

View File

@@ -18,7 +18,7 @@ env:
jobs: jobs:
version: 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' # if: github.repository == 'vbenjs/vue-vben-admin'
timeout-minutes: 15 timeout-minutes: 15
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

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

View File

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

View File

@@ -8,7 +8,7 @@ on:
jobs: jobs:
deploy-playground-ftp: deploy-playground-ftp:
name: Deploy Push 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 runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
@@ -39,7 +39,7 @@ jobs:
deploy-docs-ftp: deploy-docs-ftp:
name: Deploy Push 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 runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
@@ -63,7 +63,7 @@ jobs:
deploy-antd-ftp: deploy-antd-ftp:
name: Deploy Push 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 runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
@@ -94,7 +94,7 @@ jobs:
deploy-ele-ftp: deploy-ele-ftp:
name: Deploy Push Element 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 runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
@@ -125,7 +125,7 @@ jobs:
deploy-naive-ftp: deploy-naive-ftp:
name: Deploy Push 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 runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code

View File

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

View File

@@ -3,23 +3,29 @@ name: Issue Close Require
# 触发条件:每天零点 # 触发条件:每天零点
on: on:
workflow_dispatch:
schedule: schedule:
- cron: '0 0 * * *' - cron: '0 0 * * *'
permissions: permissions:
pull-requests: write pull-requests: write
contents: write contents: write
issues: write
jobs: jobs:
close-issues: close-issues:
if: github.repository == 'vbenjs/vue-vben-admin'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
# 步骤1关闭未活动的 Issues # 关闭未活动的 Issues
- name: Close Inactive Issues - name: Close Inactive Issues
uses: actions-cool/issues-helper@v3 uses: actions/stale@v9
with: with:
actions: 'close-issues' # 执行动作:关闭 Issues days-before-stale: -1 # Issues and PR will never be flagged stale automatically.
token: ${{ secrets.GITHUB_TOKEN }} # GitHub Token用于认证 stale-issue-label: needs-reproduction # Label that flags an issue as stale.
labels: 'needs reproduction' # 目标标签 only-labels: needs-reproduction # Only process these issues
inactive-day: 3 # 未活动天数阈值 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: jobs:
reply-labeled: reply-labeled:
if: github.repository == 'vbenjs/vue-vben-admin'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: remove enhancement pending - name: remove enhancement pending

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,4 +3,4 @@ ports:
onOpen: open-preview onOpen: open-preview
tasks: tasks:
- init: corepack enable && pnpm install - 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) [@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) [@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 ## Donate
If you think this project is helpful to you, you can help the author buy a cup of coffee to show your support! 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 pnpm build
``` ```
## 更新日志
[CHANGELOG](https://github.com/vbenjs/vue-vben-admin/releases)
## 如何贡献 ## 如何贡献
非常欢迎你的加入![提一个 Issue](https://github.com/anncwb/vue-vben-admin/issues/new/choose) 或者提交一个 Pull Request。 非常欢迎你的加入![提一个 Issue](https://github.com/anncwb/vue-vben-admin/issues/new/choose) 或者提交一个 Pull Request。
@@ -120,6 +124,10 @@ pnpm build
[@Vben](https://github.com/anncwb) [@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> <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 ## Contributor
<a href="https://github.com/vbenjs/vue-vben-admin/graphs/contributors"> <a href="https://github.com/vbenjs/vue-vben-admin/graphs/contributors">

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,3 +9,14 @@ html.dark {
.form-valid-error p { .form-valid-error p {
margin: 0; 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", "name": "@vben/docs",
"version": "5.3.0", "version": "5.3.1",
"private": true, "private": true,
"scripts": { "scripts": {
"build": "vitepress build", "build": "vitepress build",

View File

@@ -229,7 +229,7 @@ useVbenForm 返回的第二个参数,是一个对象,包含了一些表单
| --- | --- | --- | | --- | --- | --- |
| submitForm | 提交表单 | `(e:Event)=>Promise<Record<string,any>>` | | submitForm | 提交表单 | `(e:Event)=>Promise<Record<string,any>>` |
| resetForm | 重置表单 | `()=>Promise<void>` | | 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>` | | getValues | 获取表单值 | `(fields:Record<string, any>,shouldValidate: boolean = false)=>Promise<void>` |
| validate | 表单校验 | `()=>Promise<void>` | | validate | 表单校验 | `()=>Promise<void>` |
| resetValidate | 重置表单校验 | `()=>Promise<void>` | | resetValidate | 重置表单校验 | `()=>Promise<void>` |

View File

@@ -46,8 +46,6 @@ The execution command is: `pnpm run [script]` or `npm run [script]`.
```json ```json
{ {
"scripts": { "scripts": {
// Install dependencies
"bootstrap": "pnpm install",
// Build the project // Build the project
"build": "cross-env NODE_OPTIONS=--max-old-space-size=8192 turbo build", "build": "cross-env NODE_OPTIONS=--max-old-space-size=8192 turbo build",
// Build the project with analysis // Build the project with analysis
@@ -77,7 +75,7 @@ The execution command is: `pnpm run [script]` or `npm run [script]`.
// Check types // Check types
"check:type": "turbo run typecheck", "check:type": "turbo run typecheck",
// Clean the project (delete node_modules, dist, .turbo, etc.) // Clean the project (delete node_modules, dist, .turbo, etc.)
"clean": "vsh clean", "clean": "node ./scripts/clean.mjs",
// Commit code // Commit code
"commit": "czg", "commit": "czg",
// Start the project (by default, the dev scripts of all packages in the entire repository will run) // Start the project (by default, the dev scripts of all packages in the entire repository will run)
@@ -107,9 +105,9 @@ The execution command is: `pnpm run [script]` or `npm run [script]`.
// Package specification check // Package specification check
"publint": "vsh publint", "publint": "vsh publint",
// Delete all node_modules, yarn.lock, package.lock.json, and reinstall dependencies // 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 // Run vitest unit tests
"test:unit": "vitest", "test:unit": "vitest run --dom",
// Update project dependencies // Update project dependencies
"update:deps": " pnpm update --latest --recursive", "update:deps": " pnpm update --latest --recursive",
// Changeset generation and versioning // 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 * This file can be adjusted according to business logic
*/ */
import type { HttpResponse } from '@vben/request';
import { useAppConfig } from '@vben/hooks'; import { useAppConfig } from '@vben/hooks';
import { preferences } from '@vben/preferences'; import { preferences } from '@vben/preferences';
import { import {
@@ -227,7 +229,7 @@ function createRequestClient(baseURL: string) {
}); });
// Deal Response Data // Deal Response Data
client.addResponseInterceptor({ client.addResponseInterceptor<HttpResponse>({
fulfilled: (response) => { fulfilled: (response) => {
const { data: responseData, status } = 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. // Generic error handling; if none of the above error handling logic is triggered, it will fall back to this.
client.addResponseInterceptor( client.addResponseInterceptor(
errorMessageResponseInterceptor((msg: string) => message.error(msg)), errorMessageResponseInterceptor((msg: string, _error) => {
// 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg
message.error(msg);
}),
); );
return client; 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 ```vue
<AuthenticationLogin <AuthenticationLogin
:loading="authStore.loginLoading" :loading="authStore.loginLoading"
password-placeholder="123456"
username-placeholder="vben"
@submit="authStore.authLogin" @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 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 ### vsh lint
Lint checks the project to see if the code in the project conforms to standards. Lint checks the project to see if the code in the project conforms to standards.

View File

@@ -46,8 +46,6 @@ npm 脚本是项目常见的配置,用于执行一些常见的任务,比如
```json ```json
{ {
"scripts": { "scripts": {
// 安装依赖
"bootstrap": "pnpm install",
// 构建项目 // 构建项目
"build": "cross-env NODE_OPTIONS=--max-old-space-size=8192 turbo build", "build": "cross-env NODE_OPTIONS=--max-old-space-size=8192 turbo build",
// 构建项目并分析 // 构建项目并分析
@@ -77,7 +75,7 @@ npm 脚本是项目常见的配置,用于执行一些常见的任务,比如
// 检查类型 // 检查类型
"check:type": "turbo run typecheck", "check:type": "turbo run typecheck",
// 清理项目删除node_modules、dist、.turbo等目录 // 清理项目删除node_modules、dist、.turbo等目录
"clean": "vsh clean", "clean": "node ./scripts/clean.mjs",
// 提交代码 // 提交代码
"commit": "czg", "commit": "czg",
// 启动项目默认会运行整个仓库所有包的dev脚本 // 启动项目默认会运行整个仓库所有包的dev脚本
@@ -107,9 +105,9 @@ npm 脚本是项目常见的配置,用于执行一些常见的任务,比如
// 包规范检查 // 包规范检查
"publint": "vsh publint", "publint": "vsh publint",
// 删除所有的node_modules、yarn.lock、package.lock.json重新安装依赖 // 删除所有的node_modules、yarn.lock、package.lock.json重新安装依赖
"reinstall": "pnpm clean --del-lock && pnpm bootstrap", "reinstall": "pnpm clean --del-lock && pnpm install",
// 运行 vitest 单元测试 // 运行 vitest 单元测试
"test:unit": "vitest", "test:unit": "vitest run --dom",
// 更新项目依赖 // 更新项目依赖
"update:deps": " pnpm update --latest --recursive", "update:deps": " pnpm update --latest --recursive",
// changeset生成提交集 // 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 { useAppConfig } from '@vben/hooks';
import { preferences } from '@vben/preferences'; import { preferences } from '@vben/preferences';
import { import {
@@ -230,7 +232,7 @@ function createRequestClient(baseURL: string) {
}); });
// response数据解构 // response数据解构
client.addResponseInterceptor({ client.addResponseInterceptor<HttpResponse>({
fulfilled: (response) => { fulfilled: (response) => {
const { data: responseData, status } = response; const { data: responseData, status } = response;
@@ -256,7 +258,10 @@ function createRequestClient(baseURL: string) {
// 通用的错误处理,如果没有进入上面的错误处理逻辑,就会进入这里 // 通用的错误处理,如果没有进入上面的错误处理逻辑,就会进入这里
client.addResponseInterceptor( client.addResponseInterceptor(
errorMessageResponseInterceptor((msg: string) => message.error(msg)), errorMessageResponseInterceptor((msg: string, _error) => {
// 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg
message.error(msg);
}),
); );
return client; return client;

View File

@@ -1,10 +1,14 @@
---
outline: deep
---
# 登录 # 登录
本文介绍如何去改造自己的应用程序登录页。 本文介绍如何去改造自己的应用程序登录页以及如何快速的对接登录页面接口
## 登录页面调整 ## 登录页面调整
如果你想调整登录页面的标题、描述和图标以及工具栏,你可以通过配置 `AuthPageLayout` 组件的 `props` 参数来实现。 如果你想调整登录页面的标题、描述和图标以及工具栏,你可以通过配置 `AuthPageLayout` 组件的参数来实现。
![login](/guide/login.png) ![login](/guide/login.png)
@@ -30,8 +34,6 @@
```vue ```vue
<AuthenticationLogin <AuthenticationLogin
:loading="authStore.loginLoading" :loading="authStore.loginLoading"
password-placeholder="123456"
username-placeholder="vben"
@submit="authStore.authLogin" @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` 版本开始,我们不再提供精简的仓库或者分支。我们的目标是提供一个更加一致的开发体验,同时减少维护成本。在这里,我们将如何介绍自己的项目,如何去精简以及移除不需要的功能。 `5.0` 版本开始,我们不再提供精简的仓库或者分支。我们的目标是提供一个更加一致的开发体验,同时减少维护成本。在这里,我们将如何介绍自己的项目,如何去精简以及移除不需要的功能。
@@ -74,3 +78,17 @@ pnpm install
- `.github` 文件夹用于存放 GitHub 的配置文件 - `.github` 文件夹用于存放 GitHub 的配置文件
- `.vscode` 文件夹用于存放 VSCode 的配置文件,如果你使用其他编辑器,可以删除 - `.vscode` 文件夹用于存放 VSCode 的配置文件,如果你使用其他编辑器,可以删除
- `./scripts/deploy` 文件夹用于存放部署脚本如果你不需要docker部署可以删除 - `./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 pnpm vsh check-dep
``` ```
### vsh clean
删除项目的`node_modules``dist``.turbo`等目录,清理项目。
#### 用法
```bash
pnpm vsh clean
```
#### 选项 #### 选项
| 选项 | 说明 | | 选项 | 说明 |

View File

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

View File

@@ -24,6 +24,7 @@ export async function node(): Promise<Linter.Config[]> {
'vite', 'vite',
'@vue/test-utils', '@vue/test-utils',
'@vben/tailwind-config', '@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, ignores: restrictedImportIgnores,
rules: { rules: {
'perfectionist/sort-interfaces': 'off', '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: { rules: {
'no-console': 'off', 'no-console': 'off',
}, },

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
{ {
"name": "vben-admin-pro", "name": "vben-admin-pro",
"version": "5.3.0", "version": "5.3.1",
"private": true, "private": true,
"keywords": [ "keywords": [
"monorepo", "monorepo",
@@ -25,11 +25,10 @@
}, },
"type": "module", "type": "module",
"scripts": { "scripts": {
"bootstrap": "pnpm install",
"build": "cross-env NODE_OPTIONS=--max-old-space-size=8192 turbo build", "build": "cross-env NODE_OPTIONS=--max-old-space-size=8192 turbo build",
"build:analyze": "turbo build:analyze", "build:analyze": "turbo build:analyze",
"build:docker": "./build-local-docker-image.sh",
"build:antd": "pnpm run build --filter=@vben/web-antd", "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:docs": "pnpm run build --filter=@vben/docs",
"build:ele": "pnpm run build --filter=@vben/web-ele", "build:ele": "pnpm run build --filter=@vben/web-ele",
"build:naive": "pnpm run build --filter=@vben/web-naive", "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:cspell": "cspell lint **/*.ts **/README.md .changeset/*.md --no-progress",
"check:dep": "vsh check-dep", "check:dep": "vsh check-dep",
"check:type": "turbo run typecheck", "check:type": "turbo run typecheck",
"clean": "vsh clean", "clean": "node ./scripts/clean.mjs",
"commit": "czg", "commit": "czg",
"dev": "turbo-run dev", "dev": "turbo-run dev",
"dev:antd": "pnpm -F @vben/web-antd run dev", "dev:antd": "pnpm -F @vben/web-antd run dev",
@@ -55,15 +54,16 @@
"prepare": "is-ci || husky", "prepare": "is-ci || husky",
"preview": "turbo-run preview", "preview": "turbo-run preview",
"publint": "vsh publint", "publint": "vsh publint",
"reinstall": "pnpm clean --del-lock && pnpm bootstrap", "reinstall": "pnpm clean --del-lock && pnpm install",
"test:unit": "vitest", "test:unit": "vitest run --dom",
"test:e2e": "turbo run test:e2e",
"update:deps": "pnpm update --latest --recursive", "update:deps": "pnpm update --latest --recursive",
"version": "pnpm exec changeset version && pnpm install --no-frozen-lockfile" "version": "pnpm exec changeset version && pnpm install --no-frozen-lockfile"
}, },
"devDependencies": { "devDependencies": {
"@changesets/changelog-github": "catalog:", "@changesets/changelog-github": "catalog:",
"@changesets/cli": "catalog:", "@changesets/cli": "catalog:",
"@types/jsdom": "catalog:", "@playwright/test": "catalog:",
"@types/node": "catalog:", "@types/node": "catalog:",
"@vben/commitlint-config": "workspace:*", "@vben/commitlint-config": "workspace:*",
"@vben/eslint-config": "workspace:*", "@vben/eslint-config": "workspace:*",
@@ -80,10 +80,11 @@
"autoprefixer": "catalog:", "autoprefixer": "catalog:",
"cross-env": "catalog:", "cross-env": "catalog:",
"cspell": "catalog:", "cspell": "catalog:",
"happy-dom": "catalog:",
"husky": "catalog:", "husky": "catalog:",
"is-ci": "catalog:", "is-ci": "catalog:",
"jsdom": "catalog:",
"lint-staged": "catalog:", "lint-staged": "catalog:",
"playwright": "catalog:",
"rimraf": "catalog:", "rimraf": "catalog:",
"tailwindcss": "catalog:", "tailwindcss": "catalog:",
"turbo": "catalog:", "turbo": "catalog:",
@@ -109,12 +110,11 @@
"@ctrl/tinycolor": "4.1.0", "@ctrl/tinycolor": "4.1.0",
"clsx": "2.1.1", "clsx": "2.1.1",
"pinia": "2.2.2", "pinia": "2.2.2",
"vue": "3.5.7" "vue": "3.5.10"
}, },
"neverBuiltDependencies": [ "neverBuiltDependencies": [
"canvas", "canvas",
"node-gyp", "node-gyp"
"playwright"
] ]
} }
} }

View File

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

View File

@@ -82,11 +82,11 @@
@apply opacity-100; @apply opacity-100;
} }
input:-webkit-autofill { /* input:-webkit-autofill {
@apply border-none; @apply border-none;
box-shadow: 0 0 0 1000px transparent inset; box-shadow: 0 0 0 1000px transparent inset;
} } */
input[type='number']::-webkit-inner-spin-button, input[type='number']::-webkit-inner-spin-button,
input[type='number']::-webkit-outer-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 */ /* Used for accents such as hover effects on <DropdownMenuItem>, <SelectItem>...etc */
--accent: 216 5% 19%; --accent: 216 5% 19%;
--accent-lighter: 216 5% 11%;
--accent-hover: 216 5% 24%; --accent-hover: 216 5% 24%;
--accent-foreground: 0 0% 98%; --accent-foreground: 0 0% 98%;

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest'; import { describe, expect, it } from 'vitest';
import { bindMethods } from '../util'; import { bindMethods, getNestedValue } from '../util';
class TestClass { class TestClass {
public value: string; public value: string;
@@ -78,3 +78,79 @@ describe('bindMethods', () => {
expect(value).toBe('test'); 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 { 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", "name": "@vben-core/typings",
"version": "5.3.0", "version": "5.3.1",
"homepage": "https://github.com/vbenjs/vue-vben-admin", "homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": { "repository": {

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,24 +1,7 @@
// 假设这个文件为 FormApi.ts
import { beforeEach, describe, expect, it, vi } from 'vitest'; import { beforeEach, describe, expect, it, vi } from 'vitest';
import { FormApi } from '../src/form-api'; 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', () => { describe('formApi', () => {
let formApi: FormApi; let formApi: FormApi;
@@ -128,7 +111,6 @@ describe('formApi', () => {
it('should unmount form and reset state', () => { it('should unmount form and reset state', () => {
formApi.unmounted(); formApi.unmounted();
expect(formApi.isMounted).toBe(false); expect(formApi.isMounted).toBe(false);
expect(formApi.stateHandler.reset).toHaveBeenCalled();
}); });
it('should validate form', async () => { it('should validate form', async () => {

View File

@@ -12,17 +12,12 @@ import { toRaw } from 'vue';
import { Store } from '@vben-core/shared/store'; import { Store } from '@vben-core/shared/store';
import { import {
bindMethods, bindMethods,
createMerge,
isFunction, isFunction,
mergeWithArrayOverride,
StateHandler, StateHandler,
} from '@vben-core/shared/utils'; } from '@vben-core/shared/utils';
const merge = createMerge((originObj, key, updates) => { import { objectPick } from '@vueuse/core';
if (Array.isArray(originObj[key]) && Array.isArray(updates)) {
originObj[key] = updates;
return true;
}
});
function getDefaultState(): VbenFormProps { function getDefaultState(): VbenFormProps {
return { return {
@@ -43,11 +38,11 @@ function getDefaultState(): VbenFormProps {
} }
export class FormApi { export class FormApi {
private prevState: null | VbenFormProps = null;
// private api: Pick<VbenFormProps, 'handleReset' | 'handleSubmit'>; // private api: Pick<VbenFormProps, 'handleReset' | 'handleSubmit'>;
public form = {} as FormActions; public form = {} as FormActions;
isMounted = false;
// private prevState!: ModalState; isMounted = false;
public state: null | VbenFormProps = null; public state: null | VbenFormProps = null;
stateHandler: StateHandler; stateHandler: StateHandler;
@@ -66,7 +61,9 @@ export class FormApi {
}, },
{ {
onUpdate: () => { onUpdate: () => {
this.prevState = this.state;
this.state = this.store.state; this.state = this.store.state;
this.updateState();
}, },
}, },
); );
@@ -87,6 +84,24 @@ export class FormApi {
return this.form; 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 方法 // 如果需要多次更新状态,可以使用 batch 方法
batchStore(cb: () => void) { batchStore(cb: () => void) {
this.store.batch(cb); this.store.batch(cb);
@@ -101,6 +116,47 @@ export class FormApi {
return form.values; 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) { mount(formActions: FormActions) {
if (!this.isMounted) { if (!this.isMounted) {
Object.assign(this.form, formActions); Object.assign(this.form, formActions);
@@ -155,19 +211,32 @@ export class FormApi {
) { ) {
if (isFunction(stateOrFn)) { if (isFunction(stateOrFn)) {
this.store.setState((prev) => { this.store.setState((prev) => {
return merge(stateOrFn(prev), prev); return mergeWithArrayOverride(stateOrFn(prev), prev);
}); });
} else { } 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( async setValues(
fields: Record<string, any>, fields: Record<string, any>,
filterFields: boolean = true,
shouldValidate: boolean = false, shouldValidate: boolean = false,
) { ) {
const form = await this.getForm(); 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) { async submitForm(e?: Event) {
@@ -211,7 +280,10 @@ export class FormApi {
currentSchema.forEach((schema, index) => { currentSchema.forEach((schema, index) => {
const updatedData = updatedMap[schema.fieldName]; const updatedData = updatedMap[schema.fieldName];
if (updatedData) { if (updatedData) {
currentSchema[index] = merge(updatedData, schema) as FormSchema; currentSchema[index] = mergeWithArrayOverride(
updatedData,
schema,
) as FormSchema;
} }
}); });
this.setState({ schema: currentSchema }); this.setState({ schema: currentSchema });

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -83,7 +83,7 @@ li:last-child a::after {
li a::before, li a::before,
li a::after { 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 { li a::before {

View File

@@ -14,6 +14,7 @@ export * from './input-password';
export * from './link'; export * from './link';
export * from './logo'; export * from './logo';
export * from './menu-badge'; export * from './menu-badge';
export * from './pagination';
export * from './pin-input'; export * from './pin-input';
export * from './popover'; export * from './popover';
export * from './render-content'; export * from './render-content';
@@ -38,6 +39,7 @@ export * from './ui/hover-card';
export * from './ui/input'; export * from './ui/input';
export * from './ui/label'; export * from './ui/label';
export * from './ui/number-field'; export * from './ui/number-field';
export * from './ui/pagination';
export * from './ui/pin-input'; export * from './ui/pin-input';
export * from './ui/popover'; export * from './ui/popover';
export * from './ui/radio-group'; 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 { cn } from '@vben-core/shared/utils';
import { ChevronDownIcon } from '@radix-icons/vue'; import { ChevronDown } from 'lucide-vue-next';
import { import {
AccordionHeader, AccordionHeader,
AccordionTrigger, AccordionTrigger,
@@ -32,7 +32,7 @@ const delegatedProps = computed(() => {
> >
<slot></slot> <slot></slot>
<slot name="icon"> <slot name="icon">
<ChevronDownIcon <ChevronDown
class="text-muted-foreground h-4 w-4 shrink-0 transition-transform duration-200" class="text-muted-foreground h-4 w-4 shrink-0 transition-transform duration-200"
/> />
</slot> </slot>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,27 @@
<script setup lang="ts">
import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { MoreHorizontal } from 'lucide-vue-next';
import { PaginationEllipsis, type PaginationEllipsisProps } from 'radix-vue';
const props = defineProps<{ class?: any } & PaginationEllipsisProps>();
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props;
return delegated;
});
</script>
<template>
<PaginationEllipsis
v-bind="delegatedProps"
:class="cn('flex size-8 items-center justify-center', props.class)"
>
<slot>
<MoreHorizontal class="size-4" />
</slot>
</PaginationEllipsis>
</template>

View File

@@ -0,0 +1,33 @@
<script setup lang="ts">
import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { ChevronsLeft } from 'lucide-vue-next';
import { PaginationFirst, type PaginationFirstProps } from 'radix-vue';
import { Button } from '../button';
const props = withDefaults(
defineProps<{ class?: any } & PaginationFirstProps>(),
{
asChild: true,
},
);
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props;
return delegated;
});
</script>
<template>
<PaginationFirst v-bind="delegatedProps">
<Button :class="cn('size-8 p-0', props.class)" variant="outline">
<slot>
<ChevronsLeft class="size-4" />
</slot>
</Button>
</PaginationFirst>
</template>

View File

@@ -0,0 +1,33 @@
<script setup lang="ts">
import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { ChevronsRight } from 'lucide-vue-next';
import { PaginationLast, type PaginationLastProps } from 'radix-vue';
import { Button } from '../button';
const props = withDefaults(
defineProps<{ class?: any } & PaginationLastProps>(),
{
asChild: true,
},
);
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props;
return delegated;
});
</script>
<template>
<PaginationLast v-bind="delegatedProps">
<Button :class="cn('size-8 p-0', props.class)" variant="outline">
<slot>
<ChevronsRight class="size-4" />
</slot>
</Button>
</PaginationLast>
</template>

View File

@@ -0,0 +1,33 @@
<script setup lang="ts">
import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { ChevronRight } from 'lucide-vue-next';
import { PaginationNext, type PaginationNextProps } from 'radix-vue';
import { Button } from '../button';
const props = withDefaults(
defineProps<{ class?: any } & PaginationNextProps>(),
{
asChild: true,
},
);
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props;
return delegated;
});
</script>
<template>
<PaginationNext v-bind="delegatedProps">
<Button :class="cn('size-8 p-0', props.class)" variant="outline">
<slot>
<ChevronRight class="size-4" />
</slot>
</Button>
</PaginationNext>
</template>

View File

@@ -0,0 +1,33 @@
<script setup lang="ts">
import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { ChevronLeft } from 'lucide-vue-next';
import { PaginationPrev, type PaginationPrevProps } from 'radix-vue';
import { Button } from '../button';
const props = withDefaults(
defineProps<{ class?: any } & PaginationPrevProps>(),
{
asChild: true,
},
);
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props;
return delegated;
});
</script>
<template>
<PaginationPrev v-bind="delegatedProps">
<Button :class="cn('size-8 p-0', props.class)" variant="outline">
<slot>
<ChevronLeft class="size-4" />
</slot>
</Button>
</PaginationPrev>
</template>

View File

@@ -0,0 +1,10 @@
export { default as PaginationEllipsis } from './PaginationEllipsis.vue';
export { default as PaginationFirst } from './PaginationFirst.vue';
export { default as PaginationLast } from './PaginationLast.vue';
export { default as PaginationNext } from './PaginationNext.vue';
export { default as PaginationPrev } from './PaginationPrev.vue';
export {
PaginationList,
PaginationListItem,
PaginationRoot as Pagination,
} from 'radix-vue';

View File

@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { DashIcon } from '@radix-icons/vue'; import { Dot } from 'lucide-vue-next';
import { Primitive, type PrimitiveProps, useForwardProps } from 'radix-vue'; import { Primitive, type PrimitiveProps, useForwardProps } from 'radix-vue';
const props = defineProps<PrimitiveProps>(); const props = defineProps<PrimitiveProps>();
@@ -9,7 +9,7 @@ const forwardedProps = useForwardProps(props);
<template> <template>
<Primitive v-bind="forwardedProps"> <Primitive v-bind="forwardedProps">
<slot> <slot>
<DashIcon /> <Dot />
</slot> </slot>
</Primitive> </Primitive>
</template> </template>

View File

@@ -3,7 +3,7 @@ import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils'; import { cn } from '@vben-core/shared/utils';
import { CheckIcon } from '@radix-icons/vue'; import { Circle } from 'lucide-vue-next';
import { import {
RadioGroupIndicator, RadioGroupIndicator,
RadioGroupItem, RadioGroupItem,
@@ -33,7 +33,7 @@ const forwardedProps = useForwardProps(delegatedProps);
" "
> >
<RadioGroupIndicator class="flex items-center justify-center"> <RadioGroupIndicator class="flex items-center justify-center">
<CheckIcon class="fill-primary h-3.5 w-3.5" /> <Circle class="h-2.5 w-2.5 fill-current text-current" />
</RadioGroupIndicator> </RadioGroupIndicator>
</RadioGroupItem> </RadioGroupItem>
</template> </template>

View File

@@ -3,7 +3,7 @@ import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils'; import { cn } from '@vben-core/shared/utils';
import { CheckIcon } from '@radix-icons/vue'; import { Check } from 'lucide-vue-next';
import { import {
SelectItem, SelectItem,
SelectItemIndicator, SelectItemIndicator,
@@ -35,7 +35,7 @@ const forwardedProps = useForwardProps(delegatedProps);
> >
<span class="absolute right-2 flex h-3.5 w-3.5 items-center justify-center"> <span class="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectItemIndicator> <SelectItemIndicator>
<CheckIcon class="h-4 w-4" /> <Check class="h-4 w-4" />
</SelectItemIndicator> </SelectItemIndicator>
</span> </span>

View File

@@ -3,7 +3,7 @@ import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils'; import { cn } from '@vben-core/shared/utils';
import { ChevronDownIcon } from '@radix-icons/vue'; import { ChevronDown } from 'lucide-vue-next';
import { import {
SelectScrollDownButton, SelectScrollDownButton,
type SelectScrollDownButtonProps, type SelectScrollDownButtonProps,
@@ -29,7 +29,7 @@ const forwardedProps = useForwardProps(delegatedProps);
" "
> >
<slot> <slot>
<ChevronDownIcon /> <ChevronDown class="h-4 w-4" />
</slot> </slot>
</SelectScrollDownButton> </SelectScrollDownButton>
</template> </template>

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