فهرست منبع

新增卡片式自动轮播

wangfumin 3 ماه پیش
والد
کامیت
6bfb6661c5

+ 226 - 0
components/card-carousel/index.js

@@ -0,0 +1,226 @@
+Component({
+  properties: {
+    list: {
+      type: Array,
+      value: []
+    },
+    // 是否展示指示器
+    showIndicators: {
+      type: Boolean,
+      value: true
+    },
+    // 左右边距(px)
+    paddingLR: {
+      type: Number,
+      value: 30
+    },
+    // 中间卡片高度(px)
+    centerHeight: {
+      type: Number,
+      value: 200
+    },
+    // 左右两侧卡片高度(px)
+    sideHeight: {
+      type: Number,
+      value: 130
+    },
+    duration: {
+      type: Number,
+      value: 300
+    },
+    // 当前项索引
+    current: {
+      type: Number,
+      value: 0
+    },
+    // 是否使用 navigator 进行跳转(仅当前项且有 url)
+    useNavigator: {
+      type: Boolean,
+      value: true
+    },
+    onlineIcon: {
+      type: String,
+      value: 'https://klmybwg.4dage.com/mini/wxImg/exhibition/online-kz.png'
+    },
+    // 是否自动轮播
+    autoplay: {
+      type: Boolean,
+      value: false
+    },
+    // 轮播间隔时间(毫秒)
+    interval: {
+      type: Number,
+      value: 3000
+    }
+  },
+
+  data: {
+    preloadedImages: {},
+    autoplayTimer: null, // 自动轮播定时器
+    isUserInteracting: false // 用户是否正在交互
+  },
+
+  observers: {
+    'list': function(newList) {
+      if (newList && newList.length > 0) {
+        this.preloadImages(newList);
+        // 当列表数据更新时,重新启动自动轮播
+        this.restartAutoplay();
+      }
+    },
+    'autoplay, interval': function(autoplay, interval) {
+      // 当自动轮播设置改变时,重新启动
+      if (autoplay) {
+        this.startAutoplay();
+      } else {
+        this.stopAutoplay();
+      }
+    }
+  },
+
+  lifetimes: {
+    attached() {
+      if (this.data.list && this.data.list.length > 0) {
+        this.preloadImages(this.data.list);
+      }
+      // 组件初始化时启动自动轮播
+      if (this.data.autoplay) {
+        this.startAutoplay();
+      }
+    },
+    detached() {
+      // 组件销毁时清理定时器
+      this.stopAutoplay();
+    }
+  },
+
+  pageLifetimes: {
+    show() {
+      // 页面显示时重启自动轮播
+      if (this.data.autoplay && !this.data.isUserInteracting) {
+        this.startAutoplay();
+      }
+    },
+    hide() {
+      // 页面隐藏时停止自动轮播
+      this.stopAutoplay();
+    }
+  },
+
+  methods: {
+    // 预加载图片
+    preloadImages(list) {
+      const preloadedImages = {};
+      list.forEach((item, index) => {
+        if (item.image && !this.data.preloadedImages[item.image]) {
+          // 创建图片对象进行预加载
+          const img = wx.createOffscreenCanvas().getContext('2d');
+          preloadedImages[item.image] = true;
+        }
+      });
+      
+      // 同时预加载在线观展图标
+      if (this.data.onlineIcon && !this.data.preloadedImages[this.data.onlineIcon]) {
+        const img = wx.createOffscreenCanvas().getContext('2d');
+        preloadedImages[this.data.onlineIcon] = true;
+      }
+      
+      this.setData({
+        preloadedImages: { ...this.data.preloadedImages, ...preloadedImages }
+      });
+    },
+
+    onSwiperChange(e) {
+      const index = e.detail.current;
+      this.setData({ current: index });
+      this.triggerEvent('change', { current: index });
+      // swiper滑动时暂停自动轮播一段时间
+      this.pauseAutoplayTemporarily();
+    },
+
+    // 左右图片可点击切换,不可跳转
+    onSlideTap(e) {
+      const index = e.currentTarget.dataset.index;
+      if (index === this.data.current) return; // 当前项点击保持
+      this.setData({ current: index });
+      this.triggerEvent('change', { current: index });
+      // 用户点击切换时暂停自动轮播一段时间
+      this.pauseAutoplayTemporarily();
+    },
+
+    // 当前项且无 url 的点击,向外抛事件处理
+    onCurrentTap(e) {
+      const index = e.currentTarget.dataset.index;
+      const item = this.data.list[index];
+      this.triggerEvent('exhibitiontap', { item, index });
+    },
+
+    // 线上观展点击事件(有URL的情况)
+    onOnlineExhibitionTap(e) {
+      const index = e.currentTarget.dataset.index;
+      const item = this.data.list[index];
+      this.triggerEvent('onlineexhibitiontap', { item, index, url: item.url });
+    },
+
+
+    onDotTap(e) {
+      const index = e.currentTarget.dataset.index;
+      this.setData({ current: index });
+      this.triggerEvent('change', { current: index });
+      // 用户点击指示器时暂停自动轮播一段时间
+      this.pauseAutoplayTemporarily();
+    },
+
+    // 启动自动轮播
+    startAutoplay() {
+      if (!this.data.autoplay || this.data.list.length <= 1) {
+        return;
+      }
+      
+      this.stopAutoplay(); // 先清除可能存在的定时器
+      
+      const timer = setInterval(() => {
+        if (!this.data.isUserInteracting) {
+          const nextIndex = (this.data.current + 1) % this.data.list.length;
+          this.setData({ current: nextIndex });
+          this.triggerEvent('change', { current: nextIndex });
+        }
+      }, this.data.interval);
+      
+      this.setData({ autoplayTimer: timer });
+    },
+
+    // 停止自动轮播
+    stopAutoplay() {
+      if (this.data.autoplayTimer) {
+        clearInterval(this.data.autoplayTimer);
+        this.setData({ autoplayTimer: null });
+      }
+    },
+
+    // 重启自动轮播
+    restartAutoplay() {
+      if (this.data.autoplay) {
+        this.stopAutoplay();
+        this.startAutoplay();
+      }
+    },
+
+    // 暂时暂停自动轮播(用户交互时)
+    pauseAutoplayTemporarily(duration = 3000) {
+      if (!this.data.autoplay) return;
+      
+      this.setData({ isUserInteracting: true });
+      this.stopAutoplay();
+      
+      setTimeout(() => {
+        this.setData({ isUserInteracting: false });
+        if (this.data.autoplay) {
+          this.startAutoplay();
+        }
+      }, duration);
+    }
+  }
+});
+
+

