Jelajahi Sumber

feat: 新增 路由标签栏支持拖拽与增强展示

hotchicken1996 1 tahun lalu
induk
melakukan
229147e37e

+ 2 - 2
client/package.json

@@ -49,9 +49,9 @@
     "dayjs": "^1.11.10",
     "diagram-js": "^12.3.0",
     "echarts": "^5.4.3",
-    "echarts-wordcloud": "^2.1.0",
     "echarts-gl": "^1.1.2",
     "echarts-liquidfill": "^2.0.6",
+    "echarts-wordcloud": "^2.1.0",
     "element-plus": "2.3.14",
     "fast-xml-parser": "^4.3.0",
     "highlight.js": "^11.8.0",
@@ -153,4 +153,4 @@
     "node": ">= 16.0.0",
     "pnpm": ">=8.6.0"
   }
-}
+}

+ 6 - 4
client/src/store/modules/tagsView.ts

@@ -20,10 +20,7 @@ export const isSameRouteTag = (
   currentTag: RouteLocationNormalizedLoaded,
   TargetTag: RouteLocationNormalizedLoaded
 ): boolean => {
-  return (
-    currentTag.path === TargetTag.path &&
-    JSON.stringify(currentTag?.query ?? {}) === JSON.stringify(TargetTag?.query ?? {})
-  )
+  return currentTag.fullPath === TargetTag.fullPath
 }
 
 export const useTagsViewStore = defineStore('tagsView', {
@@ -42,9 +39,14 @@ export const useTagsViewStore = defineStore('tagsView', {
   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

+ 160 - 151
client/src/views/OaSystem/oaLayout/tagList.vue

@@ -1,72 +1,82 @@
 <template>
   <div class="_tagList">
     <div class="tag-view">
-      <ul>
-        <ContextMenu
-          :ref="itemRefs.set"
-          :schema="[
-            {
-              icon: 'ep:refresh',
-              label: t('common.reload'),
-              disabled: selectedTag?.fullPath !== item.fullPath,
-              command: () => {
-                refreshSelectedTag(item)
-              }
-            },
-            {
-              icon: 'ep:close',
-              label: t('common.closeTab'),
-              disabled: !!visitedViews?.length && selectedTag?.meta.affix,
-              command: () => {
-                closeSelectedTag(item)
-              }
-            },
-            {
-              divided: true,
-              icon: 'ep:d-arrow-left',
-              label: t('common.closeTheLeftTab'),
-              disabled:
-                !!visitedViews?.length &&
-                (item.fullPath === visitedViews[0].fullPath ||
-                  selectedTag?.fullPath !== item.fullPath),
-              command: () => {
-                closeLeftTags()
-              }
-            },
-            {
-              icon: 'ep:d-arrow-right',
-              label: t('common.closeTheRightTab'),
-              disabled:
-                !!visitedViews?.length &&
-                (item.fullPath === visitedViews[visitedViews.length - 1].fullPath ||
-                  selectedTag?.fullPath !== item.fullPath),
-              command: () => {
-                closeRightTags()
+      <draggable
+        class="tags-draggable-list"
+        v-model="visitedViews"
+        item-key="fullPath"
+        ghost-class="draggable-ghost"
+        filter=".unDrag"
+        @end="
+          (event) => {
+            // todo 不允许拖拽到第一项或最后一项之外
+            console.log('draged: ', event)
+          }
+        "
+      >
+        <template #item="{ element: item, index }">
+          <ContextMenu
+            :ref="itemRefs.set"
+            :schema="[
+              {
+                icon: 'ep:refresh',
+                label: t('common.reload'),
+                disabled: selectedTag?.fullPath !== item.fullPath,
+                command: () => {
+                  refreshSelectedTag(item)
+                }
+              },
+              {
+                icon: 'ep:close',
+                label: t('common.closeTab'),
+                disabled: (!!visitedViews?.length && selectedTag?.meta.affix) || index === 0,
+                command: () => {
+                  closeSelectedTag(item)
+                }
+              },
+              {
+                divided: true,
+                icon: 'ep:d-arrow-left',
+                label: t('common.closeTheLeftTab'),
+                disabled:
+                  !!visitedViews?.length &&
+                  (item.fullPath === visitedViews[0].fullPath ||
+                    selectedTag?.fullPath !== item.fullPath),
+                command: () => {
+                  closeLeftTags()
+                }
+              },
+              {
+                icon: 'ep:d-arrow-right',
+                label: t('common.closeTheRightTab'),
+                disabled:
+                  !!visitedViews?.length &&
+                  (item.fullPath === visitedViews[visitedViews.length - 1].fullPath ||
+                    selectedTag?.fullPath !== item.fullPath),
+                command: () => {
+                  closeRightTags()
+                }
+              },
+              {
+                divided: true,
+                icon: 'ep:discount',
+                label: t('common.closeOther'),
+                disabled: selectedTag?.fullPath !== item.fullPath,
+                command: () => {
+                  closeOthersTags()
+                }
               }
-            },
-            {
-              divided: true,
-              icon: 'ep:discount',
-              label: t('common.closeOther'),
-              disabled: selectedTag?.fullPath !== item.fullPath,
-              command: () => {
-                closeOthersTags()
+            ]"
+            :tag-item="item"
+            :class="[
+              `${prefixCls}__item`,
+              item?.meta?.affix ? `${prefixCls}__item--affix unDrag` : '',
+              {
+                'is-route-tag-active': isActive(item)
               }
-            }
-          ]"
-          v-for="(item, index) in visitedViews"
-          :key="index"
-          :tag-item="item"
-          :class="[
-            `${prefixCls}__item`,
-            item?.meta?.affix ? `${prefixCls}__item--affix` : '',
-            {
-              'is-route-tag-active': isActive(item)
-            }
-          ]"
-          @visible-change="visibleChange"
-        >
-          <li>
+            ]"
+            @visible-change="visibleChange"
+          >
             <router-link :to="{ ...item }" exact v-slot="{ navigate }" v-if="index === 0">
               <div class="homeBox" @click="navigate">
                 <Icon :icon="item.meta.icon || ''" />
@@ -76,24 +86,32 @@
             <router-link :to="{ ...item }" exact v-slot="{ navigate }" v-else>
               <div class="fhomeBox" @click="navigate">
                 <div class="pBox">
-                  <p>
-                    {{ item.meta.title }}
-                    <!-- 用于多开页面的区分展示(例如详情页)-->
-                    {{ (item?.query?.name ?? '') !== '' ? `(${item?.query?.name})` : '' }}
-                  </p>
+                  <el-tooltip
+                    class="box-item"
+                    effect="dark"
+                    :content="`${item.meta.title}${(item?.query?.name ?? '') !== '' ? `( ${item?.query?.name} )` : ''}`"
+                    placement="top-start"
+                  >
+                    <p>
+                      {{ item.meta.title }}
+                      <!-- 用于多开页面的区分展示(例如详情页)-->
+                      {{ (item?.query?.name ?? '') !== '' ? `(${item?.query?.name})` : '' }}
+                    </p>
+                  </el-tooltip>
                 </div>
                 <div @click.stop.prevent="closeSelectedTag(item)" class="close">
                   <span>×</span>
                 </div>
               </div>
             </router-link>
-          </li>
-        </ContextMenu>
-      </ul>
+          </ContextMenu>
+        </template>
+      </draggable>
     </div>
   </div>
 </template>
 <script setup lang="ts">
+import 'vuedraggable'
 import { computed, onMounted, nextTick, ref, watch } from 'vue'
 import { Icon } from '@/components/Icon'
 import type { RouteLocationNormalizedLoaded } from 'vue-router'
@@ -105,6 +123,7 @@ import { useTemplateRefsList } from '@vueuse/core'
 import { ContextMenu, ContextMenuExpose } from '@/layout/components/ContextMenu'
 import { useI18n } from '@/hooks/web/useI18n'
 import { useDesign } from '@/hooks/web/useDesign'
+import draggable from 'vuedraggable'
 
 const { getPrefixCls } = useDesign()
 const prefixCls = getPrefixCls('tags-view')
@@ -117,7 +136,12 @@ const tagsViewStore = useTagsViewStore()
 const affixTagArr = ref<RouteLocationNormalizedLoaded[]>([])
 // 所有右键菜单组件的元素
 const itemRefs = useTemplateRefsList<ComponentRef<typeof ContextMenu & ContextMenuExpose>>()
-const visitedViews = computed(() => tagsViewStore.getVisitedViews)
+const visitedViews = computed({
+  get: () => tagsViewStore.getVisitedViews,
+  set: (views) => tagsViewStore.allResetView(views)
+})
+
+console.log('visitedViews: ', visitedViews)
 
 // 初始化tag
 const initTags = () => {
@@ -223,19 +247,6 @@ onMounted(() => {
 })
 </script>
 <style lang="scss" scoped>
-div,
-h1,
-h2,
-h3,
-h4,
-h5,
-h6,
-p,
-ul,
-li {
-  box-sizing: border-box;
-}
-
 ._tagList {
   width: 100%;
   height: 36px;
@@ -248,86 +259,80 @@ li {
     width: 100%;
     height: 100%;
 
-    ul {
+    .tags-draggable-list {
       width: 100%;
       height: 100%;
       display: flex;
       align-items: center;
 
-      li {
+      a {
         width: 100%;
+        display: block;
         height: 100%;
-        cursor: pointer;
+        border-radius: 10px;
+        color: inherit;
+        text-decoration: none;
+        padding: 2px;
+        box-sizing: border-box;
+      }
 
-        a {
-          width: 100%;
-          display: block;
-          height: 100%;
-          border-radius: 10px;
-          color: inherit;
-          text-decoration: none;
-          padding: 2px;
-          box-sizing: border-box;
+      .homeBox {
+        width: 70px;
+        height: 16px;
+        opacity: 1;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+
+        p {
+          margin-left: 5px;
+          font-size: 16px;
         }
+      }
+
+      .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);
 
-        .homeBox {
-          width: 70px;
-          height: 16px;
-          opacity: 1;
+        .pBox {
+          width: calc(100% - 17px);
           display: flex;
           align-items: center;
           justify-content: center;
 
           p {
-            margin-left: 5px;
+            overflow: hidden;
             font-size: 16px;
+            text-overflow: ellipsis;
+            white-space: nowrap;
           }
         }
 
-        .fhomeBox {
-          width: 100%;
-          height: 16px;
+        .close {
           display: flex;
           align-items: center;
-          justify-content: space-between;
-          padding: 0 10px;
-          border-right: 2px solid rgb(209, 197, 197);
-
-          .pBox {
-            width: calc(100% - 17px);
-            display: flex;
-            align-items: center;
-            justify-content: center;
-
-            p {
-              overflow: hidden;
-              font-size: 16px;
-              text-overflow: ellipsis;
-              white-space: nowrap;
-            }
-          }
-
-          .close {
-            display: flex;
-            align-items: center;
-            justify-content: center;
-            flex-shrink: 0;
-            width: 12px;
-            height: 12px;
-            background-color: #878b91;
-            border-radius: 50%;
-            margin-left: 5px;
+          justify-content: center;
+          flex-shrink: 0;
+          width: 12px;
+          height: 12px;
+          background-color: #878b91;
+          border-radius: 50%;
+          margin-left: 5px;
 
-            span {
-              color: #fff;
-            }
+          span {
+            color: #fff;
           }
+        }
 
-          img {
-            cursor: pointer;
-            margin-top: 4px;
-            display: block;
-          }
+        img {
+          cursor: pointer;
+          margin-top: 4px;
+          display: block;
         }
       }
     }
@@ -365,18 +370,22 @@ li {
 }
 
 .tag-view {
-  ul {
-    .el-dropdown:nth-child(2) {
-      .fhomeBox {
-        border-left: 2px solid rgb(209, 197, 197);
-      }
+  .el-dropdown:nth-child(2) {
+    .fhomeBox {
+      border-left: 2px solid rgb(209, 197, 197);
     }
+  }
 
-    .el-dropdown:last-child {
-      .fhomeBox {
-        border-right: 0;
-      }
+  .el-dropdown:last-child {
+    .fhomeBox {
+      border-right: 0;
     }
   }
 }
+
+.draggable-ghost {
+  background: #f7fafc;
+  border: 1px solid #4299e1;
+  opacity: 0.5;
+}
 </style>

+ 4 - 4
client/src/views/OaSystem/projectCenter/projectBook/myProject.vue

@@ -74,12 +74,12 @@
         <div class="btnBox">
           <el-button type="primary" style="background: #3485ff" @click="searchHandle">
             <img src="@/assets/imgs/OA/search.png" class="mr-8px" alt="" />
-            查询</el-button
-          >
+            查询
+          </el-button>
           <el-button type="primary">
             <img src="@/assets/imgs/OA/open.png" class="mr-8px" alt="" />
-            导出</el-button
-          >
+            导出
+          </el-button>
         </div>
       </div>
     </div>