refactor: 优化pinia setup store组合式函数写法
Former-commit-id: 27347ede51d0952d3422c3a6c3a86652f91e5639
This commit is contained in:
@@ -1,36 +1,15 @@
|
|||||||
<template>
|
|
||||||
<el-dropdown class="lang-select" trigger="click" @command="handleSetLanguage">
|
|
||||||
<div class="lang-select__icon">
|
|
||||||
<svg-icon class-name="international-icon" icon-class="language" />
|
|
||||||
</div>
|
|
||||||
<template #dropdown>
|
|
||||||
<el-dropdown-menu>
|
|
||||||
<el-dropdown-item :disabled="language === 'zh-cn'" command="zh-cn">
|
|
||||||
中文
|
|
||||||
</el-dropdown-item>
|
|
||||||
<el-dropdown-item :disabled="language === 'en'" command="en">
|
|
||||||
English
|
|
||||||
</el-dropdown-item>
|
|
||||||
</el-dropdown-menu>
|
|
||||||
</template>
|
|
||||||
</el-dropdown>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
|
||||||
import useStore from '@/store';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
import SvgIcon from '@/components/SvgIcon/index.vue';
|
import SvgIcon from '@/components/SvgIcon/index.vue';
|
||||||
|
import { useAppStore } from '@/store/modules/app';
|
||||||
|
|
||||||
const { app } = useStore();
|
const appStore = useAppStore();
|
||||||
const language = computed(() => app.language);
|
|
||||||
|
|
||||||
const { locale } = useI18n();
|
const { locale } = useI18n();
|
||||||
|
|
||||||
function handleSetLanguage(lang: string) {
|
function handleLanguageChange(lang: string) {
|
||||||
locale.value = lang;
|
locale.value = lang;
|
||||||
app.setLanguage(lang);
|
appStore.changeLanguage(lang);
|
||||||
if (lang == 'en') {
|
if (lang == 'en') {
|
||||||
ElMessage.success('Switch Language Successful!');
|
ElMessage.success('Switch Language Successful!');
|
||||||
} else {
|
} else {
|
||||||
@@ -39,6 +18,31 @@ function handleSetLanguage(lang: string) {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<el-dropdown
|
||||||
|
class="lang-select"
|
||||||
|
trigger="click"
|
||||||
|
@command="handleLanguageChange"
|
||||||
|
>
|
||||||
|
<div class="lang-select__icon">
|
||||||
|
<svg-icon class-name="international-icon" icon-class="language" />
|
||||||
|
</div>
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<el-dropdown-item
|
||||||
|
:disabled="appStore.language === 'zh-cn'"
|
||||||
|
command="zh-cn"
|
||||||
|
>
|
||||||
|
中文
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item :disabled="appStore.language === 'en'" command="en">
|
||||||
|
English
|
||||||
|
</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.lang-select__icon {
|
.lang-select__icon {
|
||||||
line-height: 50px;
|
line-height: 50px;
|
||||||
|
|||||||
@@ -1,36 +1,16 @@
|
|||||||
<template>
|
|
||||||
<div ref="rightPanel" :class="{ show: show }">
|
|
||||||
<div class="right-panel-background" />
|
|
||||||
<div class="right-panel">
|
|
||||||
<div
|
|
||||||
class="right-panel__button"
|
|
||||||
:style="{ top: buttonTop + 'px', 'background-color': theme }"
|
|
||||||
@click="show = !show"
|
|
||||||
>
|
|
||||||
<Close class="icon" v-show="show" />
|
|
||||||
<Setting class="icon" v-show="!show" />
|
|
||||||
</div>
|
|
||||||
<div class="right-panel__items">
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
import { onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
||||||
|
|
||||||
import { addClass, removeClass } from '@/utils/index';
|
import { addClass, removeClass } from '@/utils/index';
|
||||||
|
|
||||||
import useStore from '@/store';
|
import { useSettingsStore } from '@/store/modules/settings';
|
||||||
|
|
||||||
// 图标依赖
|
// 图标依赖
|
||||||
import { Close, Setting } from '@element-plus/icons-vue';
|
import { Close, Setting } from '@element-plus/icons-vue';
|
||||||
import { ElColorPicker } from 'element-plus';
|
import { ElColorPicker } from 'element-plus';
|
||||||
|
|
||||||
const { setting } = useStore();
|
const settingsStore = useSettingsStore();
|
||||||
|
|
||||||
const theme = computed(() => setting.theme);
|
|
||||||
const show = ref(false);
|
const show = ref(false);
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
@@ -87,6 +67,28 @@ onBeforeUnmount(() => {
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div ref="rightPanel" :class="{ show: show }">
|
||||||
|
<div class="right-panel-background" />
|
||||||
|
<div class="right-panel">
|
||||||
|
<div
|
||||||
|
class="right-panel__button"
|
||||||
|
:style="{
|
||||||
|
top: buttonTop + 'px',
|
||||||
|
'background-color': settingsStore.theme
|
||||||
|
}"
|
||||||
|
@click="show = !show"
|
||||||
|
>
|
||||||
|
<Close class="icon" v-show="show" />
|
||||||
|
<Setting class="icon" v-show="!show" />
|
||||||
|
</div>
|
||||||
|
<div class="right-panel__items">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.showRightPanel {
|
.showRightPanel {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
@@ -1,14 +1,35 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { ElMessage } from 'element-plus';
|
||||||
|
|
||||||
|
import { useAppStore } from '@/store/modules/app';
|
||||||
|
import SvgIcon from '@/components/SvgIcon/index.vue';
|
||||||
|
|
||||||
|
const appStore = useAppStore();
|
||||||
|
|
||||||
|
const sizeOptions = ref([
|
||||||
|
{ label: '默认', value: 'default' },
|
||||||
|
{ label: '大型', value: 'large' },
|
||||||
|
{ label: '小型', value: 'small' }
|
||||||
|
]);
|
||||||
|
|
||||||
|
function handleSizeChange(size: string) {
|
||||||
|
appStore.changeSize(size);
|
||||||
|
ElMessage.success('切换布局大小成功');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<el-dropdown class="size-select" trigger="click" @command="handleSetSize">
|
<el-dropdown trigger="click" @command="handleSizeChange">
|
||||||
<div class="size-select__icon">
|
<div style="line-height: 50px">
|
||||||
<svg-icon class-name="size-icon" icon-class="size" />
|
<svg-icon icon-class="size" />
|
||||||
</div>
|
</div>
|
||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
<el-dropdown-menu>
|
<el-dropdown-menu>
|
||||||
<el-dropdown-item
|
<el-dropdown-item
|
||||||
v-for="item of sizeOptions"
|
v-for="item of sizeOptions"
|
||||||
:key="item.value"
|
:key="item.value"
|
||||||
:disabled="(size || 'default') == item.value"
|
:disabled="appStore.size == item.value"
|
||||||
:command="item.value"
|
:command="item.value"
|
||||||
>
|
>
|
||||||
{{ item.label }}
|
{{ item.label }}
|
||||||
@@ -17,31 +38,3 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-dropdown>
|
</el-dropdown>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref, computed } from 'vue';
|
|
||||||
import { ElMessage } from 'element-plus';
|
|
||||||
|
|
||||||
import useStore from '@/store';
|
|
||||||
import SvgIcon from '@/components/SvgIcon/index.vue';
|
|
||||||
|
|
||||||
const { app } = useStore();
|
|
||||||
const size = computed(() => app.size);
|
|
||||||
|
|
||||||
const sizeOptions = ref([
|
|
||||||
{ label: '默认', value: 'default' },
|
|
||||||
{ label: '大型', value: 'large' },
|
|
||||||
{ label: '小型', value: 'small' }
|
|
||||||
]);
|
|
||||||
|
|
||||||
function handleSetSize(size: string) {
|
|
||||||
app.setSize(size);
|
|
||||||
ElMessage.success('切换布局大小成功');
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.size-select__icon {
|
|
||||||
line-height: 50px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-color-picker
|
<el-color-picker
|
||||||
v-model="theme"
|
v-model="settingsStore.theme"
|
||||||
:predefine="[
|
:predefine="[
|
||||||
'#409EFF',
|
'#409EFF',
|
||||||
'#1890ff',
|
'#1890ff',
|
||||||
@@ -17,42 +17,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, watch } from 'vue';
|
import { useSettingsStore } from '@/store/modules/settings';
|
||||||
import useStore from '@/store';
|
const settingsStore = useSettingsStore();
|
||||||
import { localStorage } from '@/utils/storage';
|
|
||||||
|
|
||||||
// 参考连接:https://juejin.cn/post/7024025899813044232#heading-1
|
|
||||||
import { mix } from '@/utils';
|
|
||||||
// 白色混合色
|
|
||||||
const mixWhite = '#ffffff';
|
|
||||||
// 黑色混合色
|
|
||||||
const mixBlack = '#000000';
|
|
||||||
|
|
||||||
const node = document.documentElement;
|
|
||||||
|
|
||||||
const { setting } = useStore();
|
|
||||||
const theme = computed(() => setting.theme);
|
|
||||||
|
|
||||||
watch(theme, (color: string) => {
|
|
||||||
node.style.setProperty('--el-color-primary', color);
|
|
||||||
localStorage.set('theme', color);
|
|
||||||
|
|
||||||
for (let i = 1; i < 10; i += 1) {
|
|
||||||
node.style.setProperty(
|
|
||||||
`--el-color-primary-light-${i}`,
|
|
||||||
mix(color, mixWhite, i * 0.1)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
node.style.setProperty('--el-color-primary-dark', mix(color, mixBlack, 0.1));
|
|
||||||
|
|
||||||
localStorage.set('style', node.style.cssText);
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.theme-message,
|
.theme-message,
|
||||||
.theme-picker-dropdown {
|
.theme-picker-dropdown {
|
||||||
z-index: 99999 !important;
|
z-index: 9999 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-picker .el-color-picker__trigger {
|
.theme-picker .el-color-picker__trigger {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import useStore from '@/store';
|
import { useUserStoreHook } from '@/store/modules/user';
|
||||||
import { Directive, DirectiveBinding } from 'vue';
|
import { Directive, DirectiveBinding } from 'vue';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -7,8 +7,7 @@ import { Directive, DirectiveBinding } from 'vue';
|
|||||||
export const hasPerm: Directive = {
|
export const hasPerm: Directive = {
|
||||||
mounted(el: HTMLElement, binding: DirectiveBinding) {
|
mounted(el: HTMLElement, binding: DirectiveBinding) {
|
||||||
// 「超级管理员」拥有所有的按钮权限
|
// 「超级管理员」拥有所有的按钮权限
|
||||||
const { user } = useStore();
|
const { roles, perms } = useUserStoreHook();
|
||||||
const roles = user.roles;
|
|
||||||
if (roles.includes('ROOT')) {
|
if (roles.includes('ROOT')) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -17,7 +16,7 @@ export const hasPerm: Directive = {
|
|||||||
if (value) {
|
if (value) {
|
||||||
const requiredPerms = value; // DOM绑定需要的按钮权限标识
|
const requiredPerms = value; // DOM绑定需要的按钮权限标识
|
||||||
|
|
||||||
const hasPerm = user.perms?.some(perm => {
|
const hasPerm = perms?.some(perm => {
|
||||||
return requiredPerms.includes(perm);
|
return requiredPerms.includes(perm);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -41,8 +40,8 @@ export const hasRole: Directive = {
|
|||||||
|
|
||||||
if (value) {
|
if (value) {
|
||||||
const requiredRoles = value; // DOM绑定需要的角色编码
|
const requiredRoles = value; // DOM绑定需要的角色编码
|
||||||
const { user } = useStore();
|
const { roles } = useUserStoreHook();
|
||||||
const hasRole = user.roles.some(perm => {
|
const hasRole = roles.some(perm => {
|
||||||
return requiredRoles.includes(perm);
|
return requiredRoles.includes(perm);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// 自定义国际化配置
|
// 自定义国际化配置
|
||||||
import { createI18n } from 'vue-i18n';
|
import { createI18n } from 'vue-i18n';
|
||||||
import { localStorage } from '@/utils/storage';
|
import { localStorage } from '@/utils/localStorage';
|
||||||
|
|
||||||
// 本地语言包
|
// 本地语言包
|
||||||
import enLocale from './en';
|
import enLocale from './en';
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useTagsViewStore } from '@/store/modules/tagsView';
|
||||||
|
|
||||||
|
const tagsViewStore = useTagsViewStore();
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section class="app-main">
|
<section class="app-main">
|
||||||
<router-view v-slot="{ Component, route }">
|
<router-view v-slot="{ Component, route }">
|
||||||
<transition name="router-fade" mode="out-in">
|
<transition name="router-fade" mode="out-in">
|
||||||
<keep-alive :include="cachedViews">
|
<keep-alive :include="tagsViewStore.cachedViews">
|
||||||
<component :is="Component" :key="route.fullPath" />
|
<component :is="Component" :key="route.fullPath" />
|
||||||
</keep-alive>
|
</keep-alive>
|
||||||
</transition>
|
</transition>
|
||||||
@@ -10,15 +16,6 @@
|
|||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { computed } from 'vue';
|
|
||||||
import useStore from '@/store';
|
|
||||||
|
|
||||||
const { tagsView } = useStore();
|
|
||||||
|
|
||||||
const cachedViews = computed(() => tagsView.cachedViews);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.app-main {
|
.app-main {
|
||||||
/* 50= navbar 50 */
|
/* 50= navbar 50 */
|
||||||
|
|||||||
@@ -1,8 +1,55 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
import { ElMessageBox } from 'element-plus';
|
||||||
|
|
||||||
|
import Breadcrumb from '@/components/Breadcrumb/index.vue';
|
||||||
|
import Hamburger from '@/components/Hamburger/index.vue';
|
||||||
|
import Screenfull from '@/components/Screenfull/index.vue';
|
||||||
|
import SizeSelect from '@/components/SizeSelect/index.vue';
|
||||||
|
import LangSelect from '@/components/LangSelect/index.vue';
|
||||||
|
import { CaretBottom } from '@element-plus/icons-vue';
|
||||||
|
|
||||||
|
import { useAppStore, DeviceType } from '@/store/modules/app';
|
||||||
|
import { useTagsViewStore } from '@/store/modules/tagsView';
|
||||||
|
import { useUserStore } from '@/store/modules/user';
|
||||||
|
|
||||||
|
const appStore = useAppStore();
|
||||||
|
const tagsViewStore = useTagsViewStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const device = computed(() => appStore.device);
|
||||||
|
|
||||||
|
function toggleSideBar() {
|
||||||
|
appStore.toggleSidebar(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function logout() {
|
||||||
|
ElMessageBox.confirm('确定注销并退出系统吗?', '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}).then(() => {
|
||||||
|
userStore
|
||||||
|
.logout()
|
||||||
|
.then(() => {
|
||||||
|
tagsViewStore.delAllViews();
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
router.push(`/login?redirect=${route.fullPath}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="navbar">
|
<div class="navbar">
|
||||||
<hamburger
|
<hamburger
|
||||||
id="hamburger-container"
|
id="hamburger-container"
|
||||||
:is-active="sidebar.opened"
|
:is-active="appStore.sidebar.opened"
|
||||||
class="hamburger-container"
|
class="hamburger-container"
|
||||||
@toggleClick="toggleSideBar"
|
@toggleClick="toggleSideBar"
|
||||||
/>
|
/>
|
||||||
@@ -10,9 +57,7 @@
|
|||||||
<breadcrumb id="breadcrumb-container" class="breadcrumb-container" />
|
<breadcrumb id="breadcrumb-container" class="breadcrumb-container" />
|
||||||
|
|
||||||
<div class="right-menu">
|
<div class="right-menu">
|
||||||
<template v-if="device !== 'mobile'">
|
<template v-if="device !== DeviceType.mobile">
|
||||||
<!-- <search id="header-search" class="right-menu-item" />
|
|
||||||
<error-log class="errLog-container right-menu-item hover-effect" />-->
|
|
||||||
<screenfull id="screenfull" class="right-menu-item hover-effect" />
|
<screenfull id="screenfull" class="right-menu-item hover-effect" />
|
||||||
<el-tooltip content="布局大小" effect="dark" placement="bottom">
|
<el-tooltip content="布局大小" effect="dark" placement="bottom">
|
||||||
<size-select id="size-select" class="right-menu-item hover-effect" />
|
<size-select id="size-select" class="right-menu-item hover-effect" />
|
||||||
@@ -25,7 +70,7 @@
|
|||||||
trigger="click"
|
trigger="click"
|
||||||
>
|
>
|
||||||
<div class="avatar-wrapper">
|
<div class="avatar-wrapper">
|
||||||
<img :src="avatar + '?imageView2/1/w/80/h/80'" class="user-avatar" />
|
<img :src="userStore.avatar + '?imageView2/1/w/80/h/80'" />
|
||||||
<CaretBottom style="width: 0.6em; height: 0.6em; margin-left: 5px" />
|
<CaretBottom style="width: 0.6em; height: 0.6em; margin-left: 5px" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -52,53 +97,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
|
||||||
import { computed } from 'vue';
|
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
|
||||||
import { ElMessageBox } from 'element-plus';
|
|
||||||
|
|
||||||
import useStore from '@/store';
|
|
||||||
|
|
||||||
// 组件依赖
|
|
||||||
import Breadcrumb from '@/components/Breadcrumb/index.vue';
|
|
||||||
import Hamburger from '@/components/Hamburger/index.vue';
|
|
||||||
import Screenfull from '@/components/Screenfull/index.vue';
|
|
||||||
import SizeSelect from '@/components/SizeSelect/index.vue';
|
|
||||||
import LangSelect from '@/components/LangSelect/index.vue';
|
|
||||||
|
|
||||||
// 图标依赖
|
|
||||||
import { CaretBottom } from '@element-plus/icons-vue';
|
|
||||||
|
|
||||||
const { app, user, tagsView } = useStore();
|
|
||||||
|
|
||||||
const route = useRoute();
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const sidebar = computed(() => app.sidebar);
|
|
||||||
const device = computed(() => app.device);
|
|
||||||
const avatar = computed(() => user.avatar);
|
|
||||||
|
|
||||||
function toggleSideBar() {
|
|
||||||
app.toggleSidebar();
|
|
||||||
}
|
|
||||||
|
|
||||||
function logout() {
|
|
||||||
ElMessageBox.confirm('确定注销并退出系统吗?', '提示', {
|
|
||||||
confirmButtonText: '确定',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning'
|
|
||||||
}).then(() => {
|
|
||||||
user
|
|
||||||
.logout()
|
|
||||||
.then(() => {
|
|
||||||
tagsView.delAllViews();
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
router.push(`/login?redirect=${route.fullPath}`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
ul {
|
ul {
|
||||||
@@ -164,7 +162,7 @@ ul {
|
|||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
.user-avatar {
|
img {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useSettingsStore } from '@/store/modules/settings';
|
||||||
|
|
||||||
|
import ThemePicker from '@/components/ThemePicker/index.vue';
|
||||||
|
|
||||||
|
const settingsStore = useSettingsStore();
|
||||||
|
|
||||||
|
function themeChange(val: string) {
|
||||||
|
settingsStore.changeSetting({ key: 'theme', value: val });
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="drawer-container">
|
<div class="drawer-container">
|
||||||
<h3 class="drawer-title">系统布局配置</h3>
|
<h3 class="drawer-title">系统布局配置</h3>
|
||||||
@@ -10,17 +22,17 @@
|
|||||||
|
|
||||||
<div class="drawer-item">
|
<div class="drawer-item">
|
||||||
<span>开启 Tags-View</span>
|
<span>开启 Tags-View</span>
|
||||||
<el-switch v-model="tagsView" class="drawer-switch" />
|
<el-switch v-model="settingsStore.tagsView" class="drawer-switch" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="drawer-item">
|
<div class="drawer-item">
|
||||||
<span>固定 Header</span>
|
<span>固定 Header</span>
|
||||||
<el-switch v-model="fixedHeader" class="drawer-switch" />
|
<el-switch v-model="settingsStore.fixedHeader" class="drawer-switch" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="drawer-item">
|
<div class="drawer-item">
|
||||||
<span>侧边栏 Logo</span>
|
<span>侧边栏 Logo</span>
|
||||||
<el-switch v-model="sidebarLogo" class="drawer-switch" />
|
<el-switch v-model="settingsStore.sidebarLogo" class="drawer-switch" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-divider>导航栏模式</el-divider>
|
<el-divider>导航栏模式</el-divider>
|
||||||
@@ -48,49 +60,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { reactive, toRefs, watch } from 'vue';
|
|
||||||
|
|
||||||
import ThemePicker from '@/components/ThemePicker/index.vue';
|
|
||||||
|
|
||||||
import useStore from '@/store';
|
|
||||||
|
|
||||||
const { setting } = useStore();
|
|
||||||
|
|
||||||
const state = reactive({
|
|
||||||
fixedHeader: setting.fixedHeader,
|
|
||||||
tagsView: setting.tagsView,
|
|
||||||
sidebarLogo: setting.sidebarLogo
|
|
||||||
});
|
|
||||||
|
|
||||||
const { fixedHeader, tagsView, sidebarLogo } = toRefs(state);
|
|
||||||
|
|
||||||
function themeChange(val: any) {
|
|
||||||
setting.changeSetting({ key: 'theme', value: val });
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => state.fixedHeader,
|
|
||||||
value => {
|
|
||||||
setting.changeSetting({ key: 'fixedHeader', value: value });
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => state.tagsView,
|
|
||||||
value => {
|
|
||||||
setting.changeSetting({ key: 'tagsView', value: value });
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => state.sidebarLogo,
|
|
||||||
value => {
|
|
||||||
setting.changeSetting({ key: 'sidebarLogo', value: value });
|
|
||||||
}
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.drawer-container {
|
.drawer-container {
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
|
|||||||
@@ -1,3 +1,32 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { isExternal } from '@/utils/validate';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
import { DeviceType, useAppStore } from '@/store/modules/app';
|
||||||
|
const appStore = useAppStore();
|
||||||
|
|
||||||
|
const sidebar = computed(() => appStore.sidebar);
|
||||||
|
const device = computed(() => appStore.device);
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
to: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
function push() {
|
||||||
|
if (device.value === DeviceType.mobile && sidebar.value.opened == true) {
|
||||||
|
appStore.closeSideBar(false);
|
||||||
|
}
|
||||||
|
router.push(props.to).catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<a v-if="isExternal(to)" :href="to" target="_blank" rel="noopener">
|
<a v-if="isExternal(to)" :href="to" target="_blank" rel="noopener">
|
||||||
<slot />
|
<slot />
|
||||||
@@ -6,40 +35,3 @@
|
|||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { computed, defineComponent } from 'vue';
|
|
||||||
import { isExternal } from '@/utils/validate';
|
|
||||||
import { useRouter } from 'vue-router';
|
|
||||||
|
|
||||||
import useStore from '@/store';
|
|
||||||
|
|
||||||
const { app } = useStore();
|
|
||||||
|
|
||||||
const sidebar = computed(() => app.sidebar);
|
|
||||||
const device = computed(() => app.device);
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
props: {
|
|
||||||
to: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const router = useRouter();
|
|
||||||
const push = () => {
|
|
||||||
if (device.value === 'mobile' && sidebar.value.opened == true) {
|
|
||||||
app.closeSideBar(false);
|
|
||||||
}
|
|
||||||
router.push(props.to).catch(err => {
|
|
||||||
console.log(err);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
return {
|
|
||||||
push,
|
|
||||||
isExternal
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
@@ -1,3 +1,18 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
collapse: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const logo = ref<string>(
|
||||||
|
new URL(`../../../assets/logo.png`, import.meta.url).href
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="sidebar-logo-container" :class="{ collapse: collapse }">
|
<div class="sidebar-logo-container" :class="{ collapse: collapse }">
|
||||||
<transition name="sidebarLogoFade">
|
<transition name="sidebarLogoFade">
|
||||||
@@ -18,24 +33,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { reactive, toRefs } from 'vue';
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
collapse: {
|
|
||||||
type: Boolean,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const state = reactive({
|
|
||||||
isCollapse: props.collapse,
|
|
||||||
logo: new URL(`../../../assets/logo.png`, import.meta.url).href
|
|
||||||
});
|
|
||||||
|
|
||||||
const { logo } = toRefs(state);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.sidebarLogoFade-enter-active {
|
.sidebarLogoFade-enter-active {
|
||||||
transition: opacity 1.5s;
|
transition: opacity 1.5s;
|
||||||
|
|||||||
@@ -1,51 +1,3 @@
|
|||||||
<template>
|
|
||||||
<div v-if="!item.meta || !item.meta.hidden">
|
|
||||||
<template
|
|
||||||
v-if="
|
|
||||||
hasOneShowingChild(item.children, item) &&
|
|
||||||
(!onlyOneChild.children || onlyOneChild.noShowingChildren) &&
|
|
||||||
(!item.meta || !item.meta.alwaysShow)
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
|
|
||||||
<el-menu-item
|
|
||||||
:index="resolvePath(onlyOneChild.path)"
|
|
||||||
:class="{ 'submenu-title-noDropdown': !isNest }"
|
|
||||||
>
|
|
||||||
<svg-icon
|
|
||||||
v-if="onlyOneChild.meta && onlyOneChild.meta.icon"
|
|
||||||
:icon-class="onlyOneChild.meta.icon"
|
|
||||||
/>
|
|
||||||
<template #title>
|
|
||||||
{{ generateTitle(onlyOneChild.meta.title) }}
|
|
||||||
</template>
|
|
||||||
</el-menu-item>
|
|
||||||
</app-link>
|
|
||||||
</template>
|
|
||||||
<el-sub-menu v-else :index="resolvePath(item.path)" popper-append-to-body>
|
|
||||||
<!-- popper-append-to-body -->
|
|
||||||
<template #title>
|
|
||||||
<svg-icon
|
|
||||||
v-if="item.meta && item.meta.icon"
|
|
||||||
:icon-class="item.meta.icon"
|
|
||||||
></svg-icon>
|
|
||||||
<span v-if="item.meta && item.meta.title">{{
|
|
||||||
generateTitle(item.meta.title)
|
|
||||||
}}</span>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<sidebar-item
|
|
||||||
v-for="child in item.children"
|
|
||||||
:key="child.path"
|
|
||||||
:item="child"
|
|
||||||
:is-nest="true"
|
|
||||||
:base-path="resolvePath(child.path)"
|
|
||||||
class="nest-menu"
|
|
||||||
/>
|
|
||||||
</el-sub-menu>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import path from 'path-browserify';
|
import path from 'path-browserify';
|
||||||
@@ -110,5 +62,52 @@ function resolvePath(routePath: string) {
|
|||||||
return path.resolve(props.basePath, routePath);
|
return path.resolve(props.basePath, routePath);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<template>
|
||||||
|
<div v-if="!item.meta || !item.meta.hidden">
|
||||||
|
<template
|
||||||
|
v-if="
|
||||||
|
hasOneShowingChild(item.children, item) &&
|
||||||
|
(!onlyOneChild.children || onlyOneChild.noShowingChildren) &&
|
||||||
|
(!item.meta || !item.meta.alwaysShow)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
|
||||||
|
<el-menu-item
|
||||||
|
:index="resolvePath(onlyOneChild.path)"
|
||||||
|
:class="{ 'submenu-title-noDropdown': !isNest }"
|
||||||
|
>
|
||||||
|
<svg-icon
|
||||||
|
v-if="onlyOneChild.meta && onlyOneChild.meta.icon"
|
||||||
|
:icon-class="onlyOneChild.meta.icon"
|
||||||
|
/>
|
||||||
|
<template #title>
|
||||||
|
{{ generateTitle(onlyOneChild.meta.title) }}
|
||||||
|
</template>
|
||||||
|
</el-menu-item>
|
||||||
|
</app-link>
|
||||||
|
</template>
|
||||||
|
<el-sub-menu v-else :index="resolvePath(item.path)" popper-append-to-body>
|
||||||
|
<!-- popper-append-to-body -->
|
||||||
|
<template #title>
|
||||||
|
<svg-icon
|
||||||
|
v-if="item.meta && item.meta.icon"
|
||||||
|
:icon-class="item.meta.icon"
|
||||||
|
></svg-icon>
|
||||||
|
<span v-if="item.meta && item.meta.title">{{
|
||||||
|
generateTitle(item.meta.title)
|
||||||
|
}}</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<sidebar-item
|
||||||
|
v-for="child in item.children"
|
||||||
|
:key="child.path"
|
||||||
|
:item="child"
|
||||||
|
:is-nest="true"
|
||||||
|
:base-path="resolvePath(child.path)"
|
||||||
|
class="nest-menu"
|
||||||
|
/>
|
||||||
|
</el-sub-menu>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
<style lang="scss" scoped></style>
|
||||||
|
|||||||
@@ -1,6 +1,37 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
|
import SidebarItem from './SidebarItem.vue';
|
||||||
|
import Logo from './Logo.vue';
|
||||||
|
import variables from '@/styles/variables.module.scss';
|
||||||
|
|
||||||
|
import { useSettingsStore } from '@/store/modules/settings';
|
||||||
|
import { usePermissionStore } from '@/store/modules/permission';
|
||||||
|
import { useAppStore } from '@/store/modules/app';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
|
||||||
|
const settingsStore = useSettingsStore();
|
||||||
|
const permissionStore = usePermissionStore();
|
||||||
|
const appStore = useAppStore();
|
||||||
|
|
||||||
|
const { sidebarLogo } = storeToRefs(settingsStore);
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
const isCollapse = computed(() => !appStore.sidebar.opened);
|
||||||
|
|
||||||
|
const activeMenu = computed<string>(() => {
|
||||||
|
const { meta, path } = route;
|
||||||
|
if (meta?.activeMenu) {
|
||||||
|
return meta.activeMenu as string;
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="{ 'has-logo': showLogo }">
|
<div :class="{ 'has-logo': sidebarLogo }">
|
||||||
<logo v-if="showLogo" :collapse="isCollapse" />
|
<logo v-if="sidebarLogo" :collapse="isCollapse" />
|
||||||
<el-scrollbar>
|
<el-scrollbar>
|
||||||
<el-menu
|
<el-menu
|
||||||
:default-active="activeMenu"
|
:default-active="activeMenu"
|
||||||
@@ -13,7 +44,7 @@
|
|||||||
mode="vertical"
|
mode="vertical"
|
||||||
>
|
>
|
||||||
<sidebar-item
|
<sidebar-item
|
||||||
v-for="route in routes"
|
v-for="route in permissionStore.routes"
|
||||||
:item="route"
|
:item="route"
|
||||||
:key="route.path"
|
:key="route.path"
|
||||||
:base-path="route.path"
|
:base-path="route.path"
|
||||||
@@ -23,29 +54,3 @@
|
|||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { computed } from 'vue';
|
|
||||||
import { useRoute } from 'vue-router';
|
|
||||||
|
|
||||||
import SidebarItem from './SidebarItem.vue';
|
|
||||||
import Logo from './Logo.vue';
|
|
||||||
import variables from '@/styles/variables.module.scss';
|
|
||||||
import useStore from '@/store';
|
|
||||||
|
|
||||||
const { permission, setting, app } = useStore();
|
|
||||||
|
|
||||||
const route = useRoute();
|
|
||||||
const routes = computed(() => permission.routes);
|
|
||||||
const showLogo = computed(() => setting.sidebarLogo);
|
|
||||||
const isCollapse = computed(() => !app.sidebar.opened);
|
|
||||||
|
|
||||||
const activeMenu = computed(() => {
|
|
||||||
const { meta, path } = route;
|
|
||||||
// if set path, the sidebar will highlight the path you set
|
|
||||||
if (meta.activeMenu) {
|
|
||||||
return meta.activeMenu as string;
|
|
||||||
}
|
|
||||||
return path;
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
@@ -1,14 +1,3 @@
|
|||||||
<template>
|
|
||||||
<el-scrollbar
|
|
||||||
ref="scrollContainer"
|
|
||||||
:vertical="false"
|
|
||||||
class="scroll-container"
|
|
||||||
@wheel.prevent="handleScroll"
|
|
||||||
>
|
|
||||||
<slot />
|
|
||||||
</el-scrollbar>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {
|
import {
|
||||||
ref,
|
ref,
|
||||||
@@ -17,8 +6,7 @@ import {
|
|||||||
onBeforeUnmount,
|
onBeforeUnmount,
|
||||||
getCurrentInstance
|
getCurrentInstance
|
||||||
} from 'vue';
|
} from 'vue';
|
||||||
import useStore from '@/store';
|
import { useTagsViewStore, TagView } from '@/store/modules/tagsView';
|
||||||
import { TagView } from '@/store/modules/types';
|
|
||||||
|
|
||||||
const tagAndTagSpacing = ref(4);
|
const tagAndTagSpacing = ref(4);
|
||||||
const { proxy } = getCurrentInstance() as any;
|
const { proxy } = getCurrentInstance() as any;
|
||||||
@@ -28,9 +16,7 @@ const emitScroll = () => {
|
|||||||
emits('scroll');
|
emits('scroll');
|
||||||
};
|
};
|
||||||
|
|
||||||
const { tagsView } = useStore();
|
const tagsViewStore = useTagsViewStore();
|
||||||
|
|
||||||
const visitedViews = computed(() => tagsView.visitedViews);
|
|
||||||
|
|
||||||
const scrollWrapper = computed(
|
const scrollWrapper = computed(
|
||||||
() => proxy?.$refs.scrollContainer.$refs.wrapRef
|
() => proxy?.$refs.scrollContainer.$refs.wrapRef
|
||||||
@@ -58,9 +44,9 @@ function moveToTarget(currentTag: TagView) {
|
|||||||
let lastTag = null;
|
let lastTag = null;
|
||||||
|
|
||||||
// find first tag and last tag
|
// find first tag and last tag
|
||||||
if (visitedViews.value.length > 0) {
|
if (tagsViewStore.visitedViews.length > 0) {
|
||||||
firstTag = visitedViews.value[0];
|
firstTag = tagsViewStore.visitedViews[0];
|
||||||
lastTag = visitedViews.value[visitedViews.value.length - 1];
|
lastTag = tagsViewStore.visitedViews[tagsViewStore.visitedViews.length - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (firstTag === currentTag) {
|
if (firstTag === currentTag) {
|
||||||
@@ -69,7 +55,7 @@ function moveToTarget(currentTag: TagView) {
|
|||||||
$scrollWrapper.scrollLeft = $scrollWrapper.scrollWidth - $containerWidth;
|
$scrollWrapper.scrollLeft = $scrollWrapper.scrollWidth - $containerWidth;
|
||||||
} else {
|
} else {
|
||||||
const tagListDom = document.getElementsByClassName('tags-view__item');
|
const tagListDom = document.getElementsByClassName('tags-view__item');
|
||||||
const currentIndex = visitedViews.value.findIndex(
|
const currentIndex = tagsViewStore.visitedViews.findIndex(
|
||||||
item => item === currentTag
|
item => item === currentTag
|
||||||
);
|
);
|
||||||
let prevTag = null;
|
let prevTag = null;
|
||||||
@@ -78,13 +64,13 @@ function moveToTarget(currentTag: TagView) {
|
|||||||
if (k !== 'length' && Object.hasOwnProperty.call(tagListDom, k)) {
|
if (k !== 'length' && Object.hasOwnProperty.call(tagListDom, k)) {
|
||||||
if (
|
if (
|
||||||
(tagListDom[k] as any).dataset.path ===
|
(tagListDom[k] as any).dataset.path ===
|
||||||
visitedViews.value[currentIndex - 1].path
|
tagsViewStore.visitedViews[currentIndex - 1].path
|
||||||
) {
|
) {
|
||||||
prevTag = tagListDom[k];
|
prevTag = tagListDom[k];
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
(tagListDom[k] as any).dataset.path ===
|
(tagListDom[k] as any).dataset.path ===
|
||||||
visitedViews.value[currentIndex + 1].path
|
tagsViewStore.visitedViews[currentIndex + 1].path
|
||||||
) {
|
) {
|
||||||
nextTag = tagListDom[k];
|
nextTag = tagListDom[k];
|
||||||
}
|
}
|
||||||
@@ -113,6 +99,17 @@ defineExpose({
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<el-scrollbar
|
||||||
|
ref="scrollContainer"
|
||||||
|
:vertical="false"
|
||||||
|
class="scroll-container"
|
||||||
|
@wheel.prevent="handleScroll"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</el-scrollbar>
|
||||||
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.scroll-container {
|
.scroll-container {
|
||||||
.el-scrollbar__bar {
|
.el-scrollbar__bar {
|
||||||
|
|||||||
@@ -1,66 +1,5 @@
|
|||||||
<template>
|
|
||||||
<div class="tags-view__container">
|
|
||||||
<scroll-pane
|
|
||||||
ref="scrollPaneRef"
|
|
||||||
class="tags-view__wrapper"
|
|
||||||
@scroll="handleScroll"
|
|
||||||
>
|
|
||||||
<router-link
|
|
||||||
v-for="tag in visitedViews"
|
|
||||||
:key="tag.path"
|
|
||||||
:data-path="tag.path"
|
|
||||||
:class="isActive(tag) ? 'active' : ''"
|
|
||||||
:to="{ path: tag.path, query: tag.query }"
|
|
||||||
class="tags-view__item"
|
|
||||||
@click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''"
|
|
||||||
@contextmenu.prevent="openMenu(tag, $event)"
|
|
||||||
>
|
|
||||||
{{ generateTitle(tag.meta.title) }}
|
|
||||||
<span
|
|
||||||
v-if="!isAffix(tag)"
|
|
||||||
class="icon-close"
|
|
||||||
@click.prevent.stop="closeSelectedTag(tag)"
|
|
||||||
>
|
|
||||||
<svg-icon icon-class="close" />
|
|
||||||
</span>
|
|
||||||
</router-link>
|
|
||||||
</scroll-pane>
|
|
||||||
<ul
|
|
||||||
v-show="visible"
|
|
||||||
:style="{ left: left + 'px', top: top + 'px' }"
|
|
||||||
class="tags-view__menu"
|
|
||||||
>
|
|
||||||
<li @click="refreshSelectedTag(selectedTag)">
|
|
||||||
<svg-icon icon-class="refresh" />
|
|
||||||
刷新
|
|
||||||
</li>
|
|
||||||
<li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">
|
|
||||||
<svg-icon icon-class="close" />
|
|
||||||
关闭
|
|
||||||
</li>
|
|
||||||
<li @click="closeOtherTags">
|
|
||||||
<svg-icon icon-class="close_other" />
|
|
||||||
关闭其它
|
|
||||||
</li>
|
|
||||||
<li v-if="!isFirstView()" @click="closeLeftTags">
|
|
||||||
<svg-icon icon-class="close_left" />
|
|
||||||
关闭左侧
|
|
||||||
</li>
|
|
||||||
<li v-if="!isLastView()" @click="closeRightTags">
|
|
||||||
<svg-icon icon-class="close_right" />
|
|
||||||
关闭右侧
|
|
||||||
</li>
|
|
||||||
<li @click="closeAllTags(selectedTag)">
|
|
||||||
<svg-icon icon-class="close_all" />
|
|
||||||
关闭所有
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {
|
import {
|
||||||
computed,
|
|
||||||
getCurrentInstance,
|
getCurrentInstance,
|
||||||
nextTick,
|
nextTick,
|
||||||
ref,
|
ref,
|
||||||
@@ -76,24 +15,23 @@ import { useRoute, useRouter } from 'vue-router';
|
|||||||
import ScrollPane from './ScrollPane.vue';
|
import ScrollPane from './ScrollPane.vue';
|
||||||
import SvgIcon from '@/components/SvgIcon/index.vue';
|
import SvgIcon from '@/components/SvgIcon/index.vue';
|
||||||
import { generateTitle } from '@/utils/i18n';
|
import { generateTitle } from '@/utils/i18n';
|
||||||
import useStore from '@/store';
|
|
||||||
import { TagView } from '@/store/modules/types';
|
|
||||||
|
|
||||||
const { tagsView, permission } = useStore();
|
import { usePermissionStore } from '@/store/modules/permission';
|
||||||
|
import { useTagsViewStore, TagView } from '@/store/modules/tagsView';
|
||||||
|
|
||||||
|
const permissionStore = usePermissionStore();
|
||||||
|
const tagsViewStore = useTagsViewStore();
|
||||||
|
|
||||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
const visitedViews = computed<any>(() => tagsView.visitedViews);
|
|
||||||
const routes = computed<any>(() => permission.routes);
|
|
||||||
|
|
||||||
const affixTags = ref([]);
|
|
||||||
const visible = ref(false);
|
const visible = ref(false);
|
||||||
const selectedTag = ref({});
|
const selectedTag = ref({});
|
||||||
const scrollPaneRef = ref();
|
const scrollPaneRef = ref();
|
||||||
const left = ref(0);
|
const left = ref(0);
|
||||||
const top = ref(0);
|
const top = ref(0);
|
||||||
|
const affixTags = ref<TagView[]>([]);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
route,
|
route,
|
||||||
@@ -140,30 +78,30 @@ function filterAffixTags(routes: any[], basePath = '/') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function initTags() {
|
function initTags() {
|
||||||
const res = filterAffixTags(routes.value) as [];
|
const tags = filterAffixTags(permissionStore.routes);
|
||||||
affixTags.value = res;
|
affixTags.value = tags;
|
||||||
for (const tag of res) {
|
for (const tag of tags) {
|
||||||
// Must have tag name
|
// Must have tag name
|
||||||
if ((tag as TagView).name) {
|
if ((tag as TagView).name) {
|
||||||
tagsView.addVisitedView(tag);
|
tagsViewStore.addVisitedView(tag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function addTags() {
|
function addTags() {
|
||||||
if (route.name) {
|
if (route.name) {
|
||||||
tagsView.addView(route);
|
tagsViewStore.addView(route);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function moveToCurrentTag() {
|
function moveToCurrentTag() {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
for (const r of visitedViews.value) {
|
for (const r of tagsViewStore.visitedViews) {
|
||||||
if (r.path === route.path) {
|
if (r.path === route.path) {
|
||||||
scrollPaneRef.value.moveToTarget(r);
|
scrollPaneRef.value.moveToTarget(r);
|
||||||
// when query is different then update
|
// when query is different then update
|
||||||
if (r.fullPath !== route.fullPath) {
|
if (r.fullPath !== route.fullPath) {
|
||||||
tagsView.updateVisitedView(route);
|
tagsViewStore.updateVisitedView(route);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -182,7 +120,7 @@ function isFirstView() {
|
|||||||
try {
|
try {
|
||||||
return (
|
return (
|
||||||
(selectedTag.value as TagView).fullPath ===
|
(selectedTag.value as TagView).fullPath ===
|
||||||
visitedViews.value[1].fullPath ||
|
tagsViewStore.visitedViews[1].fullPath ||
|
||||||
(selectedTag.value as TagView).fullPath === '/index'
|
(selectedTag.value as TagView).fullPath === '/index'
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -194,7 +132,7 @@ function isLastView() {
|
|||||||
try {
|
try {
|
||||||
return (
|
return (
|
||||||
(selectedTag.value as TagView).fullPath ===
|
(selectedTag.value as TagView).fullPath ===
|
||||||
visitedViews.value[visitedViews.value.length - 1].fullPath
|
tagsViewStore.visitedViews[tagsViewStore.visitedViews.length - 1].fullPath
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return false;
|
return false;
|
||||||
@@ -202,7 +140,7 @@ function isLastView() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function refreshSelectedTag(view: TagView) {
|
function refreshSelectedTag(view: TagView) {
|
||||||
tagsView.delCachedView(view);
|
tagsViewStore.delCachedView(view);
|
||||||
const { fullPath } = view;
|
const { fullPath } = view;
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
router.replace({ path: '/redirect' + fullPath }).catch(err => {
|
router.replace({ path: '/redirect' + fullPath }).catch(err => {
|
||||||
@@ -228,7 +166,7 @@ function toLastView(visitedViews: TagView[], view?: any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function closeSelectedTag(view: TagView) {
|
function closeSelectedTag(view: TagView) {
|
||||||
tagsView.delView(view).then((res: any) => {
|
tagsViewStore.delView(view).then((res: any) => {
|
||||||
if (isActive(view)) {
|
if (isActive(view)) {
|
||||||
toLastView(res.visitedViews, view);
|
toLastView(res.visitedViews, view);
|
||||||
}
|
}
|
||||||
@@ -236,7 +174,7 @@ function closeSelectedTag(view: TagView) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function closeLeftTags() {
|
function closeLeftTags() {
|
||||||
tagsView.delLeftViews(selectedTag.value).then((res: any) => {
|
tagsViewStore.delLeftViews(selectedTag.value).then((res: any) => {
|
||||||
if (
|
if (
|
||||||
!res.visitedViews.find((item: any) => item.fullPath === route.fullPath)
|
!res.visitedViews.find((item: any) => item.fullPath === route.fullPath)
|
||||||
) {
|
) {
|
||||||
@@ -245,7 +183,7 @@ function closeLeftTags() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
function closeRightTags() {
|
function closeRightTags() {
|
||||||
tagsView.delRightViews(selectedTag.value).then((res: any) => {
|
tagsViewStore.delRightViews(selectedTag.value).then((res: any) => {
|
||||||
if (
|
if (
|
||||||
!res.visitedViews.find((item: any) => item.fullPath === route.fullPath)
|
!res.visitedViews.find((item: any) => item.fullPath === route.fullPath)
|
||||||
) {
|
) {
|
||||||
@@ -256,13 +194,13 @@ function closeRightTags() {
|
|||||||
|
|
||||||
function closeOtherTags() {
|
function closeOtherTags() {
|
||||||
router.push(selectedTag.value);
|
router.push(selectedTag.value);
|
||||||
tagsView.delOtherViews(selectedTag.value).then(() => {
|
tagsViewStore.delOtherViews(selectedTag.value).then(() => {
|
||||||
moveToCurrentTag();
|
moveToCurrentTag();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeAllTags(view: TagView) {
|
function closeAllTags(view: TagView) {
|
||||||
tagsView.delAllViews().then((res: any) => {
|
tagsViewStore.delAllViews().then((res: any) => {
|
||||||
toLastView(res.visitedViews, view);
|
toLastView(res.visitedViews, view);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -298,6 +236,66 @@ onMounted(() => {
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="tags-view__container">
|
||||||
|
<scroll-pane
|
||||||
|
ref="scrollPaneRef"
|
||||||
|
class="tags-view__wrapper"
|
||||||
|
@scroll="handleScroll"
|
||||||
|
>
|
||||||
|
<router-link
|
||||||
|
v-for="tag in tagsViewStore.visitedViews"
|
||||||
|
:key="tag.path"
|
||||||
|
:data-path="tag.path"
|
||||||
|
:class="isActive(tag) ? 'active' : ''"
|
||||||
|
:to="{ path: tag.path, query: tag.query }"
|
||||||
|
class="tags-view__item"
|
||||||
|
@click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''"
|
||||||
|
@contextmenu.prevent="openMenu(tag, $event)"
|
||||||
|
>
|
||||||
|
{{ generateTitle(tag.meta?.title) }}
|
||||||
|
<span
|
||||||
|
v-if="!isAffix(tag)"
|
||||||
|
class="icon-close"
|
||||||
|
@click.prevent.stop="closeSelectedTag(tag)"
|
||||||
|
>
|
||||||
|
<svg-icon icon-class="close" />
|
||||||
|
</span>
|
||||||
|
</router-link>
|
||||||
|
</scroll-pane>
|
||||||
|
<ul
|
||||||
|
v-show="visible"
|
||||||
|
:style="{ left: left + 'px', top: top + 'px' }"
|
||||||
|
class="tags-view__menu"
|
||||||
|
>
|
||||||
|
<li @click="refreshSelectedTag(selectedTag)">
|
||||||
|
<svg-icon icon-class="refresh" />
|
||||||
|
刷新
|
||||||
|
</li>
|
||||||
|
<li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">
|
||||||
|
<svg-icon icon-class="close" />
|
||||||
|
关闭
|
||||||
|
</li>
|
||||||
|
<li @click="closeOtherTags">
|
||||||
|
<svg-icon icon-class="close_other" />
|
||||||
|
关闭其它
|
||||||
|
</li>
|
||||||
|
<li v-if="!isFirstView()" @click="closeLeftTags">
|
||||||
|
<svg-icon icon-class="close_left" />
|
||||||
|
关闭左侧
|
||||||
|
</li>
|
||||||
|
<li v-if="!isLastView()" @click="closeRightTags">
|
||||||
|
<svg-icon icon-class="close_right" />
|
||||||
|
关闭右侧
|
||||||
|
</li>
|
||||||
|
<li @click="closeAllTags(selectedTag)">
|
||||||
|
<svg-icon icon-class="close_all" />
|
||||||
|
关闭所有
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.tags-view__container {
|
.tags-view__container {
|
||||||
height: 34px;
|
height: 34px;
|
||||||
|
|||||||
@@ -1,15 +1,72 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, watchEffect } from 'vue';
|
||||||
|
import { useWindowSize } from '@vueuse/core';
|
||||||
|
import { AppMain, Navbar, Settings, TagsView } from './components/index';
|
||||||
|
import Sidebar from './components/Sidebar/index.vue';
|
||||||
|
import RightPanel from '@/components/RightPanel/index.vue';
|
||||||
|
|
||||||
|
import { DeviceType, useAppStore } from '@/store/modules/app';
|
||||||
|
import { useSettingsStore } from '@/store/modules/settings';
|
||||||
|
|
||||||
|
const { width } = useWindowSize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应式布局容器固定宽度
|
||||||
|
*
|
||||||
|
* 大屏(>=1200px)
|
||||||
|
* 中屏(>=992px)
|
||||||
|
* 小屏(>=768px)
|
||||||
|
*/
|
||||||
|
const WIDTH = 992;
|
||||||
|
|
||||||
|
const appStore = useAppStore();
|
||||||
|
const settingsStore = useSettingsStore();
|
||||||
|
|
||||||
|
const fixedHeader = computed(() => settingsStore.fixedHeader);
|
||||||
|
const showTagsView = computed(() => settingsStore.tagsView);
|
||||||
|
const showSettings = computed(() => settingsStore.showSettings);
|
||||||
|
|
||||||
|
const classObj = computed(() => ({
|
||||||
|
hideSidebar: !appStore.sidebar.opened,
|
||||||
|
openSidebar: appStore.sidebar.opened,
|
||||||
|
withoutAnimation: appStore.sidebar.withoutAnimation,
|
||||||
|
mobile: appStore.device === DeviceType.mobile
|
||||||
|
}));
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
if (width.value < WIDTH) {
|
||||||
|
appStore.toggleDevice(DeviceType.mobile);
|
||||||
|
appStore.closeSideBar(true);
|
||||||
|
} else {
|
||||||
|
appStore.toggleDevice(DeviceType.desktop);
|
||||||
|
|
||||||
|
if (width.value >= 1200) {
|
||||||
|
//大屏
|
||||||
|
appStore.openSideBar(true);
|
||||||
|
} else {
|
||||||
|
appStore.closeSideBar(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleOutsideClick() {
|
||||||
|
appStore.closeSideBar(false);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="classObj" class="app-wrapper">
|
<div :class="classObj" class="app-wrapper">
|
||||||
|
<!-- 手机设备 && 侧边栏 → 显示遮罩层 -->
|
||||||
<div
|
<div
|
||||||
v-if="device === 'mobile' && sidebar.opened"
|
v-if="classObj.mobile && classObj.openSidebar"
|
||||||
class="drawer-bg"
|
class="drawer-bg"
|
||||||
@click="handleClickOutside"
|
@click="handleOutsideClick"
|
||||||
/>
|
></div>
|
||||||
<Sidebar class="sidebar-container" />
|
<Sidebar class="sidebar-container" />
|
||||||
<div :class="{ hasTagsView: needTagsView }" class="main-container">
|
<div :class="{ hasTagsView: showTagsView }" class="main-container">
|
||||||
<div :class="{ 'fixed-header': fixedHeader }">
|
<div :class="{ 'fixed-header': fixedHeader }">
|
||||||
<navbar />
|
<navbar />
|
||||||
<tags-view v-if="needTagsView" />
|
<tags-view v-if="showTagsView" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--主页面-->
|
<!--主页面-->
|
||||||
@@ -23,47 +80,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { computed, watchEffect } from 'vue';
|
|
||||||
import { useWindowSize } from '@vueuse/core';
|
|
||||||
import { AppMain, Navbar, Settings, TagsView } from './components/index';
|
|
||||||
import Sidebar from './components/Sidebar/index.vue';
|
|
||||||
import RightPanel from '@/components/RightPanel/index.vue';
|
|
||||||
|
|
||||||
import useStore from '@/store';
|
|
||||||
|
|
||||||
const { width } = useWindowSize();
|
|
||||||
const WIDTH = 992;
|
|
||||||
|
|
||||||
const { app, setting } = useStore();
|
|
||||||
|
|
||||||
const sidebar = computed(() => app.sidebar);
|
|
||||||
const device = computed(() => app.device);
|
|
||||||
const needTagsView = computed(() => setting.tagsView);
|
|
||||||
const fixedHeader = computed(() => setting.fixedHeader);
|
|
||||||
const showSettings = computed(() => setting.showSettings);
|
|
||||||
|
|
||||||
const classObj = computed(() => ({
|
|
||||||
hideSidebar: !sidebar.value.opened,
|
|
||||||
openSidebar: sidebar.value.opened,
|
|
||||||
withoutAnimation: sidebar.value.withoutAnimation,
|
|
||||||
mobile: device.value === 'mobile'
|
|
||||||
}));
|
|
||||||
|
|
||||||
watchEffect(() => {
|
|
||||||
if (width.value < WIDTH) {
|
|
||||||
app.toggleDevice('mobile');
|
|
||||||
app.closeSideBar(true);
|
|
||||||
} else {
|
|
||||||
app.toggleDevice('desktop');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleClickOutside() {
|
|
||||||
app.closeSideBar(false);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '@/styles/mixin.scss';
|
@import '@/styles/mixin.scss';
|
||||||
@import '@/styles/variables.module.scss';
|
@import '@/styles/variables.module.scss';
|
||||||
@@ -79,7 +95,6 @@ function handleClickOutside() {
|
|||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.drawer-bg {
|
.drawer-bg {
|
||||||
background: #000;
|
background: #000;
|
||||||
opacity: 0.3;
|
opacity: 0.3;
|
||||||
@@ -98,11 +113,9 @@ function handleClickOutside() {
|
|||||||
width: calc(100% - #{$sideBarWidth});
|
width: calc(100% - #{$sideBarWidth});
|
||||||
transition: width 0.28s;
|
transition: width 0.28s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hideSidebar .fixed-header {
|
.hideSidebar .fixed-header {
|
||||||
width: calc(100% - 54px);
|
width: calc(100% - 54px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mobile .fixed-header {
|
.mobile .fixed-header {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
|
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
|
||||||
import useStore from '@/store';
|
import { usePermissionStoreHook } from '@/store/modules/permission';
|
||||||
|
|
||||||
export const Layout = () => import('@/layout/index.vue');
|
export const Layout = () => import('@/layout/index.vue');
|
||||||
|
|
||||||
// 参数说明: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html
|
|
||||||
// 静态路由
|
// 静态路由
|
||||||
export const constantRoutes: Array<RouteRecordRaw> = [
|
export const constantRoutes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
path: '/redirect',
|
path: '/redirect',
|
||||||
component: Layout,
|
component: Layout,
|
||||||
@@ -43,7 +42,7 @@ export const constantRoutes: Array<RouteRecordRaw> = [
|
|||||||
path: '401',
|
path: '401',
|
||||||
component: () => import('@/views/error-page/401.vue'),
|
component: () => import('@/views/error-page/401.vue'),
|
||||||
meta: { hidden: true }
|
meta: { hidden: true }
|
||||||
},
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,8 +109,8 @@ const router = createRouter({
|
|||||||
|
|
||||||
// 重置路由
|
// 重置路由
|
||||||
export function resetRouter() {
|
export function resetRouter() {
|
||||||
const { permission } = useStore();
|
const permissionStore = usePermissionStoreHook();
|
||||||
permission.routes.forEach(route => {
|
permissionStore.routes.forEach(route => {
|
||||||
const name = route.name;
|
const name = route.name;
|
||||||
if (name && router.hasRoute(name)) {
|
if (name && router.hasRoute(name)) {
|
||||||
router.removeRoute(name);
|
router.removeRoute(name);
|
||||||
|
|||||||
@@ -1,15 +1,11 @@
|
|||||||
import useUserStore from './modules/user';
|
import type { App } from 'vue';
|
||||||
import useAppStore from './modules/app';
|
import { createPinia } from 'pinia';
|
||||||
import usePermissionStore from './modules/permission';
|
|
||||||
import useSettingStore from './modules/settings';
|
|
||||||
import useTagsViewStore from './modules/tagsView';
|
|
||||||
|
|
||||||
const useStore = () => ({
|
const store = createPinia();
|
||||||
user: useUserStore(),
|
|
||||||
app: useAppStore(),
|
|
||||||
permission: usePermissionStore(),
|
|
||||||
setting: useSettingStore(),
|
|
||||||
tagsView: useTagsViewStore()
|
|
||||||
});
|
|
||||||
|
|
||||||
export default useStore;
|
// 全局挂载store
|
||||||
|
export function setupStore(app: App<Element>) {
|
||||||
|
app.use(store);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { store };
|
||||||
|
|||||||
@@ -1,48 +1,96 @@
|
|||||||
import { AppState } from './types';
|
import {
|
||||||
import { localStorage } from '@/utils/storage';
|
getSidebarStatus,
|
||||||
|
setSidebarStatus,
|
||||||
|
getSize,
|
||||||
|
setSize,
|
||||||
|
setLanguage
|
||||||
|
} from '@/utils/localStorage';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { getLanguage } from '@/lang/index';
|
import { getLanguage } from '@/lang/index';
|
||||||
|
import { computed, reactive, ref } from 'vue';
|
||||||
|
|
||||||
const useAppStore = defineStore({
|
// Element Plus 语言包
|
||||||
id: 'app',
|
import zhCn from 'element-plus/es/locale/lang/zh-cn';
|
||||||
state: (): AppState => ({
|
import en from 'element-plus/es/locale/lang/en';
|
||||||
device: 'desktop',
|
|
||||||
sidebar: {
|
export enum DeviceType {
|
||||||
opened: localStorage.get('sidebarStatus')
|
mobile,
|
||||||
? !!+localStorage.get('sidebarStatus')
|
desktop
|
||||||
: true,
|
|
||||||
withoutAnimation: false,
|
|
||||||
},
|
|
||||||
language: getLanguage(),
|
|
||||||
size: localStorage.get('size') || 'default',
|
|
||||||
}),
|
|
||||||
actions: {
|
|
||||||
toggleSidebar() {
|
|
||||||
this.sidebar.opened = !this.sidebar.opened;
|
|
||||||
this.sidebar.withoutAnimation = false;
|
|
||||||
if (this.sidebar.opened) {
|
|
||||||
localStorage.set('sidebarStatus', 1);
|
|
||||||
} else {
|
|
||||||
localStorage.set('sidebarStatus', 0);
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
closeSideBar(withoutAnimation: any) {
|
export enum SizeType {
|
||||||
localStorage.set('sidebarStatus', 0);
|
default,
|
||||||
this.sidebar.opened = false;
|
large,
|
||||||
this.sidebar.withoutAnimation = withoutAnimation;
|
small
|
||||||
},
|
}
|
||||||
toggleDevice(device: string) {
|
|
||||||
this.device = device;
|
// setup
|
||||||
},
|
export const useAppStore = defineStore('app', () => {
|
||||||
setSize(size: string) {
|
// state
|
||||||
this.size = size;
|
const device = ref<DeviceType>(DeviceType.desktop);
|
||||||
localStorage.set('size', size);
|
const size = ref(getSize() || 'default');
|
||||||
},
|
const language = ref(getLanguage());
|
||||||
setLanguage(language: string) {
|
const sidebar = reactive({
|
||||||
this.language = language;
|
opened: getSidebarStatus() !== 'closed',
|
||||||
localStorage.set('language', language);
|
withoutAnimation: false
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default useAppStore;
|
const locale = computed(() => {
|
||||||
|
if (language?.value == 'en') {
|
||||||
|
return en;
|
||||||
|
} else {
|
||||||
|
return zhCn;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// actions
|
||||||
|
function toggleSidebar(withoutAnimation: boolean) {
|
||||||
|
sidebar.opened = !sidebar.opened;
|
||||||
|
sidebar.withoutAnimation = withoutAnimation;
|
||||||
|
if (sidebar.opened) {
|
||||||
|
setSidebarStatus('opened');
|
||||||
|
} else {
|
||||||
|
setSidebarStatus('closed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeSideBar(withoutAnimation: boolean) {
|
||||||
|
sidebar.opened = false;
|
||||||
|
sidebar.withoutAnimation = withoutAnimation;
|
||||||
|
setSidebarStatus('closed');
|
||||||
|
}
|
||||||
|
|
||||||
|
function openSideBar(withoutAnimation: boolean) {
|
||||||
|
sidebar.opened = true;
|
||||||
|
sidebar.withoutAnimation = withoutAnimation;
|
||||||
|
setSidebarStatus('opened');
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleDevice(val: DeviceType) {
|
||||||
|
device.value = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeSize(val: string) {
|
||||||
|
size.value = val;
|
||||||
|
setSize(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeLanguage(val: string) {
|
||||||
|
language.value = val;
|
||||||
|
setLanguage(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
device,
|
||||||
|
sidebar,
|
||||||
|
language,
|
||||||
|
locale,
|
||||||
|
size,
|
||||||
|
toggleDevice,
|
||||||
|
changeSize,
|
||||||
|
changeLanguage,
|
||||||
|
toggleSidebar,
|
||||||
|
closeSideBar,
|
||||||
|
openSideBar
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { PermissionState } from './types';
|
|
||||||
import { RouteRecordRaw } from 'vue-router';
|
import { RouteRecordRaw } from 'vue-router';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { constantRoutes } from '@/router';
|
import { constantRoutes } from '@/router';
|
||||||
|
import { store } from '@/store';
|
||||||
import { listRoutes } from '@/api/menu';
|
import { listRoutes } from '@/api/menu';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
const modules = import.meta.glob('../../views/**/**.vue');
|
const modules = import.meta.glob('../../views/**/**.vue');
|
||||||
export const Layout = () => import('@/layout/index.vue');
|
export const Layout = () => import('@/layout/index.vue');
|
||||||
@@ -21,10 +22,7 @@ const hasPermission = (roles: string[], route: RouteRecordRaw) => {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const filterAsyncRoutes = (
|
const filterAsyncRoutes = (routes: RouteRecordRaw[], roles: string[]) => {
|
||||||
routes: RouteRecordRaw[],
|
|
||||||
roles: string[]
|
|
||||||
) => {
|
|
||||||
const res: RouteRecordRaw[] = [];
|
const res: RouteRecordRaw[] = [];
|
||||||
routes.forEach(route => {
|
routes.forEach(route => {
|
||||||
const tmp = { ...route } as any;
|
const tmp = { ...route } as any;
|
||||||
@@ -49,24 +47,25 @@ export const filterAsyncRoutes = (
|
|||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
const usePermissionStore = defineStore({
|
// setup
|
||||||
id: 'permission',
|
export const usePermissionStore = defineStore('permission', () => {
|
||||||
state: (): PermissionState => ({
|
// state
|
||||||
routes: [],
|
const routes = ref<RouteRecordRaw[]>([]);
|
||||||
addRoutes: []
|
const addRoutes = ref<RouteRecordRaw[]>([]);
|
||||||
}),
|
|
||||||
actions: {
|
// auctions
|
||||||
setRoutes(routes: RouteRecordRaw[]) {
|
function setRoutes(newRoutes: RouteRecordRaw[]) {
|
||||||
this.addRoutes = routes;
|
addRoutes.value = newRoutes;
|
||||||
this.routes = constantRoutes.concat(routes);
|
routes.value = constantRoutes.concat(newRoutes);
|
||||||
},
|
}
|
||||||
generateRoutes(roles: string[]) {
|
|
||||||
return new Promise((resolve, reject) => {
|
function generateRoutes(roles: string[]) {
|
||||||
|
return new Promise<RouteRecordRaw[]>((resolve, reject) => {
|
||||||
listRoutes()
|
listRoutes()
|
||||||
.then(response => {
|
.then(response => {
|
||||||
const asyncRoutes = response.data;
|
const asyncRoutes = response.data;
|
||||||
const accessedRoutes = filterAsyncRoutes(asyncRoutes, roles);
|
const accessedRoutes = filterAsyncRoutes(asyncRoutes, roles);
|
||||||
this.setRoutes(accessedRoutes);
|
setRoutes(accessedRoutes);
|
||||||
resolve(accessedRoutes);
|
resolve(accessedRoutes);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
@@ -74,7 +73,10 @@ const usePermissionStore = defineStore({
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
return { routes, setRoutes, generateRoutes };
|
||||||
});
|
});
|
||||||
|
|
||||||
export default usePermissionStore;
|
// 非setup
|
||||||
|
export function usePermissionStoreHook() {
|
||||||
|
return usePermissionStore(store);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,50 +1,55 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { SettingState } from './types';
|
|
||||||
import defaultSettings from '../../settings';
|
import defaultSettings from '../../settings';
|
||||||
import { localStorage } from '@/utils/storage';
|
import { localStorage } from '@/utils/localStorage';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
const { showSettings, tagsView, fixedHeader, sidebarLogo } = defaultSettings;
|
|
||||||
const el = document.documentElement;
|
const el = document.documentElement;
|
||||||
|
|
||||||
export const useSettingStore = defineStore({
|
export const useSettingsStore = defineStore('setting', () => {
|
||||||
id: 'setting',
|
// state
|
||||||
state: (): SettingState => ({
|
const theme = ref(
|
||||||
theme:
|
|
||||||
localStorage.get('theme') ||
|
localStorage.get('theme') ||
|
||||||
getComputedStyle(el).getPropertyValue(`--el-color-primary`),
|
getComputedStyle(el).getPropertyValue(`--el-color-primary`)
|
||||||
showSettings: showSettings,
|
);
|
||||||
tagsView:
|
|
||||||
localStorage.get('tagsView') != null
|
const showSettings = ref<boolean>(defaultSettings.showSettings);
|
||||||
? localStorage.get('tagsView')
|
const tagsView = ref<boolean>(
|
||||||
: tagsView,
|
localStorage.get('tagsView') || defaultSettings.tagsView
|
||||||
fixedHeader: fixedHeader,
|
);
|
||||||
sidebarLogo: sidebarLogo,
|
const fixedHeader = ref<boolean>(defaultSettings.fixedHeader);
|
||||||
}),
|
const sidebarLogo = ref<boolean>(defaultSettings.sidebarLogo);
|
||||||
actions: {
|
|
||||||
async changeSetting(payload: { key: string; value: any }) {
|
// auction
|
||||||
const { key, value } = payload;
|
function changeSetting(param: { key: string; value: any }) {
|
||||||
|
const { key, value } = param;
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 'theme':
|
case 'theme':
|
||||||
this.theme = value;
|
theme.value = value;
|
||||||
break;
|
break;
|
||||||
case 'showSettings':
|
case 'showSettings':
|
||||||
this.showSettings = value;
|
showSettings.value = value;
|
||||||
break;
|
break;
|
||||||
case 'fixedHeader':
|
case 'fixedHeader':
|
||||||
this.fixedHeader = value;
|
fixedHeader.value = value;
|
||||||
break;
|
|
||||||
case 'tagsView':
|
|
||||||
this.tagsView = value;
|
|
||||||
localStorage.set('tagsView', value);
|
localStorage.set('tagsView', value);
|
||||||
break;
|
break;
|
||||||
case 'sidebarLogo':
|
case 'tagsView':
|
||||||
this.sidebarLogo = value;
|
tagsView.value = value;
|
||||||
|
break;
|
||||||
|
case 'sidevarLogo':
|
||||||
|
sidebarLogo.value = value;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default useSettingStore;
|
return {
|
||||||
|
theme,
|
||||||
|
showSettings,
|
||||||
|
tagsView,
|
||||||
|
fixedHeader,
|
||||||
|
sidebarLogo,
|
||||||
|
changeSetting
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,181 +1,214 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { TagsViewState } from './types';
|
import { ref } from 'vue';
|
||||||
|
import { RouteLocationNormalized } from 'vue-router';
|
||||||
|
|
||||||
const useTagsViewStore = defineStore({
|
export interface TagView extends Partial<RouteLocationNormalized> {
|
||||||
id: 'tagsView',
|
title?: string;
|
||||||
state: (): TagsViewState => ({
|
}
|
||||||
visitedViews: [],
|
|
||||||
cachedViews: [], // keepAlive 缓存页面
|
// setup
|
||||||
}),
|
export const useTagsViewStore = defineStore('tagsView', () => {
|
||||||
actions: {
|
// state
|
||||||
addVisitedView(view: any) {
|
const visitedViews = ref<TagView[]>([]);
|
||||||
if (this.visitedViews.some((v) => v.path === view.path)) return;
|
const cachedViews = ref<string[]>([]);
|
||||||
|
|
||||||
|
// auctions
|
||||||
|
function addVisitedView(view: TagView) {
|
||||||
|
if (visitedViews.value.some(v => v.path === view.path)) return;
|
||||||
if (view.meta && view.meta.affix) {
|
if (view.meta && view.meta.affix) {
|
||||||
this.visitedViews.unshift(
|
visitedViews.value.unshift(
|
||||||
Object.assign({}, view, {
|
Object.assign({}, view, {
|
||||||
title: view.meta?.title || 'no-name',
|
title: view.meta?.title || 'no-name'
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this.visitedViews.push(
|
visitedViews.value.push(
|
||||||
Object.assign({}, view, {
|
Object.assign({}, view, {
|
||||||
title: view.meta?.title || 'no-name',
|
title: view.meta?.title || 'no-name'
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
|
||||||
addCachedView(view: any) {
|
|
||||||
if (this.cachedViews.includes(view.name)) return;
|
|
||||||
if (view.meta.keepAlive) {
|
|
||||||
this.cachedViews.push(view.name);
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
delVisitedView(view: any) {
|
function addCachedView(view: TagView) {
|
||||||
return new Promise((resolve) => {
|
const viewName = view.name as string;
|
||||||
for (const [i, v] of this.visitedViews.entries()) {
|
if (cachedViews.value.includes(viewName)) return;
|
||||||
|
if (view.meta?.keepAlive) {
|
||||||
|
cachedViews.value.push(viewName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function delVisitedView(view: TagView) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
for (const [i, v] of visitedViews.value.entries()) {
|
||||||
if (v.path === view.path) {
|
if (v.path === view.path) {
|
||||||
this.visitedViews.splice(i, 1);
|
visitedViews.value.splice(i, 1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resolve([...this.visitedViews]);
|
resolve([...visitedViews.value]);
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
delCachedView(view: any) {
|
|
||||||
return new Promise((resolve) => {
|
function delCachedView(view: TagView) {
|
||||||
const index = this.cachedViews.indexOf(view.name);
|
const viewName = view.name as string;
|
||||||
index > -1 && this.cachedViews.splice(index, 1);
|
return new Promise(resolve => {
|
||||||
resolve([...this.cachedViews]);
|
const index = cachedViews.value.indexOf(viewName);
|
||||||
|
index > -1 && cachedViews.value.splice(index, 1);
|
||||||
|
resolve([...cachedViews.value]);
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
delOtherVisitedViews(view: any) {
|
|
||||||
return new Promise((resolve) => {
|
function delOtherVisitedViews(view: TagView) {
|
||||||
this.visitedViews = this.visitedViews.filter((v) => {
|
return new Promise(resolve => {
|
||||||
|
visitedViews.value = visitedViews.value.filter(v => {
|
||||||
return v.meta?.affix || v.path === view.path;
|
return v.meta?.affix || v.path === view.path;
|
||||||
});
|
});
|
||||||
resolve([...this.visitedViews]);
|
resolve([...visitedViews.value]);
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
delOtherCachedViews(view: any) {
|
|
||||||
return new Promise((resolve) => {
|
function delOtherCachedViews(view: TagView) {
|
||||||
const index = this.cachedViews.indexOf(view.name);
|
const viewName = view.name as string;
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const index = cachedViews.value.indexOf(viewName);
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
this.cachedViews = this.cachedViews.slice(index, index + 1);
|
cachedViews.value = cachedViews.value.slice(index, index + 1);
|
||||||
} else {
|
} else {
|
||||||
// if index = -1, there is no cached tags
|
// if index = -1, there is no cached tags
|
||||||
this.cachedViews = [];
|
cachedViews.value = [];
|
||||||
}
|
}
|
||||||
resolve([...this.cachedViews]);
|
resolve([...cachedViews.value]);
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
updateVisitedView(view: any) {
|
function updateVisitedView(view: TagView) {
|
||||||
for (let v of this.visitedViews) {
|
for (let v of visitedViews.value) {
|
||||||
if (v.path === view.path) {
|
if (v.path === view.path) {
|
||||||
v = Object.assign(v, view);
|
v = Object.assign(v, view);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
addView(view: any) {
|
|
||||||
this.addVisitedView(view);
|
function addView(view: TagView) {
|
||||||
this.addCachedView(view);
|
addVisitedView(view);
|
||||||
},
|
addCachedView(view);
|
||||||
delView(view: any) {
|
}
|
||||||
return new Promise((resolve) => {
|
|
||||||
this.delVisitedView(view);
|
function delView(view: TagView) {
|
||||||
this.delCachedView(view);
|
return new Promise(resolve => {
|
||||||
|
delVisitedView(view);
|
||||||
|
delCachedView(view);
|
||||||
resolve({
|
resolve({
|
||||||
visitedViews: [...this.visitedViews],
|
visitedViews: [...visitedViews.value],
|
||||||
cachedViews: [...this.cachedViews],
|
cachedViews: [...cachedViews.value]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
delOtherViews(view: any) {
|
|
||||||
return new Promise((resolve) => {
|
function delOtherViews(view: TagView) {
|
||||||
this.delOtherVisitedViews(view);
|
return new Promise(resolve => {
|
||||||
this.delOtherCachedViews(view);
|
delOtherVisitedViews(view);
|
||||||
|
delOtherCachedViews(view);
|
||||||
resolve({
|
resolve({
|
||||||
visitedViews: [...this.visitedViews],
|
visitedViews: [...visitedViews.value],
|
||||||
cachedViews: [...this.cachedViews],
|
cachedViews: [...cachedViews.value]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
delLeftViews(view: any) {
|
|
||||||
return new Promise((resolve) => {
|
function delLeftViews(view: TagView) {
|
||||||
const currIndex = this.visitedViews.findIndex(
|
return new Promise(resolve => {
|
||||||
(v) => v.path === view.path
|
const currIndex = visitedViews.value.findIndex(v => v.path === view.path);
|
||||||
);
|
|
||||||
if (currIndex === -1) {
|
if (currIndex === -1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.visitedViews = this.visitedViews.filter((item, index) => {
|
visitedViews.value = visitedViews.value.filter((item, index) => {
|
||||||
// affix:true 固定tag,例如“首页”
|
// affix:true 固定tag,例如“首页”
|
||||||
if (index >= currIndex || (item.meta && item.meta.affix)) {
|
if (index >= currIndex || (item.meta && item.meta.affix)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cacheIndex = this.cachedViews.indexOf(item.name as string);
|
const cacheIndex = cachedViews.value.indexOf(item.name as string);
|
||||||
if (cacheIndex > -1) {
|
if (cacheIndex > -1) {
|
||||||
this.cachedViews.splice(cacheIndex, 1);
|
cachedViews.value.splice(cacheIndex, 1);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
resolve({
|
resolve({
|
||||||
visitedViews: [...this.visitedViews],
|
visitedViews: [...visitedViews.value]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
delRightViews(view: any) {
|
function delRightViews(view: TagView) {
|
||||||
return new Promise((resolve) => {
|
return new Promise(resolve => {
|
||||||
const currIndex = this.visitedViews.findIndex(
|
const currIndex = visitedViews.value.findIndex(v => v.path === view.path);
|
||||||
(v) => v.path === view.path
|
|
||||||
);
|
|
||||||
if (currIndex === -1) {
|
if (currIndex === -1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.visitedViews = this.visitedViews.filter((item, index) => {
|
visitedViews.value = visitedViews.value.filter((item, index) => {
|
||||||
// affix:true 固定tag,例如“首页”
|
// affix:true 固定tag,例如“首页”
|
||||||
if (index <= currIndex || (item.meta && item.meta.affix)) {
|
if (index <= currIndex || (item.meta && item.meta.affix)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cacheIndex = this.cachedViews.indexOf(item.name as string);
|
const cacheIndex = cachedViews.value.indexOf(item.name as string);
|
||||||
if (cacheIndex > -1) {
|
if (cacheIndex > -1) {
|
||||||
this.cachedViews.splice(cacheIndex, 1);
|
cachedViews.value.splice(cacheIndex, 1);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
resolve({
|
resolve({
|
||||||
visitedViews: [...this.visitedViews],
|
visitedViews: [...visitedViews.value]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
delAllViews() {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
const affixTags = this.visitedViews.filter((tag) => tag.meta?.affix);
|
|
||||||
this.visitedViews = affixTags;
|
|
||||||
this.cachedViews = [];
|
|
||||||
resolve({
|
|
||||||
visitedViews: [...this.visitedViews],
|
|
||||||
cachedViews: [...this.cachedViews],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
delAllVisitedViews() {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
const affixTags = this.visitedViews.filter((tag) => tag.meta?.affix);
|
|
||||||
this.visitedViews = affixTags;
|
|
||||||
resolve([...this.visitedViews]);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
delAllCachedViews() {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
this.cachedViews = [];
|
|
||||||
resolve([...this.cachedViews]);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default useTagsViewStore;
|
function delAllViews() {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const affixTags = visitedViews.value.filter(tag => tag.meta?.affix);
|
||||||
|
visitedViews.value = affixTags;
|
||||||
|
cachedViews.value = [];
|
||||||
|
resolve({
|
||||||
|
visitedViews: [...visitedViews.value],
|
||||||
|
cachedViews: [...cachedViews.value]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function delAllVisitedViews() {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const affixTags = visitedViews.value.filter(tag => tag.meta?.affix);
|
||||||
|
visitedViews.value = affixTags;
|
||||||
|
resolve([...visitedViews.value]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function delAllCachedViews() {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
cachedViews.value = [];
|
||||||
|
resolve([...cachedViews.value]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
visitedViews,
|
||||||
|
cachedViews,
|
||||||
|
addVisitedView,
|
||||||
|
addCachedView,
|
||||||
|
delVisitedView,
|
||||||
|
delCachedView,
|
||||||
|
delOtherVisitedViews,
|
||||||
|
delOtherCachedViews,
|
||||||
|
updateVisitedView,
|
||||||
|
addView,
|
||||||
|
delView,
|
||||||
|
delOtherViews,
|
||||||
|
delLeftViews,
|
||||||
|
delRightViews,
|
||||||
|
delAllViews,
|
||||||
|
delAllVisitedViews,
|
||||||
|
delAllCachedViews
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,103 +1,101 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { UserState } from './types';
|
|
||||||
|
|
||||||
import { localStorage } from '@/utils/storage';
|
import { getToken, setToken, removeToken } from '@/utils/auth';
|
||||||
import { loginApi, logoutApi } from '@/api/auth';
|
import { loginApi, logoutApi } from '@/api/auth';
|
||||||
import { getUserInfo } from '@/api/user';
|
import { getUserInfo } from '@/api/user';
|
||||||
import { resetRouter } from '@/router';
|
import { resetRouter } from '@/router';
|
||||||
import { LoginForm } from '@/api/auth/types';
|
import { store } from '@/store';
|
||||||
|
import { LoginData } from '@/api/auth/types';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { UserInfo } from '@/api/user/types';
|
||||||
|
|
||||||
const useUserStore = defineStore({
|
export const useUserStore = defineStore('user', () => {
|
||||||
id: 'user',
|
// state
|
||||||
state: (): UserState => ({
|
const token = ref<string>(getToken() || '');
|
||||||
token: localStorage.get('token') || '',
|
const nickname = ref<string>('');
|
||||||
nickname: '',
|
const avatar = ref<string>('');
|
||||||
avatar: '',
|
const roles = ref<Array<string>>([]); // 用户角色编码集合 → 判断路由权限
|
||||||
roles: [],
|
const perms = ref<Array<string>>([]); // 用户权限编码集合 → 判断按钮权限
|
||||||
perms: []
|
|
||||||
}),
|
// auctions
|
||||||
actions: {
|
|
||||||
async RESET_STATE() {
|
// 登录
|
||||||
this.$reset();
|
function login(loginData: LoginData) {
|
||||||
},
|
return new Promise<void>((resolve, reject) => {
|
||||||
/**
|
loginApi(loginData)
|
||||||
* 登录
|
|
||||||
*/
|
|
||||||
login(data: LoginForm) {
|
|
||||||
const { username, password } = data;
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
loginApi({
|
|
||||||
grant_type: 'password',
|
|
||||||
username: username.trim(),
|
|
||||||
password: password
|
|
||||||
})
|
|
||||||
.then(response => {
|
.then(response => {
|
||||||
console.log('response.data', response.data);
|
const { accessToken } = response.data;
|
||||||
const accessToken = response.data;
|
token.value = accessToken;
|
||||||
localStorage.set('token', accessToken);
|
setToken(accessToken);
|
||||||
this.token = accessToken;
|
resolve();
|
||||||
resolve(accessToken);
|
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
reject(error);
|
reject(error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
/**
|
|
||||||
* 获取用户信息(昵称、头像、角色集合、权限集合)
|
// 获取信息(用户昵称、头像、角色集合、权限集合)
|
||||||
*/
|
function getInfo() {
|
||||||
getUserInfo() {
|
return new Promise<UserInfo>((resolve, reject) => {
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
getUserInfo()
|
getUserInfo()
|
||||||
.then(({ data }) => {
|
.then(({ data }) => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return reject('Verification failed, please Login again.');
|
return reject('Verification failed, please Login again.');
|
||||||
}
|
}
|
||||||
const { nickname, avatar, roles, perms } = data;
|
if (!data.roles || data.roles.length <= 0) {
|
||||||
if (!roles || roles.length <= 0) {
|
|
||||||
reject('getUserInfo: roles must be a non-null array!');
|
reject('getUserInfo: roles must be a non-null array!');
|
||||||
}
|
}
|
||||||
this.nickname = nickname;
|
nickname.value = data.nickname;
|
||||||
this.avatar = avatar;
|
avatar.value = data.avatar;
|
||||||
this.roles = roles;
|
roles.value = data.roles;
|
||||||
this.perms = perms;
|
perms.value = data.perms;
|
||||||
resolve(data);
|
resolve(data);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
reject(error);
|
reject(error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
// 注销
|
||||||
* 注销
|
function logout() {
|
||||||
*/
|
return new Promise<void>((resolve, reject) => {
|
||||||
logout() {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
logoutApi()
|
logoutApi()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
localStorage.remove('token');
|
|
||||||
this.RESET_STATE();
|
|
||||||
resetRouter();
|
resetRouter();
|
||||||
resolve(null);
|
resetToken();
|
||||||
|
resolve();
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
reject(error);
|
reject(error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
// 重置
|
||||||
* 清除 Token
|
function resetToken() {
|
||||||
*/
|
removeToken();
|
||||||
resetToken() {
|
token.value = '';
|
||||||
return new Promise(resolve => {
|
nickname.value = '';
|
||||||
localStorage.remove('token');
|
avatar.value = '';
|
||||||
this.RESET_STATE();
|
roles.value = [];
|
||||||
resolve(null);
|
perms.value = [];
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return {
|
||||||
|
token,
|
||||||
|
nickname,
|
||||||
|
avatar,
|
||||||
|
roles,
|
||||||
|
perms,
|
||||||
|
login,
|
||||||
|
getInfo,
|
||||||
|
logout,
|
||||||
|
resetToken
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
export default useUserStore;
|
// 非setup
|
||||||
|
export function useUserStoreHook() {
|
||||||
|
return useUserStore(store);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
/**
|
|
||||||
* window.localStorage 浏览器永久缓存
|
|
||||||
*/
|
|
||||||
export const localStorage = {
|
|
||||||
// 设置永久缓存
|
|
||||||
set(key: string, val: any) {
|
|
||||||
window.localStorage.setItem(key, JSON.stringify(val));
|
|
||||||
},
|
|
||||||
// 获取永久缓存
|
|
||||||
get(key: string) {
|
|
||||||
const json: any = window.localStorage.getItem(key);
|
|
||||||
return JSON.parse(json);
|
|
||||||
},
|
|
||||||
// 移除永久缓存
|
|
||||||
remove(key: string) {
|
|
||||||
window.localStorage.removeItem(key);
|
|
||||||
},
|
|
||||||
// 移除全部永久缓存
|
|
||||||
clear() {
|
|
||||||
window.localStorage.clear();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* window.sessionStorage 浏览器临时缓存
|
|
||||||
*/
|
|
||||||
export const sessionStorage = {
|
|
||||||
// 设置临时缓存
|
|
||||||
set(key: string, val: any) {
|
|
||||||
window.sessionStorage.setItem(key, JSON.stringify(val));
|
|
||||||
},
|
|
||||||
// 获取临时缓存
|
|
||||||
get(key: string) {
|
|
||||||
const json: any = window.sessionStorage.getItem(key);
|
|
||||||
return JSON.parse(json);
|
|
||||||
},
|
|
||||||
// 移除临时缓存
|
|
||||||
remove(key: string) {
|
|
||||||
window.sessionStorage.removeItem(key);
|
|
||||||
},
|
|
||||||
// 移除全部临时缓存
|
|
||||||
clear() {
|
|
||||||
window.sessionStorage.clear();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
<div class="login-container">
|
<div class="login-container">
|
||||||
<el-form
|
<el-form
|
||||||
ref="loginFormRef"
|
ref="loginFormRef"
|
||||||
:model="loginForm"
|
:model="loginData"
|
||||||
:rules="loginRules"
|
:rules="loginRules"
|
||||||
class="login-form"
|
class="login-form"
|
||||||
auto-complete="on"
|
auto-complete="on"
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<el-input
|
<el-input
|
||||||
ref="username"
|
ref="username"
|
||||||
v-model="loginForm.username"
|
v-model="loginData.username"
|
||||||
:placeholder="$t('login.username')"
|
:placeholder="$t('login.username')"
|
||||||
name="username"
|
name="username"
|
||||||
type="text"
|
type="text"
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
<el-input
|
<el-input
|
||||||
ref="passwordRef"
|
ref="passwordRef"
|
||||||
:key="passwordType"
|
:key="passwordType"
|
||||||
v-model="loginForm.password"
|
v-model="loginData.password"
|
||||||
:type="passwordType"
|
:type="passwordType"
|
||||||
placeholder="Password"
|
placeholder="Password"
|
||||||
name="password"
|
name="password"
|
||||||
@@ -95,13 +95,13 @@ import LangSelect from '@/components/LangSelect/index.vue';
|
|||||||
import SvgIcon from '@/components/SvgIcon/index.vue';
|
import SvgIcon from '@/components/SvgIcon/index.vue';
|
||||||
|
|
||||||
// 状态管理依赖
|
// 状态管理依赖
|
||||||
import useStore from '@/store';
|
import { useUserStore } from '@/store/modules/user';
|
||||||
|
|
||||||
// API依赖
|
// API依赖
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { LoginForm } from '@/api/auth/types';
|
import { LoginData } from '@/api/auth/types';
|
||||||
|
|
||||||
const { user } = useStore();
|
const userStore = useUserStore();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
const loginFormRef = ref(ElForm);
|
const loginFormRef = ref(ElForm);
|
||||||
@@ -109,10 +109,10 @@ const passwordRef = ref(ElInput);
|
|||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
redirect: '',
|
redirect: '',
|
||||||
loginForm: {
|
loginData: {
|
||||||
username: 'admin',
|
username: 'admin',
|
||||||
password: '123456'
|
password: '123456'
|
||||||
} as LoginForm,
|
} as LoginData,
|
||||||
loginRules: {
|
loginRules: {
|
||||||
username: [{ required: true, trigger: 'blur' }],
|
username: [{ required: true, trigger: 'blur' }],
|
||||||
password: [{ required: true, trigger: 'blur', validator: validatePassword }]
|
password: [{ required: true, trigger: 'blur', validator: validatePassword }]
|
||||||
@@ -136,7 +136,7 @@ function validatePassword(rule: any, value: any, callback: any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
loginForm,
|
loginData,
|
||||||
loginRules,
|
loginRules,
|
||||||
loading,
|
loading,
|
||||||
passwordType,
|
passwordType,
|
||||||
@@ -162,14 +162,14 @@ function showPwd() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录处理
|
* 登录
|
||||||
*/
|
*/
|
||||||
function handleLogin() {
|
function handleLogin() {
|
||||||
loginFormRef.value.validate((valid: boolean) => {
|
loginFormRef.value.validate((valid: boolean) => {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
state.loading = true;
|
state.loading = true;
|
||||||
user
|
userStore
|
||||||
.login(state.loginForm)
|
.login(state.loginData)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
router.push({ path: state.redirect || '/', query: state.otherQuery });
|
router.push({ path: state.redirect || '/', query: state.otherQuery });
|
||||||
state.loading = false;
|
state.loading = false;
|
||||||
|
|||||||
@@ -168,10 +168,10 @@ function handleDelete(row: any) {
|
|||||||
ElMessage.success('删除成功');
|
ElMessage.success('删除成功');
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
console.log(`删除失败`);
|
ElMessage.error('删除失败');
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(() => ElMessage.info('已取消删除'));
|
.catch(() => ElMessage.warning('已取消删除'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -344,7 +344,7 @@ async function getDeptOptions() {
|
|||||||
* 获取性别下拉项
|
* 获取性别下拉项
|
||||||
*/
|
*/
|
||||||
function getGenderOptions() {
|
function getGenderOptions() {
|
||||||
proxy.$listDictItemsByTypeCode('gender').then((response: any) => {
|
proxy.$getDictionaries('gender').then((response: any) => {
|
||||||
state.genderOptions = response?.data;
|
state.genderOptions = response?.data;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user