refactor: pinia整合优化重构
This commit is contained in:
@@ -7,17 +7,19 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
||||||
import {computed, onMounted, ref, watch} from "vue";
|
import {computed, onMounted, ref, watch} from "vue";
|
||||||
import {useAppStoreHook} from "@/store/modules/app";
|
|
||||||
import {ElConfigProvider} from 'element-plus'
|
import {ElConfigProvider} from 'element-plus'
|
||||||
import {localStorage} from "@/utils/storage";
|
import {localStorage} from "@/utils/storage";
|
||||||
|
|
||||||
|
import useStore from "@/store";
|
||||||
|
|
||||||
//官方文档: https://element-plus.gitee.io/zh-CN/guide/i18n.html
|
//官方文档: https://element-plus.gitee.io/zh-CN/guide/i18n.html
|
||||||
|
|
||||||
// 导入 Element Plus 语言包
|
// 导入 Element Plus 语言包
|
||||||
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
||||||
import en from 'element-plus/es/locale/lang/en'
|
import en from 'element-plus/es/locale/lang/en'
|
||||||
|
|
||||||
const language = computed(() => useAppStoreHook().language)
|
const {app} =useStore()
|
||||||
|
const language = computed(() => app.language)
|
||||||
|
|
||||||
const locale = ref()
|
const locale = ref()
|
||||||
watch(language, (value) => {
|
watch(language, (value) => {
|
||||||
|
|||||||
@@ -19,9 +19,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
||||||
import {computed} from "vue";
|
import {computed} from "vue";
|
||||||
import {useAppStoreHook} from "@/store/modules/app";
|
import useStore from "@/store";
|
||||||
|
|
||||||
const language = computed(() => useAppStoreHook().language)
|
const {app}=useStore()
|
||||||
|
const language = computed(() => app.language)
|
||||||
|
|
||||||
import {useI18n} from 'vue-i18n'
|
import {useI18n} from 'vue-i18n'
|
||||||
import {ElMessage} from 'element-plus'
|
import {ElMessage} from 'element-plus'
|
||||||
@@ -31,7 +32,7 @@ const {locale} = useI18n()
|
|||||||
|
|
||||||
function handleSetLanguage(lang: string) {
|
function handleSetLanguage(lang: string) {
|
||||||
locale.value = lang
|
locale.value = lang
|
||||||
useAppStoreHook().setLanguage(lang)
|
app.setLanguage(lang)
|
||||||
if (lang == 'en') {
|
if (lang == 'en') {
|
||||||
ElMessage.success('Switch Language Successful!')
|
ElMessage.success('Switch Language Successful!')
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -17,12 +17,15 @@
|
|||||||
import {computed, onBeforeUnmount, onMounted, ref, watch} from "vue";
|
import {computed, onBeforeUnmount, onMounted, ref, watch} from "vue";
|
||||||
|
|
||||||
import {addClass, removeClass} from '@/utils/index'
|
import {addClass, removeClass} from '@/utils/index'
|
||||||
import {useSettingStoreHook} from "@/store/modules/settings";
|
|
||||||
|
import useStore from "@/store";
|
||||||
|
|
||||||
// 图标依赖
|
// 图标依赖
|
||||||
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 props = defineProps({
|
const props = defineProps({
|
||||||
buttonTop: {
|
buttonTop: {
|
||||||
default: 250,
|
default: 250,
|
||||||
@@ -30,7 +33,7 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const theme = computed(() => useSettingStoreHook().theme)
|
const theme = computed(() =>setting.theme)
|
||||||
|
|
||||||
const show = ref(false)
|
const show = ref(false)
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,8 @@
|
|||||||
v-for="item of sizeOptions"
|
v-for="item of sizeOptions"
|
||||||
:key="item.value"
|
:key="item.value"
|
||||||
:disabled="(size || 'default') == item.value"
|
:disabled="(size || 'default') == item.value"
|
||||||
:command="item.value">
|
:command="item.value"
|
||||||
|
>
|
||||||
{{ item.label }}
|
{{ item.label }}
|
||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
</el-dropdown-menu>
|
</el-dropdown-menu>
|
||||||
@@ -18,29 +19,28 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
||||||
import { ref, computed } from "vue";
|
import { ref, computed } from "vue";
|
||||||
import {useRoute, useRouter} from "vue-router"
|
import { useRoute, useRouter } from "vue-router";
|
||||||
|
|
||||||
import {ElMessage} from 'element-plus'
|
import { ElMessage } from "element-plus";
|
||||||
|
|
||||||
import {useAppStoreHook} from '@/store/modules/app'
|
import useStore from "@/store";
|
||||||
import SvgIcon from '@/components/SvgIcon/index.vue'
|
import SvgIcon from "@/components/SvgIcon/index.vue";
|
||||||
|
|
||||||
const size = computed(() => useAppStoreHook().size)
|
const { app } = useStore();
|
||||||
|
const size = computed(() => app.size);
|
||||||
|
|
||||||
const sizeOptions = ref([
|
const sizeOptions = ref([
|
||||||
{label: '默认', value: 'default'},
|
{ label: "默认", value: "default" },
|
||||||
{label: '大型', value: 'large'},
|
{ label: "大型", value: "large" },
|
||||||
{label: '小型', value: 'small'}
|
{ label: "小型", value: "small" },
|
||||||
])
|
]);
|
||||||
|
|
||||||
function handleSetSize(size: string) {
|
function handleSetSize(size: string) {
|
||||||
useAppStoreHook().setSize(size)
|
app.setSize(size);
|
||||||
window.location.reload()
|
window.location.reload();
|
||||||
ElMessage.success('切换布局大小成功')
|
ElMessage.success("切换布局大小成功");
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang='scss' scoped>
|
<style lang='scss' scoped>
|
||||||
|
|||||||
@@ -9,8 +9,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {computed, nextTick, watch} from "vue";
|
import {computed, nextTick, watch} from "vue";
|
||||||
import {useSettingStoreHook} from "@/store/modules/settings";
|
import useStore from "@/store";
|
||||||
import {useTagsViewStoreHook} from "@/store/modules/tagsView";
|
|
||||||
import {useRoute, useRouter} from "vue-router";
|
import {useRoute, useRouter} from "vue-router";
|
||||||
import {localStorage} from "@/utils/storage";
|
import {localStorage} from "@/utils/storage";
|
||||||
|
|
||||||
@@ -23,7 +22,8 @@ const mixBlack = "#000000";
|
|||||||
|
|
||||||
const node = document.documentElement;
|
const node = document.documentElement;
|
||||||
|
|
||||||
const theme = computed(() => useSettingStoreHook().theme)
|
const {setting} =useStore()
|
||||||
|
const theme = computed(() => setting.theme)
|
||||||
|
|
||||||
watch(theme, (color: string) => {
|
watch(theme, (color: string) => {
|
||||||
node.style.setProperty("--el-color-primary", color);
|
node.style.setProperty("--el-color-primary", color);
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
import {useUserStoreHook} from "@/store/modules/user";
|
import useStore from "@/store";
|
||||||
import { Directive, DirectiveBinding } from "vue";
|
import { Directive, DirectiveBinding } from "vue";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按钮权限校验
|
* 按钮权限校验
|
||||||
*/
|
*/
|
||||||
export const hasPerm: Directive = {
|
export const hasPerm: Directive = {
|
||||||
mounted(el: HTMLElement, binding: DirectiveBinding) {
|
mounted(el: HTMLElement, binding: DirectiveBinding) {
|
||||||
// 「超级管理员」拥有所有的按钮权限
|
// 「超级管理员」拥有所有的按钮权限
|
||||||
const roles = useUserStoreHook().roles;
|
const { user } = useStore()
|
||||||
|
const roles = user.roles;
|
||||||
if (roles.includes('ROOT')) {
|
if (roles.includes('ROOT')) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -16,7 +18,7 @@ export const hasPerm: Directive = {
|
|||||||
if (value) {
|
if (value) {
|
||||||
const requiredPerms = value; // DOM绑定需要的按钮权限标识
|
const requiredPerms = value; // DOM绑定需要的按钮权限标识
|
||||||
|
|
||||||
const hasPerm = useUserStoreHook().perms.some(perm => {
|
const hasPerm = user.perms.some(perm => {
|
||||||
return requiredPerms.includes(perm)
|
return requiredPerms.includes(perm)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -35,10 +37,11 @@ export const hasPerm: Directive = {
|
|||||||
export const hasRole: Directive = {
|
export const hasRole: Directive = {
|
||||||
mounted(el: HTMLElement, binding: DirectiveBinding) {
|
mounted(el: HTMLElement, binding: DirectiveBinding) {
|
||||||
const { value } = binding;
|
const { value } = binding;
|
||||||
|
|
||||||
if (value) {
|
if (value) {
|
||||||
const requiredRoles = value; // DOM绑定需要的角色编码
|
const requiredRoles = value; // DOM绑定需要的角色编码
|
||||||
|
const { user } = useStore()
|
||||||
const hasRole = useUserStoreHook().roles.some(perm => {
|
const hasRole = user.roles.some(perm => {
|
||||||
return requiredRoles.includes(perm)
|
return requiredRoles.includes(perm)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -13,10 +13,11 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import {useTagsViewStoreHook} from '@/store/modules/tagsView'
|
import useStore from "@/store";
|
||||||
|
|
||||||
const cachedViews = computed(() => useTagsViewStoreHook().cachedViews);
|
const { tagsView } = useStore();
|
||||||
|
|
||||||
|
const cachedViews = computed(() => tagsView.cachedViews);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="navbar">
|
<div class="navbar">
|
||||||
<hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container"
|
<hamburger
|
||||||
@toggleClick="toggleSideBar"/>
|
id="hamburger-container"
|
||||||
|
:is-active="sidebar.opened"
|
||||||
|
class="hamburger-container"
|
||||||
|
@toggleClick="toggleSideBar"
|
||||||
|
/>
|
||||||
|
|
||||||
<breadcrumb id="breadcrumb-container" class="breadcrumb-container" />
|
<breadcrumb id="breadcrumb-container" class="breadcrumb-container" />
|
||||||
|
|
||||||
@@ -16,98 +20,105 @@
|
|||||||
<lang-select class="right-menu-item hover-effect" />
|
<lang-select class="right-menu-item hover-effect" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<el-dropdown class="avatar-container right-menu-item hover-effect" trigger="click">
|
<el-dropdown
|
||||||
|
class="avatar-container right-menu-item hover-effect"
|
||||||
|
trigger="click"
|
||||||
|
>
|
||||||
<div class="avatar-wrapper">
|
<div class="avatar-wrapper">
|
||||||
<img :src="avatar+'?imageView2/1/w/80/h/80'" class="user-avatar">
|
<img :src="avatar + '?imageView2/1/w/80/h/80'" class="user-avatar" />
|
||||||
<CaretBottom style="width: .6em; height: .6em;margin-left: 5px"/>
|
<CaretBottom style="width: 0.6em; height: 0.6em; margin-left: 5px" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
<el-dropdown-menu>
|
<el-dropdown-menu>
|
||||||
<router-link to="/">
|
<router-link to="/">
|
||||||
<el-dropdown-item>{{$t('navbar.dashboard')}}</el-dropdown-item>
|
<el-dropdown-item>{{ $t("navbar.dashboard") }}</el-dropdown-item>
|
||||||
</router-link>
|
</router-link>
|
||||||
<a target="_blank" href="https://github.com/hxrui">
|
<a target="_blank" href="https://github.com/hxrui">
|
||||||
<el-dropdown-item>Github</el-dropdown-item>
|
<el-dropdown-item>Github</el-dropdown-item>
|
||||||
</a>
|
</a>
|
||||||
<a target="_blank" href="https://gitee.com/haoxr">
|
<a target="_blank" href="https://gitee.com/haoxr">
|
||||||
<el-dropdown-item>{{$t('navbar.gitee')}}</el-dropdown-item>
|
<el-dropdown-item>{{ $t("navbar.gitee") }}</el-dropdown-item>
|
||||||
</a>
|
</a>
|
||||||
<a target="_blank" href="https://www.cnblogs.com/haoxianrui/">
|
<a target="_blank" href="https://www.cnblogs.com/haoxianrui/">
|
||||||
<el-dropdown-item>{{$t('navbar.document')}}</el-dropdown-item>
|
<el-dropdown-item>{{ $t("navbar.document") }}</el-dropdown-item>
|
||||||
</a>
|
</a>
|
||||||
<el-dropdown-item divided @click="logout">
|
<el-dropdown-item divided @click="logout">
|
||||||
{{$t('navbar.logout')}}
|
{{ $t("navbar.logout") }}
|
||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
</el-dropdown-menu>
|
</el-dropdown-menu>
|
||||||
</template>
|
</template>
|
||||||
</el-dropdown>
|
</el-dropdown>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {computed} from "vue"
|
import { computed } from "vue";
|
||||||
import {useRoute, useRouter} from "vue-router"
|
import { useRoute, useRouter } from "vue-router";
|
||||||
import {ElMessageBox} from 'element-plus'
|
import { ElMessageBox } from "element-plus";
|
||||||
|
|
||||||
import {useAppStoreHook} from '@/store/modules/app'
|
import useStore from "@/store";
|
||||||
import {useUserStoreHook} from '@/store/modules/user'
|
|
||||||
|
|
||||||
// 组件依赖
|
// 组件依赖
|
||||||
import Breadcrumb from '@/components/Breadcrumb/index.vue'
|
import Breadcrumb from "@/components/Breadcrumb/index.vue";
|
||||||
import Hamburger from '@/components/Hamburger/index.vue'
|
import Hamburger from "@/components/Hamburger/index.vue";
|
||||||
import Screenfull from '@/components/Screenfull/index.vue'
|
import Screenfull from "@/components/Screenfull/index.vue";
|
||||||
import SizeSelect from '@/components/SizeSelect/index.vue'
|
import SizeSelect from "@/components/SizeSelect/index.vue";
|
||||||
import LangSelect from '@/components/LangSelect/index.vue'
|
import LangSelect from "@/components/LangSelect/index.vue";
|
||||||
import SvgIcon from '@/components/SvgIcon/index.vue'
|
import SvgIcon from "@/components/SvgIcon/index.vue";
|
||||||
|
|
||||||
// 图标依赖
|
// 图标依赖
|
||||||
import {CaretBottom} from '@element-plus/icons-vue'
|
import { CaretBottom } from "@element-plus/icons-vue";
|
||||||
|
|
||||||
const route = useRoute()
|
const { app, user } = useStore();
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
const sidebar = computed(() => useAppStoreHook().sidebar)
|
const route = useRoute();
|
||||||
const device = computed(() => useAppStoreHook().device)
|
const router = useRouter();
|
||||||
const avatar = computed(() => useUserStoreHook().avatar)
|
|
||||||
|
const sidebar = computed(() => app.sidebar);
|
||||||
|
const device = computed(() => app.device);
|
||||||
|
const avatar = computed(() => user.avatar);
|
||||||
|
|
||||||
function toggleSideBar() {
|
function toggleSideBar() {
|
||||||
useAppStoreHook().toggleSidebar()
|
app.toggleSidebar();
|
||||||
}
|
}
|
||||||
|
|
||||||
function logout() {
|
function logout() {
|
||||||
ElMessageBox.confirm('确定注销并退出系统吗?', '提示', {
|
ElMessageBox.confirm("确定注销并退出系统吗?", "提示", {
|
||||||
confirmButtonText: '确定',
|
confirmButtonText: "确定",
|
||||||
cancelButtonText: '取消',
|
cancelButtonText: "取消",
|
||||||
type: 'warning'
|
type: "warning",
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
useUserStoreHook().logout().then(() => {
|
user.logout().then(() => {
|
||||||
router.push(`/login?redirect=${route.fullPath}`)
|
router.push(`/login?redirect=${route.fullPath}`);
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
ul { list-style: none; margin: 0; padding: 0; }
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
.navbar {
|
.navbar {
|
||||||
height: 50px;
|
height: 50px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
box-shadow: 0 1px 4px rgba(0, 21, 41, .08);
|
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
||||||
|
|
||||||
.hamburger-container {
|
.hamburger-container {
|
||||||
line-height: 46px;
|
line-height: 46px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
float: left;
|
float: left;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background .3s;
|
transition: background 0.3s;
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: rgba(0, 0, 0, .025)
|
background: rgba(0, 0, 0, 0.025);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,10 +145,10 @@ ul { list-style: none; margin: 0; padding: 0; }
|
|||||||
|
|
||||||
&.hover-effect {
|
&.hover-effect {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background .3s;
|
transition: background 0.3s;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: rgba(0, 0, 0, .025)
|
background: rgba(0, 0, 0, 0.025);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<h3 class="drawer-title">系统布局配置</h3>
|
<h3 class="drawer-title">系统布局配置</h3>
|
||||||
<div class="drawer-item">
|
<div class="drawer-item">
|
||||||
<span>主题颜色</span>
|
<span>主题颜色</span>
|
||||||
<div style="float: right;height: 26px;margin: -3px 8px 0 0;">
|
<div style="float: right; height: 26px; margin: -3px 8px 0 0">
|
||||||
<theme-picker @change="themeChange" />
|
<theme-picker @change="themeChange" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -27,33 +27,45 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { reactive, toRefs, watch } from "vue";
|
import { reactive, toRefs, watch } from "vue";
|
||||||
import {useSettingStoreHook} from "@/store/modules/settings";
|
|
||||||
import ThemePicker from '@/components/ThemePicker/index.vue';
|
import ThemePicker from "@/components/ThemePicker/index.vue";
|
||||||
|
|
||||||
|
import useStore from "@/store";
|
||||||
|
|
||||||
|
const { setting } = useStore();
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
fixedHeader: useSettingStoreHook().fixedHeader,
|
fixedHeader: setting.fixedHeader,
|
||||||
tagsView: useSettingStoreHook().tagsView,
|
tagsView: setting.tagsView,
|
||||||
sidebarLogo: useSettingStoreHook().sidebarLogo
|
sidebarLogo: setting.sidebarLogo,
|
||||||
})
|
});
|
||||||
|
|
||||||
const {fixedHeader, tagsView, sidebarLogo} = toRefs(state)
|
const { fixedHeader, tagsView, sidebarLogo } = toRefs(state);
|
||||||
|
|
||||||
function themeChange(val: any) {
|
function themeChange(val: any) {
|
||||||
useSettingStoreHook().changeSetting({key: 'theme', value: val})
|
setting.changeSetting({ key: "theme", value: val });
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(() => state.fixedHeader, (value) => {
|
watch(
|
||||||
useSettingStoreHook().changeSetting({key: 'fixedHeader', value: value})
|
() => state.fixedHeader,
|
||||||
})
|
(value) => {
|
||||||
|
setting.changeSetting({ key: "fixedHeader", value: value });
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
watch(() => state.tagsView, (value) => {
|
watch(
|
||||||
useSettingStoreHook().changeSetting({key: 'tagsView', value: value})
|
() => state.tagsView,
|
||||||
})
|
(value) => {
|
||||||
|
setting.changeSetting({ key: "tagsView", value: value });
|
||||||
watch(() => state.sidebarLogo, (value) => {
|
}
|
||||||
useSettingStoreHook().changeSetting({key: 'sidebarLogo', value: value})
|
);
|
||||||
})
|
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => state.sidebarLogo,
|
||||||
|
(value) => {
|
||||||
|
setting.changeSetting({ key: "sidebarLogo", value: value });
|
||||||
|
}
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -65,19 +77,19 @@ watch(() => state.sidebarLogo, (value) => {
|
|||||||
|
|
||||||
.drawer-title {
|
.drawer-title {
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
color: rgba(0, 0, 0, .85);
|
color: rgba(0, 0, 0, 0.85);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 22px;
|
line-height: 22px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.drawer-item {
|
.drawer-item {
|
||||||
color: rgba(0, 0, 0, .65);
|
color: rgba(0, 0, 0, 0.65);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
padding: 12px 0;
|
padding: 12px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.drawer-switch {
|
.drawer-switch {
|
||||||
float: right
|
float: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.job-link {
|
.job-link {
|
||||||
|
|||||||
@@ -19,10 +19,13 @@
|
|||||||
import {computed, defineComponent} from 'vue'
|
import {computed, defineComponent} from 'vue'
|
||||||
import { isExternal } from '@/utils/validate'
|
import { isExternal } from '@/utils/validate'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import {useAppStoreHook} from "@/store/modules/app";
|
|
||||||
|
|
||||||
const sidebar = computed(() => useAppStoreHook().sidebar);
|
import useStore from "@/store";
|
||||||
const device = computed(() => useAppStoreHook().device);
|
|
||||||
|
const {app}=useStore()
|
||||||
|
|
||||||
|
const sidebar = computed(() => app.sidebar);
|
||||||
|
const device = computed(() => app.device);
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
@@ -35,7 +38,7 @@ export default defineComponent({
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const push = () => {
|
const push = () => {
|
||||||
if (device.value === 'mobile' && sidebar.value.opened == true) {
|
if (device.value === 'mobile' && sidebar.value.opened == true) {
|
||||||
useAppStoreHook().closeSideBar(false)
|
app.closeSideBar(false)
|
||||||
}
|
}
|
||||||
router.push(props.to).catch((err) => {
|
router.push(props.to).catch((err) => {
|
||||||
console.log(err)
|
console.log(err)
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ import {isExternal} from '@/utils/validate'
|
|||||||
import AppLink from './Link.vue'
|
import AppLink from './Link.vue'
|
||||||
import {RouteRecordRaw} from "vue-router";
|
import {RouteRecordRaw} from "vue-router";
|
||||||
|
|
||||||
import SvgIcon from '@/components/SvgIcon/index.vue';
|
|
||||||
import { generateTitle } from '@/utils/i18n'
|
import { generateTitle } from '@/utils/i18n'
|
||||||
|
import SvgIcon from '@/components/SvgIcon/index.vue';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
item: {
|
item: {
|
||||||
|
|||||||
@@ -24,20 +24,22 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
|
||||||
import {computed, defineComponent} from "vue";
|
import {computed, defineComponent} from "vue";
|
||||||
|
import {useRoute} from 'vue-router'
|
||||||
|
|
||||||
import SidebarItem from './SidebarItem.vue'
|
import SidebarItem from './SidebarItem.vue'
|
||||||
import Logo from './Logo.vue'
|
import Logo from './Logo.vue'
|
||||||
import variables from '@/styles/variables.module.scss'
|
import variables from '@/styles/variables.module.scss'
|
||||||
import { useSettingStoreHook } from "@/store/modules/settings";
|
import useStore from "@/store";
|
||||||
import { useAppStoreHook } from "@/store/modules/app";
|
|
||||||
import { usePermissionStoreHook } from "@/store/modules/permission";
|
|
||||||
import {useRoute} from 'vue-router'
|
|
||||||
|
|
||||||
|
const {permission,setting,app} =useStore();
|
||||||
|
|
||||||
const route =useRoute()
|
const route =useRoute()
|
||||||
const routes =computed(() => usePermissionStoreHook().routes)
|
const routes =computed(() => permission.routes)
|
||||||
const showLogo = computed(() => useSettingStoreHook().sidebarLogo)
|
const showLogo = computed(() => setting.sidebarLogo)
|
||||||
const isCollapse = computed(() => !useAppStoreHook().sidebar.opened)
|
const isCollapse = computed(() => !app.sidebar.opened)
|
||||||
|
|
||||||
const activeMenu = computed(() => {
|
const activeMenu = computed(() => {
|
||||||
const {meta, path} = route
|
const {meta, path} = route
|
||||||
|
|||||||
@@ -3,93 +3,117 @@
|
|||||||
ref="scrollContainerRef"
|
ref="scrollContainerRef"
|
||||||
:vertical="false"
|
:vertical="false"
|
||||||
class="scroll-container"
|
class="scroll-container"
|
||||||
@wheel.prevent="handleScroll">
|
@wheel.prevent="handleScroll"
|
||||||
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {ref, computed, onMounted, onBeforeUnmount, getCurrentInstance} from "vue";
|
import {
|
||||||
import {useTagsViewStoreHook} from "@/store/modules/tagsView"
|
ref,
|
||||||
|
computed,
|
||||||
|
onMounted,
|
||||||
|
onBeforeUnmount,
|
||||||
|
getCurrentInstance,
|
||||||
|
} from "vue";
|
||||||
import { TagView } from "@/store/interface";
|
import { TagView } from "@/store/interface";
|
||||||
const emits = defineEmits()
|
import useStore from "@/store";
|
||||||
|
|
||||||
const tagAndTagSpacing = ref(4)
|
const emits = defineEmits();
|
||||||
const scrollContainerRef = ref(null)
|
|
||||||
const visitedViews = computed(() => useTagsViewStoreHook().visitedViews)
|
const tagAndTagSpacing = ref(4);
|
||||||
|
const scrollContainerRef = ref(null);
|
||||||
|
|
||||||
|
const { tagsView } = useStore();
|
||||||
|
|
||||||
|
const visitedViews = computed(() => tagsView.visitedViews);
|
||||||
|
|
||||||
const emitScroll = () => {
|
const emitScroll = () => {
|
||||||
(emits as any)('scroll')
|
(emits as any)("scroll");
|
||||||
}
|
};
|
||||||
|
|
||||||
const {ctx} = getCurrentInstance() as any
|
const { ctx } = getCurrentInstance() as any;
|
||||||
const scrollWrapper = computed(() => {
|
const scrollWrapper = computed(() => {
|
||||||
return (scrollContainerRef.value as any).$refs.wrap as HTMLElement
|
return (scrollContainerRef.value as any).$refs.wrap as HTMLElement;
|
||||||
})
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
//scrollWrapper.value.addEventListener('scroll', emitScroll, true);
|
//scrollWrapper.value.addEventListener('scroll', emitScroll, true);
|
||||||
})
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
// scrollWrapper.value.removeEventListener('scroll', emitScroll);
|
// scrollWrapper.value.removeEventListener('scroll', emitScroll);
|
||||||
})
|
});
|
||||||
|
|
||||||
function handleScroll(e: WheelEvent) {
|
function handleScroll(e: WheelEvent) {
|
||||||
const eventDelta = (e as any).wheelDelta || -e.deltaY * 40
|
const eventDelta = (e as any).wheelDelta || -e.deltaY * 40;
|
||||||
scrollWrapper.value.scrollLeft = scrollWrapper.value.scrollLeft + eventDelta / 4
|
scrollWrapper.value.scrollLeft =
|
||||||
|
scrollWrapper.value.scrollLeft + eventDelta / 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
function moveToTarget(currentTag: TagView) {
|
function moveToTarget(currentTag: TagView) {
|
||||||
const $container = ctx.$refs.scrollContainer.$el
|
const $container = ctx.$refs.scrollContainer.$el;
|
||||||
const $containerWidth = $container.offsetWidth
|
const $containerWidth = $container.offsetWidth;
|
||||||
const $scrollWrapper = scrollWrapper.value;
|
const $scrollWrapper = scrollWrapper.value;
|
||||||
|
|
||||||
let firstTag = null
|
let firstTag = null;
|
||||||
let lastTag = null
|
let lastTag = null;
|
||||||
|
|
||||||
// find first tag and last tag
|
// find first tag and last tag
|
||||||
if (visitedViews.value.length > 0) {
|
if (visitedViews.value.length > 0) {
|
||||||
firstTag = visitedViews.value[0]
|
firstTag = visitedViews.value[0];
|
||||||
lastTag = visitedViews.value[visitedViews.value.length - 1]
|
lastTag = visitedViews.value[visitedViews.value.length - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (firstTag === currentTag) {
|
if (firstTag === currentTag) {
|
||||||
$scrollWrapper.scrollLeft = 0
|
$scrollWrapper.scrollLeft = 0;
|
||||||
} else if (lastTag === currentTag) {
|
} else if (lastTag === currentTag) {
|
||||||
$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(item => item === currentTag)
|
const currentIndex = visitedViews.value.findIndex(
|
||||||
let prevTag = null
|
(item) => item === currentTag
|
||||||
let nextTag = null
|
);
|
||||||
|
let prevTag = null;
|
||||||
|
let nextTag = null;
|
||||||
for (const k in tagListDom) {
|
for (const k in tagListDom) {
|
||||||
if (k !== 'length' && Object.hasOwnProperty.call(tagListDom, k)) {
|
if (k !== "length" && Object.hasOwnProperty.call(tagListDom, k)) {
|
||||||
if ((tagListDom[k] as any).dataset.path === visitedViews.value[currentIndex - 1].path) {
|
if (
|
||||||
|
(tagListDom[k] as any).dataset.path ===
|
||||||
|
visitedViews.value[currentIndex - 1].path
|
||||||
|
) {
|
||||||
prevTag = tagListDom[k];
|
prevTag = tagListDom[k];
|
||||||
}
|
}
|
||||||
if ((tagListDom[k] as any).dataset.path === visitedViews.value[currentIndex + 1].path) {
|
if (
|
||||||
|
(tagListDom[k] as any).dataset.path ===
|
||||||
|
visitedViews.value[currentIndex + 1].path
|
||||||
|
) {
|
||||||
nextTag = tagListDom[k];
|
nextTag = tagListDom[k];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// the tag's offsetLeft after of nextTag
|
// the tag's offsetLeft after of nextTag
|
||||||
const afterNextTagOffsetLeft = (nextTag as any).offsetLeft + (nextTag as any).offsetWidth + tagAndTagSpacing.value
|
const afterNextTagOffsetLeft =
|
||||||
|
(nextTag as any).offsetLeft +
|
||||||
|
(nextTag as any).offsetWidth +
|
||||||
|
tagAndTagSpacing.value;
|
||||||
|
|
||||||
// the tag's offsetLeft before of prevTag
|
// the tag's offsetLeft before of prevTag
|
||||||
const beforePrevTagOffsetLeft = (prevTag as any).offsetLeft - tagAndTagSpacing.value
|
const beforePrevTagOffsetLeft =
|
||||||
|
(prevTag as any).offsetLeft - tagAndTagSpacing.value;
|
||||||
if (afterNextTagOffsetLeft > $scrollWrapper.scrollLeft + $containerWidth) {
|
if (afterNextTagOffsetLeft > $scrollWrapper.scrollLeft + $containerWidth) {
|
||||||
$scrollWrapper.scrollLeft = afterNextTagOffsetLeft - $containerWidth
|
$scrollWrapper.scrollLeft = afterNextTagOffsetLeft - $containerWidth;
|
||||||
} else if (beforePrevTagOffsetLeft < $scrollWrapper.scrollLeft) {
|
} else if (beforePrevTagOffsetLeft < $scrollWrapper.scrollLeft) {
|
||||||
$scrollWrapper.scrollLeft = beforePrevTagOffsetLeft
|
$scrollWrapper.scrollLeft = beforePrevTagOffsetLeft;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
moveToTarget
|
moveToTarget,
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="tags-view-container" class="tags-view-container">
|
<div id="tags-view-container" class="tags-view-container">
|
||||||
<scroll-pane ref="scrollPaneRef" class="tags-view-wrapper" @scroll="handleScroll">
|
<scroll-pane
|
||||||
|
ref="scrollPaneRef"
|
||||||
|
class="tags-view-wrapper"
|
||||||
|
@scroll="handleScroll"
|
||||||
|
>
|
||||||
<router-link
|
<router-link
|
||||||
v-for="tag in visitedViews"
|
v-for="tag in visitedViews"
|
||||||
:key="tag.path"
|
:key="tag.path"
|
||||||
@@ -11,34 +15,45 @@
|
|||||||
@contextmenu.prevent="openMenu(tag, $event)"
|
@contextmenu.prevent="openMenu(tag, $event)"
|
||||||
>
|
>
|
||||||
{{ generateTitle(tag.meta.title) }}
|
{{ generateTitle(tag.meta.title) }}
|
||||||
<span v-if="!isAffix(tag)" class="el-icon-close" @click.prevent.stop="closeSelectedTag(tag)">
|
<span
|
||||||
<close class="el-icon-close" style="width: 1em; height: 1em;vertical-align: middle;"/>
|
v-if="!isAffix(tag)"
|
||||||
|
class="el-icon-close"
|
||||||
|
@click.prevent.stop="closeSelectedTag(tag)"
|
||||||
|
>
|
||||||
|
<close
|
||||||
|
class="el-icon-close"
|
||||||
|
style="width: 1em; height: 1em; vertical-align: middle"
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</scroll-pane>
|
</scroll-pane>
|
||||||
<ul v-show="visible" :style="{left:left+'px',top:top+'px'}" class="contextmenu">
|
<ul
|
||||||
|
v-show="visible"
|
||||||
|
:style="{ left: left + 'px', top: top + 'px' }"
|
||||||
|
class="contextmenu"
|
||||||
|
>
|
||||||
<li @click="refreshSelectedTag(selectedTag)">
|
<li @click="refreshSelectedTag(selectedTag)">
|
||||||
<refresh-right style="width: 1em; height: 1em;"/>
|
<refresh-right style="width: 1em; height: 1em" />
|
||||||
刷新
|
刷新
|
||||||
</li>
|
</li>
|
||||||
<li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">
|
<li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">
|
||||||
<close style="width: 1em; height: 1em;"/>
|
<close style="width: 1em; height: 1em" />
|
||||||
关闭
|
关闭
|
||||||
</li>
|
</li>
|
||||||
<li @click="closeOtherTags">
|
<li @click="closeOtherTags">
|
||||||
<circle-close style="width: 1em; height: 1em;"/>
|
<circle-close style="width: 1em; height: 1em" />
|
||||||
关闭其它
|
关闭其它
|
||||||
</li>
|
</li>
|
||||||
<li v-if="!isFirstView()" @click="closeLeftTags">
|
<li v-if="!isFirstView()" @click="closeLeftTags">
|
||||||
<back style="width: 1em; height: 1em;"/>
|
<back style="width: 1em; height: 1em" />
|
||||||
关闭左侧
|
关闭左侧
|
||||||
</li>
|
</li>
|
||||||
<li v-if="!isLastView()" @click="closeRightTags">
|
<li v-if="!isLastView()" @click="closeRightTags">
|
||||||
<right style="width: 1em; height: 1em;"/>
|
<right style="width: 1em; height: 1em" />
|
||||||
关闭右侧
|
关闭右侧
|
||||||
</li>
|
</li>
|
||||||
<li @click="closeAllTags(selectedTag)">
|
<li @click="closeAllTags(selectedTag)">
|
||||||
<circle-close style="width: 1em; height: 1em;"/>
|
<circle-close style="width: 1em; height: 1em" />
|
||||||
关闭所有
|
关闭所有
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -46,31 +61,39 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts" >
|
<script setup lang="ts" >
|
||||||
|
|
||||||
import {useTagsViewStoreHook} from '@/store/modules/tagsView'
|
|
||||||
import {usePermissionStoreHook} from '@/store/modules/permission'
|
|
||||||
import path from 'path-browserify'
|
|
||||||
import {
|
import {
|
||||||
computed,
|
computed,
|
||||||
getCurrentInstance,
|
getCurrentInstance,
|
||||||
nextTick,
|
nextTick,
|
||||||
ref,
|
ref,
|
||||||
watch,
|
watch,
|
||||||
onMounted
|
onMounted,
|
||||||
} from "vue";
|
} from "vue";
|
||||||
import {RouteRecordRaw, useRoute, useRouter} from 'vue-router'
|
|
||||||
|
import path from "path-browserify";
|
||||||
|
|
||||||
|
import { RouteRecordRaw, useRoute, useRouter } from "vue-router";
|
||||||
import { TagView } from "@/store/interface";
|
import { TagView } from "@/store/interface";
|
||||||
|
|
||||||
import ScrollPane from './ScrollPane.vue'
|
import ScrollPane from "./ScrollPane.vue";
|
||||||
import {Close,RefreshRight,CircleClose,Back,Right} from '@element-plus/icons-vue'
|
import {
|
||||||
import { generateTitle } from '@/utils/i18n'
|
Close,
|
||||||
|
RefreshRight,
|
||||||
|
CircleClose,
|
||||||
|
Back,
|
||||||
|
Right,
|
||||||
|
} from "@element-plus/icons-vue";
|
||||||
|
import { generateTitle } from "@/utils/i18n";
|
||||||
|
import useStore from "@/store";
|
||||||
|
|
||||||
const {ctx} = getCurrentInstance() as any
|
const { tagsView, permission } = useStore();
|
||||||
const router = useRouter()
|
|
||||||
|
const { ctx } = getCurrentInstance() as any;
|
||||||
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
const visitedViews = computed<any>(() => useTagsViewStoreHook().visitedViews)
|
const visitedViews = computed<any>(() => tagsView.visitedViews);
|
||||||
const routes = computed<any>(() => usePermissionStoreHook().routes)
|
const routes = computed<any>(() => permission.routes);
|
||||||
|
|
||||||
const affixTags = ref([]);
|
const affixTags = ref([]);
|
||||||
const visible = ref(false);
|
const visible = ref(false);
|
||||||
@@ -80,197 +103,207 @@ const left = ref(0);
|
|||||||
const top = ref(0);
|
const top = ref(0);
|
||||||
|
|
||||||
watch(route, () => {
|
watch(route, () => {
|
||||||
addTags()
|
addTags();
|
||||||
moveToCurrentTag()
|
moveToCurrentTag();
|
||||||
})
|
});
|
||||||
|
|
||||||
watch(visible, (value) => {
|
watch(visible, (value) => {
|
||||||
if (value) {
|
if (value) {
|
||||||
document.body.addEventListener('click', closeMenu)
|
document.body.addEventListener("click", closeMenu);
|
||||||
} else {
|
} else {
|
||||||
document.body.removeEventListener('click', closeMenu)
|
document.body.removeEventListener("click", closeMenu);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
function filterAffixTags(routes: RouteRecordRaw[], basePath = '/') {
|
function filterAffixTags(routes: RouteRecordRaw[], basePath = "/") {
|
||||||
let tags: TagView[] = []
|
let tags: TagView[] = [];
|
||||||
|
|
||||||
routes.forEach(route => {
|
routes.forEach((route) => {
|
||||||
if (route.meta && route.meta.affix) {
|
if (route.meta && route.meta.affix) {
|
||||||
const tagPath = path.resolve(basePath, route.path)
|
const tagPath = path.resolve(basePath, route.path);
|
||||||
tags.push({
|
tags.push({
|
||||||
fullPath: tagPath,
|
fullPath: tagPath,
|
||||||
path: tagPath,
|
path: tagPath,
|
||||||
name: route.name,
|
name: route.name,
|
||||||
meta: {...route.meta}
|
meta: { ...route.meta },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (route.children) {
|
if (route.children) {
|
||||||
const childTags = filterAffixTags(route.children, route.path)
|
const childTags = filterAffixTags(route.children, route.path);
|
||||||
if (childTags.length >= 1) {
|
if (childTags.length >= 1) {
|
||||||
tags = tags.concat(childTags)
|
tags = tags.concat(childTags);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
return tags
|
return tags;
|
||||||
}
|
}
|
||||||
|
|
||||||
function initTags() {
|
function initTags() {
|
||||||
const res = filterAffixTags(routes.value) as []
|
const res = filterAffixTags(routes.value) as [];
|
||||||
affixTags.value = res
|
affixTags.value = res;
|
||||||
for (const tag of res) {
|
for (const tag of res) {
|
||||||
// Must have tag name
|
// Must have tag name
|
||||||
if ((tag as TagView).name) {
|
if ((tag as TagView).name) {
|
||||||
useTagsViewStoreHook().addVisitedView(tag)
|
tagsView.addVisitedView(tag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function addTags() {
|
function addTags() {
|
||||||
if (route.name) {
|
if (route.name) {
|
||||||
useTagsViewStoreHook().addView(route)
|
tagsView.addView(route);
|
||||||
}
|
}
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function moveToCurrentTag() {
|
function moveToCurrentTag() {
|
||||||
const tags = getCurrentInstance()?.refs.tag as any[]
|
const tags = getCurrentInstance()?.refs.tag as any[];
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
if (tags === null || tags === undefined || !Array.isArray(tags)) {
|
if (tags === null || tags === undefined || !Array.isArray(tags)) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
for (const tag of tags) {
|
for (const tag of tags) {
|
||||||
if ((tag.to as TagView).path === route.path) {
|
if ((tag.to as TagView).path === route.path) {
|
||||||
(scrollPaneRef.value as any).value.moveToTarget(tag)
|
(scrollPaneRef.value as any).value.moveToTarget(tag);
|
||||||
// when query is different then update
|
// when query is different then update
|
||||||
if ((tag.to as TagView).fullPath !== route.fullPath) {
|
if ((tag.to as TagView).fullPath !== route.fullPath) {
|
||||||
useTagsViewStoreHook().updateVisitedView(route)
|
tagsView.updateVisitedView(route);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function isActive(tag: TagView) {
|
function isActive(tag: TagView) {
|
||||||
return tag.path === route.path
|
return tag.path === route.path;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isAffix(tag: TagView) {
|
function isAffix(tag: TagView) {
|
||||||
return tag.meta && tag.meta.affix
|
return tag.meta && tag.meta.affix;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isFirstView() {
|
function isFirstView() {
|
||||||
try {
|
try {
|
||||||
return (selectedTag.value as TagView).fullPath === visitedViews.value[1].fullPath || (selectedTag.value as TagView).fullPath === '/index'
|
return (
|
||||||
|
(selectedTag.value as TagView).fullPath ===
|
||||||
|
visitedViews.value[1].fullPath ||
|
||||||
|
(selectedTag.value as TagView).fullPath === "/index"
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isLastView() {
|
function isLastView() {
|
||||||
try {
|
try {
|
||||||
return (selectedTag.value as TagView).fullPath === visitedViews.value[visitedViews.value.length - 1].fullPath
|
return (
|
||||||
|
(selectedTag.value as TagView).fullPath ===
|
||||||
|
visitedViews.value[visitedViews.value.length - 1].fullPath
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshSelectedTag(view: TagView) {
|
function refreshSelectedTag(view: TagView) {
|
||||||
useTagsViewStoreHook().delCachedView(view)
|
tagsView.delCachedView(view);
|
||||||
const {fullPath} = view
|
const { fullPath } = view;
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
router.replace({path: '/redirect' + fullPath}).catch(err => {
|
router.replace({ path: "/redirect" + fullPath }).catch((err) => {
|
||||||
console.warn(err)
|
console.warn(err);
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function toLastView(visitedViews: TagView[], view?: any) {
|
function toLastView(visitedViews: TagView[], view?: any) {
|
||||||
const latestView = visitedViews.slice(-1)[0]
|
const latestView = visitedViews.slice(-1)[0];
|
||||||
if (latestView && latestView.fullPath) {
|
if (latestView && latestView.fullPath) {
|
||||||
router.push(latestView.fullPath)
|
router.push(latestView.fullPath);
|
||||||
} else {
|
} else {
|
||||||
// now the default is to redirect to the home page if there is no tags-view,
|
// now the default is to redirect to the home page if there is no tags-view,
|
||||||
// you can adjust it according to your needs.
|
// you can adjust it according to your needs.
|
||||||
if (view.name === 'Dashboard') {
|
if (view.name === "Dashboard") {
|
||||||
// to reload home page
|
// to reload home page
|
||||||
router.replace({path: '/redirect' + view.fullPath})
|
router.replace({ path: "/redirect" + view.fullPath });
|
||||||
} else {
|
} else {
|
||||||
router.push('/')
|
router.push("/");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeSelectedTag(view: TagView) {
|
function closeSelectedTag(view: TagView) {
|
||||||
useTagsViewStoreHook().delView(view).then((res: any) => {
|
tagsView.delView(view).then((res: any) => {
|
||||||
if (isActive(view)) {
|
if (isActive(view)) {
|
||||||
toLastView(res.visitedViews, view)
|
toLastView(res.visitedViews, view);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeLeftTags() {
|
function closeLeftTags() {
|
||||||
useTagsViewStoreHook().delLeftViews(selectedTag.value).then((res: any) => {
|
tagsView.delLeftViews(selectedTag.value).then((res: any) => {
|
||||||
if (!res.visitedViews.find((item: any) => item.fullPath === route.fullPath)) {
|
if (
|
||||||
toLastView(res.visitedViews)
|
!res.visitedViews.find((item: any) => item.fullPath === route.fullPath)
|
||||||
|
) {
|
||||||
|
toLastView(res.visitedViews);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
function closeRightTags() {
|
function closeRightTags() {
|
||||||
useTagsViewStoreHook().delRightViews(selectedTag.value).then((res:any) => {
|
tagsView.delRightViews(selectedTag.value).then((res: any) => {
|
||||||
if (!res.visitedViews.find((item:any) => item.fullPath === route.fullPath)) {
|
if (
|
||||||
toLastView(res.visitedViews)
|
!res.visitedViews.find((item: any) => item.fullPath === route.fullPath)
|
||||||
|
) {
|
||||||
|
toLastView(res.visitedViews);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeOtherTags() {
|
function closeOtherTags() {
|
||||||
useTagsViewStoreHook().delOtherViews(selectedTag.value).then(() => {
|
tagsView.delOtherViews(selectedTag.value).then(() => {
|
||||||
moveToCurrentTag()
|
moveToCurrentTag();
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeAllTags(view: TagView) {
|
function closeAllTags(view: TagView) {
|
||||||
useTagsViewStoreHook().delRightViews(selectedTag.value).then((res:any) => {
|
tagsView.delRightViews(selectedTag.value).then((res: any) => {
|
||||||
if (affixTags.value.some((tag: any) => tag.path === route.path)) {
|
if (affixTags.value.some((tag: any) => tag.path === route.path)) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
toLastView(res.visitedViews, view)
|
toLastView(res.visitedViews, view);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function openMenu(tag: TagView, e: MouseEvent) {
|
function openMenu(tag: TagView, e: MouseEvent) {
|
||||||
const menuMinWidth = 105
|
const menuMinWidth = 105;
|
||||||
const offsetLeft = ctx.$el.getBoundingClientRect().left // container margin left
|
const offsetLeft = ctx.$el.getBoundingClientRect().left; // container margin left
|
||||||
const offsetWidth = ctx.$el.offsetWidth // container width
|
const offsetWidth = ctx.$el.offsetWidth; // container width
|
||||||
const maxLeft = offsetWidth - menuMinWidth // left boundary
|
const maxLeft = offsetWidth - menuMinWidth; // left boundary
|
||||||
const l = e.clientX - offsetLeft + 15 // 15: margin right
|
const l = e.clientX - offsetLeft + 15; // 15: margin right
|
||||||
|
|
||||||
if (l > maxLeft) {
|
if (l > maxLeft) {
|
||||||
left.value = maxLeft
|
left.value = maxLeft;
|
||||||
} else {
|
} else {
|
||||||
left.value = l
|
left.value = l;
|
||||||
}
|
}
|
||||||
|
|
||||||
top.value = e.clientY
|
top.value = e.clientY;
|
||||||
visible.value = true
|
visible.value = true;
|
||||||
selectedTag.value = tag
|
selectedTag.value = tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeMenu() {
|
function closeMenu() {
|
||||||
visible.value = false
|
visible.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleScroll() {
|
function handleScroll() {
|
||||||
closeMenu()
|
closeMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
initTags()
|
initTags();
|
||||||
addTags()
|
addTags();
|
||||||
})
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang='scss' scoped>
|
<style lang='scss' scoped>
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div :class="classObj" class="app-wrapper">
|
<div :class="classObj" class="app-wrapper">
|
||||||
<div v-if="device==='mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside"/>
|
<div
|
||||||
|
v-if="device === 'mobile' && sidebar.opened"
|
||||||
|
class="drawer-bg"
|
||||||
|
@click="handleClickOutside"
|
||||||
|
/>
|
||||||
<sidebar class="sidebar-container" />
|
<sidebar class="sidebar-container" />
|
||||||
<div :class="{ hasTagsView: needTagsView }" class="main-container">
|
<div :class="{ hasTagsView: needTagsView }" class="main-container">
|
||||||
<div :class="{ 'fixed-header': fixedHeader }">
|
<div :class="{ 'fixed-header': fixedHeader }">
|
||||||
@@ -16,42 +20,43 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {computed, watchEffect} from "vue"
|
import { computed, watchEffect } from "vue";
|
||||||
import {useWindowSize} from '@vueuse/core'
|
import { useWindowSize } from "@vueuse/core";
|
||||||
import {AppMain, Navbar, Settings, TagsView} from './components/index'
|
import { AppMain, Navbar, Settings, TagsView } from "./components/index";
|
||||||
import Sidebar from './components/Sidebar/index.vue'
|
import Sidebar from "./components/Sidebar/index.vue";
|
||||||
import RightPanel from '@/components/RightPanel/index.vue'
|
import RightPanel from "@/components/RightPanel/index.vue";
|
||||||
|
|
||||||
import {useAppStoreHook} from "@/store/modules/app"
|
import useStore from "@/store";
|
||||||
import {useSettingStoreHook} from "@/store/modules/settings"
|
|
||||||
|
|
||||||
const { width, height } = useWindowSize();
|
const { width, height } = useWindowSize();
|
||||||
const WIDTH = 992
|
const WIDTH = 992;
|
||||||
|
|
||||||
const sidebar = computed(() => useAppStoreHook().sidebar);
|
const { app, setting } = useStore();
|
||||||
const device = computed(() => useAppStoreHook().device);
|
|
||||||
const needTagsView = computed(() => useSettingStoreHook().tagsView);
|
const sidebar = computed(() => app.sidebar);
|
||||||
const fixedHeader = computed(() => useSettingStoreHook().fixedHeader);
|
const device = computed(() => app.device);
|
||||||
const showSettings = computed(() => useSettingStoreHook().showSettings);
|
const needTagsView = computed(() => setting.tagsView);
|
||||||
|
const fixedHeader = computed(() => setting.fixedHeader);
|
||||||
|
const showSettings = computed(() => setting.showSettings);
|
||||||
|
|
||||||
const classObj = computed(() => ({
|
const classObj = computed(() => ({
|
||||||
hideSidebar: !sidebar.value.opened,
|
hideSidebar: !sidebar.value.opened,
|
||||||
openSidebar: sidebar.value.opened,
|
openSidebar: sidebar.value.opened,
|
||||||
withoutAnimation: sidebar.value.withoutAnimation,
|
withoutAnimation: sidebar.value.withoutAnimation,
|
||||||
mobile: device.value === 'mobile'
|
mobile: device.value === "mobile",
|
||||||
}))
|
}));
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
if (width.value < WIDTH) {
|
if (width.value < WIDTH) {
|
||||||
useAppStoreHook().toggleDevice("mobile")
|
app.toggleDevice("mobile");
|
||||||
useAppStoreHook().closeSideBar(true)
|
app.closeSideBar(true);
|
||||||
} else {
|
} else {
|
||||||
useAppStoreHook().toggleDevice("desktop")
|
app.toggleDevice("desktop");
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
function handleClickOutside() {
|
function handleClickOutside() {
|
||||||
useAppStoreHook().closeSideBar(false)
|
app.closeSideBar(false);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -91,7 +96,7 @@ function handleClickOutside() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.hideSidebar .fixed-header {
|
.hideSidebar .fixed-header {
|
||||||
width: calc(100% - 54px)
|
width: calc(100% - 54px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mobile .fixed-header {
|
.mobile .fixed-header {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import {createApp, Directive} from 'vue'
|
|||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import router from "@/router";
|
import router from "@/router";
|
||||||
|
|
||||||
import {store} from "@/store";
|
import { createPinia } from "pinia"
|
||||||
import Pagination from '@/components/Pagination/index.vue'
|
import Pagination from '@/components/Pagination/index.vue'
|
||||||
|
|
||||||
import {localStorage} from "@/utils/storage";
|
import {localStorage} from "@/utils/storage";
|
||||||
@@ -34,7 +34,7 @@ app.config.globalProperties.$listDictsByCode = listDictsByCode
|
|||||||
|
|
||||||
// 注册全局组件
|
// 注册全局组件
|
||||||
app.component('Pagination', Pagination)
|
app.component('Pagination', Pagination)
|
||||||
.use(store)
|
.use(createPinia())
|
||||||
.use(router)
|
.use(router)
|
||||||
.use(ElementPlus, {size: localStorage.get('size') || 'default'})
|
.use(ElementPlus, {size: localStorage.get('size') || 'default'})
|
||||||
.use(i18n)
|
.use(i18n)
|
||||||
|
|||||||
@@ -1,39 +1,39 @@
|
|||||||
import router from "@/router";
|
import router from "@/router";
|
||||||
import { ElMessage } from "element-plus";
|
import { ElMessage } from "element-plus";
|
||||||
import { usePermissionStoreHook } from "@/store/modules/permission";
|
import useStore from "@/store";
|
||||||
import { useUserStoreHook } from "@/store/modules/user";
|
|
||||||
import NProgress from 'nprogress';
|
import NProgress from 'nprogress';
|
||||||
import 'nprogress/nprogress.css'
|
import 'nprogress/nprogress.css'
|
||||||
NProgress.configure({ showSpinner: false }) // 进度环显示/隐藏
|
NProgress.configure({ showSpinner: false }) // 进度环显示/隐藏
|
||||||
|
|
||||||
|
|
||||||
// 白名单
|
// 白名单
|
||||||
const whiteList = ['/login', '/auth-redirect']
|
const whiteList = ['/login', '/auth-redirect']
|
||||||
|
|
||||||
router.beforeEach(async (to, form, next) => {
|
router.beforeEach(async (to, form, next) => {
|
||||||
NProgress.start()
|
NProgress.start()
|
||||||
|
const { user, permission } = useStore()
|
||||||
const hasToken =useUserStoreHook().token
|
const hasToken = user.token
|
||||||
if (hasToken) {
|
if (hasToken) {
|
||||||
// 如果登录成功,跳转到首页
|
// 如果登录成功,跳转到首页
|
||||||
if (to.path === '/login') {
|
if (to.path === '/login') {
|
||||||
next({ path: '/' })
|
next({ path: '/' })
|
||||||
NProgress.done()
|
NProgress.done()
|
||||||
} else {
|
} else {
|
||||||
const hasGetUserInfo =useUserStoreHook().roles.length > 0
|
const hasGetUserInfo = user.roles.length > 0
|
||||||
if (hasGetUserInfo) {
|
if (hasGetUserInfo) {
|
||||||
next()
|
next()
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
await useUserStoreHook().getUserInfo()
|
await user.getUserInfo()
|
||||||
const roles =useUserStoreHook().roles
|
const roles = user.roles
|
||||||
const accessRoutes:any = await usePermissionStoreHook().generateRoutes(roles)
|
const accessRoutes: any = await permission.generateRoutes(roles)
|
||||||
accessRoutes.forEach((route: any) => {
|
accessRoutes.forEach((route: any) => {
|
||||||
router.addRoute(route)
|
router.addRoute(route)
|
||||||
})
|
})
|
||||||
next({ ...to, replace: true })
|
next({ ...to, replace: true })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// remove token and go to login page to re-login
|
// remove token and go to login page to re-login
|
||||||
await useUserStoreHook().resetToken()
|
await user.resetToken()
|
||||||
ElMessage.error(error as any || 'Has Error')
|
ElMessage.error(error as any || 'Has Error')
|
||||||
next(`/login?redirect=${to.path}`)
|
next(`/login?redirect=${to.path}`)
|
||||||
NProgress.done()
|
NProgress.done()
|
||||||
|
|||||||
@@ -1,3 +1,17 @@
|
|||||||
import { createPinia } from "pinia";
|
// 导入首页模块
|
||||||
const store = createPinia();
|
import useUserStore from './modules/user'
|
||||||
export { store };
|
import useAppStore from './modules/app'
|
||||||
|
import usePermissionStore from './modules/permission'
|
||||||
|
import useSettingStore from './modules/settings'
|
||||||
|
import useTagsViewStore from './modules/tagsView'
|
||||||
|
|
||||||
|
const useStore = () => ({
|
||||||
|
user: useUserStore(),
|
||||||
|
app: useAppStore(),
|
||||||
|
permission: usePermissionStore(),
|
||||||
|
setting: useSettingStore(),
|
||||||
|
tagsView: useTagsViewStore()
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
export default useStore
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
import {AppState} from "@/store/interface";
|
import {AppState} from "@/store/interface";
|
||||||
import {localStorage} from "@/utils/storage";
|
import {localStorage} from "@/utils/storage";
|
||||||
import {store} from "@/store";
|
|
||||||
import {defineStore} from "pinia";
|
import {defineStore} from "pinia";
|
||||||
import { getLanguage } from '@/lang/index'
|
import { getLanguage } from '@/lang/index'
|
||||||
|
|
||||||
export const useAppStore = defineStore({
|
const useAppStore = defineStore({
|
||||||
id: "app",
|
id: "app",
|
||||||
state: (): AppState => ({
|
state: (): AppState => ({
|
||||||
device: 'desktop',
|
device: 'desktop',
|
||||||
@@ -44,6 +43,4 @@ export const useAppStore = defineStore({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export function useAppStoreHook() {
|
export default useAppStore;
|
||||||
return useAppStore(store);
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,6 @@ import {RouteRecordRaw} from 'vue-router'
|
|||||||
import {constantRoutes} from '@/router'
|
import {constantRoutes} from '@/router'
|
||||||
import {listRoutes} from "@/api/system/menu";
|
import {listRoutes} from "@/api/system/menu";
|
||||||
import {defineStore} from "pinia";
|
import {defineStore} from "pinia";
|
||||||
import {store} from "@/store";
|
|
||||||
|
|
||||||
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')
|
||||||
@@ -48,7 +47,7 @@ export const filterAsyncRoutes = (routes: RouteRecordRaw[], roles: string[]) =>
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const usePermissionStore = defineStore({
|
const usePermissionStore = defineStore({
|
||||||
id: "permission",
|
id: "permission",
|
||||||
state: (): PermissionState => ({
|
state: (): PermissionState => ({
|
||||||
routes: [],
|
routes: [],
|
||||||
@@ -74,6 +73,6 @@ export const usePermissionStore = defineStore({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export function usePermissionStoreHook() {
|
|
||||||
return usePermissionStore(store);
|
|
||||||
}
|
export default usePermissionStore;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import {defineStore} from "pinia";
|
import {defineStore} from "pinia";
|
||||||
import {store} from "@/store";
|
|
||||||
import {SettingState} from "@/store/interface";
|
import {SettingState} from "@/store/interface";
|
||||||
import defaultSettings from '../../settings'
|
import defaultSettings from '../../settings'
|
||||||
import {localStorage} from "@/utils/storage";
|
import {localStorage} from "@/utils/storage";
|
||||||
@@ -43,7 +42,4 @@ export const useSettingStore = defineStore({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export function useSettingStoreHook() {
|
export default useSettingStore;
|
||||||
return useSettingStore(store);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import {store} from "@/store";
|
|
||||||
import { TagsViewState } from "@/store/interface";
|
import { TagsViewState } from "@/store/interface";
|
||||||
|
|
||||||
const useTagsViewStore = defineStore({
|
const useTagsViewStore = defineStore({
|
||||||
@@ -173,8 +172,4 @@ const useTagsViewStore = defineStore({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export default useTagsViewStore;
|
||||||
export function useTagsViewStoreHook() {
|
|
||||||
return useTagsViewStore(store);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,10 @@
|
|||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import { store } from "@/store";
|
|
||||||
import {UserState} from "@/store/interface";
|
import {UserState} from "@/store/interface";
|
||||||
import {localStorage} from "@/utils/storage";
|
import {localStorage} from "@/utils/storage";
|
||||||
import {getUserInfo, login, logout} from "@/api/login";
|
import {getUserInfo, login, logout} from "@/api/login";
|
||||||
import {resetRouter} from "@/router";
|
import {resetRouter} from "@/router";
|
||||||
|
|
||||||
const getDefaultState = () => {
|
const useUserStore = defineStore({
|
||||||
return {
|
|
||||||
token: localStorage.get('token'),
|
|
||||||
nickname: '',
|
|
||||||
avatar: '',
|
|
||||||
roles: [],
|
|
||||||
perms: []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useUserStore = defineStore({
|
|
||||||
id:"user",
|
id:"user",
|
||||||
state: ():UserState=>({
|
state: ():UserState=>({
|
||||||
token: localStorage.get('token') || '',
|
token: localStorage.get('token') || '',
|
||||||
@@ -113,7 +102,4 @@ export const useUserStore = defineStore({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export function useUserStoreHook() {
|
export default useUserStore;
|
||||||
return useUserStore(store);
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { ElMessage, ElMessageBox } from "element-plus";
|
import { ElMessage, ElMessageBox } from "element-plus";
|
||||||
import { localStorage } from "@/utils/storage";
|
import { localStorage } from "@/utils/storage";
|
||||||
import {useUserStoreHook} from "@/store/modules/user";
|
import useStore from "@/store";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 创建 axios 实例
|
// 创建 axios 实例
|
||||||
const service = axios.create({
|
const service = axios.create({
|
||||||
@@ -16,7 +18,8 @@ service.interceptors.request.use(
|
|||||||
if (!config?.headers) {
|
if (!config?.headers) {
|
||||||
throw new Error(`Expected 'config' and 'config.headers' not to be undefined`);
|
throw new Error(`Expected 'config' and 'config.headers' not to be undefined`);
|
||||||
}
|
}
|
||||||
if (useUserStoreHook().token) {
|
const { user } = useStore()
|
||||||
|
if (user.token) {
|
||||||
config.headers.Authorization = `${localStorage.get('token')}`;
|
config.headers.Authorization = `${localStorage.get('token')}`;
|
||||||
}
|
}
|
||||||
return config
|
return config
|
||||||
|
|||||||
@@ -28,9 +28,7 @@
|
|||||||
<svg-icon icon-class="peoples" class-name="card-panel-icon" />
|
<svg-icon icon-class="peoples" class-name="card-panel-icon" />
|
||||||
</div>
|
</div>
|
||||||
<div class="card-panel-description">
|
<div class="card-panel-description">
|
||||||
<div class="card-panel-text">
|
<div class="card-panel-text">访问数</div>
|
||||||
访问数
|
|
||||||
</div>
|
|
||||||
<div class="card-panel-num">1000</div>
|
<div class="card-panel-num">1000</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -42,9 +40,7 @@
|
|||||||
<svg-icon icon-class="message" class-name="card-panel-icon" />
|
<svg-icon icon-class="message" class-name="card-panel-icon" />
|
||||||
</div>
|
</div>
|
||||||
<div class="card-panel-description">
|
<div class="card-panel-description">
|
||||||
<div class="card-panel-text">
|
<div class="card-panel-text">消息数</div>
|
||||||
消息数
|
|
||||||
</div>
|
|
||||||
<div class="card-panel-num">1000</div>
|
<div class="card-panel-num">1000</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -56,9 +52,7 @@
|
|||||||
<svg-icon icon-class="money" class-name="card-panel-icon" />
|
<svg-icon icon-class="money" class-name="card-panel-icon" />
|
||||||
</div>
|
</div>
|
||||||
<div class="card-panel-description">
|
<div class="card-panel-description">
|
||||||
<div class="card-panel-text">
|
<div class="card-panel-text">支付金额</div>
|
||||||
支付金额
|
|
||||||
</div>
|
|
||||||
<div class="card-panel-num">1000</div>
|
<div class="card-panel-num">1000</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -69,9 +63,7 @@
|
|||||||
<svg-icon icon-class="shopping" class-name="card-panel-icon" />
|
<svg-icon icon-class="shopping" class-name="card-panel-icon" />
|
||||||
</div>
|
</div>
|
||||||
<div class="card-panel-description">
|
<div class="card-panel-description">
|
||||||
<div class="card-panel-text">
|
<div class="card-panel-text">订单数</div>
|
||||||
订单数
|
|
||||||
</div>
|
|
||||||
<div class="card-panel-num">1000</div>
|
<div class="card-panel-num">1000</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -80,7 +72,6 @@
|
|||||||
|
|
||||||
<!-- 项目 + 团队成员介绍 -->
|
<!-- 项目 + 团队成员介绍 -->
|
||||||
<el-row :gutter="40">
|
<el-row :gutter="40">
|
||||||
|
|
||||||
<!-- 项目介绍 -->
|
<!-- 项目介绍 -->
|
||||||
<el-col :md="12" :lg="12" class="card-panel-col">
|
<el-col :md="12" :lg="12" class="card-panel-col">
|
||||||
<Project />
|
<Project />
|
||||||
@@ -92,35 +83,54 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
|
|
||||||
<!-- Echarts 图表 -->
|
<!-- Echarts 图表 -->
|
||||||
<el-row :gutter="40" style="margin-top: 20px">
|
<el-row :gutter="40" style="margin-top: 20px">
|
||||||
<el-col :sm="24" :lg="8" class="card-panel-col">
|
<el-col :sm="24" :lg="8" class="card-panel-col">
|
||||||
<BarChart id="barChart" height="400px" width="100%" class="chart-container"/>
|
<BarChart
|
||||||
|
id="barChart"
|
||||||
|
height="400px"
|
||||||
|
width="100%"
|
||||||
|
class="chart-container"
|
||||||
|
/>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
|
||||||
<el-col :xs="24" :sm="12" :lg="8" class="card-panel-col">
|
<el-col :xs="24" :sm="12" :lg="8" class="card-panel-col">
|
||||||
<PieChart id="pieChart" height="400px" width="100%" class="chart-container"/>
|
<PieChart
|
||||||
|
id="pieChart"
|
||||||
|
height="400px"
|
||||||
|
width="100%"
|
||||||
|
class="chart-container"
|
||||||
|
/>
|
||||||
<!--订单漏斗图-->
|
<!--订单漏斗图-->
|
||||||
<!--<FunnelChart id="funnelChart" height="400px" width="100%" class="chart-container"/>-->
|
<!--<FunnelChart id="funnelChart" height="400px" width="100%" class="chart-container"/>-->
|
||||||
</el-col>
|
</el-col>
|
||||||
|
|
||||||
<el-col :xs="24" :sm="12" :lg="8" class="card-panel-col">
|
<el-col :xs="24" :sm="12" :lg="8" class="card-panel-col">
|
||||||
<RadarChart id="radarChart" height="400px" width="100%" class="chart-container"/>
|
<RadarChart
|
||||||
|
id="radarChart"
|
||||||
|
height="400px"
|
||||||
|
width="100%"
|
||||||
|
class="chart-container"
|
||||||
|
/>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
||||||
// Vue引用
|
// Vue引用
|
||||||
import {computed, nextTick, onMounted, reactive, toRefs, watchEffect} from "vue";
|
import {
|
||||||
|
computed,
|
||||||
|
nextTick,
|
||||||
|
onMounted,
|
||||||
|
reactive,
|
||||||
|
toRefs,
|
||||||
|
watchEffect,
|
||||||
|
} from "vue";
|
||||||
|
|
||||||
// 组件引用
|
// 组件引用
|
||||||
import GithubCorner from '@/components/GithubCorner/index.vue'
|
import GithubCorner from "@/components/GithubCorner/index.vue";
|
||||||
import SvgIcon from '@/components/SvgIcon/index.vue'
|
import SvgIcon from "@/components/SvgIcon/index.vue";
|
||||||
import BarChart from "./components/Chart/BarChart.vue";
|
import BarChart from "./components/Chart/BarChart.vue";
|
||||||
import PieChart from "./components/Chart/PieChart.vue";
|
import PieChart from "./components/Chart/PieChart.vue";
|
||||||
import RadarChart from "./components/Chart/RadarChart.vue";
|
import RadarChart from "./components/Chart/RadarChart.vue";
|
||||||
@@ -129,20 +139,19 @@ import FunnelChart from "./components/Chart/FunnelChart.vue";
|
|||||||
import Project from "./components/Project/index.vue";
|
import Project from "./components/Project/index.vue";
|
||||||
import Team from "./components/Team/index.vue";
|
import Team from "./components/Team/index.vue";
|
||||||
|
|
||||||
import BScroll from 'better-scroll'
|
import BScroll from "better-scroll";
|
||||||
|
|
||||||
import {useUserStoreHook} from "@/store/modules/user"
|
import useStore from "@/store";
|
||||||
|
|
||||||
const roles = computed(() => useUserStoreHook().roles);
|
|
||||||
const avatar = computed(() => useUserStoreHook().avatar);
|
|
||||||
const nickname = computed(() => useUserStoreHook().nickname);
|
|
||||||
|
|
||||||
|
const { user } = useStore();
|
||||||
|
|
||||||
|
const roles = computed(() => user.roles);
|
||||||
|
const avatar = computed(() => user.avatar);
|
||||||
|
const nickname = computed(() => user.nickname);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
.dashboard-container {
|
.dashboard-container {
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
background-color: rgb(240, 242, 245);
|
background-color: rgb(240, 242, 245);
|
||||||
@@ -200,8 +209,8 @@ const nickname = computed(() => useUserStoreHook().nickname);
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
color: #666;
|
color: #666;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
box-shadow: 4px 4px 40px rgba(0, 0, 0, .05);
|
box-shadow: 4px 4px 40px rgba(0, 0, 0, 0.05);
|
||||||
border-color: rgba(0, 0, 0, .05);
|
border-color: rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
.card-panel-icon-wrapper {
|
.card-panel-icon-wrapper {
|
||||||
@@ -225,7 +234,7 @@ const nickname = computed(() => useUserStoreHook().nickname);
|
|||||||
}
|
}
|
||||||
|
|
||||||
.icon-shopping {
|
.icon-shopping {
|
||||||
background: #34bfa3
|
background: #34bfa3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,12 +300,8 @@ const nickname = computed(() => useUserStoreHook().nickname);
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.chart-container {
|
.chart-container {
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
label-position="left"
|
label-position="left"
|
||||||
>
|
>
|
||||||
<div class="title-container">
|
<div class="title-container">
|
||||||
<h3 class="title">{{ $t('login.title') }}</h3>
|
<h3 class="title">{{ $t("login.title") }}</h3>
|
||||||
<lang-select class="set-language" />
|
<lang-select class="set-language" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -51,7 +51,9 @@
|
|||||||
@keyup.enter.native="handleLogin"
|
@keyup.enter.native="handleLogin"
|
||||||
/>
|
/>
|
||||||
<span class="show-pwd" @click="showPwd">
|
<span class="show-pwd" @click="showPwd">
|
||||||
<svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'"/>
|
<svg-icon
|
||||||
|
:icon-class="passwordType === 'password' ? 'eye' : 'eye-open'"
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
@@ -70,23 +72,34 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="captcha">
|
<div class="captcha">
|
||||||
<img :src="captchaBase64" @click="handleCaptchaGenerate" height="38px"/>
|
<img
|
||||||
|
:src="captchaBase64"
|
||||||
|
@click="handleCaptchaGenerate"
|
||||||
|
height="38px"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-button size="default" :loading="loading" type="primary" style="width:100%;margin-bottom:30px;"
|
<el-button
|
||||||
@click.native.prevent="handleLogin">{{ $t('login.login') }}
|
size="default"
|
||||||
|
:loading="loading"
|
||||||
|
type="primary"
|
||||||
|
style="width: 100%; margin-bottom: 30px"
|
||||||
|
@click.native.prevent="handleLogin"
|
||||||
|
>{{ $t("login.login") }}
|
||||||
</el-button>
|
</el-button>
|
||||||
|
|
||||||
<div class="tips">
|
<div class="tips">
|
||||||
<span style="margin-right:20px;">{{ $t('login.username') }}: admin</span>
|
<span style="margin-right: 20px"
|
||||||
<span> {{ $t('login.password') }}: 123456</span>
|
>{{ $t("login.username") }}: admin</span
|
||||||
|
>
|
||||||
|
<span> {{ $t("login.password") }}: 123456</span>
|
||||||
</div>
|
</div>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
<div v-if="showCopyright == true" class="copyright">
|
<div v-if="showCopyright == true" class="copyright">
|
||||||
<p>{{ $t('login.copyright') }}</p>
|
<p>{{ $t("login.copyright") }}</p>
|
||||||
<p>{{ $t('login.icp') }}</p>
|
<p>{{ $t("login.icp") }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -96,50 +109,52 @@ import {onMounted, reactive, ref, toRefs, watch, nextTick} from "vue";
|
|||||||
|
|
||||||
// 组件依赖
|
// 组件依赖
|
||||||
import { ElForm, ElInput } from "element-plus";
|
import { ElForm, ElInput } from "element-plus";
|
||||||
import router from '@/router'
|
import router from "@/router";
|
||||||
import LangSelect from '@/components/LangSelect/index.vue';
|
import LangSelect from "@/components/LangSelect/index.vue";
|
||||||
import SvgIcon from '@/components/SvgIcon/index.vue';
|
import SvgIcon from "@/components/SvgIcon/index.vue";
|
||||||
|
|
||||||
// 状态管理依赖
|
// 状态管理依赖
|
||||||
import {useUserStoreHook} from "@/store/modules/user";
|
import useStore from "@/store";
|
||||||
import {useAppStoreHook} from "@/store/modules/app";
|
|
||||||
|
|
||||||
// API依赖
|
// API依赖
|
||||||
import { getCaptcha } from "@/api/login";
|
import { getCaptcha } from "@/api/login";
|
||||||
import { useRoute } from "vue-router";
|
import { useRoute } from "vue-router";
|
||||||
|
|
||||||
|
const { user } = useStore();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
const loginFormRef = ref(ElForm)
|
const loginFormRef = ref(ElForm);
|
||||||
const passwordRef = ref(ElInput)
|
const passwordRef = ref(ElInput);
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
loginForm: {
|
loginForm: {
|
||||||
username: 'admin',
|
username: "admin",
|
||||||
password: '123456',
|
password: "123456",
|
||||||
code: '',
|
code: "",
|
||||||
uuid: ''
|
uuid: "",
|
||||||
},
|
},
|
||||||
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 },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
loading: false,
|
loading: false,
|
||||||
passwordType: 'password',
|
passwordType: "password",
|
||||||
redirect: '',
|
redirect: "",
|
||||||
captchaBase64: '',
|
captchaBase64: "",
|
||||||
// 大写提示禁用
|
// 大写提示禁用
|
||||||
capslockTooltipDisabled: true,
|
capslockTooltipDisabled: true,
|
||||||
otherQuery: {},
|
otherQuery: {},
|
||||||
clientHeight: document.documentElement.clientHeight,
|
clientHeight: document.documentElement.clientHeight,
|
||||||
showCopyright: true
|
showCopyright: true,
|
||||||
})
|
});
|
||||||
|
|
||||||
function validatePassword(rule: any, value: any, callback: any) {
|
function validatePassword(rule: any, value: any, callback: any) {
|
||||||
if (value.length < 6) {
|
if (value.length < 6) {
|
||||||
callback(new Error('The password can not be less than 6 digits'))
|
callback(new Error("The password can not be less than 6 digits"));
|
||||||
} else {
|
} else {
|
||||||
callback()
|
callback();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,83 +166,88 @@ const {
|
|||||||
redirect,
|
redirect,
|
||||||
captchaBase64,
|
captchaBase64,
|
||||||
capslockTooltipDisabled,
|
capslockTooltipDisabled,
|
||||||
showCopyright
|
showCopyright,
|
||||||
} = toRefs(state)
|
} = toRefs(state);
|
||||||
|
|
||||||
function checkCapslock(e: any) {
|
function checkCapslock(e: any) {
|
||||||
const {key} = e
|
const { key } = e;
|
||||||
state.capslockTooltipDisabled = key && key.length === 1 && (key >= 'A' && key <= 'Z')
|
state.capslockTooltipDisabled =
|
||||||
|
key && key.length === 1 && key >= "A" && key <= "Z";
|
||||||
}
|
}
|
||||||
|
|
||||||
function showPwd() {
|
function showPwd() {
|
||||||
if (state.passwordType === 'password') {
|
if (state.passwordType === "password") {
|
||||||
state.passwordType = ''
|
state.passwordType = "";
|
||||||
} else {
|
} else {
|
||||||
state.passwordType = 'password'
|
state.passwordType = "password";
|
||||||
}
|
}
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
passwordRef.value.focus()
|
passwordRef.value.focus();
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleLogin() {
|
function handleLogin() {
|
||||||
loginFormRef.value.validate((valid: boolean) => {
|
loginFormRef.value.validate((valid: boolean) => {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
state.loading = true
|
state.loading = true;
|
||||||
useUserStoreHook().login(state.loginForm).then(() => {
|
user
|
||||||
router.push({path: state.redirect || '/', query: state.otherQuery})
|
.login(state.loginForm)
|
||||||
state.loading = false
|
.then(() => {
|
||||||
}).catch(() => {
|
router.push({ path: state.redirect || "/", query: state.otherQuery });
|
||||||
state.loading = false
|
state.loading = false;
|
||||||
handleCaptchaGenerate()
|
|
||||||
})
|
})
|
||||||
|
.catch(() => {
|
||||||
|
state.loading = false;
|
||||||
|
handleCaptchaGenerate();
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取验证码
|
// 获取验证码
|
||||||
function handleCaptchaGenerate() {
|
function handleCaptchaGenerate() {
|
||||||
getCaptcha().then(response => {
|
getCaptcha().then((response) => {
|
||||||
const {img, uuid} = response.data
|
const { img, uuid } = response.data;
|
||||||
state.captchaBase64 = "data:image/gif;base64," + img
|
state.captchaBase64 = "data:image/gif;base64," + img;
|
||||||
state.loginForm.uuid = uuid;
|
state.loginForm.uuid = uuid;
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(route, () => {
|
watch(
|
||||||
const query = route.query
|
route,
|
||||||
|
() => {
|
||||||
|
const query = route.query;
|
||||||
if (query) {
|
if (query) {
|
||||||
state.redirect = query.redirect as string
|
state.redirect = query.redirect as string;
|
||||||
state.otherQuery = getOtherQuery(query)
|
state.otherQuery = getOtherQuery(query);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
immediate: true
|
immediate: true,
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
|
|
||||||
|
|
||||||
function getOtherQuery(query: any) {
|
function getOtherQuery(query: any) {
|
||||||
return Object.keys(query).reduce((acc: any, cur: any) => {
|
return Object.keys(query).reduce((acc: any, cur: any) => {
|
||||||
if (cur !== 'redirect') {
|
if (cur !== "redirect") {
|
||||||
acc[cur] = query[cur]
|
acc[cur] = query[cur];
|
||||||
}
|
}
|
||||||
return acc
|
return acc;
|
||||||
}, {})
|
}, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
handleCaptchaGenerate()
|
handleCaptchaGenerate();
|
||||||
window.onresize = () => {
|
window.onresize = () => {
|
||||||
if (state.clientHeight > document.documentElement.clientHeight) {
|
if (state.clientHeight > document.documentElement.clientHeight) {
|
||||||
state.showCopyright = false
|
state.showCopyright = false;
|
||||||
} else {
|
} else {
|
||||||
state.showCopyright = true
|
state.showCopyright = true;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@@ -240,7 +260,6 @@ $cursor: #fff;
|
|||||||
|
|
||||||
/* reset element-ui css */
|
/* reset element-ui css */
|
||||||
.login-container {
|
.login-container {
|
||||||
|
|
||||||
.title-container {
|
.title-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
@@ -262,7 +281,6 @@ $cursor: #fff;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.el-input {
|
.el-input {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
height: 47px;
|
height: 47px;
|
||||||
@@ -384,6 +402,5 @@ $light_gray: #eee;
|
|||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user