Pre Merge pull request !280 from Lau/6.X-Vue

This commit is contained in:
Lau 2026-07-05 10:29:34 +00:00 committed by Gitee
commit d398b1b9dc
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
8 changed files with 399 additions and 109 deletions

View File

@ -189,6 +189,7 @@ html.dark {
// --- tags-view app 壳变量暗色 ---
--tags-view-active-bg: var(--el-color-primary-dark-6);
--tags-view-active-border-color: var(--el-color-primary-light-2);
--tags-view-chrome-tab-hover-bg: color-mix(in srgb, var(--el-color-primary) 14%, var(--app-surface-bg));
--app-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.28);
--app-shadow-md: 0 12px 28px rgba(0, 0, 0, 0.28);

View File

@ -24,6 +24,7 @@
--tags-view-active-bg: var(--el-color-primary);
--tags-view-active-border-color: var(--el-color-primary);
--tags-view-chrome-tab-hover-bg: var(--el-color-primary-light-9);
// --- 应用壳圆角阴影表面导航强调色与文案色 ---
--app-radius-sm: calc(var(--app-radius-base) - 8px);

View File

@ -0,0 +1,13 @@
/**
*
*/
export enum TagsStyleEnum {
/**
*
*/
BUTTON = 'button',
/**
*
*/
CHROME = 'chrome'
}

View File

