dev-v5
xingyu4j 2024-12-16 10:32:21 +08:00
commit 8463f5e51f
93 changed files with 626 additions and 175 deletions

8
.vscode/launch.json vendored
View File

@ -9,7 +9,7 @@
"url": "http://localhost:5555", "url": "http://localhost:5555",
"env": { "NODE_ENV": "development" }, "env": { "NODE_ENV": "development" },
"sourceMaps": true, "sourceMaps": true,
"webRoot": "${workspaceFolder}" "webRoot": "${workspaceFolder}/playground"
}, },
{ {
"type": "chrome", "type": "chrome",
@ -18,7 +18,7 @@
"url": "http://localhost:5666", "url": "http://localhost:5666",
"env": { "NODE_ENV": "development" }, "env": { "NODE_ENV": "development" },
"sourceMaps": true, "sourceMaps": true,
"webRoot": "${workspaceFolder}" "webRoot": "${workspaceFolder}/apps/web-antd"
}, },
{ {
"type": "chrome", "type": "chrome",
@ -27,7 +27,7 @@
"url": "http://localhost:5777", "url": "http://localhost:5777",
"env": { "NODE_ENV": "development" }, "env": { "NODE_ENV": "development" },
"sourceMaps": true, "sourceMaps": true,
"webRoot": "${workspaceFolder}" "webRoot": "${workspaceFolder}/apps/web-ele"
}, },
{ {
"type": "chrome", "type": "chrome",
@ -36,7 +36,7 @@
"url": "http://localhost:5888", "url": "http://localhost:5888",
"env": { "NODE_ENV": "development" }, "env": { "NODE_ENV": "development" },
"sourceMaps": true, "sourceMaps": true,
"webRoot": "${workspaceFolder}" "webRoot": "${workspaceFolder}/apps/web-naive"
} }
] ]
} }

View File

@ -43,6 +43,31 @@ export default eventHandler(async (event) => {
await sleep(600); await sleep(600);
const { page, pageSize } = getQuery(event); const { page, pageSize, sortBy, sortOrder } = getQuery(event);
return usePageResponseSuccess(page as string, pageSize as string, mockData); const listData = structuredClone(mockData);
if (sortBy && Reflect.has(listData[0], sortBy as string)) {
listData.sort((a, b) => {
if (sortOrder === 'asc') {
if (sortBy === 'price') {
return (
Number.parseFloat(a[sortBy as string]) -
Number.parseFloat(b[sortBy as string])
);
} else {
return a[sortBy as string] > b[sortBy as string] ? 1 : -1;
}
} else {
if (sortBy === 'price') {
return (
Number.parseFloat(b[sortBy as string]) -
Number.parseFloat(a[sortBy as string])
);
} else {
return a[sortBy as string] < b[sortBy as string] ? 1 : -1;
}
}
});
}
return usePageResponseSuccess(page as string, pageSize as string, listData);
}); });

View File

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

View File

