Quellcode durchsuchen

feat(mobile): 重构首页产品展示布局并优化视觉样式

- 将产品选项卡和卡片内容分离为独立区域,实现更灵活的布局控制
- 添加卡片展示区域,支持单列和多列两种布局模式
- 优化视频容器的定位和尺寸,调整覆盖层和选项卡的定位
- 改进选项卡样式,增强交互反馈效果
- 添加产品卡片组件,包含标题、描述、特性标签和操作按钮
reaper vor 1 Monat
Ursprung
Commit
d5f2a90e32
2 geänderte Dateien mit 175 neuen und 31 gelöschten Zeilen
  1. 158 24
      app/components/mobile/home/ProductTabs.vue
  2. 17 7
      app/pages/mobile/index.vue

+ 158 - 24
app/components/mobile/home/ProductTabs.vue

@@ -1,35 +1,56 @@
 <template>
-  <section class="mb-tabs-container">
-    <section class="mb-tabs">
-      <div v-for="(tab, index) in productTabs" :key="index" class="tab-item" :class="{ active: activeTab === index }"
-        @click="activeTab = index">
-        {{ tab.name }}
+  <section class="mb-product-section">
+    <!-- tabs 盖在视频下半部分 -->
+    <section class="mb-tabs-wrapper">
+      <section class="mb-tabs">
+        <div v-for="(tab, index) in productTabs" :key="index" class="tab-item" :class="{ active: activeTab === index }"
+          @click="activeTab = index">
+          {{ tab.name }}
+        </div>
+      </section>
+    </section>
+    <!-- cards 正常向下排列 -->
+    <section class="mb-cards" :class="{ 'is-multi': isMultiLayout }">
+      <div v-for="(card, index) in displayCards" :key="index" class="mb-card">
+        <h3 class="card-title">{{ card.title }}</h3>
+        <p class="card-description">{{ card.description }}</p>
+        <div v-if="card.features?.length" class="card-features">
+          <span v-for="(feature, idx) in card.features" :key="idx" class="feature-tag">{{ feature }}</span>
+        </div>
+        <div class="card-actions">
+          <button class="btn-primary" type="button">更多详情</button>
+          <button class="btn-secondary" type="button">0元体验</button>
+        </div>
       </div>
-      <!-- <van-tabs v-model:active="activeTab">
-        <van-tab v-for="(tab, index) in productTabs" :title="tab.name">
-        </van-tab>
-      </van-tabs> -->
     </section>
   </section>
 </template>
+
 <script setup>
-import { productTabs } from '~/utils/product';
+import { ref, computed } from 'vue'
+import { productTabs } from '~/utils/product'
 
 const activeTab = ref(0)
 
-
 const currentTabData = computed(() => productTabs[activeTab.value])
-
-
+const displayCards = computed(() => {
+  const cards = currentTabData.value?.cards ?? []
+  return cards
+})
+const isMultiLayout = computed(() => currentTabData.value?.layout === 'multi')
 </script>
+
 <style lang="scss" scoped>
-.mb-tabs-container {
-  position: absolute;
+.mb-product-section {
+  width: 100%;
+}
+
+.mb-tabs-wrapper {
+  position: relative;
   width: 100%;
-  left: 0;
-  top: 58%;
-  transform: translateY(-50%);
-  z-index: 2;
+  z-index: 3;
+  padding: 0 6px;
+  box-sizing: border-box;
 }
 
 .mb-tabs {
@@ -42,13 +63,11 @@ const currentTabData = computed(() => productTabs[activeTab.value])
   backdrop-filter: blur(20px);
   display: flex;
   align-items: center;
-  gap: 8px;
   padding: 0 12px;
   overflow-x: auto;
   scroll-behavior: smooth;
   -webkit-overflow-scrolling: touch;
 
-  /* 隐藏滚动条 */
   scrollbar-width: none;
   -ms-overflow-style: none;
 
@@ -58,10 +77,10 @@ const currentTabData = computed(() => productTabs[activeTab.value])
 
   .tab-item {
     flex-shrink: 0;
-    font-size: 14px;
     font-weight: 400;
-    line-height: 1;
     color: #ffffff;
+    font-size: 12px;
+    line-height: 16px;
     cursor: pointer;
     white-space: nowrap;
     transition: color 0.3s ease, background 0.3s ease, transform 0.3s ease;
@@ -85,7 +104,122 @@ const currentTabData = computed(() => productTabs[activeTab.value])
       transform: translateY(-1px);
     }
   }
+}
+
+.mb-cards {
+  width: 100%;
+  box-sizing: border-box;
+  margin-top: 20px;
+  padding: 0 10px;
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+}
+
+.mb-cards.is-multi {
+  flex-direction: row;
+  overflow-x: auto;
+  scroll-snap-type: x mandatory;
+  scroll-padding: 10px;
+  scroll-behavior: smooth;
+  -webkit-overflow-scrolling: touch;
+  scrollbar-width: none;
+  -ms-overflow-style: none;
+
+  &::-webkit-scrollbar {
+    display: none;
+  }
+}
+
+.mb-card {
+  width: 100%;
+  border-radius: 20px;
+  border: 1px solid rgba(198, 186, 255, 0.30);
+  background: linear-gradient(177deg, rgba(165, 101, 255, 0.25) -20.47%, rgba(3, 0, 20, 0.25) 134.25%);
+  backdrop-filter: blur(20px);
+  padding: 18px 16px 16px;
+  box-sizing: border-box;
+  min-height: 290px;
+  display: flex;
+  flex-direction: column;
+}
+
+.mb-cards.is-multi .mb-card {
+  flex: 0 0 96%;
+  scroll-snap-align: center;
+}
 
