Ver Fonte

feat: 新增 拖拽限制逻辑,修改tag导航栏样式,新增关闭全部按钮

hotchicken1996 há 1 ano atrás
pai
commit
9aade1a908

+ 5 - 3
client/src/layout/components/TagsView/src/TagsView.vue

@@ -27,7 +27,7 @@ const routers = computed(() => permissionStore.getRouters)
 
 const tagsViewStore = useTagsViewStore()
 
-const visitedViews = computed(() => tagsViewStore.getVisitedViews)
+const visitedViews = computed(() => tagsViewStore.visitedViews)
 
 const affixTagArr = ref<RouteLocationNormalizedLoaded[]>([])
 
@@ -104,8 +104,7 @@ const closeRightTags = () => {
 
 // 跳转到最后一个
 const toLastView = () => {
-  const visitedViews = tagsViewStore.getVisitedViews
-  const latestView = visitedViews.slice(-1)[0]
+  const latestView = unref(visitedViews).slice(-1)[0]
   if (latestView) {
     push(latestView)
   } else {
@@ -525,6 +524,7 @@ $prefix-cls: #{$namespace}-tags-view;
       display: none;
       transform: translate(0, -50%);
     }
+
     &:not(.#{$prefix-cls}__item--affix):hover {
       .#{$prefix-cls}__item--close {
         display: block;
@@ -542,6 +542,7 @@ $prefix-cls: #{$namespace}-tags-view;
     color: var(--el-color-white);
     background-color: var(--el-color-primary);
     border: 1px solid var(--el-color-primary);
+
     .#{$prefix-cls}__item--close {
       :deep(span) {
         color: var(--el-color-white) !important;
@@ -574,6 +575,7 @@ $prefix-cls: #{$namespace}-tags-view;
       color: var(--el-color-white);
       background-color: var(--el-color-primary);
       border: 1px solid var(--el-color-primary);
+
       .#{$prefix-cls}__item--close {
         :deep(span) {
           color: var(--el-color-white) !important;

+ 144 - 132
client/src/store/modules/tagsView.ts

@@ -2,14 +2,9 @@ import router from '@/router'
 import type { RouteLocationNormalizedLoaded } from 'vue-router'
 import { getRawRoute } from '@/utils/routerHelper'
 import { defineStore } from 'pinia'
-import { store } from '@/store'
+import { ref, computed } from 'vue'
 import { findIndex } from '@/utils'
 
-export interface TagsViewState {
-  visitedViews: RouteLocationNormalizedLoaded[]
-  cachedViews: Set<string>
-}
-
 /**
  * 判断是否为相同路由tag
  * 判断path同时根据query确定是否相同(兼容详情页多开)
@@ -23,136 +18,153 @@ export const isSameRouteTag = (
   return currentTag.fullPath === TargetTag.fullPath
 }
 
-export const useTagsViewStore = defineStore('tagsView', {
-  state: (): TagsViewState => ({
-    visitedViews: [],
-    cachedViews: new Set()
-  }),
-  getters: {
-    getVisitedViews(): RouteLocationNormalizedLoaded[] {
-      return this.visitedViews
-    },
-    getCachedViews(): string[] {
-      return Array.from(this.cachedViews)
-    }
-  },
-  actions: {
-    // 新增缓存和tag
-    addView(view: RouteLocationNormalizedLoaded): void {
-      console.log('view: ', view)
-      this.addVisitedView(view)
-      this.addCachedView()
-    },
-    allResetView(views: RouteLocationNormalizedLoaded[]): void {
-      this.visitedViews = views
-      this.addCachedView() // 缓存保存顺序
-    },
-    // 新增tag
-    addVisitedView(view: RouteLocationNormalizedLoaded) {
-      if (this.visitedViews.some((v) => isSameRouteTag(v, view))) return
-      if (view.meta?.noTagsView) return
-      this.visitedViews.push(
-        Object.assign({}, view, {
-          title: view.meta?.title || 'no-name'
-        })
-      )
-    },
-    // 新增缓存
-    addCachedView() {
-      const cacheMap: Set<string> = new Set()
-      for (const v of this.visitedViews) {
-        const item = getRawRoute(v)
-        const needCache = !item.meta?.noCache
-        if (!needCache) {
-          continue
-        }
-        const name = item.name as string
-        cacheMap.add(name)
-      }
-      if (Array.from(this.cachedViews).sort().toString() === Array.from(cacheMap).sort().toString())
-        return
-      this.cachedViews = cacheMap
-    },
-    // 删除某个
-    delView(view: RouteLocationNormalizedLoaded) {
-      this.delVisitedView(view)
-      this.delCachedView()
-    },
-    // 删除tag
-    delVisitedView(view: RouteLocationNormalizedLoaded) {
-      for (const [i, v] of this.visitedViews.entries()) {
-        if (v.path === view.path) {
-          this.visitedViews.splice(i, 1)
-          break
-        }
-      }
-    },
-    // 删除缓存
-    delCachedView() {
-      const route = router.currentRoute.value
-      const index = findIndex<string>(this.getCachedViews, (v) => v === route.name)
-      if (index > -1) {
-        this.cachedViews.delete(this.getCachedViews[index])
-      }
-    },
-    // 删除所有缓存和tag
-    delAllViews() {
-      this.delAllVisitedViews()
-      this.delCachedView()
-    },
-    // 删除所有tag
-    delAllVisitedViews() {
-      // const affixTags = this.visitedViews.filter((tag) => tag.meta.affix)
-      this.visitedViews = []
-    },
-    // 删除其他
-    delOthersViews(view: RouteLocationNormalizedLoaded) {
-      this.delOthersVisitedViews(view)
-      this.addCachedView()
-    },
-    // 删除其他tag
-    delOthersVisitedViews(view: RouteLocationNormalizedLoaded) {
-      this.visitedViews = this.visitedViews.filter((v) => {
-        return v?.meta?.affix || v.path === view.path
+/**
+ * 头部tab导航栏store
+ */
+export const useTagsViewStore = defineStore('tagsView', () => {
+  const visitedViews = ref<RouteLocationNormalizedLoaded[]>([])
+  const cachedViews = ref<Set<string>>(new Set())
+  const getCachedViews = computed(() => {
+    return Array.from(cachedViews.value)
+  })
+
+  // 新增缓存和tag
+  const addView = (view: RouteLocationNormalizedLoaded): void => {
+    addVisitedView(view)
+    addCachedView()
+  }
+
+  // 重置缓存和tag(用于tag排序)
+  const allResetView = (views: RouteLocationNormalizedLoaded[]): void => {
+    visitedViews.value = views
+    addCachedView()
+  }
+
+  // 新增tag
+  const addVisitedView = (view: RouteLocationNormalizedLoaded): void => {
+    if (unref(visitedViews).some((v) => isSameRouteTag(v, view))) return
+    if (view.meta?.noTagsView) return
+    visitedViews.value.push(
+      Object.assign({}, view, {
+        title: view.meta?.title || 'no-name'
       })
-    },
-    // 删除左侧
-    delLeftViews(view: RouteLocationNormalizedLoaded) {
-      const index = findIndex<RouteLocationNormalizedLoaded>(
-        this.visitedViews,
-        (v) => v.path === view.path
-      )
-      if (index > -1) {
-        this.visitedViews = this.visitedViews.filter((v, i) => {
-          return v?.meta?.affix || v.path === view.path || i > index
-        })
-        this.addCachedView()
+    )
+  }
+
+  // 新增缓存
+  const addCachedView = (): void => {
+    const cacheMap: Set<string> = new Set()
+    for (const v of unref(visitedViews)) {
+      const item = getRawRoute(v)
+      const needCache = !item.meta?.noCache
+      if (!needCache) {
+        continue
       }
-    },
-    // 删除右侧
-    delRightViews(view: RouteLocationNormalizedLoaded) {
-      const index = findIndex<RouteLocationNormalizedLoaded>(
-        this.visitedViews,
-        (v) => v.path === view.path
-      )
-      if (index > -1) {
-        this.visitedViews = this.visitedViews.filter((v, i) => {
-          return v?.meta?.affix || v.path === view.path || i < index
-        })
-        this.addCachedView()
+      const name = item.name as string
+      cacheMap.add(name)
+    }
+    if (Array.from(visitedViews.value).sort().toString() === Array.from(cacheMap).sort().toString())
+      return
+    cachedViews.value = cacheMap
+  }
+
+  // 更新某tag
+  const updateVisitedView = (view: RouteLocationNormalizedLoaded): void => {
+    for (let v of unref(visitedViews)) {
+      if (v.path === view.path) {
+        v = Object.assign(v, view)
+        break
       }
-    },
-    updateVisitedView(view: RouteLocationNormalizedLoaded) {
-      for (let v of this.visitedViews) {
-        if (v.path === view.path) {
-          v = Object.assign(v, view)
-          break
-        }
+    }
+  }
+
+  // 删除某个
+  const delView = (view: RouteLocationNormalizedLoaded): void => {
+    delVisitedView(view)
+    delCachedView()
+  }
+
+  // 删除tag
+  const delVisitedView = (view: RouteLocationNormalizedLoaded): void => {
+    for (const [i, v] of unref(visitedViews).entries()) {
+      if (v.path === view.path) {
+        visitedViews.value.splice(i, 1)
+        break
       }
     }
   }
-})
 
-export const useTagsViewStoreWithOut = () => {
-  return useTagsViewStore(store)
-}
+  // 删除缓存
+  const delCachedView = (): void => {
+    const route = router.currentRoute.value
+    const index = findIndex<string>(unref(getCachedViews), (v) => v === route.name)
+    if (index > -1) {
+      cachedViews.value.delete(unref(getCachedViews)[index])
+    }
+  }
+
+  // 删除所有缓存和tag
+  const delAllViews = (): void => {
+    delAllVisitedViews()
+    delCachedView()
+  }
+
+  // 删除所有tag
+  const delAllVisitedViews = (): void => {
+    // const affixTags = this.visitedViews.filter((tag) => tag.meta.affix)
+    visitedViews.value = []
+  }
+
+  // 删除多个标签方法
+  const delVisitedViews = (
+    view: RouteLocationNormalizedLoaded,
+    type: 'other' | 'left' | 'right'
+  ): void => {
+    const index = findIndex<RouteLocationNormalizedLoaded>(
+      unref(visitedViews),
+      (v) => v.path === view.path
+    )
+    visitedViews.value = visitedViews.value.filter((v, i) => {
+      return (
+        v?.meta?.affix ||
+        isSameRouteTag(unref(v), view) ||
+        (type === 'other' ? false : type === 'left' ? i > index : i < index)
+      )
+    })
+    addCachedView()
+  }
+
+  // 删除其他
+  const delOthersViews = (view: RouteLocationNormalizedLoaded): void => {
+    delVisitedViews(view, 'other')
+  }
+
+  // 删除左侧
+  const delLeftViews = (view: RouteLocationNormalizedLoaded): void => {
+    delVisitedViews(view, 'left')
+  }
+
+  // 删除右侧
+  const delRightViews = (view: RouteLocationNormalizedLoaded): void => {
+    delVisitedViews(view, 'right')
+  }
+
+  return {
+    visitedViews,
+    cachedViews,
+    getCachedViews,
+    addView,
+    allResetView,
+    addVisitedView,
+    addCachedView,
+    updateVisitedView,
+    delView,
+    delVisitedView,
+    delCachedView,
+    delAllViews,
+    delAllVisitedViews,
+    delOthersViews,
+    delLeftViews,
+    delRightViews
+  }
+})

+ 112 - 111
client/src/views/OaSystem/oaLayout/tagList.vue

@@ -7,12 +7,8 @@
         item-key="fullPath"
         ghost-class="draggable-ghost"
         filter=".unDrag"
-        @end="
-          (event) => {
-            // todo 不允许拖拽到第一项或最后一项之外
-            console.log('draged: ', event)
-          }
-        "
+        @end="onDragEnd"
+        :animation="400"
       >
         <template #item="{ element: item, index }">
           <ContextMenu
@@ -69,6 +65,7 @@
             ]"
             :tag-item="item"
             :class="[
+              'tag-body',
               `${prefixCls}__item`,
               item?.meta?.affix ? `${prefixCls}__item--affix unDrag` : '',
               {
@@ -79,7 +76,7 @@
           >
             <router-link :to="{ ...item }" exact v-slot="{ navigate }" v-if="index === 0">
               <div class="homeBox" @click="navigate">
-                <Icon :icon="item.meta.icon || ''" />
+                <Icon :icon="item.meta.icon || ''" :size="18" />
                 <p>{{ item.meta.title }}</p>
               </div>
             </router-link>
@@ -87,7 +84,6 @@
               <div class="fhomeBox" @click="navigate">
                 <div class="pBox">
                   <el-tooltip
-                    class="box-item"
                     effect="dark"
                     :content="`${item.meta.title}${(item?.query?.name ?? '') !== '' ? `( ${item?.query?.name} )` : ''}`"
                     placement="top-start"
@@ -107,6 +103,9 @@
           </ContextMenu>
         </template>
       </draggable>
+      <div v-if="(visitedViews?.length ?? 0) > 1" class="all-tags-close" @click="allTagsClose">
+        关闭全部
+      </div>
     </div>
   </div>
 </template>
@@ -125,6 +124,7 @@ import { useI18n } from '@/hooks/web/useI18n'
 import { useDesign } from '@/hooks/web/useDesign'
 import draggable from 'vuedraggable'
 
+const router = useRouter()
 const { getPrefixCls } = useDesign()
 const prefixCls = getPrefixCls('tags-view')
 const { t } = useI18n()
@@ -136,13 +136,11 @@ const tagsViewStore = useTagsViewStore()
 const affixTagArr = ref<RouteLocationNormalizedLoaded[]>([])
 // 所有右键菜单组件的元素
 const itemRefs = useTemplateRefsList<ComponentRef<typeof ContextMenu & ContextMenuExpose>>()
+
 const visitedViews = computed({
-  get: () => tagsViewStore.getVisitedViews,
+  get: () => tagsViewStore.visitedViews,
   set: (views) => tagsViewStore.allResetView(views)
 })
-
-console.log('visitedViews: ', visitedViews)
-
 // 初始化tag
 const initTags = () => {
   affixTagArr.value = filterAffixTags(unref(routers))
@@ -217,8 +215,7 @@ const isActive = (route: RouteLocationNormalizedLoaded): boolean => {
 
 // 跳转到最后一个
 const toLastView = () => {
-  const visitedViews = tagsViewStore.getVisitedViews
-  const latestView = visitedViews.slice(-1)[0]
+  const latestView = unref(visitedViews).slice(-1)[0]
   if (latestView) {
     push(latestView)
   } else {
@@ -234,6 +231,20 @@ const toLastView = () => {
   }
 }
 
+// 关闭全部标签
+const allTagsClose = (): void => {
+  visitedViews.value = [unref(visitedViews)[0]]
+  router.replace('/home')
+}
+
+// 拖拽结束事件
+const onDragEnd = (event): void => {
+  // 不允许拖拽到第一项
+  if (event.newIndex === 0) {
+    unref(visitedViews)[0] = unref(visitedViews).splice(1, 1, unref(visitedViews)[0])[0]
+  }
+}
+
 watch(
   () => currentRoute.value,
   () => {
@@ -249,143 +260,133 @@ onMounted(() => {
 <style lang="scss" scoped>
 ._tagList {
   width: 100%;
-  height: 36px;
+  padding: 0 12px;
+  height: 54px;
+  background: rgba(255, 255, 255, 0.6);
+  border-radius: 20px;
   display: flex;
   align-items: center;
-  background: rgba(255, 255, 255, 1);
-  border-radius: 10px;
+  color: #2d333c;
 
   .tag-view {
+    display: flex;
+    align-items: center;
     width: 100%;
     height: 100%;
+    padding-right: 10px;
 
     .tags-draggable-list {
-      width: 100%;
+      width: calc(100% - 64px);
       height: 100%;
       display: flex;
       align-items: center;
 
-      a {
-        width: 100%;
-        display: block;
-        height: 100%;
-        border-radius: 10px;
-        color: inherit;
-        text-decoration: none;
-        padding: 2px;
-        box-sizing: border-box;
-      }
+      .tag-body {
+        min-width: 60px;
+        margin-right: 12px;
 
-      .homeBox {
-        width: 70px;
-        height: 16px;
-        opacity: 1;
-        display: flex;
-        align-items: center;
-        justify-content: center;
+        &:hover {
+          p,
+          .el-icon {
+            color: #2e77e6;
+          }
+        }
 
-        p {
-          margin-left: 5px;
-          font-size: 16px;
+        &.v-tags-view__item--affix {
+          flex-shrink: 0;
         }
-      }
 
-      .fhomeBox {
-        width: 100%;
-        height: 16px;
-        display: flex;
-        align-items: center;
-        justify-content: space-between;
-        padding: 0 10px;
-        border-right: 2px solid rgb(209, 197, 197);
+        a {
+          width: 100%;
+          color: inherit;
+          text-decoration: none;
+        }
 
-        .pBox {
-          width: calc(100% - 17px);
-          display: flex;
-          align-items: center;
-          justify-content: center;
+        &.is-route-tag-active {
+          .homeBox,
+          .fhomeBox {
+            border: 2px solid #2e77e6;
 
-          p {
-            overflow: hidden;
-            font-size: 16px;
-            text-overflow: ellipsis;
-            white-space: nowrap;
+            p,
+            .el-icon,
+            .close {
+              color: #2e77e6;
+            }
           }
         }
 
-        .close {
+        .homeBox,
+        .fhomeBox {
+          height: 42px;
+          padding: 0 20px;
+          background: #eaf0f8;
+          border-radius: 16px;
+          opacity: 1;
           display: flex;
           align-items: center;
           justify-content: center;
-          flex-shrink: 0;
-          width: 12px;
-          height: 12px;
-          background-color: #878b91;
-          border-radius: 50%;
-          margin-left: 5px;
 
-          span {
-            color: #fff;
+          .el-icon {
+            margin-right: 5px;
           }
-        }
-
-        img {
-          cursor: pointer;
-          margin-top: 4px;
-          display: block;
-        }
-      }
-    }
-  }
-}
-
-.is-route-tag-active {
-  p {
-    color: #2e77e6;
-    font-weight: 600;
-  }
 
-  .el-icon {
-    color: #2e77e6;
-  }
+          p {
+            font-size: 17px;
+          }
 
-  .close {
-    background-color: #2e77e6 !important;
-  }
-}
+          .close {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            flex-shrink: 0;
+            margin-left: 5px;
+            font-weight: 200;
+            font-size: 24px;
 
-.el-dropdown {
-  min-width: 60px;
+            &:hover {
+              color: #2e77e6;
+            }
+          }
+        }
 
-  &.v-tags-view__item--affix {
-    flex-shrink: 0;
-  }
-}
+        .fhomeBox {
+          padding-right: 18px;
+          justify-content: space-between;
 
-.el-dropdown:hover {
-  p {
-    color: #2e77e6;
-    font-weight: 600;
-  }
-}
+          .pBox {
+            width: calc(100% - 17px);
+            display: flex;
+            align-items: center;
+            justify-content: center;
 
-.tag-view {
-  .el-dropdown:nth-child(2) {
-    .fhomeBox {
-      border-left: 2px solid rgb(209, 197, 197);
+            p {
+              overflow: hidden;
+              text-overflow: ellipsis;
+              white-space: nowrap;
+            }
+          }
+        }
+      }
     }
-  }
 
-  .el-dropdown:last-child {
-    .fhomeBox {
-      border-right: 0;
+    .all-tags-close {
+      flex-shrink: 0;
+      font-size: 17px;
+      font-weight: 400;
+      color: #121518;
+      cursor: pointer;
+
+      &:hover {
+        color: #2e77e6;
+      }
     }
   }
 }
 
 .draggable-ghost {
   background: #f7fafc;
-  border: 1px solid #4299e1;
+  border: 1px solid #2e77e6;
+  border-radius: 16px;
   opacity: 0.5;
 }
 </style>