+ 5 - 0
components/card-carousel/index.json

@@ -0,0 +1,5 @@
+{
+  "component": true,
+  "usingComponents": {}
+}
+

+ 35 - 0
components/card-carousel/index.wxml

@@ -0,0 +1,35 @@
+<view class="card-carousel" style="width:100%;">
+  <swiper class="carousel-swiper" current="{{current}}" circular="true" previous-margin="{{paddingLR}}px" next-margin="{{paddingLR}}px" duration="{{duration}}" style="height: {{centerHeight}}px;" bindchange="onSwiperChange">
+    <block wx:for="{{list}}" wx:key="index" wx:for-index="index" wx:for-item="item">
+      <swiper-item>
+        <view class="slide {{current === index ? 'active' : 'inactive'}}" data-index="{{index}}" bindtap="onSlideTap" style="height: {{centerHeight}}px;">
+          <!-- 导航器容器(仅当前项且有URL时显示) -->
+          <view wx:if="{{item.url && useNavigator}}" class="card {{current === index ? 'active-card' : 'side-card'}}" data-index="{{index}}" bindtap="{{current === index ? 'onOnlineExhibitionTap' : ''}}">
+            <image class="card-image" src="{{item.image}}" mode="aspectFill" style="height: {{current === index ? centerHeight : sideHeight}}px;" lazy-load="{{false}}"></image>
+            <!-- 在线观展覆盖层(仅当前项显示) -->
+            <view class="online-exhibition {{current === index ? 'show' : 'hide'}}">
+              <image class="online-icon" src="{{onlineIcon}}" mode="aspectFit"></image>
+              <text class="online-text">线上观展</text>
+            </view>
+            <!-- 遮罩层(非当前项显示) -->
+            <view class="mask {{current === index ? 'hide' : 'show'}}"></view>
+          </view>
+          
+          <!-- 普通视图容器(无URL时使用) -->
+          <view wx:else class="card {{current === index ? 'active-card' : 'side-card'}}" data-index="{{index}}" catchtap="{{current === index ? 'onCurrentTap' : ''}}">
+            <image class="card-image" src="{{item.image}}" mode="aspectFill" style="height: {{current === index ? centerHeight : sideHeight}}px;" lazy-load="{{false}}"></image>
+            <!-- 遮罩层(非当前项显示) -->
+            <view class="mask {{current === index ? 'hide' : 'show'}}"></view>
+          </view>
+        </view>
+      </swiper-item>
+    </block>
+  </swiper>
+
+  <view class="indicators" wx:if="{{showIndicators && list.length > 1}}" style="bottom: 8px;">
+    <block wx:for="{{list}}" wx:key="i" wx:for-index="i">
+      <view class="dot {{current === i ? 'active' : ''}}" data-index="{{i}}" catchtap="onDotTap"></view>
+    </block>
+  </view>
+</view>
+

