使用 pinia 替换 vuex

This commit is contained in:
hooray 2021-12-30 03:09:32 +08:00
parent 65eff15674
commit 5d7461c5cc
36 changed files with 699 additions and 640 deletions

View File

@ -46,8 +46,6 @@ module.exports = {
withDefaults: 'readonly', withDefaults: 'readonly',
// vue-router // vue-router
useRoute: 'readonly', useRoute: 'readonly',
useRouter: 'readonly', useRouter: 'readonly'
// vuex
useStore: 'readonly'
} }
} }

View File

@ -31,12 +31,12 @@
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"path-browserify": "^1.0.1", "path-browserify": "^1.0.1",
"path-to-regexp": "^6.2.0", "path-to-regexp": "^6.2.0",
"pinia": "^2.0.9",
"qs": "^6.10.2", "qs": "^6.10.2",
"screenfull": "^6.0.0", "screenfull": "^6.0.0",
"tinymce": "^5.10.2", "tinymce": "^5.10.2",
"vue": "^3.2.24", "vue": "^3.2.24",
"vue-router": "^4.0.12", "vue-router": "^4.0.12"
"vuex": "^4.0.2"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^1.10.2", "@vitejs/plugin-vue": "^1.10.2",

View File

@ -4,18 +4,8 @@
</div> </div>
</template> </template>
<script> <script setup{{#if isGlobal}} name="{{ properCase name }}"{{/if}}>
export default { // const { proxy } = getCurrentInstance()
{{#if isGlobal}}
name: '{{ properCase name }}',
{{/if}}
props: {},
data() {
return {}
},
mounted() {},
methods: {}
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -11,7 +11,6 @@
// const { proxy } = getCurrentInstance() // const { proxy } = getCurrentInstance()
// const router = useRouter() // const router = useRouter()
// const route = useRoute() // const route = useRoute()
// const store = useStore()
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -1,15 +1,16 @@
const state = () => ({}) import { defineStore } from 'pinia'
import { piniaStore } from '@/store'
const getters = {} export const use{{ properCase name }}Store = defineStore(
// 唯一ID
'{{ camelCase name }}',
{
state: () => ({}),
getters: {},
actions: {}
}
)
const actions = {} export function use{{ properCase name }}OutsideStore() {
return use{{ properCase name }}Store(piniaStore)
const mutations = {}
export default {
namespaced: true,
state,
actions,
getters,
mutations
} }

View File

@ -14,11 +14,11 @@ module.exports = {
} }
} }
], ],
actions: data => { actions: () => {
const actions = [ const actions = [
{ {
type: 'add', type: 'add',
path: `src/store/modules/${data.name}.js`, path: 'src/store/modules/{{camelCase name}}.js',
templateFile: 'plop-templates/store/index.hbs' templateFile: 'plop-templates/store/index.hbs'
} }
] ]

View File

@ -22,6 +22,7 @@ specifiers:
nprogress: ^0.2.0 nprogress: ^0.2.0
path-browserify: ^1.0.1 path-browserify: ^1.0.1
path-to-regexp: ^6.2.0 path-to-regexp: ^6.2.0
pinia: ^2.0.9
plop: ^3.0.4 plop: ^3.0.4
postcss-html: ^1.3.0 postcss-html: ^1.3.0
postcss-scss: ^4.0.2 postcss-scss: ^4.0.2
@ -51,7 +52,6 @@ specifiers:
vue: ^3.2.24 vue: ^3.2.24
vue-eslint-parser: ^8.0.1 vue-eslint-parser: ^8.0.1
vue-router: ^4.0.12 vue-router: ^4.0.12
vuex: ^4.0.2
dependencies: dependencies:
'@element-plus/icons-vue': 0.2.4_vue@3.2.24 '@element-plus/icons-vue': 0.2.4_vue@3.2.24
@ -68,12 +68,12 @@ dependencies:
nprogress: 0.2.0 nprogress: 0.2.0
path-browserify: 1.0.1 path-browserify: 1.0.1
path-to-regexp: 6.2.0 path-to-regexp: 6.2.0
pinia: 2.0.9_vue@3.2.24
qs: 6.10.2 qs: 6.10.2
screenfull: 6.0.0 screenfull: 6.0.0
tinymce: 5.10.2 tinymce: 5.10.2
vue: 3.2.24 vue: 3.2.24
vue-router: 4.0.12_vue@3.2.24 vue-router: 4.0.12_vue@3.2.24
vuex: 4.0.2_vue@3.2.24
devDependencies: devDependencies:
'@vitejs/plugin-vue': 1.10.2_vite@2.7.1 '@vitejs/plugin-vue': 1.10.2_vite@2.7.1
@ -413,6 +413,10 @@ packages:
resolution: {integrity: sha1-+OiAWdqkJFFZkkJqDH6lzeB+mb8=, tarball: '@vue/devtools-api/download/@vue/devtools-api-6.0.0-beta.19.tgz'} resolution: {integrity: sha1-+OiAWdqkJFFZkkJqDH6lzeB+mb8=, tarball: '@vue/devtools-api/download/@vue/devtools-api-6.0.0-beta.19.tgz'}
dev: false dev: false
/@vue/devtools-api/6.0.0-beta.21.1:
resolution: {integrity: sha512-FqC4s3pm35qGVeXRGOjTsRzlkJjrBLriDS9YXbflHLsfA9FrcKzIyWnLXoNm+/7930E8rRakXuAc2QkC50swAw==, tarball: '@vue/devtools-api/download/@vue/devtools-api-6.0.0-beta.21.1.tgz'}
dev: false
/@vue/reactivity/3.2.24: /@vue/reactivity/3.2.24:
resolution: {integrity: sha512-5eVsO9wfQ5erCMSRBjpqLkkI+LglJS7E0oLZJs2gsChpvOjH2Uwt3Hk1nVv0ywStnWg71Ykn3SyQwtnl7PknOQ==, tarball: '@vue/reactivity/download/@vue/reactivity-3.2.24.tgz'} resolution: {integrity: sha512-5eVsO9wfQ5erCMSRBjpqLkkI+LglJS7E0oLZJs2gsChpvOjH2Uwt3Hk1nVv0ywStnWg71Ykn3SyQwtnl7PknOQ==, tarball: '@vue/reactivity/download/@vue/reactivity-3.2.24.tgz'}
dependencies: dependencies:
@ -3808,6 +3812,23 @@ packages:
engines: {node: '>=8.6'} engines: {node: '>=8.6'}
dev: true dev: true
/pinia/2.0.9_vue@3.2.24:
resolution: {integrity: sha512-iuYdxLJKQ07YPyOHYH05wNG9eKWqkP/4y4GE8+RqEYtz5fwHgPA5kr6zQbg/DoEJGnR2XCm1w1vdt6ppzL9ATg==, tarball: pinia/download/pinia-2.0.9.tgz}
peerDependencies:
'@vue/composition-api': ^1.4.0
typescript: '>=4.4.4'
vue: ^2.6.14 || ^3.2.0
peerDependenciesMeta:
'@vue/composition-api':
optional: true
typescript:
optional: true
dependencies:
'@vue/devtools-api': 6.0.0-beta.21.1
vue: 3.2.24
vue-demi: 0.12.1_vue@3.2.24
dev: false
/pixelsmith/2.5.0: /pixelsmith/2.5.0:
resolution: {integrity: sha1-ez5SLgjqh3vXC63xWb3D7Ffh6EE=, tarball: pixelsmith/download/pixelsmith-2.5.0.tgz} resolution: {integrity: sha1-ez5SLgjqh3vXC63xWb3D7Ffh6EE=, tarball: pixelsmith/download/pixelsmith-2.5.0.tgz}
engines: {node: '>= 8.0.0'} engines: {node: '>= 8.0.0'}
@ -5477,15 +5498,6 @@ packages:
'@vue/shared': 3.2.24 '@vue/shared': 3.2.24
dev: false dev: false
/vuex/4.0.2_vue@3.2.24:
resolution: {integrity: sha1-+Jbb1b8qDpY/AMZ+m2EN50nMrMk=, tarball: vuex/download/vuex-4.0.2.tgz}
peerDependencies:
vue: ^3.0.2
dependencies:
'@vue/devtools-api': 6.0.0-beta.19
vue: 3.2.24
dev: false
/wcwidth/1.0.1: /wcwidth/1.0.1:
resolution: {integrity: sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=, tarball: wcwidth/download/wcwidth-1.0.1.tgz} resolution: {integrity: sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=, tarball: wcwidth/download/wcwidth-1.0.1.tgz}
dependencies: dependencies:

View File

@ -8,13 +8,14 @@
</template> </template>
<script setup> <script setup>
const store = useStore() import { useSettingsStore } from '@/store/modules/settings'
const settingsStore = useSettingsStore()
// //
const mainSidebarActualWidth = computed(() => { const mainSidebarActualWidth = computed(() => {
let actualWidth = getComputedStyle(document.documentElement).getPropertyValue('--g-main-sidebar-width') let actualWidth = getComputedStyle(document.documentElement).getPropertyValue('--g-main-sidebar-width')
actualWidth = parseInt(actualWidth) actualWidth = parseInt(actualWidth)
if (['head', 'single'].includes(store.state.settings.menuMode)) { if (['head', 'single'].includes(settingsStore.menuMode)) {
actualWidth = 0 actualWidth = 0
} }
return `${actualWidth}px` return `${actualWidth}px`
@ -24,39 +25,39 @@ const mainSidebarActualWidth = computed(() => {
const subSidebarActualWidth = computed(() => { const subSidebarActualWidth = computed(() => {
let actualWidth = getComputedStyle(document.documentElement).getPropertyValue('--g-sub-sidebar-width') let actualWidth = getComputedStyle(document.documentElement).getPropertyValue('--g-sub-sidebar-width')
actualWidth = parseInt(actualWidth) actualWidth = parseInt(actualWidth)
if (store.state.settings.sidebarCollapse) { if (settingsStore.sidebarCollapse) {
actualWidth = 64 actualWidth = 64
} }
return `${actualWidth}px` return `${actualWidth}px`
}) })
watch(() => store.state.settings.mode, () => { watch(() => settingsStore.mode, () => {
if (store.state.settings.mode === 'pc') { if (settingsStore.mode === 'pc') {
store.commit('settings/updateThemeSetting', { settingsStore.updateThemeSetting({
'sidebarCollapse': store.state.settings.sidebarCollapseLastStatus 'sidebarCollapse': settingsStore.sidebarCollapseLastStatus
}) })
} else if (store.state.settings.mode === 'mobile') { } else if (settingsStore.mode === 'mobile') {
store.commit('settings/updateThemeSetting', { settingsStore.updateThemeSetting({
'sidebarCollapse': true 'sidebarCollapse': true
}) })
} }
document.body.setAttribute('data-mode', store.state.settings.mode) document.body.setAttribute('data-mode', settingsStore.mode)
}, { }, {
immediate: true immediate: true
}) })
watch(() => store.state.settings.menuMode, () => { watch(() => settingsStore.menuMode, () => {
document.body.setAttribute('data-menu-mode', store.state.settings.menuMode) document.body.setAttribute('data-menu-mode', settingsStore.menuMode)
}, { }, {
immediate: true immediate: true
}) })
watch([ watch([
() => store.state.settings.enableDynamicTitle, () => settingsStore.enableDynamicTitle,
() => store.state.settings.title () => settingsStore.title
], () => { ], () => {
if (store.state.settings.enableDynamicTitle && store.state.settings.title) { if (settingsStore.enableDynamicTitle && settingsStore.title) {
let title = store.state.settings.title let title = settingsStore.title
document.title = `${title} - ${import.meta.env.VITE_APP_TITLE}` document.title = `${title} - ${import.meta.env.VITE_APP_TITLE}`
} else { } else {
document.title = import.meta.env.VITE_APP_TITLE document.title = import.meta.env.VITE_APP_TITLE
@ -67,7 +68,7 @@ watch([
onMounted(() => { onMounted(() => {
window.onresize = () => { window.onresize = () => {
store.commit('settings/setMode', document.documentElement.clientWidth) settingsStore.setMode(document.documentElement.clientWidth)
} }
window.onresize() window.onresize()
}) })

View File

@ -1,11 +1,12 @@
import axios from 'axios' import axios from 'axios'
// import qs from 'qs' // import qs from 'qs'
import router from '@/router/index' import router from '@/router/index'
import store from '@/store/index'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { useUserOutsideStore } from '@/store/modules/user'
const toLogin = () => { const toLogin = () => {
store.dispatch('user/logout').then(() => { const userOutsideStore = useUserOutsideStore()
userOutsideStore.logout().then(() => {
router.push({ router.push({
name: 'login', name: 'login',
query: { query: {
@ -23,12 +24,13 @@ const api = axios.create({
api.interceptors.request.use( api.interceptors.request.use(
request => { request => {
const userOutsideStore = useUserOutsideStore()
/** /**
* 全局拦截请求发送前提交的参数 * 全局拦截请求发送前提交的参数
* 以下代码为示例在请求头里带上 token 信息 * 以下代码为示例在请求头里带上 token 信息
*/ */
if (store.getters['user/isLogin']) { if (userOutsideStore.isLogin) {
request.headers['Token'] = store.state.user.token request.headers['Token'] = userOutsideStore.token
} }
// 是否将 POST 请求参数进行字符串化处理 // 是否将 POST 请求参数进行字符串化处理
if (request.method === 'post') { if (request.method === 'post') {

View File

@ -1,12 +1,17 @@
<template> <template>
<footer class="copyright"> <footer class="copyright">
Copyright © {{ $store.state.settings.copyrightDates }} Copyright © {{ settingsStore.copyrightDates }}
<a v-if="$store.state.settings.copyrightWebsite" :href="$store.state.settings.copyrightWebsite" target="_blank" rel="noopener">{{ $store.state.settings.copyrightCompany }},</a> <a v-if="settingsStore.copyrightWebsite" :href="settingsStore.copyrightWebsite" target="_blank" rel="noopener">{{ settingsStore.copyrightCompany }},</a>
<span v-else>{{ $store.state.settings.copyrightCompany }},</span> <span v-else>{{ settingsStore.copyrightCompany }},</span>
All Rights Reserved All Rights Reserved
</footer> </footer>
</template> </template>
<script setup>
import { useSettingsStore } from '@/store/modules/settings'
const settingsStore = useSettingsStore()
</script>
<style lang="scss" scoped> <style lang="scss" scoped>
footer { footer {
margin: 40px 0 20px; margin: 40px 0 20px;

View File

@ -1,13 +1,13 @@
<template> <template>
<transition name="header"> <transition name="header">
<header v-if="$store.state.settings.mode === 'pc' && $store.state.settings.menuMode === 'head'"> <header v-if="settingsStore.mode === 'pc' && settingsStore.menuMode === 'head'">
<div class="header-container"> <div class="header-container">
<div class="main"> <div class="main">
<Logo /> <Logo />
<!-- 顶部模式 --> <!-- 顶部模式 -->
<div class="nav"> <div class="nav">
<template v-for="(item, index) in $store.getters['menu/routes']"> <template v-for="(item, index) in menuStore.transformRoutes">
<div v-if="item.children && item.children.length !== 0" :key="index" class="item" :class="{'active': index == $store.state.menu.headerActived}" @click="switchMenu(index)"> <div v-if="item.children && item.children.length !== 0" :key="index" class="item" :class="{'active': index == menuStore.headerActived}" @click="switchMenu(index)">
<svg-icon v-if="item.meta.icon" :name="item.meta.icon" /> <svg-icon v-if="item.meta.icon" :name="item.meta.icon" />
<span v-if="item.meta.title">{{ item.meta.title }}</span> <span v-if="item.meta.title">{{ item.meta.title }}</span>
</div> </div>
@ -24,6 +24,11 @@
import Logo from '../Logo/index.vue' import Logo from '../Logo/index.vue'
import UserMenu from '../UserMenu/index.vue' import UserMenu from '../UserMenu/index.vue'
import { useSettingsStore } from '@/store/modules/settings'
const settingsStore = useSettingsStore()
import { useMenuStore } from '@/store/modules/menu'
const menuStore = useMenuStore()
const switchMenu = inject('switchMenu') const switchMenu = inject('switchMenu')
</script> </script>

View File

@ -1,5 +1,5 @@
<template> <template>
<router-link :to="to" class="title" :class="{'is-link': $store.state.settings.enableDashboard}" :title="title"> <router-link :to="to" class="title" :class="{'is-link': settingsStore.enableDashboard}" :title="title">
<img v-if="showLogo" :src="logo" class="logo"> <img v-if="showLogo" :src="logo" class="logo">
<span v-if="showTitle">{{ title }}</span> <span v-if="showTitle">{{ title }}</span>
</router-link> </router-link>
@ -8,7 +8,8 @@
<script setup> <script setup>
import imgLogo from '@/assets/images/logo.png' import imgLogo from '@/assets/images/logo.png'
const store = useStore() import { useSettingsStore } from '@/store/modules/settings'
const settingsStore = useSettingsStore()
defineProps({ defineProps({
showLogo: { showLogo: {
@ -26,7 +27,7 @@ const logo = ref(imgLogo)
const to = computed(() => { const to = computed(() => {
let rtn = {} let rtn = {}
if (store.state.settings.enableDashboard) { if (settingsStore.enableDashboard) {
rtn.name = 'dashboard' rtn.name = 'dashboard'
} }
return rtn return rtn

View File

@ -1,14 +1,14 @@
<template> <template>
<transition name="main-sidebar"> <transition name="main-sidebar">
<div v-if="$store.state.settings.menuMode === 'side' || ($store.state.settings.mode === 'mobile' && $store.state.settings.menuMode !== 'single')" class="main-sidebar-container"> <div v-if="settingsStore.menuMode === 'side' || (settingsStore.mode === 'mobile' && settingsStore.menuMode !== 'single')" class="main-sidebar-container">
<Logo :show-title="false" class="sidebar-logo" /> <Logo :show-title="false" class="sidebar-logo" />
<!-- 侧边栏模式含主导航 --> <!-- 侧边栏模式含主导航 -->
<div class="nav"> <div class="nav">
<template v-for="(item, index) in $store.getters['menu/routes']"> <template v-for="(item, index) in menuStore.transformRoutes">
<div <div
v-if="item.children && item.children.length !== 0" :key="index" :class="{ v-if="item.children && item.children.length !== 0" :key="index" :class="{
'item': true, 'item': true,
'active': index === $store.state.menu.headerActived 'active': index === menuStore.headerActived
}" :title="item.meta.title" @click="switchMenu(index)" }" :title="item.meta.title" @click="switchMenu(index)"
> >
<svg-icon v-if="item.meta.icon" :name="item.meta.icon" /> <svg-icon v-if="item.meta.icon" :name="item.meta.icon" />
@ -23,6 +23,11 @@
<script setup> <script setup>
import Logo from '../Logo/index.vue' import Logo from '../Logo/index.vue'
import { useSettingsStore } from '@/store/modules/settings'
const settingsStore = useSettingsStore()
import { useMenuStore } from '@/store/modules/menu'
const menuStore = useMenuStore()
const switchMenu = inject('switchMenu') const switchMenu = inject('switchMenu')
</script> </script>

View File

@ -3,7 +3,7 @@
<div class="container"> <div class="container">
<div class="search-box" @click.stop> <div class="search-box" @click.stop>
<el-input ref="input" v-model="searchInput" prefix-icon="el-icon-search" placeholder="搜索页面支持标题、URL模糊查询" clearable @keydown.esc="$eventBus.emit('global-search-toggle')" @keydown.up.prevent="keyUp" @keydown.down.prevent="keyDown" @keydown.enter.prevent="keyEnter" /> <el-input ref="input" v-model="searchInput" prefix-icon="el-icon-search" placeholder="搜索页面支持标题、URL模糊查询" clearable @keydown.esc="$eventBus.emit('global-search-toggle')" @keydown.up.prevent="keyUp" @keydown.down.prevent="keyDown" @keydown.enter.prevent="keyEnter" />
<div v-if="$store.state.settings.mode === 'pc'" class="tips"> <div v-if="settingsStore.mode === 'pc'" class="tips">
<div class="tip"> <div class="tip">
<span>Alt</span>+<span>S</span> <span>Alt</span>+<span>S</span>
唤醒搜索面板 唤醒搜索面板
@ -23,7 +23,7 @@
</div> </div>
</div> </div>
</div> </div>
<div ref="search" class="result" :class="{'mobile': $store.state.settings.mode === 'mobile'}"> <div ref="search" class="result" :class="{'mobile': settingsStore.mode === 'mobile'}">
<router-link v-for="(item, index) in resultList" :key="item.path" v-slot="{ href, navigate }" custom :to="isShow ? item.path : ''"> <router-link v-for="(item, index) in resultList" :key="item.path" v-slot="{ href, navigate }" custom :to="isShow ? item.path : ''">
<a :ref="`search-item-${index}`" :href="isExternalLink(item.path) ? item.path : href" class="item" :class="{'actived': index === actived}" :target="isExternalLink(item.path) ? '_blank' : '_self'" @click="navigate" @mouseover="actived = index"> <a :ref="`search-item-${index}`" :href="isExternalLink(item.path) ? item.path : href" class="item" :class="{'actived': index === actived}" :target="isExternalLink(item.path) ? '_blank' : '_self'" @click="navigate" @mouseover="actived = index">
<div class="icon"> <div class="icon">
@ -53,7 +53,11 @@
import { deepClone, isExternalLink } from '@/util' import { deepClone, isExternalLink } from '@/util'
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
const store = useStore()
import { useSettingsStore } from '@/store/modules/settings'
const settingsStore = useSettingsStore()
import { useMenuStore } from '@/store/modules/menu'
const menuStore = useMenuStore()
const isShow = ref(false) const isShow = ref(false)
const searchInput = ref('') const searchInput = ref('')
@ -110,12 +114,12 @@ onMounted(() => {
isShow.value = !isShow.value isShow.value = !isShow.value
}) })
proxy.$hotkeys('alt+s', e => { proxy.$hotkeys('alt+s', e => {
if (store.state.settings.enableNavSearch) { if (settingsStore.enableNavSearch) {
e.preventDefault() e.preventDefault()
isShow.value = true isShow.value = true
} }
}) })
store.state.menu.routes.map(item => { menuStore.routes.map(item => {
getSourceList(item.children) getSourceList(item.children)
}) })
}) })

View File

@ -1,20 +1,20 @@
<template> <template>
<div v-if="['side', 'head', 'single'].includes($store.state.settings.menuMode) || $store.state.settings.mode === 'mobile'" class="sub-sidebar-container" :class="{'is-collapse': $store.state.settings.mode === 'pc' && $store.state.settings.sidebarCollapse}" @scroll="onSidebarScroll"> <div v-if="['side', 'head', 'single'].includes(settingsStore.menuMode) || settingsStore.mode === 'mobile'" class="sub-sidebar-container" :class="{'is-collapse': settingsStore.mode === 'pc' && settingsStore.sidebarCollapse}" @scroll="onSidebarScroll">
<Logo <Logo
:show-logo="$store.state.settings.menuMode === 'single'" :class="{ :show-logo="settingsStore.menuMode === 'single'" :class="{
'sidebar-logo': true, 'sidebar-logo': true,
'sidebar-logo-bg': $store.state.settings.menuMode === 'single', 'sidebar-logo-bg': settingsStore.menuMode === 'single',
'shadow': sidebarScrollTop 'shadow': sidebarScrollTop
}" }"
/> />
<!-- 侧边栏模式无主导航 --> <!-- 侧边栏模式无主导航 -->
<el-menu <el-menu
:unique-opened="$store.state.settings.sidebarUniqueOpened" :default-openeds="$store.state.menu.defaultOpenedPaths" :default-active="$route.meta.activeMenu || $route.path" :collapse="$store.state.settings.mode === 'pc' && $store.state.settings.sidebarCollapse" :collapse-transition="false" :class="{ :unique-opened="settingsStore.sidebarUniqueOpened" :default-openeds="menuStore.defaultOpenedPaths" :default-active="$route.meta.activeMenu || $route.path" :collapse="settingsStore.mode === 'pc' && settingsStore.sidebarCollapse" :collapse-transition="false" :class="{
'is-collapse-without-logo': $store.state.settings.menuMode !== 'single' && $store.state.settings.sidebarCollapse 'is-collapse-without-logo': settingsStore.menuMode !== 'single' && settingsStore.sidebarCollapse
}" }"
> >
<transition-group name="sub-sidebar"> <transition-group name="sub-sidebar">
<template v-for="route in $store.getters['menu/sidebarRoutes']"> <template v-for="route in menuStore.sidebarRoutes">
<SidebarItem v-if="route.meta.sidebar !== false" :key="route.path" :item="route" :base-path="route.path" /> <SidebarItem v-if="route.meta.sidebar !== false" :key="route.path" :item="route" :base-path="route.path" />
</template> </template>
</transition-group> </transition-group>
@ -26,6 +26,11 @@
import Logo from '../Logo/index.vue' import Logo from '../Logo/index.vue'
import SidebarItem from '../SidebarItem/index.vue' import SidebarItem from '../SidebarItem/index.vue'
import { useSettingsStore } from '@/store/modules/settings'
const settingsStore = useSettingsStore()
import { useMenuStore } from '@/store/modules/menu'
const menuStore = useMenuStore()
const sidebarScrollTop = ref(0) const sidebarScrollTop = ref(0)
function onSidebarScroll(e) { function onSidebarScroll(e) {

View File

@ -2,8 +2,8 @@
<div> <div>
<el-drawer v-model="isShow" title="主题配置" direction="rtl" :size="300"> <el-drawer v-model="isShow" title="主题配置" direction="rtl" :size="300">
<el-alert title="主题配置可实时预览效果,更多设置请在 src/settings.js 中进行设置,建议在生产环境隐藏主题配置功能" type="error" :closable="false" /> <el-alert title="主题配置可实时预览效果,更多设置请在 src/settings.js 中进行设置,建议在生产环境隐藏主题配置功能" type="error" :closable="false" />
<el-divider v-if="$store.state.settings.mode === 'pc'">导航栏模式</el-divider> <el-divider v-if="settingsStore.mode === 'pc'">导航栏模式</el-divider>
<div v-if="$store.state.settings.mode === 'pc'" class="menu-mode"> <div v-if="settingsStore.mode === 'pc'" class="menu-mode">
<el-tooltip content="侧边栏模式(含主导航)" placement="top" :show-after="500" :append-to-body="false"> <el-tooltip content="侧边栏模式(含主导航)" placement="top" :show-after="500" :append-to-body="false">
<div class="mode mode-side" :class="{'active': menuMode === 'side'}" @click="menuMode = 'side'"> <div class="mode mode-side" :class="{'active': menuMode === 'side'}" @click="menuMode = 'side'">
<svg-icon name="el-icon-check" /> <svg-icon name="el-icon-check" />
@ -21,7 +21,7 @@
</el-tooltip> </el-tooltip>
</div> </div>
<el-divider>侧边栏</el-divider> <el-divider>侧边栏</el-divider>
<div v-if="$store.state.settings.mode === 'pc'" class="setting-item"> <div v-if="settingsStore.mode === 'pc'" class="setting-item">
<div class="label">折叠按钮</div> <div class="label">折叠按钮</div>
<el-switch v-model="enableSidebarCollapse" /> <el-switch v-model="enableSidebarCollapse" />
</div> </div>
@ -36,7 +36,7 @@
<svg-icon name="el-icon-question-filled" /> <svg-icon name="el-icon-question-filled" />
</el-tooltip> </el-tooltip>
</div> </div>
<el-switch v-model="switchSidebarAndPageJump" :disabled="['single'].includes($store.state.settings.menuMode)" /> <el-switch v-model="switchSidebarAndPageJump" :disabled="['single'].includes(settingsStore.menuMode)" />
</div> </div>
<div class="setting-item"> <div class="setting-item">
<div class="label"> <div class="label">
@ -57,7 +57,7 @@
</div> </div>
<el-switch v-model="topbarFixed" /> <el-switch v-model="topbarFixed" />
</div> </div>
<div v-if="$store.state.settings.mode === 'pc'" class="setting-item"> <div v-if="settingsStore.mode === 'pc'" class="setting-item">
<div class="label">面包屑导航</div> <div class="label">面包屑导航</div>
<el-switch v-model="enableBreadcrumb" /> <el-switch v-model="enableBreadcrumb" />
</div> </div>
@ -71,7 +71,7 @@
</div> </div>
<el-switch v-model="enableNavSearch" /> <el-switch v-model="enableNavSearch" />
</div> </div>
<div v-if="$store.state.settings.mode === 'pc'" class="setting-item"> <div v-if="settingsStore.mode === 'pc'" class="setting-item">
<div class="label"> <div class="label">
全屏 全屏
<el-tooltip content="该功能使用场景极少,用户习惯于通过窗口“最大化”功能来扩大显示区域,以显示更多内容,并且使用 F11 键也可以进入全屏效果" placement="top" :append-to-body="false"> <el-tooltip content="该功能使用场景极少,用户习惯于通过窗口“最大化”功能来扩大显示区域,以显示更多内容,并且使用 F11 键也可以进入全屏效果" placement="top" :append-to-body="false">
@ -141,32 +141,36 @@
<script setup> <script setup>
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
const store = useStore()
const route = useRoute() const route = useRoute()
import { useSettingsStore } from '@/store/modules/settings'
const settingsStore = useSettingsStore()
import { useMenuStore } from '@/store/modules/menu'
const menuStore = useMenuStore()
const reload = inject('reload') const reload = inject('reload')
const isShow = ref(false) const isShow = ref(false)
const menuMode = computed({ const menuMode = computed({
get: function() { get: function() {
return store.state.settings.menuMode return settingsStore.menuMode
}, },
set: function(newValue) { set: function(newValue) {
store.commit('menu/switchHeaderActived', 0) menuStore.switchHeaderActived(0)
store.commit('settings/updateThemeSetting', { settingsStore.updateThemeSetting({
'menuMode': newValue 'menuMode': newValue
}) })
store.state.settings.menuMode !== 'single' && store.commit('menu/setHeaderActived', route.fullPath) settingsStore.menuMode !== 'single' && menuStore.setHeaderActived(route.fullPath)
} }
}) })
const elementSize = computed({ const elementSize = computed({
get: function() { get: function() {
return store.state.settings.elementSize return settingsStore.elementSize
}, },
set: function(newValue) { set: function(newValue) {
proxy.$ELEMENT.size = newValue proxy.$ELEMENT.size = newValue
store.commit('settings/updateThemeSetting', { settingsStore.updateThemeSetting({
'elementSize': newValue 'elementSize': newValue
}) })
reload() reload()
@ -174,130 +178,130 @@ const elementSize = computed({
}) })
const enableSidebarCollapse = computed({ const enableSidebarCollapse = computed({
get: function() { get: function() {
return store.state.settings.enableSidebarCollapse return settingsStore.enableSidebarCollapse
}, },
set: function(newValue) { set: function(newValue) {
store.commit('settings/updateThemeSetting', { settingsStore.updateThemeSetting({
'enableSidebarCollapse': newValue 'enableSidebarCollapse': newValue
}) })
} }
}) })
const sidebarCollapse = computed({ const sidebarCollapse = computed({
get: function() { get: function() {
return store.state.settings.sidebarCollapse return settingsStore.sidebarCollapse
}, },
set: function(newValue) { set: function(newValue) {
store.commit('settings/updateThemeSetting', { settingsStore.updateThemeSetting({
'sidebarCollapse': newValue 'sidebarCollapse': newValue
}) })
} }
}) })
const switchSidebarAndPageJump = computed({ const switchSidebarAndPageJump = computed({
get: function() { get: function() {
return store.state.settings.switchSidebarAndPageJump return settingsStore.switchSidebarAndPageJump
}, },
set: function(newValue) { set: function(newValue) {
store.commit('settings/updateThemeSetting', { settingsStore.updateThemeSetting({
'switchSidebarAndPageJump': newValue 'switchSidebarAndPageJump': newValue
}) })
} }
}) })
const sidebarUniqueOpened = computed({ const sidebarUniqueOpened = computed({
get: function() { get: function() {
return store.state.settings.sidebarUniqueOpened return settingsStore.sidebarUniqueOpened
}, },
set: function(newValue) { set: function(newValue) {
store.commit('settings/updateThemeSetting', { settingsStore.updateThemeSetting({
'sidebarUniqueOpened': newValue 'sidebarUniqueOpened': newValue
}) })
} }
}) })
const topbarFixed = computed({ const topbarFixed = computed({
get: function() { get: function() {
return store.state.settings.topbarFixed return settingsStore.topbarFixed
}, },
set: function(newValue) { set: function(newValue) {
store.commit('settings/updateThemeSetting', { settingsStore.updateThemeSetting({
'topbarFixed': newValue 'topbarFixed': newValue
}) })
} }
}) })
const enableBreadcrumb = computed({ const enableBreadcrumb = computed({
get: function() { get: function() {
return store.state.settings.enableBreadcrumb return settingsStore.enableBreadcrumb
}, },
set: function(newValue) { set: function(newValue) {
store.commit('settings/updateThemeSetting', { settingsStore.updateThemeSetting({
'enableBreadcrumb': newValue 'enableBreadcrumb': newValue
}) })
} }
}) })
const showCopyright = computed({ const showCopyright = computed({
get: function() { get: function() {
return store.state.settings.showCopyright return settingsStore.showCopyright
}, },
set: function(newValue) { set: function(newValue) {
store.commit('settings/updateThemeSetting', { settingsStore.updateThemeSetting({
'showCopyright': newValue 'showCopyright': newValue
}) })
} }
}) })
const enableNavSearch = computed({ const enableNavSearch = computed({
get: function() { get: function() {
return store.state.settings.enableNavSearch return settingsStore.enableNavSearch
}, },
set: function(newValue) { set: function(newValue) {
store.commit('settings/updateThemeSetting', { settingsStore.updateThemeSetting({
'enableNavSearch': newValue 'enableNavSearch': newValue
}) })
} }
}) })
const enableFullscreen = computed({ const enableFullscreen = computed({
get: function() { get: function() {
return store.state.settings.enableFullscreen return settingsStore.enableFullscreen
}, },
set: function(newValue) { set: function(newValue) {
store.commit('settings/updateThemeSetting', { settingsStore.updateThemeSetting({
'enableFullscreen': newValue 'enableFullscreen': newValue
}) })
} }
}) })
const enablePageReload = computed({ const enablePageReload = computed({
get: function() { get: function() {
return store.state.settings.enablePageReload return settingsStore.enablePageReload
}, },
set: function(newValue) { set: function(newValue) {
store.commit('settings/updateThemeSetting', { settingsStore.updateThemeSetting({
'enablePageReload': newValue 'enablePageReload': newValue
}) })
} }
}) })
const enableProgress = computed({ const enableProgress = computed({
get: function() { get: function() {
return store.state.settings.enableProgress return settingsStore.enableProgress
}, },
set: function(newValue) { set: function(newValue) {
store.commit('settings/updateThemeSetting', { settingsStore.updateThemeSetting({
'enableProgress': newValue 'enableProgress': newValue
}) })
} }
}) })
const enableDynamicTitle = computed({ const enableDynamicTitle = computed({
get: function() { get: function() {
return store.state.settings.enableDynamicTitle return settingsStore.enableDynamicTitle
}, },
set: function(newValue) { set: function(newValue) {
store.commit('settings/updateThemeSetting', { settingsStore.updateThemeSetting({
'enableDynamicTitle': newValue 'enableDynamicTitle': newValue
}) })
} }
}) })
const enableDashboard = computed({ const enableDashboard = computed({
get: function() { get: function() {
return store.state.settings.enableDashboard return settingsStore.enableDashboard
}, },
set: function(newValue) { set: function(newValue) {
store.commit('settings/updateThemeSetting', { settingsStore.updateThemeSetting({
'enableDashboard': newValue 'enableDashboard': newValue
}) })
} }

View File

@ -1,15 +1,15 @@
<template> <template>
<div <div
class="topbar-container" :class="{ class="topbar-container" :class="{
'fixed': $store.state.settings.topbarFixed, 'fixed': settingsStore.topbarFixed,
'shadow': scrollTop 'shadow': scrollTop
}" data-fixed-calc-width }" data-fixed-calc-width
> >
<div class="left-box"> <div class="left-box">
<div v-if="$store.state.settings.mode === 'mobile' || (['side', 'head', 'single'].includes($store.state.settings.menuMode) && $store.state.settings.enableSidebarCollapse)" class="sidebar-collapse" :class="{'is-collapse': $store.state.settings.sidebarCollapse}" @click="$store.commit('settings/toggleSidebarCollapse')"> <div v-if="enableSidebarCollapse" class="sidebar-collapse" :class="{'is-collapse': settingsStore.sidebarCollapse}" @click="settingsStore.toggleSidebarCollapse()">
<svg-icon name="toolbar-collapse" /> <svg-icon name="toolbar-collapse" />
</div> </div>
<el-breadcrumb v-if="$store.state.settings.enableBreadcrumb && $store.state.settings.mode === 'pc'" separator-class="el-icon-arrow-right"> <el-breadcrumb v-if="settingsStore.enableBreadcrumb && settingsStore.mode === 'pc'" separator-class="el-icon-arrow-right">
<transition-group name="breadcrumb"> <transition-group name="breadcrumb">
<template v-for="(item, index) in breadcrumbList"> <template v-for="(item, index) in breadcrumbList">
<el-breadcrumb-item v-if="index < breadcrumbList.length - 1" :key="item.path" :to="pathCompile(item.path)"> <el-breadcrumb-item v-if="index < breadcrumbList.length - 1" :key="item.path" :to="pathCompile(item.path)">
@ -31,15 +31,24 @@ import { compile } from 'path-to-regexp'
import { deepClone } from '@/util' import { deepClone } from '@/util'
import UserMenu from '../UserMenu/index.vue' import UserMenu from '../UserMenu/index.vue'
const store = useStore()
const route = useRoute() const route = useRoute()
import { useSettingsStore } from '@/store/modules/settings'
const settingsStore = useSettingsStore()
const enableSidebarCollapse = computed(() => {
return settingsStore.mode === 'mobile' || (
['side', 'head', 'single'].includes(settingsStore.menuMode) &&
settingsStore.enableSidebarCollapse
)
})
const breadcrumbList = computed(() => { const breadcrumbList = computed(() => {
let breadcrumbList = [] let breadcrumbList = []
if (store.state.settings.enableDashboard) { if (settingsStore.enableDashboard) {
breadcrumbList.push({ breadcrumbList.push({
path: '/dashboard', path: '/dashboard',
title: store.state.settings.dashboardTitle title: settingsStore.dashboardTitle
}) })
} }
if (route.meta.breadcrumbNeste) { if (route.meta.breadcrumbNeste) {

View File

@ -1,20 +1,20 @@
<template> <template>
<div class="user"> <div class="user">
<div class="tools"> <div class="tools">
<span v-if="$store.state.settings.mode == 'pc'" class="item item-pro" @click="pro"> <span v-if="settingsStore.mode == 'pc'" class="item item-pro" @click="pro">
<svg-icon name="pro" /> <svg-icon name="pro" />
<span class="title">查看专业版</span> <span class="title">查看专业版</span>
</span> </span>
<span v-if="$store.state.settings.enableNavSearch" class="item" @click="$eventBus.emit('global-search-toggle')"> <span v-if="settingsStore.enableNavSearch" class="item" @click="$eventBus.emit('global-search-toggle')">
<svg-icon name="search" /> <svg-icon name="search" />
</span> </span>
<span v-if="$store.state.settings.mode === 'pc' && isFullscreenEnable && $store.state.settings.enableFullscreen" class="item" @click="fullscreen"> <span v-if="settingsStore.mode === 'pc' && isFullscreenEnable && settingsStore.enableFullscreen" class="item" @click="fullscreen">
<svg-icon :name="isFullscreen ? 'fullscreen-exit' : 'fullscreen'" /> <svg-icon :name="isFullscreen ? 'fullscreen-exit' : 'fullscreen'" />
</span> </span>
<span v-if="$store.state.settings.enablePageReload" class="item" @click="reload()"> <span v-if="settingsStore.enablePageReload" class="item" @click="reload()">
<svg-icon name="toolbar-reload" /> <svg-icon name="toolbar-reload" />
</span> </span>
<span v-if="$store.state.settings.enableThemeSetting" class="item" @click="$eventBus.emit('global-theme-toggle')"> <span v-if="settingsStore.enableThemeSetting" class="item" @click="$eventBus.emit('global-theme-toggle')">
<svg-icon name="toolbar-theme" /> <svg-icon name="toolbar-theme" />
</span> </span>
</div> </div>
@ -23,12 +23,12 @@
<el-avatar size="medium"> <el-avatar size="medium">
<el-icon><el-icon-user-filled /></el-icon> <el-icon><el-icon-user-filled /></el-icon>
</el-avatar> </el-avatar>
{{ $store.state.user.account }} {{ userStore.account }}
<el-icon><el-icon-caret-bottom /></el-icon> <el-icon><el-icon-caret-bottom /></el-icon>
</div> </div>
<template #dropdown> <template #dropdown>
<el-dropdown-menu class="user-dropdown"> <el-dropdown-menu class="user-dropdown">
<el-dropdown-item v-if="$store.state.settings.enableDashboard" command="dashboard">控制台</el-dropdown-item> <el-dropdown-item v-if="settingsStore.enableDashboard" command="dashboard">控制台</el-dropdown-item>
<el-dropdown-item command="setting">个人设置</el-dropdown-item> <el-dropdown-item command="setting">个人设置</el-dropdown-item>
<el-dropdown-item divided command="logout">退出登录</el-dropdown-item> <el-dropdown-item divided command="logout">退出登录</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
@ -41,9 +41,13 @@
import screenfull from 'screenfull' import screenfull from 'screenfull'
const reload = inject('reload') const reload = inject('reload')
const store = useStore()
const router = useRouter() const router = useRouter()
import { useSettingsStore } from '@/store/modules/settings'
const settingsStore = useSettingsStore()
import { useUserStore } from '@/store/modules/user'
const userStore = useUserStore()
const isFullscreenEnable = computed(() => screenfull.isEnabled) const isFullscreenEnable = computed(() => screenfull.isEnabled)
const isFullscreen = ref(false) const isFullscreen = ref(false)
@ -77,7 +81,7 @@ function userCommand(command) {
}) })
break break
case 'logout': case 'logout':
store.dispatch('user/logout').then(() => { userStore.logout().then(() => {
router.push({ router.push({
name: 'login' name: 'login'
}) })

View File

@ -3,17 +3,17 @@
<div id="app-main"> <div id="app-main">
<Header /> <Header />
<div class="wrapper"> <div class="wrapper">
<div class="sidebar-container" :class="{'show': $store.state.settings.mode === 'mobile' && !$store.state.settings.sidebarCollapse}"> <div class="sidebar-container" :class="{'show': settingsStore.mode === 'mobile' && !settingsStore.sidebarCollapse}">
<MainSidebar /> <MainSidebar />
<SubSidebar /> <SubSidebar />
</div> </div>
<div class="sidebar-mask" :class="{'show': $store.state.settings.mode === 'mobile' && !$store.state.settings.sidebarCollapse}" @click="$store.commit('settings/toggleSidebarCollapse')" /> <div class="sidebar-mask" :class="{'show': settingsStore.mode === 'mobile' && !settingsStore.sidebarCollapse}" @click="settingsStore.toggleSidebarCollapse()" />
<div class="main-container" :style="{'padding-bottom': $route.meta.paddingBottom}"> <div class="main-container" :style="{'padding-bottom': $route.meta.paddingBottom}">
<Topbar v-if="!($store.state.settings.menuMode === 'head' && !$store.state.settings.enableSidebarCollapse && !$store.state.settings.enableBreadcrumb)" /> <Topbar v-if="!(settingsStore.menuMode === 'head' && !settingsStore.enableSidebarCollapse && !settingsStore.enableBreadcrumb)" />
<div class="main"> <div class="main">
<router-view v-slot="{ Component, route }"> <router-view v-slot="{ Component, route }">
<transition name="main" mode="out-in" appear> <transition name="main" mode="out-in" appear>
<keep-alive :include="$store.state.keepAlive.list"> <keep-alive :include="keepAliveStore.list">
<component :is="Component" :key="route.fullPath" /> <component :is="Component" :key="route.fullPath" />
</keep-alive> </keep-alive>
</transition> </transition>
@ -41,15 +41,21 @@ import BuyIt from './components/BuyIt/index.vue'
import { isExternalLink } from '@/util' import { isExternalLink } from '@/util'
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
const store = useStore()
const routeInfo = useRoute(), router = useRouter() const routeInfo = useRoute(), router = useRouter()
import { useSettingsStore } from '@/store/modules/settings'
const settingsStore = useSettingsStore()
import { useKeepAliveStore } from '@/store/modules/keepAlive'
const keepAliveStore = useKeepAliveStore()
import { useMenuStore } from '@/store/modules/menu'
const menuStore = useMenuStore()
const showCopyright = computed(() => { const showCopyright = computed(() => {
return typeof routeInfo.meta.copyright !== 'undefined' ? routeInfo.meta.copyright : store.state.settings.showCopyright return typeof routeInfo.meta.copyright !== 'undefined' ? routeInfo.meta.copyright : settingsStore.showCopyright
}) })
watch(() => store.state.settings.sidebarCollapse, val => { watch(() => settingsStore.sidebarCollapse, val => {
if (store.state.settings.mode === 'mobile') { if (settingsStore.mode === 'mobile') {
if (!val) { if (!val) {
document.querySelector('body').classList.add('hidden') document.querySelector('body').classList.add('hidden')
} else { } else {
@ -59,8 +65,8 @@ watch(() => store.state.settings.sidebarCollapse, val => {
}) })
watch(() => routeInfo.path, () => { watch(() => routeInfo.path, () => {
if (store.state.settings.mode === 'mobile') { if (settingsStore.mode === 'mobile') {
store.commit('settings/updateThemeSetting', { settingsStore.updateThemeSetting({
sidebarCollapse: true sidebarCollapse: true
}) })
} }
@ -68,7 +74,7 @@ watch(() => routeInfo.path, () => {
onMounted(() => { onMounted(() => {
proxy.$hotkeys('f5', e => { proxy.$hotkeys('f5', e => {
if (store.state.settings.enablePageReload) { if (settingsStore.enablePageReload) {
e.preventDefault() e.preventDefault()
reload() reload()
} }
@ -83,12 +89,12 @@ function reload() {
provide('switchMenu', switchMenu) provide('switchMenu', switchMenu)
function switchMenu(index) { function switchMenu(index) {
store.commit('menu/switchHeaderActived', index) menuStore.switchHeaderActived(index)
if (store.state.settings.switchSidebarAndPageJump) { if (settingsStore.switchSidebarAndPageJump) {
if (isExternalLink(store.getters['menu/sidebarRoutesFirstDeepestPath'])) { if (isExternalLink(menuStore.sidebarRoutesFirstDeepestPath)) {
window.open(store.getters['menu/sidebarRoutesFirstDeepestPath'], '_blank') window.open(menuStore.sidebarRoutesFirstDeepestPath, '_blank')
} else { } else {
router.push(store.getters['menu/sidebarRoutesFirstDeepestPath']) router.push(menuStore.sidebarRoutesFirstDeepestPath)
} }
} }
} }

View File

@ -2,8 +2,9 @@ import { createApp } from 'vue'
import App from './App.vue' import App from './App.vue'
const app = createApp(App) const app = createApp(App)
import store from './store' import { piniaStore } from './store'
app.use(store) import { useSettingsOutsideStore } from './store/modules/settings'
app.use(piniaStore)
import router from './router' import router from './router'
app.use(router) app.use(router)
@ -18,7 +19,7 @@ for (var key in ElementIcons) {
import zhCn from 'element-plus/es/locale/lang/zh-cn' import zhCn from 'element-plus/es/locale/lang/zh-cn'
app.use(ElementPlus, { app.use(ElementPlus, {
locale: zhCn, locale: zhCn,
size: store.state.settings.elementSize size: useSettingsOutsideStore().elementSize
}) })
import globalProperties from '@/util/global.properties' import globalProperties from '@/util/global.properties'

View File

@ -1,7 +1,10 @@
import { createRouter, createWebHashHistory } from 'vue-router' import { createRouter, createWebHashHistory } from 'vue-router'
import store from '@/store'
import NProgress from 'nprogress' import NProgress from 'nprogress'
import 'nprogress/nprogress.css' // progress bar style import 'nprogress/nprogress.css' // progress bar style
import { useSettingsOutsideStore } from '@/store/modules/settings'
import { useKeepAliveOutsideStore } from '@/store/modules/keepAlive'
import { useUserOutsideStore } from '@/store/modules/user'
import { useMenuOutsideStore } from '@/store/modules/menu'
// 固定路由 // 固定路由
const constantRoutes = [ const constantRoutes = [
@ -23,7 +26,10 @@ const constantRoutes = [
name: 'dashboard', name: 'dashboard',
component: () => import('@/views/index.vue'), component: () => import('@/views/index.vue'),
meta: { meta: {
title: () => store.state.settings.dashboardTitle title: () => {
const settingsOutsideStore = useSettingsOutsideStore()
return settingsOutsideStore.dashboardTitle
}
} }
}, },
{ {
@ -66,7 +72,7 @@ import ComponentExtendExample from './modules/component.extend.example'
import PermissionExample from './modules/permission.example' import PermissionExample from './modules/permission.example'
import MockExample from './modules/mock.example' import MockExample from './modules/mock.example'
import ExternalLinkExample from './modules/external.link.example' import ExternalLinkExample from './modules/external.link.example'
import VideosExample from './modules/videos.example' // import VideosExample from './modules/videos.example'
import EcologyExample from './modules/ecology.example' import EcologyExample from './modules/ecology.example'
import CooperationExample from './modules/cooperation.example' import CooperationExample from './modules/cooperation.example'
@ -88,15 +94,15 @@ const asyncRoutes = [
ExternalLinkExample ExternalLinkExample
] ]
}, },
{ // {
meta: { // meta: {
title: '教程', // title: '教程',
icon: 'sidebar-videos' // icon: 'sidebar-videos'
}, // },
children: [ // children: [
...VideosExample // ...VideosExample
] // ]
}, // },
{ {
meta: { meta: {
title: '生态', title: '生态',
@ -131,13 +137,16 @@ const router = createRouter({
}) })
router.beforeEach(async(to, from, next) => { router.beforeEach(async(to, from, next) => {
store.state.settings.enableProgress && NProgress.start() const settingsOutsideStore = useSettingsOutsideStore()
const userOutsideStore = useUserOutsideStore()
const menuOutsideStore = useMenuOutsideStore()
settingsOutsideStore.enableProgress && NProgress.start()
// 是否已登录 // 是否已登录
if (store.getters['user/isLogin']) { if (userOutsideStore.isLogin) {
// 是否已根据权限动态生成并挂载路由 // 是否已根据权限动态生成并挂载路由
if (store.state.menu.isGenerate) { if (menuOutsideStore.isGenerate) {
// 导航栏如果不是 single 模式,则需要根据 path 定位主导航的选中状态 // 导航栏如果不是 single 模式,则需要根据 path 定位主导航的选中状态
store.state.settings.menuMode !== 'single' && store.commit('menu/setHeaderActived', to.path) settingsOutsideStore.menuMode !== 'single' && menuOutsideStore.setHeaderActived(to.path)
if (to.name) { if (to.name) {
if (to.matched.length !== 0) { if (to.matched.length !== 0) {
// 如果已登录状态下,进入登录页会强制跳转到控制台页面 // 如果已登录状态下,进入登录页会强制跳转到控制台页面
@ -146,11 +155,11 @@ router.beforeEach(async(to, from, next) => {
name: 'dashboard', name: 'dashboard',
replace: true replace: true
}) })
} else if (!store.state.settings.enableDashboard && to.name == 'dashboard') { } else if (!settingsOutsideStore.enableDashboard && to.name == 'dashboard') {
// 如果未开启控制台页面,则默认进入侧边栏导航第一个模块 // 如果未开启控制台页面,则默认进入侧边栏导航第一个模块
if (store.getters['menu/sidebarRoutes'].length > 0) { if (menuOutsideStore.sidebarRoutes.length > 0) {
next({ next({
path: store.getters['menu/sidebarRoutesFirstDeepestPath'], path: menuOutsideStore.sidebarRoutesFirstDeepestPath,
replace: true replace: true
}) })
} else { } else {
@ -170,10 +179,10 @@ router.beforeEach(async(to, from, next) => {
} }
} else { } else {
let accessRoutes = [] let accessRoutes = []
if (!store.state.settings.enableBackendReturnRoute) { if (!settingsOutsideStore.enableBackendReturnRoute) {
accessRoutes = await store.dispatch('menu/generateRoutesAtFront', asyncRoutes) accessRoutes = await menuOutsideStore.generateRoutesAtFront(asyncRoutes)
} else { } else {
accessRoutes = await store.dispatch('menu/generateRoutesAtBack') accessRoutes = await menuOutsideStore.generateRoutesAtBack()
} }
accessRoutes.push(lastRoute) accessRoutes.push(lastRoute)
let removeRoutes = [] let removeRoutes = []
@ -183,7 +192,7 @@ router.beforeEach(async(to, from, next) => {
} }
}) })
// 记录的 accessRoutes 路由数据,在登出时会使用到,不使用 router.removeRoute 是考虑配置的路由可能不一定有设置 name ,则通过调用 router.addRoute() 返回的回调进行删除 // 记录的 accessRoutes 路由数据,在登出时会使用到,不使用 router.removeRoute 是考虑配置的路由可能不一定有设置 name ,则通过调用 router.addRoute() 返回的回调进行删除
store.commit('menu/setCurrentRemoveRoutes', removeRoutes) menuOutsideStore.setCurrentRemoveRoutes(removeRoutes)
next({ ...to, replace: true }) next({ ...to, replace: true })
} }
} else { } else {
@ -201,14 +210,16 @@ router.beforeEach(async(to, from, next) => {
}) })
router.afterEach((to, from) => { router.afterEach((to, from) => {
store.state.settings.enableProgress && NProgress.done() const settingsOutsideStore = useSettingsOutsideStore()
const keepAliveOutsideStore = useKeepAliveOutsideStore()
settingsOutsideStore.enableProgress && NProgress.done()
// 设置页面 title // 设置页面 title
to.meta.title && store.commit('settings/setTitle', typeof to.meta.title === 'function' ? to.meta.title() : to.meta.title) to.meta.title && settingsOutsideStore.setTitle(typeof to.meta.title === 'function' ? to.meta.title() : to.meta.title)
// 判断当前页面是否开启缓存,如果开启,则将当前页面的 name 信息存入 keep-alive 全局状态 // 判断当前页面是否开启缓存,如果开启,则将当前页面的 name 信息存入 keep-alive 全局状态
if (to.meta.cache) { if (to.meta.cache) {
let componentName = to.matched[to.matched.length - 1].components.default.name let componentName = to.matched[to.matched.length - 1].components.default.name
if (componentName) { if (componentName) {
store.commit('keepAlive/add', componentName) keepAliveOutsideStore.add(componentName)
} else { } else {
console.warn('该页面组件未设置组件名,会导致缓存失效,请检查') console.warn('该页面组件未设置组件名,会导致缓存失效,请检查')
} }
@ -220,18 +231,18 @@ router.afterEach((to, from) => {
switch (typeof from.meta.cache) { switch (typeof from.meta.cache) {
case 'string': case 'string':
if (from.meta.cache != to.name) { if (from.meta.cache != to.name) {
store.commit('keepAlive/remove', componentName) keepAliveOutsideStore.remove(componentName)
} }
break break
case 'object': case 'object':
if (!from.meta.cache.includes(to.name)) { if (!from.meta.cache.includes(to.name)) {
store.commit('keepAlive/remove', componentName) keepAliveOutsideStore.remove(componentName)
} }
break break
} }
// 如果进入的是 reload 页面,则也将离开页面的缓存清空 // 如果进入的是 reload 页面,则也将离开页面的缓存清空
if (to.name == 'reload') { if (to.name == 'reload') {
store.commit('keepAlive/remove', componentName) keepAliveOutsideStore.remove(componentName)
} }
} }
}) })

View File

@ -1,13 +1,2 @@
import { createStore, createLogger } from 'vuex' import { createPinia } from 'pinia'
export const piniaStore = createPinia()
const modules = {}
const modulesContext = import.meta.globEager('./modules/*.js')
for (const path in modulesContext) {
modules[path.slice(10, -3)] = modulesContext[path].default
}
export default createStore({
modules: modules,
strict: !import.meta.env.PROD,
plugins: !import.meta.env.PROD ? [createLogger()] : []
})

View File

@ -1,41 +1,41 @@
const state = () => ({ import { defineStore } from 'pinia'
list: [] import { piniaStore } from '@/store'
})
const getters = {} export const useKeepAliveStore = defineStore(
// 唯一ID
const actions = {} 'keepAlive',
{
const mutations = { state: () => ({
add(state, name) { list: []
if (typeof name === 'string') { }),
!state.list.includes(name) && state.list.push(name) actions: {
} else { add(name) {
name.map(v => { if (typeof name === 'string') {
v && !state.list.includes(v) && state.list.push(v) !this.list.includes(name) && this.list.push(name)
}) } else {
name.map(v => {
v && !this.list.includes(v) && this.list.push(v)
})
}
},
remove(name) {
if (typeof name === 'string') {
this.list = this.list.filter(v => {
return v !== name
})
} else {
this.list = this.list.filter(v => {
return !name.includes(v)
})
}
},
clean() {
this.list = []
}
} }
},
remove(state, name) {
if (typeof name === 'string') {
state.list = state.list.filter(v => {
return v != name
})
} else {
state.list = state.list.filter(v => {
return !name.includes(v)
})
}
},
clean(state) {
state.list = []
} }
} )
export default { export function useKeepAliveOutsideStore() {
namespaced: true, return useKeepAliveStore(piniaStore)
state,
actions,
getters,
mutations
} }

View File

@ -1,7 +1,12 @@
import { defineStore } from 'pinia'
import { piniaStore } from '@/store'
import path from 'path-browserify' import path from 'path-browserify'
import { deepClone, resolveRoutePath } from '@/util' import { deepClone, resolveRoutePath } from '@/util'
import api from '@/api' import api from '@/api'
import { useSettingsStore } from './settings'
import { useUserStore } from './user'
function hasPermission(permissions, route) { function hasPermission(permissions, route) {
let isAuth = false let isAuth = false
if (route.meta && route.meta.auth) { if (route.meta && route.meta.auth) {
@ -137,160 +142,170 @@ function getDeepestPath(routes, rootPath = '') {
return retnPath return retnPath
} }
const state = () => ({ export const useMenuStore = defineStore(
isGenerate: false, // 唯一ID
routes: [], 'menu',
defaultOpenedPaths: [], {
headerActived: 0, state: () => ({
currentRemoveRoutes: [] isGenerate: false,
}) routes: [],
defaultOpenedPaths: [],
const getters = { headerActived: 0,
// 由于 getter 的结果不会被缓存,导致导航栏切换时有明显的延迟,该问题会在 Vue 3.2 版本中修复,详看 https://github.com/vuejs/vuex/pull/1883 currentRemoveRoutes: []
routes: (state, getters, rootState) => { }),
let routes getters: {
if (rootState.settings.menuMode === 'single') { transformRoutes: state => {
routes = [{ children: [] }] let routes
state.routes.map(item => { const settingsStore = useSettingsStore()
routes[0].children.push(...item.children) if (settingsStore.menuMode === 'single') {
}) routes = [{ children: [] }]
} else { state.routes.map(item => {
routes = state.routes routes[0].children.push(...item.children)
} })
return routes
},
sidebarRoutes: (state, getters) => {
return getters.routes.length > 0 ? getters.routes[state.headerActived].children : []
},
sidebarRoutesFirstDeepestPath: (state, getters) => {
return getters.routes.length > 0 ? getDeepestPath(getters.sidebarRoutes[0]) : '/'
}
}
const actions = {
// 根据权限动态生成路由(前端生成)
generateRoutesAtFront({ rootState, dispatch, commit }, asyncRoutes) {
// eslint-disable-next-line no-async-promise-executor
return new Promise(async resolve => {
let accessedRoutes
// 如果权限功能开启,则需要对路由数据进行筛选过滤
if (rootState.settings.enablePermission) {
const permissions = await dispatch('user/getPermissions', null, { root: true })
accessedRoutes = filterAsyncRoutes(asyncRoutes, permissions)
} else {
accessedRoutes = deepClone(asyncRoutes)
}
commit('setRoutes', accessedRoutes)
let routes = []
accessedRoutes.map(item => {
routes.push(...item.children)
})
// 将三级及以上路由数据拍平成二级
routes.map(item => {
if (item.children) {
item.children = flatAsyncRoutes(item.children, [{
path: item.path,
title: item.meta.title
}], item.path)
}
})
commit('setDefaultOpenedPaths', routes)
resolve(routes)
})
},
// 生成路由(后端获取)
generateRoutesAtBack({ rootState, dispatch, commit }) {
return new Promise(resolve => {
api.get('route/list', {
baseURL: '/mock/'
}).then(async res => {
let asyncRoutes = formatBackRoutes(res.data)
let accessedRoutes
// 如果权限功能开启,则需要对路由数据进行筛选过滤
if (rootState.settings.enablePermission) {
const permissions = await dispatch('user/getPermissions', null, { root: true })
accessedRoutes = filterAsyncRoutes(asyncRoutes, permissions)
} else { } else {
accessedRoutes = deepClone(asyncRoutes) routes = state.routes
} }
commit('setRoutes', accessedRoutes) return routes
let routes = [] },
accessedRoutes.map(item => { sidebarRoutes() {
routes.push(...item.children) return this.transformRoutes.length > 0 ? this.transformRoutes[this.headerActived].children : []
},
sidebarRoutesFirstDeepestPath() {
return this.transformRoutes.length > 0 ? getDeepestPath(this.sidebarRoutes[0]) : '/'
}
},
actions: {
// 根据权限动态生成路由(前端生成)
generateRoutesAtFront(asyncRoutes) {
// eslint-disable-next-line no-async-promise-executor
return new Promise(async resolve => {
const settingsStore = useSettingsStore()
const userStore = useUserStore()
let accessedRoutes
// 如果权限功能开启,则需要对路由数据进行筛选过滤
if (settingsStore.enablePermission) {
const permissions = await userStore.getPermissions()
accessedRoutes = filterAsyncRoutes(asyncRoutes, permissions)
} else {
accessedRoutes = deepClone(asyncRoutes)
}
// 设置 routes 数据
this.isGenerate = true
let newRoutes = deepClone(accessedRoutes)
this.routes = newRoutes.filter(item => {
return item.children.length != 0
})
// 将三级及以上路由数据拍平成二级
let routes = []
accessedRoutes.map(item => {
routes.push(...item.children)
})
routes.map(item => {
if (item.children) {
item.children = flatAsyncRoutes(item.children, [{
path: item.path,
title: item.meta.title
}], item.path)
}
})
// 设置 defaultOpenedPaths 数据
let defaultOpenedPaths = []
routes.map(item => {
item.meta.defaultOpened && defaultOpenedPaths.push(item.path)
item.children && item.children.map(child => {
child.meta.defaultOpened && defaultOpenedPaths.push(path.resolve(item.path, child.path))
})
})
this.defaultOpenedPaths = defaultOpenedPaths
resolve(routes)
}) })
// 将三级及以上路由数据拍平成二级 },
routes.map(item => { // 生成路由(后端获取)
if (item.children) { generateRoutesAtBack() {
item.children = flatAsyncRoutes(item.children, [{ return new Promise(resolve => {
path: item.path, api.get('route/list', {
title: item.meta.title baseURL: '/mock/'
}], item.path) }).then(async res => {
const settingsStore = useSettingsStore()
const userStore = useUserStore()
let asyncRoutes = formatBackRoutes(res.data)
let accessedRoutes
// 如果权限功能开启,则需要对路由数据进行筛选过滤
if (settingsStore.enablePermission) {
const permissions = await userStore.getPermissions()
accessedRoutes = filterAsyncRoutes(asyncRoutes, permissions)
} else {
accessedRoutes = deepClone(asyncRoutes)
}
// 设置 routes 数据
this.isGenerate = true
let newRoutes = deepClone(accessedRoutes)
this.routes = newRoutes.filter(item => {
return item.children.length != 0
})
// 将三级及以上路由数据拍平成二级
let routes = []
accessedRoutes.map(item => {
routes.push(...item.children)
})
routes.map(item => {
if (item.children) {
item.children = flatAsyncRoutes(item.children, [{
path: item.path,
title: item.meta.title
}], item.path)
}
})
// 设置 defaultOpenedPaths 数据
let defaultOpenedPaths = []
routes.map(item => {
item.meta.defaultOpened && defaultOpenedPaths.push(item.path)
item.children && item.children.map(child => {
child.meta.defaultOpened && defaultOpenedPaths.push(path.resolve(item.path, child.path))
})
})
this.defaultOpenedPaths = defaultOpenedPaths
resolve(routes)
})
})
},
invalidRoutes() {
this.isGenerate = false
this.routes = []
this.defaultOpenedPaths = []
this.headerActived = 0
},
// 根据路由判断属于哪个头部导航
setHeaderActived(path) {
this.routes.map((item, index) => {
if (
item.children.some(r => {
return path.indexOf(r.path + '/') === 0 || path == r.path
})
) {
this.headerActived = index
} }
}) })
commit('setDefaultOpenedPaths', routes) },
resolve(routes) // 切换头部导航
}) switchHeaderActived(index) {
}) this.headerActived = index
} },
} // 记录 accessRoutes 路由,用于登出时删除路由
setCurrentRemoveRoutes(routes) {
const mutations = { this.currentRemoveRoutes = routes
invalidRoutes(state) { },
state.isGenerate = false // 清空动态路由
state.routes = [] removeRoutes() {
state.defaultOpenedPaths = [] this.currentRemoveRoutes.forEach(removeRoute => {
state.headerActived = 0 removeRoute()
},
setRoutes(state, routes) {
state.isGenerate = true
let newRoutes = deepClone(routes)
state.routes = newRoutes.filter(item => {
return item.children.length != 0
})
},
setDefaultOpenedPaths(state, routes) {
let defaultOpenedPaths = []
routes.map(item => {
item.meta.defaultOpened && defaultOpenedPaths.push(item.path)
item.children && item.children.map(child => {
child.meta.defaultOpened && defaultOpenedPaths.push(path.resolve(item.path, child.path))
})
})
state.defaultOpenedPaths = defaultOpenedPaths
},
// 根据路由判断属于哪个头部导航
setHeaderActived(state, path) {
state.routes.map((item, index) => {
if (
item.children.some(r => {
return path.indexOf(r.path + '/') === 0 || path == r.path
}) })
) { this.currentRemoveRoutes = []
state.headerActived = index
} }
}) }
},
// 切换头部导航
switchHeaderActived(state, index) {
state.headerActived = index
},
// 记录 accessRoutes 路由,用于登出时删除路由
setCurrentRemoveRoutes(state, routes) {
state.currentRemoveRoutes = routes
},
// 清空动态路由
removeRoutes(state) {
state.currentRemoveRoutes.forEach(removeRoute => {
removeRoute()
})
state.currentRemoveRoutes = []
} }
} )
export default { export function useMenuOutsideStore() {
namespaced: true, return useMenuStore(piniaStore)
state,
actions,
getters,
mutations
} }

View File

@ -1,62 +1,58 @@
/** import { defineStore } from 'pinia'
* 存放全局公用状态 import { piniaStore } from '@/store'
*/
import settings from '@/settings' import settings from '@/settings'
const state = () => ({ export const useSettingsStore = defineStore(
...settings, // 唯一ID
// 侧边栏是否收起(用于记录 pc 模式下最后的状态) 'settings',
sidebarCollapseLastStatus: settings.sidebarCollapse, {
// 显示模式支持mobile、pc state: () => ({
mode: 'pc', ...settings,
// 页面标题 // 侧边栏是否收起(用于记录 pc 模式下最后的状态)
title: '' sidebarCollapseLastStatus: settings.sidebarCollapse,
}) // 显示模式支持mobile、pc
mode: 'pc',
const getters = {} // 页面标题
title: ''
const actions = {} }),
actions: {
const mutations = { // 设置访问模式
// 设置访问模式 setMode(width) {
setMode(state, width) { if (this.enableMobileAdaptation) {
if (state.enableMobileAdaptation) { // 先判断 UA 是否为移动端设备(手机&平板)
// 先判断 UA 是否为移动端设备(手机&平板) if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) { this.mode = 'mobile'
state.mode = 'mobile' } else {
} else { // 如果为桌面设备,再根据页面宽度判断是否需要切换为移动端展示
// 如果为桌面设备,再根据页面宽度判断是否需要切换为移动端展示 if (width < 992) {
if (width < 992) { this.mode = 'mobile'
state.mode = 'mobile' } else {
this.mode = 'pc'
}
}
} else { } else {
state.mode = 'pc' this.mode = 'pc'
} }
},
// 设置网页标题
setTitle(title) {
this.title = title
},
// 切换侧边栏导航展开/收起
toggleSidebarCollapse() {
this.sidebarCollapse = !this.sidebarCollapse
if (this.mode == 'pc') {
this.sidebarCollapseLastStatus = !this.sidebarCollapseLastStatus
}
},
// 更新主题配置
updateThemeSetting(data) {
Object.assign(this, data)
} }
} else {
state.mode = 'pc'
} }
},
// 设置网页标题
setTitle(state, title) {
state.title = title
},
// 切换侧边栏导航展开/收起
toggleSidebarCollapse(state) {
state.sidebarCollapse = !state.sidebarCollapse
if (state.mode == 'pc') {
state.sidebarCollapseLastStatus = !state.sidebarCollapseLastStatus
}
},
// 更新主题配置
updateThemeSetting(state, data) {
Object.assign(state, data)
} }
} )
export default { export function useSettingsOutsideStore() {
namespaced: true, return useSettingsStore(piniaStore)
state,
actions,
getters,
mutations
} }

View File

@ -1,102 +1,95 @@
import { defineStore } from 'pinia'
import { piniaStore } from '@/store'
import api from '@/api' import api from '@/api'
const state = () => ({ import { useMenuStore } from './menu'
account: localStorage.account || '',
token: localStorage.token || '',
failure_time: localStorage.failure_time || '',
permissions: []
})
const getters = { export const useUserStore = defineStore(
isLogin: state => { // 唯一ID
let retn = false 'user',
if (state.token) { {
if (new Date().getTime() < state.failure_time * 1000) { state: () => ({
retn = true account: localStorage.account || '',
token: localStorage.token || '',
failure_time: localStorage.failure_time || '',
permissions: []
}),
getters: {
isLogin: state => {
let retn = false
if (state.token) {
if (new Date().getTime() < state.failure_time * 1000) {
retn = true
}
}
return retn
}
},
actions: {
login(data) {
return new Promise((resolve, reject) => {
// 通过 mock 进行登录
api.post('member/login', data, {
baseURL: '/mock/'
}).then(res => {
localStorage.setItem('account', res.data.account)
localStorage.setItem('token', res.data.token)
localStorage.setItem('failure_time', res.data.failure_time)
this.account = res.data.account
this.token = res.data.token
this.failure_time = res.data.failure_time
resolve()
}).catch(error => {
reject(error)
})
})
},
logout() {
return new Promise(resolve => {
const menuStore = useMenuStore()
localStorage.removeItem('account')
localStorage.removeItem('token')
localStorage.removeItem('failure_time')
this.account = ''
this.token = ''
this.failure_time = ''
menuStore.invalidRoutes()
menuStore.removeRoutes()
resolve()
})
},
// 获取我的权限
getPermissions() {
return new Promise(resolve => {
// 通过 mock 获取权限
api.get('member/permission', {
baseURL: '/mock/',
params: {
account: this.account
}
}).then(res => {
this.permissions = res.data.permissions
resolve(res.data.permissions)
})
})
},
editPassword(data) {
return new Promise(resolve => {
api.post('member/edit/password', {
account: this.account,
password: data.password,
newpassword: data.newpassword
}, {
baseURL: '/mock/'
}).then(() => {
resolve()
})
})
} }
} }
return retn
} }
} )
const actions = { export function useUserOutsideStore() {
login({ commit }, data) { return useUserStore(piniaStore)
return new Promise((resolve, reject) => {
// 通过 mock 进行登录
api.post('member/login', data, {
baseURL: '/mock/'
}).then(res => {
commit('setUserData', res.data)
resolve()
}).catch(error => {
reject(error)
})
})
},
logout({ commit }) {
return new Promise(resolve => {
commit('removeUserData')
commit('menu/invalidRoutes', null, { root: true })
commit('menu/removeRoutes', null, { root: true })
resolve()
})
},
// 获取我的权限
getPermissions({ state, commit }) {
return new Promise(resolve => {
// 通过 mock 获取权限
api.get('member/permission', {
baseURL: '/mock/',
params: {
account: state.account
}
}).then(res => {
commit('setPermissions', res.data.permissions)
resolve(res.data.permissions)
})
})
},
editPassword({ state }, data) {
return new Promise(resolve => {
api.post('member/edit/password', {
account: state.account,
password: data.password,
newpassword: data.newpassword
}, {
baseURL: '/mock/'
}).then(() => {
resolve()
})
})
}
}
const mutations = {
setUserData(state, data) {
localStorage.setItem('account', data.account)
localStorage.setItem('token', data.token)
localStorage.setItem('failure_time', data.failure_time)
state.account = data.account
state.token = data.token
state.failure_time = data.failure_time
},
removeUserData(state) {
localStorage.removeItem('account')
localStorage.removeItem('token')
localStorage.removeItem('failure_time')
state.account = ''
state.token = ''
state.failure_time = ''
},
setPermissions(state, permissions) {
state.permissions = permissions
}
}
export default {
namespaced: true,
state,
actions,
getters,
mutations
} }

View File

@ -1,5 +1,6 @@
import path from 'path-browserify' import path from 'path-browserify'
import store from '@/store' import { useSettingsOutsideStore } from '@/store/modules/settings'
import { useUserOutsideStore } from '@/store/modules/user'
export function deepClone(target) { export function deepClone(target) {
// 定义一个变量 // 定义一个变量
@ -35,8 +36,10 @@ export function deepClone(target) {
} }
function hasPermission(permission) { function hasPermission(permission) {
if (store.state.settings.enablePermission) { const settingsOutsideStore = useSettingsOutsideStore()
return store.state.user.permissions.some(v => { const userOutsideStore = useUserOutsideStore()
if (settingsOutsideStore.enablePermission) {
return userOutsideStore.permissions.some(v => {
return v === permission return v === permission
}) })
} else { } else {

View File

@ -4,37 +4,36 @@
<div class="content"> <div class="content">
<h1>404</h1> <h1>404</h1>
<div class="desc">抱歉你访问的页面不存在</div> <div class="desc">抱歉你访问的页面不存在</div>
<el-button type="primary" @click="goBack">{{ countdown }}秒后返回首页</el-button> <el-button type="primary" @click="goBack">{{ data.countdown }} 秒后返回首页</el-button>
</div> </div>
</div> </div>
</template> </template>
<script> <script setup>
export default { import { onBeforeRouteLeave } from 'vue-router'
beforeRouteLeave(to, from, next) { const router = useRouter()
clearInterval(this.inter)
next() const data = ref({
}, inter: null,
data() { countdown: 5
return { })
inter: null,
countdown: 5 onBeforeRouteLeave(() => {
clearInterval(data.value.inter)
})
onMounted(() => {
data.value.inter = setInterval(() => {
data.value.countdown--
if (data.value.countdown == 0) {
clearInterval(data.value.inter)
goBack()
} }
}, }, 1000)
mounted() { })
this.inter = setInterval(() => {
this.countdown-- function goBack() {
if (this.countdown == 0) { router.push('/')
clearInterval(this.inter)
this.goBack()
}
}, 1000)
},
methods: {
goBack() {
this.$router.push('/')
}
}
} }
</script> </script>

View File

@ -3,7 +3,7 @@
<page-main> <page-main>
<div>层级1</div> <div>层级1</div>
<router-view v-slot="{ Component }"> <router-view v-slot="{ Component }">
<keep-alive :include="$store.state.keepAlive.list"> <keep-alive :include="keepAliveStore.list">
<component :is="Component" /> <component :is="Component" />
</keep-alive> </keep-alive>
</router-view> </router-view>
@ -11,8 +11,7 @@
</div> </div>
</template> </template>
<script> <script setup name="TabExampleNested1">
export default { import { useKeepAliveStore } from '@/store/modules/keepAlive'
name: 'TabExampleNested1' const keepAliveStore = useKeepAliveStore()
}
</script> </script>

View File

@ -3,7 +3,7 @@
<page-main> <page-main>
<div>层级1-1</div> <div>层级1-1</div>
<router-view v-slot="{ Component }"> <router-view v-slot="{ Component }">
<keep-alive :include="$store.state.keepAlive.list"> <keep-alive :include="keepAliveStore.list">
<component :is="Component" /> <component :is="Component" />
</keep-alive> </keep-alive>
</router-view> </router-view>
@ -11,8 +11,7 @@
</div> </div>
</template> </template>
<script> <script setup name="TabExampleNested2">
export default { import { useKeepAliveStore } from '@/store/modules/keepAlive'
name: 'TabExampleNested2' const keepAliveStore = useKeepAliveStore()
}
</script> </script>

View File

@ -17,12 +17,15 @@
</template> </template>
<script> <script>
import { useKeepAliveStore } from '@/store/modules/keepAlive'
const keepAliveStore = useKeepAliveStore()
export default { export default {
name: 'KeepAliveExamplePage', name: 'KeepAliveExamplePage',
beforeRouteEnter(to, from, next) { beforeRouteEnter(to, from, next) {
// name keep-alive // name keep-alive
next(vm => { next(vm => {
vm.$store.commit('keepAlive/add', vm.$options.name) keepAliveStore.add(vm.$options.name)
}) })
}, },
beforeRouteLeave(to, from, next) { beforeRouteLeave(to, from, next) {
@ -31,10 +34,10 @@ export default {
// name keepAliveExampleDetail keepAliveExampleNestedDetail name keep-alive // name keepAliveExampleDetail keepAliveExampleNestedDetail name keep-alive
if (!['keepAliveExampleDetail', 'keepAliveExampleNestedDetail'].includes(to.name)) { if (!['keepAliveExampleDetail', 'keepAliveExampleNestedDetail'].includes(to.name)) {
// name name // name name
this.$store.commit('keepAlive/remove', 'KeepAliveExamplePage') keepAliveStore.remove('KeepAliveExamplePage')
} }
} else { } else {
this.$store.commit('keepAlive/remove', 'KeepAliveExamplePage') keepAliveStore.remove('KeepAliveExamplePage')
} }
next() next()
}, },

View File

@ -80,15 +80,19 @@
</el-row> </el-row>
</el-form> </el-form>
</div> </div>
<Copyright v-if="$store.state.settings.showCopyright" /> <Copyright v-if="settingsStore.showCopyright" />
</div> </div>
</template> </template>
<script setup name="Login"> <script setup name="Login">
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
const store = useStore()
const route = useRoute(), router = useRouter() const route = useRoute(), router = useRouter()
import { useSettingsStore } from '@/store/modules/settings'
const settingsStore = useSettingsStore()
import { useUserStore } from '@/store/modules/user'
const userStore = useUserStore()
const title = import.meta.env.VITE_APP_TITLE const title = import.meta.env.VITE_APP_TITLE
// login reset // login reset
@ -146,7 +150,7 @@ function handleLogin() {
proxy.$refs.loginFormRef.validate(valid => { proxy.$refs.loginFormRef.validate(valid => {
if (valid) { if (valid) {
loading.value = true loading.value = true
store.dispatch('user/login', loginForm.value).then(() => { userStore.login(loginForm.value).then(() => {
loading.value = false loading.value = false
if (loginForm.value.remember) { if (loginForm.value.remember) {
localStorage.setItem('login_account', loginForm.value.account) localStorage.setItem('login_account', loginForm.value.account)

View File

@ -9,21 +9,20 @@
</div> </div>
</template> </template>
<script> <script setup>
export default { import { useUserStore } from '@/store/modules/user'
methods: { const userStore = useUserStore()
getPermission() {
this.$store.dispatch('user/getPermissions').then(res => { function getPermission() {
this.$notify({ userStore.getPermissions().then(res => {
title: '当前用户权限', this.$notify({
dangerouslyUseHTMLString: true, title: '当前用户权限',
message: res.map(p => `<p>${p}</p>`).join('') dangerouslyUseHTMLString: true,
}) message: res.map(p => `<p>${p}</p>`).join('')
}) })
}, })
open(url) { }
window.open(url, 'top') function open(url) {
} window.open(url, 'top')
}
} }
</script> </script>

View File

@ -2,15 +2,15 @@
<div> <div>
<page-header title="权限验证" /> <page-header title="权限验证" />
<page-main> <page-main>
<div v-if="!$store.state.settings.enablePermission">请到 seeting.js 里打开权限功能再进入该页面查看演示</div> <div v-if="!settingsStore.enablePermission">请到 seeting.js 里打开权限功能再进入该页面查看演示</div>
<div v-else> <div v-else>
<h3>切换帐号</h3> <h3>切换帐号</h3>
<el-radio-group v-model="account" @change="accountChange"> <el-radio-group v-model="userStore.account" @change="accountChange">
<el-radio-button label="admin" /> <el-radio-button label="admin" />
<el-radio-button label="test" /> <el-radio-button label="test" />
</el-radio-group> </el-radio-group>
<h3>帐号权限</h3> <h3>帐号权限</h3>
<div>{{ $store.state.user.permissions }}</div> <div>{{ userStore.permissions }}</div>
<h3>鉴权组件请对照代码查看</h3> <h3>鉴权组件请对照代码查看</h3>
<div> <div>
<auth value="permission.browse" style="margin-bottom: 10px;"> <auth value="permission.browse" style="margin-bottom: 10px;">
@ -69,43 +69,41 @@
</div> </div>
</template> </template>
<script> <script setup>
export default { const { proxy } = getCurrentInstance()
data() {
return { import { useSettingsStore } from '@/store/modules/settings'
account: this.$store.state.user.account const settingsStore = useSettingsStore()
} import { useUserStore } from '@/store/modules/user'
}, const userStore = useUserStore()
methods: {
accountChange(val) { function accountChange(val) {
this.$loading({ proxy.$loading({
lock: true, lock: true,
text: '帐号切换中', text: '帐号切换中',
background: 'rgba(0, 0, 0, 0.7)' background: 'rgba(0, 0, 0, 0.7)'
}) })
this.$store.dispatch('user/login', { userStore.login({
account: val, account: val,
password: '' password: ''
}).then(() => { }).then(() => {
setTimeout(() => { setTimeout(() => {
location.reload() location.reload()
}, 1000) }, 1000)
}) })
}, }
permissionCheck(permissions) { function permissionCheck(permissions) {
if (this.$auth(permissions)) { if (proxy.$auth(permissions)) {
this.$message.success('校验通过') proxy.$message.success('校验通过')
} else { } else {
this.$message.error('校验不通过') proxy.$message.error('校验不通过')
} }
}, }
permissionCheck2(permissions) { function permissionCheck2(permissions) {
if (this.$authAll(permissions)) { if (proxy.$authAll(permissions)) {
this.$message.success('校验通过') proxy.$message.success('校验通过')
} else { } else {
this.$message.error('校验不通过') proxy.$message.error('校验不通过')
}
}
} }
} }
</script> </script>

View File

@ -25,10 +25,12 @@
</template> </template>
<script setup name="PersonalEditPassword"> <script setup name="PersonalEditPassword">
const store = useStore()
const route = useRoute(), router = useRouter() const route = useRoute(), router = useRouter()
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
import { useUserStore } from '@/store/modules/user'
const userStore = useUserStore()
const validatePassword = (rule, value, callback) => { const validatePassword = (rule, value, callback) => {
if (value !== form.value.newpassword) { if (value !== form.value.newpassword) {
callback(new Error('请确认新密码')) callback(new Error('请确认新密码'))
@ -60,12 +62,12 @@ const rules = ref({
function onSubmit() { function onSubmit() {
proxy.$refs['formRef'].validate(valid => { proxy.$refs['formRef'].validate(valid => {
if (valid) { if (valid) {
store.dispatch('user/editPassword', form.value).then(() => { userStore.editPassword(form.value).then(() => {
proxy.$message({ proxy.$message({
type: 'success', type: 'success',
message: '模拟修改成功,请重新登录' message: '模拟修改成功,请重新登录'
}) })
store.dispatch('user/logout').then(() => { userStore.logout().then(() => {
router.push({ router.push({
name: 'login', name: 'login',
query: { query: {

View File

@ -4,10 +4,7 @@ export default function createAutoImport() {
return autoImport({ return autoImport({
imports: [ imports: [
'vue', 'vue',
'vue-router', 'vue-router'
{
'vuex': ['useStore']
}
], ],
dts: false dts: false
}) })