!11 follow后端发布
* docs: readme * fix: missing formPath * chore: 去除锁定的esbuild版本 * perf: 去除debug组件 * perf: 参数键值 自动高度 * refactor: 代码生成配置页面重构 去除步骤条 * perf: 移除文件 * docs: 文件夹说明 * chore: 移除一些配置项 * chore: 注释优化 * refactor: 移除ele和naive目录 * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * perf: request support to set how to return response (#5436) * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * refactor: 登录超时的i18n * fix: requestClient缺失i18n内容 * refactor: 优化oss下载进度提示 * feat: 下载进度loading * fix: antd button icon style (#5421) * feat: oss下载进度(已下载的KB 无法作为进度显示 total返回为null) * fix: 下载文件时(responseType === 'blob')需要判断下载失败(返回json而非二进制)的情况 * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * refactor: 新增后跳转到未发布流程 * fix: same name route * chore: 调整为部署json类型 * fix: mouse events ignored on modal loading (#5409) * docs: update docs (#5408) * refactor: 移除已经弃用的方法 * refactor: follow官方handleRangeTimeValue更新 * chore: 删除文件夹(前端路由需要的) * chore: 修改本地路由写法(新版)/新增本地菜单图标 * fix: form update state error before form mounted (#5406) * fix: demos route fixed (#5405) * chore: 不使用基础布局(仅在顶级生效) * feat: modal state locked on submitting (#5401) * chore: 修改zIndex * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * refactor: fix popup component zIndex (#5397) * style: element plus loading style fixed (#5393) * perf: improve fieldMappingTime to support format function (#5392) * fix: hide root route in breadcrumb * feat: support set default props for drawer and modal (#5390) * fix: root router config fixed (#5389) * fix: 修改Vxe默认zIndex为995 解决右上角全屏后modal/drawer(zIndex: 1000)被遮挡 * feat: add `noBasicLayout` in route meta (#5386) * chore: wechat image * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * chore: 改为全局参数配置 去除局部参数 * fix: spinner may stop playing animation after dismiss (#5365) * style: popover bgColor is too close to common (#5364) * docs: version update * docs: changelog * chore: 文件上传 描述 * ci: retry deploy while faild * feat: 文件上传 进度条+提示文字 * feat: 文件上传 进度条 * feat: 上传文件格式说明 * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into warmflow * fix: useEcharts return invalid instance (#5360) * feat: popup component support overlay blur effect (#5359) * feat: improve `tippy` demo (#5357) * feat: integrate new component `Tippy` with demo (#5355) * chore: 优化表格图片显示 * perf: add nested modal demo (#5353) * chore: 默认显示右边的滚动条 防止出现滚动条被挤压 * perf: modal and drawer api support chain calls (#5351) * feat: allow close tab when mouse middle button click (#5347) * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into warmflow * refactor: 重构显示total的逻辑 * chore: 调整高度自适应代码 * chore: vxe升级4.10.0版本(锁定) * fix: 添加失效的option * fix: 需要为数组 * fix: locale switching logic correction (#5344) * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into warmflow * chore: 导入类型优化 解决eslint报红 * refactor: type/注释优化 去除大量any * fix: vxeGrid init without search form (#5342) * chore: 锁屏默认false 关闭该功能 * chore: 调整接口 * chore: update deps * fix: primaryColor calculation (#5337) * fix: form valid-error style in naive (#5336) * fix: form `fieldMappingTime` improve and `modelPropName` support (#5335) * fix: code lint * fix: form `fieldMappingTime` is not working (#5333) * chore: 选人组件样式 * fix: download from url triggered twice sometimes (#5319) * chore: 优化代码 * chore: 动态类名(无效)改为style * refactor: 字典相关功能重构 采用一个Map储存字典(之前为两个Map) * feat: 字典支持number类型存储 * chore: 调整样式 * chore: 修改选中border为1px * chore: 字段 * chore: 改为新窗口打开(适用于pdf/图片)而非直接下载 * chore: 更新样式 * chore: 更新字段 * chore: 改为computed * chore: 跳转到未发布流程tab * chore: 优化样式 * docs: readme * fix: name重复导致的404 * Merge branch 'dev' of https://gitee.com/dapppp/ruoyi-plus-vben5 into warmflow * chore: 使用legacy来保证copy的兼容性 * chore: 去除log 添加说明 * chore: 优化代码 * feat: 节点关联/节点独立的切换逻辑 * chore: remove logic * chore: vxe可编辑表格demo * chore: 不允许在按钮下添加数据 * docs: changelog * fix: wrong code * chore: 移除测试菜单 * chore: 优化代码 * refactor: 租户套餐菜单替换为新版 * refactor: 使用新版菜单勾选 * chore: 点行会勾选/取消全部权限 点权限不会勾选行 * chore: 全屏引导+样式优化 * chore: 调整间距 * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * feat: useEcharts exports echarts instance#5294 (#5299) * chore: update quick-start.md (#5303) * chore: updateCheckedNumber * refactor: 优化代码 * chore: 优化代码 * chore: 优化样式 * chore: keys依赖于menu 需要先加载menu * chore: 菜单加载完毕再显示 * feat: 新的菜单选择组件(beta) * chore: $t * chore: 测试菜单页面 * chore: 优化代码 * feat: 对ossId回显的支持 * chore: 只获取一次默认密码而非每次打开modal都获取 * fix: vben select placeholder color (#5286) * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * perf: format code with better style (#5283) * chore: 工作流演示站 * fix: sidebar preferences fixed (#5276) * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * fix: breadcrumb setting not valid for `header-sidebar-nav` layout (#5275) * fix: header logo may not be collapsed in `header-sidebar-nav` layout (#5274) * feat: new layout `sidebar nav with full header` (#5270) * feat: drawer close icon placement (#5269) * docs: update dialog and drawer docs * feat: drawer support destroy on close * feat: drawer support `onOpened` & `onClosed` * feat: modal support destroy on close * fix: wrong boolean * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * chore: 调整tab位置 * chore: 删除历史流程 改为tab切换 * fix: header-mixed layout side-menu active (#5265) * feat: header mixed layout (#5263) * chore: release 5.5.2 * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * chore: downgrade vue-tsc version * feat: header menu align support (#5256) * chore: update deps * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * chore: add apiSelect remote search demo (#5246) * chore: 审批改为description而非disabled的表单 * chore: 改为ts * chore: 错误的conetnt * refactor: 终止/转办/委托支持填写意见 * chore: 第一次拿到的是readonly的数据 如果需要修改 需要cloneDeep * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * fix: grid form submit button locale switch (#5205) * chore: 调整驳回 * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * fix: build error (#5199) * fix: esbuild自动升级导致运行/打包报错 * fix: esbuild自动升级导致运行/打包报错 * chore: 流程定义 激活改为switch * chore: 流程申请支持上传文件 * chore: title 审批通过 * fix: vxeGrid top padding (#5193) * fix: 表格排序翻页会丢失排序参数 * chore: 去除log打印 * chore: 流程监控 待办任务 * chore: 我发起的 * chore: 去除已经移除的菜单页面 * chore: 我的已办 * chore: 页面优化 * chore: 重置tooltip * feat: 我的抄送搜索/优化重复触发的接口 * feat: 流程定义 历史 * chore: 修改分类 * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * fix: grid tools in toolbar config not working as expected (#5190) * feat: add `resizable` and `ColPage` component (#5188) * chore: 条件 * chore: break-all * feat: 流程分类 搜索 * chore: 弹窗关闭后仍然显示表单浮层 * chore: 选人组件的样式 * chore: 搜索的样式 * chore: 漏掉的导入 * chore: 最大显示的头像数量 超过显示为省略号头像 * fix: 选人的一些问题 * Merge branch 'warmflow' of https://gitee.com/dapppp/ruoyi-plus-vben5 i… * chore: 没有更多数据了 * fix: sidebar header height (#5183) * chore: 搜索表单布局+申请人 * fix: remove the overlap caused by border-b (#5160) * docs: fix typos (#5169) * fix: resolve eslint errors as well as TS type errors (#5172) * chore: enter提交表单 * chore: 修改文案 * chore: 默认全部展开 * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * feat: page content class override (#5179) * fix: sidebar style on focus (#5178) * fix: 抄送选人 最右侧已选中删除item无效 * feat: 复制 * chore: 昵称过长的显示 * chore: 默认选中第一个 * chore: 修改relative位置 * chore: 搜索 * feat: 我的待办 - 搜索条件 * chore: 流程监控 - 待办任务页面的id不唯一 改为前端处理 * feat: 修改办理人 * chore: 流程干预 - 加签/减签 * chore: avatar大小 * chore: 抄送需要手动添加createByName * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * chore: 审批通过 抄送 * feat: 流程实例-流程预览 * chore: spell * chore: clientid * chore: 分类条件 * chore: 修改办理人 * chore: 更改postMessage参数 * chore: 内嵌iframe高度根据表单高度调整 * chore: 流程详情 * feat: 抄送选择 * chore: 调整分类树 * fix: user homePath no effect sometimes (#5166) * feat: form compact mode support (#5165) * fix: form auto submit no effect when showDefaultActions is false (#5163) * chore: 修改width * feat: 待办任务 * feat: 我的抄送 * chore: 流程定义 样式 * chore: 退回后重新申请 * chore: 请假申请布局 * chore: 请假申请-并行会签网关 * chore: 分类去除根目录 * chore: 详情modal(未完成) * chore: 请假申请根据不同状态显示按钮 * chore: 流程删除/撤销 * chore: 审批完成后刷新当前页 * feat: 选人组件(未完成) 加签减签 * docs: fix docs-link and add `EllipsisText` docs (#5158) * chore: 新窗口打开文件 * chore: 审批通过 * chore: 使用useEventListener替换原生 * chore: 字段错误 * chore: iframe通信 加载完毕后才显示表单 解决卡顿问题 * chore: 审批终止/驳回 * chore: 附件图标 * chore: process_running显示按钮 * chore: label错误 * chore: 保存的事件 * chore: 需要加上clientId * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * feat: form `colon` support (#5156) * chore: 完善请假申请 * feat: improve code login demo (#5154) * chore: 客户端管理 行高自适应 * chore: 内嵌表单的路径 * chore: 修改avatar背景色 * chore: 注释 * chore: activePath * chore: leave表单 * chore: 修改请假demo路径 * chore: categoryId * chore: 我的已办 * chore: 我发起的 * chore: loading * chore: 历史版本 * chore: 完善task api * chore: 隐藏'菜单加载中' * chore: missing import * feat: add demo for modify menu badge data * chore: 流程实例 * chore: 审批附件 * chore: 我的待办 提取公共组件 * chore: 流程部署 * chore: 新增/编辑/导出xml * chore: 流程定义(除历史版本) * feat: `autoActivateChild` support more layout mode (#5148) * feat: auto activate subMenu on select root menu (#5147) * fix: `disabledOnChangeListener` not work in form (#5146) * fix: login expired modal z-index (#5145) * feat: user-dropdown support `hover` trigger (#5143) * fix: pinInput value synchronous update (#5142) * fix: vxeGrid default sort data no effect in first query (#5141) * fix: vscode debug profile (#5140) * fix: form component events bind (#5137) * chore: 在线用户样式 开启虚拟滚动 * chore: 去掉个人中心 在线设备的分页 * chore: 去掉在线用户的分页 * chore: changelog * refactor: 获取字典的方法 提取公共函数 减少冗余代码 * fix: element plus validate failed style (#5130) * chore: 使用私有桶的提示 * feat: tabbar support mouse wheel vertical (#5129) * fix: form support `disabledOnInputListener` (#5127) * fix: form submission not appropriate (#5126) * Merge branch 'main' of https://gitee.com/dapppp/ruoyi-plus-vben5 into dev * chore: release 5.5.1 * feat: table search form visible control (#5121) * chore: 需要隐藏菜单 * chore: 我的待办 & 请假 * chore: 流程定义(未完成) * chore: 流程定义(开发中) * Merge branch 'main' of https://gitee.com/dapppp/ruoyi-plus-vben5 into dev * Merge branch 'main' of https://gitee.com/dapppp/ruoyi-plus-vben5 into dev * Merge branch 'main' of https://gitee.com/dapppp/ruoyi-plus-vben5 into dev * chore: version * chore: 锁定vxe-table版本 4.9.8版本存在样式问题 * chore: 暂时锁定cspell版本 * refactor: 由于不能输入 需要使用watch监听 * chore: https://gitee.com/dapppp/ruoyi-plus-vben5/issues/IB7ANL * chore: 移除冗余代码 * chore: 组件卸载时移除emitter * fix: the route path did not synchronize with the page (#4947) * style: typo (#4948) * chore: 替换为commonDownloadExcel * fix: 左边部门树错误emit导致会调用两次列表api * chore: label样式 * chore: 改为Textarea * chore: 滚动条宽度 * chore: 审批样式 * chore: 部门及以下或本人数据权限 * Merge branch 'main' of https://gitee.com/dapppp/ruoyi-plus-vben5 into dev * chore: 个人中心强退设备接口路径
This commit is contained in:
@@ -1 +1,2 @@
|
||||
export { default as MenuSelectTable } from './src/menu-select-table.vue';
|
||||
export { default as TreeSelectPanel } from './src/tree-select-panel.vue';
|
||||
|
85
apps/web-antd/src/components/tree/src/data.tsx
Normal file
85
apps/web-antd/src/components/tree/src/data.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
import type { ID } from '#/api/common';
|
||||
import type { MenuOption } from '#/api/system/menu/model';
|
||||
|
||||
import { h, markRaw } from 'vue';
|
||||
|
||||
import { FolderIcon, MenuIcon, OkButtonIcon, VbenIcon } from '@vben/icons';
|
||||
|
||||
export interface Permission {
|
||||
checked: boolean;
|
||||
id: ID;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export interface MenuPermissionOption extends MenuOption {
|
||||
permissions: Permission[];
|
||||
}
|
||||
|
||||
const menuTypes = {
|
||||
C: { icon: markRaw(MenuIcon), value: '菜单' },
|
||||
F: { icon: markRaw(OkButtonIcon), value: '按钮' },
|
||||
M: { icon: markRaw(FolderIcon), value: '目录' },
|
||||
};
|
||||
|
||||
export const nodeOptions = [
|
||||
{ label: '节点关联', value: true },
|
||||
{ label: '节点独立', value: false },
|
||||
];
|
||||
|
||||
export const columns: VxeGridProps['columns'] = [
|
||||
{
|
||||
type: 'checkbox',
|
||||
title: '菜单名称',
|
||||
field: 'label',
|
||||
treeNode: true,
|
||||
headerAlign: 'left',
|
||||
align: 'left',
|
||||
width: 230,
|
||||
},
|
||||
{
|
||||
title: '图标',
|
||||
field: 'icon',
|
||||
width: 80,
|
||||
slots: {
|
||||
default: ({ row }) => {
|
||||
if (row?.icon === '#') {
|
||||
return '';
|
||||
}
|
||||
return (
|
||||
<span class={'flex justify-center'}>
|
||||
<VbenIcon icon={row.icon} />
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
field: 'menuType',
|
||||
width: 80,
|
||||
slots: {
|
||||
default: ({ row }) => {
|
||||
const current = menuTypes[row.menuType as 'C' | 'F' | 'M'];
|
||||
if (!current) {
|
||||
return '未知';
|
||||
}
|
||||
return (
|
||||
<span class="flex items-center justify-center gap-1">
|
||||
{h(current.icon, { class: 'size-[18px]' })}
|
||||
<span>{current.value}</span>
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '权限标识',
|
||||
field: 'permissions',
|
||||
headerAlign: 'left',
|
||||
align: 'left',
|
||||
slots: {
|
||||
default: 'permissions',
|
||||
},
|
||||
},
|
||||
];
|
133
apps/web-antd/src/components/tree/src/helper.tsx
Normal file
133
apps/web-antd/src/components/tree/src/helper.tsx
Normal file
@@ -0,0 +1,133 @@
|
||||
import type { MenuPermissionOption } from './data';
|
||||
|
||||
import type { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import type { MenuOption } from '#/api/system/menu/model';
|
||||
|
||||
import { eachTree, treeToList } from '@vben/utils';
|
||||
|
||||
import { difference, isEmpty, isUndefined } from 'lodash-es';
|
||||
|
||||
/**
|
||||
* 权限列设置是否全选
|
||||
* @param record 行记录
|
||||
* @param checked 是否选中
|
||||
*/
|
||||
export function setPermissionsChecked(
|
||||
record: MenuPermissionOption,
|
||||
checked: boolean,
|
||||
) {
|
||||
if (record?.permissions?.length > 0) {
|
||||
// 全部设置为选中
|
||||
record.permissions.forEach((permission) => {
|
||||
permission.checked = checked;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前行 & 所有子节点选中状态
|
||||
* @param record 行
|
||||
* @param checked 是否选中
|
||||
*/
|
||||
export function rowAndChildrenChecked(
|
||||
record: MenuPermissionOption,
|
||||
checked: boolean,
|
||||
) {
|
||||
// 当前行选中
|
||||
setPermissionsChecked(record, checked);
|
||||
// 所有子节点选中
|
||||
record?.children?.forEach?.((permission) => {
|
||||
rowAndChildrenChecked(permission as MenuPermissionOption, checked);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* void方法 会直接修改原始数据
|
||||
* 将树结构转为 tree+permissions结构
|
||||
* @param menus 后台返回的menu
|
||||
*/
|
||||
export function menusWithPermissions(menus: MenuOption[]) {
|
||||
eachTree(menus, (item: MenuPermissionOption) => {
|
||||
if (item.children && item.children.length > 0) {
|
||||
/**
|
||||
* 所有为按钮的节点提取出来
|
||||
* 需要注意 这里需要过滤目录下直接是按钮的情况item.menuType !== 'M'
|
||||
* 将按钮往children添加而非加到permissions
|
||||
*/
|
||||
const permissions = item.children.filter(
|
||||
(child: MenuOption) => child.menuType === 'F' && item.menuType !== 'M',
|
||||
);
|
||||
// 取差集
|
||||
const diffCollection = difference(item.children, permissions);
|
||||
// 更新后的children 即去除按钮
|
||||
item.children = diffCollection;
|
||||
|
||||
// permissions作为字段添加到item
|
||||
const permissionsArr = permissions.map((permission) => {
|
||||
return {
|
||||
id: permission.id,
|
||||
label: permission.label,
|
||||
checked: false,
|
||||
};
|
||||
});
|
||||
item.permissions = permissionsArr;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置表格选中
|
||||
* @param checkedKeys 选中的keys
|
||||
* @param menus 菜单 转换后的菜单
|
||||
* @param tableApi api
|
||||
* @param association 是否节点关联
|
||||
*/
|
||||
export function setTableChecked(
|
||||
checkedKeys: (number | string)[],
|
||||
menus: MenuPermissionOption[],
|
||||
tableApi: ReturnType<typeof useVbenVxeGrid>['1'],
|
||||
association: boolean,
|
||||
) {
|
||||
// tree转list
|
||||
const menuList: MenuPermissionOption[] = treeToList(menus);
|
||||
// 拿到勾选的行数据
|
||||
let checkedRows = menuList.filter((item) => checkedKeys.includes(item.id));
|
||||
|
||||
/**
|
||||
* 节点独立切换到节点关联 只需要最末尾的数据 即children为空
|
||||
*/
|
||||
if (!association) {
|
||||
checkedRows = checkedRows.filter(
|
||||
(item) => isUndefined(item.children) || isEmpty(item.children),
|
||||
);
|
||||
}
|
||||
|
||||
// 设置行选中 & permissions选中
|
||||
checkedRows.forEach((item) => {
|
||||
tableApi.grid.setCheckboxRow(item, true);
|
||||
if (item?.permissions?.length > 0) {
|
||||
item.permissions.forEach((permission) => {
|
||||
if (checkedKeys.includes(permission.id)) {
|
||||
permission.checked = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 节点独立切换到节点关联
|
||||
* 勾选后还需要过滤权限没有任何勾选的情况 这时候取消行的勾选
|
||||
*/
|
||||
if (!association) {
|
||||
const emptyRows = checkedRows.filter((item) => {
|
||||
if (isUndefined(item.permissions) || isEmpty(item.permissions)) {
|
||||
return false;
|
||||
}
|
||||
return item.permissions.every(
|
||||
(permission) => permission.checked === false,
|
||||
);
|
||||
});
|
||||
// 设置为不选中
|
||||
tableApi.grid.setCheckboxRow(emptyRows, false);
|
||||
}
|
||||
}
|
62
apps/web-antd/src/components/tree/src/hook.tsx
Normal file
62
apps/web-antd/src/components/tree/src/hook.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import type { TourProps } from 'ant-design-vue';
|
||||
|
||||
import { defineComponent, ref } from 'vue';
|
||||
|
||||
import { useLocalStorage } from '@vueuse/core';
|
||||
import { Tour } from 'ant-design-vue';
|
||||
|
||||
/**
|
||||
* 全屏引导
|
||||
* @returns value
|
||||
*/
|
||||
export function useFullScreenGuide() {
|
||||
const open = ref(false);
|
||||
/**
|
||||
* 是否已读 只显示一次
|
||||
*/
|
||||
const read = useLocalStorage('menu_select_fullscreen_read', false);
|
||||
|
||||
function openGuide() {
|
||||
if (!read.value) {
|
||||
open.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
function closeGuide() {
|
||||
open.value = false;
|
||||
read.value = true;
|
||||
}
|
||||
|
||||
const steps: TourProps['steps'] = [
|
||||
{
|
||||
title: '提示',
|
||||
description: '点击这里可以全屏',
|
||||
target: () =>
|
||||
document.querySelector(
|
||||
'div#menu-select-table .vxe-tools--operate > button[title="全屏"]',
|
||||
)!,
|
||||
},
|
||||
];
|
||||
|
||||
const FullScreenGuide = defineComponent({
|
||||
name: 'FullScreenGuide',
|
||||
inheritAttrs: false,
|
||||
setup() {
|
||||
return () => (
|
||||
<Tour
|
||||
onClose={closeGuide}
|
||||
open={open.value}
|
||||
steps={steps}
|
||||
zIndex={9999}
|
||||
/>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
FullScreenGuide,
|
||||
openGuide,
|
||||
closeGuide,
|
||||
};
|
||||
}
|
412
apps/web-antd/src/components/tree/src/menu-select-table.vue
Normal file
412
apps/web-antd/src/components/tree/src/menu-select-table.vue
Normal file
@@ -0,0 +1,412 @@
|
||||
<!--
|
||||
不兼容也不会兼容一些错误用法
|
||||
比如: 菜单下放目录 菜单下放菜单
|
||||
比如: 按钮下放目录 按钮下放菜单 按钮下放按钮
|
||||
-->
|
||||
<script setup lang="tsx">
|
||||
import type { RadioChangeEvent } from 'ant-design-vue';
|
||||
|
||||
import type { MenuPermissionOption } from './data';
|
||||
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
import type { MenuOption } from '#/api/system/menu/model';
|
||||
|
||||
import { nextTick, onMounted, ref, shallowRef, watch } from 'vue';
|
||||
|
||||
import { cloneDeep, findGroupParentIds } from '@vben/utils';
|
||||
|
||||
import { Alert, Checkbox, RadioGroup, Space } from 'ant-design-vue';
|
||||
import { uniq } from 'lodash-es';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
|
||||
import { columns, nodeOptions } from './data';
|
||||
import {
|
||||
menusWithPermissions,
|
||||
rowAndChildrenChecked,
|
||||
setPermissionsChecked,
|
||||
setTableChecked,
|
||||
} from './helper';
|
||||
import { useFullScreenGuide } from './hook';
|
||||
|
||||
defineOptions({
|
||||
name: 'MenuSelectTable',
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
checkedKeys: (number | string)[];
|
||||
defaultExpandAll?: boolean;
|
||||
menus: MenuOption[];
|
||||
}>(),
|
||||
{
|
||||
/**
|
||||
* 是否默认展开全部
|
||||
*/
|
||||
defaultExpandAll: true,
|
||||
/**
|
||||
* 注意这里不是双向绑定 需要调用getCheckedKeys实例方法来获取真正选中的节点
|
||||
*/
|
||||
checkedKeys: () => [],
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* 是否节点关联
|
||||
*/
|
||||
const association = defineModel('association', {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
});
|
||||
|
||||
const gridOptions: VxeGridProps = {
|
||||
checkboxConfig: {
|
||||
// checkbox显示的字段
|
||||
labelField: 'label',
|
||||
// 是否严格模式 即节点不关联
|
||||
checkStrictly: !association.value,
|
||||
},
|
||||
size: 'small',
|
||||
columns,
|
||||
height: 'auto',
|
||||
keepSource: true,
|
||||
pagerConfig: {
|
||||
enabled: false,
|
||||
},
|
||||
proxyConfig: {
|
||||
enabled: false,
|
||||
},
|
||||
toolbarConfig: {
|
||||
// 自定义列
|
||||
custom: false,
|
||||
// 最大化
|
||||
zoom: true,
|
||||
// 刷新
|
||||
refresh: false,
|
||||
},
|
||||
rowConfig: {
|
||||
isHover: false,
|
||||
isCurrent: false,
|
||||
keyField: 'id',
|
||||
},
|
||||
/**
|
||||
* 开启虚拟滚动
|
||||
* 数据量小可以选择关闭
|
||||
* 如果遇到样式问题(空白、错位 滚动等)可以选择关闭虚拟滚动
|
||||
*/
|
||||
scrollY: {
|
||||
enabled: true,
|
||||
gt: 0,
|
||||
},
|
||||
treeConfig: {
|
||||
parentField: 'parentId',
|
||||
rowField: 'id',
|
||||
transform: false,
|
||||
},
|
||||
// 溢出换行显示
|
||||
showOverflow: false,
|
||||
};
|
||||
|
||||
/**
|
||||
* 用于界面显示选中的数量
|
||||
*/
|
||||
const checkedNum = ref(0);
|
||||
/**
|
||||
* 更新选中的数量
|
||||
*/
|
||||
function updateCheckedNumber() {
|
||||
checkedNum.value = getCheckedKeys().length;
|
||||
}
|
||||
|
||||
const [BasicTable, tableApi] = useVbenVxeGrid({
|
||||
gridOptions,
|
||||
gridEvents: {
|
||||
// 勾选事件
|
||||
checkboxChange: (params) => {
|
||||
// 选中还是取消选中
|
||||
const checked = params.checked;
|
||||
// 行
|
||||
const record = params.row;
|
||||
if (association.value) {
|
||||
// 节点关联
|
||||
// 设置所有子节点选中状态
|
||||
rowAndChildrenChecked(record, checked);
|
||||
} else {
|
||||
// 节点独立
|
||||
// 点行会勾选/取消全部权限 点权限不会勾选行
|
||||
setPermissionsChecked(record, checked);
|
||||
}
|
||||
updateCheckedNumber();
|
||||
},
|
||||
// 全选事件
|
||||
checkboxAll: (params) => {
|
||||
const records = params.$grid.getData();
|
||||
records.forEach((item) => {
|
||||
rowAndChildrenChecked(item, params.checked);
|
||||
});
|
||||
updateCheckedNumber();
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 设置表格选中
|
||||
* @param menus menu
|
||||
* @param keys 选中的key
|
||||
* @param triggerOnchange 节点独立情况 不需要触发onChange(false)
|
||||
*/
|
||||
function setCheckedByKeys(
|
||||
menus: MenuPermissionOption[],
|
||||
keys: (number | string)[],
|
||||
triggerOnchange: boolean,
|
||||
) {
|
||||
menus.forEach((item) => {
|
||||
// 设置行选中
|
||||
if (keys.includes(item.id)) {
|
||||
tableApi.grid.setCheckboxRow(item, true);
|
||||
}
|
||||
// 设置权限columns选中
|
||||
if (item.permissions && item.permissions.length > 0) {
|
||||
// 遍历 设置勾选
|
||||
item.permissions.forEach((permission) => {
|
||||
if (keys.includes(permission.id)) {
|
||||
permission.checked = true;
|
||||
// 手动触发onChange来选中 节点独立情况不需要处理
|
||||
triggerOnchange && handlePermissionChange(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
// 设置children选中
|
||||
if (item.children && item.children.length > 0) {
|
||||
setCheckedByKeys(item.children as any, keys, triggerOnchange);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const { FullScreenGuide, openGuide } = useFullScreenGuide();
|
||||
onMounted(() => {
|
||||
/**
|
||||
* 加载表格数据 转为指定结构
|
||||
*/
|
||||
watch(
|
||||
() => props.menus,
|
||||
async (menus) => {
|
||||
const clonedMenus = cloneDeep(menus);
|
||||
menusWithPermissions(clonedMenus);
|
||||
// console.log(clonedMenus);
|
||||
await tableApi.grid.loadData(clonedMenus);
|
||||
// 展开全部 默认true
|
||||
if (props.defaultExpandAll) {
|
||||
await nextTick();
|
||||
setExpandOrCollapse(true);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* 节点关联变动 更新表格勾选效果
|
||||
*/
|
||||
watch(association, (value) => {
|
||||
tableApi.setGridOptions({
|
||||
checkboxConfig: {
|
||||
checkStrictly: !value,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* checkedKeys依赖menus
|
||||
* 要注意加载顺序
|
||||
* !!!要在外部确保menus先加载!!!
|
||||
*/
|
||||
watch(
|
||||
() => props.checkedKeys,
|
||||
(value) => {
|
||||
const allCheckedKeys = uniq([...value]);
|
||||
// 获取表格data 如果checkedKeys在menus的watch之前触发 这里会拿到空 导致勾选异常
|
||||
const records = tableApi.grid.getData();
|
||||
setCheckedByKeys(records, allCheckedKeys, association.value);
|
||||
updateCheckedNumber();
|
||||
|
||||
// 全屏引导
|
||||
setTimeout(openGuide, 1000);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
// 缓存上次(切换节点关系前)选中的keys
|
||||
const lastCheckedKeys = shallowRef<(number | string)[]>([]);
|
||||
/**
|
||||
* 节点关联变动 事件
|
||||
*/
|
||||
async function handleAssociationChange(e: RadioChangeEvent) {
|
||||
lastCheckedKeys.value = getCheckedKeys();
|
||||
// 清空全部permissions选中
|
||||
const records = tableApi.grid.getData();
|
||||
records.forEach((item) => {
|
||||
rowAndChildrenChecked(item, false);
|
||||
});
|
||||
// 需要清空全部勾选
|
||||
await tableApi.grid.clearCheckboxRow();
|
||||
// 滚动到顶部
|
||||
await tableApi.grid.scrollTo(0, 0);
|
||||
|
||||
// 节点切换 不同的选中
|
||||
setTableChecked(lastCheckedKeys.value, records, tableApi, !e.target.value);
|
||||
|
||||
updateCheckedNumber();
|
||||
}
|
||||
|
||||
/**
|
||||
* 全部展开/折叠
|
||||
* @param expand 是否展开
|
||||
*/
|
||||
function setExpandOrCollapse(expand: boolean) {
|
||||
tableApi.grid?.setAllTreeExpand(expand);
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限列表 checkbox勾选的事件
|
||||
* @param row 行
|
||||
*/
|
||||
function handlePermissionChange(row: any) {
|
||||
// 节点关联
|
||||
if (association.value) {
|
||||
const checkedPermissions = row.permissions.filter(
|
||||
(item: any) => item.checked === true,
|
||||
);
|
||||
// 有一条选中 则整个行选中
|
||||
if (checkedPermissions.length > 0) {
|
||||
tableApi.grid.setCheckboxRow(row, true);
|
||||
}
|
||||
// 无任何选中 则整个行不选中
|
||||
if (checkedPermissions.length === 0) {
|
||||
tableApi.grid.setCheckboxRow(row, false);
|
||||
}
|
||||
}
|
||||
// 节点独立 不处理
|
||||
updateCheckedNumber();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取勾选的key
|
||||
* @param records 行记录列表
|
||||
* @param addCurrent 是否添加当前行的id
|
||||
*/
|
||||
function getKeys(records: MenuPermissionOption[], addCurrent: boolean) {
|
||||
const allKeys: (number | string)[] = [];
|
||||
records.forEach((item) => {
|
||||
// 处理children
|
||||
if (item.children && item.children.length > 0) {
|
||||
const keys = getKeys(item.children as MenuPermissionOption[], addCurrent);
|
||||
allKeys.push(...keys);
|
||||
} else {
|
||||
// 当前行的id
|
||||
addCurrent && allKeys.push(item.id);
|
||||
// 当前行权限id 获取已经选中的
|
||||
if (item.permissions && item.permissions.length > 0) {
|
||||
const ids = item.permissions
|
||||
.filter((m) => m.checked === true)
|
||||
.map((m) => m.id);
|
||||
allKeys.push(...ids);
|
||||
}
|
||||
}
|
||||
});
|
||||
return uniq(allKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取选中的key
|
||||
*/
|
||||
function getCheckedKeys() {
|
||||
// 节点关联
|
||||
if (association.value) {
|
||||
const records = tableApi?.grid?.getCheckboxRecords?.() ?? [];
|
||||
// 子节点
|
||||
const nodeKeys = getKeys(records, true);
|
||||
// 所有父节点
|
||||
const parentIds = findGroupParentIds(props.menus, nodeKeys as number[]);
|
||||
// 拼接 去重
|
||||
const realKeys = uniq([...parentIds, ...nodeKeys]);
|
||||
return realKeys;
|
||||
}
|
||||
// 节点独立
|
||||
|
||||
// 勾选的行
|
||||
const records = tableApi?.grid?.getCheckboxRecords?.() ?? [];
|
||||
// 全部数据 用于获取permissions
|
||||
const allRecords = tableApi?.grid?.getData?.() ?? [];
|
||||
// 表格已经选中的行ids
|
||||
const checkedIds = records.map((item) => item.id);
|
||||
// 所有已经勾选权限的ids
|
||||
const permissionIds = getKeys(allRecords, false);
|
||||
// 合并 去重
|
||||
const allIds = uniq([...checkedIds, ...permissionIds]);
|
||||
return allIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* 暴露给外部使用 获取已选中的key
|
||||
*/
|
||||
defineExpose({
|
||||
getCheckedKeys,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex h-full flex-col" id="menu-select-table">
|
||||
<BasicTable>
|
||||
<template #toolbar-actions>
|
||||
<RadioGroup
|
||||
v-model:value="association"
|
||||
:options="nodeOptions"
|
||||
button-style="solid"
|
||||
option-type="button"
|
||||
@change="handleAssociationChange"
|
||||
/>
|
||||
<Alert class="mx-2" type="info">
|
||||
<template #message>
|
||||
<div>
|
||||
已选中
|
||||
<span class="text-primary mx-1 font-semibold">
|
||||
{{ checkedNum }}
|
||||
</span>
|
||||
个节点
|
||||
</div>
|
||||
</template>
|
||||
</Alert>
|
||||
</template>
|
||||
<template #toolbar-tools>
|
||||
<Space>
|
||||
<a-button @click="setExpandOrCollapse(false)">
|
||||
{{ $t('pages.common.collapse') }}
|
||||
</a-button>
|
||||
<a-button @click="setExpandOrCollapse(true)">
|
||||
{{ $t('pages.common.expand') }}
|
||||
</a-button>
|
||||
</Space>
|
||||
</template>
|
||||
<template #permissions="{ row }">
|
||||
<div class="flex flex-wrap gap-x-3 gap-y-1">
|
||||
<Checkbox
|
||||
v-for="permission in row.permissions"
|
||||
:key="permission.id"
|
||||
v-model:checked="permission.checked"
|
||||
@change="() => handlePermissionChange(row)"
|
||||
>
|
||||
{{ permission.label }}
|
||||
</Checkbox>
|
||||
</div>
|
||||
</template>
|
||||
</BasicTable>
|
||||
<!-- 全屏引导 -->
|
||||
<FullScreenGuide />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.ant-alert) {
|
||||
padding: 4px 8px;
|
||||
}
|
||||
</style>
|
@@ -3,7 +3,9 @@ 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, nextTick, onMounted, type PropType, ref, watch } from 'vue';
|
||||
import type { PropType, SetupContext } from 'vue';
|
||||
|
||||
import { computed, nextTick, onMounted, ref, useSlots, watch } from 'vue';
|
||||
|
||||
import { findGroupParentIds, treeToList } from '@vben/utils';
|
||||
|
||||
@@ -108,8 +110,8 @@ const stop = watch([checkedKeys, () => props.treeData], () => {
|
||||
* @param info info.halfCheckedKeys为父节点的ID
|
||||
*/
|
||||
type CheckedState<T = number | string> =
|
||||
| { checked: T[]; halfChecked: T[] }
|
||||
| T[];
|
||||
| T[]
|
||||
| { checked: T[]; halfChecked: T[] };
|
||||
function handleChecked(checkedStateKeys: CheckedState, info: CheckInfo) {
|
||||
// 数组的话为节点关联
|
||||
if (Array.isArray(checkedStateKeys)) {
|
||||
@@ -157,6 +159,8 @@ onMounted(async () => {
|
||||
expandedKeys.value = allKeys.value;
|
||||
}
|
||||
});
|
||||
|
||||
const slots = useSlots() as SetupContext['slots'];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -205,7 +209,7 @@ onMounted(async () => {
|
||||
@check="handleChecked"
|
||||
>
|
||||
<template
|
||||
v-for="slotName in Object.keys($slots)"
|
||||
v-for="slotName in Object.keys(slots)"
|
||||
:key="slotName"
|
||||
#[slotName]="data"
|
||||
>
|
||||
|
Reference in New Issue
Block a user