feat: 新增 keep alive 缓存功能

This commit is contained in:
pany 2023-02-22 15:53:04 +08:00
parent 95b9691b20
commit 15fa81f5fe
13 changed files with 155 additions and 10 deletions

View File

@ -10,7 +10,9 @@ module.exports = {
defineProps: "readonly", defineProps: "readonly",
defineEmits: "readonly", defineEmits: "readonly",
defineExpose: "readonly", defineExpose: "readonly",
withDefaults: "readonly" withDefaults: "readonly",
// unplugin-vue-define-options
defineOptions: "readonly"
}, },
extends: [ extends: [
"plugin:vue/vue3-essential", "plugin:vue/vue3-essential",

View File

@ -66,6 +66,7 @@
"terser": "^5.16.4", "terser": "^5.16.4",
"typescript": "^4.9.5", "typescript": "^4.9.5",
"unocss": "^0.49.7", "unocss": "^0.49.7",
"unplugin-vue-define-options": "^1.2.2",
"vite": "^4.1.2", "vite": "^4.1.2",
"vite-plugin-svg-icons": "^2.0.1", "vite-plugin-svg-icons": "^2.0.1",
"vite-svg-loader": "^4.0.0", "vite-svg-loader": "^4.0.0",

57
pnpm-lock.yaml generated
View File

@ -36,6 +36,7 @@ specifiers:
terser: ^5.16.4 terser: ^5.16.4
typescript: ^4.9.5 typescript: ^4.9.5
unocss: ^0.49.7 unocss: ^0.49.7
unplugin-vue-define-options: ^1.2.2
vite: ^4.1.2 vite: ^4.1.2
vite-plugin-svg-icons: ^2.0.1 vite-plugin-svg-icons: ^2.0.1
vite-svg-loader: ^4.0.0 vite-svg-loader: ^4.0.0
@ -91,6 +92,7 @@ devDependencies:
terser: 5.16.4 terser: 5.16.4
typescript: 4.9.5 typescript: 4.9.5
unocss: 0.49.7_vite@4.1.2 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: 4.1.2_3ujtmoa5y2j7mu6cb7nhm4axba
vite-plugin-svg-icons: 2.0.1_vite@4.1.2 vite-plugin-svg-icons: 2.0.1_vite@4.1.2
vite-svg-loader: 4.0.0 vite-svg-loader: 4.0.0
@ -1231,6 +1233,22 @@ packages:
'@volar/vue-language-core': 1.1.4 '@volar/vue-language-core': 1.1.4
dev: true 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: /@vue/babel-helper-vue-transform-on/1.0.2:
resolution: {integrity: sha512-hz4R8tS5jMn8lDq6iD+yWL6XNB699pGIVLk7WSJnn1dbpjaazsjZQkieJoRX6gW5zpYSCFqQ7jUquPNY65tQYA==} resolution: {integrity: sha512-hz4R8tS5jMn8lDq6iD+yWL6XNB699pGIVLk7WSJnn1dbpjaazsjZQkieJoRX6gW5zpYSCFqQ7jUquPNY65tQYA==}
dev: true dev: true
@ -1556,6 +1574,14 @@ packages:
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dev: true 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: /astral-regex/2.0.0:
resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -4797,6 +4823,28 @@ packages:
- vite - vite
dev: true 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: /unset-value/1.0.0:
resolution: {integrity: sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==} resolution: {integrity: sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -5087,6 +5135,15 @@ packages:
engines: {node: '>=12'} engines: {node: '>=12'}
dev: true 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: /whatwg-encoding/2.0.0:
resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==} resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==}
engines: {node: '>=12'} engines: {node: '>=12'}

View File

@ -1,8 +1,11 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from "vue" import { computed } from "vue"
import { useRoute } from "vue-router" import { useRoute } from "vue-router"
import { useTagsViewStore } from "@/store/modules/tags-view"
const route = useRoute() const route = useRoute()
const tagsViewStore = useTagsViewStore()
const key = computed(() => { const key = computed(() => {
return route.path return route.path
}) })
@ -12,9 +15,9 @@ const key = computed(() => {
<section class="app-main"> <section class="app-main">
<router-view v-slot="{ Component }"> <router-view v-slot="{ Component }">
<transition name="fade-transform" mode="out-in"> <transition name="fade-transform" mode="out-in">
<!-- <keep-alive> --> <keep-alive :include="tagsViewStore.cachedViews">
<component :is="Component" :key="key" /> <component :is="Component" :key="key" />
<!-- </keep-alive> --> </keep-alive>
</transition> </transition>
</router-view> </router-view>
</section> </section>

View File

@ -62,15 +62,18 @@ const initTags = () => {
const addTags = () => { const addTags = () => {
if (route.name) { if (route.name) {
tagsViewStore.addVisitedView(route) tagsViewStore.addVisitedView(route)
tagsViewStore.addCachedView(route)
} }
} }
const refreshSelectedTag = (view: ITagView) => { const refreshSelectedTag = (view: ITagView) => {
tagsViewStore.delCachedView(view)
router.replace({ path: "/redirect" + view.path, query: view.query }) router.replace({ path: "/redirect" + view.path, query: view.query })
} }
const closeSelectedTag = (view: ITagView) => { const closeSelectedTag = (view: ITagView) => {
tagsViewStore.delVisitedView(view) tagsViewStore.delVisitedView(view)
tagsViewStore.delCachedView(view)
if (isActive(view)) { if (isActive(view)) {
toLastView(tagsViewStore.visitedViews, view) toLastView(tagsViewStore.visitedViews, view)
} }
@ -81,10 +84,12 @@ const closeOthersTags = () => {
router.push(selectedTag.value.fullPath) router.push(selectedTag.value.fullPath)
} }
tagsViewStore.delOthersVisitedViews(selectedTag.value) tagsViewStore.delOthersVisitedViews(selectedTag.value)
tagsViewStore.delOthersCachedViews(selectedTag.value)
} }
const closeAllTags = (view: ITagView) => { const closeAllTags = (view: ITagView) => {
tagsViewStore.delAllVisitedViews() tagsViewStore.delAllVisitedViews()
tagsViewStore.delAllCachedViews()
if (affixTags.some((tag) => tag.path === route.path)) { if (affixTags.some((tag) => tag.path === route.path)) {
return return
} }

View File

@ -102,7 +102,8 @@ export const constantRoutes: RouteRecordRaw[] = [
component: () => import("@/views/table/element-plus/index.vue"), component: () => import("@/views/table/element-plus/index.vue"),
name: "ElementPlus", name: "ElementPlus",
meta: { 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"), component: () => import("@/views/table/vxe-table/index.vue"),
name: "VxeTable", name: "VxeTable",
meta: { meta: {
title: "Vxe Table" title: "Vxe Table",
keepAlive: true
} }
} }
] ]

View File

@ -6,7 +6,9 @@ export type ITagView = Partial<RouteLocationNormalized>
export const useTagsViewStore = defineStore("tags-view", () => { export const useTagsViewStore = defineStore("tags-view", () => {
const visitedViews = ref<ITagView[]>([]) const visitedViews = ref<ITagView[]>([])
const cachedViews = ref<string[]>([])
//#region add
const addVisitedView = (view: ITagView) => { const addVisitedView = (view: ITagView) => {
if ( if (
visitedViews.value.some((v, index) => { visitedViews.value.some((v, index) => {
@ -23,6 +25,16 @@ export const useTagsViewStore = defineStore("tags-view", () => {
} }
visitedViews.value.push(Object.assign({}, 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) => { const delVisitedView = (view: ITagView) => {
for (const [i, v] of visitedViews.value.entries()) { for (const [i, v] of visitedViews.value.entries()) {
if (v.path === view.path) { 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) => { const delOthersVisitedViews = (view: ITagView) => {
visitedViews.value = visitedViews.value.filter((v) => { visitedViews.value = visitedViews.value.filter((v) => {
return v.meta?.affix || v.path === view.path 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 = () => { const delAllVisitedViews = () => {
// keep affix tags // keep affix tags
const affixTags = visitedViews.value.filter((tag) => tag.meta?.affix) const affixTags = visitedViews.value.filter((tag) => tag.meta?.affix)
visitedViews.value = affixTags 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
}
}) })

View File

@ -2,6 +2,7 @@ import { ref } from "vue"
import store from "@/store" import store from "@/store"
import { defineStore } from "pinia" import { defineStore } from "pinia"
import { usePermissionStore } from "./permission" import { usePermissionStore } from "./permission"
import { useTagsViewStore } from "./tags-view"
import { getToken, removeToken, setToken } from "@/utils/cache/cookies" import { getToken, removeToken, setToken } from "@/utils/cache/cookies"
import router, { resetRouter } from "@/router" import router, { resetRouter } from "@/router"
import { loginApi, getUserInfoApi } from "@/api/login" import { loginApi, getUserInfoApi } from "@/api/login"
@ -13,6 +14,9 @@ export const useUserStore = defineStore("user", () => {
const roles = ref<string[]>([]) const roles = ref<string[]>([])
const username = ref<string>("") const username = ref<string>("")
const permissionStore = usePermissionStore()
const tagsViewStore = useTagsViewStore()
/** 设置角色数组 */ /** 设置角色数组 */
const setRoles = (value: string[]) => { const setRoles = (value: string[]) => {
roles.value = value roles.value = value
@ -55,12 +59,12 @@ export const useUserStore = defineStore("user", () => {
token.value = newToken token.value = newToken
setToken(newToken) setToken(newToken)
await getInfo() await getInfo()
const permissionStore = usePermissionStore()
permissionStore.setRoutes(roles.value) permissionStore.setRoutes(roles.value)
resetRouter() resetRouter()
permissionStore.dynamicRoutes.forEach((item: RouteRecordRaw) => { permissionStore.dynamicRoutes.forEach((item: RouteRecordRaw) => {
router.addRoute(item) router.addRoute(item)
}) })
_resetTagsView()
} }
/** 登出 */ /** 登出 */
const logout = () => { const logout = () => {
@ -68,6 +72,7 @@ export const useUserStore = defineStore("user", () => {
token.value = "" token.value = ""
roles.value = [] roles.value = []
resetRouter() resetRouter()
_resetTagsView()
} }
/** 重置 Token */ /** 重置 Token */
const resetToken = () => { const resetToken = () => {
@ -75,6 +80,11 @@ export const useUserStore = defineStore("user", () => {
token.value = "" token.value = ""
roles.value = [] roles.value = []
} }
/** 重置 visited views 和 cached views */
const _resetTagsView = () => {
tagsViewStore.delAllVisitedViews()
tagsViewStore.delAllCachedViews()
}
return { token, roles, username, setRoles, login, getInfo, changeRoles, logout, resetToken } return { token, roles, username, setRoles, login, getInfo, changeRoles, logout, resetToken }
}) })

View File

@ -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 { Search, Refresh, CirclePlus, Delete, Download, RefreshRight } from "@element-plus/icons-vue"
import { usePagination } from "@/hooks/usePagination" import { usePagination } from "@/hooks/usePagination"
defineOptions({
name: "ElementPlus"
})
const loading = ref<boolean>(false) const loading = ref<boolean>(false)
const { paginationData, handleCurrentChange, handleSizeChange } = usePagination() const { paginationData, handleCurrentChange, handleSizeChange } = usePagination()

View File

@ -15,6 +15,10 @@ import {
type VxeFormDefines type VxeFormDefines
} from "vxe-table" } from "vxe-table"
defineOptions({
name: "VxeTable"
})
//#region vxe-grid //#region vxe-grid
interface IRowMeta { interface IRowMeta {
id: string id: string

View File

@ -24,7 +24,8 @@
/** Element Plus Volar */ /** Element Plus Volar */
"element-plus/global", "element-plus/global",
"vitest", "vitest",
"vitest/globals" "vitest/globals",
"unplugin-vue-define-options/macros-global"
], ],
/** baseUrl 使 */ /** baseUrl 使 */
"baseUrl": ".", "baseUrl": ".",

View File

@ -43,5 +43,10 @@ declare module "vue-router" {
* 使 hidden: true * 使 hidden: true
*/ */
activeMenu?: string activeMenu?: string
/**
*
* false true Name
*/
keepAlive?: boolean
} }
} }

View File

@ -7,6 +7,7 @@ import vueJsx from "@vitejs/plugin-vue-jsx"
import { createSvgIconsPlugin } from "vite-plugin-svg-icons" import { createSvgIconsPlugin } from "vite-plugin-svg-icons"
import svgLoader from "vite-svg-loader" import svgLoader from "vite-svg-loader"
import UnoCSS from "unocss/vite" import UnoCSS from "unocss/vite"
import DefineOptions from "unplugin-vue-define-options/vite"
/** 配置项文档https://cn.vitejs.dev/config */ /** 配置项文档https://cn.vitejs.dev/config */
export default (configEnv: ConfigEnv): UserConfigExport => { export default (configEnv: ConfigEnv): UserConfigExport => {
@ -77,7 +78,9 @@ export default (configEnv: ConfigEnv): UserConfigExport => {
symbolId: "icon-[dir]-[name]" symbolId: "icon-[dir]-[name]"
}), }),
/** UnoCSS */ /** UnoCSS */
UnoCSS() UnoCSS(),
/** DefineOptions 可以更简单的注册组件名称 */
DefineOptions()
/** 自动按需引入 (已更改为完整引入,所以注释了) */ /** 自动按需引入 (已更改为完整引入,所以注释了) */
// AutoImport({ // AutoImport({
// dts: "./types/auto-imports.d.ts", // dts: "./types/auto-imports.d.ts",