Merge branch 'developer' of ssh://code.cyweb.top:30033/scj/zhxy/v3/cloud-ui into developer
This commit is contained in:
45
src/App.vue
45
src/App.vue
@@ -17,6 +17,7 @@ import { Local, Session } from '/@/utils/storage';
|
||||
import mittBus from '/@/utils/mitt';
|
||||
import { needRoleSelection, isRoleDialogTriggered, setRoleDialogTriggered } from '/@/utils/roleSelect';
|
||||
import setIntroduction from '/@/utils/setIconfont';
|
||||
import { listAllRole } from '/@/api/admin/role';
|
||||
|
||||
// 引入组件
|
||||
const LockScreen = defineAsyncComponent(() => import('/@/layout/lockScreen/index.vue'));
|
||||
@@ -58,6 +59,34 @@ onBeforeMount(() => {
|
||||
});
|
||||
// 角色选择弹框是否已在本轮打开过(防止事件被触发两次)
|
||||
let roleDialogOpenedThisSession = false
|
||||
|
||||
/** 校验缓存中的 roleId 是否仍在 listAllRole 结果中;若不存在则清除 roleId/roleCode/roleName 并返回 true(需要弹框) */
|
||||
async function validateCachedRoleId(): Promise<boolean> {
|
||||
const cachedRoleId = Local.get('roleId');
|
||||
if (cachedRoleId == null || cachedRoleId === '') return false;
|
||||
try {
|
||||
const res = await listAllRole();
|
||||
const data = res?.data;
|
||||
const allRoles: any[] = Array.isArray(data)
|
||||
? data
|
||||
: data && typeof data === 'object'
|
||||
? (Object.values(data) as any[]).flat()
|
||||
: [];
|
||||
const exists = allRoles.some(
|
||||
(r: any) => r && String(r.roleId) === String(cachedRoleId)
|
||||
);
|
||||
if (!exists) {
|
||||
Local.remove('roleId');
|
||||
Local.remove('roleCode');
|
||||
Local.remove('roleName');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 唯一入口:只通过事件打开,且只打开一次;延迟打开以等待异步组件挂载
|
||||
mittBus.on('openRoleSelectDialog', () => {
|
||||
@@ -67,7 +96,7 @@ onMounted(() => {
|
||||
changeRoleFirRef.value?.open()
|
||||
}, 300)
|
||||
})
|
||||
nextTick(() => {
|
||||
nextTick(async () => {
|
||||
// 监听布局配置弹窗点击打开
|
||||
mittBus.on('openSettingsDrawer', () => {
|
||||
settingsRef.value.openDrawer();
|
||||
@@ -81,10 +110,16 @@ onMounted(() => {
|
||||
if (Session.get('isTagsViewCurrenFull')) {
|
||||
stores.setCurrenFullscreen(Session.get('isTagsViewCurrenFull'));
|
||||
}
|
||||
// 与请求拦截器共用同一逻辑:先设标志再 emit,由监听器统一打开(监听器内会延迟 300ms 以等待异步组件挂载)
|
||||
if (Session.getToken() && needRoleSelection() && !isRoleDialogTriggered()) {
|
||||
setRoleDialogTriggered(true)
|
||||
mittBus.emit('openRoleSelectDialog')
|
||||
// 有 token 时:先校验缓存 roleId 是否仍有效,无效则清缓存并弹框选角色
|
||||
if (Session.getToken()) {
|
||||
const needOpenByInvalidRole = await validateCachedRoleId();
|
||||
if (needOpenByInvalidRole && !isRoleDialogTriggered()) {
|
||||
setRoleDialogTriggered(true);
|
||||
mittBus.emit('openRoleSelectDialog');
|
||||
} else if (!needOpenByInvalidRole && needRoleSelection() && !isRoleDialogTriggered()) {
|
||||
setRoleDialogTriggered(true);
|
||||
mittBus.emit('openRoleSelectDialog');
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
@@ -114,6 +114,34 @@ const isActive = (v: RouteItem) => {
|
||||
}
|
||||
}
|
||||
};
|
||||
/** 标题为「轮播图」的 tag 不展示(切换角色后常误出现,直接过滤) */
|
||||
const isCarouselTag = (v: RouteItem) => other.setTagsViewNameI18n(v) === '轮播图';
|
||||
/** 仅首页作为固定 tag,其它 isAffix(如后端菜单的轮播图)不再默认占一条 tag */
|
||||
const isAffixTagAllowed = (v: RouteItem) => v.path === '/home' || v.path === '/home/index';
|
||||
/** 后端菜单 sortOrder/sort_order/weight 为 1 的项常被误当“默认”加载为 tag,排除(首页除外) */
|
||||
const isSortOrderOneExcluded = (v: RouteItem) => {
|
||||
const meta = v.meta as any;
|
||||
const order =
|
||||
meta?.sortOrder ?? meta?.sort_order ?? meta?.weight ?? (v as any).sort_order ?? (v as any).weight;
|
||||
return Number(order) === 1 && !isAffixTagAllowed(v);
|
||||
};
|
||||
/** 递归扁平化路由(含 children),用于校验 path 是否在当前角色菜单中 */
|
||||
const flattenRoutes = (routes: RouteItem[]): RouteItem[] => {
|
||||
let list: RouteItem[] = [];
|
||||
(routes || []).forEach((r: RouteItem) => {
|
||||
list.push(r);
|
||||
if ((r as any).children?.length) list.push(...flattenRoutes((r as any).children));
|
||||
});
|
||||
return list;
|
||||
};
|
||||
/** 当前 tag 的 path 是否存在于当前角色路由列表中(避免恢复出“不存在的 tag”) */
|
||||
const pathInCurrentRoutes = (tag: RouteItem): boolean => {
|
||||
const flat = flattenRoutes(state.tagsViewRoutesList);
|
||||
return flat.some(
|
||||
(r: RouteItem) =>
|
||||
r.path === tag.path || (r.meta?.isDynamic && (r.meta as any).isDynamicPath === tag.path)
|
||||
);
|
||||
};
|
||||
// 存储 tagsViewList 到浏览器临时缓存中,页面刷新时,保留记录
|
||||
const addBrowserSetSession = (tagsViewList: Array<object>) => {
|
||||
Session.set('tagsViewList', tagsViewList);
|
||||
@@ -128,11 +156,20 @@ const getTagsViewRoutes = async () => {
|
||||
};
|
||||
// pinia 中获取路由信息:如果是设置了固定的(isAffix),进行初始化显示
|
||||
const initTagsView = async () => {
|
||||
if (Session.get('tagsViewList') && getThemeConfig.value.isCacheTagsView) {
|
||||
state.tagsViewList = await Session.get('tagsViewList');
|
||||
const cached = Session.get('tagsViewList');
|
||||
const hasValidCache = cached && getThemeConfig.value.isCacheTagsView && Array.isArray(cached);
|
||||
// 仅当当前角色已有路由列表时,才从缓存恢复;恢复时只保留当前路由中存在的 path,避免切换角色后出现“不存在的 tag”
|
||||
if (hasValidCache && state.tagsViewRoutesList.length > 0) {
|
||||
state.tagsViewList = cached.filter(
|
||||
(v: RouteItem) =>
|
||||
!isCarouselTag(v) &&
|
||||
!isSortOrderOneExcluded(v) &&
|
||||
pathInCurrentRoutes(v) &&
|
||||
(v.meta?.isAffix ? isAffixTagAllowed(v) : true)
|
||||
);
|
||||
} else {
|
||||
await state.tagsViewRoutesList.map((v: RouteItem) => {
|
||||
if (v.meta?.isAffix && !v.meta.isHide) {
|
||||
if (v.meta?.isAffix && !v.meta.isHide && !isCarouselTag(v) && isAffixTagAllowed(v) && !isSortOrderOneExcluded(v)) {
|
||||
v.url = setTagsViewHighlight(v);
|
||||
state.tagsViewList.push({ ...v });
|
||||
storesKeepALiveNames.addCachedView(v);
|
||||
@@ -158,6 +195,8 @@ const solveAddTagsView = async (path: string, to?: RouteToFrom) => {
|
||||
// 防止:Avoid app logic that relies on enumerating keys on a component instance. The keys will be empty in production mode to avoid performance overhead.
|
||||
let findItem = state.tagsViewRoutesList.find((v: RouteItem) => v.path === isDynamicPath);
|
||||
if (!findItem) return false;
|
||||
if (isCarouselTag(findItem)) return false;
|
||||
if (isSortOrderOneExcluded(findItem)) return false;
|
||||
if (findItem.meta.isAffix) return false;
|
||||
if (findItem.meta.isLink && !findItem.meta.isIframe) return false;
|
||||
to?.meta?.isDynamic ? (findItem.params = to.params) : (findItem.query = to?.query);
|
||||
@@ -211,6 +250,8 @@ const addTagsView = (path: string, to?: RouteToFrom) => {
|
||||
item = state.tagsViewRoutesList.find((v: RouteItem) => v.path === path);
|
||||
}
|
||||
if (!item) return false;
|
||||
if (isCarouselTag(item)) return false;
|
||||
if (isSortOrderOneExcluded(item)) return false;
|
||||
if (item?.meta?.isLink && !item.meta.isIframe) return false;
|
||||
if (to?.meta?.isDynamic) item.params = to?.params ? to?.params : route.params;
|
||||
else item.query = to?.query ? to?.query : route.query;
|
||||
@@ -279,7 +320,7 @@ const closeOtherTagsView = (path: string) => {
|
||||
if (Session.get('tagsViewList')) {
|
||||
state.tagsViewList = [];
|
||||
Session.get('tagsViewList').map((v: RouteItem) => {
|
||||
if (v.meta?.isAffix && !v.meta.isHide) {
|
||||
if (v.meta?.isAffix && !v.meta?.isHide && !isCarouselTag(v) && pathInCurrentRoutes(v) && isAffixTagAllowed(v) && !isSortOrderOneExcluded(v)) {
|
||||
v.url = setTagsViewHighlight(v);
|
||||
storesKeepALiveNames.delOthersCachedViews(v);
|
||||
state.tagsViewList.push({ ...v });
|
||||
@@ -295,7 +336,7 @@ const closeAllTagsView = () => {
|
||||
storesKeepALiveNames.delAllCachedViews();
|
||||
state.tagsViewList = [];
|
||||
Session.get('tagsViewList').map((v: RouteItem) => {
|
||||
if (v.meta?.isAffix && !v.meta.isHide) {
|
||||
if (v.meta?.isAffix && !v.meta?.isHide && !isCarouselTag(v) && pathInCurrentRoutes(v) && isAffixTagAllowed(v) && !isSortOrderOneExcluded(v)) {
|
||||
v.url = setTagsViewHighlight(v);
|
||||
state.tagsViewList.push({ ...v });
|
||||
router.push({ path: state.tagsViewList[state.tagsViewList.length - 1].path });
|
||||
@@ -547,7 +588,7 @@ onBeforeMount(() => {
|
||||
router.push('/home/index');
|
||||
state.tagsViewList = [];
|
||||
state.tagsViewRoutesList.map((v: RouteItem) => {
|
||||
if (v.meta?.isAffix && !v.meta.isHide) {
|
||||
if (v.meta?.isAffix && !v.meta?.isHide && !isCarouselTag(v) && isAffixTagAllowed(v) && !isSortOrderOneExcluded(v)) {
|
||||
v.url = setTagsViewHighlight(v);
|
||||
state.tagsViewList.push({ ...v });
|
||||
}
|
||||
|
||||
@@ -46,7 +46,6 @@ export const useUserInfo = defineStore('userInfo', {
|
||||
return new Promise((resolve, reject) => {
|
||||
login(data)
|
||||
.then((res) => {
|
||||
debugger
|
||||
this.setTokenCache(res.access_token, res.refresh_token);
|
||||
Local.remove('roleCode');
|
||||
Local.remove('roleName');
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, toRef } from 'vue'
|
||||
import { listAllRole } from '/@/api/admin/role'
|
||||
import { Local } from '/@/utils/storage'
|
||||
import { Local, Session } from '/@/utils/storage'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
|
||||
/** 弹框标题,如「角色切换」「登录角色选择」 */
|
||||
@@ -108,9 +108,14 @@ const handleChangeRole = (label: string) => {
|
||||
Local.set('roleName', obj.roleName)
|
||||
Local.set('roleId', obj.roleId)
|
||||
useMessage().success('操作成功')
|
||||
// 清掉 tags 缓存,重载后只保留首页 tag
|
||||
Session.remove('tagsViewList')
|
||||
// 清除 pinia 持久化的 tagsView 路由,避免重载后先恢复旧角色菜单再被新路由覆盖前就初始化出“不存在的 tag”
|
||||
try {
|
||||
window.localStorage.removeItem('tagsViewRoutes')
|
||||
} catch (_) {}
|
||||
setTimeout(() => {
|
||||
// 切换角色后统一回到首页,避免停留在诸如 jsonflow/run-job/do-job 等内部路由
|
||||
window.location.hash = '#/'
|
||||
window.location.hash = '#/home'
|
||||
window.location.reload()
|
||||
}, 500)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user