refactor: tailwindcss样式优化

Former-commit-id: 3ab444012a3b3f81929830d5c73df8c68437cb87
This commit is contained in:
haoxr
2022-12-31 22:37:47 +08:00
parent ba2e6769b4
commit 5beaa84297
21 changed files with 423 additions and 356 deletions

View File

@@ -1,12 +1,15 @@
<template> <template>
<el-breadcrumb class="app-breadcrumb" separator-class="el-icon-arrow-right"> <el-breadcrumb
separator-class="el-icon-arrow-right"
class="h-[50px] flex items-center"
>
<transition-group name="breadcrumb"> <transition-group name="breadcrumb">
<el-breadcrumb-item v-for="(item, index) in breadcrumbs" :key="item.path"> <el-breadcrumb-item v-for="(item, index) in breadcrumbs" :key="item.path">
<span <span
v-if=" v-if="
item.redirect === 'noredirect' || index === breadcrumbs.length - 1 item.redirect === 'noredirect' || index === breadcrumbs.length - 1
" "
class="no-redirect" class="text-[#97a8be]"
>{{ generateTitle(item.meta.title) }}</span >{{ generateTitle(item.meta.title) }}</span
> >
<a v-else @click.prevent="handleLink(item)"> <a v-else @click.prevent="handleLink(item)">
@@ -88,11 +91,6 @@ onBeforeMount(() => {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.el-breadcrumb__inner,
.el-breadcrumb__inner a {
font-weight: 400 !important;
}
.app-breadcrumb.el-breadcrumb { .app-breadcrumb.el-breadcrumb {
display: inline-block; display: inline-block;
font-size: 14px; font-size: 14px;

View File

@@ -1,12 +1,13 @@
<template> <template>
<div style="padding: 0 15px" @click="toggleClick"> <div
@click="toggleClick"
class="px-[15px] hover:bg-gray-50 cursor-pointer h-[50px] leading-[50px]"
>
<svg <svg
:class="{ 'is-active': isActive }" :class="{ 'is-active': isActive }"
class="hamburger" class="hamburger"
viewBox="0 0 1024 1024" viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="64"
height="64"
> >
<path <path
d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z" d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z"
@@ -33,8 +34,6 @@ function toggleClick() {
<style scoped> <style scoped>
.hamburger { .hamburger {
display: inline-block;
vertical-align: middle;
width: 20px; width: 20px;
height: 20px; height: 20px;
} }

View File

@@ -24,7 +24,7 @@ function handleLanguageChange(lang: string) {
trigger="click" trigger="click"
@command="handleLanguageChange" @command="handleLanguageChange"
> >
<div class="lang-select__icon"> <div class="cursor-pointer w-[40px] h-[50px] leading-[50px] text-center">
<svg-icon class-name="international-icon" icon-class="language" /> <svg-icon class-name="international-icon" icon-class="language" />
</div> </div>
<template #dropdown> <template #dropdown>
@@ -42,9 +42,3 @@ function handleLanguageChange(lang: string) {
</template> </template>
</el-dropdown> </el-dropdown>
</template> </template>
<style lang="scss" scoped>
.lang-select__icon {
line-height: 50px;
}
</style>

View File

@@ -1,5 +1,5 @@
<template> <template>
<div> <div class="cursor-pointer w-[40px] h-[50px] leading-[50px] text-center">
<svg-icon <svg-icon
:icon-class="isFullscreen ? 'exit-fullscreen' : 'fullscreen'" :icon-class="isFullscreen ? 'exit-fullscreen' : 'fullscreen'"
@click="toggle" @click="toggle"

View File

@@ -21,7 +21,7 @@ function handleSizeChange(size: string) {
<template> <template>
<el-dropdown trigger="click" @command="handleSizeChange"> <el-dropdown trigger="click" @command="handleSizeChange">
<div style="line-height: 50px"> <div class="cursor-pointerw-[40px] h-[50px] leading-[50px] text-center">
<svg-icon icon-class="size" /> <svg-icon icon-class="size" />
</div> </div>
<template #dropdown> <template #dropdown>

View File

@@ -34,7 +34,6 @@ const symbolId = computed(() => `#${props.prefix}-${props.iconClass}`);
<style scoped> <style scoped>
.svg-icon { .svg-icon {
vertical-align: -0.15em;
overflow: hidden; overflow: hidden;
fill: currentColor; fill: currentColor;
} }

View File

@@ -40,12 +40,3 @@ const tagsViewStore = useTagsViewStore();
} }
} }
</style> </style>
<style lang="scss">
// fix css style bug in open el-dialog
.el-popup-parent--hidden {
.fixed-header {
padding-right: 15px;
}
}
</style>

View File

@@ -3,20 +3,23 @@ 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 Breadcrumb from '@/components/Breadcrumb/index.vue';
import Hamburger from '@/components/Hamburger/index.vue'; import Hamburger from '@/components/Hamburger/index.vue';
import Breadcrumb from '@/components/Breadcrumb/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 MixNav from './Sidebar/MixNav.vue';
import { CaretBottom } from '@element-plus/icons-vue'; import { CaretBottom } from '@element-plus/icons-vue';
import { useAppStore, DeviceType } from '@/store/modules/app'; import { useAppStore } from '@/store/modules/app';
import { useTagsViewStore } from '@/store/modules/tagsView'; import { useTagsViewStore } from '@/store/modules/tagsView';
import { useUserStore } from '@/store/modules/user'; import { useUserStore } from '@/store/modules/user';
import { useSettingsStore } from '@/store/modules/settings';
const appStore = useAppStore(); const appStore = useAppStore();
const tagsViewStore = useTagsViewStore(); const tagsViewStore = useTagsViewStore();
const userStore = useUserStore(); const userStore = useUserStore();
const settingsStore = useSettingsStore();
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
@@ -47,31 +50,44 @@ function logout() {
<template> <template>
<div class="navbar"> <div class="navbar">
<div
class="flex justify-start"
v-if="device === 'mobile' || settingsStore.layout === 'left'"
>
<hamburger <hamburger
id="hamburger-container"
:is-active="appStore.sidebar.opened" :is-active="appStore.sidebar.opened"
class="hamburger-container"
@toggleClick="toggleSideBar" @toggleClick="toggleSideBar"
/> />
<!-- 面包屑导航栏 -->
<breadcrumb />
</div>
<breadcrumb id="breadcrumb-container" class="breadcrumb-container" /> <mix-nav v-if="device !== 'mobile' && settingsStore.layout === 'mix'" />
<div class="right-menu"> <div
<template v-if="device !== DeviceType.mobile"> v-if="device === 'mobile' || settingsStore.layout === 'left'"
<screenfull id="screenfull" class="right-menu-item hover-effect" /> class="flex justify-start"
<el-tooltip content="布局大小" effect="dark" placement="bottom">
<size-select id="size-select" class="right-menu-item hover-effect" />
</el-tooltip>
<lang-select class="right-menu-item hover-effect" />
</template>
<el-dropdown
class="avatar-container right-menu-item hover-effect"
trigger="click"
> >
<div class="avatar-wrapper"> <div v-if="device !== 'mobile'" class="flex justify-center items-center">
<img :src="userStore.avatar + '?imageView2/1/w/80/h/80'" /> <!--全屏 -->
<CaretBottom style="width: 0.6em; height: 0.6em; margin-left: 5px" /> <screenfull id="screenfull" />
<!-- 布局大小 -->
<el-tooltip content="布局大小" effect="dark" placement="bottom">
<size-select />
</el-tooltip>
<!--语言选择-->
<lang-select />
</div>
<el-dropdown trigger="click">
<div class="flex justify-center items-center pr-[20px]">
<img
:src="userStore.avatar + '?imageView2/1/w/80/h/80'"
class="w-[40px] h-[40px] rounded-lg"
/>
<CaretBottom class="w-3 h-3" />
</div> </div>
<template #dropdown> <template #dropdown>
@@ -99,85 +115,15 @@ function logout() {
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
ul { .el-dropdown {
list-style: none; font-size: 18px;
margin: 0;
padding: 0;
} }
.navbar { .navbar {
height: 50px; height: 50px;
overflow: hidden; display: flex;
position: relative; align-items: center;
background: #fff; justify-content: space-between;
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08); box-shadow: 0 0px 2px rgba(0, 0, 0, 0.2);
.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;
height: 100%;
line-height: 50px;
&:focus {
outline: none;
}
.right-menu-item {
display: inline-block;
padding: 0 8px;
height: 100%;
font-size: 18px;
color: #5a5e66;
vertical-align: text-bottom;
&.hover-effect {
cursor: pointer;
transition: background 0.3s;
&:hover {
background: rgba(0, 0, 0, 0.025);
}
}
}
.avatar-container {
margin-right: 30px;
.avatar-wrapper {
margin-top: 5px;
position: relative;
img {
cursor: pointer;
width: 40px;
height: 40px;
border-radius: 10px;
}
.el-icon-caret-bottom {
cursor: pointer;
position: absolute;
right: -20px;
top: 25px;
font-size: 12px;
}
}
}
}
} }
</style> </style>

View File

@@ -1,25 +1,34 @@
<script setup lang="ts"> <script setup lang="ts">
import { Sunny, Moon } from '@element-plus/icons-vue';
import { useSettingsStore } from '@/store/modules/settings'; import { useSettingsStore } from '@/store/modules/settings';
import ThemePicker from '@/components/ThemePicker/index.vue'; import { useDark, useToggle } from '@vueuse/core';
import { ElDivider, ElSwitch, ElTooltip } from 'element-plus';
import { onMounted } from 'vue';
const settingsStore = useSettingsStore(); const settingsStore = useSettingsStore();
const isDark = useDark();
function themeChange(val: string) { function toggleTheme() {
settingsStore.changeSetting({ key: 'theme', value: val }); const isDark = useDark();
useToggle(isDark);
}
onMounted(() => {
window.document.body.setAttribute('layout', settingsStore.layout);
});
function changeLayout(layout: string) {
settingsStore.changeSetting({ key: 'layout', value: layout });
window.document.body.setAttribute('layout', settingsStore.layout);
} }
</script> </script>
<template> <template>
<div class="drawer-container"> <div class="settings-container">
<h3 class="drawer-title">系统布局配置</h3> <h3 class="text-base font-bold">项目配置</h3>
<div class="drawer-item"> <el-divider />
<span>主题颜色</span>
<div style="float: right; height: 26px; margin: -3px 8px 0 0">
<theme-picker @change="themeChange" />
</div>
</div>
<div class="drawer-item"> <div class="drawer-item">
<span>开启 Tags-View</span> <span>开启 Tags-View</span>
<el-switch v-model="settingsStore.tagsView" class="drawer-switch" /> <el-switch v-model="settingsStore.tagsView" class="drawer-switch" />
@@ -35,23 +44,53 @@ function themeChange(val: string) {
<el-switch v-model="settingsStore.sidebarLogo" class="drawer-switch" /> <el-switch v-model="settingsStore.sidebarLogo" class="drawer-switch" />
</div> </div>
<el-divider>导航栏模式</el-divider> <el-divider>主题</el-divider>
<ul class="navbar"> <div class="flex justify-center" @click.stop>
<el-switch
v-model="isDark"
inline-prompt
@change="toggleTheme"
:active-icon="Sunny"
:inactive-icon="Moon"
/>
</div>
<el-divider>导航栏布局</el-divider>
<ul class="layout">
<el-tooltip content="左侧模式" placement="bottom"> <el-tooltip content="左侧模式" placement="bottom">
<li class="navbar__item navbar__item--left"> <li
:class="
'layout-item layout-left ' +
(settingsStore.layout == 'left' ? 'is-active' : '')
"
@click="changeLayout('left')"
>
<div /> <div />
<div /> <div />
</li> </li>
</el-tooltip> </el-tooltip>
<el-tooltip content="顶部模式" placement="bottom"> <el-tooltip content="顶部模式" placement="bottom">
<li class="navbar__item navbar__item--top"> <li
:class="
'layout-item layout-top ' +
(settingsStore.layout == 'top' ? 'is-active' : '')
"
@click="changeLayout('top')"
>
<div /> <div />
<div /> <div />
</li> </li>
</el-tooltip> </el-tooltip>
<el-tooltip content="混合模式" placement="bottom"> <el-tooltip content="混合模式" placement="bottom">
<li class="navbar__item navbar__item--mix"> <li
:class="
'layout-item layout-mix ' +
(settingsStore.layout == 'mix' ? 'is-active' : '')
"
@click="changeLayout('mix')"
>
<div /> <div />
<div /> <div />
</li> </li>
@@ -61,11 +100,9 @@ function themeChange(val: string) {
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.drawer-container { .settings-container {
padding: 24px; padding: 16px;
font-size: 14px; font-size: 14px;
line-height: 1.5;
word-wrap: break-word;
.drawer-title { .drawer-title {
margin-bottom: 12px; margin-bottom: 12px;
@@ -84,16 +121,7 @@ function themeChange(val: string) {
float: right; float: right;
} }
.job-link { .layout {
display: block;
position: absolute;
width: 100%;
left: 0;
bottom: 0;
}
}
.navbar {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: space-around; justify-content: space-around;
@@ -101,7 +129,7 @@ function themeChange(val: string) {
height: 50px; height: 50px;
padding: 0; padding: 0;
&__item { &-item {
width: 18%; width: 18%;
height: 45px; height: 45px;
background: #f0f2f5; background: #f0f2f5;
@@ -109,9 +137,11 @@ function themeChange(val: string) {
overflow: hidden; overflow: hidden;
cursor: pointer; cursor: pointer;
border-radius: 4px; border-radius: 4px;
box-shadow: 0 1px 2.5px 0 rgb(0 0 0 / 18%); }
&-item.is-active {
&--left { border: 2px solid var(--el-color-primary);
}
&-left {
div { div {
&:nth-child(1) { &:nth-child(1) {
width: 30%; width: 30%;
@@ -131,7 +161,7 @@ function themeChange(val: string) {
} }
} }
&--top { &-top {
div { div {
&:nth-child(1) { &:nth-child(1) {
width: 100%; width: 100%;
@@ -142,7 +172,7 @@ function themeChange(val: string) {
} }
} }
&--mix { &-mix {
div { div {
&:nth-child(1) { &:nth-child(1) {
width: 100%; width: 100%;
@@ -156,7 +186,7 @@ function themeChange(val: string) {
height: 70%; height: 70%;
bottom: 0; bottom: 0;
left: 0; left: 0;
background: #fff; background: #1b2a47;
box-shadow: 0 0 1px #888; box-shadow: 0 0 1px #888;
position: absolute; position: absolute;
} }

View File

@@ -1,5 +1,8 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { useSettingsStore } from '@/store/modules/settings';
const settingsStore = useSettingsStore();
defineProps({ defineProps({
collapse: { collapse: {
@@ -14,71 +17,25 @@ const logo = ref<string>(
</script> </script>
<template> <template>
<div class="sidebar-logo-container" :class="{ collapse: collapse }"> <transition class="bg-gray-800">
<transition name="sidebarLogoFade">
<router-link <router-link
v-if="collapse" v-if="collapse"
key="collapse" key="collapse"
class="sidebar-logo-link" class="h-[50px] flex items-center justify-center"
to="/" to="/"
> >
<img v-if="logo" :src="logo" class="sidebar-logo" /> <img v-if="settingsStore.sidebarLogo" :src="logo" class="w-5 h-5" />
<h1 v-else class="sidebar-title">vue3-element-admin</h1> <h1 v-else>vue3-element-admin</h1>
</router-link> </router-link>
<router-link v-else key="expand" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo" /> <router-link
<h1 class="sidebar-title">vue3-element-admin</h1> v-else
key="expand"
class="h-[50px] flex items-center justify-center"
to="/"
>
<img v-if="settingsStore.sidebarLogo" :src="logo" class="w-5 h-5" />
<span class="ml-3 text-white text-sm font-bold">vue3-element-admin</span>
</router-link> </router-link>
</transition> </transition>
</div>
</template> </template>
<style lang="scss" scoped>
.sidebarLogoFade-enter-active {
transition: opacity 1.5s;
}
.sidebarLogoFade-enter,
.sidebarLogoFade-leave-to {
opacity: 0;
}
.sidebar-logo-container {
position: relative;
width: 100%;
height: 50px;
line-height: 50px;
background: #2b2f3a;
text-align: center;
overflow: hidden;
& .sidebar-logo-link {
height: 100%;
width: 100%;
& .sidebar-logo {
width: 20px;
height: 20px;
vertical-align: middle;
}
& .sidebar-title {
display: inline-block;
margin: 0;
color: #fff;
font-weight: 600;
line-height: 50px;
font-size: 14px;
font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
vertical-align: middle;
margin-left: 12px;
}
}
&.collapse {
.sidebar-logo {
margin-right: 0px;
}
}
}
</style>

View File

@@ -0,0 +1,153 @@
<script setup lang="ts">
import { computed, onMounted } from 'vue';
import { RouterLink, useRoute, useRouter, RouteRecordRaw } from 'vue-router';
import {
ElDropdown,
ElDropdownItem,
ElDropdownMenu,
ElMenu,
ElMessageBox,
ElTooltip
} from 'element-plus';
import Screenfull from '@/components/Screenfull/index.vue';
import SizeSelect from '@/components/SizeSelect/index.vue';
import LangSelect from '@/components/LangSelect/index.vue';
import { CaretBottom } from '@element-plus/icons-vue';
import SidebarItem from './SidebarItem.vue';
import variables from '@/styles/variables.module.scss';
import { useTagsViewStore } from '@/store/modules/tagsView';
import { useUserStore } from '@/store/modules/user';
import { usePermissionStore } from '@/store/modules/permission';
const tagsViewStore = useTagsViewStore();
const userStore = useUserStore();
const permissionStore = usePermissionStore();
const route = useRoute();
const router = useRouter();
const routes = [] as any[];
onMounted(() => {
console.log('origin routes', permissionStore.routes);
permissionStore.routes.forEach(item => {
const { children, ...newItem } = item;
routes.push(newItem);
});
console.log('routes', routes);
});
const activeMenu = computed<string>(() => {
const { meta, path } = route;
if (meta?.activeMenu) {
return meta.activeMenu as string;
}
return path;
});
function logout() {
ElMessageBox.confirm('确定注销并退出系统吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
userStore
.logout()
.then(() => {
tagsViewStore.delAllViews();
})
.then(() => {
router.push(`/login?redirect=${route.fullPath}`);
});
});
}
</script>
<template>
<div class="horizontal-header">
<el-menu
class="horizontal-header-menu"
:default-active="activeMenu"
:background-color="variables.menuBg"
:text-color="variables.menuText"
:active-text-color="variables.menuActiveText"
mode="horizontal"
>
<sidebar-item
v-for="route in routes"
:item="route"
:key="route.path"
:base-path="route.path"
/>
</el-menu>
<div class="horizontal-header-right">
<!--全屏 -->
<screenfull id="screenfull" />
<!-- 布局大小 -->
<el-tooltip content="布局大小" effect="dark" placement="bottom">
<size-select />
</el-tooltip>
<!--语言选择-->
<lang-select />
<el-dropdown trigger="click">
<div class="flex justify-center items-center pr-[20px]">
<img
:src="userStore.avatar + '?imageView2/1/w/80/h/80'"
class="w-[40px] h-[40px] rounded-lg"
/>
<CaretBottom class="w-3 h-3" />
</div>
<template #dropdown>
<el-dropdown-menu>
<router-link to="/">
<el-dropdown-item>{{ $t('navbar.dashboard') }}</el-dropdown-item>
</router-link>
<a target="_blank" href="https://github.com/hxrui">
<el-dropdown-item>Github</el-dropdown-item>
</a>
<a target="_blank" href="https://gitee.com/haoxr">
<el-dropdown-item>{{ $t('navbar.gitee') }}</el-dropdown-item>
</a>
<a target="_blank" href="https://www.cnblogs.com/haoxianrui/">
<el-dropdown-item>{{ $t('navbar.document') }}</el-dropdown-item>
</a>
<el-dropdown-item divided @click="logout">
{{ $t('navbar.logout') }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</template>
<style lang="scss" scoped>
.horizontal-header {
display: flex;
width: 100%;
align-items: center;
justify-content: space-around;
background: #001529;
&-menu {
height: 100%;
width: 100%;
border: none;
background-color: transparent;
}
&-right {
display: flex;
min-width: 340px;
align-items: center;
justify-content: flex-end;
color: #fff;
}
}
</style>

View File

@@ -86,13 +86,14 @@ function resolvePath(routePath: string) {
</el-menu-item> </el-menu-item>
</app-link> </app-link>
</template> </template>
<el-sub-menu v-else :index="resolvePath(item.path)" popper-append-to-body> <el-sub-menu v-else :index="resolvePath(item.path)" popper-append-to-body>
<!-- popper-append-to-body --> <!-- popper-append-to-body -->
<template #title> <template #title>
<svg-icon <svg-icon
v-if="item.meta && item.meta.icon" v-if="item.meta && item.meta.icon"
:icon-class="item.meta.icon" :icon-class="item.meta.icon"
></svg-icon> />
<span v-if="item.meta && item.meta.title">{{ <span v-if="item.meta && item.meta.title">{{
generateTitle(item.meta.title) generateTitle(item.meta.title)
}}</span> }}</span>
@@ -104,10 +105,7 @@ function resolvePath(routePath: string) {
:item="child" :item="child"
:is-nest="true" :is-nest="true"
:base-path="resolvePath(child.path)" :base-path="resolvePath(child.path)"
class="nest-menu"
/> />
</el-sub-menu> </el-sub-menu>
</div> </div>
</template> </template>
<style lang="scss" scoped></style>

View File

@@ -54,7 +54,7 @@ function moveToTarget(currentTag: TagView) {
} 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-item');
const currentIndex = tagsViewStore.visitedViews.findIndex( const currentIndex = tagsViewStore.visitedViews.findIndex(
item => item === currentTag item => item === currentTag
); );

View File

@@ -237,10 +237,12 @@ onMounted(() => {
</script> </script>
<template> <template>
<div class="tags-view__container"> <div
class="h-[34px] w-full border-b-[1px] border-gray-200 shadow-lg shadow-[rgba(0, 21, 41, 0.08)]"
>
<scroll-pane <scroll-pane
ref="scrollPaneRef" ref="scrollPaneRef"
class="tags-view__wrapper" class="tags-container"
@scroll="handleScroll" @scroll="handleScroll"
> >
<router-link <router-link
@@ -249,24 +251,26 @@ onMounted(() => {
:data-path="tag.path" :data-path="tag.path"
:class="isActive(tag) ? 'active' : ''" :class="isActive(tag) ? 'active' : ''"
:to="{ path: tag.path, query: tag.query }" :to="{ path: tag.path, query: tag.query }"
class="tags-view__item" class="tags-item"
@click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''" @click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''"
@contextmenu.prevent="openMenu(tag, $event)" @contextmenu.prevent="openMenu(tag, $event)"
> >
{{ generateTitle(tag.meta?.title) }} {{ generateTitle(tag.meta?.title) }}
<span <span
v-if="!isAffix(tag)" v-if="!isAffix(tag)"
class="icon-close" class="tags-item-remove"
@click.prevent.stop="closeSelectedTag(tag)" @click.prevent.stop="closeSelectedTag(tag)"
> >
<svg-icon icon-class="close" /> <svg-icon icon-class="close" />
</span> </span>
</router-link> </router-link>
</scroll-pane> </scroll-pane>
<ul <ul
v-show="visible" v-show="visible"
:style="{ left: left + 'px', top: top + 'px' }" :style="{ left: left + 'px', top: top + 'px' }"
class="tags-view__menu" class="tags-item-menu"
> >
<li @click="refreshSelectedTag(selectedTag)"> <li @click="refreshSelectedTag(selectedTag)">
<svg-icon icon-class="refresh" /> <svg-icon icon-class="refresh" />
@@ -297,27 +301,14 @@ onMounted(() => {
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.tags-view__container { .tags-container {
height: 34px; .tags-item {
width: 100%;
background: #fff;
border-bottom: 1px solid #d8dce5;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12), 0 0 3px 0 rgba(0, 0, 0, 0.04);
.tags-view__wrapper {
.tags-view__item {
display: inline-block; display: inline-block;
position: relative;
cursor: pointer; cursor: pointer;
height: 26px;
line-height: 26px;
border: 1px solid #d8dce5; border: 1px solid #d8dce5;
color: #495060; padding: 3px 8px;
background: #fff;
padding: 0 8px;
font-size: 12px; font-size: 12px;
margin-left: 5px; margin: 4px 0 0 5px;
margin-top: 4px;
&:first-of-type { &:first-of-type {
margin-left: 15px; margin-left: 15px;
@@ -347,40 +338,30 @@ onMounted(() => {
} }
} }
.icon-close { &-remove {
border-radius: 50%; border-radius: 50%;
text-align: center;
&:hover { &:hover {
background-color: #ccc;
color: #fff; color: #fff;
background-color: #ccc;
} }
} }
} }
} }
.tags-view__menu { .tags-item-menu {
margin: 0;
background: #fff; background: #fff;
z-index: 3000; z-index: 99;
position: absolute; position: absolute;
list-style-type: none;
padding: 5px 0;
border-radius: 4px; border-radius: 4px;
font-size: 12px; font-size: 12px;
font-weight: 400;
color: #333;
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3); box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
li { li {
margin: 0; padding: 8px 16px;
padding: 7px 16px;
cursor: pointer; cursor: pointer;
&:hover { &:hover {
background: #eee; background: #eee;
} }
} }
} }
}
</style> </style>

View File

@@ -30,15 +30,15 @@ const classObj = computed(() => ({
hideSidebar: !appStore.sidebar.opened, hideSidebar: !appStore.sidebar.opened,
openSidebar: appStore.sidebar.opened, openSidebar: appStore.sidebar.opened,
withoutAnimation: appStore.sidebar.withoutAnimation, withoutAnimation: appStore.sidebar.withoutAnimation,
mobile: appStore.device === DeviceType.mobile mobile: appStore.device === 'mobile'
})); }));
watchEffect(() => { watchEffect(() => {
if (width.value < WIDTH) { if (width.value < WIDTH) {
appStore.toggleDevice(DeviceType.mobile); appStore.toggleDevice('mobile');
appStore.closeSideBar(true); appStore.closeSideBar(true);
} else { } else {
appStore.toggleDevice(DeviceType.desktop); appStore.toggleDevice('desktop');
if (width.value >= 1200) { if (width.value >= 1200) {
//大屏 //大屏
@@ -59,10 +59,11 @@ function handleOutsideClick() {
<!-- 手机设备 && 侧边栏 显示遮罩层 --> <!-- 手机设备 && 侧边栏 显示遮罩层 -->
<div <div
v-if="classObj.mobile && classObj.openSidebar" v-if="classObj.mobile && classObj.openSidebar"
class="drawer-bg"
@click="handleOutsideClick" @click="handleOutsideClick"
></div> ></div>
<Sidebar class="sidebar-container" /> <Sidebar class="sidebar-container" />
<div :class="{ hasTagsView: showTagsView }" class="main-container"> <div :class="{ hasTagsView: showTagsView }" class="main-container">
<div :class="{ 'fixed-header': fixedHeader }"> <div :class="{ 'fixed-header': fixedHeader }">
<navbar /> <navbar />

View File

@@ -5,6 +5,8 @@ interface DefaultSettings {
fixedHeader: boolean; fixedHeader: boolean;
sidebarLogo: boolean; sidebarLogo: boolean;
errorLog: string; errorLog: string;
layout: string;
theme: string;
} }
const defaultSettings: DefaultSettings = { const defaultSettings: DefaultSettings = {
@@ -13,7 +15,9 @@ const defaultSettings: DefaultSettings = {
tagsView: true, tagsView: true,
fixedHeader: false, fixedHeader: false,
sidebarLogo: true, sidebarLogo: true,
errorLog: 'production' errorLog: 'production',
layout: 'left',
theme: 'light'
}; };
export default defaultSettings; export default defaultSettings;

View File

@@ -8,6 +8,7 @@ import {
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { getLanguage } from '@/lang/index'; import { getLanguage } from '@/lang/index';
import { computed, reactive, ref } from 'vue'; import { computed, reactive, ref } from 'vue';
import { useStorage } from '@vueuse/core';
// Element Plus 语言包 // Element Plus 语言包
import zhCn from 'element-plus/es/locale/lang/zh-cn'; import zhCn from 'element-plus/es/locale/lang/zh-cn';
@@ -27,7 +28,7 @@ export enum SizeType {
// setup // setup
export const useAppStore = defineStore('app', () => { export const useAppStore = defineStore('app', () => {
// state // state
const device = ref<DeviceType>(DeviceType.desktop); const device = useStorage<string>('device', 'desktop');
const size = ref(getSize() || 'default'); const size = ref(getSize() || 'default');
const language = ref(getLanguage()); const language = ref(getLanguage());
const sidebar = reactive({ const sidebar = reactive({
@@ -66,7 +67,7 @@ export const useAppStore = defineStore('app', () => {
setSidebarStatus('opened'); setSidebarStatus('opened');
} }
function toggleDevice(val: DeviceType) { function toggleDevice(val: string) {
device.value = val; device.value = val;
} }

View File

@@ -1,26 +1,38 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import defaultSettings from '../../settings'; import defaultSettings from '../../settings';
import { ref } from 'vue'; import { ref } from 'vue';
import { useCssVar, useStorage } from '@vueuse/core'; import { useStorage } from '@vueuse/core';
const el = document.documentElement; /**
* 主题类型
*/
export enum ThemeType {
light,
dark
}
/**
* 布局类型
*/
export enum LayoutType {
left,
top,
mix
}
export const useSettingsStore = defineStore('setting', () => { export const useSettingsStore = defineStore('setting', () => {
// state // state
const theme = useStorage('theme', useCssVar('--el-color-primary', el))
const showSettings = ref<boolean>(defaultSettings.showSettings); const showSettings = ref<boolean>(defaultSettings.showSettings);
const tagsView = useStorage<boolean>('tagsView', defaultSettings.tagsView) const tagsView = useStorage<boolean>('tagsView', defaultSettings.tagsView);
const fixedHeader = ref<boolean>(defaultSettings.fixedHeader); const fixedHeader = ref<boolean>(defaultSettings.fixedHeader);
const sidebarLogo = ref<boolean>(defaultSettings.sidebarLogo); const sidebarLogo = ref<boolean>(defaultSettings.sidebarLogo);
const layout = useStorage<string>('layout', defaultSettings.layout);
// actions // actions
function changeSetting(param: { key: string; value: any }) { function changeSetting(param: { key: string; value: any }) {
const { key, value } = param; const { key, value } = param;
switch (key) { switch (key) {
case 'theme':
theme.value = value;
break;
case 'showSettings': case 'showSettings':
showSettings.value = value; showSettings.value = value;
break; break;
@@ -33,17 +45,20 @@ export const useSettingsStore = defineStore('setting', () => {
case 'sidevarLogo': case 'sidevarLogo':
sidebarLogo.value = value; sidebarLogo.value = value;
break; break;
case 'layout':
layout.value = value;
break;
default: default:
break; break;
} }
} }
return { return {
theme,
showSettings, showSettings,
tagsView, tagsView,
fixedHeader, fixedHeader,
sidebarLogo, sidebarLogo,
layout,
changeSetting changeSetting
}; };
}); });

View File

@@ -20,7 +20,7 @@
</div> </div>
<!-- 数据表格 --> <!-- 数据表格 -->
<el-card> <el-card shadow="never">
<template #header> <template #header>
<el-button type="success" :icon="Plus" @click="handleAdd" <el-button type="success" :icon="Plus" @click="handleAdd"
>新增</el-button >新增</el-button

View File

@@ -245,7 +245,7 @@ onMounted(() => {
</el-form> </el-form>
</div> </div>
<el-card> <el-card shadow="never">
<template #header> <template #header>
<el-button type="success" :icon="Plus" @click="handleAdd" <el-button type="success" :icon="Plus" @click="handleAdd"
>新增</el-button >新增</el-button
@@ -269,19 +269,19 @@ onMounted(() => {
border border
> >
<el-table-column type="selection" width="55" align="center" /> <el-table-column type="selection" width="55" align="center" />
<el-table-column label="角色名称" prop="name" min-width="150" /> <el-table-column label="角色名称" prop="name" min-width="120" />
<el-table-column label="角色编码" prop="code" width="150" /> <el-table-column label="角色编码" prop="code" width="100" />
<el-table-column label="状态" align="center" width="150"> <el-table-column label="状态" align="center" width="100">
<template #default="scope"> <template #default="scope">
<el-tag v-if="scope.row.status === 1" type="success">正常</el-tag> <el-tag v-if="scope.row.status === 1" type="success">正常</el-tag>
<el-tag v-else type="info">禁用</el-tag> <el-tag v-else type="info">禁用</el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="排序" align="center" width="100" prop="sort" /> <el-table-column label="排序" align="center" width="80" prop="sort" />
<el-table-column prop="createTime" label="创建时间" width="160" /> <el-table-column prop="createTime" label="创建时间" width="180" />
<el-table-column prop="updateTime" label="修改时间" width="160" /> <el-table-column prop="updateTime" label="修改时间" width="180" />
<el-table-column label="操作" align="left"> <el-table-column label="操作" align="left">
<template #default="scope"> <template #default="scope">

View File

@@ -462,7 +462,7 @@ onMounted(() => {
<el-row :gutter="20"> <el-row :gutter="20">
<!-- 部门树 --> <!-- 部门树 -->
<el-col :span="4" :xs="24"> <el-col :span="4" :xs="24">
<el-card class="box-card"> <el-card shadow="never">
<el-input <el-input
v-model="deptName" v-model="deptName"
placeholder="部门名称" placeholder="部门名称"
@@ -517,7 +517,7 @@ onMounted(() => {
</el-form> </el-form>
</div> </div>
<el-card> <el-card shadow="never">
<template #header> <template #header>
<el-form-item style="float: left"> <el-form-item style="float: left">
<el-button <el-button