Flex Menu check获取菜单多选项的选中情况

菜单多选项获取方法
本文介绍了一种使用Menu对象生成菜单并实现菜单项多选的功能。通过获取点击菜单项的所有兄弟节点信息,并检查每个节点的选中状态,从而确定当前菜单中哪些选项被选中。
使用Menu对象生成菜单,菜单项为多选。
如何获取当前菜单项中,哪些被选中呢?

目前发现的方法之一(可能还有其它办法):

event 为MenuEvent
//获取点中的菜单项的全部兄弟节点,包括自己
var rowsInfo:Array = event.menu.getRowInfo();
//判断第i+1个节点是否被选中
rowsInfo[i].data.toggled = true;
<template> <view class="dropdown-wrapper"> <!-- 插槽:触发器 --> <slot name="reference" :open="open" :close="close" :toggle="toggle"> <!-- 默认触发器 --> <view class="dropdown-trigger-default" data-dropdown-trigger @tap="open"> 点击择 ▼ </view> </slot> <!-- 浮动下拉菜单 --> <view v-if="visible" ref="dropdownRef" class="zy-popup-dropdown-menu" :style="{ top: `${top}px`, left: `${left}px`, transform: position }" @touchmove.stop > <view class="dropdown-content"> <view v-for="item in props.data" :key="item[props.valueKey]" class="dropdown-item" :class="{ 'is-selected': props.modelValue === item[props.valueKey] }" @tap="handleSelect(item)" > <text class="item-label">{{ item[props.labelKey] }}</text> <text v-if="props.modelValue === item[props.valueKey]" class="icon-check"></text> </view> </view> </view> </view> </template> <script setup> import { ref, nextTick, onUnmounted } from 'vue' // ---------------------- // Props 定义 // ---------------------- const props = defineProps({ // 数据源 [{ label: 'xxx', value: '1' }] data: { type: Array, required: true }, // 当前选中值(v-model) modelValue: { type: [String, Number, null], default: null }, // 显示字段名 labelKey: { type: String, default: 'name' }, // 值字段名 valueKey: { type: String, default: 'value' }, // id名 trigger: { type: String, default: 'value' } }) // ---------------------- // Emits // ---------------------- const emit = defineEmits(['update:modelValue', 'change', 'open', 'close']) // ---------------------- // 内部状态 // ---------------------- const visible = ref(false) const top = ref(0) const left = ref(0) const position = ref('translateY(8px)') const dropdownRef = ref(null) let observer = null // ---------------------- // 获取触发器位置(核心方法) function getTriggerRect() { return new Promise(resolve => { console.log('🚀 ~ getTriggerRect ~ props.trigger:', props.trigger) uni .createSelectorQuery() .select('#' + props.trigger) .boundingClientRect(rect => { if (rect) { console.log('✅ 定位成功:', rect) resolve(rect) } else { console.error('❌ 查询失败,请确认 ID 是否正确或已渲染') resolve(null) } }) .exec() }) } // ---------------------- // 打开下拉框 // ---------------------- const open = async () => { if (visible.value) { close() return } const rect = await getTriggerRect() if (!rect) return // 获取窗口高度 const res = await new Promise(resolve => { uni.createSelectorQuery().selectViewport().scrollOffset().exec(resolve) }) const scrollInfo = res[0] const windowHeight = scrollInfo.windowHeight || scrollInfo.height const maxHeight = 486 const needUpward = windowHeight - rect.bottom < maxHeight // 设置位置 left.value = rect.left top.value = needUpward ? rect.top - maxHeight - 8 : rect.bottom + 8 position.value = needUpward ? 'translateY(-8px)' : 'translateY(8px)' visible.value = true emit('open', props.modelValue) nextTick(() => { bindOutsideClickListener() bindScrollListener() }) } // ---------------------- // 关闭 & 切换 & 择 // ---------------------- const close = () => { if (!visible.value) return visible.value = false emit('close') removeListeners() } const toggle = () => { visible.value ? close() : open() } const handleSelect = item => { const value = item[props.valueKey] const label = item[props.labelKey] if (props.modelValue !== value) { emit('update:modelValue', value) emit('change', { value, label }) } close() } // ---------------------- // 外部点击关闭 // ---------------------- const bindOutsideClickListener = () => { const handler = e => { // 阻止事件冒泡时也能监听到页面点击 const path = e.path || [] const isInside = path.some(node => node?.dataset?.dropdownTrigger) if (!isInside) close() } uni.$on('onPageTap', handler) observer = { ...observer, cleanupTap: () => uni.$off('onPageTap', handler) } } // ---------------------- // 页面滚动关闭 // ---------------------- const bindScrollListener = () => { const scrollHandler = () => close() uni.$on('onPageScroll', scrollHandler) observer = { ...observer, cleanupScroll: () => uni.$off('onPageScroll', scrollHandler) } } // ---------------------- // 移除监听 // ---------------------- const removeListeners = () => { observer?.cleanupTap?.() observer?.cleanupScroll?.() observer = null } const openByEvent = async e => { const target = e.currentTarget || e.target if (!target) { console.error('[Dropdown] openByEvent 缺少 event 对象') return } const rect = await new Promise(resolve => { const query = uni.createSelectorQuery() query.select(`#${target.id}`).boundingClientRect() query.exec(res => resolve(res[0])) }) if (!rect) { // 回退:直接用 event 自带的信息 const { top, bottom, left, height } = e.detail?.offsetTop !== undefined ? e : target const fallbackRect = { top, bottom: bottom || top + height, left, width: target.offsetWidth || 80, height: target.offsetHeight || 40 } console.warn('[Dropdown] 使用 event 数据回退定位', fallbackRect) doOpen(fallbackRect) } else { doOpen(rect) } } // 抽离打开逻辑 const doOpen = rect => { const res = uni.getSystemInfoSync() const windowHeight = res.windowHeight const maxHeight = 486 const needUpward = windowHeight - rect.bottom < maxHeight left.value = rect.left top.value = needUpward ? rect.top - maxHeight - 8 : rect.bottom + 8 position.value = needUpward ? 'translateY(-8px)' : 'translateY(8px)' visible.value = true emit('open', props.modelValue) nextTick(() => { bindOutsideClickListener() bindScrollListener() }) } // ---------------------- // 暴露方法给父组件调用 // ---------------------- defineExpose({ open, close, toggle }) // ---------------------- // 卸载清理 // ---------------------- onUnmounted(() => { removeListeners() }) </script> <style scoped> /* 整体容器 */ .dropdown-wrapper { display: inline-block; } /* 默认触发器样式 */ .dropdown-trigger-default { display: inline-flex; align-items: center; justify-content: center; padding: 8px 16px; background-color: #fff; border: 1px solid #ddd; border-radius: 6px; font-size: 15px; color: #333; } /* 下拉菜单主体 */ .zy-popup-dropdown-menu { position: fixed; width: 215px; max-height: 486px; background-color: #ffffff; border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); z-index: 9999; display: flex; flex-direction: column; } /* 内容区可滚动 */ .dropdown-content { flex: 1; height: 0; /* 防止撑开 */ max-height: 486px; overflow-y: auto; -webkit-overflow-scrolling: touch; /* iOS 平滑滚动 */ } /* 每一项 */ .dropdown-item { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; font-size: 15px; color: #333333; border-bottom: 1px solid #f5f5f5; } .dropdown-item:last-child { border-bottom: none; } /* 选中项样式 */ .dropdown-item.is-selected { color: #0f56d5; font-weight: 500; } /* 文本自动换行 */ .item-label { flex: 1; word-break: break-word; line-height: 1.4; text-align: left; } /* 对号图标 */ .icon-check { font-family: 'erda' !important; /* 可替换为 iconfont 字体 */ font-size: 16px; margin-left: 8px; color: #0f56d5; } </style> 希望再滚动的时候关闭当前打开的组件
10-15
<template> <div class="app-container"> <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch"> <el-form-item label="角色名称" prop="roleName"> <el-input v-model="queryParams.roleName" placeholder="请输入角色名称" clearable style="width: 240px" @keyup.enter.native="handleQuery" /> </el-form-item> <el-form-item label="权限字符" prop="roleKey"> <el-input v-model="queryParams.roleKey" placeholder="请输入权限字符" clearable style="width: 240px" @keyup.enter.native="handleQuery" /> </el-form-item> <el-form-item label="状态" prop="status"> <el-select v-model="queryParams.status" placeholder="角色状态" clearable style="width: 240px" > <el-option v-for="dict in dict.type.sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" /> </el-select> </el-form-item> <el-form-item label="创建时间"> <el-date-picker v-model="dateRange" style="width: 240px" value-format="yyyy-MM-dd" type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" ></el-date-picker> </el-form-item> <el-form-item> <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button> <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button> </el-form-item> </el-form> <el-row :gutter="10" class="mb8"> <el-col :span="1.5"> <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd" v-hasPermi="['system:role:add']" >新增</el-button> </el-col> <el-col :span="1.5"> <el-button type="success" plain icon="el-icon-edit" size="mini" :disabled="single" @click="handleUpdate" v-hasPermi="['system:role:edit']" >修改</el-button> </el-col> <el-col :span="1.5"> <el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete" v-hasPermi="['system:role:remove']" >删除</el-button> </el-col> <el-col :span="1.5"> <el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport" v-hasPermi="['system:role:export']" >导出</el-button> </el-col> <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar> </el-row> <el-table v-loading="loading" :data="roleList" @selection-change="handleSelectionChange"> <el-table-column type="selection" width="55" align="center" /> <el-table-column label="角色编号" prop="roleId" width="120" /> <el-table-column label="角色名称" prop="roleName" :show-overflow-tooltip="true" width="150" /> <el-table-column label="权限字符" prop="roleKey" :show-overflow-tooltip="true" width="150" /> <el-table-column label="显示顺序" prop="roleSort" width="100" /> <el-table-column label="状态" align="center" width="100"> <template slot-scope="scope"> <el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)" ></el-switch> </template> </el-table-column> <el-table-column label="创建时间" align="center" prop="createTime" width="180"> <template slot-scope="scope"> <span>{{ parseTime(scope.row.createTime) }}</span> </template> </el-table-column> <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> <template slot-scope="scope" v-if="scope.row.roleId !== 1"> <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:role:edit']" >修改</el-button> <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)" v-hasPermi="['system:role:remove']" >删除</el-button> <el-dropdown size="mini" @command="(command) => handleCommand(command, scope.row)" v-hasPermi="['system:role:edit']"> <el-button size="mini" type="text" icon="el-icon-d-arrow-right">更</el-button> <el-dropdown-menu slot="dropdown"> <el-dropdown-item command="handleDataScope" icon="el-icon-circle-check" v-hasPermi="['system:role:edit']">数据权限</el-dropdown-item> <el-dropdown-item command="handleAuthUser" icon="el-icon-user" v-hasPermi="['system:role:edit']">分配用户</el-dropdown-item> </el-dropdown-menu> </el-dropdown> </template> </el-table-column> </el-table> <pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" /> <!-- 添加或修改角色配置对话框 --> <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body> <el-form ref="form" :model="form" :rules="rules" label-width="100px"> <el-form-item label="角色名称" prop="roleName"> <el-input v-model="form.roleName" placeholder="请输入角色名称" /> </el-form-item> <el-form-item prop="roleKey"> <span slot="label"> <el-tooltip content="控制器中定义的权限字符,如:@PreAuthorize(`@ss.hasRole('admin')`)" placement="top"> <i class="el-icon-question"></i> </el-tooltip> 权限字符 </span> <el-input v-model="form.roleKey" placeholder="请输入权限字符" /> </el-form-item> <el-form-item label="角色顺序" prop="roleSort"> <el-input-number v-model="form.roleSort" controls-position="right" :min="0" /> </el-form-item> <el-form-item label="状态"> <el-radio-group v-model="form.status"> <el-radio v-for="dict in dict.type.sys_normal_disable" :key="dict.value" :label="dict.value" >{{dict.label}}</el-radio> </el-radio-group> </el-form-item> <el-form-item label="菜单权限"> <el-checkbox v-model="menuExpand" @change="handleCheckedTreeExpand($event, 'menu')">展开/折叠</el-checkbox> <el-checkbox v-model="menuNodeAll" @change="handleCheckedTreeNodeAll($event, 'menu')">全/全不</el-checkbox> <el-checkbox v-model="form.menuCheckStrictly" @change="handleCheckedTreeConnect($event, 'menu')">父子联动</el-checkbox> <el-tree class="tree-border" :data="menuOptions" show-checkbox ref="menu" node-key="id" :check-strictly="!form.menuCheckStrictly" empty-text="加载中,请稍候" :props="defaultProps" ></el-tree> </el-form-item> <el-form-item label="备注"> <el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input> </el-form-item> </el-form> <div slot="footer" class="dialog-footer"> <el-button type="primary" @click="submitForm">确 定</el-button> <el-button @click="cancel">取 消</el-button> </div> </el-dialog> <!-- 分配角色数据权限对话框 --> <el-dialog :title="title" :visible.sync="openDataScope" width="500px" append-to-body> <el-form :model="form" label-width="80px"> <el-form-item label="角色名称"> <el-input v-model="form.roleName" :disabled="true" /> </el-form-item> <el-form-item label="权限字符"> <el-input v-model="form.roleKey" :disabled="true" /> </el-form-item> <el-form-item label="权限范围"> <el-select v-model="form.dataScope" @change="dataScopeSelectChange"> <el-option v-for="item in dataScopeOptions" :key="item.value" :label="item.label" :value="item.value" ></el-option> </el-select> </el-form-item> <el-form-item label="数据权限" v-show="form.dataScope == 2"> <el-checkbox v-model="deptExpand" @change="handleCheckedTreeExpand($event, 'dept')">展开/折叠</el-checkbox> <el-checkbox v-model="deptNodeAll" @change="handleCheckedTreeNodeAll($event, 'dept')">全/全不</el-checkbox> <el-checkbox v-model="form.deptCheckStrictly" @change="handleCheckedTreeConnect($event, 'dept')">父子联动</el-checkbox> <el-tree class="tree-border" :data="deptOptions" show-checkbox default-expand-all ref="dept" node-key="id" :check-strictly="!form.deptCheckStrictly" empty-text="加载中,请稍候" :props="defaultProps" ></el-tree> </el-form-item> </el-form> <div slot="footer" class="dialog-footer"> <el-button type="primary" @click="submitDataScope">确 定</el-button> <el-button @click="cancelDataScope">取 消</el-button> </div> </el-dialog> </div> </template> <script> import { listRole, getRole, delRole, addRole, updateRole, dataScope, changeRoleStatus, deptTreeSelect } from "@/api/system/role" import { treeselect as menuTreeselect, roleMenuTreeselect } from "@/api/system/menu" export default { name: "Role", dicts: ['sys_normal_disable'], data() { return { // 遮罩层 loading: true, // 选中数组 ids: [], // 非单个禁用 single: true, // 非个禁用 multiple: true, // 显示搜索条件 showSearch: true, // 总条数 total: 0, // 角色表格数据 roleList: [], // 弹出层标题 title: "", // 是否显示弹出层 open: false, // 是否显示弹出层(数据权限) openDataScope: false, menuExpand: false, menuNodeAll: false, deptExpand: true, deptNodeAll: false, // 日期范围 dateRange: [], // 数据范围项 dataScopeOptions: [ { value: "1", label: "全部数据权限" }, { value: "2", label: "自定数据权限" }, { value: "3", label: "本部门数据权限" }, { value: "4", label: "本部门及以下数据权限" }, { value: "5", label: "仅本人数据权限" } ], // 菜单列表 menuOptions: [], // 部门列表 deptOptions: [], // 查询参数 queryParams: { pageNum: 1, pageSize: 10, roleName: undefined, roleKey: undefined, status: undefined }, // 表单参数 form: {}, defaultProps: { children: "children", label: "label" }, // 表单校验 rules: { roleName: [ { required: true, message: "角色名称不能为空", trigger: "blur" } ], roleKey: [ { required: true, message: "权限字符不能为空", trigger: "blur" } ], roleSort: [ { required: true, message: "角色顺序不能为空", trigger: "blur" } ] } } }, created() { this.getList() }, methods: { /** 查询角色列表 */ getList() { this.loading = true listRole(this.addDateRange(this.queryParams, this.dateRange)).then(response => { this.roleList = response.rows this.total = response.total this.loading = false } ) }, /** 查询菜单树结构 */ getMenuTreeselect() { menuTreeselect().then(response => { this.menuOptions = response.data }) }, // 所有菜单节点数据 getMenuAllCheckedKeys() { // 目前被选中菜单节点 let checkedKeys = this.$refs.menu.getCheckedKeys() // 半选中菜单节点 let halfCheckedKeys = this.$refs.menu.getHalfCheckedKeys() checkedKeys.unshift.apply(checkedKeys, halfCheckedKeys) return checkedKeys }, // 所有部门节点数据 getDeptAllCheckedKeys() { // 目前被选中的部门节点 let checkedKeys = this.$refs.dept.getCheckedKeys() // 半选中的部门节点 let halfCheckedKeys = this.$refs.dept.getHalfCheckedKeys() checkedKeys.unshift.apply(checkedKeys, halfCheckedKeys) return checkedKeys }, /** 根据角色ID查询菜单树结构 */ getRoleMenuTreeselect(roleId) { return roleMenuTreeselect(roleId).then(response => { this.menuOptions = response.menus return response }) }, /** 根据角色ID查询部门树结构 */ getDeptTree(roleId) { return deptTreeSelect(roleId).then(response => { this.deptOptions = response.depts return response }) }, // 角色状态修改 handleStatusChange(row) { let text = row.status === "0" ? "启用" : "停用" this.$modal.confirm('确认要"' + text + '""' + row.roleName + '"角色吗?').then(function() { return changeRoleStatus(row.roleId, row.status) }).then(() => { this.$modal.msgSuccess(text + "成功") }).catch(function() { row.status = row.status === "0" ? "1" : "0" }) }, // 取消按钮 cancel() { this.open = false this.reset() }, // 取消按钮(数据权限) cancelDataScope() { this.openDataScope = false this.reset() }, // 表单重置 reset() { if (this.$refs.menu != undefined) { this.$refs.menu.setCheckedKeys([]) } this.menuExpand = false, this.menuNodeAll = false, this.deptExpand = true, this.deptNodeAll = false, this.form = { roleId: undefined, roleName: undefined, roleKey: undefined, roleSort: 0, status: "0", menuIds: [], deptIds: [], menuCheckStrictly: true, deptCheckStrictly: true, remark: undefined } this.resetForm("form") }, /** 搜索按钮操作 */ handleQuery() { this.queryParams.pageNum = 1 this.getList() }, /** 重置按钮操作 */ resetQuery() { this.dateRange = [] this.resetForm("queryForm") this.handleQuery() }, // 选中数据 handleSelectionChange(selection) { this.ids = selection.map(item => item.roleId) this.single = selection.length!=1 this.multiple = !selection.length }, // 更操作触发 handleCommand(command, row) { switch (command) { case "handleDataScope": this.handleDataScope(row) break case "handleAuthUser": this.handleAuthUser(row) break default: break } }, // 树权限(展开/折叠) handleCheckedTreeExpand(value, type) { if (type == 'menu') { let treeList = this.menuOptions for (let i = 0; i < treeList.length; i++) { this.$refs.menu.store.nodesMap[treeList[i].id].expanded = value } } else if (type == 'dept') { let treeList = this.deptOptions for (let i = 0; i < treeList.length; i++) { this.$refs.dept.store.nodesMap[treeList[i].id].expanded = value } } }, // 树权限(全/全不) handleCheckedTreeNodeAll(value, type) { if (type == 'menu') { this.$refs.menu.setCheckedNodes(value ? this.menuOptions: []) } else if (type == 'dept') { this.$refs.dept.setCheckedNodes(value ? this.deptOptions: []) } }, // 树权限(父子联动) handleCheckedTreeConnect(value, type) { if (type == 'menu') { this.form.menuCheckStrictly = value ? true: false } else if (type == 'dept') { this.form.deptCheckStrictly = value ? true: false } }, /** 新增按钮操作 */ handleAdd() { this.reset() this.getMenuTreeselect() this.open = true this.title = "添加角色" }, /** 修改按钮操作 */ handleUpdate(row) { this.reset() const roleId = row.roleId || this.ids const roleMenu = this.getRoleMenuTreeselect(roleId) getRole(roleId).then(response => { this.form = response.data this.open = true this.$nextTick(() => { roleMenu.then(res => { let checkedKeys = res.checkedKeys checkedKeys.forEach((v) => { this.$nextTick(()=>{ this.$refs.menu.setChecked(v, true ,false) }) }) }) }) }) this.title = "修改角色" }, /** 择角色权限范围触发 */ dataScopeSelectChange(value) { if(value !== '2') { this.$refs.dept.setCheckedKeys([]) } }, /** 分配数据权限操作 */ handleDataScope(row) { this.reset() const deptTreeSelect = this.getDeptTree(row.roleId) getRole(row.roleId).then(response => { this.form = response.data this.openDataScope = true this.$nextTick(() => { deptTreeSelect.then(res => { this.$refs.dept.setCheckedKeys(res.checkedKeys) }) }) }) this.title = "分配数据权限" }, /** 分配用户操作 */ handleAuthUser: function(row) { const roleId = row.roleId this.$router.push("/system/role-auth/user/" + roleId) }, /** 提交按钮 */ submitForm: function() { this.$refs["form"].validate(valid => { if (valid) { if (this.form.roleId != undefined) { this.form.menuIds = this.getMenuAllCheckedKeys() updateRole(this.form).then(response => { this.$modal.msgSuccess("修改成功") this.open = false this.getList() }) } else { this.form.menuIds = this.getMenuAllCheckedKeys() addRole(this.form).then(response => { this.$modal.msgSuccess("新增成功") this.open = false this.getList() }) } } }) }, /** 提交按钮(数据权限) */ submitDataScope: function() { if (this.form.roleId != undefined) { this.form.deptIds = this.getDeptAllCheckedKeys() dataScope(this.form).then(response => { this.$modal.msgSuccess("修改成功") this.openDataScope = false this.getList() }) } }, /** 删除按钮操作 */ handleDelete(row) { const roleIds = row.roleId || this.ids this.$modal.confirm('是否确认删除角色编号为"' + roleIds + '"的数据项?').then(function() { return delRole(roleIds) }).then(() => { this.getList() this.$modal.msgSuccess("删除成功") }).catch(() => {}) }, /** 导出按钮操作 */ handleExport() { this.download('system/role/export', { ...this.queryParams }, `role_${new Date().getTime()}.xlsx`) } } } </script>美化
09-24
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值