12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115 |
- <template>
- <el-container class="sa-access panel-block">
- <el-main id="scrollWrap" class="sa-p-0" v-loading="state.loading">
- <template v-for="(val, level) in state.show" :key="level">
- <el-scrollbar height="100%" v-loading="val.loading">
- <div class="title sa-flex sa-row-between">
- <span>{{ val.pdata?.title }}</span>
- <slot name="add" :pdata="val.pdata">
- <template v-if="val.pdata">
- <el-checkbox
- v-if="multiple"
- class="sa-m-r-8"
- v-model="val.pdata.checked"
- :indeterminate="val.pdata.indeterminate"
- label="全选"
- @update:model-value="handleSelect($event, val.pdata)"
- />
- </template>
- <template v-if="type == 'list'">
- <el-button class="is-link" type="primary" @click="onAdd(val.pdata.id, level)"
- >+添加</el-button
- >
- <!-- <el-button
- class="is-link"
- type="success"
- @click="initMenuPermissions"
- :loading="initLoading"
- >
- 初始化菜单
- </el-button> -->
- </template>
- </slot>
- </div>
- <template v-if="val.data?.length > 0">
- <sa-draggable
- v-model="val.data"
- :animation="300"
- handle=".sortable-drag"
- item-key="element"
- @end="onEnd($event, level, val.pdata)"
- >
- <template #item="{ element, index }">
- <div
- :class="['node', 'sa-flex sa-row-between', val.index == index ? 'is-active' : '']"
- @click="onClick(element, index, level)"
- >
- <!-- multiple -->
- <el-checkbox
- v-if="multiple"
- class="sa-m-r-8"
- v-model="element.checked"
- :indeterminate="element.indeterminate"
- @click.stop
- @change="handleSelect($event, element)"
- />
- <slot class="label" :data="element" :level="level">
- <div class="item sa-flex sa-row-between">
- <div class="sa-flex">
- <sa-svg
- v-if="type == 'list'"
- class="sortable-drag sa-m-r-8"
- name="sa-round"
- ></sa-svg>
- <sa-icon class="icon sa-m-r-4" :icon="element.icon" size="16" />
- <div>{{ element.title }}</div>
- </div>
- <div v-if="type == 'list'" class="sa-flex">
- <el-icon
- class="edit sa-m-r-8"
- @click.stop="onEdit(element.id, index, level)"
- >
- <Edit />
- </el-icon>
- <el-popconfirm
- width="fit-content"
- confirm-button-text="确认"
- cancel-button-text="取消"
- title="确认删除这条记录?"
- @confirm="onDelete(element.id, index, level)"
- >
- <template #reference>
- <el-icon class="delete sa-m-r-8" @click.stop>
- <Delete />
- </el-icon>
- </template>
- </el-popconfirm>
- </div>
- </div>
- </slot>
- <div class="arrow-right">
- <el-icon v-if="!isEmpty(element.children)">
- <ArrowRight />
- </el-icon>
- </div>
- </div>
- </template>
- </sa-draggable>
- </template>
- <template v-if="!val.loading && val.data.length == 0">
- <div class="empty">暂无数据</div>
- </template>
- </el-scrollbar>
- </template>
- </el-main>
- </el-container>
- </template>
- <script setup>
- import { nextTick, onMounted, reactive, watch, ref } from 'vue';
- import admin from '@/app/admin/api';
- import SaDraggable from 'vuedraggable';
- import { useModal } from '@/sheep/hooks';
- import { ElMessage, ElMessageBox } from 'element-plus';
- import { menuRulesData } from '@/sheep/local-data/admin';
- import { isEmpty } from 'lodash';
- import AccessEdit from '../edit.vue';
- const emit = defineEmits(['update:modelValue']);
- const props = defineProps({
- type: String,
- isChangeParentId: Boolean,
- role_id: {
- type: [String, Number],
- default: 0,
- },
- modelValue: {
- type: Array,
- default: [],
- },
- multiple: {
- type: Boolean,
- default: false,
- },
- });
- let manualChecked = false;
- let isSelectingPermission = false; // 新增标志,表示正在进行权限选择操作
- const state = reactive({
- loading: false,
- app: [],
- checkedIds: props.modelValue,
- newIds: [],
- show: {},
- });
- // 初始化loading状态
- const initLoading = ref(false);
- watch(
- () => props.modelValue,
- (newValue) => {
- // 如果正在进行权限选择操作,只更新checkedIds,不触发其他计算
- if (isSelectingPermission) {
- state.checkedIds = newValue || [];
- return;
- }
- state.checkedIds = newValue || [];
- // 当modelValue变化时,重新计算权限状态(无论数据是否为空都要计算)
- if (state.app.length > 0) {
- let appItem = initAppItem();
- initCalculate(state.app, appItem);
- calculateShow(appItem);
- }
- },
- { immediate: true },
- );
- // 监听app数据变化,确保新增时也能显示
- watch(
- () => state.app,
- (newApp) => {
- // 如果正在进行权限选择操作,跳过所有处理
- if (isSelectingPermission) {
- return;
- }
- // 无论数据是否为空都要初始化显示状态
- let appItem = initAppItem();
- if (newApp && newApp.length > 0) {
- initCalculate(newApp, appItem);
- }
- calculateShow(appItem);
- },
- );
- // 数据转换函数:将新接口数据转换为组件需要的格式
- function transformPermissionData(data) {
- if (!Array.isArray(data)) {
- console.warn('transformPermissionData: data is not an array', data);
- return [];
- }
- const result = data.map((item) => {
- const transformed = {
- id: item.id,
- title: item.name || item.label, // 优先使用name,备用label
- icon: item.logo || 'menu', // 使用logo作为icon,默认为menu
- children:
- item.children && Array.isArray(item.children)
- ? transformPermissionData(item.children)
- : [],
- // 根据hasRelevance字段设置选中状态
- checked: item.hasRelevance === '1',
- indeterminate: false,
- // 保留原始数据以备后用
- _original: item,
- };
- console.log('🔄 transformPermissionData - 转换结果:', transformed);
- return transformed;
- });
- console.log('🔄 transformPermissionData - 最终结果:', result);
- return result;
- }
- async function getData() {
- state.loading = true;
- try {
- let permissionData = [];
- // 根据type类型获取不同的数据
- if (props.type === 'select' && props.role_id) {
- // 权限选择模式且有角色ID,获取角色详情中的权限数据
- console.log('🔍 获取角色权限数据, role_id:', props.role_id);
- const roleResponse = await admin.auth.role.detail(props.role_id);
- if (roleResponse.code == 200 && roleResponse.data && roleResponse.data.permissions) {
- console.log('🔍 getData - 角色权限数据:', roleResponse.data.permissions);
- permissionData = roleResponse.data.permissions;
- }
- } else {
- // 其他模式,获取完整权限树
- const response = await admin.auth.access.getTree();
- if (response.success && response.data !== null && response.data !== undefined) {
- console.log('🔍 getData - 权限树数据:', response.data);
- permissionData = response.data;
- }
- }
- // 转换数据格式(即使是空数组也要处理)
- const transformedData = Array.isArray(permissionData)
- ? transformPermissionData(permissionData)
- : [];
- console.log('🔍 getData - 转换后数据:', transformedData);
- state.app = transformedData;
- // 初始化显示状态(无论数据是否为空都要初始化)
- let appItem = initAppItem();
- if (transformedData.length > 0) {
- initCalculate(transformedData, appItem);
- }
- // 如果不是权限选择操作导致的变化,才重置展开状态
- if (!isSelectingPermission) {
- calculateShow(appItem);
- }
- } catch (error) {
- console.error('获取权限数据异常:', error);
- state.app = [];
- // 异常情况下也要初始化显示状态
- let appItem = initAppItem();
- if (!isSelectingPermission) {
- calculateShow(appItem);
- }
- }
- state.loading = false;
- }
- // 🎯 新方案:保持选中状态的数据刷新函数
- async function refreshDataKeepSelected() {
- // 1. 保存当前所有层级的选中状态
- const selectedStates = [];
- for (let level = 0; level < Object.keys(state.show).length; level++) {
- const showData = state.show[level];
- if (showData && showData.index !== null && showData.index !== undefined && showData.data) {
- const selectedItem = showData.data[showData.index];
- if (selectedItem && selectedItem.id) {
- selectedStates.push({
- level: level,
- itemId: selectedItem.id,
- itemTitle: selectedItem.title,
- selectedIndex: showData.index,
- });
- console.log(
- `💾 保存第${level}层选中状态: ${selectedItem.title} (ID: ${selectedItem.id}, 索引: ${showData.index})`,
- );
- }
- }
- }
- console.log('💾 保存的选中状态:', selectedStates);
- // 2. 重新获取数据(这会重置state.show)
- await getData();
- // 3. 恢复选中状态
- if (selectedStates.length > 0) {
- console.log('🔄 开始恢复选中状态...');
- // 逐层恢复选中状态
- for (const selectedState of selectedStates) {
- const { level, itemId, itemTitle } = selectedState;
- console.log(`🔄 恢复第${level}层选中状态: ${itemTitle} (ID: ${itemId})`);
- // 在当前层级查找目标项目
- const currentLevelData = state.show[level] ? state.show[level].data : [];
- const foundIndex = currentLevelData.findIndex((item) => item.id === itemId);
- if (foundIndex !== -1) {
- const foundItem = currentLevelData[foundIndex];
- console.log(`✅ 找到目标项目: ${foundItem.title} (索引: ${foundIndex})`);
- // 使用calculateShow来正确展开并选中这一层
- await calculateShow(foundItem, foundIndex, level);
- console.log(`✅ 第${level}层选中状态恢复完成`);
- } else {
- console.log(`⚠️ 第${level}层找不到目标项目 ID: ${itemId}`);
- break; // 如果某一层找不到,停止恢复
- }
- }
- }
- console.log('✅ 选中状态恢复完成');
- }
- // 🎯 新方案:局部更新数据函数
- async function updateLocalData(operation, newData, parentId, targetId) {
- console.log(`🔄 局部更新数据: ${operation}`, { newData, parentId, targetId });
- if (operation === 'add') {
- // 新增:将新数据添加到指定父级的children中
- await handleLocalAdd(newData, parentId);
- } else if (operation === 'edit') {
- // 编辑:找到目标项目并更新其数据
- await handleLocalEdit(newData, targetId);
- } else if (operation === 'delete') {
- // 删除:从数据中移除目标项目
- await handleLocalDelete(targetId);
- }
- console.log('✅ 局部数据更新完成');
- }
- // 处理新增操作
- async function handleLocalAdd(newData, parentId) {
- console.log(`📝 处理新增: 父级ID=${parentId}`, newData);
- if (!newData) {
- console.error('❌ 新增数据为空');
- return;
- }
- // 转换新数据格式
- const transformedNewData = {
- id: newData.id,
- title: newData.name || '新权限',
- icon: newData.logo || 'menu',
- children: [],
- _original: newData,
- };
- if (!parentId || parentId === '' || parentId === '0') {
- // 添加到根级
- state.app.push(transformedNewData);
- // 更新第0层的数据
- if (state.show[0]) {
- state.show[0].data = state.app;
- }
- console.log('✅ 已添加到根级');
- } else {
- // 添加到指定父级
- const parentItem = findItemById(state.app, parentId);
- if (parentItem) {
- if (!parentItem.children) {
- parentItem.children = [];
- }
- parentItem.children.push(transformedNewData);
- // 更新对应层级的显示数据
- updateShowDataForParent(parentId, parentItem.children);
- console.log(`✅ 已添加到父级 ${parentId}`);
- } else {
- console.error(`❌ 找不到父级项目 ID: ${parentId}`);
- }
- }
- }
- // 处理编辑操作
- async function handleLocalEdit(newData, targetId) {
- console.log(`✏️ 处理编辑: 目标ID=${targetId}`, newData);
- if (!newData) {
- console.error('❌ 编辑数据为空');
- return;
- }
- // 🔧 验证数据格式
- if (!newData.name && !newData.id) {
- console.error('❌ 编辑数据格式不正确,缺少必要字段:', newData);
- return;
- }
- const targetItem = findItemById(state.app, targetId);
- if (targetItem) {
- // 更新项目数据
- targetItem.title = newData.name || newData.label || targetItem.title;
- targetItem.icon = newData.logo || newData.icon || targetItem.icon;
- targetItem._original = { ...targetItem._original, ...newData };
- // 更新所有相关的显示数据
- updateAllShowData();
- console.log(`✅ 已更新项目 ${targetId}:`, {
- title: targetItem.title,
- icon: targetItem.icon,
- });
- } else {
- console.error(`❌ 找不到目标项目 ID: ${targetId}`);
- }
- }
- // 处理删除操作
- async function handleLocalDelete(targetId) {
- console.log(`🗑️ 处理删除: 目标ID=${targetId}`);
- const result = removeItemById(state.app, targetId);
- if (result.success) {
- // 更新所有相关的显示数据
- updateAllShowData();
- console.log(`✅ 已删除项目 ${targetId}`);
- } else {
- console.error(`❌ 找不到要删除的项目 ID: ${targetId}`);
- }
- }
- // 递归查找项目
- function findItemById(items, targetId) {
- for (const item of items) {
- if (item.id === targetId) {
- return item;
- }
- if (item.children && item.children.length > 0) {
- const found = findItemById(item.children, targetId);
- if (found) return found;
- }
- }
- return null;
- }
- // 递归删除项目
- function removeItemById(items, targetId) {
- for (let i = 0; i < items.length; i++) {
- if (items[i].id === targetId) {
- items.splice(i, 1);
- return { success: true };
- }
- if (items[i].children && items[i].children.length > 0) {
- const result = removeItemById(items[i].children, targetId);
- if (result.success) return result;
- }
- }
- return { success: false };
- }
- // 更新指定父级的显示数据
- function updateShowDataForParent(parentId, newChildren) {
- // 遍历所有层级,找到对应的父级并更新其子级数据
- for (const [level, showData] of Object.entries(state.show)) {
- if (showData.data) {
- const parentItem = showData.data.find((item) => item.id === parentId);
- if (parentItem) {
- // 找到了父级,更新下一层级的数据
- const nextLevel = parseInt(level) + 1;
- if (state.show[nextLevel]) {
- state.show[nextLevel].data = newChildren;
- }
- break;
- }
- }
- }
- }
- // 更新所有显示数据
- function updateAllShowData() {
- // 更新第0层
- if (state.show[0]) {
- state.show[0].data = state.app;
- }
- // 更新其他层级
- for (const [level, showData] of Object.entries(state.show)) {
- if (parseInt(level) > 0 && showData.pdata && showData.pdata.children) {
- showData.data = showData.pdata.children;
- }
- }
- }
- function initAppItem() {
- let appItem = {
- id: 0,
- title: '应用',
- checked: true,
- indeterminate: false,
- children: state.app,
- };
- let allData = [];
- flattenData(state.app, allData);
- if (state.checkedIds.length == 0) {
- appItem.checked = false;
- appItem.indeterminate = false;
- } else {
- if (allData.length == state.checkedIds.length) {
- appItem.checked = true;
- appItem.indeterminate = false;
- } else {
- appItem.checked = false;
- appItem.indeterminate = true;
- }
- }
- return appItem;
- }
- // 初始化选中数据
- function initCalculate(data, parent = {}) {
- data.forEach((item) => {
- item.parent = parent;
- // 兼容数字和字符串ID的比较
- const itemIdStr = String(item.id);
- const itemIdNum = Number(item.id);
- const isChecked = state.checkedIds.some((checkedId) => {
- const checkedIdStr = String(checkedId);
- const checkedIdNum = Number(checkedId);
- return checkedIdStr === itemIdStr || checkedIdNum === itemIdNum;
- });
- // 先递归处理子项
- if (!isEmpty(item.children)) {
- initCalculate(item.children, item);
- // 检查子项的选中状态
- const checkedChildren = item.children.filter((child) => child.checked);
- const indeterminateChildren = item.children.filter((child) => child.indeterminate);
- // 如果父级本身被明确选中,保持选中状态
- if (isChecked) {
- item.checked = true;
- item.indeterminate = false;
- } else if (checkedChildren.length === item.children.length) {
- // 所有子项都选中
- item.checked = true;
- item.indeterminate = false;
- } else if (checkedChildren.length > 0 || indeterminateChildren.length > 0) {
- // 部分子项选中或有半选状态
- item.checked = false;
- item.indeterminate = true;
- } else {
- // 没有子项选中
- item.checked = false;
- item.indeterminate = false;
- }
- } else {
- // 叶子节点,直接根据是否在选中列表中设置状态
- item.checked = isChecked;
- item.indeterminate = false;
- }
- });
- }
- // 扁平化数据
- function flattenData(data, arr) {
- data.forEach((item) => {
- arr.push(item);
- if (!isEmpty(item.children)) {
- flattenData(item.children, arr);
- }
- });
- }
- async function calculateShow(item, index = null, level = 0) {
- // 选中展开
- if (level != 0) {
- state.show[level].index = index;
- }
- // 清除多余数据
- for (let key in state.show) {
- if (key > level) {
- delete state.show[key];
- }
- }
- // loading
- state.show[Number(level) + 1] = {
- loading: true,
- };
- if (isEmpty(item.children)) {
- item.children = [];
- }
- state.show[Number(level) + 1] = {
- index: null,
- data: item.children,
- pdata: item,
- loading: false,
- };
- }
- async function onClick(item, index, level) {
- calculateShow(item, index, level);
- nextTick(() => {
- let left =
- document.getElementById('scrollWrap').scrollWidth -
- document.getElementById('scrollWrap').offsetWidth;
- document.getElementById('scrollWrap').scrollTo({
- top: 0,
- left: left,
- behavior: 'smooth',
- });
- });
- }
- function handleSelect(checked, item) {
- // 设置标志,阻止watch触发
- isSelectingPermission = true;
- manualChecked = true;
- // 计算所有子元素
- doChecked(item, checked);
- // 计算父元素
- doCheckedParent(item);
- // 收集选中的权限ID
- const newIds = [];
- getCheckedIds(state.app, newIds);
- // 直接发送事件,不更新state.newIds避免触发watch
- emit('update:modelValue', newIds);
- // 延迟重置标志
- setTimeout(() => {
- isSelectingPermission = false;
- }, 50);
- }
- function doChecked(item, checked) {
- item.checked = checked;
- item.indeterminate = false;
- if (!isEmpty(item.children)) {
- item.children.forEach((i) => {
- i.checked = checked;
- i.indeterminate = false;
- doChecked(i, checked);
- });
- }
- }
- function doCheckedParent(i) {
- // 如果有父级
- if (!isEmpty(i.parent)) {
- if (!isEmpty(i.parent.children)) {
- // 部分选中
- i.parent.checked = false;
- i.parent.indeterminate = true;
- // 全选中
- if (i.parent.children.every((k) => k.checked)) {
- i.parent.checked = true;
- i.parent.indeterminate = false;
- }
- // 未选中
- if (!i.parent.children.some((k) => k.checked || k.indeterminate)) {
- i.parent.checked = false;
- i.parent.indeterminate = false;
- }
- }
- doCheckedParent(i.parent);
- }
- }
- async function onEnd(e, level, pdata) {
- if (e.newIndex != e.oldIndex) {
- try {
- // 拖动元素修改seq(排序)
- const draggedItem = pdata.children[e.oldIndex];
- const targetItem = pdata.children[e.newIndex];
- // 更新排序值
- const newSeq = targetItem._original?.seq ? targetItem._original.seq - 1 : e.newIndex - 1;
- // 调用编辑接口更新排序
- await admin.auth.access.edit({
- id: draggedItem.id,
- seq: newSeq,
- });
- // 🎯 新方案:拖拽排序后保持选中状态刷新
- console.log('✅ 拖拽排序成功,保持选中状态刷新数据');
- await refreshDataKeepSelected();
- } catch (error) {
- console.error('更新排序失败:', error);
- // 如果更新失败,重新获取数据恢复状态
- await getData();
- }
- }
- }
- function onAdd(id = null, level) {
- useModal(
- AccessEdit,
- {
- title: '新建',
- type: 'add',
- parent_id: id,
- },
- {
- confirm: async (result) => {
- console.log('📝 新增回调数据:', result);
- if (result && result.event === 'confirm') {
- // 🎯 新方案:新增操作成功,保持选中状态刷新
- console.log('✅ 新增操作成功,保持选中状态刷新数据');
- await refreshDataKeepSelected();
- } else {
- console.warn('⚠️ 新增操作未确认,不执行任何操作');
- }
- },
- },
- );
- }
- function onEdit(id, index, level) {
- useModal(
- AccessEdit,
- {
- title: '编辑',
- type: 'edit',
- id: id,
- },
- {
- confirm: async (result) => {
- console.log('📝 编辑回调数据:', result);
- if (result && result.event === 'confirm') {
- // 🎯 新方案:编辑操作成功,保持选中状态刷新
- console.log('✅ 编辑操作成功,保持选中状态刷新数据');
- await refreshDataKeepSelected();
- } else {
- console.warn('⚠️ 编辑操作未确认,不执行任何操作');
- }
- },
- },
- );
- }
- async function onDelete(id, index, level) {
- try {
- const { code } = await admin.auth.access.delete({ id });
- if (code == '200') {
- // 🎯 新方案:删除操作成功,保持选中状态刷新
- console.log('✅ 删除操作成功,保持选中状态刷新数据');
- await refreshDataKeepSelected();
- }
- } catch (error) {
- console.error('删除权限失败:', error);
- }
- }
- // 移除可能导致递归更新的watch监听器
- // 权限选择的更新通过handleSelect函数直接处理
- watch(
- () => props.role_id,
- () => {
- if (props.isChangeParentId) {
- state.checkedIds = [];
- }
- getData();
- },
- );
- function getCheckedIds(data, targetArray = null) {
- const idsArray = targetArray || state.newIds;
- data.forEach((i) => {
- // 收集真正选中的权限(全选状态)
- if (i.checked && !i.indeterminate) {
- idsArray.push(i.id + '');
- }
- // 收集半选状态的父级权限(当子级被选中时,父级也要包含)
- else if (i.indeterminate) {
- idsArray.push(i.id + '');
- }
- if (!isEmpty(i.children)) {
- getCheckedIds(i.children, idsArray);
- }
- });
- }
- // 递归收集所有权限ID
- const collectAllPermissionIds = (items) => {
- const ids = [];
- items.forEach((item) => {
- ids.push(item.id);
- if (item.children && item.children.length > 0) {
- ids.push(...collectAllPermissionIds(item.children));
- }
- });
- return ids;
- };
- // 清空所有现有权限
- const clearAllPermissions = async () => {
- console.log('🗑️ 开始清空现有权限...');
- // 获取现有权限树
- const response = await admin.auth.access.getTree();
- if (response.success && response.data && response.data.length > 0) {
- // 收集所有权限ID
- const allIds = collectAllPermissionIds(response.data);
- console.log(`📋 找到 ${allIds.length} 个权限需要删除`);
- // 逐个删除(从子级开始删除,避免外键约束问题)
- for (let i = allIds.length - 1; i >= 0; i--) {
- const id = allIds[i];
- try {
- const deleteResponse = await admin.auth.access.delete({ id });
- if (deleteResponse.code === '200') {
- console.log(`✅ 删除权限成功: ID ${id}`);
- } else {
- console.warn(`⚠️ 删除权限失败: ID ${id}`, deleteResponse);
- }
- } catch (error) {
- console.warn(`⚠️ 删除权限异常: ID ${id}`, error);
- }
- // 延迟避免请求过快
- await new Promise((resolve) => setTimeout(resolve, 100));
- }
- console.log('🎯 现有权限清空完成');
- } else {
- console.log('📝 没有找到现有权限,跳过清空步骤');
- }
- };
- // 菜单权限初始化方法
- const initMenuPermissions = async () => {
- try {
- // 确认对话框
- await ElMessageBox.confirm(
- '此操作将清空所有现有权限并重新初始化菜单权限,是否继续?\n⚠️ 注意:此操作不可逆!',
- '初始化确认',
- {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning',
- dangerouslyUseHTMLString: true,
- },
- );
- initLoading.value = true;
- console.log('🚀 开始菜单权限初始化...');
- // 1. 首先清空所有现有权限
- await clearAllPermissions();
- // 数据转换函数
- const transformMenuData = (item, parentId = '') => ({
- id: 0,
- parentId: parentId,
- name: item.title,
- eName: item.name,
- logo: item.icon || '',
- composingKey: item.name,
- type: item.type === 'menu' ? 0 : item.type === 'page' ? 1 : 2,
- status: item.status === 'show' ? 0 : 1,
- seq: item.weigh || 0,
- url: '',
- isAction: item.type === 'api' ? '1' : '0',
- children: [],
- createTime: '',
- createUserId: '',
- updateTime: '',
- updateUserId: '',
- });
- // 根据权限名称和父ID查找新创建的权限ID
- const findPermissionId = async (name, parentId = '') => {
- console.log(`🔍 查找权限: ${name}, 父ID: "${parentId}"`);
- const response = await admin.auth.access.getTree();
- if (response.success && response.data) {
- console.log(`🔍 获取到的权限树数据:`, response.data);
- // 🎯 关键修复:对获取到的数据进行转换,确保有_original字段
- const transformedData = Array.isArray(response.data)
- ? transformPermissionData(response.data)
- : [];
- const findInTree = (items, level = 0) => {
- const indent = ' '.repeat(level);
- for (const item of items) {
- // 现在可以安全使用_original字段了
- const originalData = item._original || {};
- const itemName = originalData.name || '';
- const itemParentId = originalData.parentId || '';
- // 备用匹配:如果原始数据没有name,尝试使用转换后的title
- const fallbackName = item.title || '';
- // 匹配权限名称和父ID(优先使用原始数据的name字段,备用title字段)
- const nameMatches = itemName === name || (itemName === '' && fallbackName === name);
- if (nameMatches) {
- // 对于顶级权限,parentId可能是空字符串、null、undefined或0
- const isTopLevel = !parentId || parentId === '' || parentId === '0';
- const itemIsTopLevel = !itemParentId || itemParentId === '' || itemParentId === '0';
- // 🎯 修复:父ID匹配逻辑
- const parentIdMatches =
- (isTopLevel && itemIsTopLevel) || String(itemParentId) === String(parentId);
- if (parentIdMatches) {
- const matchedName = itemName || fallbackName;
- return item.id;
- } else {
- console.log(`${indent}❌ 父ID不匹配,跳过此权限`);
- }
- }
- if (item.children && item.children.length > 0) {
- const found = findInTree(item.children, level + 1);
- if (found) return found;
- }
- }
- return null;
- };
- const result = findInTree(transformedData); // 🎯 使用转换后的数据
- console.log(`🎯 查找结果: ${result ? `找到ID ${result}` : '未找到'}`);
- return result;
- }
- console.log('❌ 获取权限树失败');
- return null;
- };
- // 递归创建函数(修改版:处理后端不返回ID的情况)
- const createRecursive = async (items, parentId = '', level = 0) => {
- const indent = ' '.repeat(level);
- for (let i = 0; i < items.length; i++) {
- const item = items[i];
- console.log(`${indent}📝 创建权限: ${item.title} (${item.type}), 父ID: "${parentId}"`);
- // 创建当前权限
- const permissionData = transformMenuData(item, parentId);
- console.log(`${indent}📋 提交数据:`, permissionData);
- const response = await admin.auth.access.add(permissionData);
- if (response.code === '200') {
- console.log(`${indent}✅ 创建成功: ${item.title}`);
- // 如果有子权限,需要先查找新创建的权限ID
- if (item.children && item.children.length > 0) {
- console.log(
- `${indent}🔍 需要创建 ${item.children.length} 个子权限,正在查找父权限ID...`,
- );
- // 等待一下确保数据已保存
- await new Promise((resolve) => setTimeout(resolve, 500));
- // 查找新创建的权限ID
- const newId = await findPermissionId(item.title, parentId);
- if (newId) {
- console.log(`${indent}🎯 找到权限ID: ${newId}`);
- console.log(`${indent}📁 开始创建子权限...`);
- await createRecursive(item.children, newId, level + 1);
- } else {
- console.error(`${indent}❌ 无法找到新创建的权限ID: ${item.title}`);
- console.error(`${indent}🔍 查找条件: 名称="${item.title}", 父ID="${parentId}"`);
- // 尝试再次查找,增加等待时间
- console.log(`${indent}⏳ 等待更长时间后重试...`);
- await new Promise((resolve) => setTimeout(resolve, 1000));
- const retryId = await findPermissionId(item.title, parentId);
- if (retryId) {
- console.log(`${indent}🎯 重试成功,找到权限ID: ${retryId}`);
- await createRecursive(item.children, retryId, level + 1);
- } else {
- throw new Error(`无法找到新创建的权限ID: ${item.title}`);
- }
- }
- }
- } else {
- console.error(`${indent}❌ 创建失败: ${item.title}`, response);
- throw new Error(`创建权限失败: ${item.title} - ${response.message || '未知错误'}`);
- }
- // 延迟避免请求过快
- await new Promise((resolve) => setTimeout(resolve, 300));
- }
- };
- // 2. 开始创建新的权限结构
- console.log('🏗️ 开始创建新的权限结构...');
- const menuData = menuRulesData.data.menu;
- await createRecursive(menuData);
- console.log('🎉 菜单权限初始化完成!');
- ElMessage.success('菜单权限初始化成功!');
- // 3. 重新获取数据刷新界面
- await getData();
- } catch (error) {
- if (error.message !== 'cancel') {
- console.error('💥 初始化失败:', error);
- ElMessage.error(`初始化失败: ${error.message}`);
- }
- } finally {
- initLoading.value = false;
- }
- };
- onMounted(() => {
- // 无论是新增还是编辑都需要加载权限数据
- getData();
- });
- </script>
- <style lang="scss" scoped>
- .sa-access {
- font-size: 14px;
- color: var(--sa-subtitle);
- .el-main {
- display: flex;
- transition: all 3s;
- }
- .el-scrollbar {
- flex-shrink: 0;
- width: 258px;
- border-right: 1px solid var(--sa-border);
- border-left: 1px solid var(--sa-border);
- margin: 0 8px;
- background: var(--sa-background-assist);
- &:first-of-type {
- margin-left: 0;
- }
- &:last-of-type {
- margin-right: 0;
- }
- }
- .title {
- height: 32px;
- padding: 0 16px;
- border-bottom: 1px solid var(--sa-border);
- }
- .node {
- width: inherit;
- padding: 0 16px;
- cursor: pointer;
- .item {
- flex: 1;
- height: 32px;
- display: flex;
- align-items: center;
- justify-content: space-between;
- }
- .sa-icon {
- width: 16px !important;
- height: 16px !important;
- }
- .edit {
- color: var(--el-color-primary);
- }
- .delete {
- color: var(--el-color-danger);
- }
- .arrow-right {
- width: 14px;
- height: 14px;
- display: flex;
- align-items: center;
- }
- &.is-active {
- background: var(--t-bg-active);
- color: var(--el-color-primary);
- }
- }
- .empty {
- width: inherit;
- text-align: center;
- min-height: 400px;
- line-height: 400px;
- }
- }
- </style>
|