This commit is contained in:
XiaLangQing 2024-10-25 17:05:26 +08:00
commit 49e689a5bb
46 changed files with 198 additions and 342 deletions

View File

@ -18,6 +18,9 @@
- VxeTable在开启/关闭查询表单时 需要使用不同的padding
- VxeTable表格刷新 默认为reload 修改为在当前页刷新(query)
- 岗位管理 部门参数错误
- 角色管理 菜单分配 节点独立下的回显及提交问题
- 租户管理 套餐管理 回显时候`已选中节点`数量为0
- 用户管理 更新用户时打开drawer需要加载该部门下的岗位信息
**OTHERS**

View File

@ -2,10 +2,12 @@
## 提示
该仓库使用vben最新版本v5开发, 老版本v2地址 [前往](https://gitee.com/dapppp/ruoyi-plus-vben)
该仓库使用vben最新版本v5开发, ~~老版本v2地址(不维护)~~ [前往](https://gitee.com/dapppp/ruoyi-plus-vben)
v5版本采用分仓(包)目录结构, 具体开发路径为: `根目录/apps/web-antd`
目前对应后端版本: **5.2.3/2.2.3**
## 进度
**工作流相关模块等待后端重构后开发**

View File

@ -74,24 +74,27 @@ const checkedRealKeys = ref<(number | string)[]>([]);
* 取第一次的menuTree id 设置到checkedMenuKeys
* 主要为了解决没有任何修改 直接点击保存的情况
*/
const stop = watch(
() => props.treeData,
() => {
/** 节点关联情况下是不带父节点的 */
if (props.checkStrictly) {
/** 找到父节点 添加上 */
const parentIds = findGroupParentIds(
props.treeData,
checkedKeys.value as any,
);
checkedRealKeys.value = [...parentIds, ...checkedKeys.value];
} else {
/** 节点独立 这里是全部的节点 */
checkedRealKeys.value = checkedKeys.value;
}
const stop = watch([checkedKeys, () => props.treeData], () => {
if (
props.checkStrictly &&
checkedKeys.value.length > 0 &&
props.treeData.length > 0
) {
/** 找到父节点 添加上 */
const parentIds = findGroupParentIds(
props.treeData,
checkedKeys.value as any,
{ id: props.fieldNames.key },
);
checkedRealKeys.value = [...parentIds, ...checkedKeys.value];
stop();
},
);
}
if (!props.checkStrictly && checkedKeys.value.length > 0) {
/** 节点独立 这里是全部的节点 */
checkedRealKeys.value = checkedKeys.value;
stop();
}
});
/**
*

View File

@ -1,6 +1,6 @@
import type { RouteRecordRaw } from 'vue-router';
import { DEFAULT_HOME_PATH } from '@vben/constants';
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
import { AuthPageLayout } from '#/layouts';
import { $t } from '#/locales';
@ -45,6 +45,7 @@ const coreRoutes: RouteRecordRaw[] = [
},
name: 'Authentication',
path: '/auth',
redirect: LOGIN_PATH,
children: [
{
name: 'Login',

View File

@ -34,10 +34,7 @@ async function handleForceOffline(row: Recordable<any>) {
<template>
<div>
<BasicTable>
<template #toolbar-actions>
<span class="pl-[7px] text-[16px]">我的在线设备</span>
</template>
<BasicTable table-title="我的在线设备">
<template #action="{ row }">
<Popconfirm
:title="`确认强制下线[${row.userName}]?`"

View File

@ -153,10 +153,7 @@ async function handleUnlock() {
<template>
<Page auto-content-height>
<BasicTable>
<template #toolbar-actions>
<span class="pl-[7px] text-[16px]">登录日志列表</span>
</template>
<BasicTable table-title="登录日志列表">
<template #toolbar-tools>
<Space>
<a-button

View File

@ -55,10 +55,7 @@ async function handleForceOffline(row: Recordable<any>) {
<template>
<Page :auto-content-height="true">
<BasicTable>
<template #toolbar-actions>
<span class="pl-[7px] text-[16px]">在线用户列表</span>
</template>
<BasicTable table-title="在线用户列表">
<template #action="{ row }">
<Popconfirm
:get-popup-container="getPopupContainer"

View File

@ -153,10 +153,7 @@ async function handleDelete() {
<template>
<Page :auto-content-height="true">
<BasicTable>
<template #toolbar-actions>
<span class="pl-[7px] text-[16px]">操作日志列表</span>
</template>
<BasicTable table-title="操作日志列表">
<template #toolbar-tools>
<Space>
<a-button

View File

@ -119,10 +119,7 @@ const { hasAccessByCodes } = useAccess();
<template>
<Page :auto-content-height="true">
<BasicTable>
<template #toolbar-actions>
<span class="pl-[7px] text-[16px]">系统授权列表</span>
</template>
<BasicTable table-title="系统授权列表">
<template #toolbar-tools>
<Space>
<a-button

View File

@ -131,10 +131,7 @@ async function handleRefreshCache() {
<template>
<Page :auto-content-height="true">
<BasicTable>
<template #toolbar-actions>
<span class="pl-[7px] text-[16px]">参数列表</span>
</template>
<BasicTable table-title="参数列表">
<template #toolbar-tools>
<Space>
<a-button @click="handleRefreshCache"> 刷新缓存 </a-button>

View File

@ -11,8 +11,7 @@ import {
removeEmptyChildren,
} from '@vben/utils';
import { QuestionCircleOutlined } from '@ant-design/icons-vue';
import { Popconfirm, Space, Tooltip } from 'ant-design-vue';
import { Popconfirm, Space } from 'ant-design-vue';
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter/vxe-table';
import { deptList, deptRemove } from '#/api/system/dept';
@ -133,15 +132,7 @@ function setExpandOrCollapse(expand: boolean) {
<template>
<Page :auto-content-height="true">
<BasicTable>
<template #toolbar-actions>
<div class="flex items-center gap-[6px]">
<span class="pl-[7px] text-[16px]">部门列表</span>
<Tooltip title="提示:双击展开/收起子菜单">
<QuestionCircleOutlined class="text-center" />
</Tooltip>
</div>
</template>
<BasicTable table-title="部门列表" table-title-help="双击展开/收起子菜单">
<template #toolbar-tools>
<Space>
<a-button @click="setExpandOrCollapse(false)">

View File

@ -144,10 +144,7 @@ emitter.on('rowClick', async (value) => {
<template>
<div>
<BasicTable>
<template #toolbar-actions>
<span class="pl-[7px] text-[16px]">字典数据列表</span>
</template>
<BasicTable table-title="字典数据列表">
<template #toolbar-tools>
<Space>
<a-button

View File

@ -188,10 +188,7 @@ const couldSyncTenantDict = computed(() => {
<template>
<div>
<BasicTable>
<template #toolbar-actions>
<span class="pl-[7px] text-[16px]">字典类型列表</span>
</template>
<BasicTable table-title="字典类型列表">
<template #toolbar-tools>
<Space>
<Dropdown>

View File

@ -13,7 +13,7 @@ import {
removeEmptyChildren,
} from '@vben/utils';
import { Popconfirm, Space, Tooltip } from 'ant-design-vue';
import { Popconfirm, Space } from 'ant-design-vue';
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter/vxe-table';
import { menuList, menuRemove } from '#/api/system/menu';
@ -24,7 +24,6 @@ import menuDrawer from './menu-drawer.vue';
/**
* 不要问为什么有两个根节点 v-if会控制只会渲染一个
*/
import { QuestionCircleOutlined } from '@ant-design/icons-vue';
const formOptions: VbenFormProps = {
commonConfig: {
@ -139,15 +138,7 @@ const isAdmin = computed(() => {
<template>
<Page v-if="isAdmin" :auto-content-height="true">
<BasicTable>
<template #toolbar-actions>
<div class="flex items-center gap-[6px]">
<span class="pl-[7px] text-[16px]">菜单列表</span>
<Tooltip title="提示:双击展开/收起子菜单">
<QuestionCircleOutlined class="text-center" />
</Tooltip>
</div>
</template>
<BasicTable table-title="菜单列表" table-title-help="双击展开/收起子菜单">
<template #toolbar-tools>
<Space>
<a-button @click="setExpandOrCollapse(false)">

View File

@ -123,10 +123,7 @@ function handleMultiDelete() {
<template>
<Page :auto-content-height="true">
<BasicTable>
<template #toolbar-actions>
<span class="pl-[7px] text-[16px]">通知公告列表</span>
</template>
<BasicTable table-title="通知公告列表">
<template #toolbar-tools>
<Space>
<a-button

View File

@ -131,10 +131,7 @@ const { hasAccessByCodes } = useAccess();
<template>
<Page :auto-content-height="true">
<BasicTable>
<template #toolbar-actions>
<span class="pl-[7px] text-[16px]">oss配置列表</span>
</template>
<BasicTable table-title="oss配置列表">
<template #toolbar-tools>
<Space>
<a-button

View File

@ -167,10 +167,7 @@ const [FileUploadModal, fileUploadApi] = useVbenModal({
<template>
<Page :auto-content-height="true">
<BasicTable>
<template #toolbar-actions>
<span class="pl-[7px] text-[16px]">文件列表</span>
</template>
<BasicTable table-title="文件列表">
<template #toolbar-tools>
<Space>
<Tooltip title="预览图片">

View File

@ -139,16 +139,13 @@ function handleMultiDelete() {
</script>
<template>
<Page :auto-content-height="true" content-class="flex gap-[8px]">
<Page :auto-content-height="true" content-class="flex gap-[8px] w-full">
<DeptTree
v-model:select-dept-id="selectDeptId"
class="w-[260px]"
@select="() => tableApi.query()"
/>
<BasicTable class="flex-1 overflow-hidden">
<template #toolbar-actions>
<span class="pl-[7px] text-[16px]">岗位列表</span>
</template>
<BasicTable class="flex-1 overflow-hidden" table-title="岗位列表">
<template #toolbar-tools>
<Space>
<a-button

View File

@ -118,10 +118,7 @@ function handleMultipleAuthCancel() {
<template>
<Page :auto-content-height="true">
<BasicTable>
<template #toolbar-actions>
<span class="pl-[7px] text-[16px]">已分配的用户列表</span>
</template>
<BasicTable table-title="已分配的用户列表">
<template #toolbar-tools>
<Space>
<a-button

View File

@ -131,6 +131,7 @@ export const drawerSchema: FormSchemaGetter = () => [
componentProps: {
allowClear: false,
options: getDictOptions(DictEnum.SYS_NORMAL_DISABLE),
getPopupContainer,
},
defaultValue: '0',
fieldName: 'status',
@ -138,6 +139,15 @@ export const drawerSchema: FormSchemaGetter = () => [
label: '角色状态',
rules: 'required',
},
{
component: 'Radio',
dependencies: {
show: () => false,
triggerFields: [''],
},
fieldName: 'menuCheckStrictly',
label: '菜单权限',
},
{
component: 'Input',
defaultValue: [],

View File

@ -163,10 +163,7 @@ function handleAssignRole(record: Recordable<any>) {
<template>
<Page :auto-content-height="true">
<BasicTable>
<template #toolbar-actions>
<span class="pl-[7px] text-[16px]">角色列表</span>
</template>
<BasicTable table-title="角色列表">
<template #toolbar-tools>
<Space>
<a-button

View File

@ -1,162 +0,0 @@
<script setup lang="ts">
import type { CheckboxChangeEvent } from 'ant-design-vue/es/checkbox/interface';
import type { DataNode } from 'ant-design-vue/es/tree';
import type { CheckInfo } from 'ant-design-vue/es/vc-tree/props';
import { computed, type PropType, ref, watch } from 'vue';
import { findGroupParentIds, treeToList } from '@vben/utils';
import { Checkbox, Tree } from 'ant-design-vue';
defineOptions({ inheritAttrs: false });
const props = defineProps({
checkStrictly: {
default: true,
type: Boolean,
},
menuTree: {
default: () => [],
type: Array as PropType<DataNode[]>,
},
});
const emit = defineEmits<{ checkStrictlyChange: [boolean] }>();
const expandStatus = ref(false);
const selectAllStatus = ref(false);
/**
* 后台的这个字段跟antd/ele是反的
* 组件库这个字段代表不关联
* 后台这个代表关联
*/
const innerCheckedStrictly = computed(() => {
return !props.checkStrictly;
});
function handleCheckStrictlyChange(e: CheckboxChangeEvent) {
emit('checkStrictlyChange', e.target.checked);
}
const associationText = computed(() => {
return props.checkStrictly ? '父子节点关联' : '父子节点独立';
});
/**
* 这个只用于界面显示
* 关联情况下 只会有最末尾的节点被选中
*/
const checkedKeys = defineModel('value', {
default: () => [],
type: Array as PropType<(number | string)[]>,
});
// ID
const allKeys = computed(() => {
return treeToList(props.menuTree).map((item: any) => item.id);
});
/** 已经选择的所有节点 包括子/父节点 */
const checkedMenuKeys = ref<(number | string)[]>([]);
/**
* 取第一次的menuTree id 设置到checkedMenuKeys
* 主要为了解决没有任何修改 直接点击保存的情况
*/
const stop = watch(
() => props.menuTree,
() => {
/** 节点关联情况下是不带父节点的 */
if (props.checkStrictly) {
/** 找到父节点 添加上 */
const parentIds = findGroupParentIds(
props.menuTree,
checkedKeys.value as any,
);
checkedMenuKeys.value = [...parentIds, ...checkedKeys.value];
} else {
/** 节点独立 这里是全部的节点 */
checkedMenuKeys.value = checkedKeys.value;
}
stop();
},
);
/**
*
* @param checkedKeys 已经选中的子节点的ID
* @param info info.halfCheckedKeys为父节点的ID
*/
type CheckedState<T = number | string> =
| { checked: T[]; halfChecked: T[] }
| T[];
function handleChecked(checkedKeys: CheckedState, info: CheckInfo) {
//
if (Array.isArray(checkedKeys)) {
const halfCheckedKeys: number[] = (info.halfCheckedKeys || []) as number[];
checkedMenuKeys.value = [...halfCheckedKeys, ...checkedKeys];
} else {
checkedMenuKeys.value = [...checkedKeys.checked];
}
}
function handleExpandChange(e: CheckboxChangeEvent) {
//
checkedKeys.value = e.target.checked ? allKeys.value : [];
//
checkedMenuKeys.value = e.target.checked ? allKeys.value : [];
}
const expandedKeys = ref<string[]>([]);
function handleExpandOrCollapseAll(e: CheckboxChangeEvent) {
const expand = e.target.checked;
expandedKeys.value = expand ? allKeys.value : [];
}
defineExpose({
getCheckedKeys: () => checkedMenuKeys.value,
});
</script>
<template>
<div class="bg-background w-full rounded-lg border-[1px] p-[12px]">
<div class="flex items-center gap-2 border-b-[1px] pb-2">
<span>节点状态: </span>
<span
:class="[props.checkStrictly ? 'text-primary' : 'text-red-500']"
class="font-semibold"
>
{{ associationText }}
</span>
</div>
<div
class="flex flex-wrap items-center justify-between border-b-[1px] py-2"
>
<Checkbox
v-model:checked="expandStatus"
@change="handleExpandOrCollapseAll"
>
展开/折叠全部
</Checkbox>
<Checkbox v-model:checked="selectAllStatus" @change="handleExpandChange">
全选/取消全选
</Checkbox>
<Checkbox :checked="checkStrictly" @change="handleCheckStrictlyChange">
父子节点关联
</Checkbox>
</div>
<div class="py-2">
<Tree
v-if="menuTree.length > 0"
v-model:check-strictly="innerCheckedStrictly"
v-model:checked-keys="checkedKeys"
v-model:expanded-keys="expandedKeys"
:checkable="true"
:field-names="{ title: 'label', key: 'id' }"
:selectable="false"
:tree-data="menuTree"
@check="handleChecked"
/>
</div>
</div>
</template>

View File

@ -188,6 +188,7 @@ export const drawerSchema: FormSchemaGetter = () => [
format: 'YYYY-MM-DD HH:mm:ss',
showTime: true,
valueFormat: 'YYYY-MM-DD HH:mm:ss',
getPopupContainer,
},
defaultValue: defaultExpireTime,
fieldName: 'expireTime',

View File

@ -155,10 +155,7 @@ const isSuperAdmin = computed(() => {
<template>
<Page v-if="isSuperAdmin" :auto-content-height="true">
<BasicTable>
<template #toolbar-actions>
<span class="pl-[7px] text-[16px]">租户列表</span>
</template>
<BasicTable table-title="租户列表">
<template #toolbar-tools>
<Space>
<a-button

View File

@ -142,10 +142,7 @@ const isSuperAdmin = computed(() => {
<template>
<Page v-if="isSuperAdmin" :auto-content-height="true">
<BasicTable>
<template #toolbar-actions>
<span class="pl-[7px] text-[16px]">租户套餐列表</span>
</template>
<BasicTable table-title="租户套餐列表">
<template #toolbar-tools>
<Space>
<a-button

View File

@ -5,6 +5,8 @@ import { useVbenDrawer } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { cloneDeep, eachTree, listToTree } from '@vben/utils';
import { omit } from 'lodash-es';
import { useVbenForm } from '#/adapter/form';
import { menuList, tenantPackageMenuTreeSelect } from '#/api/system/menu';
import {
@ -66,7 +68,9 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({
isUpdate.value = !!id;
if (isUpdate.value && id) {
const record = await packageInfo(id);
await formApi.setValues(record);
// menuIds menuIdsstring
// setupMenuTreeSelect
await formApi.setValues(omit(record, ['menuIds']));
}
/**
* 加载菜单树和已勾选菜单

View File

@ -200,10 +200,7 @@ function handleResetPwd(record: Recordable<any>) {
class="w-[260px]"
@select="() => tableApi.query()"
/>
<BasicTable class="flex-1 overflow-hidden">
<template #toolbar-actions>
<span class="pl-[7px] text-[16px]">用户列表</span>
</template>
<BasicTable class="flex-1 overflow-hidden" table-title="用户列表">
<template #toolbar-tools>
<Space>
<a-button

View File

@ -60,6 +60,24 @@ function genRoleOptionlabel(role: Role) {
]);
}
/**
* 岗位的加载
*/
async function setupPostOptions(deptId: number | string) {
const postListResp = await postOptionSelect(deptId);
const options = postListResp.map((item) => ({
label: item.postName,
value: item.postId,
}));
const placeholder = options.length > 0 ? '请选择' : '该部门下暂无岗位';
formApi.updateSchema([
{
componentProps: { options, placeholder },
fieldName: 'postIds',
},
]);
}
/**
* 初始化部门选择
*/
@ -80,22 +98,7 @@ async function setupDeptSelect() {
getPopupContainer,
async onSelect(deptId: number | string) {
/** 根据部门ID加载岗位 */
const postListResp = await postOptionSelect(deptId);
const options = postListResp.map((item) => ({
label: item.postName,
value: item.postId,
}));
const placeholder =
options.length > 0 ? '请选择' : '该部门下暂无岗位';
/**
* TODO: 可以考虑加上post编码
*/
formApi.updateSchema([
{
componentProps: { options, placeholder },
fieldName: 'postIds',
},
]);
await setupPostOptions(deptId);
/** 变化后需要重新选择岗位 */
formModel.postIds = [];
},
@ -185,6 +188,8 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({
//
formApi.setFieldValue('postIds', postIds),
formApi.setFieldValue('roleIds', roleIds),
// onSelect
setupPostOptions(user.deptId),
]);
}
drawerApi.drawerLoading(false);

View File

@ -199,10 +199,7 @@ function handleImport() {
<template>
<Page :auto-content-height="true">
<BasicTable>
<template #toolbar-actions>
<span class="pl-[7px] text-[16px]">代码生成列表</span>
</template>
<BasicTable table-title="代码生成列表">
<template #toolbar-tools>
<Space>
<a-button

View File

@ -11,7 +11,10 @@ setupVbenVxeTable({
vxeUI.setConfig({
grid: {
align: 'center',
border: true,
border: false,
columnConfig: {
resizable: true,
},
minHeight: 180,
proxyConfig: {
autoLoad: true,
@ -24,6 +27,7 @@ setupVbenVxeTable({
showResponseMsg: false,
},
round: true,
showOverflow: true,
size: 'small',
},
});

View File

@ -1,6 +1,6 @@
import type { RouteRecordRaw } from 'vue-router';
import { DEFAULT_HOME_PATH } from '@vben/constants';
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
import { AuthPageLayout } from '#/layouts';
import { $t } from '#/locales';
@ -37,6 +37,7 @@ const coreRoutes: RouteRecordRaw[] = [
},
name: 'Authentication',
path: '/auth',
redirect: LOGIN_PATH,
children: [
{
name: 'Login',

View File

@ -11,7 +11,10 @@ setupVbenVxeTable({
vxeUI.setConfig({
grid: {
align: 'center',
border: true,
border: false,
columnConfig: {
resizable: true,
},
minHeight: 180,
proxyConfig: {
autoLoad: true,
@ -24,6 +27,7 @@ setupVbenVxeTable({
showResponseMsg: false,
},
round: true,
showOverflow: true,
size: 'small',
},
});

View File

@ -1,6 +1,6 @@
import type { RouteRecordRaw } from 'vue-router';
import { DEFAULT_HOME_PATH } from '@vben/constants';
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
import { AuthPageLayout } from '#/layouts';
import { $t } from '#/locales';
@ -37,6 +37,7 @@ const coreRoutes: RouteRecordRaw[] = [
},
name: 'Authentication',
path: '/auth',
redirect: LOGIN_PATH,
children: [
{
name: 'Login',

View File

@ -40,8 +40,5 @@
"pkg-types": "catalog:",
"prettier": "catalog:",
"rimraf": "catalog:"
},
"devDependencies": {
"@types/chalk": "catalog:"
}
}

View File

@ -29,7 +29,7 @@ export class ModalApi {
} = options;
const defaultState: ModalState = {
bordered: false,
bordered: true,
centered: false,
class: '',
closeOnClickModal: true,

View File

@ -258,7 +258,13 @@ function handleFocusOutside(e: Event) {
v-if="showFooter"
ref="footerRef"
:class="
cn('flex-row items-center justify-end border-t p-2', footerClass)
cn(
'flex-row items-center justify-end p-2',
{
'border-t': bordered,
},
footerClass,
)
"
>
<slot name="prepend-footer"></slot>

View File

@ -8,6 +8,7 @@
/* base */
--vxe-ui-base-popup-border-color: hsl(var(--border));
--vxe-ui-input-disabled-color: hsl(var(--border) / 60%);
/* --vxe-ui-base-popup-box-shadow: 0px 12px 30px 8px rgb(0 0 0 / 50%); */

View File

@ -19,6 +19,14 @@ export interface VxePaginationInfo {
}
export interface VxeGridProps {
/**
*
*/
tableTitle?: string;
/**
*
*/
tableTitleHelp?: string;
/**
* class
*/

View File

@ -22,7 +22,7 @@ import { EmptyIcon } from '@vben/icons';
import { $t } from '@vben/locales';
import { usePreferences } from '@vben/preferences';
import { cloneDeep, cn, mergeWithArrayOverride } from '@vben/utils';
import { VbenLoading } from '@vben-core/shadcn-ui';
import { VbenHelpTooltip, VbenLoading } from '@vben-core/shadcn-ui';
import { VxeGrid, VxeUI } from 'vxe-table';
@ -51,6 +51,8 @@ const {
gridClass,
gridEvents,
formOptions,
tableTitle,
tableTitleHelp,
} = usePriorityValues(props, state);
const { isMobile } = usePreferences();
@ -81,31 +83,45 @@ const [Form, formApi] = useTableForm({
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
});
const showTableTitle = computed(() => {
return !!slots.tableTitle?.() || tableTitle.value;
});
const showToolbar = computed(() => {
return !!slots['toolbar-actions']?.() || !!slots['toolbar-tools']?.();
return (
!!slots['toolbar-actions']?.() ||
!!slots['toolbar-tools']?.() ||
showTableTitle.value
);
});
const toolbarOptions = computed(() => {
const slotActions = slots['toolbar-actions']?.();
const slotTools = slots['toolbar-tools']?.();
if (!showToolbar.value) {
return {};
}
// 使toolbar
//
return {
toolbarConfig: {
slots: {
...(slotActions || showTableTitle.value
? { buttons: 'toolbar-actions' }
: {}),
...(slotTools ? { tools: 'toolbar-tools' } : {}),
},
},
};
});
const options = computed(() => {
const slotActions = slots['toolbar-actions']?.();
const slotTools = slots['toolbar-tools']?.();
const globalGridConfig = VxeUI?.getConfig()?.grid ?? {};
const forceUseToolbarOptions = showToolbar.value
? {
toolbarConfig: {
slots: {
...(slotActions ? { buttons: 'toolbar-actions' } : {}),
...(slotTools ? { tools: 'toolbar-tools' } : {}),
},
},
}
: {};
const mergedOptions: VxeTableGridProps = cloneDeep(
mergeWithArrayOverride(
{},
forceUseToolbarOptions,
toolbarOptions.value,
toRaw(gridOptions.value),
globalGridConfig,
),
@ -166,7 +182,7 @@ const delegatedSlots = computed(() => {
const resultSlots: string[] = [];
for (const key of Object.keys(slots)) {
if (!['empty', 'form', 'loading'].includes(key)) {
if (!['empty', 'form', 'loading', 'toolbar-actions'].includes(key)) {
resultSlots.push(key);
}
}
@ -214,6 +230,7 @@ async function init() {
);
}
// formOptions
watch(
formOptions,
() => {
@ -256,6 +273,20 @@ onMounted(() => {
v-bind="options"
v-on="events"
>
<!-- 左侧操作区域或者title -->
<template v-if="showToolbar" #toolbar-actions="slotProps">
<slot v-if="showTableTitle" name="table-title">
<div class="mr-1 pl-1 text-[1rem]">
{{ tableTitle }}
<VbenHelpTooltip v-if="tableTitleHelp" trigger-class="pb-1">
{{ tableTitleHelp }}
</VbenHelpTooltip>
</div>
</slot>
<slot name="toolbar-actions" v-bind="slotProps"> </slot>
</template>
<!-- 继承默认的slot -->
<template
v-for="slotName in delegatedSlots"
:key="slotName"
@ -263,6 +294,8 @@ onMounted(() => {
>
<slot :name="slotName" v-bind="slotProps"></slot>
</template>
<!-- form表单 -->
<template #form>
<div v-if="formOptions" class="relative rounded py-3 pb-4">
<slot name="form">
@ -296,11 +329,13 @@ onMounted(() => {
></div>
</div>
</template>
<!-- loading -->
<template #loading>
<slot name="loading">
<VbenLoading :spinning="true" />
</slot>
</template>
<!-- 统一控状态 -->
<template #empty>
<slot name="empty">
<EmptyIcon class="mx-auto" />

View File

@ -245,11 +245,11 @@ export const useTabbarStore = defineStore('core-tabbar', {
// 下一个tab存在跳转到下一个
if (after) {
this._close(currentRoute.value);
this._close(tab);
await this._goToTab(after, router);
// 上一个tab存在跳转到上一个
} else if (before) {
this._close(currentRoute.value);
this._close(tab);
await this._goToTab(before, router);
} else {
console.error('Failed to close the tab; only one tab remains open.');

View File

@ -11,7 +11,10 @@ setupVbenVxeTable({
vxeUI.setConfig({
grid: {
align: 'center',
border: true,
border: false,
columnConfig: {
resizable: true,
},
minHeight: 180,
proxyConfig: {
autoLoad: true,
@ -24,6 +27,7 @@ setupVbenVxeTable({
showResponseMsg: false,
},
round: true,
showOverflow: true,
size: 'small',
},
});

View File

@ -1,6 +1,6 @@
import type { RouteRecordRaw } from 'vue-router';
import { DEFAULT_HOME_PATH } from '@vben/constants';
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
import { AuthPageLayout } from '#/layouts';
import { $t } from '#/locales';
@ -37,6 +37,7 @@ const coreRoutes: RouteRecordRaw[] = [
},
name: 'Authentication',
path: '/auth',
redirect: LOGIN_PATH,
children: [
{
name: 'Login',

View File

@ -76,10 +76,10 @@ function changeLoading() {
<template #extra>
<DocButton path="/components/common-ui/vben-vxe-table" />
</template>
<Grid>
<template #toolbar-actions>
<Grid table-title="基础列表" table-title-help="提示">
<!-- <template #toolbar-actions>
<Button class="mr-2" type="primary">左侧插槽</Button>
</template>
</template> -->
<template #toolbar-tools>
<Button class="mr-2" type="primary" @click="changeBorder">
{{ showBorder ? '隐藏' : '显示' }}边框

View File

@ -51,7 +51,7 @@ const [Grid, gridApi] = useVbenVxeGrid({ gridOptions });
<template>
<Page auto-content-height>
<Grid>
<Grid table-title="数据列表" table-title-help="提示">
<template #toolbar-tools>
<Button class="mr-2" type="primary" @click="() => gridApi.query()">
刷新当前页面

View File

@ -50,7 +50,7 @@ const collapseAll = () => {
<template>
<Page>
<Grid>
<Grid table-title="数据列表" table-title-help="提示">
<template #toolbar-tools>
<Button class="mr-2" type="primary" @click="expandAll">
展开全部

View File

@ -22,7 +22,7 @@ catalog:
'@ctrl/tinycolor': ^4.1.0
'@eslint/js': ^9.13.0
'@faker-js/faker': ^9.0.3
'@iconify/json': ^2.2.262
'@iconify/json': ^2.2.263
'@iconify/tailwind': ^1.1.3
'@iconify/vue': ^4.1.2
'@intlify/core-base': ^10.0.4
@ -35,22 +35,21 @@ catalog:
'@stylistic/stylelint-plugin': ^3.1.1
'@tailwindcss/nesting': 0.0.0-insiders.565cd3e
'@tailwindcss/typography': ^0.5.15
'@tanstack/vue-query': ^5.59.13
'@tanstack/vue-query': ^5.59.16
'@tanstack/vue-store': ^0.5.6
'@types/archiver': ^6.0.2
'@types/chalk': ^2.2.0
'@types/archiver': ^6.0.3
'@types/eslint': ^9.6.1
'@types/html-minifier-terser': ^7.0.2
'@types/jsonwebtoken': ^9.0.7
'@types/lodash.clonedeep': ^4.5.9
'@types/node': ^22.7.8
'@types/node': ^22.7.9
'@types/nprogress': ^0.2.3
'@types/postcss-import': ^14.0.3
'@types/qrcode': ^1.5.5
'@types/sortablejs': ^1.15.8
'@typescript-eslint/eslint-plugin': ^8.11.0
'@typescript-eslint/parser': ^8.11.0
'@vee-validate/zod': ^4.14.3
'@vee-validate/zod': ^4.14.4
'@vite-pwa/vitepress': ^0.5.3
'@vitejs/plugin-vue': ^5.1.4
'@vitejs/plugin-vue-jsx': ^4.0.1
@ -127,11 +126,11 @@ catalog:
postcss-antd-fixes: ^0.2.0
postcss-html: ^1.7.0
postcss-import: ^16.1.0
postcss-preset-env: ^10.0.7
postcss-preset-env: ^10.0.8
postcss-scss: ^4.0.9
prettier: ^3.3.3
prettier-plugin-tailwindcss: ^0.6.8
publint: ^0.2.11
publint: ^0.2.12
qrcode: ^1.5.4
radix-vue: ^1.9.7
resolve.exports: ^2.0.2
@ -157,15 +156,15 @@ catalog:
typescript: ^5.6.3
unbuild: ^2.0.0
unplugin-element-plus: ^0.8.0
vee-validate: ^4.14.3
vite: ^5.4.9
vee-validate: ^4.14.4
vite: ^5.4.10
vite-plugin-compression: ^0.5.1
vite-plugin-dts: 4.2.1
vite-plugin-html: ^3.2.2
vite-plugin-lazy-import: ^1.0.7
vite-plugin-lib-inject-css: ^2.1.1
vite-plugin-pwa: ^0.20.5
vite-plugin-vue-devtools: ^7.5.2
vite-plugin-vue-devtools: ^7.5.3
vitepress: ^1.4.1
vitepress-plugin-group-icons: ^1.3.0
vitest: ^2.1.3
@ -174,8 +173,8 @@ catalog:
vue-i18n: ^10.0.4
vue-router: ^4.4.5
vue-tsc: ^2.1.6
vxe-pc-ui: ^4.2.26
vxe-table: ^4.7.93
vxe-pc-ui: ^4.2.28
vxe-table: ^4.7.94
watermark-js-plus: ^1.5.7
zod: ^3.23.8
zod-defaults: ^0.1.3