+ 129 - 0
components/card-carousel/index.wxss

@@ -0,0 +1,129 @@
+.card-carousel {
+  position: relative;
+  width: 100%;
+}
+
+.carousel-swiper {
+  width: 100%;
+}
+
+.slide {
+  position: relative;
+  transition: transform 0.3s ease;
+  padding: 0 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.slide.inactive {
+  transform: translateX(0);
+}
+
+.slide.active .card-image {
+  border-radius: 16rpx;
+}
+
+.slide.inactive .card-image {
+  filter: blur(0px);
+}
+
+.card {
+  position: relative;
+  overflow: hidden;
+
+}
+
+.active-card {
+  width: 100%;
+}
+
+.side-card {
+  width: 100%;
+  transition: all 0.3s ease;
+}
+
+.slide.inactive .side-card {
+  
+}
+
+.card-image {
+  width: 100%;
+  display: block;
+}
+
+.mask {
+  position: absolute;
+  left: 0;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0,0,0,0.25);
+  pointer-events: none;
+  transition: opacity 0.3s ease;
+}
+
+.mask.show {
+  opacity: 1;
+}
+
+.mask.hide {
+  opacity: 0;
+}
+
+.indicators {
+  position: absolute;
+  width: 100%;
+  left: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 8rpx;
+}
+
+.dot {
+  width: 12rpx;
+  height: 12rpx;
+  border-radius: 50%;
+  background: rgba(255,255,255,0.6);
+}
+
+.dot.active {
+  background: #ffffff;
+}
+
+.online-exhibition {
+  position: absolute;
+  left: 0;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  color: #fff;
+  transition: opacity 0.3s ease;
+}
+
+.online-exhibition.show {
+  opacity: 1;
+  pointer-events: none;
+}
+
+.online-exhibition.hide {
+  opacity: 0;
+  pointer-events: none;
+}
+
+.online-icon {
+  width: 80rpx;
+  height: 80rpx;
+  margin-bottom: 12rpx;
+}
+
+.online-text {
+  font-size: 28rpx;
+  color: #fff;
+}
+

+ 1 - 1
pages/collection/index.wxml

@@ -36,7 +36,7 @@
           <image class="item-image" src="{{urlImg + item.img}}" mode="aspectFill"></image>
           <view class="view-button">
             <text>查看</text>