@ -133,6 +133,36 @@
</span>
</div>
<div class="drawer-item tags-style-item">
<span>标签页风格</span>
</div>
<div class="tags-style-wrap" :class="{ 'is-disabled': !settingsStore.tagsView }">
<el-tooltip content="按钮风格" placement="bottom">
<div
class="item button"
:style="{ '--theme': theme }"
:class="{ activeItem: tagsStyle === TagsStyleEnum.BUTTON }"
@click="handleTagsStyle(TagsStyleEnum.BUTTON)"
>
<b></b>
<b></b>
<b></b>
</div>
</el-tooltip>
<el-tooltip content="谷歌风格" placement="bottom">
<div
class="item chrome"
:style="{ '--theme': theme }"
:class="{ activeItem: tagsStyle === TagsStyleEnum.CHROME }"
@click="handleTagsStyle(TagsStyleEnum.CHROME)"
>
<b></b>
<b></b>
<b></b>
</div>
</el-tooltip>
</div>
<div class="drawer-item">
<span>固定 Header</span>
<span class="comp-style">
@ -171,6 +201,7 @@
<script setup lang="ts">
import { NavTypeEnum } from '@/enums/NavTypeEnum';
import { SideThemeEnum } from '@/enums/SideThemeEnum';
import { TagsStyleEnum } from '@/enums/TagsStyleEnum';
import modal from '@/plugins/modal';
import defaultSettings from '@/settings';
import { useAppStore } from '@/store/modules/app';
@ -189,6 +220,7 @@ const sideTheme = ref(settingsStore.sideTheme);
const storeSettings = computed(() => settingsStore);
const predefineColors = ref(['#409EFF', '#ff4500', '#ff8c00', '#ffd700', '#90ee90', '#00ced1', '#1e90ff', '#c71585']);
const navType = ref(settingsStore.navType);
const tagsStyle = ref(settingsStore.tagsStyle);
const radiusBase = ref(settingsStore.radiusBase);
//
const isDark = useDark({
@ -228,6 +260,14 @@ const handleNavType = (val: NavTypeEnum) => {
navType.value = val;
};
const handleTagsStyle = (val: TagsStyleEnum) => {
if (!settingsStore.tagsView) {
return;
}
settingsStore.tagsStyle = val;
tagsStyle.value = val;
};
const dynamicTitleChange = () => {
//
useDynamicTitle();
@ -260,6 +300,7 @@ const saveSetting = () => {
settings.value.tagsView = storeSettings.value.tagsView;
settings.value.tagsViewPersist = storeSettings.value.tagsViewPersist;
settings.value.tagsIcon = storeSettings.value.tagsIcon;
settings.value.tagsStyle = storeSettings.value.tagsStyle;
settings.value.fixedHeader = storeSettings.value.fixedHeader;
settings.value.sidebarLogo = storeSettings.value.sidebarLogo;
settings.value.dynamicTitle = storeSettings.value.dynamicTitle;
@ -448,4 +489,105 @@ defineExpose({
}
}
}
.tags-style-item {
padding-bottom: 0;
border-bottom: none;
}
.tags-style-wrap {
display: flex;
justify-content: flex-start;
align-items: center;
margin-top: 4px;
margin-bottom: 12px;
padding-bottom: 12px;
border-bottom: 1px solid var(--app-surface-border);
&.is-disabled {
opacity: 0.5;
pointer-events: none;
}
.activeItem {
border: 2px solid #{'var(--theme)'} !important;
}
.item {
position: relative;
margin-right: 16px;
cursor: pointer;
width: 56px;
height: 36px;
border-radius: 8px;
background: var(--app-elevated-soft-bg);
border: 2px solid transparent;
overflow: hidden;
transition:
transform 0.2s ease,
border-color 0.2s ease;
&:hover {
transform: translateY(-1px);
}
}
.button {
b {
position: absolute;
top: 14px;
width: 12px;
height: 14px;
border-radius: 4px;
background: var(--app-surface-bg);
border: 1px solid var(--app-surface-border);
}
b:nth-child(1) {
left: 8px;
}
b:nth-child(2) {
left: 22px;
background: var(--theme, #409eff);
border-color: var(--theme, #409eff);
}
b:nth-child(3) {
left: 36px;
}
}
.chrome {
background: var(--app-surface-bg);
border: 1px solid var(--app-surface-border);
b {
position: absolute;
bottom: 4px;
width: 14px;
height: 16px;
background: transparent;
border: none;
}
b:nth-child(1) {
left: 6px;
border-right: 1px solid var(--app-surface-border);
}
b:nth-child(2) {
left: 18px;
width: 20px;
height: 18px;
background: var(--theme, #409eff);
border-radius: 4px 4px 2px 2px;
z-index: 1;
}
b:nth-child(3) {
left: 32px;
}
}
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<div id="tags-view-container" class="tags-view-container">
<div id="tags-view-container" class="tags-view-container" :class="`tags-style-${tagsStyle}`">
<span v-if="canScrollLeft" class="tags-nav-btn tags-nav-btn--left" @click="scrollLeft">
<el-icon><ArrowLeft /></el-icon>
</span>
@ -16,10 +16,12 @@
@click.middle="!isAffix(tag) ? closeSelectedTag(tag) : undefined"
@contextmenu.prevent="openMenu(tag, $event)"
>
<svg-icon v-if="tagsIcon && tag.meta && tag.meta.icon && tag.meta.icon !== '#'" :icon-class="tag.meta.icon" />
<span class="tags-view-item-title">{{ tag.title || tag.meta?.title }}</span>
<span v-if="!isAffix(tag)" @click.prevent.stop="closeSelectedTag(tag)">
<Close class="el-icon-close" style="width: 1em; height: 1em; vertical-align: middle" />
<span class="tags-view-item-inner">
<svg-icon v-if="tagsIcon && tag.meta && tag.meta.icon && tag.meta.icon !== '#'" :icon-class="tag.meta.icon" />
<span class="tags-view-item-title">{{ tag.title || tag.meta?.title }}</span>
<span v-if="!isAffix(tag)" @click.prevent.stop="closeSelectedTag(tag)">
<Close class="el-icon-close" style="width: 1em; height: 1em; vertical-align: middle" />
</span>
</span>
</router-link>
</scroll-pane>
@ -102,6 +104,7 @@ import {
Right
} from '@element-plus/icons-vue';
import tab from '@/plugins/tab';
import { TagsStyleEnum } from '@/enums/TagsStyleEnum';
import { usePermissionStore } from '@/store/modules/permission';
import { useSettingsStore } from '@/store/modules/settings';
import { useTagsViewStore } from '@/store/modules/tagsView';
@ -128,6 +131,7 @@ const tagsViewStore = useTagsViewStore();
const visitedViews = computed(() => tagsViewStore.getVisitedViews());
const routes = computed(() => permissionStore.getRoutes());
const tagsIcon = computed(() => settingsStore.tagsIcon);
const tagsStyle = computed(() => settingsStore.tagsStyle);
const selectedDropdownTag = computed<RouteLocationNormalized | undefined>(() => {
return visitedViews.value.find(tag => isActive(tag)) || selectedTag.value;
});
@ -161,6 +165,9 @@ const isActive = (currentRoute: RouteLocationNormalized): boolean => {
const activeStyle = (tag: RouteLocationNormalized) => {
if (!isActive(tag)) return {};
if (tagsStyle.value === TagsStyleEnum.CHROME) {
return {};
}
return {
backgroundColor: 'var(--tags-view-active-bg)',
borderColor: 'var(--tags-view-active-border-color)'
@ -480,54 +487,22 @@ onBeforeUnmount(() => {
.tags-view-container {
display: flex;
align-items: flex-start;
height: 38px;
width: 100%;
background-color: var(--app-surface-bg);
border: 1px solid var(--app-surface-border);
border-radius: var(--app-radius-md);
box-shadow: var(--app-shadow-sm);
$btn-width: 26px;
$btn-hover-bg: var(--el-fill-color-light);
$btn-hover-color: var(--el-text-color-primary);
.tags-nav-btn {
flex-shrink: 0;
display: inline-flex;
align-items: center;
justify-content: center;
width: $btn-width;
height: 26px;
margin-top: 4px;
width: 26px;
cursor: pointer;
color: var(--app-text-muted);
user-select: none;
background-color: var(--app-surface-bg);
border: 1px solid var(--app-surface-border);
border-radius: var(--app-radius-md);
transition:
box-shadow 0.2s ease,
transform 0.2s ease,
border-color 0.2s ease,
color 0.2s ease;
&:hover:not(.disabled) {
background: $btn-hover-bg;
color: $btn-hover-color;
border-color: var(--el-color-primary-light-5);
box-shadow: var(--app-shadow-sm);
transform: translateY(-1px);
}
}
.tags-nav-btn--left {
margin-left: 8px;
margin-right: 4px;
}
.tags-nav-btn--right {
margin-left: 4px;
margin-right: 4px;
}
.tags-view-wrapper {
@ -539,21 +514,133 @@ onBeforeUnmount(() => {
align-items: center;
position: relative;
cursor: pointer;
height: 26px;
line-height: 25px;
background-color: var(--app-surface-bg);
border: 1px solid var(--app-surface-border);
color: var(--el-text-color-regular);
padding: 0 8px;
font-size: 12px;
margin-left: 5px;
margin-top: 4px;
border-radius: var(--app-radius-md);
transition:
box-shadow 0.2s ease,
transform 0.2s ease,
border-color 0.2s ease,
color 0.2s ease;
}
}
.tags-view-item-title {
margin-left: 4px;
margin-right: 3px;
}
.tags-view-item-inner {
display: inline-flex;
align-items: center;
}
.tags-action-dropdown {
flex-shrink: 0;
display: flex;
align-items: center;
}
.tags-action-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 4px;
min-width: 26px;
cursor: pointer;
color: var(--app-text-muted);
user-select: none;
transition:
box-shadow 0.2s ease,
transform 0.2s ease,
border-color 0.2s ease,
color 0.2s ease;
}
.contextmenu {
margin: 0;
background: var(--app-surface-bg);
z-index: 3000;
position: fixed;
list-style-type: none;
padding: 5px 0;
border-radius: var(--app-radius-md);
font-size: 12px;
font-weight: 400;
box-shadow: var(--app-shadow-md);
li {
margin: 0;
padding: 7px 16px;
cursor: pointer;
&:hover {
background: var(--el-fill-color-light);
}
}
}
&.tags-style-button,
&.tags-style-chrome {
height: 38px;
align-items: center;
background-color: var(--app-surface-bg);
border: 1px solid var(--app-surface-border);
border-radius: var(--app-radius-md);
box-shadow: var(--app-shadow-sm);
.tags-nav-btn--left {
margin-left: 8px;
margin-right: 4px;
}
.tags-nav-btn--right {
margin-left: 4px;
margin-right: 4px;
}
.tags-nav-btn,
.tags-action-btn {
height: 26px;
background-color: var(--app-surface-bg);
border: 1px solid var(--app-surface-border);
border-radius: var(--app-radius-md);
&:hover:not(.disabled),
&:hover {
background: var(--el-fill-color-light);
color: var(--el-text-color-primary);
border-color: var(--el-color-primary-light-5);
box-shadow: var(--app-shadow-sm);
transform: translateY(-1px);
}
}
.tags-action-btn {
padding: 0 8px;
}
.tags-action-dropdown {
margin-left: 4px;
}
.tags-refresh-btn {
width: auto;
font-size: 12px;
margin-left: 8px;
margin-right: 8px;
}
}
&.tags-style-button {
.tags-view-wrapper .tags-view-item {
height: 26px;
background-color: var(--app-surface-bg);
border: 1px solid var(--app-surface-border);
margin-left: 5px;
margin-top: 4px;
border-radius: var(--app-radius-md);
&:hover {
color: var(--el-color-primary);
@ -586,82 +673,101 @@ onBeforeUnmount(() => {
margin-right: 5px;
}
}
&.active.has-icon::before {
content: none !important;
}
}
}
.tags-view-item.active.has-icon::before {
content: none !important;
}
&.tags-style-chrome {
.tags-view-wrapper {
align-self: stretch;
.tags-view-item-title {
margin-left: 4px;
margin-right: 3px;
}
:deep(.el-scrollbar__wrap) {
height: 34px;
align-items: flex-end;
}
.tags-action-dropdown {
flex-shrink: 0;
display: flex;
align-items: center;
margin-top: 4px;
margin-left: 4px;
}
.tags-view-item {
height: 30px;
padding: 0 15px;
background: transparent;
border: none;
border-radius: 12px 12px 0 0;
.tags-action-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 4px;
min-width: $btn-width;
height: 26px;
padding: 0 8px;
cursor: pointer;
color: var(--app-text-muted);
user-select: none;
background-color: var(--app-surface-bg);
border: 1px solid var(--app-surface-border);
border-radius: var(--app-radius-md);
transition:
box-shadow 0.2s ease,
transform 0.2s ease,
border-color 0.2s ease,
color 0.2s ease;
&:first-of-type {
margin-left: 10px;
}
&:hover {
background: $btn-hover-bg;
color: $btn-hover-color;
border-color: var(--el-color-primary-light-5);
box-shadow: var(--app-shadow-sm);
transform: translateY(-1px);
}
}
&:last-of-type {
margin-right: 10px;
}
.tags-refresh-btn {
width: auto;
font-size: 12px;
margin-top: 4px;
margin-left: 8px;
margin-right: 8px;
}
&::before,
&::after {
position: absolute;
bottom: 0;
content: '';
width: 20px;
height: 20px;
border-radius: 100%;
box-shadow: 0 0 0 30px transparent;
pointer-events: none;
}
.contextmenu {
margin: 0;
background: var(--app-surface-bg);
z-index: 3000;
position: fixed;
list-style-type: none;
padding: 5px 0;
border-radius: var(--app-radius-md);
font-size: 12px;
font-weight: 400;
box-shadow: var(--app-shadow-md);
&::before {
left: -20px;
clip-path: inset(50% -10px 0 50%);
}
li {
margin: 0;
padding: 7px 16px;
cursor: pointer;
&::after {
right: -20px;
clip-path: inset(50% 50% 0 -10px);
}
&:hover {
background: var(--el-fill-color-light);
.tags-view-item-inner {
position: relative;
z-index: 1;
}
&:not(.active):not(:hover):not(:has(+ .tags-view-item.active)) {
background-image: linear-gradient(
to bottom,
transparent calc(50% - 8px),
var(--app-surface-border) calc(50% - 8px),
var(--app-surface-border) calc(50% + 8px),
transparent calc(50% + 8px)
);
background-size: 1px 100%;
background-position: right center;
background-repeat: no-repeat;
}
&:hover:not(.active) {
z-index: 1;
color: var(--el-color-primary);
background-color: var(--tags-view-chrome-tab-hover-bg);
background-image: none;
&::before,
&::after {
box-shadow: 0 0 0 30px var(--tags-view-chrome-tab-hover-bg);
}
}
&.active {
z-index: 2;
font-weight: 500;
color: var(--el-color-white);
background-color: var(--tags-view-active-bg);
background-image: none;
&::before,
&::after {
box-shadow: 0 0 0 30px var(--tags-view-active-bg);
}
}
}
}
}
@ -696,6 +802,18 @@ onBeforeUnmount(() => {
}
}
.tags-style-chrome .tags-view-item {
.el-icon-close:hover {
background-color: var(--app-elevated-close-hover-bg);
color: var(--app-text-title);
}
&.active .el-icon-close:hover {
background-color: rgba(255, 255, 255, 0.22);
color: var(--el-color-white);
}
}
body.tags-fullscreen-mode {
overflow: hidden;
}

View File

@ -1,5 +1,6 @@
import { LanguageEnum } from '@/enums/LanguageEnum';
import { NavTypeEnum } from '@/enums/NavTypeEnum';
import { TagsStyleEnum } from '@/enums/TagsStyleEnum';
const setting: DefaultSettings = {
/**
@ -38,6 +39,11 @@ const setting: DefaultSettings = {
*/
tagsIcon: true,
/**
*
*/
tagsStyle: TagsStyleEnum.BUTTON,
/**
*
*/

View File

@ -2,6 +2,7 @@ import { useStorage } from '@vueuse/core';
import { defineStore } from 'pinia';
import { ref } from 'vue';
import { NavTypeEnum } from '@/enums/NavTypeEnum';
import { TagsStyleEnum } from '@/enums/TagsStyleEnum';
import defaultSettings from '@/settings';
import { useDynamicTitle } from '@/utils/dynamicTitle';
@ -10,6 +11,7 @@ export const useSettingsStore = defineStore('setting', () => {
tagsView: defaultSettings.tagsView,
tagsViewPersist: defaultSettings.tagsViewPersist,
tagsIcon: defaultSettings.tagsIcon,
tagsStyle: defaultSettings.tagsStyle,
fixedHeader: defaultSettings.fixedHeader,
sidebarLogo: defaultSettings.sidebarLogo,
dynamicTitle: defaultSettings.dynamicTitle,
@ -26,6 +28,7 @@ export const useSettingsStore = defineStore('setting', () => {
const tagsView = ref<boolean>(storageSetting.value.tagsView);
const tagsViewPersist = ref<boolean>(storageSetting.value.tagsViewPersist);
const tagsIcon = ref<boolean>(storageSetting.value.tagsIcon);
const tagsStyle = ref<TagsStyleEnum>(storageSetting.value.tagsStyle || TagsStyleEnum.BUTTON);
const fixedHeader = ref<boolean>(storageSetting.value.fixedHeader);
const sidebarLogo = ref<boolean>(storageSetting.value.sidebarLogo);
const dynamicTitle = ref<boolean>(storageSetting.value.dynamicTitle);
@ -47,6 +50,7 @@ export const useSettingsStore = defineStore('setting', () => {
tagsView,
tagsViewPersist,
tagsIcon,
tagsStyle,
fixedHeader,
sidebarLogo,
dynamicTitle,

View File

@ -1,6 +1,7 @@
import type { ComponentInternalInstance as ComponentInstance } from 'vue';
import { LanguageEnum } from '@/enums/LanguageEnum';
import { NavTypeEnum } from '@/enums/NavTypeEnum';
import { TagsStyleEnum } from '@/enums/TagsStyleEnum';
declare global {
/** vue Instance */
@ -107,6 +108,10 @@ declare global {
*
*/
tagsIcon: boolean;
/**
*
*/
tagsStyle: TagsStyleEnum;
/**
*
*/