shaogen1995 19 часов назад
Родитель
Сommit
751b7ddb96

+ 3 - 3
mobile/.env.production

@@ -9,12 +9,12 @@ VITE_AXIOS_BASE_URL = '/api'  # 用于代理
 # 代理配置-target
 # VITE_PROXY_TARGET = 'http://localhost:8085'
 # VITE_PROXY_TARGET = 'https://sit-huyaobangjng.4dage.com'
-VITE_PROXY_TARGET = 'https://hybgc.4dage.com'
+VITE_PROXY_TARGET = 'http://localhost'
 
 # 图片基础
 # VITE_COS_BASE_URL = 'https://hybgc.4dage.com/ArtCMS/'
-VITE_COS_BASE_URL = 'https://hybgc.4dage.com/ArtCMS/'
+VITE_COS_BASE_URL = 'http://localhost/ArtCMS/'
 # 模型基础
 # VITE_MODEL_URL = 'https://hybgc.4dage.com/'
-VITE_MODEL_URL = 'https://hybgc.4dage.com/'
+VITE_MODEL_URL = 'http://localhost/'
 # build

+ 15 - 4
mobile/index.html

@@ -1,13 +1,24 @@
-<!DOCTYPE html>
+<!doctype html>
 <html lang="">
   <head>
-    <meta charset="UTF-8">
-    <link rel="icon" href="/favicon.png">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <meta charset="UTF-8" />
+    <link rel="icon" href="/favicon.png" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <title>鉴赏系统</title>
   </head>
   <body>
     <div id="app"></div>
     <script type="module" src="/src/main.js"></script>
+
+    <!-- <script>
+      window.onload = function () {
+        var script = document.createElement('script')
+        script.src = 'https://cdn.bootcss.com/eruda/1.5.4/eruda.min.js'
+        document.body.appendChild(script)
+        script.onload = function () {
+          eruda.init()
+        }
+      }
+    </script> -->
   </body>
 </html>

+ 16 - 14
mobile/public/three/index.html

@@ -1,4 +1,4 @@
-<!DOCTYPE html>
+<!doctype html>
 <html lang="zh">
   <head>
     <meta charset="UTF-8" />
@@ -45,7 +45,7 @@
     <script type="text/javascript" src="./jquery-2.1.1.min.js"></script>
     <script type="text/javascript" src="./three.min.js"></script>
     <script>
