shish 10 месяцев назад
Родитель
Сommit
440bcde0e6
2 измененных файлов с 330 добавлено и 19 удалено
  1. 318 0
      ghsApp/src/admin/home/components/scrollable-tabs.vue
  2. 12 19
      ghsApp/src/admin/home/member.vue

+ 318 - 0
ghsApp/src/admin/home/components/scrollable-tabs.vue

@@ -0,0 +1,318 @@
+<template>
+  <view class="scrollable-tabs-container" :style="{ top: top + 'upx', height: height + 'upx' }">
+    <scroll-view 
+      class="tabs-scroll" 
+      :scroll-x="true" 
+      :scroll-left="scrollLeft"
+      :show-scrollbar="false"
+      :enable-flex="true"
+      scroll-with-animation
+    >
+      <view class="tabs-content">
+        <view 
+          v-for="(tab, index) in tabs" 
+          :key="index"
+          class="tab-item"
+          :class="{ active: currentTab === index }"
+          :id="`tab-${index}`"
+          @click="handleTabClick(index)"
+        >
+          <view class="tab-content">
+            <text class="tab-text">{{ tab.name }}</text>
+            <text class="tab-count">({{ tab.value || 0 }})</text>
+          </view>
+          <view v-if="currentTab === index" class="tab-indicator"></view>
+        </view>
+      </view>
+    </scroll-view>
+  </view>
+</template>
+
+<script>
+export default {
+  name: 'ScrollableTabs',
+  props: {
+    tabs: {
+      type: Array,
+      default: () => []
+    },
+    currentTab: {
+      type: Number,
+      default: 0
+    },
+    top: {
+      type: Number,
+      default: 0
+    },
+    height: {
+      type: Number,
+      default: 104
+    },
+    isFixed: {
+      type: Boolean,
+      default: true
+    }
+  },
+  data() {
+    return {
+      scrollLeft: 0,
+      tabItemWidth: 120, // 每个tab项的最小宽度(upx)
+      platform: ''
+    }
+  },
+  created() {
+    // 获取当前平台信息
+    // #ifdef H5
+    this.platform = 'h5'
+    // #endif
+    // #ifdef MP-WEIXIN
+    this.platform = 'mp-weixin'
+    // #endif
+    // #ifdef APP-PLUS
+    this.platform = 'app-plus'
+    // #endif
+  },
+  watch: {
+    currentTab: {
+      handler(newVal) {
+        this.$nextTick(() => {
+          this.scrollToActiveTab(newVal)
+        })
+      },
+      immediate: true
+    }
+  },
+  methods: {
+    handleTabClick(index) {
+      if (index !== this.currentTab) {
+        this.$emit('change', { index })
+        
+        // 小程序端延迟执行滚动,避免卡顿
+        // #ifdef MP-WEIXIN
+        setTimeout(() => {
+          this.scrollToActiveTab(index)
+        }, 50)
+        // #endif
+        
+        // #ifndef MP-WEIXIN
+        this.scrollToActiveTab(index)
+        // #endif
+      }
+    },
+    
+    scrollToActiveTab(index) {
+      // 使用简化的滚动计算,提升跨平台兼容性
+      this.$nextTick(() => {
+        const query = uni.createSelectorQuery().in(this)
+        
+        // 获取容器和tab的信息
+        query.select('.tabs-scroll').boundingClientRect()
+        query.select(`#tab-${index}`).boundingClientRect()
+        query.exec((res) => {
+          if (!res || res.length < 2 || !res[0] || !res[1]) {
+            // 降级方案:简单计算
+            this.scrollLeft = index * 160
+            return
+          }
+          
+          const containerRect = res[0]
+          const tabRect = res[1]
+          const containerWidth = containerRect.width
+          
+          // 计算tab相对于容器的位置
+          const tabLeft = tabRect.left - containerRect.left + this.scrollLeft
+          const tabCenter = tabLeft + tabRect.width / 2
+          
+          // 计算理想的滚动位置(让当前tab居中)
+          const idealScrollLeft = tabCenter - containerWidth / 2
+          
+          // 获取所有tab的总宽度
+          query.select('.tabs-content').boundingClientRect().exec((contentRes) => {
+            if (contentRes && contentRes[0]) {
+              const maxScrollLeft = Math.max(0, contentRes[0].width - containerWidth)
+              this.scrollLeft = Math.max(0, Math.min(idealScrollLeft, maxScrollLeft))
+            } else {
+              // 降级方案
+              this.scrollLeft = Math.max(0, idealScrollLeft)
+            }
+          })
+        })
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.scrollable-tabs-container {
+  position: fixed;
+  left: 0;
+  right: 0;
+  background-color: #fff;
+  border-bottom: 1upx solid #f0f0f0;
+  z-index: 999;
+  
+  width: 100%;
+  
+  /* #ifdef H5 */
+  /* 手机mobile屏幕小于1000px */
+  @media screen and (max-width: 1100px) {
+    width: 100%;
+  }
+  
+  /* 收银台cashier屏幕大于1000px */
+  @media screen and (min-width: 1100px) {
+    width: 40%;
+  }
+  /* #endif */
+}
+
+.tabs-scroll {
+  height: 100%;
+  white-space: nowrap;
+  
+  /* #ifdef H5 */
+  ::-webkit-scrollbar {
+    display: none;
+  }
+  /* #endif */
+}
+
+.tabs-content {
+  display: flex;
+  height: 100%;
+  align-items: center;
+  padding: 0 20upx;
+  
+  /* 小程序端优化 */
+  /* #ifdef MP-WEIXIN */
+  flex-shrink: 0;
+  /* #endif */
+}
+
+.tab-item {
+  position: relative;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  min-width: 140upx;
+  padding: 16upx 28upx;
+  height: 100%;
+  /* #ifdef H5 */
+  cursor: pointer;
+  /* #endif */
+  
+  /* #ifdef H5 */
+  transition: all 0.3s ease;
+  /* #endif */
+  
+  /* #ifdef APP-PLUS || MP-WEIXIN */
+  transition: color 0.2s;
+  /* #endif */
+  
+  /* #ifdef H5 */
+  // 增加点击区域,提升用户体验
+  &::before {
+    content: '';
+    position: absolute;
+    top: -10upx;
+    left: -10upx;
+    right: -10upx;
+    bottom: -10upx;
+    z-index: -1;
+  }
+  /* #endif */
+  
+  &:not(:last-child) {
+    margin-right: 20upx;
+  }
+  
+  &.active {
+    .tab-text {
+      color: #3385FF;
+      font-weight: bold;
+    }
+    
+    .tab-count {
+      color: #3385FF;
+    }
+  }
+}
+
+.tab-content {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  white-space: nowrap;
+  
+  /* App端优化 */
+  /* #ifdef APP-PLUS */
+  flex-shrink: 0;
+  /* #endif */
+}
+
+.tab-text {
+  font-size: 30upx;
+  color: #666;
+  
+  /* #ifdef H5 */
+  transition: color 0.3s ease;
+  /* #endif */
+  
+  /* #ifdef APP-PLUS || MP-WEIXIN */
+  transition: color 0.2s;
+  /* #endif */
+}
+
+.tab-count {
+  font-size: 24upx;
+  color: #999;
+  margin-top: 4upx;
+  
+  /* #ifdef H5 */
+  transition: color 0.3s ease;
+  /* #endif */
+  
+  /* #ifdef APP-PLUS || MP-WEIXIN */
+  transition: color 0.2s;
+  /* #endif */
+}
+
+.tab-indicator {
+  position: absolute;
+  bottom: 0;
+  left: 50%;
+  transform: translateX(-50%);
+  width: 40upx;
+  height: 4upx;
+  background-color: #3385FF;
+  border-radius: 2upx;
+}
+
+/* #ifdef H5 */
+// 深色模式支持
+@media (prefers-color-scheme: dark) {
+  .scrollable-tabs-container {
+    background-color: #1f1f1f;
+    border-bottom-color: #333;
+  }
+  
+  .tab-text {
+    color: #ccc;
+  }
+  
+  .tab-count {
+    color: #999;
+  }
+  
+  .tab-item.active {
+    .tab-text,
+    .tab-count {
+      color: #3385FF;
+    }
+  }
+}
+/* #endif */
+</style>

+ 12 - 19
ghsApp/src/admin/home/member.vue

@@ -11,7 +11,7 @@
 			</view>
     </view>
     <view class="app-tabs">
-      <app-tabs :tabs="tabs" :isFixed="false" :top="50" :currentTab="tabIndex" :height="100" @change="change" itemWidth="14.2%" :isAdd="false" />
+      <scrollable-tabs :tabs="tabs" :isFixed="true" :top="100" :currentTab="tabIndex" :height="104" @change="change" />
      </view>
     <div class="list-wrap">
     <block v-if="!$util.isEmpty(list.data)">
@@ -69,7 +69,7 @@
   </view>
 </template>
 <script>
-import AppTabs from "@/components/plugin/tabs";
+import ScrollableTabs from "./components/scrollable-tabs";
 import NotLogin from "@/components/not-login";
 import TuiListCell from "@/components/plugin/list-cell";
 import BadgeModule from "@/components/plugin/badge";
@@ -85,7 +85,7 @@ import { mapGetters } from "vuex"
 export default {
   name: "memebr",
   components: {
-    AppTabs,
+    ScrollableTabs,
     TuiListCell,
     BadgeModule,
     AppWrapperEmpty,
@@ -113,11 +113,15 @@ export default {
           value: 0
         },
         {
-          name: "零售",
+          name: "大客",
           value: 0
         },
         {
-          name: "休眠",
+          name: "零售客",
+          value: 0
+        },
+        {
+          name: "休眠客",
           value: 0
         },
         {
@@ -125,7 +129,7 @@ export default {
           value: 0
         },
         {
-          name: "已删",
+          name: "已删",
           value: 0
         }
       ],
@@ -274,18 +278,7 @@ export default {
   padding-top: 100upx;
   padding-bottom: 20upx;
 }
-.app-tabs {
-    position: fixed;
-    /* 手机mobile屏幕小于1000px */
-    @media screen and (max-width: 1100px) {
-      width: 100%;
-    }
-    /* 收银台cashier屏幕大于1000px */
-    @media screen and (min-width:1100px) {
-      width: 40%;
-    }   
-    z-index: 9;
-  }
+/* .app-tabs 样式已移到scrollable-tabs组件内部 */
 .tabs-wrap {
   position: fixed;
   .tabs-left {
@@ -319,7 +312,7 @@ export default {
     }
   }
   .list-wrap {
-    padding-top: 100upx;
+    padding-top:114upx;
     .list {
       background-color: #fff;
       margin-bottom: 20upx;