-            <text class="arrow">→</text>
+            <image class="news-img" src="https://klmybwg.4dage.com/mini/wxImg/indexPage/news.png"></image>
           </view>
           <view class="item-info">
             <view class="item-category">{{item.category || '三维文物'}}</view>

+ 8 - 3
pages/collection/index.wxss

@@ -5,14 +5,13 @@
   background-size: cover;
   background-repeat: no-repeat;
   box-sizing: border-box;
-  padding: 0 30rpx;
 }
 
 .top-section {
   position: sticky;
   top: 0;
   z-index: 10;
-  background-color: rgba(245, 242, 235, 0.95);
+  padding: 0 40rpx;
 }
 
 .nav-scroll-container {
@@ -49,7 +48,7 @@
 }
 
 .search-container {
-  padding: 20rpx 10rpx;
+  padding: 40rpx 10rpx;
 }
 
 .search-input {
@@ -96,6 +95,7 @@
   display: flex;
   flex-direction: column;
   gap: 30rpx;
+  padding: 0 40rpx;
 }
 
 .collection-item {
@@ -136,6 +136,11 @@
 .view-button text {
   font-size: 28rpx;
 }
+.news-img{
+  width: 30rpx;
+  height: 30rpx;
+  margin-left: 14rpx;
+}
 
 .arrow {
   margin-left: 12rpx;

+ 41 - 2
pages/exhibition/index.js

@@ -9,6 +9,9 @@ Page({
   data: {
     selectedType: 0, // 当前选中的类型:0-全部,1-室内,2-室外
     carouselList: [], // 轮播图数据
+    carouselRenderList: [], // 组件使用的渲染列表
+    carouselCurrent: 0,
+    showCarouselIndicators: false,
     exhibitionList: [], // 展览列表数据
     loading: false, // 加载状态
     hasMore: true, // 是否还有更多数据
@@ -51,11 +54,21 @@ Page({
       const response = await museumApi.getExhibitionList({
         pageNum: 1,
         pageSize: 5,
-        type: 0
+        type: 0,
+        status: 1,
+        recommend: 1
       });
       if (response && response.records) {
+        const list = response.records || [];
+        // 组件需要 image/url 两字段
+        const renderList = list.map(item => ({
+          image: urlImg + item.img,
+          url: item.webSite || '',
+          raw: item
+        }));
         this.setData({
-          carouselList: response.records
+          carouselList: list,
+          carouselRenderList: renderList
         });
       }
     } catch (error) {
@@ -149,6 +162,32 @@ Page({
     }
   },
 
+  // 组件:指示器/滑动变更
+  onCarouselChange(e) {
+    const { current } = e.detail || {};
+    this.setData({ carouselCurrent: current });
+  },
+
+  // 组件:当前项(无 url)点击
+  onExhibitionTapFromCarousel(e) {
+    const { item } = e.detail || {};
+    const raw = item && item.raw;
+    if (raw && raw.exhibitId) {
+      this.goToDetail(raw.exhibitId);
+    }
+  },
+
+  // 组件:线上观展点击
+  onOnlineExhibitionTapFromCarousel(e) {
+    const { url } = e.detail || {};
+    if (url) {
+      console.log('跳转到线上观展:', url);
+      navigateToWebview(url, 'open');
+    } else {
+      console.log('webSite为空');
+    }
+  },
+
   // 跳转到详情页面
   goToDetail(id) {
     navigateToWebview(`/allDetailsShow?isFrom=weixin&id=${id}&type=exhibition`);

+ 3 - 1
pages/exhibition/index.json

@@ -4,5 +4,7 @@
   "navigationBarTextStyle": "black",
   "backgroundColor": "#f8f8f8",
   "enablePullDownRefresh": true,
-  "usingComponents": {}
+  "usingComponents": {
+    "card-carousel": "/components/card-carousel/index"
+  }
 }

+ 12 - 14
pages/exhibition/index.wxml

@@ -1,19 +1,17 @@
 <!--pages/exhibition/index.wxml-->
 <view class="exhibition-container">
-  <!-- 轮播图 -->
-  <view class="carousel-section">
-    <!-- <swiper class="carousel" indicator-dots="{{true}}" autoplay="{{true}}" interval="3000" duration="500"> -->
-    <navigator hover-class='none' url="https://www.4dmodel.com/SuperTwoCustom/KLMYscene/?m=SG-alDn3FU4jQ8" class='fdkz-card'>
-      <view class="carousel">
-        <image class="carousel-image" src="https://klmybwg.4dage.com/mini/wxImg/zhanlan.png" mode="aspectFill"></image>
-        <!-- 线上观展标识 -->
-        <view class="online-exhibition" bindtap="onOnlineExhibitionTap">
-          <image class="online-icon" src="https://klmybwg.4dage.com/mini/wxImg/exhibition/online-kz.png" mode="aspectFit"></image>
-          <text class="online-text">线上观展</text>
-        </view>
-      </view>
-    <!-- </swiper> -->
-    </navigator>
+  <!-- 轮播图:卡片式 -->
+  <view wx:if="{{carouselRenderList.length > 0}}" class="carousel-section">
+    <card-carousel list="{{carouselRenderList}}"
+      current="{{carouselCurrent}}" 
+      paddingLR="30" centerHeight="160" 
+      sideHeight="130" 
+      showIndicators="{{showCarouselIndicators}}"
+      autoplay="{{true}}"
+      interval="{{4000}}"
+      bind:exhibitiontap="onExhibitionTapFromCarousel" 
+      bind:onlineexhibitiontap="onOnlineExhibitionTapFromCarousel"
+      bind:change="onCarouselChange" />
   </view>
 
   <!-- 分类选择 -->

+ 9 - 9
pages/exhibition/index.wxss

@@ -9,12 +9,12 @@
 
 /* 轮播图样式 */
 .carousel-section {
-  margin-bottom: 20rpx;
+  padding-top: 28rpx;
 }
 
 .carousel {
   position: relative;
-  height: 400rpx;
+  height: 320rpx;
   width: 100%;
 }
 
@@ -70,8 +70,8 @@
 
 /* 分类选择样式 */
 .category-section {
-  padding: 0 40rpx;
-  margin-bottom: 40rpx;
+  padding: 40rpx 60rpx 0;
+  margin-bottom: 34rpx;
 }
 
 .category-tabs {
@@ -86,7 +86,7 @@
   flex-direction: column;
   align-items: center;
   background-color: rgba(229, 200, 142, 0.2);
-  padding: 30rpx;
+  padding: 23rpx;
   border-radius: 10rpx;
   border: 2rpx solid rgba(148, 118, 90, 0.5);
   transition: all 0.3s ease;
@@ -116,16 +116,16 @@
 
 /* 展览列表样式 */
 .exhibition-list {
-  padding: 0 40rpx;
+  padding: 0 60rpx;
 }
 
 .exhibition-item {
   position: relative;
   width: 100%;
-  height: 412rpx;
+  height: 320rpx;
   border-radius: 16rpx;
   overflow: hidden;
-  margin-bottom: 40rpx;
+  margin-bottom: 20rpx;
   transition: all 0.3s ease;
   box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
 }
@@ -137,7 +137,7 @@
 
 .exhibition-image {
   width: 100%;
-  height: 412rpx;
+  height: 320rpx;
   object-fit: cover;
 }
 

+ 1 - 1
utils/util.js

@@ -1,5 +1,5 @@
 const urlImg = 'https://klmybwg.4dage.com' // 改这个公共图片地址的时候别忘记改wxparse的image和video的前缀
-// const urlImg = '' // 测试环境
+// const urlImg = 'https://sit-kelamayi.4dage.com' // 测试环境
 const formatTime = date => {
   // 确保date是Date对象
   const dateObj = date instanceof Date ? date : new Date(date)