Browse Source

新增标签页样式chrome风格

RuoYi 1 month ago
parent
commit
47656d5050

+ 26 - 0
src/assets/styles/variables.module.scss

@@ -193,6 +193,32 @@ html.dark {
     }
   }
 
+  /* 页签样式覆盖 */
+  .navbar {
+    box-shadow: 0 1px 4px rgba(255, 255, 255, 0.08);
+  }
+  .tags-view-container {
+    &.tags-view-container--chrome {
+      --chrome-strip-bg: var(--el-bg-color);
+      --chrome-strip-border: var(--el-border-color);
+      --chrome-tab-active-bg: var(--el-color-primary-light-9);
+      --chrome-tab-text: var(--el-text-color-secondary);
+      --chrome-tab-text-active: var(--el-color-primary);
+      .tags-view-wrapper .tags-view-item:not(.active) + .tags-view-item:not(.active) {
+        border-left-color: var(--el-border-color);
+      }
+      .tags-view-wrapper .tags-view-item {
+        &:hover:not(.active) {
+          background: var(--el-fill-color, #303030) !important;
+          color: var(--el-text-color-primary);
+        }
+        &.active {
+          box-shadow: 0 2px 8px rgba(0, 0, 0, 0.28);
+        }
+      }
+    }
+  }
+
   /* 分割窗格覆盖 */
   .tree-sidebar-manage-wrap {
     .tree-sidebar-content {

+ 12 - 1
src/layout/components/Settings/index.vue

@@ -57,7 +57,7 @@
     <h3 class="drawer-title">系统布局配置</h3>
 
     <div class="drawer-item">
-      <span>开启 Tags-Views</span>
+      <span>开启页签</span>
       <span class="comp-style">
         <el-switch v-model="settingsStore.tagsView" class="drawer-switch" />
       </span>
@@ -77,6 +77,16 @@
       </span>
     </div>
 
+    <div class="drawer-item">
+      <span>标签页样式</span>
+      <span class="comp-style">
+        <el-radio-group v-model="settingsStore.tagsViewStyle" :disabled="!settingsStore.tagsView" size="small">
+          <el-radio-button label="card">卡片</el-radio-button>
+          <el-radio-button label="chrome">谷歌</el-radio-button>
+        </el-radio-group>
+      </span>
+    </div>
+
     <div class="drawer-item">
       <span>固定 Header</span>
       <span class="comp-style">
@@ -184,6 +194,7 @@ function saveSetting() {
     "navType": storeSettings.value.navType,
     "tagsView": storeSettings.value.tagsView,
     "tagsIcon": storeSettings.value.tagsIcon,
+    "tagsViewStyle": storeSettings.value.tagsViewStyle,
     "tagsViewPersist": storeSettings.value.tagsViewPersist,
     "fixedHeader": storeSettings.value.fixedHeader,
     "sidebarLogo": storeSettings.value.sidebarLogo,

+ 146 - 28
src/layout/components/TagsView/index.vue

@@ -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-view-container--chrome': tagsViewStyle === 'chrome' }">
     <!-- 左切换箭头 -->
     <span class="tags-nav-btn tags-nav-btn--left" :class="{ disabled: !canScrollLeft }" @click="scrollLeft">
       <el-icon><arrow-left /></el-icon>
@@ -14,7 +14,7 @@
         :class="{ 'active': isActive(tag), 'has-icon': tagsIcon }"
         :to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
         class="tags-view-item"
-        :style="activeStyle(tag)"
+        :style="tagActiveStyle(tag)"
         @click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''"
         @contextmenu.prevent="openMenu(tag, $event)"
       >
@@ -96,6 +96,7 @@ const routes = computed(() => usePermissionStore().routes)
 const theme = computed(() => useSettingsStore().theme)
 const tagsIcon = computed(() => useSettingsStore().tagsIcon)
 const tagsViewPersist = computed(() => useSettingsStore().tagsViewPersist)
+const tagsViewStyle = computed(() => useSettingsStore().tagsViewStyle)
 
 // 下拉菜单针对当前激活的 tag
 const selectedDropdownTag = computed(() => visitedViews.value.find(v => isActive(v)) || {})
@@ -140,8 +141,8 @@ function isActive(r) {
   return r.path === route.path
 }
 
-function activeStyle(tag) {
-  if (!isActive(tag)) return {}
+function tagActiveStyle(tag) {
+  if (!isActive(tag) || tagsViewStyle.value !== 'card') return {}
   return {
     'background-color': theme.value,
     'border-color': theme.value
@@ -369,8 +370,10 @@ function handleScroll() {
 </script>
 
 <style lang="scss" scoped>
+$tags-bar-height: 34px;
+
 .tags-view-container {
-  height: 34px;
+  height: $tags-bar-height;
   width: 100%;
   background: var(--tags-bg, #fff);
   border-bottom: 1px solid var(--tags-item-border, #d8dce5);
@@ -391,7 +394,7 @@ function handleScroll() {
     align-items: center;
     justify-content: center;
     width: $btn-width;
-    height: 34px;
+    height: $tags-bar-height;
     cursor: pointer;
     color: $btn-color;
     font-size: 13px;
@@ -437,27 +440,27 @@ function handleScroll() {
 
       &:first-of-type { margin-left: 6px; }
       &:last-of-type  { margin-right: 15px; }
+    }
+  }
 
-      &.active {
-        background-color: #42b983;
-        color: #fff;
-        border-color: #42b983;
-
-        &::before {
-          content: '';
-          background: #fff;
-          display: inline-block;
-          width: 8px;
-          height: 8px;
-          border-radius: 50%;
-          position: relative;
-          margin-right: 5px;
-        }
-      }
+  &:not(.tags-view-container--chrome) .tags-view-wrapper .tags-view-item.active {
+    background-color: #42b983;
+    color: #fff;
+    border-color: #42b983;
+
+    &::before {
+      content: '';
+      background: #fff;
+      display: inline-block;
+      width: 8px;
+      height: 8px;
+      border-radius: 50%;
+      position: relative;
+      margin-right: 5px;
     }
   }
 
-  .tags-view-item.active.has-icon::before {
+  &:not(.tags-view-container--chrome) .tags-view-wrapper .tags-view-item.active.has-icon::before {
     content: none !important;
   }
 
@@ -472,7 +475,7 @@ function handleScroll() {
     align-items: center;
     justify-content: center;
     width: $btn-width;
-    height: 34px;
+    height: $tags-bar-height;
     cursor: pointer;
     color: $btn-color;
     font-size: 13px;
@@ -514,6 +517,121 @@ function handleScroll() {
       }
     }
   }
+
+  &.tags-view-container--chrome {
+    --chrome-strip-bg: #ffffff;
+    --chrome-strip-border: var(--el-border-color-lighter, #e4e7ed);
+    --chrome-tab-active-bg: var(--el-color-primary-light-9);
+    --chrome-tab-text: var(--el-text-color-regular, #606266);
+    --chrome-tab-text-active: var(--el-color-primary);
+    --chrome-wing-r: 10px;
+
+    overflow: visible;
+    background: var(--chrome-strip-bg);
+    border-bottom: 1px solid var(--chrome-strip-border);
+    align-items: flex-end;
+
+    .tags-nav-btn {
+      align-self: stretch;
+      height: auto;
+      min-height: $tags-bar-height;
+      border-color: var(--chrome-strip-border);
+    }
+
+    .tags-action-btn {
+      border-color: var(--chrome-strip-border);
+    }
+
+    .tags-view-wrapper {
+      .tags-view-item {
+        display: inline-flex !important;
+        align-items: center;
+        justify-content: center;
+        position: relative;
+        z-index: 1;
+        height: 30px;
+        min-height: 30px;
+        margin: 0 0 -1px;
+        padding: 0 12px;
+        font-size: 13px;
+        font-weight: 400;
+        line-height: 1.2;
+        border: none !important;
+        border-radius: 0;
+        background: transparent !important;
+        color: var(--chrome-tab-text);
+        padding-top: 0 !important;
+        box-shadow: none !important;
+        transition: background 0.12s ease, color 0.12s ease, border-radius 0.12s ease;
+
+        &::before,
+        &::after {
+          content: '' !important;
+          display: block !important;
+          position: absolute;
+          bottom: 0;
+          width: var(--chrome-wing-r);
+          height: var(--chrome-wing-r);
+          margin: 0 !important;
+          pointer-events: none;
+          background: transparent !important;
+          border-radius: 0 !important;
+          transition: box-shadow 0.12s ease;
+        }
+
+        &::before {
+          left: calc(-1 * var(--chrome-wing-r));
+          border-bottom-right-radius: var(--chrome-wing-r) !important;
+          box-shadow: none;
+        }
+
+        &::after {
+          right: calc(-1 * var(--chrome-wing-r));
+          border-bottom-left-radius: var(--chrome-wing-r) !important;
+          box-shadow: none;
+        }
+
+        &:first-of-type {
+          margin-left: 6px;
+        }
+
+        &:last-of-type {
+          margin-right: 10px;
+        }
+
+        &:not(.active) + .tags-view-item:not(.active) {
+          border-left: 1px solid var(--el-border-color-lighter, #e4e7ed);
+          padding-left: 11px;
+        }
+
+        &:hover:not(.active) {
+          background: var(--el-fill-color-light, #f5f7fa) !important;
+          border-radius: 6px 6px 0 0;
+          color: var(--el-text-color-primary, #303133);
+        }
+
+        &.active {
+          height: 31px;
+          min-height: 31px;
+          padding: 0 14px;
+          color: var(--chrome-tab-text-active) !important;
+          font-weight: 500;
+          background: var(--chrome-tab-active-bg) !important;
+          border: none !important;
+          border-radius: var(--chrome-wing-r) var(--chrome-wing-r) 0 0;
+          box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
+
+          &::before {
+            box-shadow: calc(var(--chrome-wing-r) * 0.5) calc(var(--chrome-wing-r) * 0.5) 0 calc(var(--chrome-wing-r) * 0.5) var(--chrome-tab-active-bg);
+          }
+
+          &::after {
+            box-shadow: calc(var(--chrome-wing-r) * -0.5) calc(var(--chrome-wing-r) * 0.5) 0 calc(var(--chrome-wing-r) * 0.5) var(--chrome-tab-active-bg);
+          }
+        }
+      }
+    }
+  }
 }
 </style>
 
@@ -581,14 +699,14 @@ function handleScroll() {
 
 .main-container.fullscreen-mode .app-main {
   position: fixed;
-  top: 34px;
+  top: 42px;
   left: 0;
   right: 0;
   bottom: 0;
   margin: 0 !important;
   padding: 0 !important;
-  height: calc(100vh - 34px) !important;
-  min-height: calc(100vh - 34px) !important;
+  height: calc(100vh - 42px) !important;
+  min-height: calc(100vh - 42px) !important;
   overflow: auto;
 }
-</style>
+</style>

+ 5 - 0
src/settings.js

@@ -34,6 +34,11 @@ export default {
    */
   tagsIcon: false,
 
+  /**
+   * 标签页样式:card 卡片(默认)、chrome 谷歌浏览器风格
+   */
+  tagsViewStyle: 'card',
+
   /**
    * 是否固定头部
    */

+ 2 - 1
src/store/modules/settings.js

@@ -5,7 +5,7 @@ import { useDynamicTitle } from '@/utils/dynamicTitle'
 const isDark = useDark()
 const toggleDark = useToggle(isDark)
 
-const { sideTheme, showSettings, navType, tagsView, tagsViewPersist, tagsIcon, fixedHeader, sidebarLogo, dynamicTitle, footerVisible, footerContent } = defaultSettings
+const { sideTheme, showSettings, navType, tagsView, tagsViewPersist, tagsIcon, tagsViewStyle, fixedHeader, sidebarLogo, dynamicTitle, footerVisible, footerContent } = defaultSettings
 
 const storageSetting = JSON.parse(localStorage.getItem('layout-setting')) || ''
 
@@ -21,6 +21,7 @@ const useSettingsStore = defineStore(
       tagsView: storageSetting.tagsView === undefined ? tagsView : storageSetting.tagsView,
       tagsViewPersist: storageSetting.tagsViewPersist === undefined ? tagsViewPersist : storageSetting.tagsViewPersist,
       tagsIcon: storageSetting.tagsIcon === undefined ? tagsIcon : storageSetting.tagsIcon,
+      tagsViewStyle: storageSetting.tagsViewStyle === undefined ? tagsViewStyle : storageSetting.tagsViewStyle,
       fixedHeader: storageSetting.fixedHeader === undefined ? fixedHeader : storageSetting.fixedHeader,
       sidebarLogo: storageSetting.sidebarLogo === undefined ? sidebarLogo : storageSetting.sidebarLogo,
       dynamicTitle: storageSetting.dynamicTitle === undefined ? dynamicTitle : storageSetting.dynamicTitle,