'admin-22.05.22:修复标记为需要缓存的tab页后,再次从左侧菜单打开,还是显示被缓存的页面内容(#I4UY3G),感谢@axcc1234、特别感谢群友@华仔'

This commit is contained in:
lyt 2022-05-22 17:18:13 +08:00
parent 2c8dbace6c
commit 1cd056cb83
20 changed files with 681 additions and 639 deletions

View File

@ -34,6 +34,7 @@
- 🐞 修复 `router.push` 路径找不到时报错问题,`404、401 界面` 已移入到 `main` 主布局里(之前全屏)
- 🐞 修复 [全局修改组件大小失效了](https://gitee.com/lyt-top/vue-next-admin/issues/I551RP),感谢[lg_boy](https://gitee.com/lg_boy)
- 🐞 修复 [修改一下配置时,需要每次都清理 `window.localStorage` 浏览器永久缓存,配置才会生效,问题解决#I567R1](https://gitee.com/lyt-top/vue-next-admin/issues/I567R1),感谢[@lanbao123](https://gitee.com/lanbao123)
- 🐞 修复 [标记为需要缓存的 tab 页后,再次从左侧菜单打开,还是显示被缓存的页面内容#I4UY3G](https://gitee.com/lyt-top/vue-next-admin/issues/I4UY3G),感谢@axcc1234、特别感谢群友@华仔
- 🌈 重构 路由(`/src/router/index.ts`)解决 No match found for location with path "xxx"(前端控制,后端控制未解决) 问题
## 2.0.2

View File

@ -131,6 +131,7 @@ cnpm run build
- <a href="https://gitee.com/click33/sa-plus" target="_blank">@省长</a>
- <a href="https://gitee.com/jskz/Jskz-SpringCloud" target="_blank">@唐参
- <a href="https://gitee.com/chuange" target="_blank">@川歌</a>
- @华仔
#### 💌 支持作者

1062
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -18,7 +18,8 @@
"echarts": "^5.3.2",
"echarts-gl": "^2.0.9",
"echarts-wordcloud": "^2.0.0",
"element-plus": "^2.2.0",
"element-plus": "^2.2.1",
"js-cookie": "^3.0.1",
"jsplumb": "^2.15.6",
"mitt": "^3.0.0",
"nprogress": "^0.2.0",
@ -28,25 +29,25 @@
"screenfull": "^6.0.1",
"sortablejs": "^1.15.0",
"splitpanes": "^3.1.1",
"vue": "^3.2.33",
"vue": "^3.2.35",
"vue-clipboard3": "^2.0.0",
"vue-grid-layout": "^3.0.0-beta1",
"vue-i18n": "^9.1.10",
"vue-router": "^4.0.15"
},
"devDependencies": {
"@types/node": "^17.0.34",
"@types/node": "^17.0.35",
"@types/nprogress": "^0.2.0",
"@types/sortablejs": "^1.13.0",
"@typescript-eslint/eslint-plugin": "^5.25.0",
"@typescript-eslint/parser": "^5.25.0",
"@vitejs/plugin-vue": "^2.3.3",
"@vue/compiler-sfc": "^3.2.33",
"@vue/compiler-sfc": "^3.2.35",
"dotenv": "^16.0.1",
"eslint": "^8.15.0",
"eslint": "^8.16.0",
"eslint-plugin-vue": "^9.0.1",
"prettier": "^2.6.2",
"sass": "^1.51.0",
"sass": "^1.52.1",
"sass-loader": "^13.0.0",
"typescript": "^4.6.4",
"vite": "^2.9.9",

1
plugins.d.ts vendored
View File

@ -1,3 +1,4 @@
declare module 'vue-grid-layout';
declare module 'qrcodejs2-fixes';
declare module 'splitpanes';
declare module 'js-cookie';

View File

@ -1,9 +1,9 @@
<template>
<el-config-provider :size="getGlobalComponentSize" :locale="i18nLocale">
<router-view v-show="themeConfig.lockScreenTime !== 0" />
<router-view v-show="themeConfig.lockScreenTime > 1" />
<LockScreen v-if="themeConfig.isLockScreen" />
<Setings ref="setingsRef" v-show="themeConfig.lockScreenTime !== 0" />
<CloseFull />
<Setings ref="setingsRef" v-show="themeConfig.lockScreenTime > 1" />
<CloseFull v-if="!themeConfig.isLockScreen" />
</el-config-provider>
</template>

View File

@ -49,9 +49,9 @@
</div>
</div>
<div class="layout-lock-screen-login-icon">
<SvgIcon name="ele-Microphone" />
<SvgIcon name="ele-AlarmClock" />
<SvgIcon name="ele-SwitchButton" />
<SvgIcon name="ele-Microphone" :size="20" />
<SvgIcon name="ele-AlarmClock" :size="20" />
<SvgIcon name="ele-SwitchButton" :size="20" />
</div>
</div>
</transition>
@ -255,11 +255,11 @@ export default defineComponent({
bottom: 50px;
&-time {
font-size: 100px;
color: var(--color-whites);
color: var(--el-color-white);
}
&-info {
font-size: 40px;
color: var(--color-whites);
color: var(--el-color-white);
}
&-minutes {
font-size: 16px;
@ -272,7 +272,7 @@ export default defineComponent({
border-radius: 100%;
border: 1px solid var(--el-border-color-light, #ebeef5);
background: rgba(255, 255, 255, 0.1);
color: var(--color-whites);
color: var(--el-color-white);
opacity: 0.8;
position: absolute;
right: 30px;
@ -288,7 +288,7 @@ export default defineComponent({
position: absolute;
top: 150%;
font-size: 12px;
color: var(--color-whites);
color: var(--el-color-white);
left: 50%;
line-height: 1.2;
transform: translate(-50%, -50%);
@ -299,7 +299,7 @@ export default defineComponent({
border: 1px solid rgba(255, 255, 255, 0.5);
background: rgba(255, 255, 255, 0.2);
box-shadow: 0 0 12px 0 rgba(255, 255, 255, 0.5);
color: var(--color-whites);
color: var(--el-color-white);
opacity: 1;
transition: all 0.3s ease;
i {
@ -324,7 +324,7 @@ export default defineComponent({
display: flex;
flex-direction: column;
justify-content: center;
color: var(--color-whites);
color: var(--el-color-white);
&-box {
text-align: center;
margin: auto;

View File

@ -93,7 +93,6 @@ export default defineComponent({
};
//
const initRouteSplit = (path: string) => {
state.breadcrumbList = [];
if (!themeConfig.value.isBreadcrumb) return false;
state.breadcrumbList = [routesList.value[0]];
state.routeSplit = path.split('/');
@ -129,12 +128,16 @@ export default defineComponent({
height: inherit;
display: flex;
align-items: center;
padding-left: 15px;
.layout-navbars-breadcrumb-icon {
cursor: pointer;
font-size: 18px;
margin-right: 15px;
color: var(--next-bg-topBarColor);
height: 100%;
width: 40px;
opacity: 0.8;
&:hover {
opacity: 1;
}
}
.layout-navbars-breadcrumb-span {
opacity: 0.7;

View File

@ -97,7 +97,7 @@ export default defineComponent({
});
//
onUnmounted(() => {
proxy.mittBus.off('getBreadcrumbIndexSetFilterRoutes');
proxy.mittBus.off('getBreadcrumbIndexSetFilterRoutes', () => {});
});
return {
setIsShowLogo,

View File

@ -64,7 +64,9 @@ export default defineComponent({
state.isShowSearch = true;
initTageView();
nextTick(() => {
layoutMenuAutocompleteRef.value.focus();
setTimeout(() => {
layoutMenuAutocompleteRef.value.focus();
});
});
};
//

View File

@ -643,7 +643,7 @@ export default defineComponent({
});
});
onUnmounted(() => {
proxy.mittBus.off('layoutMobileResize');
proxy.mittBus.off('layoutMobileResize', () => {});
});
return {
openDrawer,

View File

@ -33,7 +33,7 @@
<i class="icon-skin iconfont" :title="$t('message.user.title3')"></i>
</div>
<div class="layout-navbars-breadcrumb-user-icon">
<el-popover placement="bottom" trigger="click" transition="el-zoom-in-top" :width="300">
<el-popover placement="bottom" trigger="click" transition="el-zoom-in-top" :width="300" :persistent="false">
<template #reference>
<el-badge :is-dot="true">
<el-icon :title="$t('message.user.title4')">

View File

@ -67,6 +67,7 @@ import { storeToRefs } from 'pinia';
import pinia from '/@/stores/index';
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
import { useThemeConfig } from '/@/stores/themeConfig';
import { useKeepALiveNames } from '/@/stores/keepAliveNames';
import { Session } from '/@/utils/storage';
import { isObjectValueEqual } from '/@/utils/arrayOperation';
import other from '/@/utils/other';
@ -115,6 +116,7 @@ export default defineComponent({
const storesTagsViewRoutes = useTagsViewRoutes();
const { themeConfig } = storeToRefs(storesThemeConfig);
const { tagsViewRoutes } = storeToRefs(storesTagsViewRoutes);
const storesKeepALiveNames = useKeepALiveNames();
const route = useRoute();
const router = useRouter();
const state = reactive<TagsViewState>({
@ -176,6 +178,7 @@ export default defineComponent({
if (v.meta.isAffix && !v.meta.isHide) {
v.url = setTagsViewHighlight(v);
state.tagsViewList.push({ ...v });
storesKeepALiveNames.addCachedView(v);
}
});
await addTagsView(route.path, route);
@ -202,6 +205,7 @@ export default defineComponent({
if (findItem.meta.isLink && !findItem.meta.isIframe) return false;
to.meta.isDynamic ? (findItem.params = to.params) : (findItem.query = to.query);
findItem.url = setTagsViewHighlight(findItem);
await storesKeepALiveNames.addCachedView(findItem);
state.tagsViewList.push({ ...findItem });
addBrowserSetSession(state.tagsViewList);
}
@ -247,19 +251,26 @@ export default defineComponent({
if (to && to.meta.isDynamic) item.params = to?.params ? to?.params : route.params;
else item.query = to?.query ? to?.query : route.query;
item.url = setTagsViewHighlight(item);
await storesKeepALiveNames.addCachedView(item);
await state.tagsViewList.push({ ...item });
await addBrowserSetSession(state.tagsViewList);
});
};
// 2 tagsView
const refreshCurrentTagsView = (fullPath: string) => {
proxy.mittBus.emit('onTagsViewRefreshRouterView', fullPath);
const refreshCurrentTagsView = async (fullPath: string) => {
const item = state.tagsViewList.find((v: any) => (getThemeConfig.value.isShareTagsView ? v.path === fullPath : v.url === fullPath));
if (item != null) {
await storesKeepALiveNames.delCachedView(item);
proxy.mittBus.emit('onTagsViewRefreshRouterView', fullPath);
if (item.meta.isKeepAlive) storesKeepALiveNames.addCachedView(item);
}
};
// 3 tagsViewisAffix
const closeCurrentTagsView = (path: string) => {
state.tagsViewList.map((v: any, k: number, arr: any) => {
if (!v.meta.isAffix) {
if (getThemeConfig.value.isShareTagsView ? v.path === path : v.url === path) {
storesKeepALiveNames.delCachedView(v);
state.tagsViewList.splice(k, 1);
setTimeout(() => {
if (state.tagsViewList.length === k && getThemeConfig.value.isShareTagsView ? state.routePath === path : state.routeActive === path) {
@ -298,6 +309,7 @@ export default defineComponent({
Session.get('tagsViewList').map((v: any) => {
if (v.meta.isAffix && !v.meta.isHide) {
v.url = setTagsViewHighlight(v);
storesKeepALiveNames.delOthersCachedViews(v);
state.tagsViewList.push({ ...v });
}
});
@ -308,6 +320,7 @@ export default defineComponent({
// 5 tagsViewisAffix
const closeAllTagsView = () => {
if (Session.get('tagsViewList')) {
storesKeepALiveNames.delAllCachedViews();
state.tagsViewList = [];
Session.get('tagsViewList').map((v: any) => {
if (v.meta.isAffix && !v.meta.isHide) {
@ -531,11 +544,11 @@ export default defineComponent({
//
onUnmounted(() => {
//
proxy.mittBus.off('onCurrentContextmenuClick');
proxy.mittBus.off('onCurrentContextmenuClick', () => {});
// /
proxy.mittBus.off('openOrCloseSortable');
proxy.mittBus.off('openOrCloseSortable', () => {});
// TagsView
proxy.mittBus.off('openShareTagsView');
proxy.mittBus.off('openShareTagsView', () => {});
// resize
window.removeEventListener('resize', onSortableResize);
});

View File

@ -2,7 +2,7 @@
<div class="h100">
<router-view v-slot="{ Component }">
<transition :name="setTransitionName" mode="out-in">
<keep-alive :include="keepAliveNameList">
<keep-alive :include="getKeepAliveNames">
<component :is="Component" :key="refreshRouterViewKey" class="w100" />
</keep-alive>
</transition>
@ -28,9 +28,9 @@ export default defineComponent({
setup() {
const { proxy } = <any>getCurrentInstance();
const route = useRoute();
const stores = useKeepALiveNames();
const storesKeepAliveNames = useKeepALiveNames();
const storesThemeConfig = useThemeConfig();
const { keepAliveNames } = storeToRefs(stores);
const { keepAliveNames, cachedViews } = storeToRefs(storesKeepAliveNames);
const { themeConfig } = storeToRefs(storesThemeConfig);
const state = reactive<ParentViewState>({
refreshRouterViewKey: null,
@ -40,6 +40,10 @@ export default defineComponent({
const setTransitionName = computed(() => {
return themeConfig.value.animation;
});
// (name)
const getKeepAliveNames = computed(() => {
return themeConfig.value.isTagsview ? cachedViews.value : state.keepAliveNameList;
});
//
onBeforeMount(() => {
state.keepAliveNameList = keepAliveNames.value;
@ -54,7 +58,7 @@ export default defineComponent({
});
//
onUnmounted(() => {
proxy.mittBus.off('onTagsViewRefreshRouterView');
proxy.mittBus.off('onTagsViewRefreshRouterView', () => {});
});
// tagsView
watch(
@ -65,6 +69,7 @@ export default defineComponent({
);
return {
setTransitionName,
getKeepAliveNames,
...toRefs(state),
};
},

View File

@ -18,6 +18,7 @@ export interface UserInfosStates {
// 路由缓存列表
export interface KeepAliveNamesState {
keepAliveNames: string[];
cachedViews: string[];
}
// 后端返回原始路由(未处理时)

View File

@ -3,15 +3,35 @@ import { KeepAliveNamesState } from './interface';
/**
*
* @methods setCacheKeepAlive names
* @methods setCacheKeepAlive names Tagsview
* @methods addCachedView names Tagsview
* @methods delCachedView names Tagsview
* @methods delOthersCachedViews `关闭其它` names Tagsview
* @methods delAllCachedViews `全部关闭` names Tagsview
*/
export const useKeepALiveNames = defineStore('keepALiveNames', {
state: (): KeepAliveNamesState => ({
keepAliveNames: [],
cachedViews: [],
}),
actions: {
async setCacheKeepAlive(data: Array<string>) {
this.keepAliveNames = data;
},
async addCachedView(view: any) {
if (this.cachedViews.includes(view.name)) return;
if (view.meta.isKeepAlive) this.cachedViews.push(view.name);
},
async delCachedView(view: any) {
const index = this.cachedViews.indexOf(view.name);
index > -1 && this.cachedViews.splice(index, 1);
},
async delOthersCachedViews(view: any) {
if (view.meta.isKeepAlive) this.cachedViews = [view.name];
else this.cachedViews = [];
},
async delAllCachedViews() {
this.cachedViews = [];
},
},
});

View File

@ -1,5 +1,6 @@
import { defineStore } from 'pinia';
import { UserInfosStates, UserInfosState } from './interface';
import Cookies from 'js-cookie';
import { UserInfosStates } from './interface';
import { Session } from '/@/utils/storage';
/**
@ -9,21 +10,54 @@ import { Session } from '/@/utils/storage';
export const useUserInfo = defineStore('userInfo', {
state: (): UserInfosStates => ({
userInfos: {
authBtnList: [],
photo: '',
roles: [],
time: 0,
userName: '',
photo: '',
time: 0,
roles: [],
authBtnList: [],
},
}),
actions: {
async setUserInfos(data?: UserInfosState) {
if (data) {
this.userInfos = <any>data;
async setUserInfos() {
// 模拟数据,请求接口时,记得删除多余代码及对应依赖的引入
const userName = Cookies.get('userName');
// 模拟数据
let defaultRoles: Array<string> = [];
let defaultAuthBtnList: Array<string> = [];
// admin 页面权限标识,对应路由 meta.roles用于控制路由的显示/隐藏
let adminRoles: Array<string> = ['admin'];
// admin 按钮权限标识
let adminAuthBtnList: Array<string> = ['btn.add', 'btn.del', 'btn.edit', 'btn.link'];
// test 页面权限标识,对应路由 meta.roles用于控制路由的显示/隐藏
let testRoles: Array<string> = ['common'];
// test 按钮权限标识
let testAuthBtnList: Array<string> = ['btn.add', 'btn.link'];
// 不同用户模拟不同的用户权限
if (userName === 'admin') {
defaultRoles = adminRoles;
defaultAuthBtnList = adminAuthBtnList;
} else {
if (Session.get('userInfo')) {
this.userInfos = Session.get('userInfo');
}
defaultRoles = testRoles;
defaultAuthBtnList = testAuthBtnList;
}
// 用户信息模拟数据
const userInfos = {
userName: userName,
photo:
userName === 'admin'
? 'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1813762643,1914315241&fm=26&gp=0.jpg'
: 'https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=317673774,2961727727&fm=26&gp=0.jpg',
time: new Date().getTime(),
roles: defaultRoles,
authBtnList: defaultAuthBtnList,
};
// 存储用户信息到浏览器缓存
Session.set('userInfo', userInfos);
if (Session.get('userInfo')) {
this.userInfos = Session.get('userInfo');
} else {
this.userInfos = userInfos;
}
},
},

View File

@ -1,3 +1,5 @@
import Cookies from 'js-cookie';
/**
* window.localStorage
* @method set
@ -35,19 +37,23 @@ export const Local = {
export const Session = {
// 设置临时缓存
set(key: string, val: any) {
if (key === 'token') return Cookies.set(key, val);
window.sessionStorage.setItem(key, JSON.stringify(val));
},
// 获取临时缓存
get(key: string) {
if (key === 'token') return Cookies.get(key);
let json: any = window.sessionStorage.getItem(key);
return JSON.parse(json);
},
// 移除临时缓存
remove(key: string) {
if (key === 'token') return Cookies.remove(key);
window.sessionStorage.removeItem(key);
},
// 移除全部临时缓存
clear() {
Cookies.remove('token');
window.sessionStorage.clear();
},
};

View File

@ -24,16 +24,16 @@
<script lang="ts">
import { toRefs, reactive, onMounted, defineComponent } from 'vue';
import Cookies from 'js-cookie';
import { storeToRefs } from 'pinia';
import { useUserInfo } from '/@/stores/userInfo';
import { frontEndsResetRoute, setAddRoute, setFilterMenuAndCacheTagsViewRoutes } from '/@/router/frontEnd';
import { Session } from '/@/utils/storage';
export default defineComponent({
name: 'limitsFrontEndPage',
setup() {
const stores = useUserInfo();
const { userInfos } = storeToRefs(stores);
const storesUserInfo = useUserInfo();
const { userInfos } = storeToRefs(storesUserInfo);
const state = reactive({
val: '',
userAuth: '',
@ -46,36 +46,9 @@ export default defineComponent({
const onRadioChange = async () => {
//
frontEndsResetRoute();
let defaultRoles: string[] = [];
let defaultAuthBtnList: string[] = [];
// admin meta.roles/
let adminRoles: string[] = ['admin'];
// admin
let adminAuthBtnList: string[] = ['btn.add', 'btn.del', 'btn.edit', 'btn.link'];
// test meta.roles/
let testRoles: string[] = ['common'];
// test
let testAuthBtnList: string[] = ['btn.add', 'btn.link'];
//
if (state.userAuth === 'admin') {
defaultRoles = adminRoles;
defaultAuthBtnList = adminAuthBtnList;
} else {
defaultRoles = testRoles;
defaultAuthBtnList = testAuthBtnList;
}
const userInfos = {
userName: state.userAuth,
photo:
state.userAuth === 'admin'
? 'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1813762643,1914315241&fm=26&gp=0.jpg'
: 'https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=317673774,2961727727&fm=26&gp=0.jpg',
time: new Date().getTime(),
roles: defaultRoles,
authBtnList: defaultAuthBtnList,
};
Session.set('userInfo', userInfos);
stores.setUserInfos({ ...userInfos }); // ( pinia)
Cookies.set('userName', state.userAuth);
//
storesUserInfo.setUserInfos();
await setAddRoute();
setFilterMenuAndCacheTagsViewRoutes();
};

View File

@ -60,11 +60,11 @@ import { toRefs, reactive, defineComponent, computed } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { ElMessage } from 'element-plus';
import { useI18n } from 'vue-i18n';
import Cookies from 'js-cookie';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import { initFrontEndControlRoutes } from '/@/router/frontEnd';
import { initBackEndControlRoutes } from '/@/router/backEnd';
import { useUserInfo } from '/@/stores/userInfo';
import { Session } from '/@/utils/storage';
import { formatAxis } from '/@/utils/formatTime';
import { NextLoading } from '/@/utils/loading';
@ -73,7 +73,6 @@ export default defineComponent({
name: 'loginAccount',
setup() {
const { t } = useI18n();
const stores = useUserInfo();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const route = useRoute();
@ -95,43 +94,11 @@ export default defineComponent({
});
//
const onSignIn = async () => {
//
state.loading.signIn = true;
let defaultRoles: Array<string> = [];
let defaultAuthBtnList: Array<string> = [];
// admin meta.roles/
let adminRoles: Array<string> = ['admin'];
// admin
let adminAuthBtnList: Array<string> = ['btn.add', 'btn.del', 'btn.edit', 'btn.link'];
// test meta.roles/
let testRoles: Array<string> = ['common'];
// test
let testAuthBtnList: Array<string> = ['btn.add', 'btn.link'];
//
if (state.ruleForm.userName === 'admin') {
defaultRoles = adminRoles;
defaultAuthBtnList = adminAuthBtnList;
} else {
defaultRoles = testRoles;
defaultAuthBtnList = testAuthBtnList;
}
//
const userInfos = {
userName: state.ruleForm.userName,
photo:
state.ruleForm.userName === 'admin'
? 'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1813762643,1914315241&fm=26&gp=0.jpg'
: 'https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=317673774,2961727727&fm=26&gp=0.jpg',
time: new Date().getTime(),
roles: defaultRoles,
authBtnList: defaultAuthBtnList,
};
// token
Session.set('token', Math.random().toString(36).substr(0));
//
Session.set('userInfo', userInfos);
// 1(vuex)
stores.setUserInfos({ ...userInfos });
// `/src/stores/userInfo.ts`
Cookies.set('userName', state.ruleForm.userName);
if (!themeConfig.value.isRequestRoutes) {
// 2
await initFrontEndControlRoutes();