mirror of
https://github.com/vbenjs/vue-vben-admin.git
synced 2025-08-25 16:16:20 +08:00
Compare commits
29 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
46540a7329 | ||
![]() |
13fd0ea16c | ||
![]() |
f7016466ee | ||
![]() |
0cd865e211 | ||
![]() |
64428b9b11 | ||
![]() |
aed31a5a4e | ||
![]() |
b3e196f001 | ||
![]() |
b2c117f544 | ||
![]() |
01391ee5a1 | ||
![]() |
3572ce1538 | ||
![]() |
d1e1256202 | ||
![]() |
b7776c5148 | ||
![]() |
2d1519eca7 | ||
![]() |
93b5618b52 | ||
![]() |
639d2e1525 | ||
![]() |
26646d42f7 | ||
![]() |
17fa8eb93b | ||
![]() |
8250894a50 | ||
![]() |
a72b8acaf9 | ||
![]() |
a46c85d77d | ||
![]() |
fdc5b02c30 | ||
![]() |
476aa068d7 | ||
![]() |
bb6057cac3 | ||
![]() |
abbbbfb955 | ||
![]() |
79c87c9f46 | ||
![]() |
f815dcf3ae | ||
![]() |
1197efea26 | ||
![]() |
2a83f1d666 | ||
![]() |
4b3d2d21ed |
1
.github/workflows/build.yml
vendored
1
.github/workflows/build.yml
vendored
@@ -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:
|
||||
|
2
.github/workflows/changeset-version.yml
vendored
2
.github/workflows/changeset-version.yml
vendored
@@ -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
|
||||
|
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -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:
|
||||
|
1
.github/workflows/codeql.yml
vendored
1
.github/workflows/codeql.yml
vendored
@@ -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
|
||||
|
10
.github/workflows/deploy.yml
vendored
10
.github/workflows/deploy.yml
vendored
@@ -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
|
||||
|
1
.github/workflows/draft.yml
vendored
1
.github/workflows/draft.yml
vendored
@@ -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
|
||||
|
20
.github/workflows/issue-close-require.yml
vendored
20
.github/workflows/issue-close-require.yml
vendored
@@ -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
|
||||
|
1
.github/workflows/issue-labeled.yml
vendored
1
.github/workflows/issue-labeled.yml
vendored
@@ -13,6 +13,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
reply-labeled:
|
||||
if: github.repository == 'vbenjs/vue-vben-admin'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: remove enhancement pending
|
||||
|
3
.github/workflows/lock.yml
vendored
3
.github/workflows/lock.yml
vendored
@@ -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: ''
|
||||
|
1
.github/workflows/release-tag.yml
vendored
1
.github/workflows/release-tag.yml
vendored
@@ -15,6 +15,7 @@ permissions:
|
||||
jobs:
|
||||
build:
|
||||
name: Create Release
|
||||
if: github.repository == 'vbenjs/vue-vben-admin'
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
|
3
.github/workflows/semantic-pull-request.yml
vendored
3
.github/workflows/semantic-pull-request.yml
vendored
@@ -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
|
||||
|
1
.github/workflows/stale.yml
vendored
1
.github/workflows/stale.yml
vendored
@@ -6,6 +6,7 @@ on:
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
if: github.repository == 'vbenjs/vue-vben-admin'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
|
@@ -3,4 +3,4 @@ ports:
|
||||
onOpen: open-preview
|
||||
tasks:
|
||||
- init: corepack enable && pnpm install
|
||||
command: pnpm run dev
|
||||
command: pnpm run dev:play
|
||||
|
@@ -125,6 +125,10 @@ pnpm build
|
||||
|
||||
[@Vben](https://github.com/anncwb)
|
||||
|
||||
## スター歴史
|
||||
|
||||
[](https://star-history.com/#vbenjs/vue-vben-admin&Date)
|
||||
|
||||
## 寄付
|
||||
|
||||
このプロジェクトが役に立つと思われた場合、作者にコーヒーを一杯おごってサポートを示すことができます!
|
||||
|
@@ -124,6 +124,10 @@ Support modern browsers, not IE
|
||||
|
||||
[@Vben](https://github.com/anncwb)
|
||||
|
||||
## Star History
|
||||
|
||||
[](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!
|
||||
|
@@ -77,6 +77,10 @@ pnpm dev
|
||||
pnpm build
|
||||
```
|
||||
|
||||
## 更新日志
|
||||
|
||||
[CHANGELOG](https://github.com/vbenjs/vue-vben-admin/releases)
|
||||
|
||||
## 如何贡献
|
||||
|
||||
非常欢迎你的加入 或者提交一个 Pull Request。
|
||||
@@ -120,6 +124,10 @@ pnpm build
|
||||
|
||||
[@Vben](https://github.com/anncwb)
|
||||
|
||||
## Star History
|
||||
|
||||
[](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">
|
||||
|
@@ -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": {
|
||||
|
@@ -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: {
|
||||
|
@@ -10,10 +10,6 @@ export namespace AuthApi {
|
||||
/** 登录接口返回值 */
|
||||
export interface LoginResult {
|
||||
accessToken: string;
|
||||
desc: string;
|
||||
realName: string;
|
||||
userId: string;
|
||||
username: string;
|
||||
}
|
||||
|
||||
export interface RefreshTokenResult {
|
||||
|
@@ -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;
|
||||
|
@@ -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": {
|
||||
|
@@ -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: {
|
||||
|
@@ -10,10 +10,6 @@ export namespace AuthApi {
|
||||
/** 登录接口返回值 */
|
||||
export interface LoginResult {
|
||||
accessToken: string;
|
||||
desc: string;
|
||||
realName: string;
|
||||
userId: string;
|
||||
username: string;
|
||||
}
|
||||
|
||||
export interface RefreshTokenResult {
|
||||
|
@@ -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;
|
||||
|
@@ -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>
|
||||
|
@@ -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": {
|
||||
|
@@ -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',
|
||||
|
@@ -10,10 +10,6 @@ export namespace AuthApi {
|
||||
/** 登录接口返回值 */
|
||||
export interface LoginResult {
|
||||
accessToken: string;
|
||||
desc: string;
|
||||
realName: string;
|
||||
userId: string;
|
||||
username: string;
|
||||
}
|
||||
|
||||
export interface RefreshTokenResult {
|
||||
|
@@ -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;
|
||||
|
@@ -25,7 +25,6 @@ const routes: RouteRecordRaw[] = [
|
||||
},
|
||||
{
|
||||
meta: {
|
||||
icon: 'mdi:shield-key-outline',
|
||||
title: $t('page.demos.table'),
|
||||
},
|
||||
name: 'Table',
|
||||
|
@@ -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: [
|
||||
|
@@ -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: '组件',
|
||||
},
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/docs",
|
||||
"version": "5.3.0",
|
||||
"version": "5.3.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "vitepress build",
|
||||
|
@@ -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>` |
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
|
@@ -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"
|
||||
/>
|
||||
```
|
||||
|
@@ -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.
|
||||
|
@@ -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生成提交集
|
||||
|
@@ -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;
|
||||
|
@@ -1,10 +1,14 @@
|
||||
---
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# 登录
|
||||
|
||||
本文介绍如何去改造自己的应用程序登录页。
|
||||
本文介绍如何去改造自己的应用程序登录页以及如何快速的对接登录页面接口。
|
||||
|
||||
## 登录页面调整
|
||||
|
||||
如果你想调整登录页面的标题、描述和图标以及工具栏,你可以通过配置 `AuthPageLayout` 组件的 `props` 参数来实现。
|
||||
如果你想调整登录页面的标题、描述和图标以及工具栏,你可以通过配置 `AuthPageLayout` 组件的参数来实现。
|
||||
|
||||

|
||||
|
||||
@@ -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 [];
|
||||
}
|
||||
```
|
||||
|
@@ -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` 目录等。删除之前请先确保你的路由中没有引用到这些组件。
|
||||
|
@@ -42,16 +42,6 @@ pnpm vsh check-circular
|
||||
pnpm vsh check-dep
|
||||
```
|
||||
|
||||
### vsh clean
|
||||
|
||||
删除项目的`node_modules`、`dist`、`.turbo`等目录,清理项目。
|
||||
|
||||
#### 用法
|
||||
|
||||
```bash
|
||||
pnpm vsh clean
|
||||
```
|
||||
|
||||
#### 选项
|
||||
|
||||
| 选项 | 说明 |
|
||||
|
@@ -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",
|
||||
|
@@ -24,6 +24,7 @@ export async function node(): Promise<Linter.Config[]> {
|
||||
'vite',
|
||||
'@vue/test-utils',
|
||||
'@vben/tailwind-config',
|
||||
'@playwright/test',
|
||||
],
|
||||
},
|
||||
],
|
||||
|
@@ -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',
|
||||
},
|
||||
|
@@ -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",
|
||||
|
@@ -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",
|
||||
|
@@ -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",
|
||||
|
@@ -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))',
|
||||
|
@@ -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",
|
||||
|
@@ -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",
|
||||
|
26
package.json
26
package.json
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@@ -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": {
|
||||
|
@@ -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 {
|
||||
|
@@ -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%;
|
||||
|
||||
|
@@ -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%;
|
||||
|
||||
|
@@ -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": {
|
||||
|
@@ -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": {
|
||||
|
@@ -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();
|
||||
});
|
||||
});
|
||||
|
@@ -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;
|
||||
}
|
||||
});
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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": {
|
||||
|
@@ -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": {
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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": {
|
||||
|
@@ -171,8 +171,9 @@ class PreferenceManager {
|
||||
// 加载并合并当前存储的偏好设置
|
||||
const mergedPreference = merge(
|
||||
{},
|
||||
overrides,
|
||||
this.loadCachedPreferences() || defaultPreferences,
|
||||
// overrides,
|
||||
this.loadCachedPreferences() || {},
|
||||
this.initialPreferences,
|
||||
);
|
||||
|
||||
// 更新偏好设置
|
||||
|
@@ -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 () => {
|
||||
|
@@ -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]);
|
||||
|
@@ -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 });
|
||||
|
@@ -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,
|
||||
|
@@ -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,
|
||||
|
@@ -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?: {
|
||||
|
@@ -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"
|
||||
|
@@ -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"
|
||||
|
@@ -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": {
|
||||
|
@@ -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": {
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
|
@@ -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();
|
||||
}
|
||||
|
@@ -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:*",
|
||||
|
@@ -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 {
|
||||
|
@@ -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';
|
||||
|
@@ -0,0 +1,2 @@
|
||||
export type { PaginationProps as VbenPaginationProps } from './pagination';
|
||||
export { default as VbenPagination } from './pagination.vue';
|
@@ -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',
|
||||
};
|
@@ -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>
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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>(), {
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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
Reference in New Issue
Block a user