+.card-title {
+  margin: 0;
+  text-align: center;
+  color: #ffffff;
+  font-size: 16px;
+  font-weight: 500;
+  line-height: 18px;
+}
 
+.card-description {
+  margin: 12px 0 0;
+  color: rgba(255, 255, 255, 0.65);
+  font-size: 12px;
+  font-weight: 400;
+  line-height: 18px;
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 6;
+  overflow: hidden;
+}
+
+.card-features {
+  margin-top: 12px;
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+}
+
+.feature-tag {
+  font-size: 12px;
+  font-weight: 400;
+  line-height: 12px;
+  color: #9b71ff;
+}
+
+.card-actions {
+  margin-top: auto;
+  padding-top: 14px;
+  display: flex;
+  justify-content: flex-end;
+  gap: 12px;
+}
+
+.btn-primary {
+  display: flex;
+  width: 72px;
+  height: 20px;
+  justify-content: center;
+  align-items: center;
+  flex-shrink: 0;
+  border-radius: 4px;
+  background: linear-gradient(117deg, #A39DFF -41.28%, #7D46FF 60.31%);
+  color: #FFF;
+  font-size: 12px;
+  font-weight: 400;
+  padding: 2px 6px;
+}
+
+.btn-secondary {
+  display: flex;
+  width: 72px;
+  height: 20px;
+  justify-content: center;
+  align-items: center;
+  flex-shrink: 0;
+  border-radius: 4px;
+  background: rgba(255, 255, 255, 0.20);
+  backdrop-filter: blur(2px);
+  color: #FFF;
+  font-size: 12px;
+  font-weight: 400;
+  padding: 2px 6px;
 }
-</style>
+</style>

+ 17 - 7
app/pages/mobile/index.vue

@@ -9,7 +9,7 @@
         <video ref="videoRef" class="mb-video" preload="auto" :src="homeVideo" autoplay loop muted playsinline
           @loadedmetadata="onVideoLoaded"></video>
         <ParticlesCanvas :size="1" class="canvas-overlay" />
-        <ProductTabs />
+        <ProductTabs class="mb-tabs-positioner" />
       </div>
     </section>
   </section>
@@ -17,7 +17,7 @@
 
 <script setup>
 import homeVideo from '~/assets/video/home.webm'
-import ProductTabs from '~/components/mobile/home/ProductTabs.vue';
+import ProductTabs from '~/components/mobile/home/ProductTabs.vue'
 </script>
 
 <style scoped lang="scss">
@@ -54,25 +54,35 @@ import ProductTabs from '~/components/mobile/home/ProductTabs.vue';
   }
 
   .mb-video-content {
+    height: 500px;
     position: relative;
-    margin-top: -10%;
     z-index: 0;
 
-
     .mb-video {
       width: 100%;
       height: auto;
+      position: absolute;
+      top: 20%;
+      transform: translateY(-50%);
+      left: 0;
     }
 
     .canvas-overlay {
       position: absolute;
-      top: 40%;
+      top: 18%;
       transform: translateY(-50%);
       left: 0;
       height: 80px;
+      z-index: 1;
     }
-  }
-
 
+    .mb-tabs-positioner {
+      position: absolute;
+      top: 56%;
+      left: 0;
+      transform: translateY(-50%);
+      z-index: 1
+    }
+  }
 }
 </style>