mirror of
https://gitee.com/fantastic-admin/basic.git
synced 2024-12-04 21:18:16 +08:00
使用 pinia 替换 vuex
This commit is contained in:
parent
65eff15674
commit
5d7461c5cc
@ -46,8 +46,6 @@ module.exports = {
|
||||
withDefaults: 'readonly',
|
||||
// vue-router
|
||||
useRoute: 'readonly',
|
||||
useRouter: 'readonly',
|
||||
// vuex
|
||||
useStore: 'readonly'
|
||||
useRouter: 'readonly'
|
||||
}
|
||||
}
|
||||
|
@ -31,12 +31,12 @@
|
||||
"nprogress": "^0.2.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"path-to-regexp": "^6.2.0",
|
||||
"pinia": "^2.0.9",
|
||||
"qs": "^6.10.2",
|
||||
"screenfull": "^6.0.0",
|
||||
"tinymce": "^5.10.2",
|
||||
"vue": "^3.2.24",
|
||||
"vue-router": "^4.0.12",
|
||||
"vuex": "^4.0.2"
|
||||
"vue-router": "^4.0.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^1.10.2",
|
||||
|
@ -4,18 +4,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
{{#if isGlobal}}
|
||||
name: '{{ properCase name }}',
|
||||
{{/if}}
|
||||
props: {},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
mounted() {},
|
||||
methods: {}
|
||||
}
|
||||
<script setup{{#if isGlobal}} name="{{ properCase name }}"{{/if}}>
|
||||
// const { proxy } = getCurrentInstance()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -11,7 +11,6 @@
|
||||
// const { proxy } = getCurrentInstance()
|
||||
// const router = useRouter()
|
||||
// const route = useRoute()
|
||||
// const store = useStore()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -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 = {}
|
||||
|
||||
const mutations = {}
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state,
|
||||
actions,
|
||||
getters,
|
||||
mutations
|
||||
export function use{{ properCase name }}OutsideStore() {
|
||||
return use{{ properCase name }}Store(piniaStore)
|
||||
}
|
||||
|
@ -14,11 +14,11 @@ module.exports = {
|
||||
}
|
||||
}
|
||||
],
|
||||
actions: data => {
|
||||
actions: () => {
|
||||
const actions = [
|
||||
{
|
||||
type: 'add',
|
||||
path: `src/store/modules/${data.name}.js`,
|
||||
path: 'src/store/modules/{{camelCase name}}.js',
|
||||
templateFile: 'plop-templates/store/index.hbs'
|
||||
}
|
||||
]
|
||||
|
@ -22,6 +22,7 @@ specifiers:
|
||||
nprogress: ^0.2.0
|
||||
path-browserify: ^1.0.1
|
||||
path-to-regexp: ^6.2.0
|
||||
pinia: ^2.0.9
|
||||
plop: ^3.0.4
|
||||
postcss-html: ^1.3.0
|
||||
postcss-scss: ^4.0.2
|
||||
@ -51,7 +52,6 @@ specifiers:
|
||||
vue: ^3.2.24
|
||||
vue-eslint-parser: ^8.0.1
|
||||
vue-router: ^4.0.12
|
||||
vuex: ^4.0.2
|
||||
|
||||
dependencies:
|
||||
'@element-plus/icons-vue': 0.2.4_vue@3.2.24
|
||||
@ -68,12 +68,12 @@ dependencies:
|
||||
nprogress: 0.2.0
|
||||
path-browserify: 1.0.1
|
||||
path-to-regexp: 6.2.0
|
||||
pinia: 2.0.9_vue@3.2.24
|
||||
qs: 6.10.2
|
||||
screenfull: 6.0.0
|
||||
tinymce: 5.10.2
|
||||
vue: 3.2.24
|
||||
vue-router: 4.0.12_vue@3.2.24
|
||||
vuex: 4.0.2_vue@3.2.24
|
||||
|
||||
devDependencies:
|
||||
'@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'}
|
||||
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:
|
||||
resolution: {integrity: sha512-5eVsO9wfQ5erCMSRBjpqLkkI+LglJS7E0oLZJs2gsChpvOjH2Uwt3Hk1nVv0ywStnWg71Ykn3SyQwtnl7PknOQ==, tarball: '@vue/reactivity/download/@vue/reactivity-3.2.24.tgz'}
|
||||
dependencies:
|
||||
@ -3808,6 +3812,23 @@ packages:
|
||||
engines: {node: '>=8.6'}
|
||||
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:
|
||||
resolution: {integrity: sha1-ez5SLgjqh3vXC63xWb3D7Ffh6EE=, tarball: pixelsmith/download/pixelsmith-2.5.0.tgz}
|
||||
engines: {node: '>= 8.0.0'}
|
||||
@ -5477,15 +5498,6 @@ packages:
|
||||
'@vue/shared': 3.2.24
|
||||
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:
|
||||
resolution: {integrity: sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=, tarball: wcwidth/download/wcwidth-1.0.1.tgz}
|
||||
dependencies:
|
||||
|
35
src/App.vue
35
src/App.vue
@ -8,13 +8,14 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const store = useStore()
|
||||
import { useSettingsStore } from '@/store/modules/settings'
|
||||
const settingsStore = useSettingsStore()
|
||||
|
||||
// 侧边栏主导航当前实际宽度
|
||||
const mainSidebarActualWidth = computed(() => {
|
||||
let actualWidth = getComputedStyle(document.documentElement).getPropertyValue('--g-main-sidebar-width')
|
||||
actualWidth = parseInt(actualWidth)
|
||||
if (['head', 'single'].includes(store.state.settings.menuMode)) {
|
||||
if (['head', 'single'].includes(settingsStore.menuMode)) {
|
||||
actualWidth = 0
|
||||
}
|
||||
return `${actualWidth}px`
|
||||
@ -24,39 +25,39 @@ const mainSidebarActualWidth = computed(() => {
|
||||
const subSidebarActualWidth = computed(() => {
|
||||
let actualWidth = getComputedStyle(document.documentElement).getPropertyValue('--g-sub-sidebar-width')
|
||||
actualWidth = parseInt(actualWidth)
|
||||
if (store.state.settings.sidebarCollapse) {
|
||||
if (settingsStore.sidebarCollapse) {
|
||||
actualWidth = 64
|
||||
}
|
||||
return `${actualWidth}px`
|
||||
})
|
||||
|
||||
watch(() => store.state.settings.mode, () => {
|
||||
if (store.state.settings.mode === 'pc') {
|
||||
store.commit('settings/updateThemeSetting', {
|
||||
'sidebarCollapse': store.state.settings.sidebarCollapseLastStatus
|
||||
watch(() => settingsStore.mode, () => {
|
||||
if (settingsStore.mode === 'pc') {
|
||||
settingsStore.updateThemeSetting({
|
||||
'sidebarCollapse': settingsStore.sidebarCollapseLastStatus
|
||||
})
|
||||
} else if (store.state.settings.mode === 'mobile') {
|
||||
store.commit('settings/updateThemeSetting', {
|
||||
} else if (settingsStore.mode === 'mobile') {
|
||||
settingsStore.updateThemeSetting({
|
||||
'sidebarCollapse': true
|
||||
})
|
||||
}
|
||||
document.body.setAttribute('data-mode', store.state.settings.mode)
|
||||
document.body.setAttribute('data-mode', settingsStore.mode)
|
||||
}, {
|
||||
immediate: true
|
||||
})
|
||||
|
||||
watch(() => store.state.settings.menuMode, () => {
|
||||
document.body.setAttribute('data-menu-mode', store.state.settings.menuMode)
|
||||
watch(() => settingsStore.menuMode, () => {
|
||||
document.body.setAttribute('data-menu-mode', settingsStore.menuMode)
|
||||
}, {
|
||||
immediate: true
|
||||
})
|
||||
|
||||
watch([
|
||||
() => store.state.settings.enableDynamicTitle,
|
||||
() => store.state.settings.title
|
||||
() => settingsStore.enableDynamicTitle,
|
||||
() => settingsStore.title
|
||||
], () => {
|
||||
if (store.state.settings.enableDynamicTitle && store.state.settings.title) {
|
||||
let title = store.state.settings.title
|
||||
if (settingsStore.enableDynamicTitle && settingsStore.title) {
|
||||
let title = settingsStore.title
|
||||
document.title = `${title} - ${import.meta.env.VITE_APP_TITLE}`
|
||||
} else {
|
||||
document.title = import.meta.env.VITE_APP_TITLE
|
||||
@ -67,7 +68,7 @@ watch([
|
||||
|
||||
onMounted(() => {
|
||||
window.onresize = () => {
|
||||
store.commit('settings/setMode', document.documentElement.clientWidth)
|
||||
settingsStore.setMode(document.documentElement.clientWidth)
|
||||
}
|
||||
window.onresize()
|
||||
})
|
||||
|
@ -1,11 +1,12 @@
|
||||
import axios from 'axios'
|
||||
// import qs from 'qs'
|
||||
import router from '@/router/index'
|
||||
import store from '@/store/index'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useUserOutsideStore } from '@/store/modules/user'
|
||||
|
||||
const toLogin = () => {
|
||||
store.dispatch('user/logout').then(() => {
|
||||
const userOutsideStore = useUserOutsideStore()
|
||||
userOutsideStore.logout().then(() => {
|
||||
router.push({
|
||||
name: 'login',
|
||||
query: {
|
||||
@ -23,12 +24,13 @@ const api = axios.create({
|
||||
|
||||
api.interceptors.request.use(
|
||||
request => {
|
||||
const userOutsideStore = useUserOutsideStore()
|
||||
/**
|
||||
* 全局拦截请求发送前提交的参数
|
||||
* 以下代码为示例,在请求头里带上 token 信息
|
||||
*/
|
||||
if (store.getters['user/isLogin']) {
|
||||
request.headers['Token'] = store.state.user.token
|
||||
if (userOutsideStore.isLogin) {
|
||||
request.headers['Token'] = userOutsideStore.token
|
||||
}
|
||||
// 是否将 POST 请求参数进行字符串化处理
|
||||
if (request.method === 'post') {
|
||||
|
@ -1,12 +1,17 @@
|
||||
<template>
|
||||
<footer class="copyright">
|
||||
Copyright © {{ $store.state.settings.copyrightDates }}
|
||||
<a v-if="$store.state.settings.copyrightWebsite" :href="$store.state.settings.copyrightWebsite" target="_blank" rel="noopener">{{ $store.state.settings.copyrightCompany }},</a>
|
||||
<span v-else>{{ $store.state.settings.copyrightCompany }},</span>
|
||||
Copyright © {{ settingsStore.copyrightDates }}
|
||||
<a v-if="settingsStore.copyrightWebsite" :href="settingsStore.copyrightWebsite" target="_blank" rel="noopener">{{ settingsStore.copyrightCompany }},</a>
|
||||
<span v-else>{{ settingsStore.copyrightCompany }},</span>
|
||||
All Rights Reserved
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useSettingsStore } from '@/store/modules/settings'
|
||||
const settingsStore = useSettingsStore()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
footer {
|
||||
margin: 40px 0 20px;
|
||||
|
@ -1,13 +1,13 @@
|
||||
<template>
|
||||
<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="main">
|
||||
<Logo />
|
||||
<!-- 顶部模式 -->
|
||||
<div class="nav">
|
||||
<template v-for="(item, index) in $store.getters['menu/routes']">
|
||||
<div v-if="item.children && item.children.length !== 0" :key="index" class="item" :class="{'active': index == $store.state.menu.headerActived}" @click="switchMenu(index)">
|
||||
<template v-for="(item, index) in menuStore.transformRoutes">
|
||||
<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" />
|
||||
<span v-if="item.meta.title">{{ item.meta.title }}</span>
|
||||
</div>
|
||||
@ -24,6 +24,11 @@
|
||||
import Logo from '../Logo/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')
|
||||
</script>
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
<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">
|
||||
<span v-if="showTitle">{{ title }}</span>
|
||||
</router-link>
|
||||
@ -8,7 +8,8 @@
|
||||
<script setup>
|
||||
import imgLogo from '@/assets/images/logo.png'
|
||||
|
||||
const store = useStore()
|
||||
import { useSettingsStore } from '@/store/modules/settings'
|
||||
const settingsStore = useSettingsStore()
|
||||
|
||||
defineProps({
|
||||
showLogo: {
|
||||
@ -26,7 +27,7 @@ const logo = ref(imgLogo)
|
||||
|
||||
const to = computed(() => {
|
||||
let rtn = {}
|
||||
if (store.state.settings.enableDashboard) {
|
||||
if (settingsStore.enableDashboard) {
|
||||
rtn.name = 'dashboard'
|
||||
}
|
||||
return rtn
|
||||
|
@ -1,14 +1,14 @@
|
||||
<template>
|
||||
<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" />
|
||||
<!-- 侧边栏模式(含主导航) -->
|
||||
<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': true,
|
||||
'active': index === $store.state.menu.headerActived
|
||||
'active': index === menuStore.headerActived
|
||||
}" :title="item.meta.title" @click="switchMenu(index)"
|
||||
>
|
||||
<svg-icon v-if="item.meta.icon" :name="item.meta.icon" />
|
||||
@ -23,6 +23,11 @@
|
||||
<script setup>
|
||||
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')
|
||||
</script>
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
<div class="container">
|
||||
<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" />
|
||||
<div v-if="$store.state.settings.mode === 'pc'" class="tips">
|
||||
<div v-if="settingsStore.mode === 'pc'" class="tips">
|
||||
<div class="tip">
|
||||
<span>Alt</span>+<span>S</span>
|
||||
唤醒搜索面板
|
||||
@ -23,7 +23,7 @@
|
||||
</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 : ''">
|
||||
<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">
|
||||
@ -53,7 +53,11 @@
|
||||
import { deepClone, isExternalLink } from '@/util'
|
||||
|
||||
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 searchInput = ref('')
|
||||
@ -110,12 +114,12 @@ onMounted(() => {
|
||||
isShow.value = !isShow.value
|
||||
})
|
||||
proxy.$hotkeys('alt+s', e => {
|
||||
if (store.state.settings.enableNavSearch) {
|
||||
if (settingsStore.enableNavSearch) {
|
||||
e.preventDefault()
|
||||
isShow.value = true
|
||||
}
|
||||
})
|
||||
store.state.menu.routes.map(item => {
|
||||
menuStore.routes.map(item => {
|
||||
getSourceList(item.children)
|
||||
})
|
||||
})
|
||||
|
@ -1,20 +1,20 @@
|
||||
<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
|
||||
:show-logo="$store.state.settings.menuMode === 'single'" :class="{
|
||||
:show-logo="settingsStore.menuMode === 'single'" :class="{
|
||||
'sidebar-logo': true,
|
||||
'sidebar-logo-bg': $store.state.settings.menuMode === 'single',
|
||||
'sidebar-logo-bg': settingsStore.menuMode === 'single',
|
||||
'shadow': sidebarScrollTop
|
||||
}"
|
||||
/>
|
||||
<!-- 侧边栏模式(无主导航) -->
|
||||
<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="{
|
||||
'is-collapse-without-logo': $store.state.settings.menuMode !== 'single' && $store.state.settings.sidebarCollapse
|
||||
: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': settingsStore.menuMode !== 'single' && settingsStore.sidebarCollapse
|
||||
}"
|
||||
>
|
||||
<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" />
|
||||
</template>
|
||||
</transition-group>
|
||||
@ -26,6 +26,11 @@
|
||||
import Logo from '../Logo/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)
|
||||
|
||||
function onSidebarScroll(e) {
|
||||
|
@ -2,8 +2,8 @@
|
||||
<div>
|
||||
<el-drawer v-model="isShow" title="主题配置" direction="rtl" :size="300">
|
||||
<el-alert title="主题配置可实时预览效果,更多设置请在 src/settings.js 中进行设置,建议在生产环境隐藏主题配置功能" type="error" :closable="false" />
|
||||
<el-divider v-if="$store.state.settings.mode === 'pc'">导航栏模式</el-divider>
|
||||
<div v-if="$store.state.settings.mode === 'pc'" class="menu-mode">
|
||||
<el-divider v-if="settingsStore.mode === 'pc'">导航栏模式</el-divider>
|
||||
<div v-if="settingsStore.mode === 'pc'" class="menu-mode">
|
||||
<el-tooltip content="侧边栏模式(含主导航)" placement="top" :show-after="500" :append-to-body="false">
|
||||
<div class="mode mode-side" :class="{'active': menuMode === 'side'}" @click="menuMode = 'side'">
|
||||
<svg-icon name="el-icon-check" />
|
||||
@ -21,7 +21,7 @@
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<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>
|
||||
<el-switch v-model="enableSidebarCollapse" />
|
||||
</div>
|
||||
@ -36,7 +36,7 @@
|
||||
<svg-icon name="el-icon-question-filled" />
|
||||
</el-tooltip>
|
||||
</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 class="setting-item">
|
||||
<div class="label">
|
||||
@ -57,7 +57,7 @@
|
||||
</div>
|
||||
<el-switch v-model="topbarFixed" />
|
||||
</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>
|
||||
<el-switch v-model="enableBreadcrumb" />
|
||||
</div>
|
||||
@ -71,7 +71,7 @@
|
||||
</div>
|
||||
<el-switch v-model="enableNavSearch" />
|
||||
</div>
|
||||
<div v-if="$store.state.settings.mode === 'pc'" class="setting-item">
|
||||
<div v-if="settingsStore.mode === 'pc'" class="setting-item">
|
||||
<div class="label">
|
||||
全屏
|
||||
<el-tooltip content="该功能使用场景极少,用户习惯于通过窗口“最大化”功能来扩大显示区域,以显示更多内容,并且使用 F11 键也可以进入全屏效果" placement="top" :append-to-body="false">
|
||||
@ -141,32 +141,36 @@
|
||||
|
||||
<script setup>
|
||||
const { proxy } = getCurrentInstance()
|
||||
const store = useStore()
|
||||
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 isShow = ref(false)
|
||||
|
||||
const menuMode = computed({
|
||||
get: function() {
|
||||
return store.state.settings.menuMode
|
||||
return settingsStore.menuMode
|
||||
},
|
||||
set: function(newValue) {
|
||||
store.commit('menu/switchHeaderActived', 0)
|
||||
store.commit('settings/updateThemeSetting', {
|
||||
menuStore.switchHeaderActived(0)
|
||||
settingsStore.updateThemeSetting({
|
||||
'menuMode': newValue
|
||||
})
|
||||
store.state.settings.menuMode !== 'single' && store.commit('menu/setHeaderActived', route.fullPath)
|
||||
settingsStore.menuMode !== 'single' && menuStore.setHeaderActived(route.fullPath)
|
||||
}
|
||||
})
|
||||
const elementSize = computed({
|
||||
get: function() {
|
||||
return store.state.settings.elementSize
|
||||
return settingsStore.elementSize
|
||||
},
|
||||
set: function(newValue) {
|
||||
proxy.$ELEMENT.size = newValue
|
||||
store.commit('settings/updateThemeSetting', {
|
||||
settingsStore.updateThemeSetting({
|
||||
'elementSize': newValue
|
||||
})
|
||||
reload()
|
||||
@ -174,130 +178,130 @@ const elementSize = computed({
|
||||
})
|
||||
const enableSidebarCollapse = computed({
|
||||
get: function() {
|
||||
return store.state.settings.enableSidebarCollapse
|
||||
return settingsStore.enableSidebarCollapse
|
||||
},
|
||||
set: function(newValue) {
|
||||
store.commit('settings/updateThemeSetting', {
|
||||
settingsStore.updateThemeSetting({
|
||||
'enableSidebarCollapse': newValue
|
||||
})
|
||||
}
|
||||
})
|
||||
const sidebarCollapse = computed({
|
||||
get: function() {
|
||||
return store.state.settings.sidebarCollapse
|
||||
return settingsStore.sidebarCollapse
|
||||
},
|
||||
set: function(newValue) {
|
||||
store.commit('settings/updateThemeSetting', {
|
||||
settingsStore.updateThemeSetting({
|
||||
'sidebarCollapse': newValue
|
||||
})
|
||||
}
|
||||
})
|
||||
const switchSidebarAndPageJump = computed({
|
||||
get: function() {
|
||||
return store.state.settings.switchSidebarAndPageJump
|
||||
return settingsStore.switchSidebarAndPageJump
|
||||
},
|
||||
set: function(newValue) {
|
||||
store.commit('settings/updateThemeSetting', {
|
||||
settingsStore.updateThemeSetting({
|
||||
'switchSidebarAndPageJump': newValue
|
||||
})
|
||||
}
|
||||
})
|
||||
const sidebarUniqueOpened = computed({
|
||||
get: function() {
|
||||
return store.state.settings.sidebarUniqueOpened
|
||||
return settingsStore.sidebarUniqueOpened
|
||||
},
|
||||
set: function(newValue) {
|
||||
store.commit('settings/updateThemeSetting', {
|
||||
settingsStore.updateThemeSetting({
|
||||
'sidebarUniqueOpened': newValue
|
||||
})
|
||||
}
|
||||
})
|
||||
const topbarFixed = computed({
|
||||
get: function() {
|
||||
return store.state.settings.topbarFixed
|
||||
return settingsStore.topbarFixed
|
||||
},
|
||||
set: function(newValue) {
|
||||
store.commit('settings/updateThemeSetting', {
|
||||
settingsStore.updateThemeSetting({
|
||||
'topbarFixed': newValue
|
||||
})
|
||||
}
|
||||
})
|
||||
const enableBreadcrumb = computed({
|
||||
get: function() {
|
||||
return store.state.settings.enableBreadcrumb
|
||||
return settingsStore.enableBreadcrumb
|
||||
},
|
||||
set: function(newValue) {
|
||||
store.commit('settings/updateThemeSetting', {
|
||||
settingsStore.updateThemeSetting({
|
||||
'enableBreadcrumb': newValue
|
||||
})
|
||||
}
|
||||
})
|
||||
const showCopyright = computed({
|
||||
get: function() {
|
||||
return store.state.settings.showCopyright
|
||||
return settingsStore.showCopyright
|
||||
},
|
||||
set: function(newValue) {
|
||||
store.commit('settings/updateThemeSetting', {
|
||||
settingsStore.updateThemeSetting({
|
||||
'showCopyright': newValue
|
||||
})
|
||||
}
|
||||
})
|
||||
const enableNavSearch = computed({
|
||||
get: function() {
|
||||
return store.state.settings.enableNavSearch
|
||||
return settingsStore.enableNavSearch
|
||||
},
|
||||
set: function(newValue) {
|
||||
store.commit('settings/updateThemeSetting', {
|
||||
settingsStore.updateThemeSetting({
|
||||
'enableNavSearch': newValue
|
||||
})
|
||||
}
|
||||
})
|
||||
const enableFullscreen = computed({
|
||||
get: function() {
|
||||
return store.state.settings.enableFullscreen
|
||||
return settingsStore.enableFullscreen
|
||||
},
|
||||
set: function(newValue) {
|
||||
store.commit('settings/updateThemeSetting', {
|
||||
settingsStore.updateThemeSetting({
|
||||
'enableFullscreen': newValue
|
||||
})
|
||||
}
|
||||
})
|
||||
const enablePageReload = computed({
|
||||
get: function() {
|
||||
return store.state.settings.enablePageReload
|
||||
return settingsStore.enablePageReload
|
||||
},
|
||||
set: function(newValue) {
|
||||
store.commit('settings/updateThemeSetting', {
|
||||
settingsStore.updateThemeSetting({
|
||||
'enablePageReload': newValue
|
||||
})
|
||||
}
|
||||
})
|
||||
const enableProgress = computed({
|
||||
get: function() {
|
||||
return store.state.settings.enableProgress
|
||||
return settingsStore.enableProgress
|
||||
},
|
||||
set: function(newValue) {
|
||||
store.commit('settings/updateThemeSetting', {
|
||||
settingsStore.updateThemeSetting({
|
||||
'enableProgress': newValue
|
||||
})
|
||||
}
|
||||
})
|
||||
const enableDynamicTitle = computed({
|
||||
get: function() {
|
||||
return store.state.settings.enableDynamicTitle
|
||||
return settingsStore.enableDynamicTitle
|
||||
},
|
||||
set: function(newValue) {
|
||||
store.commit('settings/updateThemeSetting', {
|
||||
settingsStore.updateThemeSetting({
|
||||
'enableDynamicTitle': newValue
|
||||
})
|
||||
}
|
||||
})
|
||||
const enableDashboard = computed({
|
||||
get: function() {
|
||||
return store.state.settings.enableDashboard
|
||||
return settingsStore.enableDashboard
|
||||
},
|
||||
set: function(newValue) {
|
||||
store.commit('settings/updateThemeSetting', {
|
||||
settingsStore.updateThemeSetting({
|
||||
'enableDashboard': newValue
|
||||
})
|
||||
}
|
||||
|
@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<div
|
||||
class="topbar-container" :class="{
|
||||
'fixed': $store.state.settings.topbarFixed,
|
||||
'fixed': settingsStore.topbarFixed,
|
||||
'shadow': scrollTop
|
||||
}" data-fixed-calc-width
|
||||
>
|
||||
<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" />
|
||||
</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">
|
||||
<template v-for="(item, index) in breadcrumbList">
|
||||
<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 UserMenu from '../UserMenu/index.vue'
|
||||
|
||||
const store = useStore()
|
||||
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(() => {
|
||||
let breadcrumbList = []
|
||||
if (store.state.settings.enableDashboard) {
|
||||
if (settingsStore.enableDashboard) {
|
||||
breadcrumbList.push({
|
||||
path: '/dashboard',
|
||||
title: store.state.settings.dashboardTitle
|
||||
title: settingsStore.dashboardTitle
|
||||
})
|
||||
}
|
||||
if (route.meta.breadcrumbNeste) {
|
||||
|
@ -1,20 +1,20 @@
|
||||
<template>
|
||||
<div class="user">
|
||||
<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" />
|
||||
<span class="title">查看专业版</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" />
|
||||
</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'" />
|
||||
</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" />
|
||||
</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" />
|
||||
</span>
|
||||
</div>
|
||||
@ -23,12 +23,12 @@
|
||||
<el-avatar size="medium">
|
||||
<el-icon><el-icon-user-filled /></el-icon>
|
||||
</el-avatar>
|
||||
{{ $store.state.user.account }}
|
||||
{{ userStore.account }}
|
||||
<el-icon><el-icon-caret-bottom /></el-icon>
|
||||
</div>
|
||||
<template #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 divided command="logout">退出登录</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
@ -41,9 +41,13 @@
|
||||
import screenfull from 'screenfull'
|
||||
|
||||
const reload = inject('reload')
|
||||
const store = useStore()
|
||||
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 isFullscreen = ref(false)
|
||||
|
||||
@ -77,7 +81,7 @@ function userCommand(command) {
|
||||
})
|
||||
break
|
||||
case 'logout':
|
||||
store.dispatch('user/logout').then(() => {
|
||||
userStore.logout().then(() => {
|
||||
router.push({
|
||||
name: 'login'
|
||||
})
|
||||
|
@ -3,17 +3,17 @@
|
||||
<div id="app-main">
|
||||
<Header />
|
||||
<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 />
|
||||
<SubSidebar />
|
||||
</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}">
|
||||
<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">
|
||||
<router-view v-slot="{ Component, route }">
|
||||
<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" />
|
||||
</keep-alive>
|
||||
</transition>
|
||||
@ -41,15 +41,21 @@ import BuyIt from './components/BuyIt/index.vue'
|
||||
import { isExternalLink } from '@/util'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
const store = useStore()
|
||||
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(() => {
|
||||
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 => {
|
||||
if (store.state.settings.mode === 'mobile') {
|
||||
watch(() => settingsStore.sidebarCollapse, val => {
|
||||
if (settingsStore.mode === 'mobile') {
|
||||
if (!val) {
|
||||
document.querySelector('body').classList.add('hidden')
|
||||
} else {
|
||||
@ -59,8 +65,8 @@ watch(() => store.state.settings.sidebarCollapse, val => {
|
||||
})
|
||||
|
||||
watch(() => routeInfo.path, () => {
|
||||
if (store.state.settings.mode === 'mobile') {
|
||||
store.commit('settings/updateThemeSetting', {
|
||||
if (settingsStore.mode === 'mobile') {
|
||||
settingsStore.updateThemeSetting({
|
||||
sidebarCollapse: true
|
||||
})
|
||||
}
|
||||
@ -68,7 +74,7 @@ watch(() => routeInfo.path, () => {
|
||||
|
||||
onMounted(() => {
|
||||
proxy.$hotkeys('f5', e => {
|
||||
if (store.state.settings.enablePageReload) {
|
||||
if (settingsStore.enablePageReload) {
|
||||
e.preventDefault()
|
||||
reload()
|
||||
}
|
||||
@ -83,12 +89,12 @@ function reload() {
|
||||
|
||||
provide('switchMenu', switchMenu)
|
||||
function switchMenu(index) {
|
||||
store.commit('menu/switchHeaderActived', index)
|
||||
if (store.state.settings.switchSidebarAndPageJump) {
|
||||
if (isExternalLink(store.getters['menu/sidebarRoutesFirstDeepestPath'])) {
|
||||
window.open(store.getters['menu/sidebarRoutesFirstDeepestPath'], '_blank')
|
||||
menuStore.switchHeaderActived(index)
|
||||
if (settingsStore.switchSidebarAndPageJump) {
|
||||
if (isExternalLink(menuStore.sidebarRoutesFirstDeepestPath)) {
|
||||
window.open(menuStore.sidebarRoutesFirstDeepestPath, '_blank')
|
||||
} else {
|
||||
router.push(store.getters['menu/sidebarRoutesFirstDeepestPath'])
|
||||
router.push(menuStore.sidebarRoutesFirstDeepestPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,9 @@ import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
const app = createApp(App)
|
||||
|
||||
import store from './store'
|
||||
app.use(store)
|
||||
import { piniaStore } from './store'
|
||||
import { useSettingsOutsideStore } from './store/modules/settings'
|
||||
app.use(piniaStore)
|
||||
|
||||
import router from './router'
|
||||
app.use(router)
|
||||
@ -18,7 +19,7 @@ for (var key in ElementIcons) {
|
||||
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
||||
app.use(ElementPlus, {
|
||||
locale: zhCn,
|
||||
size: store.state.settings.elementSize
|
||||
size: useSettingsOutsideStore().elementSize
|
||||
})
|
||||
|
||||
import globalProperties from '@/util/global.properties'
|
||||
|
@ -1,7 +1,10 @@
|
||||
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||
import store from '@/store'
|
||||
import NProgress from 'nprogress'
|
||||
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 = [
|
||||
@ -23,7 +26,10 @@ const constantRoutes = [
|
||||
name: 'dashboard',
|
||||
component: () => import('@/views/index.vue'),
|
||||
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 MockExample from './modules/mock.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 CooperationExample from './modules/cooperation.example'
|
||||
|
||||
@ -88,15 +94,15 @@ const asyncRoutes = [
|
||||
ExternalLinkExample
|
||||
]
|
||||
},
|
||||
{
|
||||
meta: {
|
||||
title: '教程',
|
||||
icon: 'sidebar-videos'
|
||||
},
|
||||
children: [
|
||||
...VideosExample
|
||||
]
|
||||
},
|
||||
// {
|
||||
// meta: {
|
||||
// title: '教程',
|
||||
// icon: 'sidebar-videos'
|
||||
// },
|
||||
// children: [
|
||||
// ...VideosExample
|
||||
// ]
|
||||
// },
|
||||
{
|
||||
meta: {
|
||||
title: '生态',
|
||||
@ -131,13 +137,16 @@ const router = createRouter({
|
||||
})
|
||||
|
||||
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 定位主导航的选中状态
|
||||
store.state.settings.menuMode !== 'single' && store.commit('menu/setHeaderActived', to.path)
|
||||
settingsOutsideStore.menuMode !== 'single' && menuOutsideStore.setHeaderActived(to.path)
|
||||
if (to.name) {
|
||||
if (to.matched.length !== 0) {
|
||||
// 如果已登录状态下,进入登录页会强制跳转到控制台页面
|
||||
@ -146,11 +155,11 @@ router.beforeEach(async(to, from, next) => {
|
||||
name: 'dashboard',
|
||||
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({
|
||||
path: store.getters['menu/sidebarRoutesFirstDeepestPath'],
|
||||
path: menuOutsideStore.sidebarRoutesFirstDeepestPath,
|
||||
replace: true
|
||||
})
|
||||
} else {
|
||||
@ -170,10 +179,10 @@ router.beforeEach(async(to, from, next) => {
|
||||
}
|
||||
} else {
|
||||
let accessRoutes = []
|
||||
if (!store.state.settings.enableBackendReturnRoute) {
|
||||
accessRoutes = await store.dispatch('menu/generateRoutesAtFront', asyncRoutes)
|
||||
if (!settingsOutsideStore.enableBackendReturnRoute) {
|
||||
accessRoutes = await menuOutsideStore.generateRoutesAtFront(asyncRoutes)
|
||||
} else {
|
||||
accessRoutes = await store.dispatch('menu/generateRoutesAtBack')
|
||||
accessRoutes = await menuOutsideStore.generateRoutesAtBack()
|
||||
}
|
||||
accessRoutes.push(lastRoute)
|
||||
let removeRoutes = []
|
||||
@ -183,7 +192,7 @@ router.beforeEach(async(to, from, next) => {
|
||||
}
|
||||
})
|
||||
// 记录的 accessRoutes 路由数据,在登出时会使用到,不使用 router.removeRoute 是考虑配置的路由可能不一定有设置 name ,则通过调用 router.addRoute() 返回的回调进行删除
|
||||
store.commit('menu/setCurrentRemoveRoutes', removeRoutes)
|
||||
menuOutsideStore.setCurrentRemoveRoutes(removeRoutes)
|
||||
next({ ...to, replace: true })
|
||||
}
|
||||
} else {
|
||||
@ -201,14 +210,16 @@ router.beforeEach(async(to, from, next) => {
|
||||
})
|
||||
|
||||
router.afterEach((to, from) => {
|
||||
store.state.settings.enableProgress && NProgress.done()
|
||||
const settingsOutsideStore = useSettingsOutsideStore()
|
||||
const keepAliveOutsideStore = useKeepAliveOutsideStore()
|
||||
settingsOutsideStore.enableProgress && NProgress.done()
|
||||
// 设置页面 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 全局状态
|
||||
if (to.meta.cache) {
|
||||
let componentName = to.matched[to.matched.length - 1].components.default.name
|
||||
if (componentName) {
|
||||
store.commit('keepAlive/add', componentName)
|
||||
keepAliveOutsideStore.add(componentName)
|
||||
} else {
|
||||
console.warn('该页面组件未设置组件名,会导致缓存失效,请检查')
|
||||
}
|
||||
@ -220,18 +231,18 @@ router.afterEach((to, from) => {
|
||||
switch (typeof from.meta.cache) {
|
||||
case 'string':
|
||||
if (from.meta.cache != to.name) {
|
||||
store.commit('keepAlive/remove', componentName)
|
||||
keepAliveOutsideStore.remove(componentName)
|
||||
}
|
||||
break
|
||||
case 'object':
|
||||
if (!from.meta.cache.includes(to.name)) {
|
||||
store.commit('keepAlive/remove', componentName)
|
||||
keepAliveOutsideStore.remove(componentName)
|
||||
}
|
||||
break
|
||||
}
|
||||
// 如果进入的是 reload 页面,则也将离开页面的缓存清空
|
||||
if (to.name == 'reload') {
|
||||
store.commit('keepAlive/remove', componentName)
|
||||
keepAliveOutsideStore.remove(componentName)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -1,13 +1,2 @@
|
||||
import { createStore, createLogger } from 'vuex'
|
||||
|
||||
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()] : []
|
||||
})
|
||||
import { createPinia } from 'pinia'
|
||||
export const piniaStore = createPinia()
|
||||
|
@ -1,41 +1,41 @@
|
||||
const state = () => ({
|
||||
list: []
|
||||
})
|
||||
import { defineStore } from 'pinia'
|
||||
import { piniaStore } from '@/store'
|
||||
|
||||
const getters = {}
|
||||
|
||||
const actions = {}
|
||||
|
||||
const mutations = {
|
||||
add(state, name) {
|
||||
if (typeof name === 'string') {
|
||||
!state.list.includes(name) && state.list.push(name)
|
||||
} else {
|
||||
name.map(v => {
|
||||
v && !state.list.includes(v) && state.list.push(v)
|
||||
})
|
||||
export const useKeepAliveStore = defineStore(
|
||||
// 唯一ID
|
||||
'keepAlive',
|
||||
{
|
||||
state: () => ({
|
||||
list: []
|
||||
}),
|
||||
actions: {
|
||||
add(name) {
|
||||
if (typeof name === 'string') {
|
||||
!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 {
|
||||
namespaced: true,
|
||||
state,
|
||||
actions,
|
||||
getters,
|
||||
mutations
|
||||
export function useKeepAliveOutsideStore() {
|
||||
return useKeepAliveStore(piniaStore)
|
||||
}
|
||||
|
@ -1,7 +1,12 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { piniaStore } from '@/store'
|
||||
import path from 'path-browserify'
|
||||
import { deepClone, resolveRoutePath } from '@/util'
|
||||
import api from '@/api'
|
||||
|
||||
import { useSettingsStore } from './settings'
|
||||
import { useUserStore } from './user'
|
||||
|
||||
function hasPermission(permissions, route) {
|
||||
let isAuth = false
|
||||
if (route.meta && route.meta.auth) {
|
||||
@ -137,160 +142,170 @@ function getDeepestPath(routes, rootPath = '') {
|
||||
return retnPath
|
||||
}
|
||||
|
||||
const state = () => ({
|
||||
isGenerate: false,
|
||||
routes: [],
|
||||
defaultOpenedPaths: [],
|
||||
headerActived: 0,
|
||||
currentRemoveRoutes: []
|
||||
})
|
||||
|
||||
const getters = {
|
||||
// 由于 getter 的结果不会被缓存,导致导航栏切换时有明显的延迟,该问题会在 Vue 3.2 版本中修复,详看 https://github.com/vuejs/vuex/pull/1883
|
||||
routes: (state, getters, rootState) => {
|
||||
let routes
|
||||
if (rootState.settings.menuMode === 'single') {
|
||||
routes = [{ children: [] }]
|
||||
state.routes.map(item => {
|
||||
routes[0].children.push(...item.children)
|
||||
})
|
||||
} else {
|
||||
routes = state.routes
|
||||
}
|
||||
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)
|
||||
export const useMenuStore = defineStore(
|
||||
// 唯一ID
|
||||
'menu',
|
||||
{
|
||||
state: () => ({
|
||||
isGenerate: false,
|
||||
routes: [],
|
||||
defaultOpenedPaths: [],
|
||||
headerActived: 0,
|
||||
currentRemoveRoutes: []
|
||||
}),
|
||||
getters: {
|
||||
transformRoutes: state => {
|
||||
let routes
|
||||
const settingsStore = useSettingsStore()
|
||||
if (settingsStore.menuMode === 'single') {
|
||||
routes = [{ children: [] }]
|
||||
state.routes.map(item => {
|
||||
routes[0].children.push(...item.children)
|
||||
})
|
||||
} else {
|
||||
accessedRoutes = deepClone(asyncRoutes)
|
||||
routes = state.routes
|
||||
}
|
||||
commit('setRoutes', accessedRoutes)
|
||||
let routes = []
|
||||
accessedRoutes.map(item => {
|
||||
routes.push(...item.children)
|
||||
return routes
|
||||
},
|
||||
sidebarRoutes() {
|
||||
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) {
|
||||
item.children = flatAsyncRoutes(item.children, [{
|
||||
path: item.path,
|
||||
title: item.meta.title
|
||||
}], item.path)
|
||||
},
|
||||
// 生成路由(后端获取)
|
||||
generateRoutesAtBack() {
|
||||
return new Promise(resolve => {
|
||||
api.get('route/list', {
|
||||
baseURL: '/mock/'
|
||||
}).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)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const mutations = {
|
||||
invalidRoutes(state) {
|
||||
state.isGenerate = false
|
||||
state.routes = []
|
||||
state.defaultOpenedPaths = []
|
||||
state.headerActived = 0
|
||||
},
|
||||
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
|
||||
},
|
||||
// 切换头部导航
|
||||
switchHeaderActived(index) {
|
||||
this.headerActived = index
|
||||
},
|
||||
// 记录 accessRoutes 路由,用于登出时删除路由
|
||||
setCurrentRemoveRoutes(routes) {
|
||||
this.currentRemoveRoutes = routes
|
||||
},
|
||||
// 清空动态路由
|
||||
removeRoutes() {
|
||||
this.currentRemoveRoutes.forEach(removeRoute => {
|
||||
removeRoute()
|
||||
})
|
||||
) {
|
||||
state.headerActived = index
|
||||
this.currentRemoveRoutes = []
|
||||
}
|
||||
})
|
||||
},
|
||||
// 切换头部导航
|
||||
switchHeaderActived(state, index) {
|
||||
state.headerActived = index
|
||||
},
|
||||
// 记录 accessRoutes 路由,用于登出时删除路由
|
||||
setCurrentRemoveRoutes(state, routes) {
|
||||
state.currentRemoveRoutes = routes
|
||||
},
|
||||
// 清空动态路由
|
||||
removeRoutes(state) {
|
||||
state.currentRemoveRoutes.forEach(removeRoute => {
|
||||
removeRoute()
|
||||
})
|
||||
state.currentRemoveRoutes = []
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state,
|
||||
actions,
|
||||
getters,
|
||||
mutations
|
||||
export function useMenuOutsideStore() {
|
||||
return useMenuStore(piniaStore)
|
||||
}
|
||||
|
@ -1,62 +1,58 @@
|
||||
/**
|
||||
* 存放全局公用状态
|
||||
*/
|
||||
import { defineStore } from 'pinia'
|
||||
import { piniaStore } from '@/store'
|
||||
import settings from '@/settings'
|
||||
|
||||
const state = () => ({
|
||||
...settings,
|
||||
// 侧边栏是否收起(用于记录 pc 模式下最后的状态)
|
||||
sidebarCollapseLastStatus: settings.sidebarCollapse,
|
||||
// 显示模式,支持:mobile、pc
|
||||
mode: 'pc',
|
||||
// 页面标题
|
||||
title: ''
|
||||
})
|
||||
|
||||
const getters = {}
|
||||
|
||||
const actions = {}
|
||||
|
||||
const mutations = {
|
||||
// 设置访问模式
|
||||
setMode(state, width) {
|
||||
if (state.enableMobileAdaptation) {
|
||||
// 先判断 UA 是否为移动端设备(手机&平板)
|
||||
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
|
||||
state.mode = 'mobile'
|
||||
} else {
|
||||
// 如果为桌面设备,再根据页面宽度判断是否需要切换为移动端展示
|
||||
if (width < 992) {
|
||||
state.mode = 'mobile'
|
||||
export const useSettingsStore = defineStore(
|
||||
// 唯一ID
|
||||
'settings',
|
||||
{
|
||||
state: () => ({
|
||||
...settings,
|
||||
// 侧边栏是否收起(用于记录 pc 模式下最后的状态)
|
||||
sidebarCollapseLastStatus: settings.sidebarCollapse,
|
||||
// 显示模式,支持:mobile、pc
|
||||
mode: 'pc',
|
||||
// 页面标题
|
||||
title: ''
|
||||
}),
|
||||
actions: {
|
||||
// 设置访问模式
|
||||
setMode(width) {
|
||||
if (this.enableMobileAdaptation) {
|
||||
// 先判断 UA 是否为移动端设备(手机&平板)
|
||||
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
|
||||
this.mode = 'mobile'
|
||||
} else {
|
||||
// 如果为桌面设备,再根据页面宽度判断是否需要切换为移动端展示
|
||||
if (width < 992) {
|
||||
this.mode = 'mobile'
|
||||
} else {
|
||||
this.mode = 'pc'
|
||||
}
|
||||
}
|
||||
} 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 {
|
||||
namespaced: true,
|
||||
state,
|
||||
actions,
|
||||
getters,
|
||||
mutations
|
||||
export function useSettingsOutsideStore() {
|
||||
return useSettingsStore(piniaStore)
|
||||
}
|
||||
|
@ -1,102 +1,95 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { piniaStore } from '@/store'
|
||||
import api from '@/api'
|
||||
|
||||
const state = () => ({
|
||||
account: localStorage.account || '',
|
||||
token: localStorage.token || '',
|
||||
failure_time: localStorage.failure_time || '',
|
||||
permissions: []
|
||||
})
|
||||
import { useMenuStore } from './menu'
|
||||
|
||||
const getters = {
|
||||
isLogin: state => {
|
||||
let retn = false
|
||||
if (state.token) {
|
||||
if (new Date().getTime() < state.failure_time * 1000) {
|
||||
retn = true
|
||||
export const useUserStore = defineStore(
|
||||
// 唯一ID
|
||||
'user',
|
||||
{
|
||||
state: () => ({
|
||||
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 = {
|
||||
login({ commit }, data) {
|
||||
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
|
||||
export function useUserOutsideStore() {
|
||||
return useUserStore(piniaStore)
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
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) {
|
||||
// 定义一个变量
|
||||
@ -35,8 +36,10 @@ export function deepClone(target) {
|
||||
}
|
||||
|
||||
function hasPermission(permission) {
|
||||
if (store.state.settings.enablePermission) {
|
||||
return store.state.user.permissions.some(v => {
|
||||
const settingsOutsideStore = useSettingsOutsideStore()
|
||||
const userOutsideStore = useUserOutsideStore()
|
||||
if (settingsOutsideStore.enablePermission) {
|
||||
return userOutsideStore.permissions.some(v => {
|
||||
return v === permission
|
||||
})
|
||||
} else {
|
||||
|
@ -4,37 +4,36 @@
|
||||
<div class="content">
|
||||
<h1>404</h1>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
beforeRouteLeave(to, from, next) {
|
||||
clearInterval(this.inter)
|
||||
next()
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
inter: null,
|
||||
countdown: 5
|
||||
<script setup>
|
||||
import { onBeforeRouteLeave } from 'vue-router'
|
||||
const router = useRouter()
|
||||
|
||||
const data = ref({
|
||||
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()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.inter = setInterval(() => {
|
||||
this.countdown--
|
||||
if (this.countdown == 0) {
|
||||
clearInterval(this.inter)
|
||||
this.goBack()
|
||||
}
|
||||
}, 1000)
|
||||
},
|
||||
methods: {
|
||||
goBack() {
|
||||
this.$router.push('/')
|
||||
}
|
||||
}
|
||||
}, 1000)
|
||||
})
|
||||
|
||||
function goBack() {
|
||||
router.push('/')
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
<page-main>
|
||||
<div>层级:1</div>
|
||||
<router-view v-slot="{ Component }">
|
||||
<keep-alive :include="$store.state.keepAlive.list">
|
||||
<keep-alive :include="keepAliveStore.list">
|
||||
<component :is="Component" />
|
||||
</keep-alive>
|
||||
</router-view>
|
||||
@ -11,8 +11,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'TabExampleNested1'
|
||||
}
|
||||
<script setup name="TabExampleNested1">
|
||||
import { useKeepAliveStore } from '@/store/modules/keepAlive'
|
||||
const keepAliveStore = useKeepAliveStore()
|
||||
</script>
|
||||
|
@ -3,7 +3,7 @@
|
||||
<page-main>
|
||||
<div>层级:1-1</div>
|
||||
<router-view v-slot="{ Component }">
|
||||
<keep-alive :include="$store.state.keepAlive.list">
|
||||
<keep-alive :include="keepAliveStore.list">
|
||||
<component :is="Component" />
|
||||
</keep-alive>
|
||||
</router-view>
|
||||
@ -11,8 +11,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'TabExampleNested2'
|
||||
}
|
||||
<script setup name="TabExampleNested2">
|
||||
import { useKeepAliveStore } from '@/store/modules/keepAlive'
|
||||
const keepAliveStore = useKeepAliveStore()
|
||||
</script>
|
||||
|
@ -17,12 +17,15 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useKeepAliveStore } from '@/store/modules/keepAlive'
|
||||
const keepAliveStore = useKeepAliveStore()
|
||||
|
||||
export default {
|
||||
name: 'KeepAliveExamplePage',
|
||||
beforeRouteEnter(to, from, next) {
|
||||
// 进入页面时,先将当前页面的 name 信息存入 keep-alive 全局状态
|
||||
next(vm => {
|
||||
vm.$store.commit('keepAlive/add', vm.$options.name)
|
||||
keepAliveStore.add(vm.$options.name)
|
||||
})
|
||||
},
|
||||
beforeRouteLeave(to, from, next) {
|
||||
@ -31,10 +34,10 @@ export default {
|
||||
// 所以下面的代码意思就是,如果目标路由的 name 不是 keepAliveExampleDetail 或者 keepAliveExampleNestedDetail ,则将当前页面的 name 从 keep-alive 中删除
|
||||
if (!['keepAliveExampleDetail', 'keepAliveExampleNestedDetail'].includes(to.name)) {
|
||||
// 注意:上面校验的是路由的 name ,下面记录的是当前页面的 name
|
||||
this.$store.commit('keepAlive/remove', 'KeepAliveExamplePage')
|
||||
keepAliveStore.remove('KeepAliveExamplePage')
|
||||
}
|
||||
} else {
|
||||
this.$store.commit('keepAlive/remove', 'KeepAliveExamplePage')
|
||||
keepAliveStore.remove('KeepAliveExamplePage')
|
||||
}
|
||||
next()
|
||||
},
|
||||
|
@ -80,15 +80,19 @@
|
||||
</el-row>
|
||||
</el-form>
|
||||
</div>
|
||||
<Copyright v-if="$store.state.settings.showCopyright" />
|
||||
<Copyright v-if="settingsStore.showCopyright" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="Login">
|
||||
const { proxy } = getCurrentInstance()
|
||||
const store = useStore()
|
||||
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
|
||||
|
||||
// 表单类型,login 登录,reset 重置密码
|
||||
@ -146,7 +150,7 @@ function handleLogin() {
|
||||
proxy.$refs.loginFormRef.validate(valid => {
|
||||
if (valid) {
|
||||
loading.value = true
|
||||
store.dispatch('user/login', loginForm.value).then(() => {
|
||||
userStore.login(loginForm.value).then(() => {
|
||||
loading.value = false
|
||||
if (loginForm.value.remember) {
|
||||
localStorage.setItem('login_account', loginForm.value.account)
|
||||
|
@ -9,21 +9,20 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
methods: {
|
||||
getPermission() {
|
||||
this.$store.dispatch('user/getPermissions').then(res => {
|
||||
this.$notify({
|
||||
title: '当前用户权限',
|
||||
dangerouslyUseHTMLString: true,
|
||||
message: res.map(p => `<p>${p}</p>`).join('')
|
||||
})
|
||||
})
|
||||
},
|
||||
open(url) {
|
||||
window.open(url, 'top')
|
||||
}
|
||||
}
|
||||
<script setup>
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
const userStore = useUserStore()
|
||||
|
||||
function getPermission() {
|
||||
userStore.getPermissions().then(res => {
|
||||
this.$notify({
|
||||
title: '当前用户权限',
|
||||
dangerouslyUseHTMLString: true,
|
||||
message: res.map(p => `<p>${p}</p>`).join('')
|
||||
})
|
||||
})
|
||||
}
|
||||
function open(url) {
|
||||
window.open(url, 'top')
|
||||
}
|
||||
</script>
|
||||
|
@ -2,15 +2,15 @@
|
||||
<div>
|
||||
<page-header title="权限验证" />
|
||||
<page-main>
|
||||
<div v-if="!$store.state.settings.enablePermission">请到 seeting.js 里打开权限功能,再进入该页面查看演示</div>
|
||||
<div v-if="!settingsStore.enablePermission">请到 seeting.js 里打开权限功能,再进入该页面查看演示</div>
|
||||
<div v-else>
|
||||
<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="test" />
|
||||
</el-radio-group>
|
||||
<h3>帐号权限</h3>
|
||||
<div>{{ $store.state.user.permissions }}</div>
|
||||
<div>{{ userStore.permissions }}</div>
|
||||
<h3>鉴权组件(请对照代码查看)</h3>
|
||||
<div>
|
||||
<auth value="permission.browse" style="margin-bottom: 10px;">
|
||||
@ -69,43 +69,41 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
account: this.$store.state.user.account
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
accountChange(val) {
|
||||
this.$loading({
|
||||
lock: true,
|
||||
text: '帐号切换中',
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
})
|
||||
this.$store.dispatch('user/login', {
|
||||
account: val,
|
||||
password: ''
|
||||
}).then(() => {
|
||||
setTimeout(() => {
|
||||
location.reload()
|
||||
}, 1000)
|
||||
})
|
||||
},
|
||||
permissionCheck(permissions) {
|
||||
if (this.$auth(permissions)) {
|
||||
this.$message.success('校验通过')
|
||||
} else {
|
||||
this.$message.error('校验不通过')
|
||||
}
|
||||
},
|
||||
permissionCheck2(permissions) {
|
||||
if (this.$authAll(permissions)) {
|
||||
this.$message.success('校验通过')
|
||||
} else {
|
||||
this.$message.error('校验不通过')
|
||||
}
|
||||
}
|
||||
<script setup>
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
import { useSettingsStore } from '@/store/modules/settings'
|
||||
const settingsStore = useSettingsStore()
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
const userStore = useUserStore()
|
||||
|
||||
function accountChange(val) {
|
||||
proxy.$loading({
|
||||
lock: true,
|
||||
text: '帐号切换中',
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
})
|
||||
userStore.login({
|
||||
account: val,
|
||||
password: ''
|
||||
}).then(() => {
|
||||
setTimeout(() => {
|
||||
location.reload()
|
||||
}, 1000)
|
||||
})
|
||||
}
|
||||
function permissionCheck(permissions) {
|
||||
if (proxy.$auth(permissions)) {
|
||||
proxy.$message.success('校验通过')
|
||||
} else {
|
||||
proxy.$message.error('校验不通过')
|
||||
}
|
||||
}
|
||||
function permissionCheck2(permissions) {
|
||||
if (proxy.$authAll(permissions)) {
|
||||
proxy.$message.success('校验通过')
|
||||
} else {
|
||||
proxy.$message.error('校验不通过')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -25,10 +25,12 @@
|
||||
</template>
|
||||
|
||||
<script setup name="PersonalEditPassword">
|
||||
const store = useStore()
|
||||
const route = useRoute(), router = useRouter()
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
const userStore = useUserStore()
|
||||
|
||||
const validatePassword = (rule, value, callback) => {
|
||||
if (value !== form.value.newpassword) {
|
||||
callback(new Error('请确认新密码'))
|
||||
@ -60,12 +62,12 @@ const rules = ref({
|
||||
function onSubmit() {
|
||||
proxy.$refs['formRef'].validate(valid => {
|
||||
if (valid) {
|
||||
store.dispatch('user/editPassword', form.value).then(() => {
|
||||
userStore.editPassword(form.value).then(() => {
|
||||
proxy.$message({
|
||||
type: 'success',
|
||||
message: '模拟修改成功,请重新登录'
|
||||
})
|
||||
store.dispatch('user/logout').then(() => {
|
||||
userStore.logout().then(() => {
|
||||
router.push({
|
||||
name: 'login',
|
||||
query: {
|
||||
|
@ -4,10 +4,7 @@ export default function createAutoImport() {
|
||||
return autoImport({
|
||||
imports: [
|
||||
'vue',
|
||||
'vue-router',
|
||||
{
|
||||
'vuex': ['useStore']
|
||||
}
|
||||
'vue-router'
|
||||
],
|
||||
dts: false
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user