wangfumin 3 месяцев назад
Родитель
Сommit
aee5fa1b7e

BIN
public/wxImg/indexPage/icon_online.png


BIN
src/assets/indexPage/icon_online.png


+ 5 - 0
src/router/index.js

@@ -49,6 +49,11 @@ const router = createRouter({
           name: 'map',
           component: () => import('@/views/user/map.vue'),
         },
+        {
+          path: '/indexPage/onlineList',
+          name: 'onlineList',
+          component: () => import('@/views/indexPage/onlineList.vue'),
+        },
       ],
     },
     {

+ 103 - 14
src/setup/useIndexPageApi.js

@@ -13,6 +13,12 @@ export function useIndexPageApi() {
   const activeList = ref([])
   const loading = ref(false)
 
+  // 分页相关状态
+  const exhibitionHasMore = ref(true)
+  const exhibitionCurrentPage = ref(1)
+  const activeHasMore = ref(true)
+  const activeCurrentPage = ref(1)
+
   /**
    * 获取轮播图数据
    * @param {Object} params - 请求参数
@@ -85,11 +91,14 @@ export function useIndexPageApi() {
    * @param {number} params.pageNum - 页码,默认1
    * @param {number} params.pageSize - 每页数量,默认5
    * @param {number} params.status - 状态,默认1
+   * @param {boolean} isLoadMore - 是否为加载更多
    */
-  const getExhibitionList = async (params = {}) => {
+  const getExhibitionList = async (params = {}, isLoadMore = false) => {
+    if (loading.value) return
+
     const defaultParams = {
-      pageNum: 1,
-      pageSize: 5,
+      pageNum: isLoadMore ? exhibitionCurrentPage.value : 1,
+      pageSize: 10,
       status: 1,
       ...params,
     }
@@ -98,13 +107,33 @@ export function useIndexPageApi() {
       loading.value = true
       const response = await museumApi.getExhibitionList(defaultParams)
       console.log('展览列表数据:', response)
-      // 根据接口返回的数据结构设置exhibitionList
-      exhibitionList.value = response.records || response.list || response.data || response || []
+
+      // 获取数据列表
+      const newData = response.records || response.list || response.data || response || []
+
+      // 过滤掉没有 webSite 或 webSiteB 字段的数据
+      const filteredData = newData.filter((item) => {
+        return item.webSite || item.webSiteB
+      })
+
+      if (isLoadMore) {
+        // 加载更多时追加数据
+        exhibitionList.value = [...exhibitionList.value, ...filteredData]
+      } else {
+        // 首次加载或刷新时替换数据
+        exhibitionList.value = filteredData
+        exhibitionCurrentPage.value = 1
+      }
+
+      // 判断是否还有更多数据
+      exhibitionHasMore.value = filteredData.length >= defaultParams.pageSize
+
       return response
     } catch (error) {
       console.error('获取展览列表数据失败:', error)
-      // 如果接口失败,使用默认数据
-      exhibitionList.value = []
+      if (!isLoadMore) {
+        exhibitionList.value = []
+      }
       throw error
     } finally {
       loading.value = false
@@ -117,11 +146,14 @@ export function useIndexPageApi() {
    * @param {number} params.pageNum - 页码,默认1
    * @param {number} params.pageSize - 每页数量,默认5
    * @param {number} params.status - 状态,默认1
+   * @param {boolean} isLoadMore - 是否为加载更多
    */
-  const getActiveList = async (params = {}) => {
+  const getActiveList = async (params = {}, isLoadMore = false) => {
+    if (loading.value) return
+
     const defaultParams = {
-      pageNum: 1,
-      pageSize: 5,
+      pageNum: isLoadMore ? activeCurrentPage.value : 1,
+      pageSize: 10,
       status: 1,
       ...params,
     }
@@ -130,13 +162,28 @@ export function useIndexPageApi() {
       loading.value = true
       const response = await museumApi.getSocialActivityList(defaultParams)
       console.log('活动列表数据:', response)
-      // 根据接口返回的数据结构设置activeList
-      activeList.value = response.records || response.list || response.data || response || []
+
+      // 获取数据列表
+      const newData = response.records || response.list || response.data || response || []
+
+      if (isLoadMore) {
+        // 加载更多时追加数据
+        activeList.value = [...activeList.value, ...newData]
+      } else {
+        // 首次加载或刷新时替换数据
+        activeList.value = newData
+        activeCurrentPage.value = 1
+      }
+
+      // 判断是否还有更多数据
+      activeHasMore.value = newData.length >= defaultParams.pageSize
+
       return response
     } catch (error) {
       console.error('获取活动列表数据失败:', error)
-      // 如果接口失败,使用默认数据
-      activeList.value = []
+      if (!isLoadMore) {
+        activeList.value = []
+      }
       throw error
     } finally {
       loading.value = false
@@ -166,6 +213,42 @@ export function useIndexPageApi() {
   }
 
   /**
+   * 加载更多展览数据
+   */
+  const loadMoreExhibitions = async (params = {}) => {
+    if (!exhibitionHasMore.value || loading.value) return
+
+    // 先递增页码
+    exhibitionCurrentPage.value++
+
+    try {
+      await getExhibitionList(params, true)
+    } catch (error) {
+      // 如果请求失败,回退页码
+      exhibitionCurrentPage.value--
+      throw error
+    }
+  }
+
+  /**
+   * 加载更多活动数据
+   */
+  const loadMoreActivities = async (params = {}) => {
+    if (!activeHasMore.value || loading.value) return
+
+    // 先递增页码
+    activeCurrentPage.value++
+
+    try {
+      await getActiveList(params, true)
+    } catch (error) {
+      // 如果请求失败,回退页码
+      activeCurrentPage.value--
+      throw error
+    }
+  }
+
+  /**
    * 初始化所有数据
    * @param {Object} options - 配置选项
    * @param {Object} options.bannerParams - 轮播图请求参数
@@ -196,6 +279,10 @@ export function useIndexPageApi() {
     exhibitionList,
     activeList,
     loading,
+    exhibitionHasMore,
+    exhibitionCurrentPage,
+    activeHasMore,
+    activeCurrentPage,
 
     // API方法
     getBannerData,
@@ -204,5 +291,7 @@ export function useIndexPageApi() {
     getActiveList,
     getInformationDetail,
     initAllData,
+    loadMoreExhibitions,
+    loadMoreActivities,
   }
 }

+ 93 - 6
src/views/indexPage/activity.vue

@@ -9,7 +9,7 @@
     <div class="section-title">社教活动</div>
 
     <!-- 活动列表 -->
-    <div v-if="loading" class="loading-container">
+    <div v-if="loading && activeList.length === 0" class="loading-container">
       <div class="loading-text">加载中...</div>
     </div>
     <div v-else class="content-section">
@@ -42,6 +42,16 @@
       <div v-if="activeList.length === 0" class="empty-state">
         <div class="empty-text">暂无活动数据</div>
       </div>
+
+      <!-- 加载更多指示器 -->
+      <div v-if="activeList.length > 0 && isLoadingMore" class="loading-more">
+        <div class="loading-more-text">加载中...</div>
+      </div>
+
+      <!-- 没有更多数据提示 -->
+      <div v-if="activeList.length > 0 && !activeHasMore && !isLoadingMore" class="no-more">
+        <div class="no-more-text">没有更多数据了</div>
+      </div>
     </div>
   </div>
 </template>
@@ -53,7 +63,7 @@ export default {
 </script>
 
 <script setup>
-import { onMounted, computed } from 'vue'
+import { onMounted, computed, nextTick, onUnmounted } from 'vue'
 import { useRouter, useRoute } from 'vue-router'
 import { useIndexPageApi } from '@/setup/useIndexPageApi'
 
@@ -74,7 +84,41 @@ const getFieldValue = (item, fieldName) => {
 }
 
 // 使用组合式函数获取API方法和响应式数据
-const { activeList, getActiveList, loading } = useIndexPageApi()
+const { activeList, getActiveList, loading, activeHasMore, loadMoreActivities } = useIndexPageApi()
+
+// 滚动加载相关
+const isLoadingMore = computed(() => loading.value && activeList.value.length > 0)
+
+// 滚动事件处理
+const handleScroll = async () => {
+  const container = document.querySelector('.activity-container')
+  if (!container) return
+
+  const { scrollTop, scrollHeight, clientHeight } = container
+  const threshold = 100 // 距离底部100px时触发加载
+
+  if (
+    scrollTop + clientHeight >= scrollHeight - threshold &&
+    !isLoadingMore.value &&
+    activeHasMore.value
+  ) {
+    await loadMore()
+  }
+}
+
+// 加载更多数据
+const loadMore = async () => {
+  try {
+    const params = {}
+    // 如果是预览模式,设置status为-1
+    if (isPreviewMode.value) {
+      params.status = -1
+    }
+    await loadMoreActivities(params)
+  } catch (error) {
+    console.error('加载更多活动数据失败:', error)
+  }
+}
 
 // 页面初始化时获取活动数据
 onMounted(async () => {
@@ -85,11 +129,26 @@ onMounted(async () => {
       params.status = -1
     }
     await getActiveList(params)
+
+    // 添加滚动事件监听
+    await nextTick()
+    const container = document.querySelector('.activity-container')
+    if (container) {
+      container.addEventListener('scroll', handleScroll)
+    }
   } catch (error) {
     console.error('获取活动数据失败:', error)
   }
 })
 
+// 组件卸载时移除滚动事件监听
+onUnmounted(() => {
+  const container = document.querySelector('.activity-container')
+  if (container) {
+    container.removeEventListener('scroll', handleScroll)
+  }
+})
+
 // 返回首页函数
 const goBackToIndex = () => {
   if (isPreviewMode.value) {
@@ -121,6 +180,7 @@ const viewActivity = (item) => {
 .activity-container {
   position: relative;
   height: 100vh;
+  min-width: 375px;
   padding: 20px;
   background: url('@/assets/indexPage/bg.png') no-repeat;
   background-size: cover;
@@ -160,9 +220,6 @@ const viewActivity = (item) => {
   }
 }
 
-.content-section {
-}
-
 .collection-list {
   display: flex;
   flex-direction: column;
@@ -265,4 +322,34 @@ const viewActivity = (item) => {
     opacity: 0.6;
   }
 }
+
+// 加载更多样式
+.loading-more {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 60px;
+  margin-top: 20px;
+
+  .loading-more-text {
+    font-size: 16px;
+    color: #584735;
+    opacity: 0.8;
+  }
+}
+
+// 没有更多数据样式
+.no-more {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 60px;
+  margin-top: 20px;
+
+  .no-more-text {
+    font-size: 16px;
+    color: #584735;
+    opacity: 0.6;
+  }
+}
 </style>

+ 31 - 17
src/views/indexPage/index.vue

@@ -37,6 +37,10 @@
           <img src="@/assets/indexPage/introduce.png" alt="展馆介绍" class="function-icon" />
           <span class="function-text">展馆介绍</span>
         </div>
+        <div class="function-item" @click="handleFunctionClick('online')">
+          <img src="@/assets/indexPage/icon_online.png" alt="展馆介绍" class="function-icon" />
+          <span class="function-text">在线展览</span>
+        </div>
       </div>
 
       <!-- 展览资讯 -->
@@ -67,19 +71,19 @@
           <h3 class="section-title">推荐展览</h3>
           <span class="view-more" @click="viewMore('exhibition')">查看更多 +</span>
         </div>
-        <div class="scroll-container">
+        <!-- <div class="scroll-container">
           <div class="scroll-wrapper">
-            <div
-              class="exhibition-item"
-              v-for="exhibition in exhibitionList"
-              :key="exhibition.exhibitId"
-              @click="viewExhibition(exhibition)"
-            >
-              <img :src="getImg(exhibition)" alt="展览" class="exhibition-img" />
-              <div class="exhibition-info">
-                <h4 class="exhibition-title">{{ getTitle(exhibition) }}</h4>
-              </div>
-            </div>
+          </div>
+        </div> -->
+        <div
+          class="exhibition-item"
+          v-for="exhibition in exhibitionList"
+          :key="exhibition.exhibitId"
+          @click="viewExhibition(exhibition)"
+        >
+          <img :src="getImg(exhibition)" alt="展览" class="exhibition-img" />
+          <div class="exhibition-info">
+            <h4 class="exhibition-title">{{ getTitle(exhibition) }}</h4>
           </div>
         </div>
       </div>
@@ -170,7 +174,7 @@ const getIndexImg = (item) => {
 // 功能点击处理
 const handleFunctionClick = (type) => {
   // 预览模式下禁止点击
-  if (isPreviewMode.value && !['map', 'introduce'].includes(type)) {
+  if (isPreviewMode.value && !['map', 'introduce', 'online'].includes(type)) {
     return
   }
   const preview = isPreviewMode.value ? '1' : ''
@@ -199,6 +203,14 @@ const handleFunctionClick = (type) => {
         },
       })
       break
+    case 'online':
+      router.push({
+        path: '/indexPage/onlineList',
+        query: {
+          preview,
+        },
+      })
+      break
   }
 }
 
@@ -385,17 +397,17 @@ onMounted(() => {
     display: flex;
     flex-direction: column;
     align-items: center;
-    width: 22%;
+    width: 18%;
     cursor: pointer;
 
     .function-icon {
-      width: 56px;
-      height: 56px;
+      width: 50px;
+      height: 50px;
       margin-bottom: 5px;
     }
 
     .function-text {
-      font-size: 16px;
+      font-size: 14px;
       color: #b1967b;
     }
 
@@ -539,6 +551,8 @@ onMounted(() => {
   margin-right: 10px;
   border-radius: 8px;
   overflow: hidden;
+  margin-top: 10px;
+  cursor: pointer;
 
   &:last-child {
     margin-right: 0;

+ 388 - 0
src/views/indexPage/onlineList.vue

@@ -0,0 +1,388 @@
+<template>
+  <div class="activity-container">
+    <!-- 返回按钮 -->
+    <div class="back-button" @click="goBackToIndex">
+      <img src="@/assets/indexPage/icon_back.png" alt="" />
+    </div>
+
+    <!-- 标题 -->
+    <div class="section-title">线上展览</div>
+
+    <!-- 活动列表 -->
+    <div v-if="loading && exhibitionList.length === 0" class="loading-container">
+      <div class="loading-text">加载中...</div>
+    </div>
+    <div v-else class="content-section">
+      <div class="collection-list">
+        <div
+          class="exhibition-item"
+          v-for="exhibition in exhibitionList"
+          :key="exhibition.exhibitId"
+          @click="viewExhibition(exhibition)"
+        >
+          <img :src="getImg(exhibition)" alt="展览" class="exhibition-img" />
+          <div class="exhibition-info">
+            <h4 class="exhibition-title">{{ getTitle(exhibition) }}</h4>
+          </div>
+        </div>
+      </div>
+      <!-- 空状态 -->
+      <div v-if="exhibitionList.length === 0" class="empty-state">
+        <div class="empty-text">暂无展览数据</div>
+      </div>
+
+      <!-- 加载更多指示器 -->
+      <div v-if="exhibitionList.length > 0 && isLoadingMore" class="loading-more">
+        <div class="loading-more-text">加载中...</div>
+      </div>
+
+      <!-- 没有更多数据提示 -->
+      <div v-if="exhibitionList.length > 0 && !exhibitionHasMore && !isLoadingMore" class="no-more">
+        <div class="no-more-text">没有更多数据了</div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'OnlineList',
+}
+</script>
+
+<script setup>
+import { onMounted, computed, nextTick, onUnmounted } from 'vue'
+import { useRouter, useRoute } from 'vue-router'
+import { useIndexPageApi } from '@/setup/useIndexPageApi'
+
+const router = useRouter()
+const route = useRoute()
+
+// 检查是否为预览模式
+const isPreviewMode = computed(() => {
+  return route.query.preview === '1'
+})
+
+// 获取字段值的通用方法,优先获取带B后缀的字段
+const getFieldValue = (item, fieldName) => {
+  if (isPreviewMode.value) {
+    return item[fieldName + 'B'] || item[fieldName]
+  }
+  return item[fieldName]
+}
+// 获取图片的方法
+const getImg = (item) => {
+  return getFieldValue(item, 'img')
+}
+// 获取标题的方法
+const getTitle = (item) => {
+  return getFieldValue(item, 'title')
+}
+// 使用组合式函数获取API方法和响应式数据
+const { getExhibitionList, loading, exhibitionList, exhibitionHasMore, loadMoreExhibitions } =
+  useIndexPageApi()
+
+// 滚动加载相关
+const isLoadingMore = computed(() => loading.value && exhibitionList.value.length > 0)
+
+// 滚动事件处理
+const handleScroll = async () => {
+  const container = document.querySelector('.activity-container')
+  if (!container) return
+
+  const { scrollTop, scrollHeight, clientHeight } = container
+  const threshold = 100 // 距离底部100px时触发加载
+
+  if (
+    scrollTop + clientHeight >= scrollHeight - threshold &&
+    !isLoadingMore.value &&
+    exhibitionHasMore.value
+  ) {
+    await loadMore()
+  }
+}
+
+// 加载更多数据
+const loadMore = async () => {
+  try {
+    const params = {}
+    // 如果是预览模式,设置status为-1
+    if (isPreviewMode.value) {
+      params.status = -1
+    }
+    await loadMoreExhibitions(params)
+  } catch (error) {
+    console.error('加载更多展览数据失败:', error)
+  }
+}
+
+// 页面初始化时获取展览数据
+onMounted(async () => {
+  try {
+    const params = { pageNum: 1, pageSize: 5 }
+    // 如果是预览模式,设置status为-1
+    if (isPreviewMode.value) {
+      params.status = -1
+    }
+    await getExhibitionList(params)
+
+    // 添加滚动事件监听
+    await nextTick()
+    const container = document.querySelector('.activity-container')
+    if (container) {
+      container.addEventListener('scroll', handleScroll)
+    }
+  } catch (error) {
+    console.error('获取展览数据失败:', error)
+  }
+})
+
+// 组件卸载时移除滚动事件监听
+onUnmounted(() => {
+  const container = document.querySelector('.activity-container')
+  if (container) {
+    container.removeEventListener('scroll', handleScroll)
+  }
+})
+
+// 返回首页函数
+const goBackToIndex = () => {
+  if (isPreviewMode.value) {
+    router.replace({
+      path: '/indexPage',
+      query: { preview: '1' },
+    })
+  } else {
+    router.replace('/indexPage')
+  }
+}
+
+// 查看展览详情
+const viewExhibition = (item) => {
+  console.log('查看展览详情:', item)
+  // 跳转到详情页,传递展览ID和类型参数
+  let url = getFieldValue(item, 'webSite')
+  console.log(url, 777)
+  window.open(url, '_blank')
+}
+</script>
+
+<style lang="scss" scoped>
+.activity-container {
+  position: relative;
+  height: 100vh;
+  min-width: 375px;
+  padding: 20px;
+  background: url('@/assets/indexPage/bg.png') no-repeat;
+  background-size: cover;
+  overflow-y: auto;
+}
+// @media (min-width: 1024px) {
+//   .exhibition-container {
+//     width: 400px;
+//   }
+// }
+.back-button {
+  position: absolute;
+  top: 20px;
+  left: 20px;
+  width: 40px;
+  height: 40px;
+  border-radius: 50%;
+
+  img {
+    width: 40px;
+    height: 40px;
+  }
+}
+
+.section-title {
+  font-size: 20px;
+  font-weight: bold;
+  color: #584735;
+  margin: 54px 0 10px 0;
+  position: relative;
+  padding-bottom: 10px;
+
+  &::after {
+    content: '';
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    width: 100%;
+    height: 2px;
+    background: linear-gradient(90deg, rgba(91, 71, 46, 0.5) 0%, rgba(91, 71, 46, 0) 100%);
+  }
+}
+
+.collection-list {
+  display: flex;
+  flex-direction: column;
+}
+
+// 展览项
+.exhibition-item {
+  position: relative;
+  flex: 0 0 auto;
+  width: 100%;
+  height: 206px;
+  margin-right: 10px;
+  border-radius: 8px;
+  overflow: hidden;
+  margin-top: 10px;
+  cursor: pointer;
+
+  &:last-child {
+    margin-right: 0;
+  }
+
+  .exhibition-img {
+    width: 100%;
+    height: 206px;
+    object-fit: cover;
+  }
+
+  .exhibition-info {
+    width: 100%;
+    height: 30px;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    opacity: 0.8;
+    position: absolute;
+    bottom: 0;
+    background: #b1967b;
+
+    .exhibition-title {
+      font-size: 16px;
+      margin: 0;
+      color: #fff;
+      font-weight: bold;
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      font-family: 'heavy';
+    }
+  }
+}
+
+.item-image-container {
+  position: relative;
+  height: 218px;
+  overflow: hidden;
+  cursor: pointer;
+
+  .item-image {
+    width: 100%;
+    height: 100%;
+    object-fit: cover;
+  }
+
+  .view-button {
+    position: absolute;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 80px;
+    height: 36px;
+    top: 12px;
+    right: 12px;
+    background-color: rgba(0, 0, 0, 0.2);
+    color: #fff;
+    padding: 4px 8px;
+    border-radius: 50px;
+    font-size: 12px;
+    border: 1px solid #fff;
+
+    span {
+      font-size: 14px;
+      margin-right: 6px;
+    }
+  }
+}
+
+.item-info {
+  position: absolute;
+  width: 90%;
+  bottom: 16px;
+  left: 24px;
+
+  .item-category {
+    font-size: 14px;
+    color: #fff;
+  }
+
+  .item-title {
+    font-size: 20px;
+    font-weight: bold;
+    color: #fff;
+    font-family: 'heavy';
+  }
+
+  .item-description {
+    font-size: 12px;
+    color: #fff;
+    overflow: hidden;
+    text-overflow: ellipsis;
+  }
+}
+
+// 加载状态样式
+.loading-container {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 200px;
+
+  .loading-text {
+    font-size: 18px;
+    color: #584735;
+    opacity: 0.8;
+  }
+}
+
+// 空状态样式
+.empty-state {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 200px;
+  margin-top: 50px;
+
+  .empty-text {
+    font-size: 18px;
+    color: #584735;
+    opacity: 0.6;
+  }
+}
+
+// 加载更多样式
+.loading-more {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 60px;
+  margin-top: 20px;
+
+  .loading-more-text {
+    font-size: 16px;
+    color: #584735;
+    opacity: 0.8;
+  }
+}
+
+// 没有更多数据样式
+.no-more {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 60px;
+  margin-top: 20px;
+
+  .no-more-text {
+    font-size: 16px;
+    color: #584735;
+    opacity: 0.6;
+  }
+}
+</style>