mirror of
https://github.com/pure-admin/vue-pure-admin.git
synced 2025-11-03 13:44:47 +08:00
feat: add horizontal nav (#45)
* feat: add horizontal nav * workflow: update linter.yml * fix: update * fix: update * fix: update * Rename Link.vue to link.vue * Rename SidebarItem.vue to sidebarItem.vue * Rename Logo.vue to logo.vue * Rename AppMain.vue to appMain.vue * Rename Navbar.vue to navbar.vue * fix: update * fix: update * fix: update * workflow: update linter.yml * fix: update * chore: update * workflow: update * fix: update * fix: update * fix: update * perf: 外链功能实现方式改变 * style: update nav style * perf: 优化国际化 * fix: update * fix: update
This commit is contained in:
@@ -1,5 +0,0 @@
|
||||
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";
|
||||
export { default as tag } from "./tag/index.vue";
|
||||
@@ -8,98 +8,84 @@
|
||||
|
||||
<Breadcrumb class="breadcrumb-container" />
|
||||
|
||||
<div class="right-menu">
|
||||
<div class="vertical-header-right">
|
||||
<!-- 全屏 -->
|
||||
<screenfull v-show="!deviceDetection()" />
|
||||
<!-- 国际化 -->
|
||||
<div
|
||||
v-show="!deviceDetection()"
|
||||
class="inter"
|
||||
:title="currentLocale ? '中文' : 'English'"
|
||||
@click="toggleLang"
|
||||
>
|
||||
<img :src="currentLocale ? ch : en" />
|
||||
</div>
|
||||
<i
|
||||
class="el-icon-setting hsset"
|
||||
:title="$t('message.hssystemSet')"
|
||||
@click="onPanel"
|
||||
></i>
|
||||
<el-dropdown trigger="click">
|
||||
<iconinternationality />
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu class="translation">
|
||||
<el-dropdown-item
|
||||
:style="{
|
||||
background: locale === 'zh' ? '#1b2a47' : '',
|
||||
color: locale === 'zh' ? '#f4f4f5' : '#000'
|
||||
}"
|
||||
@click="translationCh"
|
||||
>简体中文</el-dropdown-item
|
||||
>
|
||||
<el-dropdown-item
|
||||
:style="{
|
||||
background: locale === 'en' ? '#1b2a47' : '',
|
||||
color: locale === 'en' ? '#f4f4f5' : '#000'
|
||||
}"
|
||||
@click="translationEn"
|
||||
>English</el-dropdown-item
|
||||
>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<!-- 退出登陆 -->
|
||||
<el-dropdown trigger="click">
|
||||
<span class="el-dropdown-link">
|
||||
<img :src="favicon" />
|
||||
<img
|
||||
src="https://avatars.githubusercontent.com/u/44761321?s=400&u=30907819abd29bb3779bc247910873e7c7f7c12f&v=4"
|
||||
/>
|
||||
<p>{{ usename }}</p>
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-menu class="logout">
|
||||
<el-dropdown-item icon="el-icon-switch-button" @click="logout">{{
|
||||
$t("message.hsLoginOut")
|
||||
}}</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<i
|
||||
class="el-icon-setting"
|
||||
:title="$t('message.hssystemSet')"
|
||||
@click="onPanel"
|
||||
></i>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
defineComponent,
|
||||
onMounted,
|
||||
unref,
|
||||
watch,
|
||||
getCurrentInstance
|
||||
} from "vue";
|
||||
import { defineComponent, unref, watch, getCurrentInstance } from "vue";
|
||||
import Breadcrumb from "/@/components/ReBreadCrumb";
|
||||
import Hamburger from "/@/components/ReHamBurger";
|
||||
import screenfull from "../components/screenfull/index.vue";
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
import { useAppStoreHook } from "/@/store/modules/app";
|
||||
import { storageSession } from "/@/utils/storage";
|
||||
import ch from "/@/assets/ch.png";
|
||||
import en from "/@/assets/en.png";
|
||||
import favicon from "/favicon.ico";
|
||||
import { emitter } from "/@/utils/mitt";
|
||||
import { deviceDetection } from "/@/utils/deviceDetection";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import iconinternationality from "/@/assets/svg/iconinternationality.svg";
|
||||
|
||||
let routerArrays: Array<object> = [
|
||||
{
|
||||
path: "/welcome",
|
||||
parentPath: "/",
|
||||
meta: {
|
||||
title: "message.hshome",
|
||||
icon: "el-icon-s-home",
|
||||
showLink: true,
|
||||
savedPosition: false
|
||||
}
|
||||
}
|
||||
];
|
||||
export default defineComponent({
|
||||
name: "Navbar",
|
||||
components: {
|
||||
Breadcrumb,
|
||||
Hamburger,
|
||||
screenfull
|
||||
screenfull,
|
||||
iconinternationality
|
||||
},
|
||||
// @ts-ignore
|
||||
computed: {
|
||||
// eslint-disable-next-line vue/return-in-computed-property
|
||||
currentLocale() {
|
||||
if (
|
||||
!this.$storage.routesInStorage ||
|
||||
this.$storage.routesInStorage.length === 0
|
||||
) {
|
||||
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
|
||||
this.$storage.routesInStorage = routerArrays;
|
||||
}
|
||||
|
||||
if (!this.$storage.locale) {
|
||||
// eslint-disable-next-line
|
||||
this.$storage.locale = { locale: "zh" };
|
||||
useI18n().locale.value = "zh";
|
||||
}
|
||||
switch (this.$storage.locale?.locale) {
|
||||
case "zh":
|
||||
return true;
|
||||
@@ -114,25 +100,9 @@ export default defineComponent({
|
||||
const pureApp = useAppStoreHook();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
let usename = storageSession.getItem("info")?.username;
|
||||
|
||||
const { locale, t } = useI18n();
|
||||
|
||||
// 国际化语言切换
|
||||
const toggleLang = (): void => {
|
||||
switch (instance.locale.locale) {
|
||||
case "zh":
|
||||
instance.locale = { locale: "en" };
|
||||
locale.value = "en";
|
||||
break;
|
||||
case "en":
|
||||
instance.locale = { locale: "zh" };
|
||||
locale.value = "zh";
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => locale.value,
|
||||
() => {
|
||||
@@ -155,28 +125,31 @@ export default defineComponent({
|
||||
pureApp.toggleSideBar();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document
|
||||
.querySelector(".el-dropdown__popper")
|
||||
?.setAttribute("class", "resetTop");
|
||||
document
|
||||
.querySelector(".el-popper__arrow")
|
||||
?.setAttribute("class", "hidden");
|
||||
});
|
||||
// 简体中文
|
||||
function translationCh() {
|
||||
instance.locale = { locale: "zh" };
|
||||
locale.value = "zh";
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
// English
|
||||
function translationEn() {
|
||||
instance.locale = { locale: "en" };
|
||||
locale.value = "en";
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
return {
|
||||
pureApp,
|
||||
toggleSideBar,
|
||||
usename,
|
||||
toggleLang,
|
||||
logout,
|
||||
ch,
|
||||
en,
|
||||
favicon,
|
||||
onPanel,
|
||||
deviceDetection,
|
||||
locale,
|
||||
t
|
||||
usename,
|
||||
pureApp,
|
||||
favicon,
|
||||
logout,
|
||||
onPanel,
|
||||
translationCh,
|
||||
translationEn,
|
||||
toggleSideBar,
|
||||
deviceDetection
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -185,13 +158,13 @@ export default defineComponent({
|
||||
<style lang="scss" scoped>
|
||||
.navbar {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
height: 48px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
||||
|
||||
.hamburger-container {
|
||||
line-height: 46px;
|
||||
line-height: 48px;
|
||||
height: 100%;
|
||||
float: left;
|
||||
cursor: pointer;
|
||||
@@ -203,56 +176,46 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumb-container {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.right-menu {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
.vertical-header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 280px;
|
||||
height: 48px;
|
||||
line-height: 48px;
|
||||
align-items: center;
|
||||
color: #000000d9;
|
||||
justify-content: flex-end;
|
||||
|
||||
.inter {
|
||||
width: 40px;
|
||||
height: 48px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
.screen-full {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 25px;
|
||||
background: #f6f6f6;
|
||||
}
|
||||
}
|
||||
|
||||
.hsset {
|
||||
width: 40px;
|
||||
.iconinternationality {
|
||||
height: 48px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
margin-right: 5px;
|
||||
width: 40px;
|
||||
padding: 11px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background: #f0f0f0;
|
||||
background: #f6f6f6;
|
||||
}
|
||||
}
|
||||
|
||||
.el-dropdown-link {
|
||||
width: 70px;
|
||||
width: 100px;
|
||||
height: 48px;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
margin-right: 10px;
|
||||
cursor: pointer;
|
||||
color: #000000d9;
|
||||
|
||||
&:hover {
|
||||
background: #f6f6f6;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 14px;
|
||||
@@ -261,22 +224,50 @@ export default defineComponent({
|
||||
img {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.el-icon-setting {
|
||||
height: 48px;
|
||||
width: 40px;
|
||||
padding: 11px;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
|
||||
&:hover {
|
||||
background: #f6f6f6;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// single element-plus reset
|
||||
.el-dropdown-menu__item {
|
||||
padding: 0 10px;
|
||||
|
||||
.breadcrumb-container {
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
|
||||
.el-dropdown-menu {
|
||||
padding: 6px 0;
|
||||
.translation {
|
||||
.el-dropdown-menu__item {
|
||||
padding: 0 40px !important;
|
||||
}
|
||||
|
||||
.el-dropdown-menu__item:focus,
|
||||
.el-dropdown-menu__item:not(.is-disabled):hover {
|
||||
color: #606266;
|
||||
background: #f0f0f0;
|
||||
}
|
||||
}
|
||||
|
||||
.el-dropdown-menu__item:focus,
|
||||
.el-dropdown-menu__item:not(.is-disabled):hover {
|
||||
color: #606266;
|
||||
background: #f0f0f0;
|
||||
.logout {
|
||||
.el-dropdown-menu__item {
|
||||
padding: 0 18px !important;
|
||||
}
|
||||
|
||||
.el-dropdown-menu__item:focus,
|
||||
.el-dropdown-menu__item:not(.is-disabled):hover {
|
||||
color: #606266;
|
||||
background: #f0f0f0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -3,7 +3,7 @@ import { ref } from "vue";
|
||||
import { useEventListener, onClickOutside } from "@vueuse/core";
|
||||
import { emitter } from "/@/utils/mitt";
|
||||
|
||||
let show = ref(false);
|
||||
let show = ref<Boolean>(false);
|
||||
const target = ref(null);
|
||||
onClickOutside(target, () => {
|
||||
show.value = false;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { useFullscreen } from "@vueuse/core";
|
||||
|
||||
const { isFullscreen, toggle } = useFullscreen();
|
||||
</script>
|
||||
|
||||
@@ -23,15 +22,10 @@ const { isFullscreen, toggle } = useFullscreen();
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.screen-full {
|
||||
width: 40px;
|
||||
height: 48px;
|
||||
width: 36px;
|
||||
height: 62px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background: #f0f0f0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
<script setup lang="ts">
|
||||
import { split } from "lodash-es";
|
||||
import panel from "../panel/index.vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { emitter } from "/@/utils/mitt";
|
||||
import { templateRef } from "@vueuse/core";
|
||||
import { reactive, ref, unref, useCssModule } from "vue";
|
||||
import { storageLocal, storageSession } from "/@/utils/storage";
|
||||
import { reactive, ref, unref, useCssModule, getCurrentInstance } from "vue";
|
||||
|
||||
const router = useRouter();
|
||||
const { isSelect } = useCssModule();
|
||||
|
||||
const instance =
|
||||
getCurrentInstance().appContext.app.config.globalProperties.$storage;
|
||||
|
||||
// 默认灵动模式
|
||||
const markValue = ref(storageLocal.getItem("showModel") || "smart");
|
||||
|
||||
@@ -80,37 +84,54 @@ function onChange({ label }) {
|
||||
emitter.emit("tagViewsShowModel", label);
|
||||
}
|
||||
|
||||
const firstTheme = templateRef<HTMLElement | null>("firstTheme", null);
|
||||
const secondTheme = templateRef<HTMLElement | null>("secondTheme", null);
|
||||
const verticalDarkDom = templateRef<HTMLElement | null>(
|
||||
"verticalDarkDom",
|
||||
null
|
||||
);
|
||||
const verticalLightDom = templateRef<HTMLElement | null>(
|
||||
"verticalLightDom",
|
||||
null
|
||||
);
|
||||
const horizontalDarkDom = templateRef<HTMLElement | null>(
|
||||
"horizontalDarkDom",
|
||||
null
|
||||
);
|
||||
const horizontalLightDom = templateRef<HTMLElement | null>(
|
||||
"horizontalLightDom",
|
||||
null
|
||||
);
|
||||
|
||||
const dataTheme = ref(storageLocal.getItem("data-theme") || "dark");
|
||||
if (dataTheme.value) {
|
||||
storageLocal.setItem("data-theme", unref(dataTheme));
|
||||
window.document.body.setAttribute("data-theme", unref(dataTheme));
|
||||
}
|
||||
|
||||
// dark主题
|
||||
function onDark() {
|
||||
storageLocal.setItem("data-theme", "dark");
|
||||
window.document.body.setAttribute("data-theme", "dark");
|
||||
toggleClass(true, isSelect, unref(firstTheme));
|
||||
toggleClass(false, isSelect, unref(secondTheme));
|
||||
}
|
||||
|
||||
// light主题
|
||||
function onLight() {
|
||||
storageLocal.setItem("data-theme", "light");
|
||||
window.document.body.setAttribute("data-theme", "light");
|
||||
toggleClass(false, isSelect, unref(firstTheme));
|
||||
toggleClass(true, isSelect, unref(secondTheme));
|
||||
let dataTheme =
|
||||
ref(storageLocal.getItem("responsive-layout")) ||
|
||||
ref({
|
||||
layout: "horizontal-dark"
|
||||
});
|
||||
|
||||
if (unref(dataTheme)) {
|
||||
// 设置主题
|
||||
let theme = split(unref(dataTheme).layout, "-")[1];
|
||||
window.document.body.setAttribute("data-theme", theme);
|
||||
// 设置导航模式
|
||||
let layout = split(unref(dataTheme).layout, "-")[0];
|
||||
window.document.body.setAttribute("data-layout", layout);
|
||||
}
|
||||
|
||||
// 侧边栏Logo
|
||||
function logoChange() {
|
||||
unref(logoVal) === "1"
|
||||
? storageLocal.setItem("logoVal", "1")
|
||||
: storageLocal.setItem("logoVal", "-1");
|
||||
emitter.emit("logoChange", unref(logoVal));
|
||||
}
|
||||
|
||||
function setTheme(layout: string, theme: string, dom: HTMLElement) {
|
||||
dataTheme.value.layout = `${layout}-${theme}`;
|
||||
window.document.body.setAttribute("data-layout", layout);
|
||||
window.document.body.setAttribute("data-theme", theme);
|
||||
instance.layout = { layout: `${layout}-${theme}` };
|
||||
toggleClass(true, isSelect, unref(dom));
|
||||
toggleClass(false, isSelect, unref(dom));
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -124,9 +145,9 @@ function logoChange() {
|
||||
placement="bottom"
|
||||
>
|
||||
<li
|
||||
:class="dataTheme === 'dark' ? $style.isSelect : ''"
|
||||
ref="firstTheme"
|
||||
@click="onDark"
|
||||
:class="dataTheme.layout === 'vertical-dark' ? $style.isSelect : ''"
|
||||
ref="verticalDarkDom"
|
||||
@click="setTheme('vertical', 'dark', verticalDarkDom)"
|
||||
>
|
||||
<div></div>
|
||||
<div></div>
|
||||
@@ -140,9 +161,43 @@ function logoChange() {
|
||||
placement="bottom"
|
||||
>
|
||||
<li
|
||||
:class="dataTheme === 'light' ? $style.isSelect : ''"
|
||||
ref="secondTheme"
|
||||
@click="onLight"
|
||||
:class="dataTheme.layout === 'vertical-light' ? $style.isSelect : ''"
|
||||
ref="verticalLightDom"
|
||||
@click="setTheme('vertical', 'light', verticalLightDom)"
|
||||
>
|
||||
<div></div>
|
||||
<div></div>
|
||||
</li>
|
||||
</el-tooltip>
|
||||
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="dark"
|
||||
content="暗色主题"
|
||||
placement="bottom"
|
||||
>
|
||||
<li
|
||||
:class="dataTheme.layout === 'horizontal-dark' ? $style.isSelect : ''"
|
||||
ref="horizontalDarkDom"
|
||||
@click="setTheme('horizontal', 'dark', horizontalDarkDom)"
|
||||
>
|
||||
<div></div>
|
||||
<div></div>
|
||||
</li>
|
||||
</el-tooltip>
|
||||
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="dark"
|
||||
content="暗色主题"
|
||||
placement="bottom"
|
||||
>
|
||||
<li
|
||||
:class="
|
||||
dataTheme.layout === 'horizontal-light' ? $style.isSelect : ''
|
||||
"
|
||||
ref="horizontalLightDom"
|
||||
@click="setTheme('horizontal', 'light', horizontalLightDom)"
|
||||
>
|
||||
<div></div>
|
||||
<div></div>
|
||||
@@ -237,18 +292,19 @@ function logoChange() {
|
||||
.theme-stley {
|
||||
margin-top: 25px;
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
height: 180px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-around;
|
||||
|
||||
li {
|
||||
width: 30%;
|
||||
height: 100%;
|
||||
margin: 10px;
|
||||
width: 36%;
|
||||
height: 70px;
|
||||
background: #f0f2f5;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
background-color: #f0f2f5;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 2.5px 0 rgb(0 0 0 / 18%);
|
||||
|
||||
@@ -265,7 +321,7 @@ function logoChange() {
|
||||
height: 30%;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background-color: #fff;
|
||||
background: #fff;
|
||||
box-shadow: 0 0 1px #888;
|
||||
position: absolute;
|
||||
}
|
||||
@@ -278,7 +334,7 @@ function logoChange() {
|
||||
width: 30%;
|
||||
height: 100%;
|
||||
box-shadow: 0 0 1px #888;
|
||||
background-color: #fff;
|
||||
background: #fff;
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
|
||||
@@ -287,12 +343,34 @@ function logoChange() {
|
||||
height: 30%;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background-color: #fff;
|
||||
background: #fff;
|
||||
box-shadow: 0 0 1px #888;
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
div {
|
||||
&:nth-child(1) {
|
||||
width: 100%;
|
||||
height: 30%;
|
||||
background: #1b2a47;
|
||||
box-shadow: 0 0 1px #888;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(4) {
|
||||
div {
|
||||
&:nth-child(1) {
|
||||
width: 100%;
|
||||
height: 30%;
|
||||
background: #fff;
|
||||
box-shadow: 0 0 1px #888;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
<template>
|
||||
<component :is="type" v-bind="linkProps(to)">
|
||||
<slot />
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, unref } from "vue";
|
||||
import { isUrl } from "/@/utils/is";
|
||||
|
||||
export default defineComponent({
|
||||
name: "Link",
|
||||
props: {
|
||||
to: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
const isExternal = computed(() => {
|
||||
return isUrl(props.to);
|
||||
});
|
||||
|
||||
const type = computed(() => {
|
||||
if (unref(isExternal)) {
|
||||
return "a";
|
||||
}
|
||||
return "router-link";
|
||||
});
|
||||
|
||||
function linkProps(to) {
|
||||
if (unref(isExternal)) {
|
||||
return {
|
||||
href: to,
|
||||
target: "_blank",
|
||||
rel: "noopener"
|
||||
};
|
||||
}
|
||||
return {
|
||||
to: to
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
isExternal,
|
||||
type,
|
||||
linkProps
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,116 +0,0 @@
|
||||
<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-sub-menu
|
||||
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-sub-menu>
|
||||
</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";
|
||||
import { isUrl } from "/@/utils/is.ts";
|
||||
|
||||
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: any) => {
|
||||
if (item.hidden) {
|
||||
// 不显示hidden属性为true的菜单
|
||||
return false;
|
||||
} else {
|
||||
onlyOneChild.value = item;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
if (showingChildren.length === 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (showingChildren.length === 0) {
|
||||
// @ts-ignore
|
||||
onlyOneChild.value = { ...parent, path: "", noShowingChildren: true };
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// const resolvePath = (routePath: string) => {
|
||||
// return path.resolve(props.basePath, routePath);
|
||||
// };
|
||||
|
||||
function resolvePath(routePath) {
|
||||
if (isUrl(routePath)) {
|
||||
return routePath;
|
||||
}
|
||||
if (isUrl(this.basePath)) {
|
||||
return props.basePath;
|
||||
}
|
||||
// @ts-ignore
|
||||
return path.resolve(props.basePath, routePath);
|
||||
}
|
||||
|
||||
return { hasOneShowingChild, resolvePath, onlyOneChild };
|
||||
}
|
||||
});
|
||||
</script>
|
||||
254
src/layout/components/sidebar/horizontal.vue
Normal file
254
src/layout/components/sidebar/horizontal.vue
Normal file
@@ -0,0 +1,254 @@
|
||||
<template>
|
||||
<div class="horizontal-header">
|
||||
<div class="horizontal-header-left" @click="backHome">
|
||||
<i class="fa fa-optin-monster"></i>
|
||||
<h4>{{ settings.title }}</h4>
|
||||
</div>
|
||||
<el-menu
|
||||
:default-active="activeMenu"
|
||||
unique-opened
|
||||
router
|
||||
class="horizontal-header-menu"
|
||||
mode="horizontal"
|
||||
@select="menuSelect"
|
||||
>
|
||||
<sidebar-item
|
||||
v-for="route in routeStore.wholeRoutes"
|
||||
:key="route.path"
|
||||
:item="route"
|
||||
:base-path="route.path"
|
||||
/>
|
||||
</el-menu>
|
||||
<div class="horizontal-header-right">
|
||||
<!-- 全屏 -->
|
||||
<screenfull v-show="!deviceDetection()" />
|
||||
<!-- 国际化 -->
|
||||
<el-dropdown trigger="click">
|
||||
<iconinternationality />
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu class="translation">
|
||||
<el-dropdown-item
|
||||
:style="{
|
||||
background: locale === 'zh' ? '#1b2a47' : '',
|
||||
color: locale === 'zh' ? '#f4f4f5' : '#000'
|
||||
}"
|
||||
@click="translationCh"
|
||||
>简体中文</el-dropdown-item
|
||||
>
|
||||
<el-dropdown-item
|
||||
:style="{
|
||||
background: locale === 'en' ? '#1b2a47' : '',
|
||||
color: locale === 'en' ? '#f4f4f5' : '#000'
|
||||
}"
|
||||
@click="translationEn"
|
||||
>English</el-dropdown-item
|
||||
>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<!-- 退出登陆 -->
|
||||
<el-dropdown trigger="click">
|
||||
<span class="el-dropdown-link">
|
||||
<img
|
||||
src="https://avatars.githubusercontent.com/u/44761321?s=400&u=30907819abd29bb3779bc247910873e7c7f7c12f&v=4"
|
||||
/>
|
||||
<p>{{ usename }}</p>
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu class="logout">
|
||||
<el-dropdown-item icon="el-icon-switch-button" @click="logout">{{
|
||||
$t("message.hsLoginOut")
|
||||
}}</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<i
|
||||
class="el-icon-setting"
|
||||
:title="$t('message.hssystemSet')"
|
||||
@click="onPanel"
|
||||
></i>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
unref,
|
||||
watch,
|
||||
getCurrentInstance
|
||||
} from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import settings from "/@/settings";
|
||||
import { emitter } from "/@/utils/mitt";
|
||||
import SidebarItem from "./sidebarItem.vue";
|
||||
import { algorithm } from "/@/utils/algorithm";
|
||||
import screenfull from "../screenfull/index.vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { storageSession } from "/@/utils/storage";
|
||||
import { deviceDetection } from "/@/utils/deviceDetection";
|
||||
import { usePermissionStoreHook } from "/@/store/modules/permission";
|
||||
import iconinternationality from "/@/assets/svg/iconinternationality.svg";
|
||||
|
||||
let routerArrays: Array<object> = [
|
||||
{
|
||||
path: "/welcome",
|
||||
parentPath: "/",
|
||||
meta: {
|
||||
title: "message.hshome",
|
||||
icon: "el-icon-s-home",
|
||||
showLink: true,
|
||||
savedPosition: false
|
||||
}
|
||||
}
|
||||
];
|
||||
export default defineComponent({
|
||||
name: "sidebar",
|
||||
components: { SidebarItem, screenfull, iconinternationality },
|
||||
// @ts-ignore
|
||||
computed: {
|
||||
// eslint-disable-next-line vue/return-in-computed-property
|
||||
currentLocale() {
|
||||
if (
|
||||
!this.$storage.routesInStorage ||
|
||||
this.$storage.routesInStorage.length === 0
|
||||
) {
|
||||
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
|
||||
this.$storage.routesInStorage = routerArrays;
|
||||
}
|
||||
|
||||
if (!this.$storage.locale) {
|
||||
// eslint-disable-next-line
|
||||
this.$storage.locale = { locale: "zh" };
|
||||
useI18n().locale.value = "zh";
|
||||
}
|
||||
switch (this.$storage.locale?.locale) {
|
||||
case "zh":
|
||||
return true;
|
||||
case "en":
|
||||
return false;
|
||||
}
|
||||
}
|
||||
},
|
||||
setup() {
|
||||
const instance =
|
||||
getCurrentInstance().appContext.config.globalProperties.$storage;
|
||||
const routeStore = usePermissionStoreHook();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const routers = useRouter().options.routes;
|
||||
let usename = storageSession.getItem("info")?.username;
|
||||
const { locale, t } = useI18n();
|
||||
|
||||
watch(
|
||||
() => locale.value,
|
||||
() => {
|
||||
//@ts-ignore
|
||||
// 动态title
|
||||
document.title = t(unref(route.meta.title));
|
||||
}
|
||||
);
|
||||
|
||||
// 退出登录
|
||||
const logout = (): void => {
|
||||
storageSession.removeItem("info");
|
||||
router.push("/login");
|
||||
};
|
||||
|
||||
function onPanel() {
|
||||
emitter.emit("openPanel");
|
||||
}
|
||||
|
||||
const activeMenu = computed(() => {
|
||||
const { meta, path } = route;
|
||||
if (meta.activeMenu) {
|
||||
return meta.activeMenu;
|
||||
}
|
||||
return path;
|
||||
});
|
||||
|
||||
const menuSelect = (indexPath: string): void => {
|
||||
let parentPath = "";
|
||||
let parentPathIndex = indexPath.lastIndexOf("/");
|
||||
if (parentPathIndex > 0) {
|
||||
parentPath = indexPath.slice(0, parentPathIndex);
|
||||
}
|
||||
// 找到当前路由的信息
|
||||
function findCurrentRoute(routes) {
|
||||
return routes.map(item => {
|
||||
if (item.path === indexPath) {
|
||||
// 切换左侧菜单 通知标签页
|
||||
emitter.emit("changLayoutRoute", {
|
||||
indexPath,
|
||||
parentPath
|
||||
});
|
||||
} else {
|
||||
if (item.children) findCurrentRoute(item.children);
|
||||
}
|
||||
});
|
||||
}
|
||||
findCurrentRoute(algorithm.increaseIndexes(routers));
|
||||
};
|
||||
|
||||
function backHome() {
|
||||
router.push("/welcome");
|
||||
}
|
||||
|
||||
// 简体中文
|
||||
function translationCh() {
|
||||
instance.locale = { locale: "zh" };
|
||||
locale.value = "zh";
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
// English
|
||||
function translationEn() {
|
||||
instance.locale = { locale: "en" };
|
||||
locale.value = "en";
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
return {
|
||||
locale,
|
||||
usename,
|
||||
settings,
|
||||
routeStore,
|
||||
activeMenu,
|
||||
logout,
|
||||
onPanel,
|
||||
backHome,
|
||||
menuSelect,
|
||||
translationCh,
|
||||
translationEn,
|
||||
deviceDetection
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.translation {
|
||||
.el-dropdown-menu__item {
|
||||
padding: 0 40px !important;
|
||||
}
|
||||
|
||||
.el-dropdown-menu__item:focus,
|
||||
.el-dropdown-menu__item:not(.is-disabled):hover {
|
||||
color: #606266;
|
||||
background: #f0f0f0;
|
||||
}
|
||||
}
|
||||
|
||||
.logout {
|
||||
.el-dropdown-menu__item {
|
||||
padding: 0 18px !important;
|
||||
}
|
||||
|
||||
.el-dropdown-menu__item:focus,
|
||||
.el-dropdown-menu__item:not(.is-disabled):hover {
|
||||
color: #606266;
|
||||
background: #f0f0f0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,9 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import settings from "/@/settings";
|
||||
|
||||
const props = defineProps({
|
||||
collapse: Boolean
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="sidebar-logo-container" :class="{ collapse: collapse }">
|
||||
<div class="sidebar-logo-container" :class="{ collapse: props.collapse }">
|
||||
<transition name="sidebarLogoFade">
|
||||
<router-link
|
||||
v-if="collapse"
|
||||
key="collapse"
|
||||
v-if="props.collapse"
|
||||
key="props.collapse"
|
||||
:title="settings.title"
|
||||
class="sidebar-logo-link"
|
||||
to="/"
|
||||
@@ -25,25 +33,6 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import settings from "/@/settings";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
collapse: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
settings
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.sidebar-logo-container {
|
||||
position: relative;
|
||||
104
src/layout/components/sidebar/sidebarItem.vue
Normal file
104
src/layout/components/sidebar/sidebarItem.vue
Normal file
@@ -0,0 +1,104 @@
|
||||
<script setup lang="ts">
|
||||
import path from "path";
|
||||
import { PropType, ref } from "vue";
|
||||
import { RouteRecordRaw } from "vue-router";
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object as PropType<RouteRecordRaw>
|
||||
},
|
||||
isNest: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
basePath: {
|
||||
type: String,
|
||||
default: ""
|
||||
}
|
||||
});
|
||||
|
||||
type childrenType = {
|
||||
path?: string;
|
||||
noShowingChildren?: boolean;
|
||||
children?: RouteRecordRaw[];
|
||||
meta?: {
|
||||
icon?: string;
|
||||
title?: string;
|
||||
};
|
||||
};
|
||||
|
||||
const onlyOneChild = ref<RouteRecordRaw | childrenType>({} as any);
|
||||
|
||||
function hasOneShowingChild(
|
||||
children: RouteRecordRaw[] = [],
|
||||
parent: RouteRecordRaw
|
||||
) {
|
||||
const showingChildren = children.filter((item: any) => {
|
||||
if (item.hidden) {
|
||||
// 不显示hidden属性为true的菜单
|
||||
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;
|
||||
}
|
||||
|
||||
function resolvePath(routePath) {
|
||||
return path.resolve(props.basePath, routePath);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<template
|
||||
v-if="
|
||||
hasOneShowingChild(props.item.children, props.item) &&
|
||||
(!onlyOneChild.children || onlyOneChild.noShowingChildren) &&
|
||||
!props.item.alwaysShow
|
||||
"
|
||||
>
|
||||
<el-menu-item
|
||||
:index="resolvePath(onlyOneChild.path)"
|
||||
:class="{ 'submenu-title-noDropdown': !isNest }"
|
||||
>
|
||||
<i
|
||||
:class="
|
||||
onlyOneChild.meta.icon || (props.item.meta && props.item.meta.icon)
|
||||
"
|
||||
/>
|
||||
<template #title>
|
||||
<span>{{ $t(onlyOneChild.meta.title) }}</span>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
|
||||
<el-sub-menu
|
||||
v-else
|
||||
ref="subMenu"
|
||||
:index="resolvePath(props.item.path)"
|
||||
popper-append-to-body
|
||||
>
|
||||
<template #title>
|
||||
<i :class="props.item.meta.icon"></i>
|
||||
<span>{{ $t(props.item.meta.title) }}</span>
|
||||
</template>
|
||||
<sidebar-item
|
||||
v-for="child in props.item.children"
|
||||
:key="child.path"
|
||||
:is-nest="true"
|
||||
:item="child"
|
||||
:base-path="resolvePath(child.path)"
|
||||
class="nest-menu"
|
||||
/>
|
||||
</el-sub-menu>
|
||||
</template>
|
||||
@@ -1,11 +1,12 @@
|
||||
<template>
|
||||
<div :class="{ 'has-logo': showLogo }">
|
||||
<div :class="['sidebar-container', showLogo ? 'has-logo' : '']">
|
||||
<Logo v-if="showLogo === '1'" :collapse="isCollapse" />
|
||||
<el-scrollbar wrap-class="scrollbar-wrapper">
|
||||
<el-menu
|
||||
:default-active="activeMenu"
|
||||
:collapse="isCollapse"
|
||||
unique-opened
|
||||
router
|
||||
:collapse-transition="false"
|
||||
mode="vertical"
|
||||
@select="menuSelect"
|
||||
@@ -22,14 +23,14 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, ref, onBeforeMount } from "vue";
|
||||
import Logo from "./logo.vue";
|
||||
import { emitter } from "/@/utils/mitt";
|
||||
import SidebarItem from "./sidebarItem.vue";
|
||||
import { algorithm } from "/@/utils/algorithm";
|
||||
import { storageLocal } from "/@/utils/storage";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { useAppStoreHook } from "/@/store/modules/app";
|
||||
import SidebarItem from "./SidebarItem.vue";
|
||||
import { algorithm } from "/@/utils/algorithm";
|
||||
import { emitter } from "/@/utils/mitt";
|
||||
import Logo from "./Logo.vue";
|
||||
import { storageLocal } from "/@/utils/storage";
|
||||
import { computed, defineComponent, ref, onBeforeMount } from "vue";
|
||||
import { usePermissionStoreHook } from "/@/store/modules/permission";
|
||||
|
||||
export default defineComponent({
|
||||
@@ -61,6 +62,7 @@ export default defineComponent({
|
||||
parentPath = indexPath.slice(0, parentPathIndex);
|
||||
}
|
||||
// 找到当前路由的信息
|
||||
// eslint-disable-next-line no-inner-declarations
|
||||
function findCurrentRoute(routes) {
|
||||
return routes.map(item => {
|
||||
if (item.path === indexPath) {
|
||||
@@ -1,3 +1,41 @@
|
||||
<script lang="ts">
|
||||
let routerArrays: Array<object> = [
|
||||
{
|
||||
path: "/welcome",
|
||||
parentPath: "/",
|
||||
meta: {
|
||||
title: "message.hshome",
|
||||
icon: "el-icon-s-home",
|
||||
showLink: true,
|
||||
savedPosition: false
|
||||
}
|
||||
}
|
||||
];
|
||||
export default {
|
||||
computed: {
|
||||
layout() {
|
||||
if (!this.$storage.layout) {
|
||||
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
|
||||
this.$storage.layout = { layout: "vertical-dark" };
|
||||
}
|
||||
if (
|
||||
!this.$storage.routesInStorage ||
|
||||
this.$storage.routesInStorage.length === 0
|
||||
) {
|
||||
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
|
||||
this.$storage.routesInStorage = routerArrays;
|
||||
}
|
||||
if (!this.$storage.locale) {
|
||||
// eslint-disable-next-line
|
||||
this.$storage.locale = { locale: "zh" };
|
||||
useI18n().locale.value = "zh";
|
||||
}
|
||||
return this.$storage?.layout.layout;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ref,
|
||||
@@ -10,13 +48,20 @@ import {
|
||||
useCssModule
|
||||
} from "vue";
|
||||
import options from "/@/settings";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { toggleClass } from "/@/utils/operate";
|
||||
import { useEventListener } from "@vueuse/core";
|
||||
import { useAppStoreHook } from "/@/store/modules/app";
|
||||
import fullScreen from "/@/assets/svg/full_screen.svg";
|
||||
import exitScreen from "/@/assets/svg/exit_screen.svg";
|
||||
import { useSettingStoreHook } from "/@/store/modules/settings";
|
||||
import { Navbar, Sidebar, AppMain, setting, tag } from "./components";
|
||||
|
||||
import navbar from "./components/navbar.vue";
|
||||
import tag from "./components/tag/index.vue";
|
||||
import appMain from "./components/appMain.vue";
|
||||
import setting from "./components/setting/index.vue";
|
||||
import Vertical from "./components/sidebar/vertical.vue";
|
||||
import Horizontal from "./components/sidebar/horizontal.vue";
|
||||
|
||||
interface setInter {
|
||||
sidebar: any;
|
||||
@@ -118,19 +163,25 @@ onBeforeMount(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="set.classes" class="app-wrapper">
|
||||
<div :class="['app-wrapper', set.classes]">
|
||||
<div
|
||||
v-if="set.device === 'mobile' && set.sidebar.opened"
|
||||
v-show="
|
||||
set.device === 'mobile' &&
|
||||
set.sidebar.opened &&
|
||||
layout.includes('vertical')
|
||||
"
|
||||
class="drawer-bg"
|
||||
@click="handleClickOutside(false)"
|
||||
/>
|
||||
<!-- 侧边栏 -->
|
||||
<sidebar class="sidebar-container" v-if="!containerHiddenSideBar" />
|
||||
<Vertical v-show="!containerHiddenSideBar && layout.includes('vertical')" />
|
||||
<div class="main-container">
|
||||
<div :class="{ 'fixed-header': set.fixedHeader }">
|
||||
<!-- 顶部导航栏 -->
|
||||
<navbar v-show="!containerHiddenSideBar" />
|
||||
<navbar
|
||||
v-show="!containerHiddenSideBar && layout.includes('vertical')"
|
||||
/>
|
||||
<!-- tabs标签页 -->
|
||||
<Horizontal v-show="layout.includes('horizontal')" />
|
||||
<tag>
|
||||
<span @click="onFullScreen">
|
||||
<fullScreen v-if="!containerHiddenSideBar" />
|
||||
@@ -194,10 +245,6 @@ $sideBarWidth: 210px;
|
||||
transition: width 0.28s;
|
||||
}
|
||||
|
||||
.hideSidebar .fixed-header {
|
||||
width: calc(100% - 54px);
|
||||
}
|
||||
|
||||
.mobile .fixed-header {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user