Compare commits

...

9 Commits
v5.3.1 ... main

Author SHA1 Message Date
Vben 4173264805
feat: add vxe-table component (#4563)
* chore: wip vxe-table

* feat: add table demo

* chore: follow ci recommendations to adjust details

* chore: add custom-cell demo

* feat: add custom-cell table demo

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

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

* perf: default placeholder for input and select components

* chore: update ts type

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

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


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

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

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

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

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

* chore: update deps

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-03 12:54:04 +08:00
LinaBell b2c117f544
chore: demo of customizing form layout using tailwind (#4549) 2024-09-30 09:47:16 +08:00
128 changed files with 3851 additions and 936 deletions

View 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);
});

View File

@ -10,6 +10,7 @@
"start": "nitro dev"
},
"dependencies": {
"@faker-js/faker": "catalog:",
"jsonwebtoken": "catalog:",
"nitropack": "catalog:"
},

View File

@ -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,
@ -27,3 +48,18 @@ export function unAuthorizedResponse(event: H3Event<EventHandlerRequest>) {
setResponseStatus(event, 401);
return useResponseError('UnauthorizedException', '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));
}

View File

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

View File

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

View File

@ -1 +1,2 @@
export * from './form';
export * from './vxe-table';

View 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';

View File

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

View File

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

View File

@ -1 +1,2 @@
export * from './form';
export * from './vxe-table';

View 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';

View File

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

View File

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

View File

@ -1,2 +1,3 @@
export * from './form';
export * from './naive';
export * from './vxe-table';

View 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';

View File

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

View File

@ -19,6 +19,7 @@
"intlify",
"mkdist",
"mockjs",
"vitejs",
"noopener",
"noreferrer",
"nprogress",

View File

@ -1,6 +1,6 @@
{
"name": "@vben/docs",
"version": "5.3.1",
"version": "5.3.2",
"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:",

View File

@ -31,6 +31,7 @@ import type {
VbenFormProps,
} from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue';
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
@ -84,6 +85,16 @@ export type FormComponentType =
| 'Upload'
| BaseFormComponentType;
const withDefaultPlaceholder = <T extends Component>(
component: T,
type: 'input' | 'select',
) => {
return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
const placeholder = props?.placeholder || $t(`placeholder.${type}`);
return h(component, { ...props, ...attrs, placeholder }, slots);
};
};
// 初始化表单组件并注册到form组件内部
setupVbenForm<FormComponentType>({
components: {
@ -100,26 +111,27 @@ setupVbenForm<FormComponentType>({
return h(Button, { ...props, attrs, type: 'primary' }, slots);
},
Divider,
Input,
InputNumber,
InputPassword,
Mentions,
Input: withDefaultPlaceholder(Input, 'input'),
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
Mentions: withDefaultPlaceholder(Mentions, 'input'),
Radio,
RadioGroup,
RangePicker,
Rate,
Select,
Select: withDefaultPlaceholder(Select, 'select'),
Space,
Switch,
Textarea,
Textarea: withDefaultPlaceholder(Textarea, 'input'),
TimePicker,
TreeSelect,
TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
Upload,
},
config: {
// 是否禁用onChange事件监听naive ui组件库默认不需要监听onChange事件否则会在控制台报错
disabledOnChangeListener: true,
// ant design vue组件库默认都是 v-model:value
baseModelPropName: 'value',
// 一些组件是 v-model:checked 或者 v-model:fileList
modelPropNameMap: {
Checkbox: 'checked',
@ -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` | - |

View File

@ -95,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

View File

@ -95,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的安装

View File

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

View File

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

View File

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

View File

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

View File

@ -91,7 +91,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))',

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "@vben/vite-config",
"version": "5.3.1",
"version": "5.3.2",
"private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
@ -53,6 +53,7 @@
"vite": "catalog:",
"vite-plugin-compression": "catalog:",
"vite-plugin-dts": "catalog:",
"vite-plugin-html": "catalog:"
"vite-plugin-html": "catalog:",
"vite-plugin-lazy-import": "catalog:"
}
}

View File

@ -47,6 +47,7 @@ function defineApplicationConfig(userConfigPromise?: DefineApplicationOptions) {
},
pwa: true,
pwaOptions: getDefaultPwaOptions(appTitle),
vxeTableLazyImport: true,
...envConfig,
...application,
});

View File

@ -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 () => {

View 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 };

View File

@ -123,6 +123,8 @@ interface ApplicationPluginOptions extends CommonPluginOptions {
pwa?: boolean;
/** pwa 插件配置 */
pwaOptions?: Partial<PwaPluginOptions>;
/** 是否开启vxe-table懒加载 */
vxeTableLazyImport?: boolean;
}
interface LibraryPluginOptions extends CommonPluginOptions {

View File

@ -1,6 +1,6 @@
{
"name": "vben-admin-pro",
"version": "5.3.1",
"version": "5.3.2",
"private": true,
"keywords": [
"monorepo",
@ -49,7 +49,7 @@
"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",
@ -99,7 +99,7 @@
"node": ">=20.10.0",
"pnpm": ">=9.5.0"
},
"packageManager": "pnpm@9.11.0",
"packageManager": "pnpm@9.12.0",
"pnpm": {
"peerDependencyRules": {
"allowedVersions": {

View File

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

View File

@ -79,6 +79,7 @@
/* 遮罩颜色 */
--overlay: 0deg 0% 0% / 40%;
--overlay-content: 0deg 0% 0% / 40%;
/* 基本文字大小 */
--font-size-base: 16px;

View File

@ -79,7 +79,7 @@
/* 遮罩颜色 */
--overlay: 0 0% 0% / 45%;
--overlay-light: 0 0% 95% / 45%;
--overlay-content: 0 0% 95% / 45%;
/* 基本文字大小 */
--font-size-base: 16px;

View File

@ -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'],
},
],
});

View File

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

View 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>

View File

@ -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';

View File

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

View File

@ -1,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

View File

@ -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);
}

View File

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

View File

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

View File

@ -1,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';

View File

@ -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`;
},
};
}

View File

@ -39,7 +39,7 @@ exports[`defaultPreferences immutability test > should not modify the config obj
"icpLink": "",
},
"footer": {
"enable": true,
"enable": false,
"fixed": false,
},
"header": {

View File

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

View File

@ -39,7 +39,7 @@ const defaultPreferences: Preferences = {
icpLink: '',
},
footer: {
enable: true,
enable: false,
fixed: false,
},
header: {

View File

@ -28,6 +28,10 @@ function usePreferences() {
return isDarkTheme(preferences.theme.mode);
});
const locale = computed(() => {
return preferences.app.locale;
});
const isMobile = computed(() => {
return appPreferences.value.isMobile;
});
@ -218,6 +222,7 @@ function usePreferences() {
isSideNav,
keepAlive,
layout,
locale,
preferencesButtonPosition,
sidebarCollapsed,
theme,

View File

@ -109,7 +109,7 @@ describe('formApi', () => {
});
it('should unmount form and reset state', () => {
formApi.unmounted();
formApi.unmount();
expect(formApi.isMounted).toBe(false);
});

View File

@ -1,9 +1,9 @@
<script setup lang="ts">
import { computed, toRaw, unref } from 'vue';
import { computed, toRaw, unref, watch } from 'vue';
import { useSimpleLocale } from '@vben-core/composables';
import { VbenExpandableArrow } from '@vben-core/shadcn-ui';
import { cn, isFunction } from '@vben-core/shared/utils';
import { cn, isFunction, triggerWindowResize } from '@vben-core/shared/utils';
import { COMPONENT_MAP } from '../config';
import { injectFormProps } from '../use-form-context';
@ -65,6 +65,16 @@ async function handleReset(e: Event) {
form.resetForm();
}
}
watch(
() => collapsed.value,
() => {
const props = unref(rootProps);
if (props.collapseTriggerResize) {
triggerWindowResize();
}
},
);
</script>
<template>
<div

View File

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

View File

@ -24,9 +24,11 @@ function getDefaultState(): VbenFormProps {
actionWrapperClass: '',
collapsed: false,
collapsedRows: 1,
collapseTriggerResize: false,
commonConfig: {},
handleReset: undefined,
handleSubmit: undefined,
handleValuesChange: undefined,
layout: 'horizontal',
resetButtonOptions: {},
schema: [],
@ -249,7 +251,7 @@ export class FormApi {
return rawValues;
}
unmounted() {
unmount() {
// this.state = null;
this.isMounted = false;
this.stateHandler.reset();

View File

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

View File

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

View File

@ -1,5 +1,5 @@
import type { VbenButtonProps } from '@vben-core/shadcn-ui';
import type { Field, FormContext, GenericObject } from 'vee-validate';
import type { FieldOptions, FormContext, GenericObject } from 'vee-validate';
import type { ZodTypeAny } from 'zod';
import type { FormApi } from './form-api';
@ -33,6 +33,15 @@ export type FormItemClassType =
| (Record<never, never> & string)
| WrapperClassType;
export type FormFieldOptions = Partial<
{
validateOnBlur?: boolean;
validateOnChange?: boolean;
validateOnInput?: boolean;
validateOnModelUpdate?: boolean;
} & FieldOptions
>;
export interface FormShape {
/** 默认值 */
default?: any;
@ -139,11 +148,16 @@ export interface FormCommonConfig {
* @default false
*/
disabled?: boolean;
/**
* change事件监听
* @default false
*/
disabledOnChangeListener?: boolean;
/**
*
* @default {}
*/
formFieldProps?: Partial<typeof Field>;
formFieldProps?: FormFieldOptions;
/**
*
* @default ""
@ -230,6 +244,11 @@ export interface FormRenderProps<
* @default 1
*/
collapsedRows?: number;
/**
* resize事件
* @default false
*/
collapseTriggerResize?: boolean;
/**
* 使
*/
@ -288,6 +307,10 @@ export interface VbenFormProps<
*
*/
handleSubmit?: HandleSubmitFn;
/**
*
*/
handleValuesChange?: (values: Record<string, any>) => void;
/**
*
*/
@ -317,6 +340,7 @@ export interface VbenFormAdapterOptions<
components: Partial<Record<T, Component>>;
config?: {
baseModelPropName?: string;
disabledOnChangeListener?: boolean;
modelPropNameMap?: Partial<Record<T, string>>;
};
defineRules?: {

View File

@ -24,7 +24,7 @@ export function useVbenForm<
const Form = defineComponent(
(props: VbenFormProps, { attrs, slots }) => {
onBeforeUnmount(() => {
api.unmounted();
api.unmount();
});
return () =>
h(VbenUseForm, { ...props, ...attrs, formApi: extendedApi }, slots);

View File

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

View File

@ -1,13 +1,19 @@
<script setup lang="ts">
import type { ExtendedFormApi, VbenFormProps } from './types';
// import { toRaw, watch } from 'vue';
import { useForwardPriorityValues } from '@vben-core/composables';
// import { isFunction } from '@vben-core/shared/utils';
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;
@ -28,6 +34,18 @@ props.formApi?.mount?.(form);
const handleUpdateCollapsed = (value: boolean) => {
props.formApi?.setState({ collapsed: !!value });
};
// if (isFunction(forward.value.handleValuesChange)) {
// watch(
// () => form.values,
// (val) => {
// forward.value.handleValuesChange?.(toRaw(val));
// },
// {
// deep: true,
// immediate: true,
// },
// );
// }
</script>
<template>
@ -36,6 +54,7 @@ const handleUpdateCollapsed = (value: boolean) => {
:component-bind-event-map="COMPONENT_BIND_EVENT_MAP"
:component-map="COMPONENT_MAP"
:form="form"
:global-common-config="DEFAULT_FORM_COMMON_CONFIG"
>
<template
v-for="slotName in delegatedSlots"

View File

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

View File

@ -4,7 +4,7 @@ import type { ContentCompactType } from '@vben-core/typings';
import type { CSSProperties } from 'vue';
import { computed } from 'vue';
import { useContentStyle } from '@vben-core/composables';
import { useLayoutContentStyle } from '@vben-core/composables';
import { Slot } from '@vben-core/shadcn-ui';
interface Props {
@ -25,7 +25,7 @@ interface Props {
const props = withDefaults(defineProps<Props>(), {});
const { contentElement, overlayStyle } = useContentStyle();
const { contentElement, overlayStyle } = useLayoutContentStyle();
const style = computed((): CSSProperties => {
const {

View File

@ -4,7 +4,11 @@ import type { VbenLayoutProps } from './vben-layout';
import type { CSSProperties } from 'vue';
import { computed, ref, watch } from 'vue';
import { SCROLL_FIXED_CLASS } from '@vben-core/composables';
import {
SCROLL_FIXED_CLASS,
useLayoutFooterStyle,
useLayoutHeaderStyle,
} from '@vben-core/composables';
import { Menu } from '@vben-core/icons';
import { VbenIconButton } from '@vben-core/shadcn-ui';
@ -74,6 +78,9 @@ const {
y: scrollY,
} = useScroll(document);
const { setLayoutHeaderHeight } = useLayoutHeaderStyle();
const { setLayoutFooterHeight } = useLayoutFooterStyle();
const { y: mouseY } = useMouse({ target: contentRef, type: 'client' });
const {
@ -356,6 +363,26 @@ watch(
},
);
watch(
[() => headerWrapperHeight.value, () => isFullContent.value],
([height]) => {
setLayoutHeaderHeight(isFullContent.value ? 0 : height);
},
{
immediate: true,
},
);
watch(
() => props.footerHeight,
(height: number) => {
setLayoutFooterHeight(height);
},
{
immediate: true,
},
);
{
const mouseMove = () => {
mouseY.value > headerWrapperHeight.value

View File

@ -15,13 +15,6 @@ export default defineBuildConfig({
loaders: ['vue'],
pattern: ['**/*.vue'],
},
// {
// builder: 'mkdist',
// format: 'cjs',
// input: './src',
// loaders: ['js'],
// pattern: ['**/*.ts'],
// },
{
builder: 'mkdist',
format: 'esm',

View File

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

View File

@ -16,13 +16,6 @@ export default defineBuildConfig({
loaders: ['vue'],
pattern: ['**/*.vue'],
},
// {
// builder: 'mkdist',
// format: 'cjs',
// input: './src',
// loaders: ['js'],
// pattern: ['**/*.ts'],
// },
{
builder: 'mkdist',
format: 'esm',

View File

@ -1,6 +1,6 @@
{
"name": "@vben-core/shadcn-ui",
"version": "5.3.1",
"version": "5.3.2",
"#main": "./dist/index.mjs",
"#module": "./dist/index.mjs",
"homepage": "https://github.com/vbenjs/vue-vben-admin",

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import { computed } from 'vue';
import { computed, watch } from 'vue';
import { cn } from '@vben-core/shared/utils';
@ -32,10 +32,13 @@ const {
showRowsPerPage = true,
showTotalText = true,
siblingCount = 1,
size = 'default',
size = 'small',
total = 500,
} = defineProps<Props>();
const emit = defineEmits<{
pageChange: [currentPage: number, pageSize: number];
}>();
const currentPage = defineModel<number>('currentPage', { default: 1 });
const itemPerPage = defineModel<number>('itemPerPage', { default: 20 });
@ -53,6 +56,13 @@ const options = computed(() => {
function handleUpdateModelValue(value: string) {
itemPerPage.value = Number(value);
}
watch(
[() => itemPerPage.value, () => currentPage.value],
([itemPerPage, currentPage]) => {
emit('pageChange', currentPage, itemPerPage);
},
);
</script>
<template>

View File

@ -69,7 +69,7 @@ function onTransitionEnd() {
<div
:class="
cn(
'z-100 dark:bg-overlay pointer-events-none absolute left-0 top-0 flex size-full flex-col items-center justify-center bg-[hsl(var(--overlay-light))] transition-all duration-500',
'z-100 dark:bg-overlay bg-overlay-content pointer-events-none absolute left-0 top-0 flex size-full flex-col items-center justify-center transition-all duration-500',
{
'invisible opacity-0': !showSpinner,
},

View File

@ -63,7 +63,7 @@ function onTransitionEnd() {
<div
:class="
cn(
'flex-center z-100 dark:bg-overlay absolute left-0 top-0 size-full bg-[hsl(var(--overlay-light))] backdrop-blur-sm transition-all duration-500',
'flex-center z-100 bg-overlay-content absolute left-0 top-0 size-full backdrop-blur-sm transition-all duration-500',
{
'invisible opacity-0': !showSpinner,
},

View File

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

View File

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

View File

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

View File

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

View File

@ -54,7 +54,7 @@ describe('page.vue', () => {
},
});
const contentDiv = wrapper.find('.m-4');
const contentDiv = wrapper.find('.p-4');
expect(contentDiv.classes()).toContain('custom-class');
});

View File

@ -1,37 +1,79 @@
<script setup lang="ts">
import { computed, nextTick, onMounted, ref, useTemplateRef } from 'vue';
interface Props {
title?: string;
description?: string;
contentClass?: string;
showFooter?: boolean;
/**
* 根据content可见高度自适应
*/
autoContentHeight?: boolean;
}
defineOptions({
name: 'Page',
});
const props = withDefaults(defineProps<Props>(), {
contentClass: '',
description: '',
showFooter: false,
title: '',
const {
contentClass = '',
description = '',
autoContentHeight = false,
title = '',
} = defineProps<Props>();
const headerHeight = ref(0);
const footerHeight = ref(0);
const shouldAutoHeight = ref(false);
const headerRef = useTemplateRef<HTMLDivElement>('headerRef');
const footerRef = useTemplateRef<HTMLDivElement>('footerRef');
const contentStyle = computed(() => {
if (autoContentHeight) {
return {
height: shouldAutoHeight.value
? `calc(var(--vben-content-height) - ${headerHeight.value}px - ${footerHeight.value}px)`
: '0',
// 'overflow-y': shouldAutoHeight.value?'auto':'unset',
};
}
return {};
});
async function calcContentHeight() {
if (!autoContentHeight) {
return;
}
await nextTick();
headerHeight.value = headerRef.value?.offsetHeight || 0;
footerHeight.value = footerRef.value?.offsetHeight || 0;
setTimeout(() => {
shouldAutoHeight.value = true;
}, 30);
}
onMounted(() => {
calcContentHeight();
});
</script>
<template>
<div class="relative h-full">
<div class="relative">
<div
v-if="description || $slots.description || title"
class="bg-card px-6 py-4"
v-if="
description ||
$slots.description ||
title ||
$slots.title ||
$slots.extra
"
ref="headerRef"
class="bg-card relative px-6 py-4"
>
<slot name="title">
<div
v-if="title"
class="mb-2 flex justify-between text-lg font-semibold"
>
<div v-if="title" class="mb-2 flex text-lg font-semibold">
{{ title }}
<slot name="extra"></slot>
</div>
</slot>
@ -40,14 +82,19 @@ const props = withDefaults(defineProps<Props>(), {
{{ description }}
</p>
</slot>
<div v-if="$slots.extra" class="absolute bottom-4 right-4">
<slot name="extra"></slot>
</div>
</div>
<div :class="contentClass" class="m-4">
<div :class="contentClass" :style="contentStyle" class="h-full p-4">
<slot></slot>
</div>
<div
v-if="props.showFooter"
v-if="$slots.footer"
ref="footerRef"
class="bg-card align-center absolute bottom-0 left-0 right-0 flex px-6 py-4"
>
<slot name="footer"></slot>

View File

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

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "@vben/plugins",
"version": "5.3.1",
"version": "5.3.2",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {
@ -17,12 +17,26 @@
"./echarts": {
"types": "./src/echarts/index.ts",
"default": "./src/echarts/index.ts"
},
"./vxe-table": {
"types": "./src/vxe-table/index.ts",
"default": "./src/vxe-table/index.ts"
}
},
"dependencies": {
"@vben-core/form-ui": "workspace:*",
"@vben-core/shadcn-ui": "workspace:*",
"@vben-core/shared": "workspace:*",
"@vben/hooks": "workspace:*",
"@vben/icons": "workspace:*",
"@vben/locales": "workspace:*",
"@vben/preferences": "workspace:*",
"@vben/types": "workspace:*",
"@vben/utils": "workspace:*",
"@vueuse/core": "catalog:",
"echarts": "catalog:",
"vue": "catalog:"
"vue": "catalog:",
"vxe-pc-ui": "catalog:",
"vxe-table": "catalog:"
}
}

View File

@ -0,0 +1 @@
export { default } from '@vben/tailwind-config/postcss';

View File

@ -0,0 +1,111 @@
import type { VxeGridInstance } from 'vxe-table';
import type { VxeGridProps } from './types';
import { toRaw } from 'vue';
import { Store } from '@vben-core/shared/store';
import {
bindMethods,
isFunction,
mergeWithArrayOverride,
StateHandler,
} from '@vben-core/shared/utils';
function getDefaultState(): VxeGridProps {
return {
class: '',
gridClass: '',
gridOptions: {},
gridEvents: {},
formOptions: undefined,
};
}
export class VxeGridApi {
// private prevState: null | VxeGridProps = null;
public grid = {} as VxeGridInstance;
isMounted = false;
public state: null | VxeGridProps = null;
stateHandler: StateHandler;
public store: Store<VxeGridProps>;
constructor(options: VxeGridProps = {}) {
const storeState = { ...options };
const defaultState = getDefaultState();
this.store = new Store<VxeGridProps>(
mergeWithArrayOverride(storeState, defaultState),
{
onUpdate: () => {
// this.prevState = this.state;
this.state = this.store.state;
},
},
);
this.state = this.store.state;
this.stateHandler = new StateHandler();
bindMethods(this);
}
mount(instance: null | VxeGridInstance) {
if (!this.isMounted && instance) {
this.grid = instance;
this.stateHandler.setConditionTrue();
this.isMounted = true;
}
}
async query(params: Record<string, any> = {}) {
try {
await this.grid.commitProxy('query', toRaw(params));
} catch (error) {
console.error('Error occurred while querying:', error);
}
}
async reload(params: Record<string, any> = {}) {
try {
await this.grid.commitProxy('reload', toRaw(params));
} catch (error) {
console.error('Error occurred while reloading:', error);
}
}
setGridOptions(options: Partial<VxeGridProps['gridOptions']>) {
this.setState({
gridOptions: options,
});
}
setLoading(isLoading: boolean) {
this.setState({
gridOptions: {
loading: isLoading,
},
});
}
setState(
stateOrFn:
| ((prev: VxeGridProps) => Partial<VxeGridProps>)
| Partial<VxeGridProps>,
) {
if (isFunction(stateOrFn)) {
this.store.setState((prev) => {
return mergeWithArrayOverride(stateOrFn(prev), prev);
});
} else {
this.store.setState((prev) => mergeWithArrayOverride(stateOrFn, prev));
}
}
unmount() {
this.isMounted = false;
this.stateHandler.reset();
}
}

View File

@ -0,0 +1,4 @@
export { setupVbenVxeTable } from './init';
export * from './use-vxe-grid';
export { default as VbenVxeGrid } from './use-vxe-grid.vue';
export type { VxeGridListeners, VxeGridProps } from 'vxe-table';

View File

@ -0,0 +1,122 @@
import type { SetupVxeTable } from './types';
import { defineComponent, watch } from 'vue';
import { usePreferences } from '@vben/preferences';
import { useVbenForm } from '@vben-core/form-ui';
import {
VxeButton,
VxeButtonGroup,
// VxeFormGather,
// VxeForm,
// VxeFormItem,
VxeIcon,
VxeInput,
VxeLoading,
VxePager,
// VxeList,
// VxeModal,
// VxeOptgroup,
// VxeOption,
// VxePulldown,
// VxeRadio,
// VxeRadioButton,
// VxeRadioGroup,
VxeSelect,
VxeTooltip,
VxeUI,
// VxeSwitch,
// VxeTextarea,
} from 'vxe-pc-ui';
import enUS from 'vxe-pc-ui/lib/language/en-US';
// 导入默认的语言
import zhCN from 'vxe-pc-ui/lib/language/zh-CN';
import {
VxeColgroup,
VxeColumn,
VxeGrid,
VxeTable,
VxeToolbar,
} from 'vxe-table';
// 是否加载过
let isInit = false;
// eslint-disable-next-line import/no-mutable-exports
export let useTableForm: typeof useVbenForm;
// 部分组件如果没注册vxe-table 会报错,这里实际没用组件,只是为了不报错,同时可以减少打包体积
const createVirtualComponent = (name = '') => {
return defineComponent({
name,
});
};
export function initVxeTable() {
if (isInit) {
return;
}
VxeUI.component(VxeTable);
VxeUI.component(VxeColumn);
VxeUI.component(VxeColgroup);
VxeUI.component(VxeLoading);
VxeUI.component(VxeGrid);
VxeUI.component(VxeToolbar);
VxeUI.component(VxeButton);
VxeUI.component(VxeButtonGroup);
// VxeUI.component(VxeCheckbox);
// VxeUI.component(VxeCheckboxGroup);
VxeUI.component(createVirtualComponent('VxeForm'));
// VxeUI.component(VxeFormGather);
// VxeUI.component(VxeFormItem);
VxeUI.component(VxeIcon);
VxeUI.component(VxeInput);
// VxeUI.component(VxeList);
VxeUI.component(VxeLoading);
// VxeUI.component(VxeModal);
// VxeUI.component(VxeOptgroup);
// VxeUI.component(VxeOption);
VxeUI.component(VxePager);
// VxeUI.component(VxePulldown);
// VxeUI.component(VxeRadio);
// VxeUI.component(VxeRadioButton);
// VxeUI.component(VxeRadioGroup);
VxeUI.component(VxeSelect);
// VxeUI.component(VxeSwitch);
// VxeUI.component(VxeTextarea);
VxeUI.component(VxeTooltip);
isInit = true;
}
export function setupVbenVxeTable(setupOptions: SetupVxeTable) {
const { configVxeTable, useVbenForm } = setupOptions;
initVxeTable();
useTableForm = useVbenForm;
const preference = usePreferences();
const localMap = {
'zh-CN': zhCN,
'en-US': enUS,
};
watch(
[() => preference.theme.value, () => preference.locale.value],
([theme, locale]) => {
VxeUI.setTheme(theme === 'dark' ? 'dark' : 'light');
VxeUI.setI18n(locale, localMap[locale]);
VxeUI.setLanguage(locale);
},
{
immediate: true,
},
);
configVxeTable(VxeUI);
}

View File

@ -0,0 +1,78 @@
:root {
--vxe-ui-font-color: hsl(var(--foreground));
--vxe-ui-font-primary-color: hsl(var(--primary));
/* --vxe-ui-font-lighten-color: #babdc0;
--vxe-ui-font-darken-color: #86898e; */
--vxe-ui-font-disabled-color: hsl(var(--foreground) / 50%);
/* base */
--vxe-ui-base-popup-border-color: hsl(var(--border));
/* --vxe-ui-base-popup-box-shadow: 0px 12px 30px 8px rgb(0 0 0 / 50%); */
/* layout */
--vxe-ui-layout-background-color: hsl(var(--background));
--vxe-ui-table-resizable-line-color: hsl(var(--border));
/* --vxe-ui-table-fixed-left-scrolling-box-shadow: 8px 0px 10px -5px hsl(var(--accent));
--vxe-ui-table-fixed-right-scrolling-box-shadow: -8px 0px 10px -5px hsl(var(--accent)); */
/* input */
--vxe-ui-input-border-color: hsl(var(--border));
/* --vxe-ui-input-placeholder-color: #8d9095; */
/* --vxe-ui-input-disabled-background-color: #262727; */
/* loading */
--vxe-ui-loading-background-color: hsl(var(--overlay-content));
/* table */
--vxe-ui-table-header-background-color: hsl(var(--accent));
--vxe-ui-table-border-color: hsl(var(--border));
--vxe-ui-table-row-hover-background-color: hsl(var(--accent-hover));
--vxe-ui-table-row-striped-background-color: hsl(var(--accent) / 60%);
--vxe-ui-table-row-hover-striped-background-color: hsl(var(--accent));
--vxe-ui-table-row-radio-checked-background-color: hsl(var(--accent));
--vxe-ui-table-row-hover-radio-checked-background-color: hsl(
var(--accent-hover)
);
--vxe-ui-table-row-checkbox-checked-background-color: hsl(var(--accent));
--vxe-ui-table-row-hover-checkbox-checked-background-color: hsl(
var(--accent-hover)
);
--vxe-ui-table-row-current-background-color: hsl(var(--accent));
--vxe-ui-table-row-hover-current-background-color: hsl(var(--accent-hover));
/* --vxe-ui-table-fixed-scrolling-box-shadow-color: rgb(0 0 0 / 80%); */
}
.vxe-pager {
.vxe-pager--prev-btn:not(.is--disabled):active,
.vxe-pager--next-btn:not(.is--disabled):active,
.vxe-pager--num-btn:not(.is--disabled):active,
.vxe-pager--jump-prev:not(.is--disabled):active,
.vxe-pager--jump-next:not(.is--disabled):active,
.vxe-pager--prev-btn:not(.is--disabled):focus,
.vxe-pager--next-btn:not(.is--disabled):focus,
.vxe-pager--num-btn:not(.is--disabled):focus,
.vxe-pager--jump-prev:not(.is--disabled):focus,
.vxe-pager--jump-next:not(.is--disabled):focus {
color: hsl(var(--accent-foreground));
background-color: hsl(var(--accent));
border: 1px solid hsl(var(--border));
box-shadow: 0 0 0 1px hsl(var(--border));
}
.vxe-pager {
&--wrapper {
display: flex;
align-items: center;
}
&--sizes {
margin-right: auto;
}
}
}

View File

@ -0,0 +1,53 @@
import type { DeepPartial } from '@vben/types';
import type { VbenFormProps } from '@vben-core/form-ui';
import type {
VxeGridListeners,
VxeGridProps as VxeTableGridProps,
VxeUIExport,
} from 'vxe-table';
import type { VxeGridApi } from './api';
import type { Ref } from 'vue';
import { useVbenForm } from '@vben-core/form-ui';
export interface VxePaginationInfo {
currentPage: number;
pageSize: number;
total: number;
}
export interface VxeGridProps {
/**
* class
*/
class?: any;
/**
* vxe-grid class
*/
gridClass?: any;
/**
* vxe-grid
*/
gridOptions?: DeepPartial<VxeTableGridProps>;
/**
* vxe-grid
*/
gridEvents?: DeepPartial<VxeGridListeners>;
/**
*
*/
formOptions?: VbenFormProps;
}
export type ExtendedVxeGridApi = {
useStore: <T = NoInfer<VxeGridProps>>(
selector?: (state: NoInfer<VxeGridProps>) => T,
) => Readonly<Ref<T>>;
} & VxeGridApi;
export interface SetupVxeTable {
configVxeTable: (ui: VxeUIExport) => void;
useVbenForm: typeof useVbenForm;
}

View File

@ -0,0 +1,42 @@
import type { ExtendedVxeGridApi, VxeGridProps } from './types';
import { defineComponent, h, onBeforeUnmount } from 'vue';
import { useStore } from '@vben-core/shared/store';
import { VxeGridApi } from './api';
import VxeGrid from './use-vxe-grid.vue';
export function useVbenVxeGrid(options: VxeGridProps) {
// const IS_REACTIVE = isReactive(options);
const api = new VxeGridApi(options);
const extendedApi: ExtendedVxeGridApi = api as ExtendedVxeGridApi;
extendedApi.useStore = (selector) => {
return useStore(api.store, selector);
};
const Grid = defineComponent(
(props: VxeGridProps, { attrs, slots }) => {
onBeforeUnmount(() => {
api.unmount();
});
return () => h(VxeGrid, { ...props, ...attrs, api: extendedApi }, slots);
},
{
inheritAttrs: false,
name: 'VbenVxeGrid',
},
);
// Add reactivity support
// if (IS_REACTIVE) {
// watch(
// () => options,
// () => {
// api.setState(options);
// },
// { immediate: true },
// );
// }
return [Grid, extendedApi] as const;
}

View File

@ -0,0 +1,264 @@
<script lang="ts" setup>
import type { VbenFormProps } from '@vben-core/form-ui';
import type {
VxeGridInstance,
VxeGridProps as VxeTableGridProps,
} from 'vxe-table';
import type { ExtendedVxeGridApi, VxeGridProps } from './types';
import {
computed,
nextTick,
onMounted,
toRaw,
useSlots,
useTemplateRef,
} from 'vue';
import { usePriorityValues } from '@vben/hooks';
import { EmptyIcon } from '@vben/icons';
import { $t } from '@vben/locales';
import { cloneDeep, cn, mergeWithArrayOverride } from '@vben/utils';
import { VbenLoading } from '@vben-core/shadcn-ui';
import { VxeGrid, VxeUI } from 'vxe-table';
import { useTableForm } from './init';
import 'vxe-table/styles/cssvar.scss';
import 'vxe-pc-ui/styles/cssvar.scss';
import './theme.css';
interface Props extends VxeGridProps {
api: ExtendedVxeGridApi;
}
const props = withDefaults(defineProps<Props>(), {});
const gridRef = useTemplateRef<VxeGridInstance>('gridRef');
const state = props.api?.useStore?.();
const {
gridOptions,
class: className,
gridClass,
gridEvents,
formOptions,
} = usePriorityValues(props, state);
const slots = useSlots();
const [Form, formApi] = useTableForm({});
const showToolbar = computed(() => {
return !!slots['toolbar-actions']?.() || !!slots['toolbar-tools']?.();
});
const options = computed(() => {
const slotActions = slots['toolbar-actions']?.();
const slotTools = slots['toolbar-tools']?.();
const forceUseToolbarOptions = showToolbar.value
? {
toolbarConfig: {
slots: {
...(slotActions ? { buttons: 'toolbar-actions' } : {}),
...(slotTools ? { tools: 'toolbar-tools' } : {}),
},
},
}
: {};
const mergedOptions: VxeTableGridProps = cloneDeep(
mergeWithArrayOverride(
{},
forceUseToolbarOptions,
toRaw(gridOptions.value),
),
);
if (mergedOptions.proxyConfig) {
const { ajax } = mergedOptions.proxyConfig;
mergedOptions.proxyConfig.enabled = !!ajax;
// ,
mergedOptions.proxyConfig.autoLoad = false;
}
if (!showToolbar.value && mergedOptions.toolbarConfig) {
mergedOptions.toolbarConfig.enabled = false;
}
if (mergedOptions.pagerConfig) {
mergedOptions.pagerConfig = mergeWithArrayOverride(
{},
mergedOptions.pagerConfig,
{
pageSize: 20,
background: true,
pageSizes: [10, 20, 30, 50, 100, 200],
className: 'mt-2 w-full',
layouts: [
'Total',
'Sizes',
'Home',
'PrevJump',
'PrevPage',
'Number',
'NextPage',
'NextJump',
'End',
// 'FullJump',
] as any[],
size: 'mini' as const,
},
);
}
if (mergedOptions.formConfig) {
mergedOptions.formConfig.enabled = false;
}
return mergedOptions;
});
const events = computed(() => {
return {
...gridEvents.value,
};
});
const vbenFormOptions = computed(() => {
const defaultFormProps: VbenFormProps = {
handleSubmit: async () => {
const formValues = formApi.form.values;
props.api.reload(formValues);
},
handleReset: async () => {
formApi.resetForm();
const formValues = formApi.form.values;
props.api.reload(formValues);
},
collapseTriggerResize: true,
commonConfig: {
componentProps: {
class: 'w-full',
},
},
showCollapseButton: true,
submitButtonOptions: {
text: $t('common.query'),
},
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
};
return {
...mergeWithArrayOverride({}, formOptions.value, defaultFormProps),
};
});
const delegatedSlots = computed(() => {
const resultSlots: string[] = [];
for (const key of Object.keys(slots)) {
if (!['empty', 'form', 'loading'].includes(key)) {
resultSlots.push(key);
}
}
return resultSlots;
});
const delegatedFormSlots = computed(() => {
const resultSlots: string[] = [];
for (const key of Object.keys(slots)) {
if (key.startsWith('form-')) {
resultSlots.push(key);
}
}
return resultSlots;
});
async function init() {
await nextTick();
const globalGridConfig = VxeUI?.getConfig()?.grid ?? {};
const defaultGridOptions: VxeTableGridProps = mergeWithArrayOverride(
{},
toRaw(gridOptions.value),
toRaw(globalGridConfig),
);
// form
const autoLoad = defaultGridOptions.proxyConfig?.autoLoad;
const enableProxyConfig = options.value.proxyConfig?.enabled;
if (enableProxyConfig && autoLoad) {
props.api.reload(formApi.form.values);
}
// form vben-formformConfig
const formConfig = defaultGridOptions.formConfig;
if (formConfig?.enabled) {
console.warn(
'[Vben Vxe Table]: The formConfig in the grid is not supported, please use the `formOptions` props',
);
}
}
onMounted(() => {
props.api?.mount?.(gridRef.value);
init();
});
</script>
<template>
<div :class="cn('bg-card h-full rounded-md', className)">
<VxeGrid
ref="gridRef"
:class="
cn(
'p-2',
{
'pt-0': showToolbar && !formOptions,
},
gridClass,
)
"
v-bind="options"
v-on="events"
>
<template
v-for="slotName in delegatedSlots"
:key="slotName"
#[slotName]="slotProps"
>
<slot :name="slotName" v-bind="slotProps"></slot>
</template>
<template #form>
<div v-if="formOptions" class="relative rounded py-3 pb-6">
<slot name="form">
<Form v-bind="vbenFormOptions">
<template
v-for="slotName in delegatedFormSlots"
:key="slotName"
#[slotName]="slotProps"
>
<slot :name="slotName" v-bind="slotProps"></slot>
</template>
</Form>
</slot>
<div
class="bg-background-deep z-100 absolute -left-2 bottom-2 h-4 w-[calc(100%+1rem)] overflow-hidden"
></div>
</div>
</template>
<template #loading>
<slot name="loading">
<VbenLoading :spinning="true" />
</slot>
</template>
<template #empty>
<slot name="empty">
<EmptyIcon class="mx-auto" />
<div class="mt-2">{{ $t('common.noData') }}</div>
</slot>
</template>
</VxeGrid>
</div>
</template>

View File

@ -0,0 +1 @@
export { default } from '@vben/tailwind-config';

View File

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

View File

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

View File

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

View File

@ -31,7 +31,8 @@
"confirm": "Comfirm",
"noData": "No Data",
"refresh": "Refresh",
"loadingMenu": "Loading Menu"
"loadingMenu": "Loading Menu",
"query": "Search"
},
"fallback": {
"pageNotFound": "Oops! Page Not Found",
@ -58,6 +59,10 @@
"required": "Please enter {0}",
"selectRequired": "Please select {0}"
},
"placeholder": {
"input": "Please enter",
"select": "Please select"
},
"widgets": {
"document": "Document",
"qa": "Q&A",

View File

@ -31,7 +31,8 @@
"confirm": "确认",
"noData": "暂无数据",
"refresh": "刷新",
"loadingMenu": "加载菜单中"
"loadingMenu": "加载菜单中",
"query": "查询"
},
"fallback": {
"pageNotFound": "哎呀!未找到页面",
@ -58,6 +59,10 @@
"required": "请输入{0}",
"selectRequired": "请选择{0}"
},
"placeholder": {
"input": "请输入",
"select": "请选择"
},
"widgets": {
"document": "文档",
"qa": "问题 & 帮助",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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