Compare commits

...

36 Commits
main ... dev

Author SHA1 Message Date
dap
4baa0aed8b refactor: 这是iframe页面! iframe页面! iframe页面! 不是我写的真服了 2025-06-16 11:17:02 +08:00
尘墨
b2d3cf10aa !42 perf: 优化顶部栏头像展开用户信息绑定
Merge pull request !42 from 尘墨/main
2025-06-13 07:22:39 +00:00
dap
63d2b38fd1 refactor: tinymce在modal下全屏 2025-06-12 14:58:15 +08:00
dap
78cd6677c3 Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev 2025-06-11 21:01:42 +08:00
Netfan
c0962fec18
fix: auto close popup on deactivated (#6368)
* 修复挂载到内容区域的弹窗和抽屉被意外关闭的问题
2025-06-11 12:20:52 +08:00
dap
d38093ca7d refactor: Partial 2025-06-10 10:53:08 +08:00
dap
687c33ec29 Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev 2025-06-10 10:51:00 +08:00
XiaoHetitu
8ba7bdf2bd
fix(button): 为按钮添加type属性防止表单提交意外触发表单验证机制 (#6340)
在按钮组件中,按钮元素缺少type="button"属性可能导致在表单中意外提交。添加此属性以确保按钮行为符合预期。

Co-authored-by: yuanwj <ywj6792341@qq.com>
2025-06-08 17:56:24 +08:00
zyy
b015fbc9fc
fix: [adapter] 表格配置类型报错 (#6327)
配置toolbarConfig中的search时会有类型报错
2025-06-08 17:53:55 +08:00
broBinChen
b69320c070
feat(hooks): support separate enter/leave delays in useHoverToggle (#6325)
Co-authored-by: xiaobin <xiaobin_chen@fzzixun.com>
2025-06-08 17:53:29 +08:00
zhang
dcccc213ce
fix: requestClient.upload会将vbenform中value为undefined的值转为字符串undefined’提… (#6300)
* fix: requestClient.upload会将vbenform中value为undefined的值转为字符串undefined’提交给后台保存

* fix: requestClient.upload会将vbenform中value为undefined的值转为字符串'undefined’提交给后台保存
2025-06-08 17:51:16 +08:00
ali-pay
c0e601c020
fix: menu type is not 'button' (#6277)
Co-authored-by: Jin Mao <50581550+jinmao88@users.noreply.github.com>
2025-06-08 17:50:44 +08:00
RanMaoting
017ed1a9e1
types: 为useVbenVxeGrid添加泛型声明,使grid实例上能正确获取到行数据的类型 (#5653)
Co-authored-by: Jin Mao <50581550+jinmao88@users.noreply.github.com>
2025-06-08 17:43:02 +08:00
dap
598f371568 refactor: 代码生成 导入 hover样式 2025-06-08 12:53:05 +08:00
dap
ca2aadaf4a refactor: 租户套餐 新增 过滤租户相关菜单 2025-06-06 12:02:56 +08:00
dap
616db1c127 docs: changelog 2025-06-05 21:35:59 +08:00
dap
08de1a6f19 refactor: 代码生成 字典下拉加载改为每次都重新加载 2025-06-05 21:33:53 +08:00
dap
006370798b refactor: tinymce+表单 校验失败样式 2025-06-05 21:18:36 +08:00
dap
831700660c refactor: 角色去掉moredropdown 改为平铺 2025-06-04 21:53:11 +08:00
dap
a53b9382f5 refactor: 排序 默认值 2025-06-04 19:49:03 +08:00
dap
703586123a refactor: 字典接口抛出异常(为什么会抛出异常?)无限调用接口 兼容处理 2025-06-04 19:06:18 +08:00
dap
0295418f79 fix: 菜单管理 路由地址的必填项不生效 2025-06-04 17:53:09 +08:00
dap
14b0d9b50f Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev 2025-06-04 09:18:54 +08:00
vben
b9aef618fe chore: release 5.5.7 2025-06-04 05:33:06 +08:00
Netfan
4102cc2211
feat: improve vbenCheckButtonGroup (#6329)
* 按钮组支持单选清除和多选限制最大选项数

* 按钮组支持icon插槽来定制图标
2025-06-03 23:11:56 +08:00
chewenye
ea776aa710
types: 扩展user-dropdown组件的menus类型,支持iconify (#6283)
Co-authored-by: 车文烨 <chewy@china-lehua.com>
2025-06-03 06:07:06 +08:00
huanghezhen
feb96dc8ea
fix: resolve onClosed method failure in connectedComponent of useVbenModal (#6309) 2025-06-02 08:16:48 +08:00
wyc001122
470fd43b49
fix: 修复使用useVbenVxeGrid配置hasEmptyText、hasEmptyRender不生效的问题 (#6310) 2025-06-02 08:16:26 +08:00
zhang
76d106e474
fix: When defaultHomePage is inconsistent with user.homePath, the pa… (#6299)
* fix:  When defaultHomePage is inconsistent with user.homePath, the page refresh route jump will be abnormal

* fix:  When defaultHomePage is inconsistent with user.homePath, the page refresh route jump will be abnormal
2025-06-02 08:07:06 +08:00
wyc001122
78c3c9da6f
docs(settings): 完善'生产环境动态配置'步骤 (#6297) 2025-06-02 08:05:23 +08:00
dap
8b7d717b21 Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev 2025-05-31 18:53:41 +08:00
Netfan
081d08a7f8
fix: alert width fixed in small screen (#6312) 2025-05-30 19:54:26 +08:00
dap
0da75418d0 refactor: 菜单管理 刷新接口后 记录展开行的情况 (改为vxe配置) 2025-05-30 17:51:06 +08:00
dap
55f0da3085 refactor: tree-search 2025-05-30 14:24:40 +08:00
dap
3849800388 Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev 2025-05-30 09:20:33 +08:00
Netfan
96a10ca83f
style: fix lint error (#6298) 2025-05-28 19:23:21 +08:00
80 changed files with 539 additions and 255 deletions

View File

@ -241,6 +241,7 @@
"napi",
"nolebase",
"rollup",
"tinymce",
"vitest"
]
}

View File

@ -1,9 +1,24 @@
# 1.4.1
**FEATURES**
- Tinymce添加在antd原生表单/useVbenForm下的校验样式
**BUG FIX**
- 菜单管理 路由地址的必填项不生效
**REFACTOR**
- 字典接口抛出异常(为什么会抛出异常?)无限调用接口 兼容处理
- 代码生成 字典下拉加载 改为每次进入编辑页面都加载
# 1.4.0
**FEATURES**
- 菜单管理(通用方法) 保存表格滚动/展开状态并执行回调 用于树表在执行 新增/编辑/删除等操作后 依然在当前位置(体验优化)
-
- 菜单管理 级联删除 删除菜单和children
**REFACTOR**

View File

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

View File

@ -39,6 +39,11 @@ const { apiURL, clientId, enableEncrypt } = useAppConfig(
*/
let isLogoutProcessing = false;
/**
* 401 ?
*/
export class UnauthorizedException extends Error {}
function createRequestClient(baseURL: string) {
const client = new RequestClient({
// 后端地址
@ -228,7 +233,7 @@ function createRequestClient(baseURL: string) {
case 401: {
// 已经在登出过程中 不再执行
if (isLogoutProcessing) {
throw new Error(timeoutMsg);
throw new UnauthorizedException(timeoutMsg);
}
isLogoutProcessing = true;
const _msg = $t('http.loginTimeout');
@ -238,7 +243,7 @@ function createRequestClient(baseURL: string) {
isLogoutProcessing = false;
});
// 不再执行下面逻辑
throw new Error(_msg);
throw new UnauthorizedException(_msg);
}
default: {
if (msg) {

View File

@ -202,13 +202,31 @@ const events = computed(() => {
</template>
<style lang="scss">
/***
由于modal/drawer的zIndex升级后为2000
这里会造成遮挡 修改为更高的zIndex
*/
// z-index
$dropdown-index: 2025;
@mixin tinymce-valid-fail($color) {
.app-tinymce {
// tinymce
.tox-tinymce {
border-color: $color;
}
// focus
.tox .tox-edit-area::before {
border-color: $color;
border-right: none;
border-left: none;
}
}
}
.tox.tox-silver-sink.tox-tinymce-aux {
/** 该样式默认为1300的zIndex */
z-index: 2025;
z-index: $dropdown-index;
}
.tox-fullscreen .tox.tox-tinymce-aux {
z-index: $dropdown-index !important;
}
.app-tinymce {
@ -218,5 +236,29 @@ const events = computed(() => {
.tox-promotion {
display: none;
}
/** 保持focus时与primary色一致 */
.tox .tox-edit-area::before {
border-color: hsl(var(--primary));
}
}
// antd
.ant-form-item:has(.ant-form-item-explain-error) {
$error-color: #ff3860;
@include tinymce-valid-fail($error-color);
}
// useVbenForm
.form-valid-error {
$error-color: hsl(var(--destructive));
@include tinymce-valid-fail($error-color);
}
// transform
div[role='dialog']:has(.tox.tox-tinymce.tox-fullscreen) {
transform: none !important;
}
</style>

View File

@ -138,8 +138,8 @@ watch(
:avatar
:menus
:text="userStore.userInfo?.realName"
description="ann.vben@gmail.com"
tag-text="Pro"
:description="userStore.userInfo?.email"
:tag-text="userStore.userInfo?.username"
@logout="handleLogout"
/>
</template>

View File

@ -118,6 +118,7 @@ export const useAuthStore = defineStore('auth', () => {
roles,
userId: user.userId,
username: user.userName,
email: user.email ?? '',
};
userStore.setUserInfo(userInfo);
/**

View File

@ -1,3 +1,4 @@
import { UnauthorizedException } from '#/api/request';
import { dictDataInfo } from '#/api/system/dict/dict-data';
import { useDictStore } from '#/store/dict';
@ -27,9 +28,16 @@ function fetchAndCacheDictData<T>(
// 内部处理了push的逻辑 这里不用push
setDictInfo(dictName, resp, formatNumber);
})
.catch(() => {
// 401时 移除字典缓存 下次登录重新获取
dictRequestCache.delete(dictName);
.catch((error) => {
/**
* 401 401
* error清除缓存会导致无限循环调用字典接口
*/
if (error instanceof UnauthorizedException) {
// 401时 移除字典缓存 下次登录重新获取
dictRequestCache.delete(dictName);
}
// 其他不做处理
})
.finally(() => {
// 移除请求状态缓存

View File

@ -95,6 +95,7 @@ export const drawerSchema: FormSchemaGetter = () => [
fieldName: 'orderNum',
label: '显示排序',
rules: 'required',
defaultValue: 0,
},
{
component: 'Input',

View File

@ -99,6 +99,7 @@ export const drawerSchema: FormSchemaGetter = () => [
fieldName: 'dictSort',
label: '显示排序',
rules: 'required',
defaultValue: 0,
},
{
component: 'Textarea',

View File

@ -238,6 +238,7 @@ export const drawerSchema: FormSchemaGetter = () => [
if (model.isFrame !== '0') {
return z
.string({ message: '请输入路由地址' })
.min(1, '请输入路由地址')
.refine((val) => !val.startsWith('/'), {
message: '路由地址不需要带/',
});

View File

@ -1,25 +0,0 @@
import type { MaybePromise } from '@vben/types';
import type { useVbenVxeGrid } from '#/adapter/vxe-table';
/**
* / // ()
*
* @param tableApi api
* @param callback
*/
export async function preserveTreeTableState(
tableApi: ReturnType<typeof useVbenVxeGrid>[1],
callback: () => MaybePromise<void>,
) {
// 保存当前状态
const scrollState = tableApi.grid.getScroll();
const expandRecords = tableApi.grid.getTreeExpandRecords();
// 执行回调
await callback();
// 恢复状态
tableApi.grid.setTreeExpand(expandRecords, true);
tableApi.grid.scrollTo(scrollState.scrollLeft, scrollState.scrollTop);
}

View File

@ -17,7 +17,6 @@ import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { menuCascadeRemove, menuList, menuRemove } from '#/api/system/menu';
import { columns, querySchema } from './data';
import { preserveTreeTableState } from './helper';
import menuDrawer from './menu-drawer.vue';
/**
@ -69,6 +68,8 @@ const gridOptions: VxeGridProps = {
rowField: 'menuId',
// tree vxe
transform: true,
//
reserve: true,
},
id: 'system-menu-index',
};
@ -118,17 +119,15 @@ async function handleEdit(record: Menu) {
*/
const cascadingDeletion = ref(false);
async function handleDelete(row: Menu) {
await preserveTreeTableState(tableApi, async () => {
if (cascadingDeletion.value) {
//
const menuAndChildren: Menu[] = treeToList([row], { id: 'menuId' });
await menuCascadeRemove(menuAndChildren.map((item) => item.menuId));
} else {
//
await menuRemove([row.menuId]);
}
await tableApi.query();
});
if (cascadingDeletion.value) {
//
const menuAndChildren: Menu[] = treeToList([row], { id: 'menuId' });
await menuCascadeRemove(menuAndChildren.map((item) => item.menuId));
} else {
//
await menuRemove([row.menuId]);
}
await tableApi.query();
}
function removeConfirmTitle(row: Menu) {
@ -147,7 +146,7 @@ function removeConfirmTitle(row: Menu) {
* 编辑/添加成功后刷新表格
*/
async function afterEditOrAdd() {
await preserveTreeTableState(tableApi, () => tableApi.query());
tableApi.query();
}
/**

View File

@ -111,6 +111,7 @@ export const drawerSchema: FormSchemaGetter = () => [
fieldName: 'postSort',
label: '岗位排序',
rules: 'required',
defaultValue: 0,
},
{
component: 'RadioGroup',

View File

@ -127,6 +127,7 @@ export const drawerSchema: FormSchemaGetter = () => [
fieldName: 'roleSort',
label: '角色排序',
rules: 'required',
defaultValue: 0,
},
{
component: 'Select',

View File

@ -11,14 +11,7 @@ import { useAccess } from '@vben/access';
import { Page, useVbenDrawer, useVbenModal } from '@vben/common-ui';
import { getVxePopupContainer } from '@vben/utils';
import {
Dropdown,
Menu,
MenuItem,
Modal,
Popconfirm,
Space,
} from 'ant-design-vue';
import { Modal, Popconfirm, Space } from 'ant-design-vue';
import { useVbenVxeGrid, vxeCheckboxChecked } from '#/adapter/vxe-table';
import {
@ -200,6 +193,18 @@ function handleAssignRole(record: Role) {
>
{{ $t('pages.common.edit') }}
</ghost-button>
<ghost-button
v-access:code="['system:role:edit']"
@click.stop="handleAuthEdit(row)"
>
权限
</ghost-button>
<ghost-button
v-access:code="['system:role:edit']"
@click.stop="handleAssignRole(row)"
>
分配
</ghost-button>
<Popconfirm
:get-popup-container="getVxePopupContainer"
placement="left"
@ -215,25 +220,6 @@ function handleAssignRole(record: Role) {
</ghost-button>
</Popconfirm>
</Space>
<Dropdown placement="bottomRight">
<template #overlay>
<Menu>
<MenuItem key="1" @click="handleAuthEdit(row)">
数据权限
</MenuItem>
<MenuItem key="2" @click="handleAssignRole(row)">
分配用户
</MenuItem>
</Menu>
</template>
<a-button
size="small"
type="link"
v-access:code="'system:role:edit'"
>
{{ $t('pages.common.more') }}
</a-button>
</Dropdown>
</template>
</template>
</BasicTable>

View File

@ -10,7 +10,7 @@ import { cloneDeep, eachTree } from '@vben/utils';
import { omit } from 'lodash-es';
import { useVbenForm } from '#/adapter/form';
import { menuTreeSelect, tenantPackageMenuTreeSelect } from '#/api/system/menu';
import { tenantPackageMenuTreeSelect } from '#/api/system/menu';
import {
packageAdd,
packageInfo,
@ -40,30 +40,18 @@ const [BasicForm, formApi] = useVbenForm({
const menuTree = ref<MenuOption[]>([]);
async function setupMenuTree(id?: number | string) {
if (id) {
const resp = await tenantPackageMenuTreeSelect(id);
const menus = resp.menus;
// i18n
eachTree(menus, (node) => {
node.label = $t(node.label);
});
//
menuTree.value = resp.menus;
// keysmenu menu
await nextTick();
await formApi.setFieldValue('menuIds', resp.checkedKeys);
} else {
const resp = await menuTreeSelect();
// i18n
eachTree(resp, (node) => {
node.label = $t(node.label);
});
//
menuTree.value = resp;
// keysmenu menu
await nextTick();
await formApi.setFieldValue('menuIds', []);
}
// 0使 ``
const resp = await tenantPackageMenuTreeSelect(id ?? 0);
const menus = resp.menus;
// i18n
eachTree(menus, (node) => {
node.label = $t(node.label);
});
//
menuTree.value = menus;
// keysmenu menu
await nextTick();
await formApi.setFieldValue('menuIds', resp.checkedKeys);
}
async function customFormValueGetter() {

View File

@ -80,6 +80,7 @@ onMounted(loadTree);
v-model:value="searchValue"
:placeholder="$t('pages.common.search')"
size="small"
allow-clear
>
<template #enterButton>
<a-button @click="handleReload">
@ -102,9 +103,9 @@ onMounted(loadTree);
@select="$emit('select')"
>
<template #title="{ label }">
<span v-if="label.indexOf(searchValue) > -1">
<span v-if="label.includes(searchValue)">
{{ label.substring(0, label.indexOf(searchValue)) }}
<span style="color: #f50">{{ searchValue }}</span>
<span class="text-primary">{{ searchValue }}</span>
{{
label.substring(
label.indexOf(searchValue) + searchValue.length,

View File

@ -4,9 +4,10 @@ import type { Ref } from 'vue';
import type { VxeGridProps } from '#/adapter/vxe-table';
import type { GenInfo } from '#/api/tool/gen/model';
import { inject } from 'vue';
import { inject, onMounted, reactive } from 'vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { dictOptionSelectList } from '#/api/system/dict/dict-type';
import { validRules, vxeTableColumns } from './gen-data';
@ -15,8 +16,26 @@ import { validRules, vxeTableColumns } from './gen-data';
*/
const genInfoData = inject('genInfoData') as Ref<GenInfo['info']>;
const dictOptions = reactive<{ label: string; value: string }[]>([
{ label: '未设置', value: '' },
]);
/**
* 加载字典下拉数据
*/
onMounted(async () => {
const resp = await dictOptionSelectList();
const options = resp.map((dict) => ({
label: `${dict.dictName} | ${dict.dictType}`,
value: dict.dictType,
}));
dictOptions.push(...options);
});
const gridOptions: VxeGridProps = {
columns: vxeTableColumns,
columns: vxeTableColumns(dictOptions),
keepSource: true,
editConfig: { trigger: 'click', mode: 'cell', showStatus: true },
editRules: validRules,

View File

@ -2,14 +2,10 @@ import type { Recordable } from '@vben/types';
import type { VxeGridProps } from '#/adapter/vxe-table';
import { reactive } from 'vue';
import { getPopupContainer } from '@vben/utils';
import { Checkbox, Input, Select } from 'ant-design-vue';
import { dictOptionSelectList } from '#/api/system/dict/dict-type';
const JavaTypes: string[] = [
'Long',
'String',
@ -45,24 +41,6 @@ const componentsOptions = [
{ label: '富文本', value: 'editor' },
];
const dictOptions = reactive<{ label: string; value: string }[]>([
{ label: '未设置', value: '' },
]);
/**
*
*/
(async function init() {
const ret = await dictOptionSelectList();
ret.forEach((dict) => {
const option = {
label: `${dict.dictName} | ${dict.dictType}`,
value: dict.dictType,
};
dictOptions.push(option);
});
})();
function renderBooleanTag(row: Recordable<any>, field: string) {
const value = row[field] ? '是' : '否';
const className = row[field] ? 'text-green-500' : 'text-red-500';
@ -78,7 +56,10 @@ export const validRules: VxeGridProps['editRules'] = {
javaField: [{ required: true, message: '请输入' }],
};
export const vxeTableColumns: VxeGridProps['columns'] = [
// 内部依赖的字典从外部通过函数传入
export const vxeTableColumns: (
dictOptions: { label: string; value: string }[],
) => VxeGridProps['columns'] = (dictOptions) => [
{
title: '序号',
type: 'seq',

View File

@ -91,6 +91,7 @@ const gridOptions: VxeGridProps = {
toolbarConfig: {
enabled: false,
},
cellClassName: 'cursor-pointer',
};
const [BasicTable, tableApi] = useVbenVxeGrid({ formOptions, gridOptions });

View File

@ -6,6 +6,7 @@ import { stringify } from '@vben/request';
import { useAccessStore } from '@vben/stores';
import { useEventListener } from '@vueuse/core';
import { Alert } from 'ant-design-vue';
defineOptions({ name: 'FlowDesigner' });
@ -48,5 +49,13 @@ useEventListener('message', messageHandler);
</script>
<template>
<iframe :src="url" class="size-full"></iframe>
<div class="size-full">
<Alert
class="mx-4 my-2"
type="warning"
:show-icon="true"
message="这是iframe页面! iframe页面! iframe页面! 不是我写的真服了"
/>
<iframe :src="url" class="size-full"></iframe>
</div>
</template>

View File

@ -73,6 +73,7 @@ onMounted(loadTree);
v-model:value="searchValue"
:placeholder="$t('pages.common.search')"
size="small"
allow-clear
>
<template #enterButton>
<a-button @click="handleReload">
@ -95,9 +96,9 @@ onMounted(loadTree);
@select="$emit('select')"
>
<template #title="{ label }">
<span v-if="label.indexOf(searchValue) > -1">
<span v-if="label.includes(searchValue)">
{{ label.substring(0, label.indexOf(searchValue)) }}
<span style="color: #f50">{{ searchValue }}</span>
<span class="text-primary">{{ searchValue }}</span>
{{
label.substring(
label.indexOf(searchValue) + searchValue.length,

View File

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

View File

@ -21,7 +21,7 @@ The rules are consistent with [Vite Env Variables and Modes](https://vitejs.dev/
console.log(import.meta.env.VITE_PROT);
```
- Variables starting with `VITE_GLOB_*` will be added to the `_app.config.js` configuration file during packaging. :::
- Variables starting with `VITE_GLOB_*` will be added to the `_app.config.js` configuration file during packaging.
:::
@ -138,6 +138,27 @@ To add a new dynamically modifiable configuration item, simply follow the steps
}
```
- In `packages/effects/hooks/src/use-app-config.ts`, add the corresponding configuration item, such as:
```ts
export function useAppConfig(
env: Record<string, any>,
isProduction: boolean,
): ApplicationConfig {
// In production environment, directly use the window._VBEN_ADMIN_PRO_APP_CONF_ global variable
const config = isProduction
? window._VBEN_ADMIN_PRO_APP_CONF_
: (env as VbenAdminProAppConfigRaw);
const { VITE_GLOB_API_URL, VITE_GLOB_OTHER_API_URL } = config; // [!code ++]
return {
apiURL: VITE_GLOB_API_URL,
otherApiURL: VITE_GLOB_OTHER_API_URL, // [!code ++]
};
}
```
At this point, you can use the `useAppConfig` method within the project to access the newly added configuration item.
```ts

View File

@ -21,7 +21,7 @@
console.log(import.meta.env.VITE_PROT);
```
- 以 `VITE_GLOB_*` 开头的的变量,在打包的时候,会被加入 `_app.config.js`配置文件当中. :::
- 以 `VITE_GLOB_*` 开头的的变量,在打包的时候,会被加入 `_app.config.js`配置文件当中.
:::
@ -137,6 +137,27 @@ const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
}
```
- 在 `packages/effects/hooks/src/use-app-config.ts` 中,新增对应的配置项,如:
```ts
export function useAppConfig(
env: Record<string, any>,
isProduction: boolean,
): ApplicationConfig {
// 生产环境下,直接使用 window._VBEN_ADMIN_PRO_APP_CONF_ 全局变量
const config = isProduction
? window._VBEN_ADMIN_PRO_APP_CONF_
: (env as VbenAdminProAppConfigRaw);
const { VITE_GLOB_API_URL, VITE_GLOB_OTHER_API_URL } = config; // [!code ++]
return {
apiURL: VITE_GLOB_API_URL,
otherApiURL: VITE_GLOB_OTHER_API_URL, // [!code ++]
};
}
```
到这里,就可以在项目内使用 `useAppConfig`方法获取到新增的配置项了。
```ts

View File

@ -1,6 +1,6 @@
{
"name": "@vben/commitlint-config",
"version": "5.5.6",
"version": "5.5.7",
"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.5.6",
"version": "5.5.7",
"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.5.6",
"version": "5.5.7",
"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.5.6",
"version": "5.5.7",
"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/tsconfig",
"version": "5.5.6",
"version": "5.5.7",
"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-admin-monorepo",
"version": "5.5.6",
"version": "5.5.7",
"private": true,
"keywords": [
"monorepo",

View File

@ -1,6 +1,6 @@
{
"name": "@vben-core/design",
"version": "5.5.6",
"version": "5.5.7",
"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/icons",
"version": "5.5.6",
"version": "5.5.7",
"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/shared",
"version": "5.5.6",
"version": "5.5.7",
"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/typings",
"version": "5.5.6",
"version": "5.5.7",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@ -12,6 +12,10 @@ interface BasicUserInfo {
*
*/
avatar: string;
/**
*
*/
email: string;
/**
*
*/

View File

@ -1,6 +1,6 @@
{
"name": "@vben-core/composables",
"version": "5.5.6",
"version": "5.5.7",
"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/preferences",
"version": "5.5.6",
"version": "5.5.7",
"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/form-ui",
"version": "5.5.6",
"version": "5.5.7",
"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/layout-ui",
"version": "5.5.6",
"version": "5.5.7",
"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/menu-ui",
"version": "5.5.6",
"version": "5.5.7",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@ -34,7 +34,6 @@ const props = withDefaults(defineProps<AlertProps>(), {
bordered: true,
buttonAlign: 'end',
centered: true,
containerClass: 'w-[520px]',
});
const emits = defineEmits(['closed', 'confirm', 'opened']);
const open = defineModel<boolean>('open', { default: false });
@ -148,7 +147,7 @@ async function handleOpenChange(val: boolean) {
:class="
cn(
containerClass,
'left-0 right-0 mx-auto flex max-h-[80%] flex-col p-0 duration-300 sm:rounded-[var(--radius)] md:w-[520px] md:max-w-[80%]',
'left-0 right-0 mx-auto flex max-h-[80%] flex-col p-0 duration-300 sm:w-[520px] sm:max-w-[80%] sm:rounded-[var(--radius)]',
{
'border-border border': bordered,
'shadow-3xl': !bordered,

View File

@ -1,7 +1,15 @@
<script lang="ts" setup>
import type { DrawerProps, ExtendedDrawerApi } from './drawer';
import { computed, provide, ref, unref, useId, watch } from 'vue';
import {
computed,
onDeactivated,
provide,
ref,
unref,
useId,
watch,
} from 'vue';
import {
useIsMobile,
@ -94,6 +102,16 @@ const {
// },
// );
/**
* 在开启keepAlive情况下 直接通过浏览器按钮/手势等返回 不会关闭弹窗
*/
onDeactivated(() => {
//
if (!appendToMain.value) {
props.drawerApi?.close();
}
});
function interactOutside(e: Event) {
if (!closeOnClickModal.value || submitting.value) {
e.preventDefault();

View File

@ -9,7 +9,6 @@ import {
h,
inject,
nextTick,
onDeactivated,
provide,
reactive,
ref,
@ -72,13 +71,6 @@ export function useVbenDrawer<
},
);
/**
* keepAlive情况下 /
*/
onDeactivated(() => {
(extendedApi as ExtendedDrawerApi)?.close?.();
});
return [Drawer, extendedApi as ExtendedDrawerApi] as const;
}

View File

@ -1,7 +1,16 @@
<script lang="ts" setup>
import type { ExtendedModalApi, ModalProps } from './modal';
import { computed, nextTick, provide, ref, unref, useId, watch } from 'vue';
import {
computed,
nextTick,
onDeactivated,
provide,
ref,
unref,
useId,
watch,
} from 'vue';
import {
useIsMobile,
@ -135,6 +144,16 @@ watch(
// },
// );
/**
* 在开启keepAlive情况下 直接通过浏览器按钮/手势等返回 不会关闭弹窗
*/
onDeactivated(() => {
//
if (!appendToMain.value) {
props.modalApi?.close();
}
});
function handleFullscreen() {
props.modalApi?.setState((prev) => {
// if (prev.fullscreen) {

View File

@ -5,7 +5,6 @@ import {
h,
inject,
nextTick,
onDeactivated,
provide,
reactive,
ref,
@ -71,13 +70,6 @@ export function useVbenModal<TParentModalProps extends ModalProps = ModalProps>(
},
);
/**
* keepAlive情况下 /
*/
onDeactivated(() => {
(extendedApi as ExtendedModalApi)?.close?.();
});
return [Modal, extendedApi as ExtendedModalApi] as const;
}
@ -94,8 +86,9 @@ export function useVbenModal<TParentModalProps extends ModalProps = ModalProps>(
injectData.options?.onOpenChange?.(isOpen);
};
const onClosed = mergedOptions.onClosed;
mergedOptions.onClosed = () => {
options.onClosed?.();
onClosed?.();
if (mergedOptions.destroyOnClose) {
injectData.reCreateModal?.();
}
@ -129,6 +122,7 @@ export function useVbenModal<TParentModalProps extends ModalProps = ModalProps>(
},
);
injectData.extendApi?.(extendedApi);
return [Modal, extendedApi] as const;
}

View File

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

View File

@ -29,14 +29,25 @@ export type ValueType = boolean | number | string;
export interface VbenButtonGroupProps
extends Pick<VbenButtonProps, 'disabled'> {
/** 单选模式下允许清除选中 */
allowClear?: boolean;
/** 值改变前的回调 */
beforeChange?: (
value: ValueType,
isChecked: boolean,
) => boolean | PromiseLike<boolean | undefined> | undefined;
/** 按钮样式 */
btnClass?: any;
/** 按钮间隔距离 */
gap?: number;
/** 多选模式下限制最多选择的数量。0表示不限制 */
maxCount?: number;
/** 是否允许多选 */
multiple?: boolean;
/** 选项 */
options?: { [key: string]: any; label: CustomRenderType; value: ValueType }[];
/** 显示图标 */
showIcon?: boolean;
/** 尺寸 */
size?: 'large' | 'middle' | 'small';
}

View File

@ -19,6 +19,8 @@ const props = withDefaults(defineProps<VbenButtonGroupProps>(), {
multiple: false,
showIcon: true,
size: 'middle',
allowClear: false,
maxCount: 0,
});
const emit = defineEmits(['btnClick']);
const btnDefaultProps = computed(() => {
@ -82,12 +84,22 @@ async function onBtnClick(value: ValueType) {
if (innerValue.value.includes(value)) {
innerValue.value = innerValue.value.filter((item) => item !== value);
} else {
if (props.maxCount > 0 && innerValue.value.length >= props.maxCount) {
innerValue.value = innerValue.value.slice(0, props.maxCount - 1);
}
innerValue.value.push(value);
}
modelValue.value = innerValue.value;
} else {
innerValue.value = [value];
modelValue.value = value;
if (props.allowClear && innerValue.value.includes(value)) {
innerValue.value = [];
modelValue.value = undefined;
emit('btnClick', undefined);
return;
} else {
innerValue.value = [value];
modelValue.value = value;
}
}
emit('btnClick', value);
}
@ -110,14 +122,21 @@ async function onBtnClick(value: ValueType) {
v-bind="btnDefaultProps"
:variant="innerValue.includes(btn.value) ? 'default' : 'outline'"
@click="onBtnClick(btn.value)"
type="button"
>
<div class="icon-wrapper" v-if="props.showIcon">
<LoaderCircle
class="animate-spin"
v-if="loadingValues.includes(btn.value)"
/>
<CircleCheckBig v-else-if="innerValue.includes(btn.value)" />
<Circle v-else />
<slot
name="icon"
:loading="loadingValues.includes(btn.value)"
:checked="innerValue.includes(btn.value)"
>
<LoaderCircle
class="animate-spin"
v-if="loadingValues.includes(btn.value)"
/>
<CircleCheckBig v-else-if="innerValue.includes(btn.value)" />
<Circle v-else />
</slot>
</div>
<slot name="option" :label="btn.label" :value="btn.value" :data="btn">
<VbenRenderContent :content="btn.label" />

View File

@ -80,7 +80,7 @@ defineExpose({
v-bind="forwarded"
:class="
cn(
'z-popup bg-background w-full p-6 shadow-lg outline-none sm:rounded-xl',
'z-popup bg-background p-6 shadow-lg outline-none sm:rounded-xl',
'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
{

View File

@ -1,6 +1,6 @@
{
"name": "@vben-core/tabs-ui",
"version": "5.5.6",
"version": "5.5.7",
"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.5.6",
"version": "5.5.7",
"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.5.6",
"version": "5.5.7",
"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.5.6",
"version": "5.5.7",
"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/hooks",
"version": "5.5.6",
"version": "5.5.7",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@ -8,19 +8,40 @@ import { isFunction } from '@vben/utils';
import { useElementHover } from '@vueuse/core';
interface HoverDelayOptions {
/** 鼠标进入延迟时间 */
enterDelay?: (() => number) | number;
/** 鼠标离开延迟时间 */
leaveDelay?: (() => number) | number;
}
const DEFAULT_LEAVE_DELAY = 500; // 鼠标离开延迟时间,默认为 500ms
const DEFAULT_ENTER_DELAY = 0; // 鼠标进入延迟时间,默认为 0立即响应
/**
* true false
* @param refElement true
* @param delay
* @param delay /
* @returns ref enable disable
*/
export function useHoverToggle(
refElement: Arrayable<MaybeElementRef>,
delay: (() => number) | number = 500,
delay: (() => number) | HoverDelayOptions | number = DEFAULT_LEAVE_DELAY,
) {
// 兼容旧版本API
const normalizedOptions: HoverDelayOptions =
typeof delay === 'number' || isFunction(delay)
? { enterDelay: DEFAULT_ENTER_DELAY, leaveDelay: delay }
: {
enterDelay: DEFAULT_ENTER_DELAY,
leaveDelay: DEFAULT_LEAVE_DELAY,
...delay,
};
const isHovers: Array<Ref<boolean>> = [];
const value = ref(false);
const timer = ref<ReturnType<typeof setTimeout> | undefined>();
const enterTimer = ref<ReturnType<typeof setTimeout> | undefined>();
const leaveTimer = ref<ReturnType<typeof setTimeout> | undefined>();
const refs = Array.isArray(refElement) ? refElement : [refElement];
refs.forEach((refEle) => {
const eleRef = computed(() => {
@ -32,15 +53,47 @@ export function useHoverToggle(
});
const isOutsideAll = computed(() => isHovers.every((v) => !v.value));
function clearTimers() {
if (enterTimer.value) {
clearTimeout(enterTimer.value);
enterTimer.value = undefined;
}
if (leaveTimer.value) {
clearTimeout(leaveTimer.value);
leaveTimer.value = undefined;
}
}
function setValueDelay(val: boolean) {
timer.value && clearTimeout(timer.value);
timer.value = setTimeout(
() => {
value.value = val;
timer.value = undefined;
},
isFunction(delay) ? delay() : delay,
);
clearTimers();
if (val) {
// 鼠标进入
const enterDelay = normalizedOptions.enterDelay ?? DEFAULT_ENTER_DELAY;
const delayTime = isFunction(enterDelay) ? enterDelay() : enterDelay;
if (delayTime <= 0) {
value.value = true;
} else {
enterTimer.value = setTimeout(() => {
value.value = true;
enterTimer.value = undefined;
}, delayTime);
}
} else {
// 鼠标离开
const leaveDelay = normalizedOptions.leaveDelay ?? DEFAULT_LEAVE_DELAY;
const delayTime = isFunction(leaveDelay) ? leaveDelay() : leaveDelay;
if (delayTime <= 0) {
value.value = false;
} else {
leaveTimer.value = setTimeout(() => {
value.value = false;
leaveTimer.value = undefined;
}, delayTime);
}
}
}
const watcher = watch(
@ -61,7 +114,7 @@ export function useHoverToggle(
};
onUnmounted(() => {
timer.value && clearTimeout(timer.value);
clearTimers();
});
return [value, controller] as [typeof value, typeof controller];

View File

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

View File

@ -46,7 +46,11 @@ interface Props {
/**
* 菜单数组
*/
menus?: Array<{ handler: AnyFunction; icon?: Component; text: string }>;
menus?: Array<{
handler: AnyFunction;
icon?: Component | Function | string;
text: string;
}>;
/**
* 标签文本

View File

@ -1,8 +1,11 @@
import type { ExtendedFormApi } from '@vben-core/form-ui';
import type { VxeGridInstance } from 'vxe-table';
import type { ExtendedFormApi } from '@vben-core/form-ui';
import type { VxeGridProps } from './types';
import { toRaw } from 'vue';
import { Store } from '@vben-core/shared/store';
import {
bindMethods,
@ -11,7 +14,6 @@ import {
mergeWithArrayOverride,
StateHandler,
} from '@vben-core/shared/utils';
import { toRaw } from 'vue';
function getDefaultState(): VxeGridProps {
return {
@ -24,18 +26,18 @@ function getDefaultState(): VxeGridProps {
};
}
export class VxeGridApi {
private isMounted = false;
private stateHandler: StateHandler;
export class VxeGridApi<T extends Record<string, any> = any> {
public formApi = {} as ExtendedFormApi;
// private prevState: null | VxeGridProps = null;
public grid = {} as VxeGridInstance;
public grid = {} as VxeGridInstance<T>;
public state: null | VxeGridProps<T> = null;
public state: null | VxeGridProps = null;
public store: Store<VxeGridProps<T>>;
public store: Store<VxeGridProps>;
private isMounted = false;
private stateHandler: StateHandler;
constructor(options: VxeGridProps = {}) {
const storeState = { ...options };
@ -97,8 +99,8 @@ export class VxeGridApi {
setState(
stateOrFn:
| ((prev: VxeGridProps) => Partial<VxeGridProps>)
| Partial<VxeGridProps>,
| ((prev: VxeGridProps<T>) => Partial<VxeGridProps<T>>)
| Partial<VxeGridProps<T>>,
) {
if (isFunction(stateOrFn)) {
this.store.setState((prev) => {

View File

@ -9,7 +9,7 @@ import type { Ref } from 'vue';
import type { ClassType, DeepPartial } from '@vben/types';
import type { VbenFormProps } from '@vben-core/form-ui';
import type { BaseFormComponentType, VbenFormProps } from '@vben-core/form-ui';
import type { VxeGridApi } from './api';
@ -35,7 +35,11 @@ export interface SeparatorOptions {
show?: boolean;
backgroundColor?: string;
}
export interface VxeGridProps {
export interface VxeGridProps<
T extends Record<string, any> = any,
D extends BaseFormComponentType = BaseFormComponentType,
> {
/**
*
*/
@ -55,15 +59,15 @@ export interface VxeGridProps {
/**
* vxe-grid
*/
gridOptions?: DeepPartial<VxeTableGridOptions>;
gridOptions?: DeepPartial<VxeTableGridOptions<T>>;
/**
* vxe-grid
*/
gridEvents?: Partial<VxeGridListeners>;
gridEvents?: Partial<VxeGridListeners<T>>;
/**
*
*/
formOptions?: VbenFormProps;
formOptions?: VbenFormProps<D>;
/**
*
*/
@ -74,9 +78,12 @@ export interface VxeGridProps {
separator?: boolean | SeparatorOptions;
}
export type ExtendedVxeGridApi = VxeGridApi & {
useStore: <T = NoInfer<VxeGridProps>>(
selector?: (state: NoInfer<VxeGridProps>) => T,
export type ExtendedVxeGridApi<
D extends Record<string, any> = any,
F extends BaseFormComponentType = BaseFormComponentType,
> = VxeGridApi<D> & {
useStore: <T = NoInfer<VxeGridProps<D, F>>>(
selector?: (state: NoInfer<VxeGridProps<any, any>>) => T,
) => Readonly<Ref<T>>;
};

View File

@ -1,3 +1,5 @@
import type { BaseFormComponentType } from '@vben-core/form-ui';
import type { ExtendedVxeGridApi, VxeGridProps } from './types';
import { defineComponent, h, onBeforeUnmount } from 'vue';
@ -7,16 +9,19 @@ import { useStore } from '@vben-core/shared/store';
import { VxeGridApi } from './api';
import VxeGrid from './use-vxe-grid.vue';
export function useVbenVxeGrid(options: VxeGridProps) {
export function useVbenVxeGrid<
T extends Record<string, any> = any,
D extends BaseFormComponentType = BaseFormComponentType,
>(options: VxeGridProps<T, D>) {
// const IS_REACTIVE = isReactive(options);
const api = new VxeGridApi(options);
const extendedApi: ExtendedVxeGridApi = api as ExtendedVxeGridApi;
const extendedApi: ExtendedVxeGridApi<T, D> = api as ExtendedVxeGridApi<T, D>;
extendedApi.useStore = (selector) => {
return useStore(api.store, selector);
};
const Grid = defineComponent(
(props: VxeGridProps, { attrs, slots }) => {
(props: VxeGridProps<T>, { attrs, slots }) => {
onBeforeUnmount(() => {
api.unmount();
});

View File

@ -280,6 +280,15 @@ const delegatedFormSlots = computed(() => {
return resultSlots.map((key) => key.replace(FORM_SLOT_PREFIX, ''));
});
const showDefaultEmpty = computed(() => {
// VXE Table
const hasEmptyText = options.value.emptyText !== undefined;
const hasEmptyRender = options.value.emptyRender !== undefined;
//
return !hasEmptyText && !hasEmptyRender;
});
async function init() {
await nextTick();
const globalGridConfig = VxeUI?.getConfig()?.grid ?? {};
@ -464,7 +473,7 @@ onUnmounted(() => {
</slot>
</template>
<!-- 统一控状态 -->
<template #empty>
<template v-if="showDefaultEmpty" #empty>
<slot name="empty">
<EmptyIcon class="mx-auto" />
<div class="mt-2">{{ $t('common.noData') }}</div>

View File

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

View File

@ -1,6 +1,8 @@
import type { RequestClient } from '../request-client';
import type { RequestClientConfig } from '../types';
import { isUndefined } from '@vben/utils';
class FileUploader {
private client: RequestClient;
@ -18,10 +20,10 @@ class FileUploader {
Object.entries(data).forEach(([key, value]) => {
if (Array.isArray(value)) {
value.forEach((item, index) => {
formData.append(`${key}[${index}]`, item);
!isUndefined(item) && formData.append(`${key}[${index}]`, item);
});
} else {
formData.append(key, value);
!isUndefined(value) && formData.append(key, value);
}
});

View File

@ -1,6 +1,6 @@
{
"name": "@vben/icons",
"version": "5.5.6",
"version": "5.5.7",
"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.5.6",
"version": "5.5.7",
"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/preferences",
"version": "5.5.6",
"version": "5.5.7",
"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.5.6",
"version": "5.5.7",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@ -6,6 +6,10 @@ interface BasicUserInfo {
*
*/
avatar: string;
/**
*
*/
email: string;
/**
*
*/

View File

@ -1,6 +1,6 @@
{
"name": "@vben/styles",
"version": "5.5.6",
"version": "5.5.7",
"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.5.6",
"version": "5.5.7",
"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.5.6",
"version": "5.5.7",
"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/playground",
"version": "5.5.6",
"version": "5.5.7",
"homepage": "https://vben.pro",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@ -1,10 +1,16 @@
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
import type { Recordable } from '@vben/types';
import type { ComponentType } from './component';
import { h } from 'vue';
import { IconifyIcon } from '@vben/icons';
import { $te } from '@vben/locales';
import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';
import {
setupVbenVxeTable,
useVbenVxeGrid as useGrid,
} from '@vben/plugins/vxe-table';
import { get, isFunction, isString } from '@vben/utils';
import { objectOmit } from '@vueuse/core';
@ -42,7 +48,7 @@ setupVbenVxeTable({
round: true,
showOverflow: true,
size: 'small',
},
} as VxeTableGridOptions,
});
/**
@ -277,7 +283,10 @@ setupVbenVxeTable({
useVbenForm,
});
export { useVbenVxeGrid };
export const useVbenVxeGrid = <T extends Record<string, any>>(
...rest: Parameters<typeof useGrid<T, ComponentType>>
) => useGrid<T, ComponentType>(...rest);
export type OnActionClickParams<T = Recordable<any>> = {
code: string;
row: T;

View File

@ -105,11 +105,16 @@ function setupAccessGuard(router: Router) {
accessStore.setAccessMenus(accessibleMenus);
accessStore.setAccessRoutes(accessibleRoutes);
accessStore.setIsAccessChecked(true);
const redirectPath = (from.query.redirect ??
(to.path === preferences.app.defaultHomePath
? userInfo.homePath || preferences.app.defaultHomePath
: to.fullPath)) as string;
let redirectPath: string;
if (from.query.redirect) {
redirectPath = from.query.redirect as string;
} else if (to.path === preferences.app.defaultHomePath) {
redirectPath = preferences.app.defaultHomePath;
} else if (userInfo.homePath && to.path === userInfo.homePath) {
redirectPath = userInfo.homePath;
} else {
redirectPath = to.fullPath;
}
return {
...router.resolve(decodeURIComponent(redirectPath)),
replace: true,

View File

@ -9,6 +9,7 @@ import {
VbenButtonGroup,
VbenCheckButtonGroup,
} from '@vben/common-ui';
import { LoaderCircle, Square, SquareCheckBig } from '@vben/icons';
import { Button, Card, message } from 'ant-design-vue';
@ -51,6 +52,7 @@ const compProps = reactive({
gap: 0,
showIcon: true,
size: 'middle',
allowClear: false,
} as Recordable<any>);
const [Form] = useVbenForm({
@ -63,6 +65,9 @@ const [Form] = useVbenForm({
}
});
},
commonConfig: {
labelWidth: 150,
},
schema: [
{
component: 'RadioGroup',
@ -109,6 +114,20 @@ const [Form] = useVbenForm({
fieldName: 'beforeChange',
label: '前置回调',
},
{
component: 'Switch',
defaultValue: false,
fieldName: 'allowClear',
label: '允许清除',
help: '单选时是否允许取消选中值为undefined',
},
{
component: 'InputNumber',
defaultValue: 0,
fieldName: 'maxCount',
label: '最大选中数量',
help: '多选时有效0表示不限制',
},
],
showDefaultActions: false,
submitOnChange: true,
@ -186,6 +205,21 @@ function onBtnClick(value: any) {
v-bind="compProps"
/>
</div>
<p class="mt-4">自定义图标{{ checkValue }}</p>
<div class="mt-2 flex flex-col gap-2">
<VbenCheckButtonGroup
v-model="checkValue"
multiple
:options="options"
v-bind="compProps"
>
<template #icon="{ loading, checked }">
<LoaderCircle class="animate-spin" v-if="loading" />
<SquareCheckBig v-else-if="checked" />
<Square v-else />
</template>
</VbenCheckButtonGroup>
</div>
</Card>
<Card title="设置" class="mt-4">

View File

@ -43,7 +43,22 @@ const gridEvents: VxeGridListeners<RowType> = {
},
};
const [Grid, gridApi] = useVbenVxeGrid({ gridEvents, gridOptions });
const [Grid, gridApi] = useVbenVxeGrid<RowType>({
//
// formOptions: {
// schema: [
// {
// component: 'Switch',
// fieldName: 'name',
// },
// ],
// },
gridEvents,
gridOptions,
});
//
// gridApi.grid
const showBorder = gridApi.useStore((state) => state.gridOptions?.border);
const showStripe = gridApi.useStore((state) => state.gridOptions?.stripe);

View File

@ -241,10 +241,10 @@ const schema: VbenFormSchema[] = [
component: 'Input',
dependencies: {
rules: (values) => {
return values.type === 'action' ? 'required' : null;
return values.type === 'button' ? 'required' : null;
},
show: (values) => {
return ['action', 'catalog', 'embedded', 'menu'].includes(values.type);
return ['button', 'catalog', 'embedded', 'menu'].includes(values.type);
},
triggerFields: ['type'],
},
@ -277,7 +277,7 @@ const schema: VbenFormSchema[] = [
},
dependencies: {
show: (values) => {
return values.type !== 'action';
return values.type !== 'button';
},
triggerFields: ['type'],
},
@ -295,7 +295,7 @@ const schema: VbenFormSchema[] = [
},
dependencies: {
show: (values) => {
return values.type !== 'action';
return values.type !== 'button';
},
triggerFields: ['type'],
},
@ -314,7 +314,7 @@ const schema: VbenFormSchema[] = [
},
dependencies: {
show: (values) => {
return values.type !== 'action';
return values.type !== 'button';
},
triggerFields: ['type'],
},
@ -325,7 +325,7 @@ const schema: VbenFormSchema[] = [
component: 'Divider',
dependencies: {
show: (values) => {
return !['action', 'link'].includes(values.type);
return !['button', 'link'].includes(values.type);
},
triggerFields: ['type'],
},
@ -372,7 +372,7 @@ const schema: VbenFormSchema[] = [
component: 'Checkbox',
dependencies: {
show: (values) => {
return !['action'].includes(values.type);
return !['button'].includes(values.type);
},
triggerFields: ['type'],
},
@ -402,7 +402,7 @@ const schema: VbenFormSchema[] = [
component: 'Checkbox',
dependencies: {
show: (values) => {
return !['action', 'link'].includes(values.type);
return !['button', 'link'].includes(values.type);
},
triggerFields: ['type'],
},
@ -417,7 +417,7 @@ const schema: VbenFormSchema[] = [
component: 'Checkbox',
dependencies: {
show: (values) => {
return !['action', 'link'].includes(values.type);
return !['button', 'link'].includes(values.type);
},
triggerFields: ['type'],
},

View File

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