@ -10,6 +10,7 @@ import { $t } from '@vben/locales';
defineOptions({ name: 'CodeLogin' }); defineOptions({ name: 'CodeLogin' });
const loading = ref(false); const loading = ref(false);
const CODE_LENGTH = 6;
const formSchema = computed((): VbenFormSchema[] => { const formSchema = computed((): VbenFormSchema[] => {
return [ return [
@ -30,6 +31,7 @@ const formSchema = computed((): VbenFormSchema[] => {
{ {
component: 'VbenPinInput', component: 'VbenPinInput',
componentProps: { componentProps: {
codeLength: CODE_LENGTH,
createText: (countdown: number) => { createText: (countdown: number) => {
const text = const text =
countdown > 0 countdown > 0
@ -41,7 +43,9 @@ const formSchema = computed((): VbenFormSchema[] => {
}, },
fieldName: 'code', fieldName: 'code',
label: $t('authentication.code'), label: $t('authentication.code'),
rules: z.string().min(1, { message: $t('authentication.codeTip') }), rules: z.string().length(CODE_LENGTH, {
message: $t('authentication.codeTip', [CODE_LENGTH]),
}),
}, },
]; ];
}); });

View File

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

View File

@ -10,6 +10,7 @@ import { $t } from '@vben/locales';
defineOptions({ name: 'CodeLogin' }); defineOptions({ name: 'CodeLogin' });
const loading = ref(false); const loading = ref(false);
const CODE_LENGTH = 6;
const formSchema = computed((): VbenFormSchema[] => { const formSchema = computed((): VbenFormSchema[] => {
return [ return [
@ -30,6 +31,7 @@ const formSchema = computed((): VbenFormSchema[] => {
{ {
component: 'VbenPinInput', component: 'VbenPinInput',
componentProps: { componentProps: {
codeLength: CODE_LENGTH,
createText: (countdown: number) => { createText: (countdown: number) => {
const text = const text =
countdown > 0 countdown > 0
@ -41,7 +43,9 @@ const formSchema = computed((): VbenFormSchema[] => {
}, },
fieldName: 'code', fieldName: 'code',
label: $t('authentication.code'), label: $t('authentication.code'),
rules: z.string().min(1, { message: $t('authentication.codeTip') }), rules: z.string().length(CODE_LENGTH, {
message: $t('authentication.codeTip', [CODE_LENGTH]),
}),
}, },
]; ];
}); });

View File

@ -139,6 +139,7 @@ const [Form, formApi] = useVbenForm({
fieldName: 'select', fieldName: 'select',
label: 'Select', label: 'Select',
componentProps: { componentProps: {
filterable: true,
options: [ options: [
{ value: 'A', label: '选项A' }, { value: 'A', label: '选项A' },
{ value: 'B', label: '选项B' }, { value: 'B', label: '选项B' },

View File

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

View File

@ -10,8 +10,6 @@ import { $t } from '@vben/locales';
setupVbenForm<ComponentType>({ setupVbenForm<ComponentType>({
config: { config: {
// naive-ui组件不接受onChang事件所以需要禁用
disabledOnChangeListener: true,
// naive-ui组件的空值为null,不能是undefined否则重置表单时不生效 // naive-ui组件的空值为null,不能是undefined否则重置表单时不生效
emptyStateValue: null, emptyStateValue: null,
baseModelPropName: 'value', baseModelPropName: 'value',

View File

@ -10,6 +10,7 @@ import { $t } from '@vben/locales';
defineOptions({ name: 'CodeLogin' }); defineOptions({ name: 'CodeLogin' });
const loading = ref(false); const loading = ref(false);
const CODE_LENGTH = 6;
const formSchema = computed((): VbenFormSchema[] => { const formSchema = computed((): VbenFormSchema[] => {
return [ return [
@ -30,6 +31,7 @@ const formSchema = computed((): VbenFormSchema[] => {
{ {
component: 'VbenPinInput', component: 'VbenPinInput',
componentProps: { componentProps: {
codeLength: CODE_LENGTH,
createText: (countdown: number) => { createText: (countdown: number) => {
const text = const text =
countdown > 0 countdown > 0
@ -41,7 +43,9 @@ const formSchema = computed((): VbenFormSchema[] => {
}, },
fieldName: 'code', fieldName: 'code',
label: $t('authentication.code'), label: $t('authentication.code'),
rules: z.string().min(1, { message: $t('authentication.codeTip') }), rules: z.string().length(CODE_LENGTH, {
message: $t('authentication.codeTip', [CODE_LENGTH]),
}),
}, },
]; ];
}); });

View File

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

View File

@ -14,8 +14,6 @@ initComponentAdapter();
setupVbenForm<ComponentType>({ setupVbenForm<ComponentType>({
config: { config: {
baseModelPropName: 'value', baseModelPropName: 'value',
// naive-ui组件不接受onChang事件所以需要禁用
disabledOnChangeListener: true,
// naive-ui组件的空值为null,不能是undefined否则重置表单时不生效 // naive-ui组件的空值为null,不能是undefined否则重置表单时不生效
emptyStateValue: null, emptyStateValue: null,
modelPropNameMap: { modelPropNameMap: {

View File

@ -165,6 +165,8 @@ vxeUI.renderer.add('CellLink', {
**表单搜索** 部分采用了`Vben Form 表单`,参考 [Vben Form 表单文档](/components/common-ui/vben-form)。 **表单搜索** 部分采用了`Vben Form 表单`,参考 [Vben Form 表单文档](/components/common-ui/vben-form)。
当启用了表单搜索时可以在toolbarConfig中配置`search`为`true`来让表格在工具栏区域显示一个搜索表单控制按钮。
<DemoPreview dir="demos/vben-vxe-table/form" /> <DemoPreview dir="demos/vben-vxe-table/form" />
## 单元格编辑 ## 单元格编辑
@ -215,14 +217,15 @@ const [Grid, gridApi] = useVbenVxeGrid({
useVbenVxeGrid 返回的第二个参数,是一个对象,包含了一些表单的方法。 useVbenVxeGrid 返回的第二个参数,是一个对象,包含了一些表单的方法。
| 方法名 | 描述 | 类型 | | 方法名 | 描述 | 类型 | 说明 |
| --- | --- | --- | | --- | --- | --- | --- |
| setLoading | 设置loading状态 | `(loading)=>void` | | setLoading | 设置loading状态 | `(loading)=>void` | - |
| setGridOptions | 设置vxe-table grid组件参数 | `(options: Partial<VxeGridProps['gridOptions'])=>void` | | setGridOptions | 设置vxe-table grid组件参数 | `(options: Partial<VxeGridProps['gridOptions'])=>void` | - |
| reload | 重载表格,会进行初始化 | `(params:any)=>void` | | reload | 重载表格,会进行初始化 | `(params:any)=>void` | - |
| query | 重载表格,会保留当前分页 | `(params:any)=>void` | | query | 重载表格,会保留当前分页 | `(params:any)=>void` | - |
| grid | vxe-table grid实例 | `VxeGridInstance` | | grid | vxe-table grid实例 | `VxeGridInstance` | - |
| formApi | vbenForm api实例 | `FormApi` | | formApi | vbenForm api实例 | `FormApi` | - |
| toggleSearchForm | 设置搜索表单显示状态 | `(show?: boolean)=>boolean` | 当省略参数时,则将表单在显示和隐藏两种状态之间切换 |
## Props ## Props
@ -236,3 +239,4 @@ useVbenVxeGrid 返回的第二个参数,是一个对象,包含了一些表
| gridOptions | grid组件的参数 | `VxeTableGridProps` | | gridOptions | grid组件的参数 | `VxeTableGridProps` |
| gridEvents | grid组件的触发的⌚ | `VxeGridListeners` | | gridEvents | grid组件的触发的⌚ | `VxeGridListeners` |
| formOptions | 表单参数 | `VbenFormProps` | | formOptions | 表单参数 | `VbenFormProps` |
| showSearchForm | 是否显示搜索表单 | `boolean` |

View File

@ -110,6 +110,11 @@ const gridOptions: VxeGridProps<RowType> = {
}, },
}, },
}, },
toolbarConfig: {
//
// @ts-ignore
search: true,
},
}; };
const [Grid] = useVbenVxeGrid({ formOptions, gridOptions }); const [Grid] = useVbenVxeGrid({ formOptions, gridOptions });

View File

@ -217,6 +217,7 @@ const defaultPreferences: Preferences = {
globalSearch: true, globalSearch: true,
}, },
sidebar: { sidebar: {
autoActivateChild: false,
collapsed: false, collapsed: false,
collapsedShowTitle: false, collapsedShowTitle: false,
enable: true, enable: true,

View File

@ -240,6 +240,7 @@ const defaultPreferences: Preferences = {
globalSearch: true, globalSearch: true,
}, },
sidebar: { sidebar: {
autoActivateChild: false,
collapsed: false, collapsed: false,
collapsedShowTitle: false, collapsedShowTitle: false,
enable: true, enable: true,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
{ {
"name": "vben-admin-monorepo", "name": "vben-admin-monorepo",
"version": "5.5.0", "version": "5.5.1",
"private": true, "private": true,
"keywords": [ "keywords": [
"monorepo", "monorepo",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -65,6 +65,7 @@ exports[`defaultPreferences immutability test > should not modify the config obj
"globalSearch": true, "globalSearch": true,
}, },
"sidebar": { "sidebar": {
"autoActivateChild": false,
"collapsed": false, "collapsed": false,
"collapsedShowTitle": false, "collapsedShowTitle": false,
"enable": true, "enable": true,
@ -83,6 +84,7 @@ exports[`defaultPreferences immutability test > should not modify the config obj
"showMaximize": true, "showMaximize": true,
"showMore": true, "showMore": true,
"styleType": "chrome", "styleType": "chrome",
"wheelable": true,
}, },
"theme": { "theme": {
"builtinType": "default", "builtinType": "default",

View File

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

View File

@ -65,6 +65,7 @@ const defaultPreferences: Preferences = {
globalSearch: true, globalSearch: true,
}, },
sidebar: { sidebar: {
autoActivateChild: false,
collapsed: false, collapsed: false,
collapsedShowTitle: false, collapsedShowTitle: false,
enable: true, enable: true,
@ -83,6 +84,7 @@ const defaultPreferences: Preferences = {
showMaximize: true, showMaximize: true,
showMore: true, showMore: true,
styleType: 'chrome', styleType: 'chrome',
wheelable: true,
}, },
theme: { theme: {
builtinType: 'default', builtinType: 'default',

View File

@ -125,6 +125,8 @@ interface NavigationPreferences {
} }
interface SidebarPreferences { interface SidebarPreferences {
/** 点击目录时自动激活子菜单 */
autoActivateChild: boolean;
/** 侧边栏是否折叠 */ /** 侧边栏是否折叠 */
collapsed: boolean; collapsed: boolean;
/** 侧边栏折叠时是否显示title */ /** 侧边栏折叠时是否显示title */
@ -173,6 +175,8 @@ interface TabbarPreferences {
showMore: boolean; showMore: boolean;
/** 标签页风格 */ /** 标签页风格 */
styleType: TabsStyleType; styleType: TabsStyleType;
/** 是否开启鼠标滚轮响应 */
wheelable: boolean;
} }
interface ThemePreferences { interface ThemePreferences {

View File

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

View File

@ -44,11 +44,15 @@ export function setupVbenForm<
>(options: VbenFormAdapterOptions<T>) { >(options: VbenFormAdapterOptions<T>) {
const { config, defineRules } = options; const { config, defineRules } = options;
const { disabledOnChangeListener = false, emptyStateValue = undefined } = const {
(config || {}) as FormCommonConfig; disabledOnChangeListener = true,
disabledOnInputListener = true,
emptyStateValue = undefined,
} = (config || {}) as FormCommonConfig;
Object.assign(DEFAULT_FORM_COMMON_CONFIG, { Object.assign(DEFAULT_FORM_COMMON_CONFIG, {
disabledOnChangeListener, disabledOnChangeListener,
disabledOnInputListener,
emptyStateValue, emptyStateValue,
}); });

View File

@ -34,6 +34,7 @@ const {
description, description,
disabled, disabled,
disabledOnChangeListener, disabledOnChangeListener,
disabledOnInputListener,
emptyStateValue, emptyStateValue,
fieldName, fieldName,
formFieldProps, formFieldProps,
@ -236,10 +237,13 @@ function fieldBindEvent(slotProps: Record<string, any>) {
return onChange?.(e?.target?.[bindEventField] ?? e); return onChange?.(e?.target?.[bindEventField] ?? e);
}, },
onInput: () => {}, ...(disabledOnInputListener ? { onInput: undefined } : {}),
}; };
} }
return {}; return {
...(disabledOnInputListener ? { onInput: undefined } : {}),
...(disabledOnChangeListener ? { onChange: undefined } : {}),
};
} }
function createComponentProps(slotProps: Record<string, any>) { function createComponentProps(slotProps: Record<string, any>) {

View File

@ -89,7 +89,8 @@ const computedSchema = computed(
componentProps = {}, componentProps = {},
controlClass = '', controlClass = '',
disabled, disabled,
disabledOnChangeListener = false, disabledOnChangeListener = true,
disabledOnInputListener = true,
emptyStateValue = undefined, emptyStateValue = undefined,
formFieldProps = {}, formFieldProps = {},
formItemClass = '', formItemClass = '',
@ -111,6 +112,7 @@ const computedSchema = computed(
return { return {
disabled, disabled,
disabledOnChangeListener, disabledOnChangeListener,
disabledOnInputListener,
emptyStateValue, emptyStateValue,
hideLabel, hideLabel,
hideRequiredMark, hideRequiredMark,

View File

@ -160,9 +160,14 @@ export interface FormCommonConfig {
disabled?: boolean; disabled?: boolean;
/** /**
* change * change
* @default false * @default true
*/ */
disabledOnChangeListener?: boolean; disabledOnChangeListener?: boolean;
/**
* input
* @default true
*/
disabledOnInputListener?: boolean;
/** /**
* ,undefinednaive-uinull * ,undefinednaive-uinull
*/ */
@ -382,6 +387,7 @@ export interface VbenFormAdapterOptions<
config?: { config?: {
baseModelPropName?: string; baseModelPropName?: string;
disabledOnChangeListener?: boolean; disabledOnChangeListener?: boolean;
disabledOnInputListener?: boolean;
emptyStateValue?: null | undefined; emptyStateValue?: null | undefined;
modelPropNameMap?: Partial<Record<T, string>>; modelPropNameMap?: Partial<Record<T, string>>;
}; };

View File

@ -6,7 +6,9 @@ import type { ExtendedFormApi, VbenFormProps } from './types';
import { useForwardPriorityValues } from '@vben-core/composables'; import { useForwardPriorityValues } from '@vben-core/composables';
// import { isFunction } from '@vben-core/shared/utils'; // import { isFunction } from '@vben-core/shared/utils';
import { toRaw, useTemplateRef, watch } from 'vue'; import { nextTick, onMounted, useTemplateRef, watch } from 'vue';
import { cloneDeep } from '@vben-core/shared/utils';
import { useDebounceFn } from '@vueuse/core'; import { useDebounceFn } from '@vueuse/core';
@ -59,14 +61,16 @@ function handleKeyDownEnter(event: KeyboardEvent) {
formActionsRef.value?.handleSubmit?.(); formActionsRef.value?.handleSubmit?.();
} }
watch( const handleValuesChangeDebounced = useDebounceFn((newVal) => {
() => form.values, forward.value.handleValuesChange?.(cloneDeep(newVal));
useDebounceFn(() => { state.value.submitOnChange && formActionsRef.value?.handleSubmit?.();
forward.value.handleValuesChange?.(toRaw(form.values)); }, 300);
state.value.submitOnChange && props.formApi?.submitForm();
}, 300), onMounted(async () => {
{ deep: true }, // form.values
); await nextTick();
watch(() => form.values, handleValuesChangeDebounced, { deep: true });
});
</script> </script>
<template> <template>

View File

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

View File

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

View File

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

View File

@ -47,6 +47,10 @@ watch(
}, },
); );
watch(inputValue, (val) => {
modelValue.value = val.join('');
});
function handleComplete(e: string[]) { function handleComplete(e: string[]) {
modelValue.value = e.join(''); modelValue.value = e.join('');
emit('complete'); emit('complete');

View File

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

View File

@ -19,6 +19,7 @@ const props = withDefaults(defineProps<Props>(), {
contentClass: 'vben-tabs-content', contentClass: 'vben-tabs-content',
draggable: true, draggable: true,
styleType: 'chrome', styleType: 'chrome',
wheelable: true,
}); });
const emit = defineEmits<TabsEmits>(); const emit = defineEmits<TabsEmits>();
@ -27,6 +28,7 @@ const forward = useForwardPropsEmits(props, emit);
const { const {
handleScrollAt, handleScrollAt,
handleWheel,
scrollbarRef, scrollbarRef,
scrollDirection, scrollDirection,
scrollIsAtLeft, scrollIsAtLeft,
@ -34,6 +36,14 @@ const {
showScrollButton, showScrollButton,
} = useTabsViewScroll(props); } = useTabsViewScroll(props);
function onWheel(e: WheelEvent) {
if (props.wheelable) {
handleWheel(e);
e.stopPropagation();
e.preventDefault();
}
}
useTabsDrag(props, emit); useTabsDrag(props, emit);
</script> </script>
@ -69,6 +79,7 @@ useTabsDrag(props, emit);
shadow-left shadow-left
shadow-right shadow-right
@scroll-at="handleScrollAt" @scroll-at="handleScrollAt"
@wheel="onWheel"
> >
<TabsChrome <TabsChrome
v-if="styleType === 'chrome'" v-if="styleType === 'chrome'"

View File

@ -33,7 +33,6 @@ export interface TabsProps {
* tabs-chrome * tabs-chrome
*/ */
maxWidth?: number; maxWidth?: number;
/** /**
* @zh_CN tab * @zh_CN tab
* tabs-chrome * tabs-chrome
@ -44,15 +43,20 @@ export interface TabsProps {
* @zh_CN * @zh_CN
*/ */
showIcon?: boolean; showIcon?: boolean;
/** /**
* @zh_CN * @zh_CN
*/ */
styleType?: TabsStyleType; styleType?: TabsStyleType;
/** /**
* @zh_CN * @zh_CN
*/ */
tabs?: TabDefinition[]; tabs?: TabDefinition[];
/**
* @zh_CN
*/
wheelable?: boolean;
} }
export interface TabConfig extends TabDefinition { export interface TabConfig extends TabDefinition {

View File

@ -142,6 +142,13 @@ export function useTabsViewScroll(props: TabsProps) {
scrollIsAtRight.value = right; scrollIsAtRight.value = right;
}, 100); }, 100);
function handleWheel({ deltaY }: WheelEvent) {
scrollViewportEl.value?.scrollBy({
behavior: 'smooth',
left: deltaY * 3,
});
}
watch( watch(
() => props.active, () => props.active,
async () => { async () => {
@ -184,6 +191,7 @@ export function useTabsViewScroll(props: TabsProps) {
return { return {
handleScrollAt, handleScrollAt,
handleWheel,
initScrollbar, initScrollbar,
scrollbarRef, scrollbarRef,
scrollDirection, scrollDirection,

View File

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

View File

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

View File

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

View File

@ -1,21 +1,23 @@
<script setup lang="ts"> <script setup lang="ts">
import type { AuthenticationProps } from './types'; import type { AuthenticationProps } from './types';
import { watch } from 'vue'; import { computed, watch } from 'vue';
import { useVbenModal } from '@vben-core/popup-ui'; import { useVbenModal } from '@vben-core/popup-ui';
import { Slot, VbenAvatar } from '@vben-core/shadcn-ui'; import { Slot, VbenAvatar } from '@vben-core/shadcn-ui';
interface Props extends AuthenticationProps { interface Props extends AuthenticationProps {
avatar?: string; avatar?: string;
zIndex?: number;
} }
defineOptions({ defineOptions({
name: 'LoginExpiredModal', name: 'LoginExpiredModal',
}); });
withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
avatar: '', avatar: '',
zIndex: 0,
}); });
const open = defineModel<boolean>('open'); const open = defineModel<boolean>('open');
@ -28,6 +30,26 @@ watch(
modalApi.setState({ isOpen: val }); modalApi.setState({ isOpen: val });
}, },
); );
const getZIndex = computed(() => {
return props.zIndex || calcZIndex();
});
/**
* 获取最大的zIndex值
*/
function calcZIndex() {
let maxZ = 0;
const elements = document.querySelectorAll('*');
[...elements].forEach((element) => {
const style = window.getComputedStyle(element);
const zIndex = style.getPropertyValue('z-index');
if (zIndex && !Number.isNaN(Number.parseInt(zIndex))) {
maxZ = Math.max(maxZ, Number.parseInt(zIndex));
}
});
return maxZ + 1;
}
</script> </script>
<template> <template>
@ -39,6 +61,7 @@ watch(
:footer="false" :footer="false"
:fullscreen-button="false" :fullscreen-button="false"
:header="false" :header="false"
:z-index="getZIndex"
class="border-none px-10 py-6 text-center shadow-xl sm:w-[600px] sm:rounded-2xl md:h-[unset]" class="border-none px-10 py-6 text-center shadow-xl sm:w-[600px] sm:rounded-2xl md:h-[unset]"
> >
<VbenAvatar :src="avatar" class="mx-auto mb-6 size-20" /> <VbenAvatar :src="avatar" class="mx-auto mb-6 size-20" />

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben/hooks", "name": "@vben/hooks",
"version": "5.5.0", "version": "5.5.1",
"homepage": "https://github.com/vbenjs/vue-vben-admin", "homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": { "repository": {
@ -25,6 +25,7 @@
"@vben/stores": "workspace:*", "@vben/stores": "workspace:*",
"@vben/types": "workspace:*", "@vben/types": "workspace:*",
"@vben/utils": "workspace:*", "@vben/utils": "workspace:*",
"@vueuse/core": "catalog:",
"vue": "catalog:", "vue": "catalog:",
"vue-router": "catalog:", "vue-router": "catalog:",
"watermark-js-plus": "catalog:" "watermark-js-plus": "catalog:"

View File

@ -1,6 +1,7 @@
export * from './use-app-config'; export * from './use-app-config';
export * from './use-content-maximize'; export * from './use-content-maximize';
export * from './use-design-tokens'; export * from './use-design-tokens';
export * from './use-hover-toggle';
export * from './use-pagination'; export * from './use-pagination';
export * from './use-refresh'; export * from './use-refresh';
export * from './use-tabs'; export * from './use-tabs';

View File

@ -0,0 +1,63 @@
import type { Arrayable, MaybeElementRef } from '@vueuse/core';
import { computed, onUnmounted, ref, watch } from 'vue';
import type { Ref } from 'vue';
import { isFunction } from '@vben/utils';
import { useMouseInElement } from '@vueuse/core';
/**
* true false
* @param refElement true
* @param delay
* @returns ref enable disable
*/
export function useHoverToggle(
refElement: Arrayable<MaybeElementRef>,
delay: (() => number) | number = 500,
) {
const isOutsides: Array<Ref<boolean>> = [];
const value = ref(false);
const timer = ref<ReturnType<typeof setTimeout> | undefined>();
const refs = Array.isArray(refElement) ? refElement : [refElement];
refs.forEach((refEle) => {
const listener = useMouseInElement(refEle, { handleOutside: true });
isOutsides.push(listener.isOutside);
});
const isOutsideAll = computed(() => isOutsides.every((v) => v.value));
function setValueDelay(val: boolean) {
timer.value && clearTimeout(timer.value);
timer.value = setTimeout(
() => {
value.value = val;
timer.value = undefined;
},
isFunction(delay) ? delay() : delay,
);
}
const watcher = watch(
isOutsideAll,
(val) => {
setValueDelay(!val);
},
{ immediate: true },
);
const controller = {
enable() {
watcher.resume();
},
disable() {
watcher.pause();
},
};
onUnmounted(() => {
timer.value && clearTimeout(timer.value);
});
return [value, controller] as [typeof value, typeof controller];
}

View File

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

View File

@ -103,6 +103,7 @@ const {
const { const {
handleMenuSelect, handleMenuSelect,
handleMenuOpen,
headerActive, headerActive,
headerMenus, headerMenus,
sidebarActive, sidebarActive,
@ -260,6 +261,7 @@ const headerSlots = computed(() => {
:rounded="isMenuRounded" :rounded="isMenuRounded"
:theme="sidebarTheme" :theme="sidebarTheme"
mode="vertical" mode="vertical"
@open="handleMenuOpen"
@select="handleMenuSelect" @select="handleMenuSelect"
/> />
</template> </template>

View File

@ -14,12 +14,17 @@ const props = withDefaults(defineProps<Props>(), {
}); });
const emit = defineEmits<{ const emit = defineEmits<{
open: [string, string[]];
select: [string, string?]; select: [string, string?];
}>(); }>();
function handleMenuSelect(key: string) { function handleMenuSelect(key: string) {
emit('select', key, props.mode); emit('select', key, props.mode);
} }
function handleMenuOpen(key: string, path: string[]) {
emit('open', key, path);
}
</script> </script>
<template> <template>
@ -32,6 +37,7 @@ function handleMenuSelect(key: string) {
:mode="mode" :mode="mode"
:rounded="rounded" :rounded="rounded"
:theme="theme" :theme="theme"
@open="handleMenuOpen"
@select="handleMenuSelect" @select="handleMenuSelect"
/> />
</template> </template>

View File

@ -15,6 +15,9 @@ function useExtraMenu() {
const menus = computed(() => accessStore.accessMenus); const menus = computed(() => accessStore.accessMenus);
/** 记录当前顶级菜单下哪个子菜单最后激活 */
const defaultSubMap = new Map<string, string>();
const route = useRoute(); const route = useRoute();
const extraMenus = ref<MenuRecordRaw[]>([]); const extraMenus = ref<MenuRecordRaw[]>([]);
const sidebarExtraVisible = ref<boolean>(false); const sidebarExtraVisible = ref<boolean>(false);
@ -32,6 +35,12 @@ function useExtraMenu() {
sidebarExtraVisible.value = hasChildren; sidebarExtraVisible.value = hasChildren;
if (!hasChildren) { if (!hasChildren) {
await navigation(menu.path); await navigation(menu.path);
} else if (preferences.sidebar.autoActivateChild) {
await navigation(
defaultSubMap.has(menu.path)
? (defaultSubMap.get(menu.path) as string)
: menu.path,
);
} }
}; };
@ -89,6 +98,7 @@ function useExtraMenu() {
menus.value, menus.value,
currentPath, currentPath,
); );
if (rootMenuPath) defaultSubMap.set(rootMenuPath, currentPath);
extraActiveMenu.value = rootMenuPath ?? findMenu?.path ?? ''; extraActiveMenu.value = rootMenuPath ?? findMenu?.path ?? '';
extraMenus.value = rootMenu?.children ?? []; extraMenus.value = rootMenu?.children ?? [];
}, },

View File

@ -15,7 +15,8 @@ function useMixedMenu() {
const route = useRoute(); const route = useRoute();
const splitSideMenus = ref<MenuRecordRaw[]>([]); const splitSideMenus = ref<MenuRecordRaw[]>([]);
const rootMenuPath = ref<string>(''); const rootMenuPath = ref<string>('');
/** 记录当前顶级菜单下哪个子菜单最后激活 */
const defaultSubMap = new Map<string, string>();
const { isMixedNav } = usePreferences(); const { isMixedNav } = usePreferences();
const needSplit = computed( const needSplit = computed(
@ -86,6 +87,25 @@ function useMixedMenu() {
splitSideMenus.value = rootMenu?.children ?? []; splitSideMenus.value = rootMenu?.children ?? [];
if (splitSideMenus.value.length === 0) { if (splitSideMenus.value.length === 0) {
navigation(key); navigation(key);
} else if (rootMenu && preferences.sidebar.autoActivateChild) {
navigation(
defaultSubMap.has(rootMenu.path)
? (defaultSubMap.get(rootMenu.path) as string)
: rootMenu.path,
);
}
};
/**
*
* @param key
* @param parentsPath
*/
const handleMenuOpen = (key: string, parentsPath: string[]) => {
if (parentsPath.length <= 1 && preferences.sidebar.autoActivateChild) {
navigation(
defaultSubMap.has(key) ? (defaultSubMap.get(key) as string) : key,
);
} }
}; };
@ -107,6 +127,8 @@ function useMixedMenu() {
(path) => { (path) => {
const currentPath = (route?.meta?.activePath as string) ?? path; const currentPath = (route?.meta?.activePath as string) ?? path;
calcSideMenus(currentPath); calcSideMenus(currentPath);
if (rootMenuPath.value)
defaultSubMap.set(rootMenuPath.value, currentPath);
}, },
{ immediate: true }, { immediate: true },
); );
@ -118,6 +140,7 @@ function useMixedMenu() {
return { return {
handleMenuSelect, handleMenuSelect,
handleMenuOpen,
headerActive, headerActive,
headerMenus, headerMenus,
sidebarActive, sidebarActive,

View File

@ -55,6 +55,7 @@ if (!preferences.tabbar.persist) {
:show-icon="showIcon" :show-icon="showIcon"
:style-type="preferences.tabbar.styleType" :style-type="preferences.tabbar.styleType"
:tabs="currentTabs" :tabs="currentTabs"
:wheelable="preferences.tabbar.wheelable"
@close="handleClose" @close="handleClose"
@sort-tabs="tabbarStore.sortTabs" @sort-tabs="tabbarStore.sortTabs"
@unpin="unpinTab" @unpin="unpinTab"

View File

@ -1,17 +1,23 @@
<script setup lang="ts"> <script setup lang="ts">
import type { LayoutType } from '@vben/types';
import { $t } from '@vben/locales'; import { $t } from '@vben/locales';
import NumberFieldItem from '../number-field-item.vue'; import NumberFieldItem from '../number-field-item.vue';
import SwitchItem from '../switch-item.vue'; import SwitchItem from '../switch-item.vue';
defineProps<{ disabled: boolean }>(); defineProps<{ currentLayout?: LayoutType; disabled: boolean }>();
const sidebarEnable = defineModel<boolean>('sidebarEnable'); const sidebarEnable = defineModel<boolean>('sidebarEnable');
const sidebarWidth = defineModel<number>('sidebarWidth'); const sidebarWidth = defineModel<number>('sidebarWidth');
const sidebarCollapsedShowTitle = defineModel<boolean>( const sidebarCollapsedShowTitle = defineModel<boolean>(
'sidebarCollapsedShowTitle', 'sidebarCollapsedShowTitle',
); );
const sidebarAutoActivateChild = defineModel<boolean>(
'sidebarAutoActivateChild',
);
const sidebarCollapsed = defineModel<boolean>('sidebarCollapsed'); const sidebarCollapsed = defineModel<boolean>('sidebarCollapsed');
const sidebarExpandOnHover = defineModel<boolean>('sidebarExpandOnHover');
</script> </script>
<template> <template>
@ -21,12 +27,32 @@ const sidebarCollapsed = defineModel<boolean>('sidebarCollapsed');
<SwitchItem v-model="sidebarCollapsed" :disabled="!sidebarEnable || disabled"> <SwitchItem v-model="sidebarCollapsed" :disabled="!sidebarEnable || disabled">
{{ $t('preferences.sidebar.collapsed') }} {{ $t('preferences.sidebar.collapsed') }}
</SwitchItem> </SwitchItem>
<SwitchItem
v-model="sidebarExpandOnHover"
:disabled="!sidebarEnable || disabled || !sidebarCollapsed"
:tip="$t('preferences.sidebar.expandOnHoverTip')"
>
{{ $t('preferences.sidebar.expandOnHover') }}
</SwitchItem>
<SwitchItem <SwitchItem
v-model="sidebarCollapsedShowTitle" v-model="sidebarCollapsedShowTitle"
:disabled="!sidebarEnable || disabled || !sidebarCollapsed" :disabled="!sidebarEnable || disabled || !sidebarCollapsed"
> >
{{ $t('preferences.sidebar.collapsedShowTitle') }} {{ $t('preferences.sidebar.collapsedShowTitle') }}
</SwitchItem> </SwitchItem>
<SwitchItem
v-model="sidebarAutoActivateChild"
:disabled="
!sidebarEnable ||
!['sidebar-mixed-nav', 'mixed-nav', 'sidebar-nav'].includes(
currentLayout as string,
) ||
disabled
"
:tip="$t('preferences.sidebar.autoActivateChildTip')"
>
{{ $t('preferences.sidebar.autoActivateChild') }}
</SwitchItem>
<NumberFieldItem <NumberFieldItem
v-model="sidebarWidth" v-model="sidebarWidth"
:disabled="!sidebarEnable || disabled" :disabled="!sidebarEnable || disabled"

View File

@ -18,6 +18,7 @@ const tabbarEnable = defineModel<boolean>('tabbarEnable');
const tabbarShowIcon = defineModel<boolean>('tabbarShowIcon'); const tabbarShowIcon = defineModel<boolean>('tabbarShowIcon');
const tabbarPersist = defineModel<boolean>('tabbarPersist'); const tabbarPersist = defineModel<boolean>('tabbarPersist');
const tabbarDraggable = defineModel<boolean>('tabbarDraggable'); const tabbarDraggable = defineModel<boolean>('tabbarDraggable');
const tabbarWheelable = defineModel<boolean>('tabbarWheelable');
const tabbarStyleType = defineModel<string>('tabbarStyleType'); const tabbarStyleType = defineModel<string>('tabbarStyleType');
const tabbarShowMore = defineModel<boolean>('tabbarShowMore'); const tabbarShowMore = defineModel<boolean>('tabbarShowMore');
const tabbarShowMaximize = defineModel<boolean>('tabbarShowMaximize'); const tabbarShowMaximize = defineModel<boolean>('tabbarShowMaximize');
@ -53,6 +54,13 @@ const styleItems = computed((): SelectOption[] => [
<SwitchItem v-model="tabbarDraggable" :disabled="!tabbarEnable"> <SwitchItem v-model="tabbarDraggable" :disabled="!tabbarEnable">
{{ $t('preferences.tabbar.draggable') }} {{ $t('preferences.tabbar.draggable') }}
</SwitchItem> </SwitchItem>
<SwitchItem
v-model="tabbarWheelable"
:disabled="!tabbarEnable"
:tip="$t('preferences.tabbar.wheelableTip')"
>
{{ $t('preferences.tabbar.wheelable') }}
</SwitchItem>
<SwitchItem v-model="tabbarShowIcon" :disabled="!tabbarEnable"> <SwitchItem v-model="tabbarShowIcon" :disabled="!tabbarEnable">
{{ $t('preferences.tabbar.icon') }} {{ $t('preferences.tabbar.icon') }}
</SwitchItem> </SwitchItem>

View File

@ -8,8 +8,9 @@ defineOptions({
name: 'PreferenceSwitchItem', name: 'PreferenceSwitchItem',
}); });
withDefaults(defineProps<{ disabled?: boolean }>(), { withDefaults(defineProps<{ disabled?: boolean; tip?: string }>(), {
disabled: false, disabled: false,
tip: '',
}); });
const checked = defineModel<boolean>(); const checked = defineModel<boolean>();
@ -32,11 +33,17 @@ function handleClick() {
<span class="flex items-center text-sm"> <span class="flex items-center text-sm">
<slot></slot> <slot></slot>
<VbenTooltip v-if="slots.tip" side="bottom"> <VbenTooltip v-if="slots.tip || tip" side="bottom">
<template #trigger> <template #trigger>
<CircleHelp class="ml-1 size-3 cursor-help" /> <CircleHelp class="ml-1 size-3 cursor-help" />
</template> </template>
<slot name="tip"></slot> <slot name="tip">
<template v-if="tip">
<p v-for="(line, index) in tip.split('\n')" :key="index">
{{ line }}
</p>
</template>
</slot>
</VbenTooltip> </VbenTooltip>
</span> </span>
<span v-if="$slots.shortcut" class="ml-auto mr-2 text-xs opacity-60"> <span v-if="$slots.shortcut" class="ml-auto mr-2 text-xs opacity-60">

View File

@ -87,6 +87,10 @@ const sidebarCollapsed = defineModel<boolean>('sidebarCollapsed');
const sidebarCollapsedShowTitle = defineModel<boolean>( const sidebarCollapsedShowTitle = defineModel<boolean>(
'sidebarCollapsedShowTitle', 'sidebarCollapsedShowTitle',
); );
const sidebarAutoActivateChild = defineModel<boolean>(
'sidebarAutoActivateChild',
);
const SidebarExpandOnHover = defineModel<boolean>('sidebarExpandOnHover');
const headerEnable = defineModel<boolean>('headerEnable'); const headerEnable = defineModel<boolean>('headerEnable');
const headerMode = defineModel<LayoutHeaderModeType>('headerMode'); const headerMode = defineModel<LayoutHeaderModeType>('headerMode');
@ -105,6 +109,7 @@ const tabbarShowMore = defineModel<boolean>('tabbarShowMore');
const tabbarShowMaximize = defineModel<boolean>('tabbarShowMaximize'); const tabbarShowMaximize = defineModel<boolean>('tabbarShowMaximize');
const tabbarPersist = defineModel<boolean>('tabbarPersist'); const tabbarPersist = defineModel<boolean>('tabbarPersist');
const tabbarDraggable = defineModel<boolean>('tabbarDraggable'); const tabbarDraggable = defineModel<boolean>('tabbarDraggable');
const tabbarWheelable = defineModel<boolean>('tabbarWheelable');
const tabbarStyleType = defineModel<string>('tabbarStyleType'); const tabbarStyleType = defineModel<string>('tabbarStyleType');
const navigationStyleType = defineModel<NavigationStyleType>( const navigationStyleType = defineModel<NavigationStyleType>(
@ -298,10 +303,13 @@ async function handleReset() {
<Block :title="$t('preferences.sidebar.title')"> <Block :title="$t('preferences.sidebar.title')">
<Sidebar <Sidebar
v-model:sidebar-auto-activate-child="sidebarAutoActivateChild"
v-model:sidebar-collapsed="sidebarCollapsed" v-model:sidebar-collapsed="sidebarCollapsed"
v-model:sidebar-collapsed-show-title="sidebarCollapsedShowTitle" v-model:sidebar-collapsed-show-title="sidebarCollapsedShowTitle"
v-model:sidebar-enable="sidebarEnable" v-model:sidebar-enable="sidebarEnable"
v-model:sidebar-expand-on-hover="SidebarExpandOnHover"
v-model:sidebar-width="sidebarWidth" v-model:sidebar-width="sidebarWidth"
:current-layout="appLayout"
:disabled="!isSideMode" :disabled="!isSideMode"
/> />
</Block> </Block>
@ -345,6 +353,7 @@ async function handleReset() {
v-model:tabbar-show-maximize="tabbarShowMaximize" v-model:tabbar-show-maximize="tabbarShowMaximize"
v-model:tabbar-show-more="tabbarShowMore" v-model:tabbar-show-more="tabbarShowMore"
v-model:tabbar-style-type="tabbarStyleType" v-model:tabbar-style-type="tabbarStyleType"
v-model:tabbar-wheelable="tabbarWheelable"
/> />
</Block> </Block>
<Block :title="$t('preferences.widget.title')"> <Block :title="$t('preferences.widget.title')">

View File

@ -2,8 +2,9 @@
import type { AnyFunction } from '@vben/types'; import type { AnyFunction } from '@vben/types';
import type { Component } from 'vue'; import type { Component } from 'vue';
import { computed, ref } from 'vue'; import { computed, useTemplateRef, watch } from 'vue';
import { useHoverToggle } from '@vben/hooks';
import { LockKeyhole, LogOut } from '@vben/icons'; import { LockKeyhole, LogOut } from '@vben/icons';
import { $t } from '@vben/locales'; import { $t } from '@vben/locales';
import { preferences, usePreferences } from '@vben/preferences'; import { preferences, usePreferences } from '@vben/preferences';
@ -53,6 +54,10 @@ interface Props {
* 文本 * 文本
*/ */
text?: string; text?: string;
/** 触发方式 */
trigger?: 'both' | 'click' | 'hover';
/** hover触发时延迟响应的时间 */
hoverDelay?: number;
} }
defineOptions({ defineOptions({
@ -67,10 +72,11 @@ const props = withDefaults(defineProps<Props>(), {
showShortcutKey: true, showShortcutKey: true,
tagText: '', tagText: '',
text: '', text: '',
trigger: 'click',
hoverDelay: 500,
}); });
const emit = defineEmits<{ logout: [] }>(); const emit = defineEmits<{ logout: [] }>();
const openPopover = ref(false);
const { globalLockScreenShortcutKey, globalLogoutShortcutKey } = const { globalLockScreenShortcutKey, globalLogoutShortcutKey } =
usePreferences(); usePreferences();
@ -84,6 +90,27 @@ const [LogoutModal, logoutModalApi] = useVbenModal({
}, },
}); });
const refTrigger = useTemplateRef('refTrigger');
const refContent = useTemplateRef('refContent');
const [openPopover, hoverWatcher] = useHoverToggle(
[refTrigger, refContent],
() => props.hoverDelay,
);
watch(
() => props.trigger === 'hover' || props.trigger === 'both',
(val) => {
if (val) {
hoverWatcher.enable();
} else {
hoverWatcher.disable();
}
},
{
immediate: true,
},
);
const altView = computed(() => (isWindowsOs() ? 'Alt' : '⌥')); const altView = computed(() => (isWindowsOs() ? 'Alt' : '⌥'));
const enableLogoutShortcutKey = computed(() => { const enableLogoutShortcutKey = computed(() => {
@ -155,8 +182,8 @@ if (enableShortcutKey.value) {
{{ $t('ui.widgets.logoutTip') }} {{ $t('ui.widgets.logoutTip') }}
</LogoutModal> </LogoutModal>
<DropdownMenu> <DropdownMenu v-model:open="openPopover">
<DropdownMenuTrigger> <DropdownMenuTrigger ref="refTrigger" :disabled="props.trigger === 'hover'">
<div class="hover:bg-accent ml-1 mr-2 cursor-pointer rounded-full p-1.5"> <div class="hover:bg-accent ml-1 mr-2 cursor-pointer rounded-full p-1.5">
<div class="hover:text-accent-foreground flex-center"> <div class="hover:text-accent-foreground flex-center">
<VbenAvatar :alt="text" :src="avatar" class="size-8" dot /> <VbenAvatar :alt="text" :src="avatar" class="size-8" dot />
@ -164,64 +191,66 @@ if (enableShortcutKey.value) {
</div> </div>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent class="mr-2 min-w-[240px] p-0 pb-1"> <DropdownMenuContent class="mr-2 min-w-[240px] p-0 pb-1">
<DropdownMenuLabel class="flex items-center p-3"> <div ref="refContent">
<VbenAvatar <DropdownMenuLabel class="flex items-center p-3">
:alt="text" <VbenAvatar
:src="avatar" :alt="text"
class="size-12" :src="avatar"
dot class="size-12"
dot-class="bottom-0 right-1 border-2 size-4 bg-green-500" dot
/> dot-class="bottom-0 right-1 border-2 size-4 bg-green-500"
<div class="ml-2 w-full"> />
<div <div class="ml-2 w-full">
v-if="tagText || text || $slots.tagText" <div
class="text-foreground mb-1 flex items-center text-sm font-medium" v-if="tagText || text || $slots.tagText"
> class="text-foreground mb-1 flex items-center text-sm font-medium"
{{ text }} >
<slot name="tagText"> {{ text }}
<Badge v-if="tagText" class="ml-2 text-green-400"> <slot name="tagText">
{{ tagText }} <Badge v-if="tagText" class="ml-2 text-green-400">
</Badge> {{ tagText }}
</slot> </Badge>
</slot>
</div>
<div class="text-muted-foreground text-xs font-normal">
{{ description }}
</div>
</div> </div>
<div class="text-muted-foreground text-xs font-normal"> </DropdownMenuLabel>
{{ description }} <DropdownMenuSeparator v-if="menus?.length" />
</div> <DropdownMenuItem
</div> v-for="menu in menus"
</DropdownMenuLabel> :key="menu.text"
<DropdownMenuSeparator v-if="menus?.length" /> class="mx-1 flex cursor-pointer items-center rounded-sm py-1 leading-8"
<DropdownMenuItem @click="menu.handler"
v-for="menu in menus" >
:key="menu.text" <VbenIcon :icon="menu.icon" class="mr-2 size-4" />
class="mx-1 flex cursor-pointer items-center rounded-sm py-1 leading-8" {{ menu.text }}
@click="menu.handler" </DropdownMenuItem>
> <DropdownMenuSeparator />
<VbenIcon :icon="menu.icon" class="mr-2 size-4" /> <DropdownMenuItem
{{ menu.text }} v-if="preferences.widget.lockScreen"
</DropdownMenuItem> class="mx-1 flex cursor-pointer items-center rounded-sm py-1 leading-8"
<DropdownMenuSeparator /> @click="handleOpenLock"
<DropdownMenuItem >
v-if="preferences.widget.lockScreen" <LockKeyhole class="mr-2 size-4" />
class="mx-1 flex cursor-pointer items-center rounded-sm py-1 leading-8" {{ $t('ui.widgets.lockScreen.title') }}
@click="handleOpenLock" <DropdownMenuShortcut v-if="enableLockScreenShortcutKey">
> {{ altView }} L
<LockKeyhole class="mr-2 size-4" /> </DropdownMenuShortcut>
{{ $t('ui.widgets.lockScreen.title') }} </DropdownMenuItem>
<DropdownMenuShortcut v-if="enableLockScreenShortcutKey"> <DropdownMenuSeparator v-if="preferences.widget.lockScreen" />
{{ altView }} L <DropdownMenuItem
</DropdownMenuShortcut> class="mx-1 flex cursor-pointer items-center rounded-sm py-1 leading-8"
</DropdownMenuItem> @click="handleLogout"
<DropdownMenuSeparator v-if="preferences.widget.lockScreen" /> >
<DropdownMenuItem <LogOut class="mr-2 size-4" />
class="mx-1 flex cursor-pointer items-center rounded-sm py-1 leading-8" {{ $t('common.logout') }}
@click="handleLogout" <DropdownMenuShortcut v-if="enableLogoutShortcutKey">
> {{ altView }} Q
<LogOut class="mr-2 size-4" /> </DropdownMenuShortcut>
{{ $t('common.logout') }} </DropdownMenuItem>
<DropdownMenuShortcut v-if="enableLogoutShortcutKey"> </div>
{{ altView }} Q
</DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
</template> </template>

View File

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

View File

@ -8,6 +8,7 @@ import { toRaw } from 'vue';
import { Store } from '@vben-core/shared/store'; import { Store } from '@vben-core/shared/store';
import { import {
bindMethods, bindMethods,
isBoolean,
isFunction, isFunction,
mergeWithArrayOverride, mergeWithArrayOverride,
StateHandler, StateHandler,
@ -20,6 +21,7 @@ function getDefaultState(): VxeGridProps {
gridOptions: {}, gridOptions: {},
gridEvents: {}, gridEvents: {},
formOptions: undefined, formOptions: undefined,
showSearchForm: true,
}; };
} }
@ -108,6 +110,16 @@ export class VxeGridApi {
} }
} }
toggleSearchForm(show?: boolean) {
this.setState({
showSearchForm: isBoolean(show) ? show : !this.state?.showSearchForm,
});
// nextTick(() => {
// this.grid.recalculate();
// });
return this.state?.showSearchForm;
}
unmount() { unmount() {
this.isMounted = false; this.isMounted = false;
this.stateHandler.reset(); this.stateHandler.reset();

View File

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

View File

@ -2,6 +2,7 @@ import type { ClassType, DeepPartial } from '@vben/types';
import type { VbenFormProps } from '@vben-core/form-ui'; import type { VbenFormProps } from '@vben-core/form-ui';
import type { import type {
VxeGridListeners, VxeGridListeners,
VxeGridPropTypes,
VxeGridProps as VxeTableGridProps, VxeGridProps as VxeTableGridProps,
VxeUIExport, VxeUIExport,
} from 'vxe-table'; } from 'vxe-table';
@ -18,6 +19,16 @@ export interface VxePaginationInfo {
total: number; total: number;
} }
interface ToolbarConfigOptions extends VxeGridPropTypes.ToolbarConfig {
/** 是否显示切换搜索表单的按钮 */
search?: boolean;
}
export interface VxeTableGridOptions<T = any> extends VxeTableGridProps<T> {
/** 工具栏配置 */
toolbarConfig?: ToolbarConfigOptions;
}
export interface VxeGridProps { export interface VxeGridProps {
/** /**
* *
@ -38,7 +49,7 @@ export interface VxeGridProps {
/** /**
* vxe-grid * vxe-grid
*/ */
gridOptions?: DeepPartial<VxeTableGridProps>; gridOptions?: DeepPartial<VxeTableGridOptions>;
/** /**
* vxe-grid * vxe-grid
*/ */
@ -47,6 +58,10 @@ export interface VxeGridProps {
* *
*/ */
formOptions?: VbenFormProps; formOptions?: VbenFormProps;
/**
*
*/
showSearchForm?: boolean;
} }
export type ExtendedVxeGridApi = { export type ExtendedVxeGridApi = {

View File

@ -1,7 +1,10 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { VbenFormProps } from '@vben-core/form-ui'; import type { VbenFormProps } from '@vben-core/form-ui';
import type { import type {
VxeGridDefines,
VxeGridInstance, VxeGridInstance,
VxeGridListeners,
VxeGridPropTypes,
VxeGridProps as VxeTableGridProps, VxeGridProps as VxeTableGridProps,
} from 'vxe-table'; } from 'vxe-table';
@ -57,6 +60,7 @@ const {
formOptions, formOptions,
tableTitle, tableTitle,
tableTitleHelp, tableTitleHelp,
showSearchForm,
} = usePriorityValues(props, state); } = usePriorityValues(props, state);
const { isMobile } = usePreferences(); const { isMobile } = usePreferences();
@ -103,21 +107,37 @@ const toolbarOptions = computed(() => {
const slotActions = slots[TOOLBAR_ACTIONS]?.(); const slotActions = slots[TOOLBAR_ACTIONS]?.();
const slotTools = slots[TOOLBAR_TOOLS]?.(); const slotTools = slots[TOOLBAR_TOOLS]?.();
const toolbarConfig: VxeGridPropTypes.ToolbarConfig = {
tools:
gridOptions.value?.toolbarConfig?.search && !!formOptions.value
? [
{
code: 'search',
icon: 'vxe-icon--search',
circle: true,
status: showSearchForm.value ? 'primary' : undefined,
title: $t('common.search'),
},
]
: [],
};
if (!showToolbar.value) { if (!showToolbar.value) {
return {}; return { toolbarConfig };
} }
// if (gridOptions.value?.toolbarConfig?.search) {
// }
// 使toolbar // 使toolbar
// //
return { toolbarConfig.slots = {
toolbarConfig: { ...(slotActions || showTableTitle.value
slots: { ? { buttons: TOOLBAR_ACTIONS }
...(slotActions || showTableTitle.value : {}),
? { buttons: TOOLBAR_ACTIONS } ...(slotTools ? { tools: TOOLBAR_TOOLS } : {}),
: {}),
...(slotTools ? { tools: TOOLBAR_TOOLS } : {}),
},
},
}; };
return { toolbarConfig };
}); });
const options = computed(() => { const options = computed(() => {
@ -173,9 +193,19 @@ const options = computed(() => {
return mergedOptions; return mergedOptions;
}); });
function onToolbarToolClick(event: VxeGridDefines.ToolbarToolClickEventParams) {
if (event.code === 'search') {
props.api?.toggleSearchForm?.();
}
(
gridEvents.value?.toolbarToolClick as VxeGridListeners['toolbarToolClick']
)?.(event);
}
const events = computed(() => { const events = computed(() => {
return { return {
...gridEvents.value, ...gridEvents.value,
toolbarToolClick: onToolbarToolClick,
}; };
}); });
@ -213,7 +243,8 @@ async function init() {
const autoLoad = defaultGridOptions.proxyConfig?.autoLoad; const autoLoad = defaultGridOptions.proxyConfig?.autoLoad;
const enableProxyConfig = options.value.proxyConfig?.enabled; const enableProxyConfig = options.value.proxyConfig?.enabled;
if (enableProxyConfig && autoLoad) { if (enableProxyConfig && autoLoad) {
props.api.reload(formApi.form?.values ?? {}); props.api.grid.commitProxy?.('_init', formApi.form?.values ?? {});
// props.api.reload(formApi.form?.values ?? {});
} }
// form vben-formformConfig // form vben-formformConfig
@ -304,7 +335,11 @@ onUnmounted(() => {
<!-- form表单 --> <!-- form表单 -->
<template #form> <template #form>
<div v-if="formOptions" class="relative rounded py-3 pb-4"> <div
v-if="formOptions"
v-show="showSearchForm !== false"
class="relative rounded py-3 pb-4"
>
<slot name="form"> <slot name="form">
<Form> <Form>
<template <template

View File

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

View File

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

View File

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

View File

@ -38,7 +38,7 @@
"qrcodeLogin": "QR Code Login", "qrcodeLogin": "QR Code Login",
"codeSubtitle": "Enter your phone number to start managing your project", "codeSubtitle": "Enter your phone number to start managing your project",
"code": "Security code", "code": "Security code",
"codeTip": "Security code is required", "codeTip": "Security code required {0} characters",
"mobile": "Mobile", "mobile": "Mobile",
"mobileLogin": "Mobile Login", "mobileLogin": "Mobile Login",
"mobileTip": "Please enter mobile number", "mobileTip": "Please enter mobile number",

View File

@ -9,5 +9,6 @@
"noData": "No Data", "noData": "No Data",
"refresh": "Refresh", "refresh": "Refresh",
"loadingMenu": "Loading Menu", "loadingMenu": "Loading Menu",
"query": "Search" "query": "Search",
"search": "Search"
} }

View File

@ -45,7 +45,11 @@
"width": "Width", "width": "Width",
"visible": "Show Sidebar", "visible": "Show Sidebar",
"collapsed": "Collpase Menu", "collapsed": "Collpase Menu",
"collapsedShowTitle": "Show Menu Title" "collapsedShowTitle": "Show Menu Title",
"autoActivateChild": "Auto Activate SubMenu",
"autoActivateChildTip": "`Enabled` to automatically activate the submenu while click menu.",
"expandOnHover": "Expand On Hover",
"expandOnHoverTip": "When the mouse hovers over menu, \n `Enabled` to expand children menus \n `Disabled` to expand whole sidebar."
}, },
"tabbar": { "tabbar": {
"title": "Tabbar", "title": "Tabbar",
@ -55,6 +59,8 @@
"showMaximize": "Show Maximize Button", "showMaximize": "Show Maximize Button",
"persist": "Persist Tabs", "persist": "Persist Tabs",
"draggable": "Enable Draggable Sort", "draggable": "Enable Draggable Sort",
"wheelable": "Support Mouse Wheel",
"wheelableTip": "When enabled, the Tabbar area responds to vertical scrolling events of the scroll wheel.",
"styleType": { "styleType": {
"title": "Tabs Style", "title": "Tabs Style",
"chrome": "Chrome", "chrome": "Chrome",

View File

@ -38,7 +38,7 @@
"qrcodeLogin": "扫码登录", "qrcodeLogin": "扫码登录",
"codeSubtitle": "请输入您的手机号码以开始管理您的项目", "codeSubtitle": "请输入您的手机号码以开始管理您的项目",
"code": "验证码", "code": "验证码",
"codeTip": "请输入验证码", "codeTip": "请输入{0}位验证码",
"mobile": "手机号码", "mobile": "手机号码",
"mobileTip": "请输入手机号", "mobileTip": "请输入手机号",
"mobileErrortip": "手机号码格式错误", "mobileErrortip": "手机号码格式错误",

View File

@ -9,5 +9,6 @@
"noData": "暂无数据", "noData": "暂无数据",
"refresh": "刷新", "refresh": "刷新",
"loadingMenu": "加载菜单中", "loadingMenu": "加载菜单中",
"query": "查询" "query": "查询",
"search": "搜索"
} }

View File

@ -45,7 +45,11 @@
"width": "宽度", "width": "宽度",
"visible": "显示侧边栏", "visible": "显示侧边栏",
"collapsed": "折叠菜单", "collapsed": "折叠菜单",
"collapsedShowTitle": "折叠显示菜单名" "collapsedShowTitle": "折叠显示菜单名",
"autoActivateChild": "自动激活子菜单",
"autoActivateChildTip": "点击顶层菜单时,自动激活第一个子菜单或者上一次激活的子菜单",
"expandOnHover": "鼠标悬停展开",
"expandOnHoverTip": "鼠标在折叠区域悬浮时,`启用`则展开当前子菜单,`禁用`则展开整个侧边栏"
}, },
"tabbar": { "tabbar": {
"title": "标签栏", "title": "标签栏",
@ -55,6 +59,8 @@
"showMaximize": "显示最大化按钮", "showMaximize": "显示最大化按钮",
"persist": "持久化标签页", "persist": "持久化标签页",
"draggable": "启动拖拽排序", "draggable": "启动拖拽排序",
"wheelable": "启用纵向滚轮响应",
"wheelableTip": "开启后,标签栏区域可以响应滚轮的纵向滚动事件。\n关闭时只能响应系统的横向滚动事件需要按下Shift再滚动滚轮",
"styleType": { "styleType": {
"title": "标签页风格", "title": "标签页风格",
"chrome": "谷歌", "chrome": "谷歌",

View File

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

View File

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

View File

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

View File

@ -1,3 +1,40 @@
.el-card { .el-card {
--el-card-border-radius: var(--radius) !important; --el-card-border-radius: var(--radius) !important;
} }
.form-valid-error {
/** select 选择器的样式 */
.el-select .el-select__wrapper {
box-shadow: 0 0 0 1px var(--el-color-danger) inset;
}
/** input 选择器的样式 */
.el-input .el-input__wrapper {
box-shadow: 0 0 0 1px var(--el-color-danger) inset;
}
/** radio和checkbox 选择器的样式 */
.el-radio .el-radio__inner,
.el-checkbox .el-checkbox__inner {
border: 1px solid var(--el-color-danger);
}
.el-checkbox-button .el-checkbox-button__inner,
.el-radio-button .el-radio-button__inner {
border: 1px solid var(--el-color-danger);
}
.el-checkbox-button:first-child .el-checkbox-button__inner,
.el-radio-button:first-child .el-radio-button__inner {
border-left: 1px solid var(--el-color-danger);
}
.el-checkbox-button:not(:first-child) .el-checkbox-button__inner,
.el-radio-button:not(:first-child) .el-radio-button__inner {
border-left: none;
}
.el-textarea .el-textarea__inner {
border: 1px solid var(--el-color-danger);
}
}

View File

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

View File

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

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben/playground", "name": "@vben/playground",
"version": "5.5.0", "version": "5.5.1",
"homepage": "https://vben.pro", "homepage": "https://vben.pro",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": { "repository": {

View File

@ -132,6 +132,7 @@ watch(
:text="userStore.userInfo?.realName" :text="userStore.userInfo?.realName"
description="ann.vben@gmail.com" description="ann.vben@gmail.com"
tag-text="Pro" tag-text="Pro"
trigger="both"
@logout="handleLogout" @logout="handleLogout"
/> />
</template> </template>

View File

@ -10,6 +10,7 @@ import { $t } from '@vben/locales';
defineOptions({ name: 'CodeLogin' }); defineOptions({ name: 'CodeLogin' });
const loading = ref(false); const loading = ref(false);
const CODE_LENGTH = 6;
const formSchema = computed((): VbenFormSchema[] => { const formSchema = computed((): VbenFormSchema[] => {
return [ return [
@ -30,6 +31,7 @@ const formSchema = computed((): VbenFormSchema[] => {
{ {
component: 'VbenPinInput', component: 'VbenPinInput',
componentProps: { componentProps: {
codeLength: CODE_LENGTH,
createText: (countdown: number) => { createText: (countdown: number) => {
const text = const text =
countdown > 0 countdown > 0
@ -41,7 +43,9 @@ const formSchema = computed((): VbenFormSchema[] => {
}, },
fieldName: 'code', fieldName: 'code',
label: $t('authentication.code'), label: $t('authentication.code'),
rules: z.string().min(1, { message: $t('authentication.codeTip') }), rules: z.string().length(CODE_LENGTH, {
message: $t('authentication.codeTip', [CODE_LENGTH]),
}),
}, },
]; ];
}); });

View File

@ -82,6 +82,7 @@ const gridOptions: VxeGridProps<RowType> = {
}, },
}, },
}, },
showOverflow: false,
}; };
const [Grid] = useVbenVxeGrid({ gridOptions }); const [Grid] = useVbenVxeGrid({ gridOptions });

View File

@ -1,6 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { VbenFormProps } from '#/adapter/form'; import type { VbenFormProps } from '#/adapter/form';
import type { VxeGridProps } from '#/adapter/vxe-table'; import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { Page } from '@vben/common-ui'; import { Page } from '@vben/common-ui';
@ -71,7 +71,7 @@ const formOptions: VbenFormProps = {
submitOnEnter: false, submitOnEnter: false,
}; };
const gridOptions: VxeGridProps<RowType> = { const gridOptions: VxeTableGridOptions<RowType> = {
checkboxConfig: { checkboxConfig: {
highlight: true, highlight: true,
labelField: 'name', labelField: 'name',
@ -85,6 +85,7 @@ const gridOptions: VxeGridProps<RowType> = {
{ field: 'price', title: 'Price' }, { field: 'price', title: 'Price' },
{ field: 'releaseDate', formatter: 'formatDateTime', title: 'Date' }, { field: 'releaseDate', formatter: 'formatDateTime', title: 'Date' },
], ],
exportConfig: {},
height: 'auto', height: 'auto',
keepSource: true, keepSource: true,
pagerConfig: {}, pagerConfig: {},
@ -100,9 +101,20 @@ const gridOptions: VxeGridProps<RowType> = {
}, },
}, },
}, },
toolbarConfig: {
custom: true,
export: true,
refresh: true,
resizable: true,
search: true,
zoom: true,
},
}; };
const [Grid] = useVbenVxeGrid({ formOptions, gridOptions }); const [Grid] = useVbenVxeGrid({
formOptions,
gridOptions,
});
</script> </script>
<template> <template>

View File

@ -25,10 +25,10 @@ const gridOptions: VxeGridProps<RowType> = {
columns: [ columns: [
{ title: '序号', type: 'seq', width: 50 }, { title: '序号', type: 'seq', width: 50 },
{ align: 'left', title: 'Name', type: 'checkbox', width: 100 }, { align: 'left', title: 'Name', type: 'checkbox', width: 100 },
{ field: 'category', title: 'Category' }, { field: 'category', sortable: true, title: 'Category' },
{ field: 'color', title: 'Color' }, { field: 'color', sortable: true, title: 'Color' },
{ field: 'productName', title: 'Product Name' }, { field: 'productName', sortable: true, title: 'Product Name' },
{ field: 'price', title: 'Price' }, { field: 'price', sortable: true, title: 'Price' },
{ field: 'releaseDate', formatter: 'formatDateTime', title: 'DateTime' }, { field: 'releaseDate', formatter: 'formatDateTime', title: 'DateTime' },
], ],
exportConfig: {}, exportConfig: {},
@ -36,19 +36,26 @@ const gridOptions: VxeGridProps<RowType> = {
keepSource: true, keepSource: true,
proxyConfig: { proxyConfig: {
ajax: { ajax: {
query: async ({ page }) => { query: async ({ page, sort }) => {
return await getExampleTableApi({ return await getExampleTableApi({
page: page.currentPage, page: page.currentPage,
pageSize: page.pageSize, pageSize: page.pageSize,
sortBy: sort.field,
sortOrder: sort.order,
}); });
}, },
}, },
sort: true,
},
sortConfig: {
defaultSort: { field: 'category', order: 'desc' },
remote: true,
}, },
toolbarConfig: { toolbarConfig: {
custom: true, custom: true,
export: true, export: true,
// import: true, // import: true,
refresh: true, refresh: { code: 'query' },
zoom: true, zoom: true,
}, },
}; };

View File

@ -1578,6 +1578,9 @@ importers:
'@vben/utils': '@vben/utils':
specifier: workspace:* specifier: workspace:*
version: link:../../utils version: link:../../utils
'@vueuse/core':
specifier: 'catalog:'
version: 12.0.0(typescript@5.7.2)
vue: vue:
specifier: ^3.5.13 specifier: ^3.5.13
version: 3.5.13(typescript@5.7.2) version: 3.5.13(typescript@5.7.2)

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben/turbo-run", "name": "@vben/turbo-run",
"version": "5.5.0", "version": "5.5.1",
"private": true, "private": true,
"license": "MIT", "license": "MIT",
"type": "module", "type": "module",

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben/vsh", "name": "@vben/vsh",
"version": "5.5.0", "version": "5.5.1",
"private": true, "private": true,
"license": "MIT", "license": "MIT",
"type": "module", "type": "module",