mirror of
https://github.com/un-pany/v3-admin-vite.git
synced 2025-04-21 11:29:20 +08:00
feat: 新增 keep alive 缓存功能
This commit is contained in:
parent
95b9691b20
commit
15fa81f5fe
@ -10,7 +10,9 @@ module.exports = {
|
||||
defineProps: "readonly",
|
||||
defineEmits: "readonly",
|
||||
defineExpose: "readonly",
|
||||
withDefaults: "readonly"
|
||||
withDefaults: "readonly",
|
||||
// unplugin-vue-define-options
|
||||
defineOptions: "readonly"
|
||||
},
|
||||
extends: [
|
||||
"plugin:vue/vue3-essential",
|
||||
|
@ -66,6 +66,7 @@
|
||||
"terser": "^5.16.4",
|
||||
"typescript": "^4.9.5",
|
||||
"unocss": "^0.49.7",
|
||||
"unplugin-vue-define-options": "^1.2.2",
|
||||
"vite": "^4.1.2",
|
||||
"vite-plugin-svg-icons": "^2.0.1",
|
||||
"vite-svg-loader": "^4.0.0",
|
||||
|
57
pnpm-lock.yaml
generated
57
pnpm-lock.yaml
generated
@ -36,6 +36,7 @@ specifiers:
|
||||
terser: ^5.16.4
|
||||
typescript: ^4.9.5
|
||||
unocss: ^0.49.7
|
||||
unplugin-vue-define-options: ^1.2.2
|
||||
vite: ^4.1.2
|
||||
vite-plugin-svg-icons: ^2.0.1
|
||||
vite-svg-loader: ^4.0.0
|
||||
@ -91,6 +92,7 @@ devDependencies:
|
||||
terser: 5.16.4
|
||||
typescript: 4.9.5
|
||||
unocss: 0.49.7_vite@4.1.2
|
||||
unplugin-vue-define-options: 1.2.2_vue@3.2.47
|
||||
vite: 4.1.2_3ujtmoa5y2j7mu6cb7nhm4axba
|
||||
vite-plugin-svg-icons: 2.0.1_vite@4.1.2
|
||||
vite-svg-loader: 4.0.0
|
||||
@ -1231,6 +1233,22 @@ packages:
|
||||
'@volar/vue-language-core': 1.1.4
|
||||
dev: true
|
||||
|
||||
/@vue-macros/common/1.0.1_vue@3.2.47:
|
||||
resolution: {integrity: sha512-61rD1NEqSwTJaZgHwOr//nyfWNow6dFdcuTJegOKiKY+Y4Xu+uTsBDhHDo+M7Rp+ZxjCS6DNThn24wP+8e+QmA==}
|
||||
engines: {node: '>=14.19.0'}
|
||||
peerDependencies:
|
||||
vue: ^2.7.0 || ^3.2.25
|
||||
peerDependenciesMeta:
|
||||
vue:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/types': 7.20.7
|
||||
'@vue/compiler-sfc': 3.2.47
|
||||
local-pkg: 0.4.3
|
||||
magic-string: 0.29.0
|
||||
vue: 3.2.47
|
||||
dev: true
|
||||
|
||||
/@vue/babel-helper-vue-transform-on/1.0.2:
|
||||
resolution: {integrity: sha512-hz4R8tS5jMn8lDq6iD+yWL6XNB699pGIVLk7WSJnn1dbpjaazsjZQkieJoRX6gW5zpYSCFqQ7jUquPNY65tQYA==}
|
||||
dev: true
|
||||
@ -1556,6 +1574,14 @@ packages:
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/ast-walker-scope/0.4.0:
|
||||
resolution: {integrity: sha512-THVisYmmqkcopZXJDniGgVW6BRKtjutRLytqAgw0XDabYZmxC0GfFggTFZouMhvNT7jPBkx0vOy/2Y+udCDwgg==}
|
||||
engines: {node: '>=14.19.0'}
|
||||
dependencies:
|
||||
'@babel/parser': 7.20.15
|
||||
'@babel/types': 7.20.7
|
||||
dev: true
|
||||
|
||||
/astral-regex/2.0.0:
|
||||
resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==}
|
||||
engines: {node: '>=8'}
|
||||
@ -4797,6 +4823,28 @@ packages:
|
||||
- vite
|
||||
dev: true
|
||||
|
||||
/unplugin-vue-define-options/1.2.2_vue@3.2.47:
|
||||
resolution: {integrity: sha512-7LWYNqESu6uXZARwKOs6nhQY7l+UHkW2rtpeGv7iAYFeUp1dk1Esu5qzT2arZ93Wt25fL6HfLBQp3hCkov2d2A==}
|
||||
engines: {node: '>=14.19.0'}
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 5.0.2
|
||||
'@vue-macros/common': 1.0.1_vue@3.2.47
|
||||
ast-walker-scope: 0.4.0
|
||||
unplugin: 1.1.0
|
||||
transitivePeerDependencies:
|
||||
- rollup
|
||||
- vue
|
||||
dev: true
|
||||
|
||||
/unplugin/1.1.0:
|
||||
resolution: {integrity: sha512-I8obQ8Rs/hnkxokRV6g8JKOQFgYNnTd9DL58vcSt5IJ9AkK8wbrtsnzD5hi4BJlvcY536JzfEXj9L6h7j559/A==}
|
||||
dependencies:
|
||||
acorn: 8.8.2
|
||||
chokidar: 3.5.3
|
||||
webpack-sources: 3.2.3
|
||||
webpack-virtual-modules: 0.5.0
|
||||
dev: true
|
||||
|
||||
/unset-value/1.0.0:
|
||||
resolution: {integrity: sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@ -5087,6 +5135,15 @@ packages:
|
||||
engines: {node: '>=12'}
|
||||
dev: true
|
||||
|
||||
/webpack-sources/3.2.3:
|
||||
resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
dev: true
|
||||
|
||||
/webpack-virtual-modules/0.5.0:
|
||||
resolution: {integrity: sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==}
|
||||
dev: true
|
||||
|
||||
/whatwg-encoding/2.0.0:
|
||||
resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==}
|
||||
engines: {node: '>=12'}
|
||||
|
@ -1,8 +1,11 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue"
|
||||
import { useRoute } from "vue-router"
|
||||
import { useTagsViewStore } from "@/store/modules/tags-view"
|
||||
|
||||
const route = useRoute()
|
||||
const tagsViewStore = useTagsViewStore()
|
||||
|
||||
const key = computed(() => {
|
||||
return route.path
|
||||
})
|
||||
@ -12,9 +15,9 @@ const key = computed(() => {
|
||||
<section class="app-main">
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition name="fade-transform" mode="out-in">
|
||||
<!-- <keep-alive> -->
|
||||
<component :is="Component" :key="key" />
|
||||
<!-- </keep-alive> -->
|
||||
<keep-alive :include="tagsViewStore.cachedViews">
|
||||
<component :is="Component" :key="key" />
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</router-view>
|
||||
</section>
|
||||
|
@ -62,15 +62,18 @@ const initTags = () => {
|
||||
const addTags = () => {
|
||||
if (route.name) {
|
||||
tagsViewStore.addVisitedView(route)
|
||||
tagsViewStore.addCachedView(route)
|
||||
}
|
||||
}
|
||||
|
||||
const refreshSelectedTag = (view: ITagView) => {
|
||||
tagsViewStore.delCachedView(view)
|
||||
router.replace({ path: "/redirect" + view.path, query: view.query })
|
||||
}
|
||||
|
||||
const closeSelectedTag = (view: ITagView) => {
|
||||
tagsViewStore.delVisitedView(view)
|
||||
tagsViewStore.delCachedView(view)
|
||||
if (isActive(view)) {
|
||||
toLastView(tagsViewStore.visitedViews, view)
|
||||
}
|
||||
@ -81,10 +84,12 @@ const closeOthersTags = () => {
|
||||
router.push(selectedTag.value.fullPath)
|
||||
}
|
||||
tagsViewStore.delOthersVisitedViews(selectedTag.value)
|
||||
tagsViewStore.delOthersCachedViews(selectedTag.value)
|
||||
}
|
||||
|
||||
const closeAllTags = (view: ITagView) => {
|
||||
tagsViewStore.delAllVisitedViews()
|
||||
tagsViewStore.delAllCachedViews()
|
||||
if (affixTags.some((tag) => tag.path === route.path)) {
|
||||
return
|
||||
}
|
||||
|
@ -102,7 +102,8 @@ export const constantRoutes: RouteRecordRaw[] = [
|
||||
component: () => import("@/views/table/element-plus/index.vue"),
|
||||
name: "ElementPlus",
|
||||
meta: {
|
||||
title: "Element Plus"
|
||||
title: "Element Plus",
|
||||
keepAlive: true
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -110,7 +111,8 @@ export const constantRoutes: RouteRecordRaw[] = [
|
||||
component: () => import("@/views/table/vxe-table/index.vue"),
|
||||
name: "VxeTable",
|
||||
meta: {
|
||||
title: "Vxe Table"
|
||||
title: "Vxe Table",
|
||||
keepAlive: true
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -6,7 +6,9 @@ export type ITagView = Partial<RouteLocationNormalized>
|
||||
|
||||
export const useTagsViewStore = defineStore("tags-view", () => {
|
||||
const visitedViews = ref<ITagView[]>([])
|
||||
const cachedViews = ref<string[]>([])
|
||||
|
||||
//#region add
|
||||
const addVisitedView = (view: ITagView) => {
|
||||
if (
|
||||
visitedViews.value.some((v, index) => {
|
||||
@ -23,6 +25,16 @@ export const useTagsViewStore = defineStore("tags-view", () => {
|
||||
}
|
||||
visitedViews.value.push(Object.assign({}, view))
|
||||
}
|
||||
const addCachedView = (view: ITagView) => {
|
||||
if (typeof view.name !== "string") return
|
||||
if (cachedViews.value.includes(view.name)) return
|
||||
if (view.meta?.keepAlive) {
|
||||
cachedViews.value.push(view.name)
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region del
|
||||
const delVisitedView = (view: ITagView) => {
|
||||
for (const [i, v] of visitedViews.value.entries()) {
|
||||
if (v.path === view.path) {
|
||||
@ -31,16 +43,52 @@ export const useTagsViewStore = defineStore("tags-view", () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
const delCachedView = (view: ITagView) => {
|
||||
if (typeof view.name !== "string") return
|
||||
const index = cachedViews.value.indexOf(view.name)
|
||||
index > -1 && cachedViews.value.splice(index, 1)
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region delOthers
|
||||
const delOthersVisitedViews = (view: ITagView) => {
|
||||
visitedViews.value = visitedViews.value.filter((v) => {
|
||||
return v.meta?.affix || v.path === view.path
|
||||
})
|
||||
}
|
||||
const delOthersCachedViews = (view: ITagView) => {
|
||||
if (typeof view.name !== "string") return
|
||||
const index = cachedViews.value.indexOf(view.name)
|
||||
if (index > -1) {
|
||||
cachedViews.value = cachedViews.value.slice(index, index + 1)
|
||||
} else {
|
||||
// 如果 index = -1, 没有缓存的 tags
|
||||
cachedViews.value = []
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region delAll
|
||||
const delAllVisitedViews = () => {
|
||||
// keep affix tags
|
||||
const affixTags = visitedViews.value.filter((tag) => tag.meta?.affix)
|
||||
visitedViews.value = affixTags
|
||||
}
|
||||
const delAllCachedViews = () => {
|
||||
cachedViews.value = []
|
||||
}
|
||||
//#endregion
|
||||
|
||||
return { visitedViews, addVisitedView, delVisitedView, delOthersVisitedViews, delAllVisitedViews }
|
||||
return {
|
||||
visitedViews,
|
||||
cachedViews,
|
||||
addVisitedView,
|
||||
addCachedView,
|
||||
delVisitedView,
|
||||
delCachedView,
|
||||
delOthersVisitedViews,
|
||||
delOthersCachedViews,
|
||||
delAllVisitedViews,
|
||||
delAllCachedViews
|
||||
}
|
||||
})
|
||||
|
@ -2,6 +2,7 @@ import { ref } from "vue"
|
||||
import store from "@/store"
|
||||
import { defineStore } from "pinia"
|
||||
import { usePermissionStore } from "./permission"
|
||||
import { useTagsViewStore } from "./tags-view"
|
||||
import { getToken, removeToken, setToken } from "@/utils/cache/cookies"
|
||||
import router, { resetRouter } from "@/router"
|
||||
import { loginApi, getUserInfoApi } from "@/api/login"
|
||||
@ -13,6 +14,9 @@ export const useUserStore = defineStore("user", () => {
|
||||
const roles = ref<string[]>([])
|
||||
const username = ref<string>("")
|
||||
|
||||
const permissionStore = usePermissionStore()
|
||||
const tagsViewStore = useTagsViewStore()
|
||||
|
||||
/** 设置角色数组 */
|
||||
const setRoles = (value: string[]) => {
|
||||
roles.value = value
|
||||
@ -55,12 +59,12 @@ export const useUserStore = defineStore("user", () => {
|
||||
token.value = newToken
|
||||
setToken(newToken)
|
||||
await getInfo()
|
||||
const permissionStore = usePermissionStore()
|
||||
permissionStore.setRoutes(roles.value)
|
||||
resetRouter()
|
||||
permissionStore.dynamicRoutes.forEach((item: RouteRecordRaw) => {
|
||||
router.addRoute(item)
|
||||
})
|
||||
_resetTagsView()
|
||||
}
|
||||
/** 登出 */
|
||||
const logout = () => {
|
||||
@ -68,6 +72,7 @@ export const useUserStore = defineStore("user", () => {
|
||||
token.value = ""
|
||||
roles.value = []
|
||||
resetRouter()
|
||||
_resetTagsView()
|
||||
}
|
||||
/** 重置 Token */
|
||||
const resetToken = () => {
|
||||
@ -75,6 +80,11 @@ export const useUserStore = defineStore("user", () => {
|
||||
token.value = ""
|
||||
roles.value = []
|
||||
}
|
||||
/** 重置 visited views 和 cached views */
|
||||
const _resetTagsView = () => {
|
||||
tagsViewStore.delAllVisitedViews()
|
||||
tagsViewStore.delAllCachedViews()
|
||||
}
|
||||
|
||||
return { token, roles, username, setRoles, login, getInfo, changeRoles, logout, resetToken }
|
||||
})
|
||||
|
@ -5,6 +5,10 @@ import { type FormInstance, type FormRules, ElMessage, ElMessageBox } from "elem
|
||||
import { Search, Refresh, CirclePlus, Delete, Download, RefreshRight } from "@element-plus/icons-vue"
|
||||
import { usePagination } from "@/hooks/usePagination"
|
||||
|
||||
defineOptions({
|
||||
name: "ElementPlus"
|
||||
})
|
||||
|
||||
const loading = ref<boolean>(false)
|
||||
const { paginationData, handleCurrentChange, handleSizeChange } = usePagination()
|
||||
|
||||
|
@ -15,6 +15,10 @@ import {
|
||||
type VxeFormDefines
|
||||
} from "vxe-table"
|
||||
|
||||
defineOptions({
|
||||
name: "VxeTable"
|
||||
})
|
||||
|
||||
//#region vxe-grid
|
||||
interface IRowMeta {
|
||||
id: string
|
||||
|
@ -24,7 +24,8 @@
|
||||
/** Element Plus 的 Volar 插件支持 */
|
||||
"element-plus/global",
|
||||
"vitest",
|
||||
"vitest/globals"
|
||||
"vitest/globals",
|
||||
"unplugin-vue-define-options/macros-global"
|
||||
],
|
||||
/** baseUrl 用来告诉编译器到哪里去查找模块,使用非相对模块时必须配置此项 */
|
||||
"baseUrl": ".",
|
||||
|
5
types/vue-router.d.ts
vendored
5
types/vue-router.d.ts
vendored
@ -43,5 +43,10 @@ declare module "vue-router" {
|
||||
* 该属性适合使用在有 hidden: true 属性的路由上
|
||||
*/
|
||||
activeMenu?: string
|
||||
/**
|
||||
* 是否缓存该路由页面
|
||||
* 默认为 false,为 true 时代表需要缓存,此时该路由和该页面都需要设置一致的 Name
|
||||
*/
|
||||
keepAlive?: boolean
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import vueJsx from "@vitejs/plugin-vue-jsx"
|
||||
import { createSvgIconsPlugin } from "vite-plugin-svg-icons"
|
||||
import svgLoader from "vite-svg-loader"
|
||||
import UnoCSS from "unocss/vite"
|
||||
import DefineOptions from "unplugin-vue-define-options/vite"
|
||||
|
||||
/** 配置项文档:https://cn.vitejs.dev/config */
|
||||
export default (configEnv: ConfigEnv): UserConfigExport => {
|
||||
@ -77,7 +78,9 @@ export default (configEnv: ConfigEnv): UserConfigExport => {
|
||||
symbolId: "icon-[dir]-[name]"
|
||||
}),
|
||||
/** UnoCSS */
|
||||
UnoCSS()
|
||||
UnoCSS(),
|
||||
/** DefineOptions 可以更简单的注册组件名称 */
|
||||
DefineOptions()
|
||||
/** 自动按需引入 (已更改为完整引入,所以注释了) */
|
||||
// AutoImport({
|
||||
// dts: "./types/auto-imports.d.ts",
|
||||
|
Loading…
x
Reference in New Issue
Block a user