-      let vfov = 60; //垂直视角范围度数
+      let vfov = 60 //垂直视角范围度数
       window.setting = {
         vfov,
         cards: {
@@ -55,16 +55,18 @@
           fadeInDur: 3000,
           highest: Math.tan(THREE.Math.degToRad(vfov / 2)), //当card在1米处时最高可以多少才能在视线内
         },
-      };
+      }
 
       function initViewer(cardNames) {
-        window.cardNames = cardNames;
-        var startTime = new Date().getTime();
-        window.viewer = new Viewer(0, $("#player")[0])
+        window.cardNames = cardNames
+        var startTime = new Date().getTime()
+        window.viewer = new Viewer(0, $('#player')[0])
 
         // 点击
-        viewer.addEventListener('clickObject', e => {
-          window.top.clickObject(e.imgName);
+        viewer.addEventListener('clickObject', (e) => {
+          console.log('点击了111', e)
+
+          window.top.clickObject(e.imgName)
           // 暂停动画
           viewer.setAutoMove(false)
         })
@@ -72,23 +74,23 @@
         let flag = false
 
         // 鼠标移入
-        viewer.addEventListener('hoverObject', e => {
+        viewer.addEventListener('hoverObject', (e) => {
           // console.log('鼠标移入',e);
           flag = true
-          window.top.hoverObject(e);
+          window.top.hoverObject(e)
         })
 
         // 鼠标移出
-        viewer.addEventListener('mouseoutObject', e => {
+        viewer.addEventListener('mouseoutObject', (e) => {
           // console.log('鼠标移出',e);
           flag = false
-          window.top.mouseoutObject(e);
+          window.top.mouseoutObject(e)
         })
 
         document.querySelector('#player').onmousemove = (event) => {
           if (!flag) return
-          let e = event || window.event;
-          window.top.mouseLoc(e.clientX, e.clientY);
+          let e = event || window.event
+          window.top.mouseLoc(e.clientX, e.clientY)
         }
       }
     </script>

+ 16 - 3
mobile/public/three/index.js

@@ -84,7 +84,7 @@ var BlurShader = {
 
 var isMobile = (function() {
     var e = navigator.userAgent || navigator.vendor || window.opera
-    return (
+    var uaCheck = (
         /(android|bb\d+|meego).+mobile|android|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od|ad)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(
             e
         ) ||
@@ -92,6 +92,9 @@ var isMobile = (function() {
             e.substr(0, 4)
         )
     )
+    // 检测是否有触屏能力(包括触屏一体机)
+    var hasTouch = navigator.maxTouchPoints > 0 || 'ontouchstart' in window
+    return uaCheck || hasTouch
 })()
 
 
@@ -417,10 +420,17 @@ Viewer.prototype.onPointerMove = function (event, force) {
 Viewer.prototype.onPointerDown = function (event) {
 
     if (event.isPrimary === false) return;
+    // 标记是否为触屏事件(红外触摸屏、电容屏等)
+    this.isTouchEvent = event.pointerType === 'touch'
     mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
     mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
     this.pointerDownPos = mouse.clone()
     this.pointerDownTime = Date.now()
+
+    // 触屏设备在按下时就检测交点,确保点击时 hoveredObject 已设置
+    if (this.isTouchEvent || isMobile) {
+        this.checkIntersection();
+    }
 }
 
 Viewer.prototype.onPointerUp = function (event) {
@@ -429,7 +439,9 @@ Viewer.prototype.onPointerUp = function (event) {
     mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
     mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
     let now = Date.now()
-    if (mouse.distanceTo(this.pointerDownPos) < 0.006 && now - this.pointerDownTime < 1000) {//click
+    // 移动阈值:触屏设备(包括红外触摸屏)使用更大的阈值,抖动更明显
+    var clickThreshold = (this.isTouchEvent || isMobile) ? 0.05 : 0.006;
+    if (mouse.distanceTo(this.pointerDownPos) < clickThreshold && now - this.pointerDownTime < 1000) {//click
 
         //doubleClick
         /* var time = new Date().getTime();
@@ -440,7 +452,7 @@ Viewer.prototype.onPointerUp = function (event) {
                 transitions.start(lerp.vector(this.control.target, this.intersects[0].point), 600, null, 0 , easing.easeInOutQuad, null, Transitions.doubleClick);
             }
         } */
-        if(isMobile){
+        if(this.isTouchEvent || isMobile){
             this.onPointerMove(event,true)
         }
         if (this.hoveredObject) {
@@ -449,6 +461,7 @@ Viewer.prototype.onPointerUp = function (event) {
     }
 
     this.pointerDownPos = null
+    this.isTouchEvent = false
 }
 
 

+ 10 - 7
mobile/src/App.vue

@@ -1,15 +1,18 @@
 <script setup>
-import Tabbar from "@/components/Tabbar/index.vue";
-import { computed } from "vue";
-import { useRoute } from "vue-router";
-const route = useRoute();
-const tabbarVisible = computed(() => !route.meta.hideTabbar);
+import Tabbar from '@/components/Tabbar/index.vue'
+import { computed } from 'vue'
+import { useRoute } from 'vue-router'
+const route = useRoute()
+const tabbarVisible = computed(() => !route.meta.hideTabbar)
 </script>
 
 <template>
   <div class="app">
-    <RouterView :key="$route.fullPath" :class="tabbarVisible ? 'page-content' : 'page-content-no-tabbar'" />
-    <Tabbar :visible="tabbarVisible" />
+    <RouterView
+      :key="$route.fullPath"
+      :class="tabbarVisible ? 'page-content' : 'page-content-no-tabbar'"
+    />
+    <!-- <Tabbar :visible="tabbarVisible" /> -->
   </div>
 </template>
 

+ 24 - 23
mobile/src/views/Home/components/SearchPane.vue

@@ -16,52 +16,53 @@
       </el-input>
 
       <div class="search-pane-more" @click="$router.push({ name: 'collectpage' })">
-        <span class="limit-line">共收录{{ total }}件藏品,查看藏品<img src="@/assets/img/icon_more.png" alt=""></span>
+        <span class="limit-line"
+          >共收录{{ total }}件藏品,查看藏品<img src="@/assets/img/icon_more.png" alt=""
+        /></span>
       </div>
     </div>
   </div>
 </template>
 
 <script setup>
-import { ref } from "vue";
-import { useStore } from 'vuex';
-import { useRouter } from "vue-router";
+import { ref } from 'vue'
+import { useStore } from 'vuex'
+import { useRouter } from 'vue-router'
 
-const store = useStore();
-const router = useRouter();
+const store = useStore()
+const router = useRouter()
 
 defineProps({
   total: {
     type: Number,
     default: 0,
   },
-});
-const modelValue = ref("");
+})
+const modelValue = ref('')
 const handleSearch = () => {
   store.dispatch('setHomeSearchText', modelValue.value)
   router.push({
-    name: "collectpage",
-  });
-};
-
+    name: 'collectpage',
+  })
+}
 </script>
 
 <style lang="scss" scoped>
 .search-pane {
-  .limit-line{
+  .limit-line {
     height: 0.64rem;
     width: auto;
-    background: url("@/assets/img/btn_more.png") no-repeat center / contain;
+    background: url('@/assets/img/btn_more.png') no-repeat center / contain;
   }
-  :deep(.el-input__wrapper){
+  :deep(.el-input__wrapper) {
     background: rgba(255, 255, 255, 0.5);
-    border: 0.0533rem solid #D3BFA2;
+    border: 0.0533rem solid #d3bfa2;
     border-radius: 2.6667rem;
     box-shadow: none;
     height: 1.0667rem;
     padding-right: 0.1067rem;
   }
-  :deep(.el-input__inner){
+  :deep(.el-input__inner) {
     background: transparent;
     border: none;
     outline: none;
@@ -75,17 +76,17 @@ const handleSearch = () => {
     display: flex;
     justify-content: center;
     align-items: center;
-    background-color: #D3BFA2!important;
+    background-color: #d3bfa2 !important;
     border-radius: 50%;
     background: none;
-    img{
+    img {
       width: 0.6133rem;
       height: 0.6133rem;
     }
   }
   &-main {
     position: absolute;
-    top: 25%;
+    top: 38%;
     left: 0;
     right: 0;
     padding: 0 0.8rem;
@@ -104,7 +105,7 @@ const handleSearch = () => {
     height: 1.2rem;
     font-size: 0.6133rem;
     color: var(--van-primary-color);
-    background: url("../images/img_tip@3x.png") no-repeat center / contain;
+    background: url('../images/img_tip@3x.png') no-repeat center / contain;
     box-sizing: border-box;
   }
   &-tips {
@@ -129,13 +130,13 @@ const handleSearch = () => {
   .limit-line {
     text-align: center;
     font-size: 0.32rem;
-    color: #D3BFA2;
+    color: #d3bfa2;
     margin-top: 0.2667rem;
     line-height: 0.64rem;
     display: flex;
     align-items: center;
     justify-content: center;
-    img{
+    img {
       width: 0.32rem;
       height: 0.32rem;
       margin-left: 0.1333rem;

+ 134 - 100
mobile/src/views/Home/index.vue

@@ -2,202 +2,236 @@
   <div v-show="!isLoading" class="home">
     <search-pane :total="total" />
 
-    <iframe
-      id="iframe"
-      class="home-iframe"
-      src="./three/index.html"
-      frameborder="0"
-    ></iframe>
+    <iframe id="iframe" class="home-iframe" src="./three/index.html" frameborder="0"></iframe>
+
+    <!-- 跳到馆藏文物页面 -->
+    <div class="myPush" @click="handleClick(item)">
+      <img :src="BookActiveIcon" />
+      <p>馆藏文物</p>
+    </div>
   </div>
 </template>
 
 <script setup>
-import { onMounted, ref } from "vue";
-import getBookCountApi from "@/api";
-import { useRouter } from "vue-router";
-import SearchPane from "./components/SearchPane.vue";
-import { useStore } from 'vuex';
-import { addWatermarkToCollectionImage } from '@/utils/index.js';
-
-const list = ref([]);
-
-const store = useStore();
-const imguRL =  import.meta.env.VITE_COS_BASE_URL
-const router = useRouter();
-const txt = ref({ show: false });
-const txtDom = ref(null);
+import BookActiveIcon from '@/assets/img/icon_collection_active.png'
+import { onMounted, ref } from 'vue'
+import getBookCountApi from '@/api'
+import { useRouter } from 'vue-router'
+import SearchPane from './components/SearchPane.vue'
+import { useStore } from 'vuex'
+import { addWatermarkToCollectionImage } from '@/utils/index.js'
+
+const handleClick = (item) => {
+  router.push({
+    name: 'collectpage',
+  })
+}
+const imguRL = import.meta.env.VITE_COS_BASE_URL
+const router = useRouter()
+const txt = ref({ show: false })
+const txtDom = ref(null)
 // const baseUrl = import.meta.env.VITE_AXIOS_BASE_URL;
-const datalist = ref([]);
-const total = ref(0);
-const isLoading = ref(false);
+const datalist = ref([])
+const total = ref(0)
+const isLoading = ref(false)
 // 水印缓存和处理逻辑
-const watermarkedImages = ref(new Map()); // 存储处理后的水印图片
+const watermarkedImages = ref(new Map()) // 存储处理后的水印图片
 // 处理图片水印
 const processImageWithWatermark = async (item) => {
   // 如果已经处理过,直接返回
   if (watermarkedImages.value.has(item.id)) {
-    return watermarkedImages.value.get(item.id);
+    return watermarkedImages.value.get(item.id)
   }
-  
+
   // 如果正在处理,避免重复处理
   if (item.watermarkProcessing) {
-    return item.thumb;
+    return item.thumb
   }
-  
+
   try {
     // 标记为处理中
-    item.watermarkProcessing = true;
+    item.watermarkProcessing = true
     // 处理水印
     const watermarkedImageUrl = await addWatermarkToCollectionImage(item.thumb, {
       position: 'center',
       opacity: 1,
       scale: 0.5,
-      margin: 15
-    });
+      margin: 15,
+    })
     // 存储处理后的图片
-    watermarkedImages.value.set(item.id, watermarkedImageUrl);
-    return watermarkedImageUrl;
+    watermarkedImages.value.set(item.id, watermarkedImageUrl)
+    return watermarkedImageUrl
   } catch (error) {
-    console.error('水印处理失败:', error);
+    console.error('水印处理失败:', error)
     // 统一处理:失败时直接缓存并返回原图
-    watermarkedImages.value.set(item.id, item.thumb);
-    return item.thumb;
+    watermarkedImages.value.set(item.id, item.thumb)
+    return item.thumb
   } finally {
     // 标记处理完成
-    item.watermarkProcessing = false;
+    item.watermarkProcessing = false
   }
-};
+}
 // 获取图片地址(带水印处理)
 const getImageUrl = (item) => {
   // 如果已经有水印图片,直接返回
   if (watermarkedImages.value.has(item.id)) {
-    return watermarkedImages.value.get(item.id);
+    return watermarkedImages.value.get(item.id)
   }
-  
+
   // 先返回原图
-  return item.thumb;
-};
+  return item.thumb
+}
 onMounted(() => {
-  getRecommendList();
+  getRecommendList()
   // 点击图片
   window.clickObject = (val) => {
+    console.log('点击了图片222')
+
     // console.log(val)
     // const item = datalist.value.find((i) => i.thumb.indexOf(val) > -1);
-    let item = null;
+    let item = null
     for (const [id, watermarkedUrl] of watermarkedImages.value) {
       if (watermarkedUrl === val) {
         // 找到对应的原始URL,再通过原始URL找到数据项
-        item = datalist.value.find((i) => i.id === id);
-        break;
+        item = datalist.value.find((i) => i.id === id)
+        break
       }
     }
     router.push({
-      name: "collectDetail",
+      name: 'collectDetail',
       query: {
         id: item.id,
       },
-    });
-  };
+    })
+  }
   // 鼠标移入
   window.hoverObject = (val) => {
-    const item = datalist.value.find((i) => i.thumb.indexOf(val.imgName) > -1);
-    if (!item) return;
+    const item = datalist.value.find((i) => i.thumb.indexOf(val.imgName) > -1)
+    if (!item) return
 
     txt.value = {
       title: item.name,
       con: item.author,
       show: true,
-    };
-  };
+    }
+  }
   // 鼠标移出
   window.mouseoutObject = (val) => {
-    txt.value.show = false;
-  };
+    txt.value.show = false
+  }
 
   // 获取鼠标坐标
   window.mouseLoc = (x, y) => {
     // console.log("ppp", x, y);
     // 最大X值
-    const maxX = window.innerWidth - 200;
-    let xRes = x >= maxX ? maxX : x;
+    const maxX = window.innerWidth - 200
+    let xRes = x >= maxX ? maxX : x
     // xRes = xRes - 100 <= 0 ? 0 : xRes - 100;
     // 最大y值
-    const domHeight = txtDom.value.clientHeight;
-    const maxY = window.innerHeight - domHeight;
-    let yRes = y >= maxY ? maxY : y;
+    const domHeight = txtDom.value.clientHeight
+    const maxY = window.innerHeight - domHeight
+    let yRes = y >= maxY ? maxY : y
     // yRes = yRes - domHeight / 2 <= 0 ? 0 : yRes - domHeight / 2;
-    txtDom.value.style.top = yRes + "px";
-    txtDom.value.style.left = xRes + "px";
-  };
-});
+    txtDom.value.style.top = yRes + 'px'
+    txtDom.value.style.left = xRes + 'px'
+  }
+})
 
 const getRecommendList = async () => {
-  isLoading.value = true;
-  let data = await getBookCountApi.getRecommendListApi();
+  isLoading.value = true
+  let data = await getBookCountApi.getRecommendListApi()
   const searchParams = {
-      dim: 3,
-      searchText: '',
-      level: '',
-      category: '',
-      material: '',
-      era: '',
-      orderBy: '',
-      orderbyList: [],
-      pageNo: 1,
-      pageSize: 1
-    };
-    const totalData = await getBookCountApi.getArtifactListApi(searchParams);
-    const totalCount = totalData.total ?  totalData.total : 26;
+    dim: 3,
+    searchText: '',
+    level: '',
+    category: '',
+    material: '',
+    era: '',
+    orderBy: '',
+    orderbyList: [],
+    pageNo: 1,
+    pageSize: 1,
+  }
+  const totalData = await getBookCountApi.getArtifactListApi(searchParams)
+  const totalCount = totalData.total ? totalData.total : 26
   // 处理接口返回的数据
-  let list = data;
+  let list = data
 
   // 更新总数
-  total.value = totalCount;
+  total.value = totalCount
 
   const processedData = list.map((i) => ({
     ...i,
     thumb: `${imguRL}${i.thumbnail}`,
     originalImage: `${imguRL}${i.thumbnail}`,
-  }));
+  }))
 
-  processedData.maxWidth = 1000;
-  processedData.qualityRatio = 0.1;
+  processedData.maxWidth = 1000
+  processedData.qualityRatio = 0.1
 
   // 批量处理所有图片的水印
-  const watermarkPromises = processedData.map(item => processImageWithWatermark(item));
-  await Promise.all(watermarkPromises);
+  const watermarkPromises = processedData.map((item) => processImageWithWatermark(item))
+  await Promise.all(watermarkPromises)
 
   // 使用水印图片生成图片列表
-  const imgList = processedData.map((i) => getImageUrl(i));
+  const imgList = processedData.map((i) => getImageUrl(i))
   // console.log(imgList, 'imgList')
-  const iframe = document.getElementById("iframe");
-  const iframeDoc = iframe?.contentDocument || iframe?.contentWindow.document;
-  if (iframeDoc?.readyState === "complete") {
-    iframe.contentWindow.initViewer(imgList);
+  const iframe = document.getElementById('iframe')
+  const iframeDoc = iframe?.contentDocument || iframe?.contentWindow.document
+  if (iframeDoc?.readyState === 'complete') {
+    iframe.contentWindow.initViewer(imgList)
   } else {
     iframe.onload = () => {
-      iframe.contentWindow.initViewer(imgList);
-    };
+      iframe.contentWindow.initViewer(imgList)
+    }
   }
-  isLoading.value = false;
-  datalist.value = processedData;
-};
-
+  isLoading.value = false
+  datalist.value = processedData
+}
 </script>
 
 <style lang="scss" scoped>
 .home {
   height: 100vh;
-  background: url("@/assets/images/bg@2x-min.png") no-repeat top center / cover;
+  background: url('@/assets/images/bg@2x-min.png') no-repeat top center / cover;
   overflow: hidden;
 
   &-iframe {
     position: absolute;
     top: 0;
     left: 0;
-    width: 100%!important;
-    height: 100%!important;
+    width: 100% !important;
+    height: 100% !important;
     z-index: 1;
   }
+
+  .myPush {
+    position: absolute;
+    bottom: 1rem;
+    right: 0.5rem;
+    z-index: 999;
+    color: #a99271;
+    height: 1.4rem;
+    width: 1.4rem;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    background-color: rgba(255, 255, 255, 0.8);
+    border-radius: 0.7rem;
+    padding-bottom: 0.15rem;
+    box-sizing: border-box;
+
+    & > img {
+      width: 0.8rem;
+      height: 0.8rem;
+    }
+    & > p {
+      font-size: 0.26rem;
+      width: 1.3333rem;
+      text-align: center;
+      line-height: 0.32rem;
+    }
+  }
 }
 </style>

+ 77 - 58
mobile/src/views/collectpage/components/collectDetail.vue

@@ -1,42 +1,44 @@
 <template>
   <div class="collect-detail-container">
-    <!-- 顶部固定导航 -->
-    <div class="top-navigation">
-      <!-- 返回按钮 -->
-      <div class="back-button" @click="goBack">
-        <img src="@/assets/img/icon_back.png" alt="返回" />
-      </div>
-      <!-- 自定义标签页导航 -->
-      <div class="custom-tabs">
-        <div
-          v-if="hasModel"
-          class="tab-item"
-          :class="{ active: activeTab === 'model' }"
-          @click="switchTab('model')"
-        >
-          模型
-        </div>
-        <div
-          v-if="hasImage"
-          class="tab-item"
-          :class="{ active: activeTab === 'image' }"
-          @click="switchTab('image')"
-        >
-          图片
+    <!-- 内容展示区域 -->
+    <div class="content-display" :class="{ zoomed: isZoomed }" v-show="lodingRef">
+      <!-- 顶部固定导航 -->
+      <div class="top-navigation" v-show="!isZoomed">
+        <!-- 返回按钮 -->
+        <div class="back-button" @click="goBack">
+          <img src="@/assets/img/icon_back.png" alt="返回" />
         </div>
-        <div
-          v-if="hasVideo"
-          class="tab-item"
-          :class="{ active: activeTab === 'video' }"
-          @click="switchTab('video')"
-        >
-          视频
+        <!-- 自定义标签页导航 -->
+        <div class="custom-tabs">
+          <div
+            v-if="hasModel"
+            class="tab-item"
+            :class="{ active: activeTab === 'model' }"
+            @click="switchTab('model')"
+          >
+            模型
+          </div>
+          <div
+            v-if="hasImage"
+            class="tab-item"
+            :class="{ active: activeTab === 'image' }"
+            @click="switchTab('image')"
+          >
+            图片
+          </div>
+          <div
+            v-if="hasVideo"
+            class="tab-item"
+            :class="{ active: activeTab === 'video' }"
+            @click="switchTab('video')"
+          >
+            视频
+          </div>
         </div>
       </div>
-    </div>
 
-    <!-- 内容展示区域 -->
-    <div class="content-display" :class="{ zoomed: isZoomed }" v-show="lodingRef">
+      <div class="artifact-title">{{ getFieldValue(artifactData, 'title') }}</div>
+
       <!-- 轮播展示区域 -->
       <div class="carousel-viewer">
         <!-- 模型展示容器 -->
@@ -173,8 +175,6 @@
     <!-- 底部固定信息卡片 -->
     <div class="artifact-info-card" :class="{ hidden: isZoomed }">
       <div class="card-content">
-        <div class="artifact-title">{{ getFieldValue(artifactData, 'title') }}</div>
-
         <!-- 信息表格 -->
         <div class="info-table">
           <div class="info-row">
@@ -211,15 +211,22 @@
           </div>
         </div>
 
+        <div class="divider" v-show="getFieldValue(artifactData, 'size') !== '暂无介绍'">
+          <span class="divider-text">藏品介绍</span>
+        </div>
+
         <div class="artifact-size" v-if="getFieldValue(artifactData, 'size')">
           {{ getFieldValue(artifactData, 'size') }}
         </div>
 
-        <div class="divider">
+        <div class="divider" v-show="getFieldValue(artifactData, 'intro') !== '暂无描述'">
           <span class="divider-text">藏品描述</span>
         </div>
 
-        <div class="artifact-description">
+        <div
+          class="artifact-description"
+          v-show="getFieldValue(artifactData, 'intro') !== '暂无描述'"
+        >
           {{ getFieldValue(artifactData, 'intro') }}
         </div>
       </div>
@@ -425,7 +432,7 @@ const getArtifactDetail = async () => {
         category: response.category || '--',
         level: response.grade || '--',
         texture: response.texture || '--',
-        size: response.size || response.dimensions,
+        size: response.size || response.dimensions || '暂无介绍',
         imageFiles: response.imgFiles || [],
         videoFiles: response.videoFiles || [],
         audioFiles: response.audioFiles || [],
@@ -744,7 +751,7 @@ onMounted(() => {
 
 .top-navigation {
   position: absolute;
-  top: 0;
+  bottom: 6%;
   left: 0;
   right: 0;
   display: flex;
@@ -796,9 +803,25 @@ onMounted(() => {
 }
 
 .content-display {
-  height: 49%;
+  height: 76%;
   background-color: #d1bb9e;
   transition: height 0.3s ease;
+  position: relative;
+
+  .artifact-title {
+    position: absolute;
+    top: 0.5rem;
+    width: 100vw;
+    text-align: center;
+    font-size: 0.4rem;
+    font-weight: bold;
+    color: #fff;
+    pointer-events: none;
+    z-index: 999;
+    padding: 0 5%;
+    box-sizing: border-box;
+    letter-spacing: 0.02rem;
+  }
 
   &.zoomed {
     height: 100%;
@@ -980,6 +1003,7 @@ onMounted(() => {
 }
 
 .zoom-icon {
+  z-index: 999;
   position: absolute;
   bottom: 0.6933rem;
   right: 0.4267rem;
@@ -1006,32 +1030,27 @@ onMounted(() => {
 
 .artifact-info-card {
   position: absolute;
-  top: 44%;
+  bottom: 0;
   width: 90%;
-  height: 47%;
+  height: 20%;
   background: #f4efe9;
   border-radius: 0.4rem 0.4rem 0 0;
   padding: 0.5333rem;
-  margin: 0.5333rem 0;
+  margin: 0.5333rem 0 0;
   overflow: auto;
   transition:
     opacity 0.3s ease,
     visibility 0.3s ease;
 
+  .card-content {
+    padding-bottom: 0.3rem;
+  }
+
   &.hidden {
     opacity: 0;
     visibility: hidden;
   }
 
-  .artifact-title {
-    font-size: 0.5333rem;
-    font-weight: bold;
-    color: #7c6444;
-    padding-bottom: 0.4rem;
-    margin-bottom: 0.64rem;
-    border-bottom: 0.0267rem solid #b49d7e;
-  }
-
   .info-table {
     background: #f4efe9;
     border-radius: 0.2133rem;
@@ -1051,11 +1070,11 @@ onMounted(() => {
         .info-label {
           display: flex;
           justify-content: space-between;
-          font-size: 0.4267rem;
+          font-size: 0.3267rem;
           color: #464646;
           margin-bottom: 0.1067rem;
           .info-label-text {
-            font-size: 0.4267rem;
+            font-size: 0.3267rem;
             color: #d1bb9e;
             font-weight: bold;
           }
@@ -1076,25 +1095,25 @@ onMounted(() => {
   }
 
   .artifact-size {
-    font-size: 0.3733rem;
+    font-size: 0.3rem;
     color: #584735;
     margin-bottom: 0.4rem;
   }
 
   .divider {
     width: 100%;
-    margin: 0.64rem 0;
+    margin: 0.2rem 0;
     display: flex;
     align-items: center;
     .divider-text {
-      font-size: 0.4267rem;
+      font-size: 0.36rem;
       color: #d1bb9e;
       font-weight: bold;
     }
   }
 
   .artifact-description {
-    font-size: 0.3467rem;
+    font-size: 0.3rem;
     line-height: 1.6;
     color: #584735;
     text-align: justify;

+ 79 - 23
mobile/src/views/collectpage/index.vue

@@ -1,5 +1,10 @@
 <template>
   <div class="collectpage">
+    <div class="myPush" @click="handleClick(item)">
+      <img :src="HomeActiveIcon" />
+      <p>首页</p>
+    </div>
+
     <!-- 搜索页面 -->
     <!-- <div v-if="showSearchPage" class="search-page">
       <SearchPageComponent
@@ -19,21 +24,6 @@
       <!-- 搜索输入框 -->
       <div class="search-container">
         <div class="search-wrapper">
-          <!-- 搜索输入框和检索按钮 -->
-          <div class="search-input-wrapper">
-            <el-input
-              v-model="searchParams.searchText"
-              placeholder="请输入关键词..."
-              class="search-input cut-corner-input"
-              @keyup.enter="quickSearch"
-            >
-              <template #suffix>
-                <el-button @click="quickSearch" class="quick-search-btn">
-                  <img src="@/assets/img/icon_search.png" alt="检索" class="search-icon" />
-                </el-button>
-              </template>
-            </el-input>
-          </div>
           <!-- 搜索筛选(拆分自搜索页组件,放到输入框下方) -->
           <div class="filter-row">
             <!-- 类别 -->
@@ -106,7 +96,7 @@
               class="cut-corner-select cut-corner-select-dim"
               placeholder="全部文物"
               @change="quickSearch"
-              placement="bottom"
+              placement="top"
             >
               <el-option :label="'全部文物'" :value="3" />
               <el-option :label="'二维文物'" :value="1" />
@@ -118,11 +108,26 @@
               <img src="@/assets/img/icon_restart.png" alt="重置" />
             </button>
           </div>
+          <!-- 搜索输入框和检索按钮 -->
+          <div class="search-input-wrapper">
+            <el-input
+              v-model="searchParams.searchText"
+              placeholder="请输入关键词..."
+              class="search-input cut-corner-input"
+              @keyup.enter="quickSearch"
+            >
+              <template #suffix>
+                <el-button @click="quickSearch" class="quick-search-btn">
+                  <img src="@/assets/img/icon_search.png" alt="检索" class="search-icon" />
+                </el-button>
+              </template>
+            </el-input>
+          </div>
         </div>
       </div>
 
       <!-- 搜索结果列表 -->
-      <div v-loading="loading" class="search-results" @scroll="handleScroll">
+      <div v-loading="loading" class="search-results" @scroll="handleScroll" id="moveBox">
         <div class="results-columns">
           <div class="column left">
             <div
@@ -171,12 +176,20 @@
 </template>
 
 <script setup>
+import HomeActiveIcon from '@/assets/img/icon_home_active.png'
 import { ref, computed, onMounted } from 'vue'
 import { useRouter } from 'vue-router'
 import SearchPageComponent from './components/SearchPageComponent.vue'
 import getBookCountApi from '@/api'
 import { useStore } from 'vuex'
 import { addWatermarkToCollectionImage } from '@/utils/index.js'
+import { set } from 'lodash'
+
+const handleClick = (item) => {
+  router.push({
+    name: 'home',
+  })
+}
 
 const store = useStore()
 
@@ -330,6 +343,19 @@ const handleSearch = async (searchFilters = {}) => {
     // 调用实际的搜索API,使用与CollectionContent相同的API
     const response = await getBookCountApi.getArtifactListApi(searchObj)
 
+    setTimeout(() => {
+      // 滚动到相对位置
+      const moveBox = document.getElementById('moveBox')
+      if (moveBox) {
+        const allHeight =
+          window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
+        moveBox.scrollTo({
+          top: allHeight * 0.48,
+          behavior: 'smooth',
+        })
+      }
+    }, 50)
+
     // 处理返回的数据
     const artifacts = response.pageData || response.list || response.records || []
 
@@ -399,6 +425,7 @@ const loadMoreData = async () => {
     }
 
     const response = await getBookCountApi.getArtifactListApi(searchObj)
+
     const artifacts = response.pageData || response.list || response.records || []
 
     const processedArtifacts = artifacts.map((item) => ({
@@ -624,6 +651,35 @@ const resetInline = () => {
   height: 100vh;
   position: relative;
   background: #f5f3ec;
+
+  .myPush {
+    position: absolute;
+    bottom: 3rem;
+    right: 0.5rem;
+    z-index: 999;
+    color: #a99271;
+    height: 1.4rem;
+    width: 1.4rem;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    background-color: rgba(255, 255, 255, 0.8);
+    border-radius: 0.7rem;
+    padding-bottom: 0.15rem;
+    box-sizing: border-box;
+
+    & > img {
+      width: 0.8rem;
+      height: 0.8rem;
+    }
+    & > p {
+      font-size: 0.26rem;
+      width: 1.3333rem;
+      text-align: center;
+      line-height: 0.32rem;
+    }
+  }
 }
 
 .search-page {
@@ -673,7 +729,7 @@ const resetInline = () => {
 
 .search-container {
   position: absolute;
-  top: 0.5333rem;
+  bottom: 0;
   left: 50%;
   transform: translateX(-50%);
   z-index: 10;
@@ -684,9 +740,10 @@ const resetInline = () => {
 .search-wrapper {
   display: flex;
   flex-direction: column;
+  justify-content: flex-start;
   // border-bottom: 0.0267rem dotted #d3bfa2;
   border-radius: 0.16rem;
-  gap: 0.0533rem;
+  height: 2.2rem;
 }
 
 // 内联筛选行
@@ -695,7 +752,7 @@ const resetInline = () => {
   align-items: center;
   flex-wrap: wrap;
   gap: 0.1067rem;
-  margin: 0.2667rem 0;
+  margin: 0.2667rem 0 0.05rem;
 }
 
 .reset-inline-btn {
@@ -825,16 +882,15 @@ const resetInline = () => {
 
 .search-results {
   position: absolute;
-  top: 2.8rem;
+  top: 0;
   left: 0;
   width: 100%;
   height: calc(100% - 2.5rem);
   overflow-y: auto;
   overflow-x: hidden;
   z-index: 10;
-  padding: 0 0.2667rem;
+  padding: 8.8rem 0.2667rem 2.4rem;
   box-sizing: border-box;
-  padding-bottom: 2.4rem;
 }
 
 .results-columns {

+ 112 - 90
mobile/src/views/contentPage/index.vue

@@ -1,14 +1,18 @@
 <template>
   <div class="collectpage">
-
     <!-- 主要内容 -->
-    <div  class="main-content">
+    <div class="main-content">
       <!-- 搜索输入框 -->
       <div class="search-container">
         <div class="search-wrapper">
           <!-- 搜索输入框和检索按钮 -->
           <div class="search-input-wrapper">
-            <el-input v-model="searchParams.searchText" placeholder="请输入关键词..." class="search-input cut-corner-input" @keyup.enter="quickSearch">
+            <el-input
+              v-model="searchParams.searchText"
+              placeholder="请输入关键词..."
+              class="search-input cut-corner-input"
+              @keyup.enter="quickSearch"
+            >
               <template #suffix>
                 <el-button @click="quickSearch" class="quick-search-btn">
                   <img src="@/assets/img/icon_search.png" alt="检索" class="search-icon" />
@@ -57,12 +61,12 @@
 import { ref, onMounted, nextTick } from 'vue'
 import { useRouter } from 'vue-router'
 import getBookCountApi from '@/api'
-import { addWatermarkToCollectionImage } from "@/utils/index.js";
+import { addWatermarkToCollectionImage } from '@/utils/index.js'
 
 defineOptions({
-  name: 'CollectPage'
+  name: 'CollectPage',
 })
-const imgUrl = import.meta.env.VITE_MODEL_URL;
+const imgUrl = import.meta.env.VITE_MODEL_URL
 const router = useRouter()
 
 // 响应式数据
@@ -80,54 +84,54 @@ const hasMoreData = ref(true) // 是否还有更多数据
 const resultsContainer = ref(null)
 const STORAGE_KEY = 'collectPageState'
 // 处理图片水印
-const watermarkedImages = ref(new Map()); // 存储处理后的水印图片
+const watermarkedImages = ref(new Map()) // 存储处理后的水印图片
 const processImageWithWatermark = async (item) => {
   // 如果已经处理过,直接返回
   if (watermarkedImages.value.has(item.id)) {
     // console.log('图片已处理过,直接返回缓存结果');
-    return watermarkedImages.value.get(item.id);
+    return watermarkedImages.value.get(item.id)
   }
   // 如果正在处理,避免重复处理
   if (item.watermarkProcessing) {
-    return item.originalImage;
+    return item.originalImage
   }
   try {
     // 标记为处理中
-    item.watermarkProcessing = true;
+    item.watermarkProcessing = true
     // 处理水印
     const watermarkedImageUrl = await addWatermarkToCollectionImage(item.originalImage, {
       position: 'center',
       opacity: 1,
       scale: 0.5,
-      margin: 15
-    });
+      margin: 15,
+    })
     // 存储处理后的图片
-    watermarkedImages.value.set(item.id, watermarkedImageUrl);
+    watermarkedImages.value.set(item.id, watermarkedImageUrl)
     // 更新图片地址
-    item.image = watermarkedImageUrl;
-    return watermarkedImageUrl;
+    item.image = watermarkedImageUrl
+    return watermarkedImageUrl
   } catch (error) {
-    console.error('水印处理失败:', error);
+    console.error('水印处理失败:', error)
     // 统一处理:失败时直接缓存并返回原图
-    watermarkedImages.value.set(item.id, item.originalImage);
-    item.image = item.originalImage;
-    return item.originalImage;
+    watermarkedImages.value.set(item.id, item.originalImage)
+    item.image = item.originalImage
+    return item.originalImage
   } finally {
     // 标记处理完成
-    item.watermarkProcessing = false;
+    item.watermarkProcessing = false
   }
-};
+}
 // 获取图片地址(带水印处理)
 const getImageUrl = (item) => {
   // 如果已经有水印图片,直接返回
   if (watermarkedImages.value.has(item.id)) {
-    return watermarkedImages.value.get(item.id);
+    return watermarkedImages.value.get(item.id)
   }
   // 异步处理水印
-  processImageWithWatermark(item);
+  processImageWithWatermark(item)
   // 先返回原图
-  return item.originalImage;
-};
+  return item.originalImage
+}
 const savePageState = () => {
   try {
     const scrollTop = resultsContainer.value ? resultsContainer.value.scrollTop || 0 : 0
@@ -136,7 +140,7 @@ const savePageState = () => {
       searchResults: [...searchResults.value],
       currentPage: currentPage.value,
       hasMoreData: hasMoreData.value,
-      scrollTop
+      scrollTop,
     }
     sessionStorage.setItem(STORAGE_KEY, JSON.stringify(state))
   } catch {
@@ -166,7 +170,6 @@ const restorePageStateIfAvailable = async () => {
   return true
 }
 
-
 // 快速搜索(回车键触发)
 const quickSearch = () => {
   console.log('searchParams', searchParams.value)
@@ -175,30 +178,30 @@ const quickSearch = () => {
 
 // 加载藏品数据
 const loadArtifactsData = async (isLoadMore = false) => {
-  if (loading.value) return;
+  if (loading.value) return
 
-  loading.value = true;
+  loading.value = true
   try {
     const searchObj = {
       searchText: searchParams.value.searchText,
       pageNo: currentPage.value,
-      pageSize: pageSize
-    };
+      pageSize: pageSize,
+    }
 
     // 调用API获取数据
-    const response = await getBookCountApi.getNewsListApi(searchObj);
+    const response = await getBookCountApi.getNewsListApi(searchObj)
 
     // 处理返回的数据
-    const artifacts = response.pageData || [];
+    const artifacts = response.pageData || []
 
     if (artifacts.length === 0) {
-      hasMoreData.value = false;
-      loading.value = false;
-      return;
+      hasMoreData.value = false
+      loading.value = false
+      return
     }
 
     // 数据格式化处理
-    const processedArtifacts = artifacts.map(item => ({
+    const processedArtifacts = artifacts.map((item) => ({
       id: item.id,
       contentType: item.contentType || '富文本编辑',
       name: item.name || item.title,
@@ -210,57 +213,61 @@ const loadArtifactsData = async (isLoadMore = false) => {
       image: `${imgUrl}${item.thumbnail}`,
       originalImage: `${imgUrl}${item.thumbnail}`,
       isLoading: true,
-      ...item
-    }));
+      ...item,
+    }))
 
     if (isLoadMore) {
       // 追加数据
-      searchResults.value.push(...processedArtifacts);
+      searchResults.value.push(...processedArtifacts)
     } else {
       // 重新加载
-      searchResults.value = processedArtifacts;
+      searchResults.value = processedArtifacts
     }
 
     // 延迟触发瀑布效果 - 使用 AnnouncementContent 的动画时机
     setTimeout(() => {
       processedArtifacts.forEach((item, index) => {
-        setTimeout(() => {
-          const foundItem = searchResults.value.find(listItem => listItem.id === item.id && listItem.isLoading);
-          if (foundItem) {
-            foundItem.isLoading = false;
-          }
-        }, index * 200 + 300); // 使用与 AnnouncementContent 相同的时机
-      });
-    }, 200);
+        setTimeout(
+          () => {
+            const foundItem = searchResults.value.find(
+              (listItem) => listItem.id === item.id && listItem.isLoading,
+            )
+            if (foundItem) {
+              foundItem.isLoading = false
+            }
+          },
+          index * 200 + 300,
+        ) // 使用与 AnnouncementContent 相同的时机
+      })
+    }, 200)
 
     // 检查是否还有更多数据
     if (artifacts.length < pageSize) {
-      hasMoreData.value = false;
+      hasMoreData.value = false
     }
-
   } catch (error) {
-    console.error('获取藏品数据失败:', error);
-    searchResults.value = [];
-    searched.value = true;
+    console.error('获取藏品数据失败:', error)
+    searchResults.value = []
+    searched.value = true
   } finally {
-    loading.value = false;
+    loading.value = false
   }
-};
+}
 
 // 处理搜索
 const handleSearch = async () => {
-  console.log('执行搜索:', searchParams.value.searchText);
-  currentPage.value = 1;
-  searchResults.value = [];
-  hasMoreData.value = true;
-  searched.value = true;
-  await loadArtifactsData();
+  console.log('执行搜索:', searchParams.value.searchText)
+  currentPage.value = 1
+  searchResults.value = []
+  hasMoreData.value = true
+  searched.value = true
+  await loadArtifactsData()
 }
 
 // 查看详情
 const viewDetails = (item) => {
   console.log('item', item)
-  if(item.contentType == '公众号链接'){
+  if (item.contentType == '公众号链接') {
     // 离开前保存列表与滚动状态
     console.log('公众号链接', item.content)
     savePageState()
@@ -276,47 +283,52 @@ const viewDetails = (item) => {
       if (document && document.body) {
         document.body.scrollTop = 0
       }
-    } catch { /* ignore */ }
+    } catch {
+      /* ignore */
+    }
     window.location.href = item.content
     // scrollTop
     // const a = document.createElement('a')
     // a.href = item.content
     // a.target = '_blank'
     // a.click()
-  } else{
+  } else {
     // 前往详情前保存状态,返回时恢复
     savePageState()
     router.push({
       path: '/contentDetail',
       query: {
-        id: item.id
-      }
+        id: item.id,
+      },
     })
   }
 }
 
 // 滚动处理 - 加载更多
 const handleScroll = async (event) => {
-  const container = event.target;
-  if (!container || loading.value || !hasMoreData.value) return;
+  const container = event.target
+  if (!container || loading.value || !hasMoreData.value) return
 
   // 检查是否滚动到底部
-  const isScrolledToBottom = container.scrollTop + container.clientHeight >= container.scrollHeight - 50;
+  const isScrolledToBottom =
+    container.scrollTop + container.clientHeight >= container.scrollHeight - 50
 
   if (isScrolledToBottom) {
-    console.log('开始加载更多藏品数据...');
-    currentPage.value++;
-    await loadArtifactsData(true);
+    console.log('开始加载更多藏品数据...')
+    currentPage.value++
+    await loadArtifactsData(true)
   }
-};
+}
 
 // 组件挂载
 onMounted(async () => {
-  console.log('内容页组件已挂载');
+  console.log('内容页组件已挂载')
   if ('scrollRestoration' in history) {
     try {
       history.scrollRestoration = 'manual'
-    } catch { /* ignore */ }
+    } catch {
+      /* ignore */
+    }
   }
 
   // 优先尝试恢复上次状态,若没有再加载初始数据
@@ -324,8 +336,8 @@ onMounted(async () => {
   if (!restored) {
     // 延迟加载初始数据,确保DOM已渲染
     setTimeout(() => {
-      loadArtifactsData();
-    }, 100);
+      loadArtifactsData()
+    }, 100)
   }
 })
 </script>
@@ -443,7 +455,7 @@ onMounted(async () => {
     border: none;
     background: url('@/assets/img/btn_active.png') no-repeat;
     background-size: 30px 30px;
-    .search-icon{
+    .search-icon {
       position: absolute;
       top: 50%;
       left: 50%;
@@ -481,7 +493,7 @@ onMounted(async () => {
   gap: 2px;
   border: none;
   border-radius: 4px;
-  color: #B49D7E;
+  color: #b49d7e;
   font-size: 16px;
   font-weight: 500;
   cursor: pointer;
@@ -539,10 +551,13 @@ onMounted(async () => {
 
   // 瀑布加载状态
   &.loading-waterfall {
-    .result-image, .result-info {
+    .result-image,
+    .result-info {
       opacity: 0.3;
       filter: grayscale(100%);
-      transition: opacity 0.3s ease, filter 0.3s ease;
+      transition:
+        opacity 0.3s ease,
+        filter 0.3s ease;
     }
   }
 
@@ -552,19 +567,22 @@ onMounted(async () => {
     left: 0;
     width: 100%;
     height: 100%;
-    background: linear-gradient(180deg,
+    background: linear-gradient(
+      180deg,
       rgba(0, 0, 0, 0.9) 0%,
       rgba(0, 0, 0, 0.8) 50%,
-      rgba(0, 0, 0, 0.9) 100%);
+      rgba(0, 0, 0, 0.9) 100%
+    );
     z-index: 10;
     transform: translateY(-100%);
     border-radius: 8px;
-    animation: waterfallDown 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards,
-               waterfallUp 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94) 0.8s forwards;
+    animation:
+      waterfallDown 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards,
+      waterfallUp 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94) 0.8s forwards;
     will-change: transform, opacity;
   }
 }
-.result-image-wrapper{
+.result-image-wrapper {
   display: flex;
   justify-content: center;
   width: 170px;
@@ -578,7 +596,9 @@ onMounted(async () => {
   object-fit: cover;
   flex-shrink: 0;
   border-radius: 6px;
-  transition: opacity 0.3s ease, filter 0.3s ease;
+  transition:
+    opacity 0.3s ease,
+    filter 0.3s ease;
 }
 
 .result-info {
@@ -586,7 +606,9 @@ onMounted(async () => {
   // display: flex;
   // flex-direction: column;
   // justify-content: center;
-  transition: opacity 0.3s ease, filter 0.3s ease;
+  transition:
+    opacity 0.3s ease,
+    filter 0.3s ease;
   position: relative;
   padding-bottom: 30px;
 
@@ -621,7 +643,7 @@ onMounted(async () => {
     height: 30px;
     line-height: 30px;
     text-align: center;
-    color: #A99271;
+    color: #a99271;
     font-size: 12px;
     padding: 0 10px;
   }