refactor: pinia整合优化重构

This commit is contained in:
郝先瑞
2022-03-11 00:07:34 +08:00
parent 7bd9d70433
commit 4ae629ab65
26 changed files with 675 additions and 563 deletions

View File

@@ -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) => {

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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>

View File

@@ -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);

View File

@@ -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)
}) })

View File

@@ -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>

View File

@@ -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);
} }
} }
} }

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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: {

View File

@@ -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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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()

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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>

View File

@@ -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>