mirror of
https://github.com/vbenjs/vue-vben-admin.git
synced 2025-08-26 00:26:20 +08:00
Compare commits
57 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
9856bc88d2 | ||
![]() |
68465b5fbf | ||
![]() |
0ea0f204cb | ||
![]() |
1b65254383 | ||
![]() |
0a99f27127 | ||
![]() |
304b1b2efc | ||
![]() |
f923f59070 | ||
![]() |
437cb02e11 | ||
![]() |
ba539f6793 | ||
![]() |
078f255e1a | ||
![]() |
ba4662522e | ||
![]() |
8fe87b10dc | ||
![]() |
2dbd323b2a | ||
![]() |
8ad2b8665d | ||
![]() |
518b869f9d | ||
![]() |
2d019b3c8a | ||
![]() |
ab44926ec8 | ||
![]() |
f0edad8a51 | ||
![]() |
f8ce3fdf1f | ||
![]() |
60c615ce8a | ||
![]() |
324cdd8259 | ||
![]() |
9ad4f96e38 | ||
![]() |
47d162e6e4 | ||
![]() |
d37e2f599c | ||
![]() |
402eaf4275 | ||
![]() |
0fcc42a2fb | ||
![]() |
28b54b587a | ||
![]() |
4173264805 | ||
![]() |
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
|
||||
|
4
.vscode/extensions.json
vendored
4
.vscode/extensions.json
vendored
@@ -19,7 +19,9 @@
|
||||
// i18n 插件
|
||||
"Lokalise.i18n-ally",
|
||||
// CSS 变量提示
|
||||
"vunguyentuan.vscode-css-variables"
|
||||
"vunguyentuan.vscode-css-variables",
|
||||
// 在 package.json 中显示 PNPM catalog 的版本
|
||||
"antfu.pnpm-catalog-lens"
|
||||
],
|
||||
"unwantedRecommendations": [
|
||||
// 和 volar 冲突
|
||||
|
@@ -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,7 +1,7 @@
|
||||
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||
import { unAuthorizedResponse } from '~/utils/response';
|
||||
|
||||
export default eventHandler((event) => {
|
||||
export default eventHandler(async (event) => {
|
||||
const userinfo = verifyAccessToken(event);
|
||||
if (!userinfo) {
|
||||
return unAuthorizedResponse(event);
|
||||
|
48
apps/backend-mock/api/table/list.ts
Normal file
48
apps/backend-mock/api/table/list.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||
import { unAuthorizedResponse } from '~/utils/response';
|
||||
|
||||
function generateMockDataList(count: number) {
|
||||
const dataList = [];
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const dataItem = {
|
||||
id: faker.string.uuid(),
|
||||
imageUrl: faker.image.avatar(),
|
||||
imageUrl2: faker.image.avatar(),
|
||||
open: faker.datatype.boolean(),
|
||||
status: faker.helpers.arrayElement(['success', 'error', 'warning']),
|
||||
productName: faker.commerce.productName(),
|
||||
price: faker.commerce.price(),
|
||||
currency: faker.finance.currencyCode(),
|
||||
quantity: faker.number.int({ min: 1, max: 100 }),
|
||||
available: faker.datatype.boolean(),
|
||||
category: faker.commerce.department(),
|
||||
releaseDate: faker.date.past(),
|
||||
rating: faker.number.float({ min: 1, max: 5 }),
|
||||
description: faker.commerce.productDescription(),
|
||||
weight: faker.number.float({ min: 0.1, max: 10 }),
|
||||
color: faker.color.human(),
|
||||
inProduction: faker.datatype.boolean(),
|
||||
tags: Array.from({ length: 3 }, () => faker.commerce.productAdjective()),
|
||||
};
|
||||
|
||||
dataList.push(dataItem);
|
||||
}
|
||||
|
||||
return dataList;
|
||||
}
|
||||
|
||||
const mockData = generateMockDataList(100);
|
||||
|
||||
export default eventHandler(async (event) => {
|
||||
const userinfo = verifyAccessToken(event);
|
||||
if (!userinfo) {
|
||||
return unAuthorizedResponse(event);
|
||||
}
|
||||
|
||||
await sleep(600);
|
||||
|
||||
const { page, pageSize } = getQuery(event);
|
||||
return usePageResponseSuccess(page as string, pageSize as string, mockData);
|
||||
});
|
@@ -6,6 +6,5 @@ export default eventHandler((event) => {
|
||||
if (!userinfo) {
|
||||
return unAuthorizedResponse(event);
|
||||
}
|
||||
|
||||
return useResponseSuccess(userinfo);
|
||||
});
|
||||
|
@@ -10,6 +10,7 @@
|
||||
"start": "nitro dev"
|
||||
},
|
||||
"dependencies": {
|
||||
"@faker-js/faker": "catalog:",
|
||||
"jsonwebtoken": "catalog:",
|
||||
"nitropack": "catalog:"
|
||||
},
|
||||
|
@@ -9,6 +9,27 @@ export function useResponseSuccess<T = any>(data: T) {
|
||||
};
|
||||
}
|
||||
|
||||
export function usePageResponseSuccess<T = any>(
|
||||
page: number | string,
|
||||
pageSize: number | string,
|
||||
list: T[],
|
||||
{ message = 'ok' } = {},
|
||||
) {
|
||||
const pageData = pagination(
|
||||
Number.parseInt(`${page}`),
|
||||
Number.parseInt(`${pageSize}`),
|
||||
list,
|
||||
);
|
||||
|
||||
return {
|
||||
...useResponseSuccess({
|
||||
items: pageData,
|
||||
total: list.length,
|
||||
}),
|
||||
message,
|
||||
};
|
||||
}
|
||||
|
||||
export function useResponseError(message: string, error: any = null) {
|
||||
return {
|
||||
code: -1,
|
||||
@@ -20,10 +41,25 @@ export function useResponseError(message: string, error: any = null) {
|
||||
|
||||
export function forbiddenResponse(event: H3Event<EventHandlerRequest>) {
|
||||
setResponseStatus(event, 403);
|
||||
return useResponseError('ForbiddenException', 'Forbidden Exception');
|
||||
return useResponseError('Forbidden Exception', 'Forbidden Exception');
|
||||
}
|
||||
|
||||
export function unAuthorizedResponse(event: H3Event<EventHandlerRequest>) {
|
||||
setResponseStatus(event, 401);
|
||||
return useResponseError('UnauthorizedException', 'Unauthorized Exception');
|
||||
return useResponseError('Unauthorized Exception', 'Unauthorized Exception');
|
||||
}
|
||||
|
||||
export function sleep(ms: number) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
export function pagination<T = any>(
|
||||
pageNo: number,
|
||||
pageSize: number,
|
||||
array: T[],
|
||||
): T[] {
|
||||
const offset = (pageNo - 1) * Number(pageSize);
|
||||
return offset + Number(pageSize) >= array.length
|
||||
? array.slice(offset)
|
||||
: array.slice(offset, offset + Number(pageSize));
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/web-antd",
|
||||
"version": "5.3.0",
|
||||
"version": "5.4.0",
|
||||
"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: {
|
||||
|
@@ -1 +1,2 @@
|
||||
export * from './form';
|
||||
export * from './vxe-table';
|
||||
|
59
apps/web-antd/src/adapter/vxe-table.ts
Normal file
59
apps/web-antd/src/adapter/vxe-table.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { h } from 'vue';
|
||||
|
||||
import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';
|
||||
|
||||
import { Button, Image } from 'ant-design-vue';
|
||||
|
||||
import { useVbenForm } from './form';
|
||||
|
||||
setupVbenVxeTable({
|
||||
configVxeTable: (vxeUI) => {
|
||||
vxeUI.setConfig({
|
||||
grid: {
|
||||
align: 'center',
|
||||
border: true,
|
||||
minHeight: 180,
|
||||
proxyConfig: {
|
||||
autoLoad: true,
|
||||
response: {
|
||||
result: 'items',
|
||||
total: 'total',
|
||||
list: 'items',
|
||||
},
|
||||
showActiveMsg: true,
|
||||
showResponseMsg: false,
|
||||
},
|
||||
round: true,
|
||||
size: 'small',
|
||||
},
|
||||
});
|
||||
|
||||
// 表格配置项可以用 cellRender: { name: 'CellImage' },
|
||||
vxeUI.renderer.add('CellImage', {
|
||||
renderDefault(_renderOpts, params) {
|
||||
const { column, row } = params;
|
||||
return h(Image, { src: row[column.field] });
|
||||
},
|
||||
});
|
||||
|
||||
// 表格配置项可以用 cellRender: { name: 'CellLink' },
|
||||
vxeUI.renderer.add('CellLink', {
|
||||
renderDefault(renderOpts) {
|
||||
const { props } = renderOpts;
|
||||
return h(
|
||||
Button,
|
||||
{ size: 'small', type: 'link' },
|
||||
{ default: () => props?.text },
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
// 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化
|
||||
// vxeUI.formats.add
|
||||
},
|
||||
useVbenForm,
|
||||
});
|
||||
|
||||
export { useVbenVxeGrid };
|
||||
|
||||
export type * from '@vben/plugins/vxe-table';
|
@@ -3,17 +3,13 @@ import { baseRequestClient, requestClient } from '#/api/request';
|
||||
export namespace AuthApi {
|
||||
/** 登录接口参数 */
|
||||
export interface LoginParams {
|
||||
password: string;
|
||||
username: string;
|
||||
password?: string;
|
||||
username?: string;
|
||||
}
|
||||
|
||||
/** 登录接口返回值 */
|
||||
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,14 @@ function createRequestClient(baseURL: string) {
|
||||
|
||||
// 通用的错误处理,如果没有进入上面的错误处理逻辑,就会进入这里
|
||||
client.addResponseInterceptor(
|
||||
errorMessageResponseInterceptor((msg: string) => message.error(msg)),
|
||||
errorMessageResponseInterceptor((msg: string, error) => {
|
||||
// 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg
|
||||
// 当前mock接口返回的错误字段是 error 或者 message
|
||||
const responseData = error?.response?.data ?? {};
|
||||
const errorMessage = responseData?.error ?? responseData?.message ?? '';
|
||||
// 如果没有错误信息,则会根据状态码进行提示
|
||||
message.error(errorMessage || msg);
|
||||
}),
|
||||
);
|
||||
|
||||
return client;
|
||||
|
@@ -1,10 +1,11 @@
|
||||
<script lang="ts" setup>
|
||||
import type { NotificationItem } from '@vben/layouts';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import { AuthenticationLoginExpiredModal } from '@vben/common-ui';
|
||||
import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
|
||||
import { useWatermark } from '@vben/hooks';
|
||||
import { BookOpenText, CircleHelp, MdiGithub } from '@vben/icons';
|
||||
import {
|
||||
BasicLayout,
|
||||
@@ -54,6 +55,7 @@ const notifications = ref<NotificationItem[]>([
|
||||
const userStore = useUserStore();
|
||||
const authStore = useAuthStore();
|
||||
const accessStore = useAccessStore();
|
||||
const { destroyWatermark, updateWatermark } = useWatermark();
|
||||
const showDot = computed(() =>
|
||||
notifications.value.some((item) => !item.isRead),
|
||||
);
|
||||
@@ -103,6 +105,21 @@ function handleNoticeClear() {
|
||||
function handleMakeAll() {
|
||||
notifications.value.forEach((item) => (item.isRead = true));
|
||||
}
|
||||
watch(
|
||||
() => preferences.app.watermark,
|
||||
async (enable) => {
|
||||
if (enable) {
|
||||
await updateWatermark({
|
||||
content: `${userStore.userInfo?.username}`,
|
||||
});
|
||||
} else {
|
||||
destroyWatermark();
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@@ -34,9 +34,7 @@ function setupCommonGuard(router: Router) {
|
||||
router.afterEach((to) => {
|
||||
// 记录页面是否加载,如果已经加载,后续的页面切换动画等效果不在重复执行
|
||||
|
||||
if (preferences.tabbar.enable) {
|
||||
loadedPaths.add(to.path);
|
||||
}
|
||||
loadedPaths.add(to.path);
|
||||
|
||||
// 关闭页面加载进度条
|
||||
if (preferences.transition.progress) {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/web-ele",
|
||||
"version": "5.3.0",
|
||||
"version": "5.4.0",
|
||||
"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: {
|
||||
|
@@ -1 +1,2 @@
|
||||
export * from './form';
|
||||
export * from './vxe-table';
|
||||
|
60
apps/web-ele/src/adapter/vxe-table.ts
Normal file
60
apps/web-ele/src/adapter/vxe-table.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { h } from 'vue';
|
||||
|
||||
import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';
|
||||
|
||||
import { ElButton, ElImage } from 'element-plus';
|
||||
|
||||
import { useVbenForm } from './form';
|
||||
|
||||
setupVbenVxeTable({
|
||||
configVxeTable: (vxeUI) => {
|
||||
vxeUI.setConfig({
|
||||
grid: {
|
||||
align: 'center',
|
||||
border: true,
|
||||
minHeight: 180,
|
||||
proxyConfig: {
|
||||
autoLoad: true,
|
||||
response: {
|
||||
result: 'items',
|
||||
total: 'total',
|
||||
list: 'items',
|
||||
},
|
||||
showActiveMsg: true,
|
||||
showResponseMsg: false,
|
||||
},
|
||||
round: true,
|
||||
size: 'small',
|
||||
},
|
||||
});
|
||||
|
||||
// 表格配置项可以用 cellRender: { name: 'CellImage' },
|
||||
vxeUI.renderer.add('CellImage', {
|
||||
renderDefault(_renderOpts, params) {
|
||||
const { column, row } = params;
|
||||
const src = row[column.field];
|
||||
return h(ElImage, { src, previewSrcList: [src] });
|
||||
},
|
||||
});
|
||||
|
||||
// 表格配置项可以用 cellRender: { name: 'CellLink' },
|
||||
vxeUI.renderer.add('CellLink', {
|
||||
renderDefault(renderOpts) {
|
||||
const { props } = renderOpts;
|
||||
return h(
|
||||
ElButton,
|
||||
{ size: 'small', link: true },
|
||||
{ default: () => props?.text },
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
// 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化
|
||||
// vxeUI.formats.add
|
||||
},
|
||||
useVbenForm,
|
||||
});
|
||||
|
||||
export { useVbenVxeGrid };
|
||||
|
||||
export type * from '@vben/plugins/vxe-table';
|
@@ -3,17 +3,13 @@ import { baseRequestClient, requestClient } from '#/api/request';
|
||||
export namespace AuthApi {
|
||||
/** 登录接口参数 */
|
||||
export interface LoginParams {
|
||||
password: string;
|
||||
username: string;
|
||||
password?: string;
|
||||
username?: string;
|
||||
}
|
||||
|
||||
/** 登录接口返回值 */
|
||||
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,14 @@ function createRequestClient(baseURL: string) {
|
||||
|
||||
// 通用的错误处理,如果没有进入上面的错误处理逻辑,就会进入这里
|
||||
client.addResponseInterceptor(
|
||||
errorMessageResponseInterceptor((msg: string) => ElMessage.error(msg)),
|
||||
errorMessageResponseInterceptor((msg: string, error) => {
|
||||
// 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg
|
||||
// 当前mock接口返回的错误字段是 error 或者 message
|
||||
const responseData = error?.response?.data ?? {};
|
||||
const errorMessage = responseData?.error ?? responseData?.message ?? '';
|
||||
// 如果没有错误信息,则会根据状态码进行提示
|
||||
ElMessage.error(errorMessage || msg);
|
||||
}),
|
||||
);
|
||||
|
||||
return client;
|
||||
|
@@ -1,10 +1,11 @@
|
||||
<script lang="ts" setup>
|
||||
import type { NotificationItem } from '@vben/layouts';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import { AuthenticationLoginExpiredModal } from '@vben/common-ui';
|
||||
import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
|
||||
import { useWatermark } from '@vben/hooks';
|
||||
import { BookOpenText, CircleHelp, MdiGithub } from '@vben/icons';
|
||||
import {
|
||||
BasicLayout,
|
||||
@@ -54,6 +55,7 @@ const notifications = ref<NotificationItem[]>([
|
||||
const userStore = useUserStore();
|
||||
const authStore = useAuthStore();
|
||||
const accessStore = useAccessStore();
|
||||
const { destroyWatermark, updateWatermark } = useWatermark();
|
||||
const showDot = computed(() =>
|
||||
notifications.value.some((item) => !item.isRead),
|
||||
);
|
||||
@@ -103,6 +105,21 @@ function handleNoticeClear() {
|
||||
function handleMakeAll() {
|
||||
notifications.value.forEach((item) => (item.isRead = true));
|
||||
}
|
||||
watch(
|
||||
() => preferences.app.watermark,
|
||||
async (enable) => {
|
||||
if (enable) {
|
||||
await updateWatermark({
|
||||
content: `${userStore.userInfo?.username}`,
|
||||
});
|
||||
} else {
|
||||
destroyWatermark();
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@@ -34,9 +34,7 @@ function setupCommonGuard(router: Router) {
|
||||
router.afterEach((to) => {
|
||||
// 记录页面是否加载,如果已经加载,后续的页面切换动画等效果不在重复执行
|
||||
|
||||
if (preferences.tabbar.enable) {
|
||||
loadedPaths.add(to.path);
|
||||
}
|
||||
loadedPaths.add(to.path);
|
||||
|
||||
// 关闭页面加载进度条
|
||||
if (preferences.transition.progress) {
|
||||
|
@@ -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.4.0",
|
||||
"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: {
|
||||
@@ -51,28 +62,28 @@ setupVbenForm<FormComponentType>({
|
||||
DatePicker: NDatePicker,
|
||||
// 自定义默认的重置按钮
|
||||
DefaultResetActionButton: (props, { attrs, slots }) => {
|
||||
return h(NButton, { ...props, attrs, text: false, type: 'info' }, slots);
|
||||
return h(NButton, { ...props, attrs, type: 'info' }, slots);
|
||||
},
|
||||
// 自定义默认的提交按钮
|
||||
DefaultSubmitActionButton: (props, { attrs, slots }) => {
|
||||
return h(
|
||||
NButton,
|
||||
{ ...props, attrs, text: false, type: 'primary' },
|
||||
slots,
|
||||
);
|
||||
return h(NButton, { ...props, attrs, type: 'primary' }, slots);
|
||||
},
|
||||
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: {
|
||||
// naive-ui组件不接受onChang事件,所以需要禁用
|
||||
disabledOnChangeListener: true,
|
||||
// naive-ui组件的空值为null,不能是undefined,否则重置表单时不生效
|
||||
emptyStateValue: null,
|
||||
baseModelPropName: 'value',
|
||||
modelPropNameMap: {
|
||||
Checkbox: 'checked',
|
||||
|
@@ -1,2 +1,3 @@
|
||||
export * from './form';
|
||||
export * from './naive';
|
||||
export * from './vxe-table';
|
||||
|
59
apps/web-naive/src/adapter/vxe-table.ts
Normal file
59
apps/web-naive/src/adapter/vxe-table.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { h } from 'vue';
|
||||
|
||||
import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';
|
||||
|
||||
import { NButton, NImage } from 'naive-ui';
|
||||
|
||||
import { useVbenForm } from './form';
|
||||
|
||||
setupVbenVxeTable({
|
||||
configVxeTable: (vxeUI) => {
|
||||
vxeUI.setConfig({
|
||||
grid: {
|
||||
align: 'center',
|
||||
border: true,
|
||||
minHeight: 180,
|
||||
proxyConfig: {
|
||||
autoLoad: true,
|
||||
response: {
|
||||
result: 'items',
|
||||
total: 'total',
|
||||
list: 'items',
|
||||
},
|
||||
showActiveMsg: true,
|
||||
showResponseMsg: false,
|
||||
},
|
||||
round: true,
|
||||
size: 'small',
|
||||
},
|
||||
});
|
||||
|
||||
// 表格配置项可以用 cellRender: { name: 'CellImage' },
|
||||
vxeUI.renderer.add('CellImage', {
|
||||
renderDefault(_renderOpts, params) {
|
||||
const { column, row } = params;
|
||||
return h(NImage, { src: row[column.field] });
|
||||
},
|
||||
});
|
||||
|
||||
// 表格配置项可以用 cellRender: { name: 'CellLink' },
|
||||
vxeUI.renderer.add('CellLink', {
|
||||
renderDefault(renderOpts) {
|
||||
const { props } = renderOpts;
|
||||
return h(
|
||||
NButton,
|
||||
{ size: 'small', type: 'primary', quaternary: true },
|
||||
{ default: () => props?.text },
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
// 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化
|
||||
// vxeUI.formats.add
|
||||
},
|
||||
useVbenForm,
|
||||
});
|
||||
|
||||
export { useVbenVxeGrid };
|
||||
|
||||
export type * from '@vben/plugins/vxe-table';
|
@@ -3,17 +3,13 @@ import { baseRequestClient, requestClient } from '#/api/request';
|
||||
export namespace AuthApi {
|
||||
/** 登录接口参数 */
|
||||
export interface LoginParams {
|
||||
password: string;
|
||||
username: string;
|
||||
password?: string;
|
||||
username?: string;
|
||||
}
|
||||
|
||||
/** 登录接口返回值 */
|
||||
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,14 @@ function createRequestClient(baseURL: string) {
|
||||
|
||||
// 通用的错误处理,如果没有进入上面的错误处理逻辑,就会进入这里
|
||||
client.addResponseInterceptor(
|
||||
errorMessageResponseInterceptor((msg: string) => message.error(msg)),
|
||||
errorMessageResponseInterceptor((msg: string, error) => {
|
||||
// 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg
|
||||
// 当前mock接口返回的错误字段是 error 或者 message
|
||||
const responseData = error?.response?.data ?? {};
|
||||
const errorMessage = responseData?.error ?? responseData?.message ?? '';
|
||||
// 如果没有错误信息,则会根据状态码进行提示
|
||||
message.error(errorMessage || msg);
|
||||
}),
|
||||
);
|
||||
|
||||
return client;
|
||||
|
@@ -1,10 +1,11 @@
|
||||
<script lang="ts" setup>
|
||||
import type { NotificationItem } from '@vben/layouts';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import { AuthenticationLoginExpiredModal } from '@vben/common-ui';
|
||||
import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
|
||||
import { useWatermark } from '@vben/hooks';
|
||||
import { BookOpenText, CircleHelp, MdiGithub } from '@vben/icons';
|
||||
import {
|
||||
BasicLayout,
|
||||
@@ -54,6 +55,7 @@ const notifications = ref<NotificationItem[]>([
|
||||
const userStore = useUserStore();
|
||||
const authStore = useAuthStore();
|
||||
const accessStore = useAccessStore();
|
||||
const { destroyWatermark, updateWatermark } = useWatermark();
|
||||
const showDot = computed(() =>
|
||||
notifications.value.some((item) => !item.isRead),
|
||||
);
|
||||
@@ -103,6 +105,22 @@ function handleNoticeClear() {
|
||||
function handleMakeAll() {
|
||||
notifications.value.forEach((item) => (item.isRead = true));
|
||||
}
|
||||
|
||||
watch(
|
||||
() => preferences.app.watermark,
|
||||
async (enable) => {
|
||||
if (enable) {
|
||||
await updateWatermark({
|
||||
content: `${userStore.userInfo?.username}`,
|
||||
});
|
||||
} else {
|
||||
destroyWatermark();
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@@ -34,9 +34,7 @@ function setupCommonGuard(router: Router) {
|
||||
router.afterEach((to) => {
|
||||
// 记录页面是否加载,如果已经加载,后续的页面切换动画等效果不在重复执行
|
||||
|
||||
if (preferences.tabbar.enable) {
|
||||
loadedPaths.add(to.path);
|
||||
}
|
||||
loadedPaths.add(to.path);
|
||||
|
||||
// 关闭页面加载进度条
|
||||
if (preferences.transition.progress) {
|
||||
|
@@ -25,7 +25,6 @@ const routes: RouteRecordRaw[] = [
|
||||
},
|
||||
{
|
||||
meta: {
|
||||
icon: 'mdi:shield-key-outline',
|
||||
title: $t('page.demos.table'),
|
||||
},
|
||||
name: 'Table',
|
||||
|
@@ -19,6 +19,7 @@
|
||||
"intlify",
|
||||
"mkdist",
|
||||
"mockjs",
|
||||
"vitejs",
|
||||
"noopener",
|
||||
"noreferrer",
|
||||
"nprogress",
|
||||
|
@@ -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.4.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "vitepress build",
|
||||
@@ -13,9 +13,7 @@
|
||||
"dependencies": {
|
||||
"@vben-core/shadcn-ui": "workspace:*",
|
||||
"@vben/common-ui": "workspace:*",
|
||||
"@vben/hooks": "workspace:*",
|
||||
"@vben/locales": "workspace:*",
|
||||
"@vben/preferences": "workspace:*",
|
||||
"@vben/styles": "workspace:*",
|
||||
"ant-design-vue": "catalog:",
|
||||
"lucide-vue-next": "catalog:",
|
||||
|
@@ -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>` |
|
||||
@@ -255,6 +267,7 @@ useVbenForm 返回的第二个参数,是一个对象,包含了一些表单
|
||||
| submitButtonOptions | 提交按钮组件参数 | `ActionButtonOptions` | - |
|
||||
| showDefaultActions | 是否显示默认操作按钮 | `boolean` | `true` |
|
||||
| collapsed | 是否折叠,在`是否展开,在showCollapseButton=true`时生效 | `boolean` | `false` |
|
||||
| collapseTriggerResize | 折叠时,触发`resize`事件 | `boolean` | `false` |
|
||||
| collapsedRows | 折叠时保持的行数 | `number` | `1` |
|
||||
| commonConfig | 表单项的通用配置,每个配置都会传递到每个表单项,表单项可覆盖 | `FormCommonConfig` | - |
|
||||
| schema | 表单项的每一项配置 | `FormSchema` | - |
|
||||
|
@@ -78,7 +78,7 @@ const [QueryForm] = useVbenForm({
|
||||
// 是否可展开
|
||||
showCollapseButton: true,
|
||||
submitButtonOptions: {
|
||||
text: '查询',
|
||||
content: '查询',
|
||||
},
|
||||
wrapperClass: 'grid-cols-1 md:grid-cols-2',
|
||||
});
|
||||
|
@@ -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,14 @@ 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
|
||||
// 当前mock接口返回的错误字段是 error 或者 message
|
||||
const responseData = error?.response?.data ?? {};
|
||||
const errorMessage = responseData?.error ?? responseData?.message ?? '';
|
||||
// 如果没有错误信息,则会根据状态码进行提示
|
||||
message.error(errorMessage || msg);
|
||||
}),
|
||||
);
|
||||
|
||||
return client;
|
||||
|
@@ -226,7 +226,7 @@ const defaultPreferences: Preferences = {
|
||||
width: 230,
|
||||
},
|
||||
tabbar: {
|
||||
dragable: true,
|
||||
draggable: true,
|
||||
enable: true,
|
||||
height: 36,
|
||||
keepAlive: true,
|
||||
@@ -234,7 +234,6 @@ const defaultPreferences: Preferences = {
|
||||
showIcon: true,
|
||||
showMaximize: true,
|
||||
showMore: true,
|
||||
showRefresh: true,
|
||||
styleType: 'chrome',
|
||||
},
|
||||
theme: {
|
||||
@@ -262,6 +261,7 @@ const defaultPreferences: Preferences = {
|
||||
notification: true,
|
||||
sidebarToggle: true,
|
||||
themeToggle: true,
|
||||
refresh: true,
|
||||
},
|
||||
};
|
||||
```
|
||||
@@ -406,7 +406,7 @@ interface ShortcutKeyPreferences {
|
||||
|
||||
interface TabbarPreferences {
|
||||
/** Whether dragging of multiple tabs is enabled */
|
||||
dragable: boolean;
|
||||
draggable: boolean;
|
||||
/** Whether multiple tabs are enabled */
|
||||
enable: boolean;
|
||||
/** Tab height */
|
||||
@@ -421,8 +421,6 @@ interface TabbarPreferences {
|
||||
showMaximize: boolean;
|
||||
/** Whether to show the more button */
|
||||
showMore: boolean;
|
||||
/** Whether to show the refresh button */
|
||||
showRefresh: boolean;
|
||||
/** Tab style */
|
||||
styleType: TabsStyleType;
|
||||
}
|
||||
@@ -469,6 +467,8 @@ interface WidgetPreferences {
|
||||
lockScreen: boolean;
|
||||
/** Whether notification widget is displayed */
|
||||
notification: boolean;
|
||||
/** Whether to show the refresh button */
|
||||
refresh: boolean;
|
||||
/** Whether sidebar show/hide widget is displayed */
|
||||
sidebarToggle: boolean;
|
||||
/** Whether theme switch widget is displayed */
|
||||
|
@@ -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,14 @@ function createRequestClient(baseURL: string) {
|
||||
|
||||
// 通用的错误处理,如果没有进入上面的错误处理逻辑,就会进入这里
|
||||
client.addResponseInterceptor(
|
||||
errorMessageResponseInterceptor((msg: string) => message.error(msg)),
|
||||
errorMessageResponseInterceptor((msg: string, error) => {
|
||||
// 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg
|
||||
// 当前mock接口返回的错误字段是 error 或者 message
|
||||
const responseData = error?.response?.data ?? {};
|
||||
const errorMessage = responseData?.error ?? responseData?.message ?? '';
|
||||
// 如果没有错误信息,则会根据状态码进行提示
|
||||
message.error(errorMessage || msg);
|
||||
}),
|
||||
);
|
||||
|
||||
return client;
|
||||
|
@@ -248,7 +248,7 @@ const defaultPreferences: Preferences = {
|
||||
width: 230,
|
||||
},
|
||||
tabbar: {
|
||||
dragable: true,
|
||||
draggable: true,
|
||||
enable: true,
|
||||
height: 36,
|
||||
keepAlive: true,
|
||||
@@ -256,7 +256,6 @@ const defaultPreferences: Preferences = {
|
||||
showIcon: true,
|
||||
showMaximize: true,
|
||||
showMore: true,
|
||||
showRefresh: true,
|
||||
styleType: 'chrome',
|
||||
},
|
||||
theme: {
|
||||
@@ -282,6 +281,7 @@ const defaultPreferences: Preferences = {
|
||||
languageToggle: true,
|
||||
lockScreen: true,
|
||||
notification: true,
|
||||
refresh: true,
|
||||
sidebarToggle: true,
|
||||
themeToggle: true,
|
||||
},
|
||||
@@ -430,7 +430,7 @@ interface ShortcutKeyPreferences {
|
||||
|
||||
interface TabbarPreferences {
|
||||
/** 是否开启多标签页拖拽 */
|
||||
dragable: boolean;
|
||||
draggable: boolean;
|
||||
/** 是否开启多标签页 */
|
||||
enable: boolean;
|
||||
/** 标签页高度 */
|
||||
@@ -445,8 +445,6 @@ interface TabbarPreferences {
|
||||
showMaximize: boolean;
|
||||
/** 显示更多按钮 */
|
||||
showMore: boolean;
|
||||
/** 显示刷新按钮 */
|
||||
showRefresh: boolean;
|
||||
/** 标签页风格 */
|
||||
styleType: TabsStyleType;
|
||||
}
|
||||
@@ -494,6 +492,8 @@ interface WidgetPreferences {
|
||||
lockScreen: boolean;
|
||||
/** 是否显示通知部件 */
|
||||
notification: boolean;
|
||||
/** 显示刷新按钮 */
|
||||
refresh: boolean;
|
||||
/** 是否显示侧边栏显示/隐藏部件 */
|
||||
sidebarToggle: boolean;
|
||||
/** 是否显示主题切换部件 */
|
||||
|
@@ -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` 版本开始,我们不再提供精简的仓库或者分支。我们的目标是提供一个更加一致的开发体验,同时减少维护成本。在这里,我们将如何介绍自己的项目,如何去精简以及移除不需要的功能。
|
||||
@@ -8,7 +12,7 @@
|
||||
|
||||
```bash
|
||||
apps/web-ele
|
||||
apps/web-native
|
||||
apps/web-naive
|
||||
|
||||
```
|
||||
|
||||
@@ -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.4.0",
|
||||
"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',
|
||||
],
|
||||
},
|
||||
],
|
||||
|
@@ -26,6 +26,7 @@ export async function unicorn(): Promise<Linter.Config[]> {
|
||||
'unicorn/prefer-at': 'off',
|
||||
'unicorn/prefer-dom-node-text-content': 'off',
|
||||
'unicorn/prefer-export-from': ['error', { ignoreUsedVariables: true }],
|
||||
'unicorn/prefer-global-this': 'off',
|
||||
'unicorn/prefer-top-level-await': 'off',
|
||||
'unicorn/prevent-abbreviations': 'off',
|
||||
},
|
||||
|
@@ -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.4.0",
|
||||
"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.4.0",
|
||||
"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.4.0",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import type { Config } from 'tailwindcss';
|
||||
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
import { getPackagesSync } from '@vben/node-utils';
|
||||
@@ -19,9 +18,9 @@ const tailwindPackages: string[] = [];
|
||||
|
||||
packages.forEach((pkg) => {
|
||||
// apps目录下和 @vben-core/tailwind-ui 包需要使用到 tailwindcss ui
|
||||
if (fs.existsSync(path.join(pkg.dir, 'tailwind.config.mjs'))) {
|
||||
tailwindPackages.push(pkg.dir);
|
||||
}
|
||||
// if (fs.existsSync(path.join(pkg.dir, 'tailwind.config.mjs'))) {
|
||||
tailwindPackages.push(pkg.dir);
|
||||
// }
|
||||
});
|
||||
|
||||
const shadcnUiColors = {
|
||||
@@ -29,6 +28,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))',
|
||||
@@ -90,7 +90,10 @@ const customColors = {
|
||||
main: {
|
||||
DEFAULT: 'hsl(var(--main))',
|
||||
},
|
||||
overlay: 'hsl(var(--overlay))',
|
||||
overlay: {
|
||||
content: 'hsl(var(--overlay-content))',
|
||||
DEFAULT: 'hsl(var(--overlay))',
|
||||
},
|
||||
red: {
|
||||
...createColorsPalette('red'),
|
||||
foreground: 'hsl(var(--destructive-foreground))',
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/tsconfig",
|
||||
"version": "5.3.0",
|
||||
"version": "5.4.0",
|
||||
"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.4.0",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
@@ -40,6 +40,7 @@
|
||||
"vite-plugin-vue-devtools": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@pnpm/workspace.read-manifest": "catalog:",
|
||||
"@types/archiver": "catalog:",
|
||||
"@types/html-minifier-terser": "catalog:",
|
||||
"@vben/node-utils": "workspace:*",
|
||||
@@ -53,6 +54,7 @@
|
||||
"vite": "catalog:",
|
||||
"vite-plugin-compression": "catalog:",
|
||||
"vite-plugin-dts": "catalog:",
|
||||
"vite-plugin-html": "catalog:"
|
||||
"vite-plugin-html": "catalog:",
|
||||
"vite-plugin-lazy-import": "catalog:"
|
||||
}
|
||||
}
|
||||
|
@@ -47,6 +47,7 @@ function defineApplicationConfig(userConfigPromise?: DefineApplicationOptions) {
|
||||
},
|
||||
pwa: true,
|
||||
pwaOptions: getDefaultPwaOptions(appTitle),
|
||||
vxeTableLazyImport: true,
|
||||
...envConfig,
|
||||
...application,
|
||||
});
|
||||
|
@@ -26,6 +26,7 @@ import { viteMetadataPlugin } from './inject-metadata';
|
||||
import { viteLicensePlugin } from './license';
|
||||
import { viteNitroMockPlugin } from './nitro-mock';
|
||||
import { vitePrintPlugin } from './print';
|
||||
import { viteVxeTableImportsPlugin } from './vxe-table';
|
||||
|
||||
/**
|
||||
* 获取条件成立的 vite 插件
|
||||
@@ -110,6 +111,7 @@ async function loadApplicationPlugins(
|
||||
printInfoMap,
|
||||
pwa,
|
||||
pwaOptions,
|
||||
vxeTableLazyImport,
|
||||
...commonOptions
|
||||
} = options;
|
||||
|
||||
@@ -135,6 +137,12 @@ async function loadApplicationPlugins(
|
||||
return [await vitePrintPlugin({ infoMap: printInfoMap })];
|
||||
},
|
||||
},
|
||||
{
|
||||
condition: vxeTableLazyImport,
|
||||
plugins: async () => {
|
||||
return [await viteVxeTableImportsPlugin()];
|
||||
},
|
||||
},
|
||||
{
|
||||
condition: nitroMock,
|
||||
plugins: async () => {
|
||||
|
@@ -1,20 +1,35 @@
|
||||
import type { PluginOption } from 'vite';
|
||||
|
||||
import { dateUtil, getPackages, readPackageJSON } from '@vben/node-utils';
|
||||
import {
|
||||
dateUtil,
|
||||
findMonorepoRoot,
|
||||
getPackages,
|
||||
readPackageJSON,
|
||||
} from '@vben/node-utils';
|
||||
|
||||
import { readWorkspaceManifest } from '@pnpm/workspace.read-manifest';
|
||||
|
||||
function resolvePackageVersion(
|
||||
pkgsMeta: Record<string, string>,
|
||||
name: string,
|
||||
value: string,
|
||||
catalog: Record<string, string>,
|
||||
) {
|
||||
if (value.includes('catalog:')) {
|
||||
return catalog[name];
|
||||
}
|
||||
|
||||
if (value.includes('workspace')) {
|
||||
return pkgsMeta[name];
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
async function resolveMonorepoDependencies() {
|
||||
const { packages } = await getPackages();
|
||||
const manifest = await readWorkspaceManifest(findMonorepoRoot());
|
||||
const catalog = manifest?.catalog || {};
|
||||
|
||||
const resultDevDependencies: Record<string, string | undefined> = {};
|
||||
const resultDependencies: Record<string, string | undefined> = {};
|
||||
@@ -27,10 +42,20 @@ async function resolveMonorepoDependencies() {
|
||||
for (const { packageJson } of packages) {
|
||||
const { dependencies = {}, devDependencies = {} } = packageJson;
|
||||
for (const [key, value] of Object.entries(dependencies)) {
|
||||
resultDependencies[key] = resolvePackageVersion(pkgsMeta, key, value);
|
||||
resultDependencies[key] = resolvePackageVersion(
|
||||
pkgsMeta,
|
||||
key,
|
||||
value,
|
||||
catalog,
|
||||
);
|
||||
}
|
||||
for (const [key, value] of Object.entries(devDependencies)) {
|
||||
resultDevDependencies[key] = resolvePackageVersion(pkgsMeta, key, value);
|
||||
resultDevDependencies[key] = resolvePackageVersion(
|
||||
pkgsMeta,
|
||||
key,
|
||||
value,
|
||||
catalog,
|
||||
);
|
||||
}
|
||||
}
|
||||
return {
|
||||
|
20
internal/vite-config/src/plugins/vxe-table.ts
Normal file
20
internal/vite-config/src/plugins/vxe-table.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { PluginOption } from 'vite';
|
||||
|
||||
import { lazyImport, VxeResolver } from 'vite-plugin-lazy-import';
|
||||
|
||||
async function viteVxeTableImportsPlugin(): Promise<PluginOption> {
|
||||
return [
|
||||
lazyImport({
|
||||
resolvers: [
|
||||
VxeResolver({
|
||||
libraryName: 'vxe-table',
|
||||
}),
|
||||
VxeResolver({
|
||||
libraryName: 'vxe-pc-ui',
|
||||
}),
|
||||
],
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
export { viteVxeTableImportsPlugin };
|
@@ -123,6 +123,8 @@ interface ApplicationPluginOptions extends CommonPluginOptions {
|
||||
pwa?: boolean;
|
||||
/** pwa 插件配置 */
|
||||
pwaOptions?: Partial<PwaPluginOptions>;
|
||||
/** 是否开启vxe-table懒加载 */
|
||||
vxeTableLazyImport?: boolean;
|
||||
}
|
||||
|
||||
interface LibraryPluginOptions extends CommonPluginOptions {
|
||||
@@ -137,12 +139,12 @@ type ApplicationOptions = ApplicationPluginOptions;
|
||||
|
||||
type LibraryOptions = LibraryPluginOptions;
|
||||
|
||||
type DefineApplicationOptions = (config: ConfigEnv) => Promise<{
|
||||
type DefineApplicationOptions = (config?: ConfigEnv) => Promise<{
|
||||
application?: ApplicationOptions;
|
||||
vite?: UserConfig;
|
||||
}>;
|
||||
|
||||
type DefineLibraryOptions = (config: ConfigEnv) => Promise<{
|
||||
type DefineLibraryOptions = (config?: ConfigEnv) => Promise<{
|
||||
library?: LibraryOptions;
|
||||
vite?: UserConfig;
|
||||
}>;
|
||||
|
32
package.json
32
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vben-admin-pro",
|
||||
"version": "5.3.0",
|
||||
"version": "5.4.0",
|
||||
"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.1",
|
||||
"pnpm": {
|
||||
"peerDependencyRules": {
|
||||
"allowedVersions": {
|
||||
@@ -106,15 +107,14 @@
|
||||
}
|
||||
},
|
||||
"overrides": {
|
||||
"@ctrl/tinycolor": "4.1.0",
|
||||
"clsx": "2.1.1",
|
||||
"pinia": "2.2.2",
|
||||
"vue": "3.5.7"
|
||||
"@ctrl/tinycolor": "catalog:",
|
||||
"clsx": "catalog:",
|
||||
"pinia": "catalog:",
|
||||
"vue": "catalog:"
|
||||
},
|
||||
"neverBuiltDependencies": [
|
||||
"canvas",
|
||||
"node-gyp",
|
||||
"playwright"
|
||||
"node-gyp"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/design",
|
||||
"version": "5.3.0",
|
||||
"version": "5.4.0",
|
||||
"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 {
|
||||
|
@@ -39,6 +39,11 @@
|
||||
|
||||
/* Used for success actions such as <message> */
|
||||
|
||||
--info: 180, 1.54%, 12.75%;
|
||||
--info-foreground: 220, 4%, 58%;
|
||||
|
||||
/* Used for success actions such as <message> */
|
||||
|
||||
--success: 144 57% 58%;
|
||||
--success-foreground: 0 0% 98%;
|
||||
|
||||
@@ -53,6 +58,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%;
|
||||
|
||||
@@ -78,6 +84,7 @@
|
||||
|
||||
/* 遮罩颜色 */
|
||||
--overlay: 0deg 0% 0% / 40%;
|
||||
--overlay-content: 0deg 0% 0% / 40%;
|
||||
|
||||
/* 基本文字大小 */
|
||||
--font-size-base: 16px;
|
||||
|
@@ -38,6 +38,11 @@
|
||||
|
||||
/* Used for success actions such as <message> */
|
||||
|
||||
--info: 240, 5%, 96%;
|
||||
--info-foreground: 220, 4%, 58%;
|
||||
|
||||
/* Used for success actions such as <message> */
|
||||
|
||||
--success: 144 57% 58%;
|
||||
--success-foreground: 0 0% 98%;
|
||||
|
||||
@@ -53,6 +58,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%;
|
||||
|
||||
@@ -78,7 +84,7 @@
|
||||
|
||||
/* 遮罩颜色 */
|
||||
--overlay: 0 0% 0% / 45%;
|
||||
--overlay-light: 0 0% 95% / 45%;
|
||||
--overlay-content: 0 0% 95% / 45%;
|
||||
|
||||
/* 基本文字大小 */
|
||||
--font-size-base: 16px;
|
||||
|
@@ -3,5 +3,19 @@ import { defineBuildConfig } from 'unbuild';
|
||||
export default defineBuildConfig({
|
||||
clean: true,
|
||||
declaration: true,
|
||||
entries: ['src/index'],
|
||||
entries: [
|
||||
{
|
||||
builder: 'mkdist',
|
||||
input: './src',
|
||||
loaders: ['vue'],
|
||||
pattern: ['**/*.vue'],
|
||||
},
|
||||
{
|
||||
builder: 'mkdist',
|
||||
format: 'esm',
|
||||
input: './src',
|
||||
loaders: ['js'],
|
||||
pattern: ['**/*.ts'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/icons",
|
||||
"version": "5.3.0",
|
||||
"version": "5.4.0",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
27
packages/@core/base/icons/src/components/empty.vue
Normal file
27
packages/@core/base/icons/src/components/empty.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<svg
|
||||
height="41"
|
||||
viewBox="0 0 64 41"
|
||||
width="64"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g fill="none" fill-rule="evenodd" transform="translate(0 1)">
|
||||
<ellipse
|
||||
cx="32"
|
||||
cy="33"
|
||||
fill="hsl(var(--background-deep))"
|
||||
rx="32"
|
||||
ry="7"
|
||||
/>
|
||||
<g fill-rule="nonzero" stroke="hsl(var(--heavy))">
|
||||
<path
|
||||
d="M55 12.76L44.854 1.258C44.367.474 43.656 0 42.907 0H21.093c-.749 0-1.46.474-1.947 1.257L9 12.761V22h46v-9.24z"
|
||||
/>
|
||||
<path
|
||||
d="M41.613 15.931c0-1.605.994-2.93 2.227-2.931H55v18.137C55 33.26 53.68 35 52.05 35h-40.1C10.32 35 9 33.259 9 31.137V13h11.16c1.233 0 2.227 1.323 2.227 2.928v.022c0 1.605 1.005 2.901 2.237 2.901h14.752c1.232 0 2.237-1.308 2.237-2.913v-.007z"
|
||||
fill="hsl(var(--accent))"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
@@ -1,4 +1,5 @@
|
||||
export { default as EmptyIcon } from './components/empty.vue';
|
||||
export * from './create-icon';
|
||||
export * from './lucide';
|
||||
|
||||
export * from './lucide';
|
||||
export * from '@iconify/vue';
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/shared",
|
||||
"version": "5.3.0",
|
||||
"version": "5.4.0",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -1,9 +1,11 @@
|
||||
/**
|
||||
* @zh_CN 布局内容高度 css变量
|
||||
* @en_US Layout content height
|
||||
*/
|
||||
/** layout content 组件的高度 */
|
||||
export const CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT = `--vben-content-height`;
|
||||
/** layout content 组件的宽度 */
|
||||
export const CSS_VARIABLE_LAYOUT_CONTENT_WIDTH = `--vben-content-width`;
|
||||
/** layout header 组件的高度 */
|
||||
export const CSS_VARIABLE_LAYOUT_HEADER_HEIGHT = `--vben-header-height`;
|
||||
/** layout footer 组件的高度 */
|
||||
export const CSS_VARIABLE_LAYOUT_FOOTER_HEIGHT = `--vben-footer-height`;
|
||||
|
||||
/**
|
||||
* @zh_CN 默认命名空间
|
||||
|
@@ -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();
|
||||
});
|
||||
});
|
||||
|
@@ -85,3 +85,11 @@ export function needsScrollbar() {
|
||||
// 在其他情况下,根据 scrollHeight 和 innerHeight 比较判断
|
||||
return doc.scrollHeight > window.innerHeight;
|
||||
}
|
||||
|
||||
export function triggerWindowResize(): void {
|
||||
// 创建一个新的 resize 事件
|
||||
const resizeEvent = new Event('resize');
|
||||
|
||||
// 触发 window 的 resize 事件
|
||||
window.dispatchEvent(resizeEvent);
|
||||
}
|
||||
|
@@ -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.4.0",
|
||||
"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.4.0",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
export * from './use-content-style';
|
||||
export * from './use-is-mobile';
|
||||
export * from './use-layout-style';
|
||||
export * from './use-namespace';
|
||||
export * from './use-priority-value';
|
||||
export * from './use-scroll-lock';
|
||||
|
@@ -4,6 +4,8 @@ import { computed, onMounted, onUnmounted, ref } from 'vue';
|
||||
import {
|
||||
CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT,
|
||||
CSS_VARIABLE_LAYOUT_CONTENT_WIDTH,
|
||||
CSS_VARIABLE_LAYOUT_FOOTER_HEIGHT,
|
||||
CSS_VARIABLE_LAYOUT_HEADER_HEIGHT,
|
||||
} from '@vben-core/shared/constants';
|
||||
import {
|
||||
getElementVisibleRect,
|
||||
@@ -15,7 +17,7 @@ import { useCssVar, useDebounceFn } from '@vueuse/core';
|
||||
/**
|
||||
* @zh_CN content style
|
||||
*/
|
||||
function useContentStyle() {
|
||||
export function useLayoutContentStyle() {
|
||||
let resizeObserver: null | ResizeObserver = null;
|
||||
const contentElement = ref<HTMLDivElement | null>(null);
|
||||
const visibleDomRect = ref<null | VisibleDomRect>(null);
|
||||
@@ -40,7 +42,7 @@ function useContentStyle() {
|
||||
contentHeight.value = `${visibleDomRect.value.height}px`;
|
||||
contentWidth.value = `${visibleDomRect.value.width}px`;
|
||||
},
|
||||
100,
|
||||
16,
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
@@ -58,4 +60,28 @@ function useContentStyle() {
|
||||
return { contentElement, overlayStyle, visibleDomRect };
|
||||
}
|
||||
|
||||
export { useContentStyle };
|
||||
export function useLayoutHeaderStyle() {
|
||||
const headerHeight = useCssVar(CSS_VARIABLE_LAYOUT_HEADER_HEIGHT);
|
||||
|
||||
return {
|
||||
getLayoutHeaderHeight: () => {
|
||||
return Number.parseInt(`${headerHeight.value}`, 10);
|
||||
},
|
||||
setLayoutHeaderHeight: (height: number) => {
|
||||
headerHeight.value = `${height}px`;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function useLayoutFooterStyle() {
|
||||
const footerHeight = useCssVar(CSS_VARIABLE_LAYOUT_FOOTER_HEIGHT);
|
||||
|
||||
return {
|
||||
getLayoutFooterHeight: () => {
|
||||
return Number.parseInt(`${footerHeight.value}`, 10);
|
||||
},
|
||||
setLayoutFooterHeight: (height: number) => {
|
||||
footerHeight.value = `${height}px`;
|
||||
},
|
||||
};
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user