refactor(ui): 界面升级与代码规范优化
This commit is contained in:
359
src/layouts/DoubleLayout.vue
Normal file
359
src/layouts/DoubleLayout.vue
Normal file
@@ -0,0 +1,359 @@
|
||||
<template>
|
||||
<BaseLayout>
|
||||
<aside
|
||||
v-show="!appStore.contentFullscreen"
|
||||
class="layout__double-sidebar"
|
||||
:class="{ 'layout__double-sidebar--collapsed': !isSidebarOpen }"
|
||||
>
|
||||
<div class="layout__double-primary">
|
||||
<LayoutLogo v-if="showLogo" :collapse="true" />
|
||||
|
||||
<el-scrollbar class="layout__double-primary-scroll">
|
||||
<button
|
||||
v-for="item in topMenuItems"
|
||||
:key="item.path"
|
||||
class="layout__primary-item"
|
||||
:class="{ 'is-active': item.path === activeTopMenuPath }"
|
||||
:title="item.meta?.title ? translateRouteTitle(item.meta.title) : undefined"
|
||||
type="button"
|
||||
@click="handleTopMenuSelect(item.path)"
|
||||
>
|
||||
<MenuIcon :icon="item.meta?.icon" />
|
||||
<span v-if="item.meta?.title">{{ translateRouteTitle(item.meta.title) }}</span>
|
||||
</button>
|
||||
</el-scrollbar>
|
||||
|
||||
<button
|
||||
class="layout__primary-toggle"
|
||||
:title="isSidebarOpen ? '收起菜单' : '展开菜单'"
|
||||
type="button"
|
||||
@click="toggleSidebar"
|
||||
>
|
||||
<svg
|
||||
class="layout__primary-toggle-icon"
|
||||
:class="{ 'is-collapsed': !isSidebarOpen }"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
d="M12 5l-5 5 5 5"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.8"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="layout__double-secondary">
|
||||
<div v-if="showLogo" class="layout__double-title">
|
||||
{{ appConfig.title }}
|
||||
</div>
|
||||
|
||||
<el-scrollbar class="layout__double-secondary-scroll">
|
||||
<LayoutSidebar
|
||||
:data="sideMenuRoutes"
|
||||
:base-path="activeTopMenuPath"
|
||||
:always-expand="true"
|
||||
/>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main
|
||||
class="layout__main"
|
||||
:class="{
|
||||
hasTagsView: showTagsView,
|
||||
'layout__main--collapsed': !isSidebarOpen,
|
||||
'layout__main--fullscreen': appStore.contentFullscreen,
|
||||
}"
|
||||
>
|
||||
<LayoutNavbar v-show="!appStore.contentFullscreen" />
|
||||
<LayoutTagsView v-if="showTagsView" />
|
||||
<LayoutMain />
|
||||
</main>
|
||||
</BaseLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useLayout } from "./useLayout";
|
||||
import { useMixMenu } from "./composables/useMixMenu";
|
||||
import { useAppStore } from "@/stores";
|
||||
import { appConfig } from "@/settings";
|
||||
import { translateRouteTitle } from "@/lang/utils";
|
||||
import BaseLayout from "./BaseLayout.vue";
|
||||
import LayoutLogo from "./components/LayoutLogo.vue";
|
||||
import LayoutNavbar from "./components/LayoutNavbar.vue";
|
||||
import LayoutTagsView from "./components/LayoutTagsView.vue";
|
||||
import LayoutMain from "./components/LayoutMain.vue";
|
||||
import LayoutSidebar from "./components/LayoutSidebar.vue";
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { showTagsView, showLogo, isSidebarOpen, toggleSidebar } = useLayout();
|
||||
|
||||
const { MenuIcon, topMenuItems, activeTopMenuPath, sideMenuRoutes, handleTopMenuSelect } =
|
||||
useMixMenu();
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@use "@/styles/mixins" as *;
|
||||
|
||||
$double-sidebar-width: $sidebar-primary-width + $sidebar-secondary-width;
|
||||
|
||||
.layout {
|
||||
&__double-sidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 999;
|
||||
display: flex;
|
||||
width: $double-sidebar-width;
|
||||
overflow: hidden;
|
||||
background-color: var(--menu-background);
|
||||
border-right: 1px solid var(--menu-border);
|
||||
transition: width 0.28s;
|
||||
|
||||
&--collapsed {
|
||||
width: $sidebar-width-collapsed + $sidebar-secondary-width;
|
||||
|
||||
.layout__double-primary {
|
||||
flex-basis: $sidebar-width-collapsed;
|
||||
width: $sidebar-width-collapsed;
|
||||
}
|
||||
|
||||
.layout__primary-item {
|
||||
gap: 0;
|
||||
min-height: 48px;
|
||||
padding: 8px 4px;
|
||||
|
||||
span {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.layout__primary-toggle-icon {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.layout__double-secondary {
|
||||
flex: 0 0 $sidebar-secondary-width;
|
||||
width: $sidebar-secondary-width;
|
||||
min-width: $sidebar-secondary-width;
|
||||
border-right: 1px solid var(--menu-border);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__double-primary {
|
||||
display: flex;
|
||||
flex: 0 0 $sidebar-primary-width;
|
||||
flex-direction: column;
|
||||
width: $sidebar-primary-width;
|
||||
overflow: hidden;
|
||||
background:
|
||||
linear-gradient(
|
||||
180deg,
|
||||
color-mix(in srgb, var(--menu-background) 94%, var(--el-color-primary) 6%),
|
||||
var(--menu-background)
|
||||
),
|
||||
var(--menu-background);
|
||||
border-right: 1px solid var(--menu-border);
|
||||
transition:
|
||||
flex-basis 0.28s,
|
||||
width 0.28s;
|
||||
|
||||
:deep(.logo) {
|
||||
height: $navbar-height;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
&__double-primary-scroll {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
&__primary-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: calc(100% - 12px);
|
||||
min-height: 64px;
|
||||
padding: 8px 4px;
|
||||
margin: 6px;
|
||||
font: inherit;
|
||||
font-size: 12px;
|
||||
line-height: 1.2;
|
||||
color: var(--menu-text);
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
transition:
|
||||
color 0.18s,
|
||||
background-color 0.18s;
|
||||
|
||||
:deep(.el-icon),
|
||||
:deep([class^="i-svg:"]) {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
font-size: 20px;
|
||||
color: currentcolor !important;
|
||||
}
|
||||
|
||||
span {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--menu-active-text);
|
||||
background-color: var(--menu-hover);
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
color: var(--menu-active-text);
|
||||
background-color: var(--menu-active-bg);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: none;
|
||||
background-color: var(--menu-hover);
|
||||
}
|
||||
}
|
||||
|
||||
&__primary-toggle {
|
||||
display: flex;
|
||||
flex: 0 0 48px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
font: inherit;
|
||||
color: var(--menu-text);
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-top: 1px solid var(--menu-border);
|
||||
transition:
|
||||
color 0.18s,
|
||||
background-color 0.18s;
|
||||
|
||||
&:hover {
|
||||
color: var(--menu-active-text);
|
||||
background-color: var(--menu-hover);
|
||||
}
|
||||
}
|
||||
|
||||
&__primary-toggle-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
transition: transform 0.28s;
|
||||
|
||||
&.is-collapsed {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
&__double-secondary {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
flex-direction: column;
|
||||
width: $sidebar-secondary-width;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
background-color: var(--menu-background);
|
||||
transition: width 0.28s;
|
||||
|
||||
:deep(.el-menu) {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* 恢复 .hideSidebar 被隐藏的文字(第二列始终展开) */
|
||||
:deep(.el-menu-item span),
|
||||
:deep(.el-sub-menu__title span) {
|
||||
visibility: visible !important;
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
:deep(.el-sub-menu__icon-arrow) {
|
||||
display: inline-flex !important;
|
||||
}
|
||||
}
|
||||
|
||||
&__double-secondary-scroll {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
&__double-title {
|
||||
display: flex;
|
||||
flex: 0 0 $navbar-height;
|
||||
align-items: center;
|
||||
min-width: $sidebar-secondary-width;
|
||||
padding: 0 18px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--sidebar-logo-text-color);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__main {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
margin-left: $double-sidebar-width;
|
||||
overflow-y: auto;
|
||||
transition: margin-left 0.28s;
|
||||
|
||||
&--collapsed {
|
||||
margin-left: $sidebar-width-collapsed + $sidebar-secondary-width;
|
||||
}
|
||||
|
||||
&--fullscreen {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.hasTagsView {
|
||||
:deep(.app-main) {
|
||||
@include app-main-height-with-tags;
|
||||
}
|
||||
}
|
||||
|
||||
.mobile {
|
||||
.layout__double-sidebar {
|
||||
width: $double-sidebar-width;
|
||||
transition:
|
||||
transform 0.28s,
|
||||
width 0s;
|
||||
}
|
||||
|
||||
&.hideSidebar {
|
||||
.layout__double-sidebar {
|
||||
transform: translateX(-$double-sidebar-width);
|
||||
}
|
||||
}
|
||||
|
||||
&.openSidebar {
|
||||
.layout__double-sidebar {
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
.layout__main {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -45,7 +45,7 @@ const appStore = useAppStore();
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@use "@/styles/base/mixins" as *;
|
||||
@use "@/styles/mixins" as *;
|
||||
|
||||
.layout {
|
||||
&__sidebar {
|
||||
|
||||
@@ -89,7 +89,6 @@ import LayoutMain from "./components/LayoutMain.vue";
|
||||
import LayoutSidebarItem from "./components/LayoutSidebarItem.vue";
|
||||
import Hamburger from "@/components/Hamburger/index.vue";
|
||||
import variables from "@/styles/variables.module.scss";
|
||||
|
||||
const { width } = useWindowSize();
|
||||
|
||||
const appStore = useAppStore();
|
||||
@@ -123,7 +122,7 @@ const useMenuColors = computed(
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@use "@/styles/base/mixins" as *;
|
||||
@use "@/styles/mixins" as *;
|
||||
|
||||
.layout {
|
||||
&__header {
|
||||
@@ -133,8 +132,7 @@ const useMenuColors = computed(
|
||||
width: 100%;
|
||||
height: $navbar-height;
|
||||
background-color: var(--menu-background);
|
||||
border-bottom: 1px solid var(--card-border);
|
||||
box-shadow: 0 1px 3px 0 rgb(0 0 0 / 6%);
|
||||
border-bottom: 1px solid var(--menu-border);
|
||||
|
||||
&-content {
|
||||
display: flex;
|
||||
@@ -167,16 +165,33 @@ const useMenuColors = computed(
|
||||
|
||||
:deep(.el-menu--horizontal) {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
|
||||
.el-menu-item {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
padding: 0 12px;
|
||||
line-height: $navbar-height;
|
||||
border-bottom: none;
|
||||
border-radius: 0;
|
||||
transition:
|
||||
color 0.16s ease,
|
||||
background-color 0.16s ease,
|
||||
border-color 0.16s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--menu-hover) !important;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
background-color: rgba(255, 255, 255, 0.12);
|
||||
color: var(--el-color-primary) !important;
|
||||
background-color: color-mix(
|
||||
in srgb,
|
||||
var(--el-color-primary) 10%,
|
||||
var(--el-bg-color-overlay)
|
||||
) !important;
|
||||
border-bottom: 2px solid var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
@@ -202,7 +217,7 @@ const useMenuColors = computed(
|
||||
width: $sidebar-width;
|
||||
height: 100%;
|
||||
background-color: var(--menu-background);
|
||||
border-right: 1px solid var(--card-border);
|
||||
border-right: 1px solid var(--menu-border);
|
||||
transition: width 0.28s;
|
||||
|
||||
&.layout__sidebar--collapsed {
|
||||
@@ -228,7 +243,7 @@ const useMenuColors = computed(
|
||||
height: $sidebar-toggle-height;
|
||||
line-height: $sidebar-toggle-height;
|
||||
background-color: var(--menu-background);
|
||||
box-shadow: 0 0 6px -2px var(--el-color-primary);
|
||||
border-top: 1px solid var(--menu-border);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,4 +282,20 @@ const useMenuColors = computed(
|
||||
@include app-main-height-with-tags;
|
||||
}
|
||||
}
|
||||
|
||||
:global(html.dark),
|
||||
:global(html.sidebar-color-blue) {
|
||||
.layout__header-menu {
|
||||
:deep(.el-menu--horizontal) {
|
||||
.el-menu-item.is-active {
|
||||
color: var(--menu-active-text) !important;
|
||||
background-color: color-mix(
|
||||
in srgb,
|
||||
var(--el-color-primary) 18%,
|
||||
var(--menu-background)
|
||||
) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -49,7 +49,7 @@ const isLogoCollapsed = computed(() => width.value < 768);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@use "@/styles/base/mixins" as *;
|
||||
@use "@/styles/mixins" as *;
|
||||
|
||||
.layout {
|
||||
&__header {
|
||||
|
||||
@@ -33,10 +33,10 @@ function toggleSideBar() {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: $navbar-height;
|
||||
padding: 0 16px 0 4px;
|
||||
padding: 0 14px 0 6px;
|
||||
background-color: var(--content-bg);
|
||||
border-bottom: 1px solid var(--card-border);
|
||||
box-shadow: 0 1px 2px 0 rgb(0 0 0 / 3%);
|
||||
box-shadow: 0 1px 0 rgb(15 23 42 / 3%);
|
||||
|
||||
&__actions {
|
||||
display: flex;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@
|
||||
<el-menu
|
||||
ref="menuRef"
|
||||
:default-active="activeMenuPath"
|
||||
:collapse="!appStore.sidebar.opened"
|
||||
:collapse="props.alwaysExpand ? false : !appStore.sidebar.opened"
|
||||
:background-color="menuThemeProps.backgroundColor"
|
||||
:text-color="menuThemeProps.textColor"
|
||||
:active-text-color="menuThemeProps.activeTextColor"
|
||||
@@ -34,7 +34,6 @@ import { useSettingsStore, useAppStore } from "@/stores";
|
||||
import { isExternal } from "@/utils/index";
|
||||
import LayoutSidebarItem from "./LayoutSidebarItem.vue";
|
||||
import variables from "@/styles/variables.module.scss";
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Array as PropType<RouteRecordRaw[]>,
|
||||
@@ -50,6 +49,11 @@ const props = defineProps({
|
||||
default: "vertical",
|
||||
validator: (value: string) => ["vertical", "horizontal"].includes(value),
|
||||
},
|
||||
/** 强制展开,忽略全局 sidebar.opened 状态(DoubleLayout 第二列需要始终展开) */
|
||||
alwaysExpand: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const menuRef = ref<MenuInstance>();
|
||||
|
||||
@@ -284,11 +284,11 @@ html.sidebar-color-blue {
|
||||
// 深色主题下的父菜单激活状态"
|
||||
html.dark & {
|
||||
&.has-active-child > .el-sub-menu__title {
|
||||
color: var(--el-color-primary-light-3) !important;
|
||||
background-color: rgba(64, 128, 255, 0.15) !important;
|
||||
color: var(--menu-active-text) !important;
|
||||
background-color: var(--menu-active-bg) !important;
|
||||
|
||||
.menu-icon {
|
||||
color: var(--el-color-primary-light-3) !important;
|
||||
color: var(--menu-active-text) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -296,11 +296,11 @@ html.sidebar-color-blue {
|
||||
// 深蓝色侧边栏配色下的父菜单激活状态"
|
||||
html.sidebar-color-blue & {
|
||||
&.has-active-child > .el-sub-menu__title {
|
||||
color: var(--el-color-primary-light-3) !important;
|
||||
background-color: rgba(64, 128, 255, 0.2) !important;
|
||||
color: var(--menu-active-text) !important;
|
||||
background-color: var(--menu-active-bg) !important;
|
||||
|
||||
.menu-icon {
|
||||
color: var(--el-color-primary-light-3) !important;
|
||||
color: var(--menu-active-text) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="tags-container">
|
||||
<div :class="['tags-container', tagsStyleClass]">
|
||||
<!-- 页签滚动区 -->
|
||||
<el-scrollbar
|
||||
ref="scrollbarRef"
|
||||
@@ -129,8 +129,10 @@
|
||||
<script setup lang="ts">
|
||||
import { useRoute, useRouter, type RouteRecordRaw } from "vue-router";
|
||||
import { resolve } from "path-browserify";
|
||||
import { TagsViewStyle } from "@/enums";
|
||||
import { translateRouteTitle } from "@/lang/utils";
|
||||
import { useAppStore, usePermissionStore, useTagsViewStore } from "@/stores";
|
||||
import { useAppStore, usePermissionStore, useSettingsStore, useTagsViewStore } from "@/stores";
|
||||
import type { TagView } from "@/stores/tags-view";
|
||||
|
||||
interface ContextMenu {
|
||||
visible: boolean;
|
||||
@@ -143,6 +145,7 @@ const route = useRoute();
|
||||
|
||||
const appStore = useAppStore();
|
||||
const permissionStore = usePermissionStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
const tagsViewStore = useTagsViewStore();
|
||||
|
||||
const { visitedViews } = storeToRefs(tagsViewStore);
|
||||
@@ -161,6 +164,16 @@ const currentTag = computed(() => {
|
||||
return visitedViews.value.find((tag) => tagsViewStore.isActive(tag)) || null;
|
||||
});
|
||||
|
||||
const tagsStyleClass = computed(() => {
|
||||
switch (settingsStore.tagsViewStyle) {
|
||||
case TagsViewStyle.CARD:
|
||||
return "tags-container--card";
|
||||
case TagsViewStyle.LINE:
|
||||
default:
|
||||
return "tags-container--line";
|
||||
}
|
||||
});
|
||||
|
||||
// 图标兼容:判断是否为 Element Plus 图标 (el-icon-xxx)
|
||||
const isEpIcon = (icon: string) => icon.startsWith("el-icon");
|
||||
|
||||
@@ -338,7 +351,8 @@ const handleScroll = (event: WheelEvent) => {
|
||||
const hasHorizontalScroll = scrollWrapper.scrollWidth > scrollWrapper.clientWidth;
|
||||
if (!hasHorizontalScroll) return;
|
||||
|
||||
const deltaY = event.deltaY || -(event as any).wheelDelta || 0;
|
||||
const legacyEvent = event as WheelEvent & { wheelDelta?: number };
|
||||
const deltaY = event.deltaY || -(legacyEvent.wheelDelta ?? 0);
|
||||
const newScrollLeft = scrollWrapper.scrollLeft + deltaY;
|
||||
|
||||
scrollbarRef.value.setScrollLeft(newScrollLeft);
|
||||
@@ -358,7 +372,7 @@ const refreshSelectedTag = (tag: TagView | null) => {
|
||||
const closeSelectedTag = (tag: TagView | null) => {
|
||||
if (!tag) return;
|
||||
|
||||
tagsViewStore.delView(tag).then((result: any) => {
|
||||
tagsViewStore.delView(tag).then((result) => {
|
||||
if (tagsViewStore.isActive(tag)) {
|
||||
tagsViewStore.toLastView(result.visitedViews, tag);
|
||||
}
|
||||
@@ -421,7 +435,7 @@ const closeOtherTags = () => {
|
||||
|
||||
// 关闭所有标签页
|
||||
const closeAllTags = (tag: TagView | null) => {
|
||||
tagsViewStore.delAllViews().then((result: any) => {
|
||||
tagsViewStore.delAllViews().then((result) => {
|
||||
tagsViewStore.toLastView(result.visitedViews, tag || undefined);
|
||||
});
|
||||
};
|
||||
@@ -475,9 +489,9 @@ useContextMenuManager();
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: $tags-view-height;
|
||||
padding: 0 4px 0 8px;
|
||||
padding: 0 12px;
|
||||
background-color: var(--content-bg);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
border-bottom: 1px solid var(--card-border);
|
||||
}
|
||||
|
||||
.scroll-container {
|
||||
@@ -496,31 +510,30 @@ useContextMenuManager();
|
||||
|
||||
.tags-wrapper {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 标签项
|
||||
// 未激活: 透明底,融入白色容器背景
|
||||
// 激活: 浅主色底 + 边框,从背景中"跳"出来
|
||||
// ============================================
|
||||
|
||||
.tags-item {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
flex-shrink: 0;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
height: 30px;
|
||||
padding: 0 14px;
|
||||
height: 26px;
|
||||
padding: 0 12px;
|
||||
font-size: 13px;
|
||||
color: var(--el-text-color-regular);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 8px;
|
||||
background: #f8fafc;
|
||||
border: 1px solid var(--card-border);
|
||||
border-radius: 2px;
|
||||
transition:
|
||||
color 0.15s ease,
|
||||
background-color 0.15s ease,
|
||||
@@ -562,10 +575,10 @@ useContextMenuManager();
|
||||
}
|
||||
}
|
||||
|
||||
// 悬浮态 — 浅灰底浮现
|
||||
&:hover {
|
||||
color: var(--el-text-color-primary);
|
||||
background-color: var(--el-fill-color-light);
|
||||
background-color: var(--content-bg);
|
||||
border-color: var(--el-border-color);
|
||||
|
||||
.tags-item__close {
|
||||
opacity: 1;
|
||||
@@ -576,12 +589,11 @@ useContextMenuManager();
|
||||
}
|
||||
}
|
||||
|
||||
// 激活态 — 浅主色块突出显示
|
||||
&.is-active {
|
||||
font-weight: 500;
|
||||
color: var(--el-color-primary);
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
border-color: var(--el-color-primary-light-7);
|
||||
background-color: var(--content-bg);
|
||||
border-color: var(--el-color-primary-light-5);
|
||||
|
||||
.tags-item__icon {
|
||||
color: var(--el-color-primary);
|
||||
@@ -606,6 +618,78 @@ useContextMenuManager();
|
||||
}
|
||||
}
|
||||
|
||||
.tags-container--line {
|
||||
.tags-wrapper {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.tags-item {
|
||||
height: 100%;
|
||||
padding: 0 8px;
|
||||
color: var(--el-text-color-secondary);
|
||||
background: transparent;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
right: 6px;
|
||||
bottom: 0;
|
||||
left: 6px;
|
||||
height: 2px;
|
||||
content: "";
|
||||
background: var(--el-color-primary);
|
||||
transform: scaleX(0);
|
||||
transform-origin: center;
|
||||
transition: transform 0.16s ease;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--el-text-color-primary);
|
||||
background: var(--el-fill-color-lighter);
|
||||
border-color: transparent;
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
color: var(--el-color-primary);
|
||||
background: transparent;
|
||||
border-color: transparent;
|
||||
|
||||
&::after {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tags-container--card {
|
||||
.tags-wrapper {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.tags-item {
|
||||
height: 28px;
|
||||
padding: 0 10px;
|
||||
color: var(--el-text-color-secondary);
|
||||
background: transparent;
|
||||
border-color: transparent;
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
color: var(--el-text-color-primary);
|
||||
background: var(--el-fill-color-lighter);
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
color: var(--el-color-primary);
|
||||
background: var(--el-color-primary-light-9);
|
||||
border-color: var(--el-color-primary-light-6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 右侧操作按钮
|
||||
// ============================================
|
||||
@@ -613,23 +697,23 @@ useContextMenuManager();
|
||||
.tags-actions {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
gap: 2px;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
padding-left: 4px;
|
||||
padding-left: 8px;
|
||||
|
||||
&__btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
transition: background-color 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--el-fill-color-light);
|
||||
background-color: var(--el-fill-color);
|
||||
}
|
||||
|
||||
:deep(.el-icon),
|
||||
|
||||
@@ -178,23 +178,27 @@ function handleSettingsClick() {
|
||||
<style lang="scss" scoped>
|
||||
.navbar-actions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
min-height: 44px;
|
||||
min-height: 32px;
|
||||
|
||||
&__item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 44px; /* 增加最小点击区域到44px,符合人机交互标准 */
|
||||
height: 44px;
|
||||
padding: 0 8px;
|
||||
min-width: 32px;
|
||||
height: 32px;
|
||||
padding: 0 6px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
border-radius: 8px;
|
||||
transition:
|
||||
background-color 0.16s,
|
||||
color 0.16s;
|
||||
|
||||
// 只对需要居中的子元素生效,不使用通配符避免影响选择器组件
|
||||
> [class^="i-svg:"] {
|
||||
> [class*="i-svg:"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -207,7 +211,7 @@ function handleSettingsClick() {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
:deep(.i-svg\:language) {
|
||||
@@ -220,7 +224,7 @@ function handleSettingsClick() {
|
||||
}
|
||||
|
||||
// 图标样式
|
||||
:deep([class^="i-svg:"]) {
|
||||
:deep([class*="i-svg:"]) {
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
color: var(--el-text-color-secondary);
|
||||
@@ -228,9 +232,9 @@ function handleSettingsClick() {
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--el-fill-color-light);
|
||||
background: var(--el-fill-color);
|
||||
|
||||
:deep([class^="i-svg:"]) {
|
||||
:deep([class*="i-svg:"]) {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
@@ -240,8 +244,8 @@ function handleSettingsClick() {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 44px;
|
||||
padding: 0 8px;
|
||||
height: 32px;
|
||||
padding: 0 6px 0 2px;
|
||||
|
||||
&__avatar {
|
||||
flex-shrink: 0;
|
||||
@@ -252,6 +256,7 @@ function handleSettingsClick() {
|
||||
|
||||
&__name {
|
||||
margin-left: 8px;
|
||||
font-size: 13px;
|
||||
color: var(--el-text-color-regular);
|
||||
white-space: nowrap;
|
||||
transition: color 0.3s;
|
||||
@@ -262,14 +267,14 @@ function handleSettingsClick() {
|
||||
// 白色文字样式(用于深色背景:暗黑主题、顶部布局、混合布局等)
|
||||
.navbar-actions--white-text {
|
||||
.navbar-actions__item {
|
||||
:deep([class^="i-svg:"]) {
|
||||
:deep([class*="i-svg:"]) {
|
||||
color: color-mix(in srgb, var(--el-color-white) 85%, transparent);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: color-mix(in srgb, var(--el-color-white) 10%, transparent);
|
||||
|
||||
:deep([class^="i-svg:"]) {
|
||||
:deep([class*="i-svg:"]) {
|
||||
color: var(--el-color-white);
|
||||
}
|
||||
}
|
||||
@@ -298,14 +303,14 @@ function handleSettingsClick() {
|
||||
// 深色文字样式(用于浅色背景:明亮主题下的左侧布局等)
|
||||
.navbar-actions--dark-text {
|
||||
.navbar-actions__item {
|
||||
:deep([class^="i-svg:"]) {
|
||||
:deep([class*="i-svg:"]) {
|
||||
color: var(--el-text-color-secondary) !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
|
||||
:deep([class^="i-svg:"]) {
|
||||
:deep([class*="i-svg:"]) {
|
||||
color: var(--el-color-primary) !important;
|
||||
}
|
||||
}
|
||||
@@ -333,7 +338,7 @@ function handleSettingsClick() {
|
||||
|
||||
// 确保下拉菜单中的图标不受影响
|
||||
::v-deep(.el-dropdown-menu) {
|
||||
[class^="i-svg:"] {
|
||||
[class*="i-svg:"] {
|
||||
color: var(--el-text-color-regular) !important;
|
||||
|
||||
&:hover {
|
||||
|
||||
@@ -12,6 +12,7 @@ import { LayoutMode } from "@/enums/settings";
|
||||
import LeftLayout from "./LeftLayout.vue";
|
||||
import TopLayout from "./TopLayout.vue";
|
||||
import MixLayout from "./MixLayout.vue";
|
||||
import DoubleLayout from "./DoubleLayout.vue";
|
||||
import Settings from "./components/LayoutSettings.vue";
|
||||
|
||||
const route = useRoute();
|
||||
@@ -26,6 +27,8 @@ const currentLayoutComponent = computed(() => {
|
||||
return TopLayout;
|
||||
case LayoutMode.MIX:
|
||||
return MixLayout;
|
||||
case LayoutMode.DOUBLE:
|
||||
return DoubleLayout;
|
||||
default:
|
||||
return LeftLayout;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user