chore:更换到主分支

This commit is contained in:
张益铭
2021-03-01 15:26:05 +08:00
parent 9064b372e8
commit 6a5f1810f9
3530 changed files with 59613 additions and 479452 deletions

View File

@@ -0,0 +1,46 @@
<template>
<section class="app-main">
<router-view :key="key" v-slot="{ Component }">
<transition appear name="fade-transform" mode="out-in">
<keep-alive>
<component :is="Component" />
</keep-alive>
</transition>
</router-view>
</section>
</template>
<script>
import { computed, defineComponent } from "vue";
import { useRoute } from "vue-router";
export default defineComponent({
name: "AppMain",
setup() {
const route = useRoute();
const key = computed(() => route.path);
return { key };
},
});
</script>
<style scoped>
.app-main {
min-height: calc(100vh - 50px);
width: 100%;
position: relative;
overflow-x: hidden;
margin: 10px;
}
.fixed-header + .app-main {
padding-top: 50px;
}
</style>
<style lang="scss">
.el-popup-parent--hidden {
.fixed-header {
padding-right: 15px;
}
}
</style>

View File

@@ -0,0 +1,178 @@
<template>
<div class="navbar">
<hamburger
:is-active="sidebar.opened"
class="hamburger-container"
@toggleClick="toggleSideBar"
/>
<breadcrumb class="breadcrumb-container" />
<div class="right-menu">
<screenfull />
<div class="inter" :title="langs ? '中文' : '英文'" @click="toggleLang">
<img :src="langs ? ch : en" />
</div>
<el-dropdown>
<span class="el-dropdown-link">
<img :src="favicon" />
<p>{{ usename }}</p>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item icon="el-icon-switch-button" @click="logout">
{{ $t("LoginOut") }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</template>
<script lang="ts">
import { ref, reactive, defineComponent, onMounted, nextTick } from "vue";
import Breadcrumb from "../../components/breadCrumb/index.vue";
import Hamburger from "../../components/hamBurger/index.vue";
import screenfull from "../components/screenfull/index.vue";
import { useMapGetters } from "../store";
import { useRoute, useRouter } from "vue-router";
import { mapGetters, useStore } from "vuex";
import { storageSession } from "../../utils/storage";
import { useI18n } from "vue-i18n";
import ch from "/@/assets/ch.png";
import en from "/@/assets/en.png";
import favicon from "/favicon.ico";
export default defineComponent({
name: "Navbar",
components: {
Breadcrumb,
Hamburger,
screenfull,
},
setup() {
let langs = ref(true);
const store = useStore();
const router = useRouter();
let usename = storageSession.getItem("info").username;
const { locale } = useI18n();
// 国际化语言切换
const toggleLang = (): void => {
langs.value = !langs.value;
langs.value ? (locale.value = "ch") : (locale.value = "en");
};
// 退出登录
const logout = (): void => {
storageSession.removeItem("info");
router.push("/login");
};
onMounted(() => {
document
.querySelector(".el-dropdown__popper")
?.setAttribute("class", "resetTop");
document
.querySelector(".el-popper__arrow")
?.setAttribute("class", "hidden");
});
return {
// @ts-ignore
...useMapGetters(["sidebar"]),
toggleSideBar() {
store.dispatch("app/toggleSideBar");
},
langs,
usename,
toggleLang,
logout,
ch,
en,
favicon
};
},
});
</script>
<style lang="scss" scoped>
.navbar {
height: 50px;
overflow: hidden;
position: relative;
background: #fff;
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
.hamburger-container {
line-height: 46px;
height: 100%;
float: left;
cursor: pointer;
transition: background 0.3s;
-webkit-tap-highlight-color: transparent;
&:hover {
background: rgba(0, 0, 0, 0.025);
}
}
.breadcrumb-container {
float: left;
}
.right-menu {
float: right;
display: flex;
align-items: center;
.inter {
width: 40px;
height: 48px;
display: flex;
align-items: center;
justify-content: space-around;
margin-right: 5px;
&:hover {
cursor: pointer;
background: #f0f0f0;
}
img {
width: 25px;
}
}
.el-dropdown-link {
width: 80px;
height: 48px;
display: flex;
align-items: center;
justify-content: space-around;
margin-right: 20px;
p {
font-size: 13px;
}
&:hover {
background: #f0f0f0;
}
img {
width: 22px;
height: 22px;
}
}
}
}
// single element-plus reset
.el-dropdown-menu__item {
padding: 0 10px;
}
.el-dropdown-menu {
padding: 0;
}
.el-dropdown-menu__item:focus,
.el-dropdown-menu__item:not(.is-disabled):hover {
color: #606266;
background: #f0f0f0;
}
</style>

View File

@@ -0,0 +1,4 @@
export { default as Navbar } from './Navbar.vue'
export { default as Sidebar } from './sidebar/index.vue'
export { default as AppMain } from './AppMain.vue'
export { default as setting } from './setting/index.vue'

View File

@@ -0,0 +1,133 @@
<template>
<div ref="right-panel" :class="{ show: show }" class="right-panel-container">
<div class="right-panel-background" />
<div class="right-panel">
<div
class="handle-button"
:title="show ? '关闭设置' : '打开设置'"
@click="show = !show"
>
<i :class="show ? 'el-icon-close' : 'el-icon-setting'" />
</div>
<div class="right-panel-items">
<slot />
</div>
</div>
</div>
</template>
<script lang='ts'>
import { addClass, removeClass } from "../../../utils/operate";
import { ref, watch, getCurrentInstance, onMounted, onBeforeMount } from "vue";
export default {
name: "panel",
setup() {
let vm: any;
let show = ref(false);
watch(
show,
(val, prevVal) => {
val ? addEventClick() : () => {};
if (val) {
addClass(document.body, "showright-panel");
} else {
removeClass(document.body, "showright-panel");
}
},
{ immediate: true }
);
const addEventClick = (): void => {
window.addEventListener("click", closeSidebar);
};
const closeSidebar = (evt: any): void => {
const parent = evt.target.closest(".right-panel");
if (!parent) {
show.value = false;
window.removeEventListener("click", closeSidebar);
}
};
onBeforeMount(() => {
vm = getCurrentInstance();
});
return {
show,
};
},
};
</script>
<style>
.showright-panel {
overflow: hidden;
position: relative;
width: calc(100% - 15px);
}
</style>
<style lang="scss" scoped>
.right-panel-background {
position: fixed;
top: 0;
left: 0;
opacity: 0;
transition: opacity 0.3s cubic-bezier(0.7, 0.3, 0.1, 1);
background: rgba(0, 0, 0, 0.2);
z-index: -1;
}
.right-panel {
width: 100%;
max-width: 260px;
height: 100vh;
position: fixed;
top: 0;
right: 0;
box-shadow: 0px 0px 15px 0px rgba(0, 0, 0, 0.05);
transition: all 0.25s cubic-bezier(0.7, 0.3, 0.1, 1);
transform: translate(100%);
background: #fff;
z-index: 40000;
}
.show {
transition: all 0.3s cubic-bezier(0.7, 0.3, 0.1, 1);
.right-panel-background {
z-index: 20000;
opacity: 1;
width: 100%;
height: 100%;
}
.right-panel {
transform: translate(0);
}
}
.handle-button {
width: 48px;
height: 48px;
position: absolute;
left: -48px;
text-align: center;
font-size: 24px;
border-radius: 6px 0 0 6px !important;
z-index: 0;
pointer-events: auto;
cursor: pointer;
color: #fff;
line-height: 48px;
top: 45%;
background: rgb(24, 144, 255);
i {
font-size: 24px;
line-height: 48px;
}
}
</style>

View File

@@ -0,0 +1,77 @@
<template>
<div class="screen-full" @click="onClick">
<i
:title="isFullscreen ? '退出全屏' : '全屏'"
:class="
isFullscreen
? 'iconfont team-iconexit-fullscreen'
: 'iconfont team-iconfullscreen'
"
></i>
</div>
</template>
<script>
import screenfull from "screenfull";
import {
ref,
onBeforeMount,
onUnmounted,
defineComponent,
onMounted,
} from "vue";
export default defineComponent({
name: "screenfull",
setup() {
let isFullscreen = ref(false);
const onClick = () => {
if (!screenfull.isEnabled) return;
screenfull.toggle();
};
const change = () => {
isFullscreen.value = screenfull.isFullscreen;
};
const init = () => {
if (screenfull.isEnabled) {
screenfull.on("change", change);
}
};
const destroy = () => {
if (screenfull.isEnabled) {
screenfull.off("change", change);
}
};
onMounted(() => {
init();
});
onUnmounted(() => {
destroy();
});
return {
isFullscreen,
onClick,
};
},
});
</script>
<style lang="scss" scoped>
.screen-full {
width: 40px;
height: 48px;
display: flex;
align-items: center;
justify-content: space-around;
&:hover {
cursor: pointer;
background: #f0f0f0;
}
}
</style>

View File

@@ -0,0 +1,74 @@
<template>
<panel>
<el-divider>界面显示</el-divider>
<ul class="setting">
<li>
<span>灰色模式</span>
<vxe-switch
v-model="greyVal"
open-label=""
close-label=""
@change="greyChange"
></vxe-switch>
</li>
</ul>
</panel>
</template>
<script lang='ts'>
import panel from "../panel/index.vue";
import { onMounted, reactive, toRefs } from "vue";
import { storageLocal } from "../../../utils/storage";
export default {
name: "setting",
components: { panel },
setup() {
const localOperate = (key: string, value?: any, model?: string): any => {
model && model === "set"
? storageLocal.setItem(key, value)
: storageLocal.getItem(key);
};
const settings = reactive({
greyVal: storageLocal.getItem("greyVal"),
});
settings.greyVal === null
? localOperate("greyVal", false, "set")
: document.querySelector("html")?.setAttribute("class", "html-grey");
// 灰色模式设置
const greyChange = ({ value }): void => {
if (value) {
localOperate("greyVal", true, "set");
document.querySelector("html")?.setAttribute("class", "html-grey");
} else {
localOperate("greyVal", false, "set");
document.querySelector("html")?.removeAttribute("class");
}
};
return {
...toRefs(settings),
localOperate,
greyChange,
};
},
};
</script>
<style lang="scss" scoped>
.setting {
width: 100%;
li {
display: flex;
justify-content: space-between;
align-items: center;
margin: 16px;
}
}
:deep(.el-divider__text) {
font-size: 16px;
font-weight: 700;
}
</style>

View File

@@ -0,0 +1,30 @@
<template>
<component :is="type" v-bind="linkProps(to)">
<slot />
</component>
</template>
<script>
import { computed, defineComponent } from "vue";
export default defineComponent({
name: "Link",
props: {
to: {
type: String,
required: true,
},
},
setup(props) {
const linkProps = (to) => {
return {
to: to,
};
};
return {
type: "router-link",
linkProps,
};
},
});
</script>

View File

@@ -0,0 +1,101 @@
<template>
<div v-if="!item.hidden">
<template
v-if="
hasOneShowingChild(item.children, item) &&
(!onlyOneChild.children || onlyOneChild.noShowingChildren) &&
!item.alwaysShow
"
>
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
<el-menu-item
:index="resolvePath(onlyOneChild.path)"
:class="{ 'submenu-title-noDropdown': !isNest }"
>
<i :class="onlyOneChild.meta.icon || (item.meta && item.meta.icon)" />
<template #title>
<span>{{ $t(onlyOneChild.meta.title) }}</span>
</template>
</el-menu-item>
</app-link>
</template>
<el-submenu
v-else
ref="subMenu"
:index="resolvePath(item.path)"
popper-append-to-body
>
<template #title>
<i :class="item.meta.icon"></i>
<span>{{ $t(item.meta.title) }}</span>
</template>
<sidebar-item
v-for="child in item.children"
:key="child.path"
:is-nest="true"
:item="child"
:base-path="resolvePath(child.path)"
class="nest-menu"
/>
</el-submenu>
</div>
</template>
<script lang="ts">
import path from "path";
import AppLink from "./Link.vue";
import { defineComponent, PropType, ref } from "vue";
import { RouteRecordRaw } from "vue-router";
export default defineComponent({
name: "SidebarItem",
components: { AppLink },
props: {
item: {
type: Object as PropType<RouteRecordRaw>,
required: true,
},
isNest: {
type: Boolean,
default: false,
},
basePath: {
type: String,
default: "",
},
},
setup(props) {
const onlyOneChild = ref<RouteRecordRaw>({} as any);
function hasOneShowingChild(
children: RouteRecordRaw[] = [],
parent: RouteRecordRaw
) {
const showingChildren = children.filter((item) => {
if (item.hidden) {
return false;
} else {
onlyOneChild.value = item;
return true;
}
});
if (showingChildren.length === 1) {
return true;
}
if (showingChildren.length === 0) {
onlyOneChild.value = { ...parent, path: "", noShowingChildren: true };
return true;
}
return false;
}
const resolvePath = (routePath: string) => {
return path.resolve(props.basePath, routePath);
};
return { hasOneShowingChild, resolvePath, onlyOneChild };
},
});
</script>

View File

@@ -0,0 +1,55 @@
<template>
<el-scrollbar wrap-class="scrollbar-wrapper">
<el-menu
:default-active="activeMenu"
:collapse="isCollapse"
background-color="#304156"
text-color="#bfcbd9"
:unique-opened="false"
active-text-color="#409EFF"
:collapse-transition="false"
mode="vertical"
>
<sidebar-item
v-for="route in routes"
:key="route.path"
:item="route"
:base-path="route.path"
/>
</el-menu>
</el-scrollbar>
</template>
<script lang="ts">
import { computed, defineComponent } from "vue";
import { useRoute, useRouter } from "vue-router";
import { useStore } from "vuex";
import SidebarItem from "./SidebarItem.vue";
import { algorithm } from "../../../utils/algorithm";
export default defineComponent({
name: "sidebar",
components: { SidebarItem },
setup() {
const router = useRouter().options.routes;
const store = useStore();
const route = useRoute();
const activeMenu = computed(() => {
const { meta, path } = route;
if (meta.activeMenu) {
return meta.activeMenu;
}
return path;
});
return {
routes: computed(() => algorithm.increaseIndexes(router)),
activeMenu,
isCollapse: computed(() => !store.getters.sidebar.opened),
};
},
});
</script>

View File

@@ -0,0 +1,51 @@
<template>
<div class="tags">
<el-tag
size="medium"
v-for="tag in tags"
:key="tag.name"
closable
:type="tag.type"
>{{ tag.name }}</el-tag
>
</div>
</template>
<script lang='ts'>
import { ref, defineComponent, onUnmounted, onMounted } from "vue";
export default defineComponent({
name: "tag",
setup() {
let flag = ref(true);
const tags = ref([
{ name: "首页", type: "info" },
{ name: "基础管理", type: "info" },
]);
return {
tags,
flag,
};
},
});
</script>
<style lang="scss" scoped>
.tags {
height: 32px;
float: right;
border: 1px solid #f0f0f0;
display: flex;
align-items: center;
transition: 0.18s;
}
:deep(.el-tag) {
background-color: #fff;
border: 1px solid #d0d7e7;
margin-left: 4px;
&:first-child {
margin-left: 8px;
}
}
</style>