多页签增加持久化功能

多页签的快捷键切换功能可自行启用停用
设置面板中的多页签项独立化
config增加了本地存储配置
其他代码优化
master
toesbieya 6 years ago
parent 7a27e8a0b9
commit f6a628cf66
  1. 11
      vue/src/config/index.js
  2. 5
      vue/src/layout/component/Aside/index.vue
  3. 71
      vue/src/layout/component/Header/component/Navbar/component/SettingDrawer.vue
  4. 28
      vue/src/layout/component/Header/component/TagsView/index.vue
  5. 6
      vue/src/layout/mixin/decideRouterTransition.js
  6. 56
      vue/src/layout/mixin/tagsViewPersistent.js
  7. 27
      vue/src/layout/mixin/tagsViewShortcut.js
  8. 10
      vue/src/layout/store/tagsView.js
  9. 2
      vue/src/router/guardian/security.js
  10. 23
      vue/src/store/module/resource.js
  11. 6
      vue/src/util/auth.js
  12. 117
      vue/src/util/storage.js

@ -48,7 +48,7 @@ module.exports = {
//文件配置
file: {
//上传地址
uploadUrl:'https://upload.qiniup.com',
uploadUrl: 'https://upload.qiniup.com',
//上传后的存储地址前缀
storePrefix: 'https://static.toesbieya.cn/',
@ -57,6 +57,15 @@ module.exports = {
previewPrefix: isDev ? 'http://localhost:8012' : 'https://preview.toesbieya.cn',
},
//本地存储配置
storage: {
//键名前缀
keyPrefix: 'GCC-',
//读写时是否进行压缩的默认值
zip: !isDev
},
//省市地区json数据请求地址
regionDataUrl: `${contextPath}static/json/region-pca.json`
}

@ -115,12 +115,15 @@ export default {
},
//
shouldBindMouseMoveEvent(v) {
shouldBindMouseMoveEvent: {
immediate: true,
handler(v) {
//
window.removeEventListener('mousemove', this.moveEvent)
v && window.addEventListener('mousemove', this.moveEvent)
}
}
},
methods: {

@ -26,10 +26,6 @@
<span>显示页头</span>
<el-switch :value="pageGetters.showPageHeader" @input="changePageShowHeader"/>
</div>
<div class="drawer-item">
<span>使用多页签</span>
<el-switch :value="tagsViewGetters.enabled" @input="changeTagsViewEnabled"/>
</div>
<div class="drawer-item">
<span>显示返回顶部按钮</span>
<el-switch :value="pageGetters.showBackToTop" @input="changePageBackToTop"/>
@ -56,6 +52,20 @@
<span>自动隐藏</span>
<el-switch :value="asideGetters.autoHide" @input="changeAsideAutoHide"/>
</div>
<el-divider>多页签设置</el-divider>
<div class="drawer-item">
<span>启用</span>
<el-switch :value="tagsViewGetters.enabled" @input="changeTagsViewEnabled"/>
</div>
<div class="drawer-item">
<span>启用快捷键切换</span>
<el-switch :value="tagsViewGetters.shortcut" @input="changeTagsViewShortcut"/>
</div>
<div class="drawer-item">
<span>持久化</span>
<el-switch :value="tagsViewGetters.persistent" @input="changeTagsViewPersistent"/>
</div>
</div>
</el-drawer>
</template>
@ -111,7 +121,6 @@ export default {
},
page: {
showPageHeader: true,
useTagsView: true,
showBackToTop: true
},
aside: {
@ -120,6 +129,11 @@ export default {
collapse: false,
showParentOnCollapse: false,
autoHide: false
},
tagsView: {
enabled: true,
shortcut: true,
persistent: true
}
}
}
@ -128,13 +142,12 @@ export default {
methods: {
//layoutstore
updateLayoutStore() {
const {app, page, aside} = this.setting
const {app, page, aside, tagsView} = this.setting
appMutations.color(app.color)
appMutations.navMode(app.navMode)
pageMutations.showPageHeader(page.showPageHeader)
tagsViewMutations.enabled(page.useTagsView)
pageMutations.showBackToTop(page.showBackToTop)
asideMutations.showLogo(aside.showLogo)
@ -142,15 +155,18 @@ export default {
asideMutations.collapse(aside.collapse)
asideMutations.showParentOnCollapse(aside.showParentOnCollapse)
asideMutations.autoHide(aside.autoHide)
tagsViewMutations.enabled(tagsView.enabled)
tagsViewMutations.shortcut(tagsView.shortcut)
tagsViewMutations.persistent(tagsView.persistent)
},
//layoutstore
syncLayoutStore() {
const {app, page, aside} = this.setting
const {app, page, aside, tagsView} = this.setting
app.color = appGetters.color
app.navMode = appGetters.navMode
page.showPageHeader = pageGetters.showPageHeader
page.useTagsView = tagsViewGetters.enabled
page.showBackToTop = pageGetters.showBackToTop
aside.showLogo = asideGetters.showLogo
@ -158,6 +174,10 @@ export default {
aside.collapse = asideGetters.collapse
aside.showParentOnCollapse = asideGetters.showParentOnCollapse
aside.autoHide = asideGetters.autoHide
tagsView.enabled = tagsViewGetters.enabled
tagsView.shortcut = tagsViewGetters.shortcut
tagsView.persistent = tagsViewGetters.persistent
},
//
saveSetting() {
@ -168,49 +188,56 @@ export default {
changeThemeColor(v) {
isDev && client.changer.changeColor({newColors: forElementUI.getElementUISeries(v)})
appMutations.color(v)
this.saveSetting()
},
changeNavMode(v) {
appMutations.navMode(v)
this.saveSetting()
},
changePageShowHeader(v) {
pageMutations.showPageHeader(v)
this.saveSetting()
},
changeTagsViewEnabled(v) {
tagsViewMutations.enabled(v)
this.saveSetting()
},
changePageBackToTop(v) {
pageMutations.showBackToTop(v)
this.saveSetting()
},
changeAsideShowLogo(v) {
asideMutations.showLogo(v)
this.saveSetting()
},
changeAsideUniqueOpen(v) {
asideMutations.uniqueOpen(v)
this.saveSetting()
},
changeAsideCollapse(v) {
asideMutations.collapse(v)
this.saveSetting()
},
changeAsideCollapseParent(v) {
asideMutations.showParentOnCollapse(v)
this.saveSetting()
},
changeAsideAutoHide(v) {
asideMutations.autoHide(v)
this.saveSetting()
},
changeTagsViewEnabled(v) {
tagsViewMutations.enabled(v)
},
changeTagsViewShortcut(v) {
tagsViewMutations.shortcut(v)
},
changeTagsViewPersistent(v) {
tagsViewMutations.persistent(v)
}
},
created() {
//change
for (const key of Object.keys(this)) {
if (!key.startsWith('change')) continue
const originMethod = this[key]
this[key] = (...arg) => {
originMethod.apply(this, arg)
this.saveSetting()
}
}
mergeObj(this.setting, getLocalPersonalSettings())
//

@ -1,6 +1,7 @@
<script type="text/jsx">
import shortcutMixin from '@/layout/mixin/shortcut'
import shortcutMixin from '@/layout/mixin/tagsViewShortcut'
import decideRouterTransitionMixin from '@/layout/mixin/decideRouterTransition'
import persistenceMixin from '@/layout/mixin/tagsViewPersistent'
import {route as routeConfig} from '@/config'
import {getters as appGetters} from "@/layout/store/app"
import {getters as tagsViewGetters, mutations as tagsViewMutations} from "@/layout/store/tagsView"
@ -8,7 +9,7 @@ import ContextMenu from "@/component/menu/ContextMenu"
import ScrollPanel from './ScrollPanel'
export default {
mixins: [shortcutMixin, decideRouterTransitionMixin],
mixins: [shortcutMixin, decideRouterTransitionMixin, persistenceMixin],
components: {ContextMenu, ScrollPanel},
@ -19,9 +20,7 @@ export default {
top: 0,
left: 0,
},
selectedTag: {},
affixTags: [],
scroller: null
selectedTag: {}
}
},
@ -31,15 +30,14 @@ export default {
},
watch: {
$route(to, from) {
this.decideRouteTransition && this.decideRouteTransition(to, from)
this.addTags(to)
$route(to) {
this.addTag(to)
this.$nextTick(this.moveToCurrentTag)
}
},
methods: {
//
//redirect
isActive({path}) {
const {path: routePath} = this.$route
return routePath === path || routePath === `/redirect${path}`
@ -70,14 +68,12 @@ export default {
return tags
}
this.affixTags = getAffixTags(this.menus)
for (const tag of this.affixTags) {
for (const tag of getAffixTags(this.menus)) {
tagsViewMutations.addVisitedView(tag)
}
},
//meta.titletab
addTags(to = this.$route) {
//meta.titletab
addTag(to = this.$route) {
to.meta.title && tagsViewMutations.addView(to)
},
@ -145,7 +141,7 @@ export default {
renderTags(h) {
return this.visitedViews.map(tag => {
const {title, path, query, fullPath} = tag
const {path, query, fullPath, meta: {title}} = tag
const active = this.isActive(tag), affix = this.isAffix(tag)
const closeSelectedTag = (e, tag) => {
e.preventDefault()
@ -191,7 +187,7 @@ export default {
mounted() {
this.initTags()
this.addTags()
this.addTag()
},
render(h) {

@ -4,6 +4,12 @@ import {mutations as tagsViewMutations} from "@/layout/store/tagsView"
const {animate} = routeConfig
export default {
watch: {
$route(to, from) {
this.decideRouteTransition(to, from)
}
},
methods: {
//根据访问的tab页的左右顺序来确定路由动画
decideRouteTransition(to, from) {

@ -0,0 +1,56 @@
/**
* 多页签持久化混入
* 初始化时读取本地存储中的数据
* 页签变化时写入本地存储
*/
import {getters as tagsViewGetters, mutations as tagsViewMutations} from "@/layout/store/tagsView"
import {debounce} from "@/util"
import {getTagsView, setTagsView} from "@/util/storage"
export default {
computed: {
shouldPersistent() {
return tagsViewGetters.persistent
}
},
watch: {
shouldPersistent: {
immediate: true,
handler(v) {
//尝试清除之前的watch
if (this.watchVisitedViewsCallback) {
this.watchVisitedViewsCallback()
this.watchVisitedViewsCallback = null
}
//停用持久化时清空本地存储的页签数据
if (!v) return setTagsView()
//启用时先存储一次(仅在mounted后,否则此时页签数据不完整)
this._isMounted && this.persistentTagsView(this.visitedViews)
this.watchVisitedViewsCallback = this.$watch('visitedViews', this.persistentTagsView)
}
}
},
methods: {
persistentTagsView(v) {
//只提取部分属性
setTagsView(v.map(({name, path, fullPath, meta}) => ({name, path, fullPath, meta})))
}
},
beforeCreate() {
this.persistentTagsView = debounce(this.persistentTagsView)
},
mounted() {
if (!this.shouldPersistent) return
const tags = getTagsView()
Array.isArray(tags) && tags.forEach(tagsViewMutations.addVisitedView)
}
}

@ -3,7 +3,28 @@
* ctrl + 下一个页签
* ctrl + 上一个页签
*/
import {getters as tagsViewGetters} from "@/layout/store/tagsView"
export default {
computed: {
shouldBindKeydownEvent() {
return tagsViewGetters.shortcut
}
},
watch: {
shouldBindKeydownEvent: {
immediate: true,
handler(v) {
//尝试移除之前可能添加的事件
window.removeEventListener('keydown', this.shortcutsListen)
v && window.addEventListener('keydown', this.shortcutsListen)
}
}
},
methods: {
//上一个页签
gotoViewFront() {
@ -40,11 +61,7 @@ export default {
}
},
mounted() {
window.addEventListener('keydown', this.shortcutsListen)
this.$once('hook:beforeDestroy', () => {
beforeDestroy() {
window.removeEventListener('keydown', this.shortcutsListen)
})
}
}

@ -11,8 +11,12 @@ import {createGetters, createMutations} from "@/util/observable"
const state = {
//是否启用
enabled: true,
//是否启用快捷键切换功能
shortcut: true,
//是否将页签持久化到sessionStorage
persistent: true,
//显示的页签,{...route,title:route.meta.title}对象数组
//显示的页签,routeConfig对象数组
visitedViews: [],
//缓存的页签,用于<keep-router-view-alive/>:include
@ -36,11 +40,9 @@ export const mutations = bindThis({
},
addVisitedView(view) {
const {title = '暂无标题'} = view.meta || {}
if (store.visitedViews.some(v => v.path === view.path)) return
store.visitedViews.push({...view, title})
store.visitedViews.push(view)
},
addCachedView(view) {
const {noCache, iframe, usePathKey, useFullPathKey} = view.meta || {}

@ -18,7 +18,7 @@ const beforeEach = async (to, from, next) => {
//初始化路由和菜单权限
if (!store.state.resource.init) {
await store.dispatch('resource/init', {...store.state.user, addRoutes: true})
await store.dispatch('resource/init', store.state.user)
return next({...to, replace: true})
}

@ -6,7 +6,7 @@ import {getDynamicRoutes} from '@/router/define'
import {parseRoutes, metaExtend} from "@/router/util"
import {getAll} from "@/api/system/resource"
import {isEmpty} from "@/util"
import {needAuth} from "@/util/auth"
import {needAuth, auth} from "@/util/auth" // 此处存在循环引用?
import {createTree} from "@/util/tree"
import {isExternal} from "@/util/validate"
@ -42,20 +42,20 @@ const mutations = {
}
const actions = {
init({commit}, {resources, admin, addRoutes = false}) {
init({state, commit}, {resources, admin}) {
return getAll
.request()
.then(({data}) => {
commit('resource', {data: data || [], admin})
//动态添加路由,这里不需要进行权限过滤
const routes = transformOriginRouteData(data)
metaExtend(routes)
addRoutes && addDynamicRoutes(routes)
//可能存在多次调用的情况,所以仅在第一次调用时添加进vue-router
!state.init && addDynamicRoutes(routes)
//获取经过权限过滤后的菜单
const menus = getAuthorizedMenus({resources, admin}, routes)
appMutations.menus(menus)
commit('resource', {data: data || [], admin})
//生成经过权限过滤后的菜单
appMutations.menus(getAuthorizedMenus({resources, admin}, routes))
//设置初始化完成的标志
commit('init', true)
@ -99,9 +99,10 @@ function getAuthorizedMenus({resources, admin}, menus) {
menus = JSON.parse(JSON.stringify(menus))
clean(menus)
addFullPath(menus)
if (admin === true) return menus
if (!resources) return []
filter(menus, i => !needAuth(i) || i.fullPath in resources)
// if (admin === true) return menus
// if (!resources) return []
// filter(menus, i => !needAuth(i) || i.fullPath in resources)
filter(menus, i => !needAuth(i) || auth(i.fullPath))
return menus
}

@ -2,7 +2,11 @@ import store from '@/store'
import {uppercaseFirst} from "@/filter"
import {isEmpty} from "@/util"
//判断路由是否需要鉴权,需要则返回true
/**
* @desc 判断是否需要鉴权需要则返回true
* @param route vue-router的routeConfig或项目中说明的路由配置项
* @returns {boolean}
*/
export function needAuth(route) {
if (route.path.startsWith('/redirect')) return false
const {meta} = route

@ -1,56 +1,117 @@
import {storage} from "@/config"
import {isEmpty} from "@/util"
import {unzip, zip} from "@/util/secret"
const sessionUserKey = 'GCC-SESS-USER'
const localPersonalSettingsKey = 'GCC-LOCAL-PERSONAL-SETTINGS'
const {keyPrefix, zip: defaultUseZip} = storage
/**
* @desc 读取本地存储
* @param key 键名自动加上统一前缀
* @param storage window.sessionStorage或window.localStorage
* @param useZip 是否启用了压缩
* @returns {any|undefined}
*/
export function get(key, storage = window.sessionStorage, useZip = defaultUseZip) {
let obj = storage.getItem(`${keyPrefix}${key}`)
if (isEmpty(obj)) return undefined
try {
obj = JSON.parse(useZip ? unzip(obj) : obj)
}
catch (e) {
console.error(`获取本地存储[${key}]失败`, e)
obj = undefined
}
return obj
}
/**
* 写入本地存储
* @param key 键名自动加上统一前缀
* @param obj 需要写入本地存储的js对象
* @param storage window.sessionStorage或window.localStorage
* @param useZip 是否启用了压缩
*/
export function set(key, obj, storage = window.sessionStorage, useZip = defaultUseZip) {
const item = useZip ? zip(JSON.stringify(obj)) : JSON.stringify(obj)
storage.setItem(`${keyPrefix}${key}`, item)
}
/**
* @desc 判断本地存储中是否存在指定键名
* @param key 键名自动加上统一前缀
* @param storage window.sessionStorage或window.localStorage
* @returns {boolean} 存在则返回true
*/
export function exist(key, storage = window.sessionStorage) {
return !isEmpty(storage.getItem(`${keyPrefix}${key}`))
}
/**
* @desc 根据指定键名移除本地存储对应项
* @param key 键名自动加上统一前缀
* @param storage window.sessionStorage或window.localStorage
*/
export function remove(key, storage = window.sessionStorage) {
storage.removeItem(`${keyPrefix}${key}`)
}
const sessionUserKey = 'SESS-USER'
const localPersonalSettingsKey = 'LOCAL-PERSONAL-SETTINGS'
const sessionTagsViewKey = 'SESS-TAGS-VIEW'
export function isUserExist() {
return !isEmpty(sessionStorage.getItem(sessionUserKey))
return exist(sessionUserKey)
}
export function getUser() {
let obj = sessionStorage.getItem(sessionUserKey)
if (isEmpty(obj)) return {}
try {
obj = JSON.parse(unzip(obj))
}
catch (e) {
console.error('用户数据异常!', e)
obj = {}
let user = get(sessionUserKey)
if (isEmpty(user)) {
user = {}
removeUser()
}
return obj
return user
}
export function setUser(user) {
if (isEmpty(user)) return removeUser()
sessionStorage.setItem(sessionUserKey, zip(JSON.stringify(user)))
isEmpty(user) ? removeUser() : set(sessionUserKey, user)
}
export function getLocalPersonalSettings() {
let obj = localStorage.getItem(localPersonalSettingsKey)
if (isEmpty(obj)) return {}
try {
obj = JSON.parse(obj)
}
catch (e) {
console.error('本地个性化设置数据解析失败!', e)
let settings = get(localPersonalSettingsKey, window.localStorage)
if (isEmpty(settings)) {
settings = {}
removeLocalPersonalSettings()
return {}
}
return obj
return settings
}
export function setLocalPersonalSettings(settings) {
isEmpty(settings) ?
removeLocalPersonalSettings() :
localStorage.setItem(localPersonalSettingsKey, JSON.stringify(settings))
isEmpty(settings)
? removeLocalPersonalSettings()
: set(localPersonalSettingsKey, settings, window.localStorage)
}
export function getTagsView() {
let tagsView = get(sessionTagsViewKey)
if (isEmpty(tagsView)) {
tagsView = []
removeTagsView()
}
return tagsView
}
export function setTagsView(tagsView) {
isEmpty(tagsView) ? removeTagsView() : set(sessionTagsViewKey, tagsView)
}
function removeUser() {
sessionStorage.removeItem(sessionUserKey)
remove(sessionUserKey)
}
function removeLocalPersonalSettings() {
localStorage.removeItem(localPersonalSettingsKey)
remove(localPersonalSettingsKey, window.localStorage)
}
function removeTagsView() {
remove(sessionTagsViewKey)
}

Loading…
Cancel
Save