基于vue3.0和element-plus的组件库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

267 lines
6.3 KiB

<template>
<el-container ref="main" v-show="!flag.loading" class="root-container">
<el-header v-show="state.size.headHeight != '0px'" class="app-head" :height="state.size.headHeight">
<Head :title="title" :logo="logo" :username="username">
<template #left>
<MenuTree v-show="mode == 'horizontal'" :data="menus" mode="horizontal" />
</template>
<HeadPersonal @updatePwd="updatePwd" @logout="onLogout" :center="center" />
<Fullscreen />
<StyleChange v-if="styleAble" />
<LangChange v-if="langAble" />
<SizeChange v-if="sizeAble" />
</Head>
</el-header>
<el-container id="container">
<el-aside v-show="mode == 'vertical' && state.size.asideWidth != '0px'" :width="state.size.asideWidth">
<MenuTree :data="menus" mode="vertical">
<template #footer>
<div>v{{ VITE_APP_VERSION }}-{{ VITE_GIT_HASH.substring(0, 8) }}</div>
<template v-if="NODE_ENV != 'production'">
<div>dev-{{ DEV_MODE_TS }}</div>
</template>
</template>
</MenuTree>
</el-aside>
<el-main class="app-main">
<router-view />
</el-main>
</el-container>
</el-container>
</template>
<script lang="ts" setup>
import { onBeforeUnmount, onMounted, reactive, ref, watch, computed } from "vue";
import { useStore } from "vuex";
import { useRouter, useRoute } from "vue-router";
import { Api, NoobHead } from "noob-mengyxu";
import { md5 } from "js-md5";
const DEV_MODE_TS = "2026-03-26T08:35:00.000Z";
const { VITE_APP_VERSION, VITE_GIT_HASH, NODE_ENV } = import.meta.env;
const { Head, MenuTree, HeadPersonal, Fullscreen, StyleChange, LangChange, SizeChange } = NoobHead;
const { state, commit, dispatch } = useStore();
const emit = defineEmits(["updatePwd", "logout"]);
const router = useRouter();
const route = useRoute();
const main = ref();
const flag = reactive({
showHeader: true,
showAside: true,
loading: false,
});
let interval = 0;
const props = defineProps({
title: {
type: String,
default: null,
},
menus: {
type: Array<any>(),
default: null,
},
mode: {
type: String,
default: "vertical",
},
styleAble: {
type: Boolean,
default: true,
},
sizeAble: {
type: Boolean,
default: true,
},
langAble: {
type: Boolean,
default: true,
},
center: {
type: String,
default: "home",
},
checkUser: {
type: Boolean,
default: true,
},
username: {
type: String,
default: null,
},
closeAble: {
type: Boolean,
default: false,
},
logo: {
type: String,
default: null,
},
});
const onResize = () => {
const height = window.innerHeight;
commit("initSize", [height, window.innerWidth]);
};
const getUser = (first?) => {
if (!props.checkUser) return;
if (first) flag.loading = true;
Api.pub.getInfo().then((rsp) => {
if (first) flag.loading = false;
if (rsp) {
if (state.user?.userId !== rsp.userId) {
commit("updateState", ["user", rsp]);
}
} else {
router.push("/login");
}
});
};
const onLogout = () => {
Api.pub.logout().then((rsp) => {
getUser();
emit("logout");
});
};
const updatePwd = (pwd) => {
pwd.old = md5(pwd.old);
pwd.new = md5(pwd.new);
pwd.reNew = md5(pwd.reNew);
emit("updatePwd", pwd);
Api.user.updatePwd(pwd);
};
onMounted(() => {
// router.push("/"); // redundant - already navigated from login
dispatch("getMenus");
getUser(); // Don't show loading screen after successful login
interval = setInterval(getUser, 5000);
window.onresize = onResize;
onResize();
});
// Watch for navigation from login page
watch(
() => route.path,
(newPath, oldPath) => {
if (oldPath === "/login" && newPath !== "/login" && !state.user?.userId) {
getUser();
interval = setInterval(getUser, 5000);
}
}
);
onBeforeUnmount(() => {
clearInterval(interval);
});
</script>
<style lang="scss">
body {
font-size: v-bind("state.size.fontSize") !important;
font-family: "Microsoft YaHei";
width: 100%;
min-height: 100vh;
overflow: hidden;
padding: 0px;
margin: 0px;
}
</style>
<style lang="scss" scoped>
.root-container {
min-height: 100vh !important;
height: 100vh !important;
max-height: 100vh !important;
display: flex !important;
flex-direction: column !important;
overflow: hidden !important;
background-color: v-bind("state.style.bodyBg");
}
#container {
flex: 1 1 auto !important;
min-height: 0 !important;
overflow: hidden !important;
}
.app-main {
box-shadow: 2px 2px 5px 3px #e5e6eb;
border-radius: 4px;
margin: 0px 0px 0px 3px !important;
padding: 0 !important;
flex: 1 1 auto !important;
min-height: 0 !important;
display: flex !important;
flex-direction: column !important;
overflow: hidden !important;
}
.el-header,
.main-head,
.main-table {
padding: 0;
}
#container,
.app-main {
background-color: v-bind("state.style.bodyBg");
color: v-bind("state.style.color");
font-size: v-bind("state.size.fontSize");
}
.app-head {
padding: 0px !important;
background-color: v-bind("state.style.headBg");
height: v-bind("state.size.headHeight");
}
:deep(.head-icon) {
float: right;
cursor: pointer;
height: v-bind("state.size.headHeight");
margin-right: 20px;
font-size: v-bind("state.size.headIconSize");
align-items: center;
color: v-bind("state.style.color");
}
:deep(.el-input),
:deep(.el-textarea),
:deep(.el-date-editor),
:deep(.el-input__wrapper) {
--el-input-bg-color: v-bind("state.style.itemBg") !important;
--el-fill-color-blank: v-bind("state.style.itemBg") !important;
--el-input-text-color: v-bind("state.style.color") !important;
}
:deep(.el-popper) {
--el-bg-color-overlay: v-bind("state.style.itemBg") !important;
--el-fill-color-light: v-bind("state.style.bodyBg") !important;
text-align: left;
}
:deep(.el-dialog) {
--el-dialog-bg-color: v-bind("state.style.bodyBg") !important;
}
:deep(.el-drawer) {
--el-drawer-bg-color: v-bind("state.style.bodyBg") !important;
}
:deep(*) {
--el-text-color-regular: v-bind("state.style.color") !important;
--el-text-color-primary: v-bind("state.style.color") !important;
--el-disabled-bg-color: v-bind("state.style.itemBg") !important;
}
:deep(*::selection) {
background-color: v-bind("state.style.selectionBg");
color: v-bind("state.style.selectionColor");
}
</style>