| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098 |
- <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="t('modules.auth.selectAll')"
- @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)">{{
- t('modules.auth.addPermission') }}</el-button>
- <!-- <el-button
- class="is-link"
- type="success"
- @click="initMenuPermissions"
- :loading="initLoading"
- >
- {{ t('modules.auth.initMenu') }}
- </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="t('common.confirm')"
- :cancel-button-text="t('common.cancel')" :title="t('modules.auth.confirmDeleteRecord')"
- @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">{{ t('modules.auth.noData') }}</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 { useI18n } from 'vue-i18n';
- import { isEmpty } from 'lodash';
- import AccessEdit from '../edit.vue';
- const { t } = useI18n();
- 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: t('modules.auth.application'),
- 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 (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: t('common.add'),
- 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: t('common.edit'),
- 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(
- t('modules.auth.confirmInit'),
- t('modules.auth.initConfirmTitle'),
- {
- confirmButtonText: t('common.confirm'),
- cancelButtonText: t('common.cancel'),
- 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(t('modules.auth.initSuccess'));
- // 3. 重新获取数据刷新界面
- await getData();
- } catch (error) {
- if (error.message !== 'cancel') {
- console.error('💥 初始化失败:', error);
- ElMessage.error(t('modules.auth.initFailed', { message: 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>
|