wangfumin 1 месяц назад
Родитель
Сommit
161f7b7c47

+ 2 - 2
mobile/.env.development

@@ -9,8 +9,8 @@ VITE_AXIOS_BASE_URL = '/api'  # 用于代理
 # VITE_AXIOS_BASE_URL = 'https://mock.apipark.cn/m1/3776410-0-default'  # apifox云端mock
 # VITE_AXIOS_BASE_URL = 'http://192.168.0.73:8085'
 # 代理配置-target
-VITE_PROXY_TARGET = 'http://192.168.0.73:8180'
-# VITE_PROXY_TARGET = 'https://sit-huyaobangjng.4dage.com'
+# VITE_PROXY_TARGET = 'http://192.168.0.73:8180'
+VITE_PROXY_TARGET = 'https://sit-huyaobangjng.4dage.com'
 
 # 图片基础
 VITE_COS_BASE_URL = 'https://hybgc.4dage.com/ArtCMS/'

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

@@ -444,7 +444,7 @@ Viewer.prototype.onPointerUp = function (event) {
             this.onPointerMove(event,true)
         }
         if (this.hoveredObject) {
-            this.dispatchEvent({ type: 'clickObject', imgName: this.hoveredObject.material.uniforms.map.value.image.src.split('/').pop() })
+            this.dispatchEvent({ type: 'clickObject', imgName: this.hoveredObject.material.uniforms.map.value.image.src })
         }
     }
 
@@ -459,17 +459,14 @@ Viewer.prototype.checkIntersection = function () {
     raycaster.near = 2
     const intersects = raycaster.intersectObject(this.cardGroup, true);
     var recover = () => {
-        if (this.hoveredObject) {
-
-
-        }
+        if (this.hoveredObject) {}
     }
     if (intersects.length > 0) {
 
         const hoveredObject = intersects[0].object;
         if (this.hoveredObject != hoveredObject) {
             recover()
-            this.dispatchEvent({ type: "hoverObject", imgName: hoveredObject.material.uniforms.map.value.image.src.split('/').pop() })
+            this.dispatchEvent({ type: "hoverObject", imgName: hoveredObject.material.uniforms.map.value.image.src })
             this.hoveredObject = hoveredObject;
             this.dom.style.cursor = 'pointer'
 

BIN
mobile/src/assets/img/title.png


BIN
mobile/src/assets/img/waterMar.png


+ 117 - 0
mobile/src/utils/index.js

@@ -0,0 +1,117 @@
+// 专门用于收藏品图片的水印处理方法
+export function addWatermarkToCollectionImage(imageUrl, options = {}) {
+  return new Promise((resolve, reject) => {
+      // console.log('开始处理水印,原图URL:', imageUrl);
+      
+      // 默认选项
+      const {
+          position = 'bottom-right',
+          opacity = 0.6,
+          scale = 0.15,
+          margin = 15
+      } = options;
+      
+      const image = new Image();
+      const watermark = new Image();
+      
+      // 设置跨域属性,如果图片来自不同域
+      image.crossOrigin = 'anonymous';
+      watermark.crossOrigin = 'anonymous';
+      
+      let imagesLoaded = 0;
+      
+      function onImageLoad() {
+          imagesLoaded++;
+          // console.log(`图片加载完成 ${imagesLoaded}/2`);
+          if (imagesLoaded === 2) {
+              try {
+                  console.log('开始绘制水印');
+                  // 两张图片都加载完成
+                  const canvas = document.createElement('canvas');
+                  const ctx = canvas.getContext('2d');
+                  
+                  // 设置Canvas尺寸与主图片相同
+                  canvas.width = image.width;
+                  canvas.height = image.height;
+                  
+                  // console.log('Canvas尺寸:', canvas.width, 'x', canvas.height);
+                  // console.log('水印图片尺寸:', watermark.width, 'x', watermark.height);
+                  
+                  // 绘制主图片
+                  ctx.drawImage(image, 0, 0);
+                  
+                  // 计算水印尺寸
+                  const watermarkWidth = image.width * scale;
+                  const watermarkHeight = (watermark.height / watermark.width) * watermarkWidth;
+                  
+                  // console.log('计算后的水印尺寸:', watermarkWidth, 'x', watermarkHeight);
+                  
+                  // 设置透明度
+                  ctx.globalAlpha = opacity;
+                  
+                  // 计算水印位置
+                  let x, y;
+                  
+                  switch(position) {
+                      case 'top-left':
+                          x = margin;
+                          y = margin;
+                          break;
+                      case 'top-right':
+                          x = canvas.width - watermarkWidth - margin;
+                          y = margin;
+                          break;
+                      case 'bottom-left':
+                          x = margin;
+                          y = canvas.height - watermarkHeight - margin;
+                          break;
+                      case 'bottom-right':
+                          x = canvas.width - watermarkWidth - margin;
+                          y = canvas.height - watermarkHeight - margin;
+                          break;
+                      case 'center':
+                          x = (canvas.width - watermarkWidth) / 2;
+                          y = (canvas.height - watermarkHeight) / 2;
+                          break;
+                      default:
+                          x = canvas.width - watermarkWidth - margin;
+                          y = canvas.height - watermarkHeight - margin;
+                  }
+                  
+                  // console.log('水印位置:', x, y);
+                  
+                  // 绘制水印
+                  ctx.drawImage(watermark, x, y, watermarkWidth, watermarkHeight);
+                  
+                  // 将Canvas转换为Data URL
+                  const watermarkedImageUrl = canvas.toDataURL('image/png', 0.9);
+                  // console.log('水印处理完成,生成的URL长度:', watermarkedImageUrl.length);
+                  resolve(watermarkedImageUrl);
+              } catch (error) {
+                  console.error('Canvas绘制错误:', error);
+                  reject(new Error('水印处理失败: ' + error.message));
+              }
+          }
+      }
+      
+      image.onload = onImageLoad;
+      watermark.onload = onImageLoad;
+      
+      image.onerror = () => {
+        //   console.error('主图片加载失败:', imageUrl);
+        //   reject(new Error('主图片加载失败'));
+      };
+      watermark.onerror = () => {
+        //   console.error('水印图片加载失败');
+        //   reject(new Error('水印图片加载失败'));
+      };
+      
+      // 开始加载图片
+      // console.log('开始加载主图片:', imageUrl);
+      image.src = imageUrl;
+      
+      // 直接使用相对路径加载水印图片
+      // console.log('开始加载水印图片');
+      watermark.src = '/src/assets/img/waterMar.png';
+  });
+}

+ 68 - 8
mobile/src/views/Home/index.vue

@@ -17,6 +17,7 @@ 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([]);
 
@@ -29,12 +30,66 @@ const txtDom = ref(null);
 const datalist = ref([]);
 const total = ref(0);
 const isLoading = ref(false);
+// 水印缓存和处理逻辑
+const watermarkedImages = ref(new Map()); // 存储处理后的水印图片
+// 处理图片水印
+const processImageWithWatermark = async (item) => {
+  // 如果已经处理过,直接返回
+  if (watermarkedImages.value.has(item.id)) {
+    return watermarkedImages.value.get(item.id);
+  }
+  
+  // 如果正在处理,避免重复处理
+  if (item.watermarkProcessing) {
+    return item.thumb;
+  }
+  
+  try {
+    // 标记为处理中
+    item.watermarkProcessing = true;
+    // 处理水印
+    const watermarkedImageUrl = await addWatermarkToCollectionImage(item.thumb, {
+      position: 'center',
+      opacity: 1,
+      scale: 0.7,
+      margin: 15
+    });
+    // 存储处理后的图片
+    watermarkedImages.value.set(item.id, watermarkedImageUrl);
+    return watermarkedImageUrl;
+  } catch (error) {
+    console.error('水印处理失败:', error);
+    // 失败时使用原图
+    return item.thumb;
+  } finally {
+    // 标记处理完成
+    item.watermarkProcessing = false;
+  }
+};
+// 获取图片地址(带水印处理)
+const getImageUrl = (item) => {
+  // 如果已经有水印图片,直接返回
+  if (watermarkedImages.value.has(item.id)) {
+    return watermarkedImages.value.get(item.id);
+  }
+  
+  // 先返回原图
+  return item.thumb;
+};
 onMounted(() => {
   getRecommendList();
   // 点击图片
   window.clickObject = (val) => {
-    const item = datalist.value.find((i) => i.thumb.indexOf(val) > -1);
-    console.log(item, item.id, 'item')
+    // console.log(val)
+    // const item = datalist.value.find((i) => i.thumb.indexOf(val) > -1);
+    let item = null;
+    for (const [id, watermarkedUrl] of watermarkedImages.value) {
+      if (watermarkedUrl === val) {
+        // 找到对应的原始URL,再通过原始URL找到数据项
+        item = datalist.value.find((i) => i.id === id);
+        break;
+      }
+    }
     router.push({
       name: "collectDetail",
       query: {
@@ -89,14 +144,20 @@ const getRecommendList = async () => {
 
   const processedData = list.map((i) => ({
     ...i,
-    thumb: `${imguRL}/${i.thumbnail}`,
+    thumb: `${imguRL}${i.thumbnail}`,
+    originalImage: `${imguRL}${i.thumbnail}`,
   }));
 
   processedData.maxWidth = 1000;
-  processedData.qualityRatio = 0.8;
+  processedData.qualityRatio = 0.1;
+
+  // 批量处理所有图片的水印
+  const watermarkPromises = processedData.map(item => processImageWithWatermark(item));
+  await Promise.all(watermarkPromises);
 
-  const imgList = processedData.map((i) => i.thumb);
-  console.log(imgList, 'imgList')
+  // 使用水印图片生成图片列表
+  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") {
@@ -106,9 +167,8 @@ const getRecommendList = async () => {
       iframe.contentWindow.initViewer(imgList);
     };
   }
-  console.log(processedData)
-  datalist.value = processedData;
   isLoading.value = false;
+  datalist.value = processedData;
 };
 
 </script>

+ 49 - 5
mobile/src/views/collectpage/components/collectDetail.vue

@@ -71,10 +71,11 @@
           <div class="carousel-container-image" v-if="artifactData && artifactData.imageFiles && artifactData.imageFiles.length > 0">
             <el-carousel
               height="100%"
-              arrow="always"
               ref="imageCarouselRef"
+              trigger="click"
               @change="handleImageCarouselChange"
               :autoplay="autoplayEnabled"
+              :arrow="imageList.length > 1 ? 'always' : 'never'"
               :interval="4000"
               :pause-on-hover="false"
             >
@@ -83,7 +84,7 @@
                 :key="index"
               >
                 <div class="image-item">
-                  <img :src="item.src" :alt="item.alt" class="carousel-image" />
+                  <img :src="getImageUrl(item)" :alt="item.alt" class="carousel-image" />
                 </div>
               </el-carousel-item>
             </el-carousel>
@@ -221,6 +222,7 @@ import getBookCountApi from '@/api'
 import { ZoomIn, ZoomOut } from '@element-plus/icons-vue'
 import iconVoiceOff from '@/assets/img/icon_voiceoff.png'
 import iconVoiceOn from '@/assets/img/icon_voiceon.png'
+import { addWatermarkToCollectionImage } from "@/utils/index.js";
 const modelUrl = import.meta.env.VITE_MODEL_URL
 
 const router = useRouter()
@@ -247,9 +249,50 @@ const imgUrl = import.meta.env.VITE_COS_BASE_URL
 const getFieldValue = (item, fieldName) => {
   return item[fieldName]
 }
-
-
-
+// 处理图片水印
+const watermarkedImages = ref(new Map()); // 存储处理后的水印图片
+const processImageWithWatermark = async (item) => {
+  // 如果已经处理过,直接返回
+  if (watermarkedImages.value.has(item.id)) {
+    // console.log('图片已处理过,直接返回缓存结果');
+    return watermarkedImages.value.get(item.id);
+  }
+  // 如果正在处理,避免重复处理
+  if (item.watermarkProcessing) {
+    return item.originalImage;
+  }
+  try {
+    // 标记为处理中
+    item.watermarkProcessing = true;
+    // 处理水印
+    const watermarkedImageUrl = await addWatermarkToCollectionImage(item.originalImage, {
+      position: 'center',
+      opacity: 1,
+      scale: 0.7,
+      margin: 15
+    });
+    // 存储处理后的图片
+    watermarkedImages.value.set(item.id, watermarkedImageUrl);
+    // 更新图片地址
+    item.src = watermarkedImageUrl;
+    return watermarkedImageUrl;
+  } catch (error) {
+    console.error('水印处理失败:', error);
+    // 失败时使用原图
+    return item.originalImage;
+  } finally {
+    // 标记处理完成
+    item.watermarkProcessing = false;
+  }
+};
+// 获取图片地址(带水印处理)
+const getImageUrl = (item) => {
+  if (watermarkedImages.value.has(item.id)) {
+    return watermarkedImages.value.get(item.id);
+  }
+  processImageWithWatermark(item);
+  return item.originalImage;
+};
 // 初始化模型、图片、视频和音频列表
 const initializeLists = () => {
   if (!artifactData.value) {
@@ -275,6 +318,7 @@ const initializeLists = () => {
   imageList.value = imgFiles.map((imgFile, index) => ({
     type: 'image',
     src: `${imgUrl}${imgFile}`,
+    originalImage: `${imgUrl}${imgFile}`,
     alt: `${artifactData.value.title || artifactData.value.name} 图片${index + 1}`
   }))
 

+ 57 - 3
mobile/src/views/collectpage/index.vue

@@ -52,7 +52,7 @@
           @click="viewDetails(item)"
         >
           <div class="waterfall-mask" v-if="item.isLoading"></div>
-          <img :src="item.image" :alt="item.name" class="result-image" />
+          <img :src="getImageUrl(item)" :alt="item.name" class="result-image" />
           <div class="result-info">
             <div class="result-name">{{ item.name }}</div>
             <div class="more-info"><img src="@/assets/img/icon_collect_more.png" alt="更多" class="more-icon" /></div>
@@ -79,6 +79,7 @@ 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";
 
 const store = useStore()
 
@@ -129,7 +130,7 @@ const buildOrderbyList = (orderByValue = searchParams.value.orderBy) => {
       categoryOptions.value.forEach(item => {
         orderbyList.push(item.value);
       });
-    } else if (orderByValue === 'texture') {
+    } else if (orderByValue === 'material') {
       materialOptions.value.forEach(item => {
         orderbyList.push(item.value);
       });
@@ -265,6 +266,7 @@ const handleSearch = async (searchFilters = {}) => {
       level: item.level || item.grade || '未定级',
       material: item.texture || '其他',
       image: `${imgUrl}${item.thumbnail}`,
+      originalImage: `${imgUrl}${item.thumbnail}`,
       hasImage: item.hasImage !== false,
       hasModel: item.hasModel !== false,
       isLoading: false
@@ -355,7 +357,7 @@ const loadDictionaryData = async () => {
     orderByOptions.value = [
       { label: '等级', value: 'grade' },
       { label: '类别', value: 'category' },
-      { label: '材质', value: 'texture' },
+      { label: '材质', value: 'material' },
       { label: '年代', value: 'era' }
     ];
 
@@ -418,6 +420,58 @@ const performInitialSearch = () => {
   });
 };
 
+const watermarkedImages = ref(new Map()); // 存储处理后的水印图片
+// 处理图片水印
+const processImageWithWatermark = async (item) => {
+  // console.log('开始处理图片水印,item:', item.id, item.originalImage);
+  // 如果已经处理过,直接返回
+  if (watermarkedImages.value.has(item.id)) {
+    // console.log('图片已处理过,直接返回缓存结果');
+    return watermarkedImages.value.get(item.id);
+  }
+  // 如果正在处理,避免重复处理
+  if (item.watermarkProcessing) {
+    // console.log('图片正在处理中,返回原图');
+    return item.originalImage;
+  }
+  try {
+    // 标记为处理中
+    item.watermarkProcessing = true;
+    // console.log('开始调用水印处理函数');
+    // 处理水印
+    const watermarkedImageUrl = await addWatermarkToCollectionImage(item.originalImage, {
+      position: 'center',
+      opacity: 1,
+      scale: 0.9,
+      margin: 15
+    });
+    // console.log('水印处理成功,结果URL:', watermarkedImageUrl);
+    // 存储处理后的图片
+    watermarkedImages.value.set(item.id, watermarkedImageUrl);
+    // 更新图片地址
+    item.image = watermarkedImageUrl;
+    return watermarkedImageUrl;
+  } catch (error) {
+    console.error('水印处理失败:', error);
+    // 失败时使用原图
+    return item.originalImage;
+  } finally {
+    // 标记处理完成
+    item.watermarkProcessing = false;
+  }
+};
+// 获取图片地址(带水印处理)
+const getImageUrl = (item) => {
+  // 如果已经有水印图片,直接返回
+  if (watermarkedImages.value.has(item.id)) {
+    return watermarkedImages.value.get(item.id);
+  }
+  // 异步处理水印
+  processImageWithWatermark(item);
+  // 先返回原图
+  return item.originalImage;
+};
+
 // 组件挂载
 onMounted(() => {
   // 加载字典数据,字典数据加载完成后会自动进行初始化搜索

+ 51 - 3
mobile/src/views/contentPage/index.vue

@@ -30,7 +30,7 @@
         >
           <div class="waterfall-mask" v-if="item.isLoading"></div>
           <div class="result-image-wrapper">
-            <img :src="item.image" :alt="item.name" class="result-image" />
+            <img :src="getImageUrl(item)" :alt="item.name" class="result-image" />
           </div>
           <div class="result-info">
             <div class="result-name">{{ item.name }}</div>
@@ -57,6 +57,7 @@
 import { ref, onMounted, nextTick } from 'vue'
 import { useRouter } from 'vue-router'
 import getBookCountApi from '@/api'
+import { addWatermarkToCollectionImage } from "@/utils/index.js";
 
 defineOptions({
   name: 'CollectPage'
@@ -78,7 +79,53 @@ const hasMoreData = ref(true) // 是否还有更多数据
 // 滚动容器引用与状态存储键
 const resultsContainer = ref(null)
 const STORAGE_KEY = 'collectPageState'
-
+// 处理图片水印
+const watermarkedImages = ref(new Map()); // 存储处理后的水印图片
+const processImageWithWatermark = async (item) => {
+  // 如果已经处理过,直接返回
+  if (watermarkedImages.value.has(item.id)) {
+    // console.log('图片已处理过,直接返回缓存结果');
+    return watermarkedImages.value.get(item.id);
+  }
+  // 如果正在处理,避免重复处理
+  if (item.watermarkProcessing) {
+    return item.originalImage;
+  }
+  try {
+    // 标记为处理中
+    item.watermarkProcessing = true;
+    // 处理水印
+    const watermarkedImageUrl = await addWatermarkToCollectionImage(item.originalImage, {
+      position: 'center',
+      opacity: 1,
+      scale: 0.7,
+      margin: 15
+    });
+    // 存储处理后的图片
+    watermarkedImages.value.set(item.id, watermarkedImageUrl);
+    // 更新图片地址
+    item.image = watermarkedImageUrl;
+    return watermarkedImageUrl;
+  } catch (error) {
+    console.error('水印处理失败:', error);
+    // 失败时使用原图
+    return item.originalImage;
+  } finally {
+    // 标记处理完成
+    item.watermarkProcessing = false;
+  }
+};
+// 获取图片地址(带水印处理)
+const getImageUrl = (item) => {
+  // 如果已经有水印图片,直接返回
+  if (watermarkedImages.value.has(item.id)) {
+    return watermarkedImages.value.get(item.id);
+  }
+  // 异步处理水印
+  processImageWithWatermark(item);
+  // 先返回原图
+  return item.originalImage;
+};
 const savePageState = () => {
   try {
     const scrollTop = resultsContainer.value ? resultsContainer.value.scrollTop || 0 : 0
@@ -159,6 +206,7 @@ const loadArtifactsData = async (isLoadMore = false) => {
       level: item.level || item.grade || '未定级',
       material: item.material || item.texture || '其他',
       image: `${imgUrl}${item.thumbnail}`,
+      originalImage: `${imgUrl}${item.thumbnail}`,
       isLoading: true,
       ...item
     }));
@@ -523,7 +571,7 @@ onMounted(async () => {
   background: #fff;
 }
 .result-image {
-  // width: 166px;
+  width: 170px;
   height: 136px;
   object-fit: cover;
   flex-shrink: 0;

+ 2 - 2
pc/.env.development

@@ -9,8 +9,8 @@ VITE_AXIOS_BASE_URL = '/api'  # 用于代理
 # VITE_AXIOS_BASE_URL = 'https://mock.apipark.cn/m1/3776410-0-default'  # apifox云端mock
 # VITE_AXIOS_BASE_URL = 'http://192.168.0.73:8085'
 # 代理配置-target
-VITE_PROXY_TARGET = 'http://192.168.0.73:8180'
-# VITE_PROXY_TARGET = 'https://sit-huyaobangjng.4dage.com'
+# VITE_PROXY_TARGET = 'http://192.168.0.73:8180'
+VITE_PROXY_TARGET = 'https://sit-huyaobangjng.4dage.com'
 
 # 图片基础
 VITE_COS_BASE_URL = 'https://hybgc.4dage.com/ArtCMS/'

+ 0 - 1
pc/public/three/index.html

@@ -59,7 +59,6 @@
 
       function initViewer(cardNames) {
         window.cardNames = cardNames;
-        console.log(cardNames, 11111)
         var startTime = new Date().getTime();
         window.viewer = new Viewer(0, $("#player")[0])
 

+ 4 - 6
pc/public/three/index.js

@@ -443,7 +443,7 @@ Viewer.prototype.onPointerUp = function (event) {
             this.onPointerMove(event,true)
         }
         if (this.hoveredObject) {
-            this.dispatchEvent({ type: 'clickObject', imgName: this.hoveredObject.material.uniforms.map.value.image.src.split('/').pop() })
+            this.dispatchEvent({ type: 'clickObject', imgName: this.hoveredObject.material.uniforms.map.value.image.src })
         }
     }
 
@@ -458,17 +458,15 @@ Viewer.prototype.checkIntersection = function () {
     raycaster.near = 2
     const intersects = raycaster.intersectObject(this.cardGroup, true);
     var recover = () => {
-        if (this.hoveredObject) {
-
-
-        }
+        if (this.hoveredObject) {}
     }
     if (intersects.length > 0) {
 
         const hoveredObject = intersects[0].object;
         if (this.hoveredObject != hoveredObject) {
             recover()
-            this.dispatchEvent({ type: "hoverObject", imgName: hoveredObject.material.uniforms.map.value.image.src.split('/').pop() })
+            // 这里返回整个链接是因为在点击事件中需要根据这个链接来找到对应的数据项,而且是重新生成的带水印图片数据
+            this.dispatchEvent({ type: "hoverObject", imgName: hoveredObject.material.uniforms.map.value.image.src })
             this.hoveredObject = hoveredObject;
             this.dom.style.cursor = 'pointer'
 

BIN
pc/src/assets/img/banner_01.png


BIN
pc/src/assets/img/icon_home.png


BIN
pc/src/assets/img/title.png


BIN
pc/src/assets/img/waterMar.png


+ 117 - 0
pc/src/utils/index.js

@@ -0,0 +1,117 @@
+// 专门用于收藏品图片的水印处理方法
+export function addWatermarkToCollectionImage(imageUrl, options = {}) {
+  return new Promise((resolve, reject) => {
+      // console.log('开始处理水印,原图URL:', imageUrl);
+      
+      // 默认选项
+      const {
+          position = 'bottom-right',
+          opacity = 0.6,
+          scale = 0.15,
+          margin = 15
+      } = options;
+      
+      const image = new Image();
+      const watermark = new Image();
+      
+      // 设置跨域属性,如果图片来自不同域
+      image.crossOrigin = 'anonymous';
+      watermark.crossOrigin = 'anonymous';
+      
+      let imagesLoaded = 0;
+      
+      function onImageLoad() {
+          imagesLoaded++;
+          // console.log(`图片加载完成 ${imagesLoaded}/2`);
+          if (imagesLoaded === 2) {
+              try {
+                  console.log('开始绘制水印');
+                  // 两张图片都加载完成
+                  const canvas = document.createElement('canvas');
+                  const ctx = canvas.getContext('2d');
+                  
+                  // 设置Canvas尺寸与主图片相同
+                  canvas.width = image.width;
+                  canvas.height = image.height;
+                  
+                  // console.log('Canvas尺寸:', canvas.width, 'x', canvas.height);
+                  // console.log('水印图片尺寸:', watermark.width, 'x', watermark.height);
+                  
+                  // 绘制主图片
+                  ctx.drawImage(image, 0, 0);
+                  
+                  // 计算水印尺寸
+                  const watermarkWidth = image.width * scale;
+                  const watermarkHeight = (watermark.height / watermark.width) * watermarkWidth;
+                  
+                  // console.log('计算后的水印尺寸:', watermarkWidth, 'x', watermarkHeight);
+                  
+                  // 设置透明度
+                  ctx.globalAlpha = opacity;
+                  
+                  // 计算水印位置
+                  let x, y;
+                  
+                  switch(position) {
+                      case 'top-left':
+                          x = margin;
+                          y = margin;
+                          break;
+                      case 'top-right':
+                          x = canvas.width - watermarkWidth - margin;
+                          y = margin;
+                          break;
+                      case 'bottom-left':
+                          x = margin;
+                          y = canvas.height - watermarkHeight - margin;
+                          break;
+                      case 'bottom-right':
+                          x = canvas.width - watermarkWidth - margin;
+                          y = canvas.height - watermarkHeight - margin;
+                          break;
+                      case 'center':
+                          x = (canvas.width - watermarkWidth) / 2;
+                          y = (canvas.height - watermarkHeight) / 2;
+                          break;
+                      default:
+                          x = canvas.width - watermarkWidth - margin;
+                          y = canvas.height - watermarkHeight - margin;
+                  }
+                  
+                  // console.log('水印位置:', x, y);
+                  
+                  // 绘制水印
+                  ctx.drawImage(watermark, x, y, watermarkWidth, watermarkHeight);
+                  
+                  // 将Canvas转换为Data URL
+                  const watermarkedImageUrl = canvas.toDataURL('image/png', 0.9);
+                  // console.log('水印处理完成,生成的URL长度:', watermarkedImageUrl.length);
+                  resolve(watermarkedImageUrl);
+              } catch (error) {
+                  console.error('Canvas绘制错误:', error);
+                  reject(new Error('水印处理失败: ' + error.message));
+              }
+          }
+      }
+      
+      image.onload = onImageLoad;
+      watermark.onload = onImageLoad;
+      
+      image.onerror = () => {
+          console.error('主图片加载失败:', imageUrl);
+          reject(new Error('主图片加载失败'));
+      };
+      watermark.onerror = () => {
+          console.error('水印图片加载失败');
+          reject(new Error('水印图片加载失败'));
+      };
+      
+      // 开始加载图片
+      // console.log('开始加载主图片:', imageUrl);
+      image.src = imageUrl;
+      
+      // 直接使用相对路径加载水印图片
+      // console.log('开始加载水印图片');
+      watermark.src = '/src/assets/img/waterMar.png';
+  });
+}

+ 78 - 6
pc/src/views/Home/index.vue

@@ -21,7 +21,7 @@
 
       <div class="home-search">
         <input
-          v-model="searchText"
+          v-model="searchText" 
           class="home-search__input"
           type="text"
           placeholder="请输入关键词..."
@@ -46,6 +46,7 @@ import { onMounted, ref } from "vue";
 import getBookCountApi from "@/api";
 import { useRouter } from "vue-router";
 import { useStore } from 'vuex';
+import { addWatermarkToCollectionImage } from '@/utils/index.js';
 
 const store = useStore();
 const imguRL =  import.meta.env.VITE_COS_BASE_URL
@@ -58,11 +59,68 @@ const total = ref("--");
 const searchText = ref("");
 const isLoading = ref(false);
 
+// 水印缓存和处理逻辑
+const watermarkedImages = ref(new Map()); // 存储处理后的水印图片
+// 处理图片水印
+const processImageWithWatermark = async (item) => {
+  // 如果已经处理过,直接返回
+  if (watermarkedImages.value.has(item.id)) {
+    return watermarkedImages.value.get(item.id);
+  }
+  
+  // 如果正在处理,避免重复处理
+  if (item.watermarkProcessing) {
+    return item._thumb;
+  }
+  
+  try {
+    // 标记为处理中
+    item.watermarkProcessing = true;
+    // 处理水印
+    const watermarkedImageUrl = await addWatermarkToCollectionImage(item._thumb, {
+      position: 'center',
+      opacity: 1,
+      scale: 0.7,
+      margin: 15
+    });
+    // 存储处理后的图片
+    watermarkedImages.value.set(item.id, watermarkedImageUrl);
+    return watermarkedImageUrl;
+  } catch (error) {
+    console.error('水印处理失败:', error);
+    // 失败时使用原图
+    return item._thumb;
+  } finally {
+    // 标记处理完成
+    item.watermarkProcessing = false;
+  }
+};
+// 获取图片地址(带水印处理)
+const getImageUrl = (item) => {
+  // 如果已经有水印图片,直接返回
+  if (watermarkedImages.value.has(item.id)) {
+    return watermarkedImages.value.get(item.id);
+  }
+  
+  // 先返回原图
+  return item._thumb;
+};
+
 onMounted(() => {
   getRecommendList();
   // 点击图片
   window.clickObject = (val) => {
-    const item = datalist.value.find((i) => i._thumb.indexOf(val) > -1);
+    // console.log(datalist.value, 777)
+    // const item = datalist.value.find((i) => i.originalImage.indexOf(val) > -1);
+    let item = null;
+    for (const [id, watermarkedUrl] of watermarkedImages.value) {
+      if (watermarkedUrl === val) {
+        // 找到对应的原始URL,再通过原始URL找到数据项
+        item = datalist.value.find((i) => i.id === id);
+        break;
+      }
+    }
+    // console.log(datalist.value, item, val, watermarkedImages.value, 999)
     router.push({
       name: "collect",
       query: {
@@ -74,7 +132,15 @@ onMounted(() => {
   };
   // 鼠标移入
   window.hoverObject = (val) => {
-    const item = datalist.value.find((i) => i._thumb.indexOf(val.imgName) > -1);
+    // const item = datalist.value.find((i) => i.originalImage.indexOf(val.imgName) > -1);
+    let item = null;
+    for (const [id, watermarkedUrl] of watermarkedImages.value) {
+      if (watermarkedUrl === val.imgName) {
+        // 找到对应的原始URL,再通过原始URL找到数据项
+        item = datalist.value.find((i) => i.id === id);
+        break;
+      }
+    }
     if (!item) return;
 
     txt.value = {
@@ -120,14 +186,20 @@ const getRecommendList = async () => {
 
   const processedData = list.map((i) => ({
     ...i,
-    _thumb: `${imguRL}/${i.thumbnail}`,
+    _thumb: `${imguRL}${i.thumbnail}`,
+    originalImage: `${imguRL}${i.thumbnail}`,
   }));
 
   processedData.maxWidth = 1000;
   processedData.qualityRatio = 0.1;
 
-  const imgList = processedData.map((i) => i._thumb);
-  console.log(imgList, 'imgList')
+  // 批量处理所有图片的水印
+  const watermarkPromises = processedData.map(item => processImageWithWatermark(item));
+  await Promise.all(watermarkPromises);
+
+  // 使用水印图片生成图片列表
+  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") {

+ 62 - 2
pc/src/views/collect/components/AnnouncementContent.vue

@@ -53,7 +53,7 @@
       >
         <div class="waterfall-mask" v-if="item.isLoading"></div>
         <div class="item-image">
-          <img :src="item.thumbnail" :alt="item.title" />
+          <img :src="getImageUrl(item)" :alt="item.title" />
         </div>
         <div class="item-info">
           <div class="item-title">
@@ -81,6 +81,7 @@
 <script setup>
 import { onMounted, ref, reactive } from "vue";
 import getBookCountApi from "@/api";
+import { addWatermarkToCollectionImage } from "@/utils/index.js";
 import downIcon from "@/assets/img/icon_down.png";
 import upIcon from "@/assets/img/icon_up.png";
 const imgUrl = import.meta.env.VITE_MODEL_URL;
@@ -178,7 +179,8 @@ const loadNewsData = async (isLoadMore = false) => {
       title: item.title || item.name,
       date: item.platformPublishTime,
       description: item.description,
-      thumbnail: `${imgUrl}${item.thumbnail}`,
+      image: `${imgUrl}${item.thumbnail}`,
+      originalImage: `${imgUrl}${item.thumbnail}`, // 保存原始图片地址
       content: item.content,
       contentType: item.contentType === '公众号链接' ? 1 : 2, // 默认富文本
 
@@ -232,6 +234,64 @@ const handleScroll = async () => {
   }
 };
 
+// 处理图片水印
+const watermarkedImages = ref(new Map()); // 存储处理后的水印图片
+const processImageWithWatermark = async (item) => {
+  
+  // 如果已经处理过,直接返回
+  if (watermarkedImages.value.has(item.id)) {
+    // console.log('图片已处理过,直接返回缓存结果');
+    return watermarkedImages.value.get(item.id);
+  }
+  
+  // 如果正在处理,避免重复处理
+  if (item.watermarkProcessing) {
+    return item.originalImage;
+  }
+  
+  try {
+    // 标记为处理中
+    item.watermarkProcessing = true;
+    // 处理水印
+    const watermarkedImageUrl = await addWatermarkToCollectionImage(item.originalImage, {
+      position: 'center',
+      opacity: 1,
+      scale: 0.7,
+      margin: 15
+    });
+    // 存储处理后的图片
+    watermarkedImages.value.set(item.id, watermarkedImageUrl);
+    // 更新图片地址
+    item.image = watermarkedImageUrl;
+    return watermarkedImageUrl;
+  } catch (error) {
+    console.error('水印处理失败:', error);
+    // 失败时使用原图
+    return item.originalImage;
+  } finally {
+    // 标记处理完成
+    item.watermarkProcessing = false;
+  }
+};
+// 获取图片地址(带水印处理)
+const getImageUrl = (item) => {
+  // console.log('getImageUrl被调用,item:', item.id);
+  
+  // 如果已经有水印图片,直接返回
+  if (watermarkedImages.value.has(item.id)) {
+    // console.log('返回已缓存的水印图片');
+    return watermarkedImages.value.get(item.id);
+  }
+  
+  // 异步处理水印
+  // console.log('开始异步处理水印');
+  processImageWithWatermark(item);
+  
+  // 先返回原图
+  // console.log('返回原图作为临时显示:', item.originalImage);
+  return item.originalImage;
+};
+
 onMounted(() => {
   console.log('公告组件已挂载');
 

+ 65 - 2
pc/src/views/collect/components/CollectionContent.vue

@@ -129,7 +129,7 @@
       >
         <div class="waterfall-mask" v-if="item.isLoading"></div>
         <div class="item-image">
-          <img :src="item.image" :alt="item.name" />
+          <img :src="getImageUrl(item)" :alt="item.name" />
         </div>
         <div class="item-info">
           <div class="item-name">{{ item.name }}</div>
@@ -154,6 +154,7 @@ import { useRoute, useRouter } from 'vue-router';
 import getBookCountApi from "@/api";
 import downIcon from "@/assets/img/icon_down.png";
 import upIcon from "@/assets/img/icon_up.png";
+import { addWatermarkToCollectionImage } from "@/utils/index.js";
 // 定义emit
 const emit = defineEmits(['show-details']);
 // Vuex store
@@ -187,6 +188,7 @@ const filters = reactive({
 // 前端分页相关数据
 const allArtifacts = ref([]); // 所有文物数据(API返回的完整数据集)
 const displayedArtifacts = ref([]); // 当前显示的文物数据(分页显示)
+const watermarkedImages = ref(new Map()); // 存储处理后的水印图片
 const currentPage = ref(1);
 const pageSize = 20; // 每页显示20条
 
@@ -232,6 +234,65 @@ const toggleSearch = () => {
   searchCollapsed.value = !searchCollapsed.value;
 };
 
+// 处理图片水印
+const processImageWithWatermark = async (item) => {
+  // console.log('开始处理图片水印,item:', item.id, item.originalImage);
+  // 如果已经处理过,直接返回
+  if (watermarkedImages.value.has(item.id)) {
+    // console.log('图片已处理过,直接返回缓存结果');
+    return watermarkedImages.value.get(item.id);
+  }
+  // 如果正在处理,避免重复处理
+  if (item.watermarkProcessing) {
+    // console.log('图片正在处理中,返回原图');
+    return item.originalImage;
+  }
+  try {
+    // 标记为处理中
+    item.watermarkProcessing = true;
+    // console.log('开始调用水印处理函数');
+    // 处理水印
+    const watermarkedImageUrl = await addWatermarkToCollectionImage(item.originalImage, {
+      position: 'center',
+      opacity: 1,
+      scale: 0.9,
+      margin: 15
+    });
+    // console.log('水印处理成功,结果URL:', watermarkedImageUrl);
+    // 存储处理后的图片
+    watermarkedImages.value.set(item.id, watermarkedImageUrl);
+    // 更新图片地址
+    item.image = watermarkedImageUrl;
+    return watermarkedImageUrl;
+  } catch (error) {
+    console.error('水印处理失败:', error);
+    // 失败时使用原图
+    return item.originalImage;
+  } finally {
+    // 标记处理完成
+    item.watermarkProcessing = false;
+  }
+};
+
+// 获取图片地址(带水印处理)
+const getImageUrl = (item) => {
+  // console.log('getImageUrl被调用,item:', item.id);
+  
+  // 如果已经有水印图片,直接返回
+  if (watermarkedImages.value.has(item.id)) {
+    // console.log('返回已缓存的水印图片');
+    return watermarkedImages.value.get(item.id);
+  }
+  
+  // 异步处理水印
+  // console.log('开始异步处理水印');
+  processImageWithWatermark(item);
+  
+  // 先返回原图
+  // console.log('返回原图作为临时显示:', item.originalImage);
+  return item.originalImage;
+};
+
 // 构建orderbyList的通用方法
 const buildOrderbyList = (orderByValue = searchForm.orderBy) => {
   let orderbyList = [];
@@ -446,9 +507,11 @@ const loadAllArtifacts = async (orderbyList) => {
       level: item.level || item.grade || '未定级',
       material: item.texture || '其他',
       image: `${imgUrl}${item.thumbnail}`,
+      originalImage: `${imgUrl}${item.thumbnail}`, // 保存原始图片地址
       hasImage: item.hasImage !== false,
       hasModel: item.hasModel !== false,
-      isLoading: false
+      isLoading: false,
+      watermarkProcessing: false // 水印处理状态
     }));
 
     allArtifacts.value = processedArtifacts;

+ 82 - 13
pc/src/views/collect/components/collectDetails.vue

@@ -53,9 +53,10 @@
             v-show="!showModel && currentDisplayArtifact.imgFiles && currentDisplayArtifact.imgFiles.length > 0"
             ref="imageCarouselRef"
             height="512px"
+            trigger="click"
             :autoplay="true"
             :interval="5000"
-            arrow="always"
+            :arrow="imageList.length > 1 ? 'always' : 'never'"
             indicator-position="outside"
             @change="handleImageCarouselChange"
           >
@@ -65,7 +66,7 @@
             >
               <div class="image-item">
                 <el-image
-                  :src="image.src"
+                  :src="getImageUrl(image)"
                   :alt="image.alt"
                   fit="contain"
                   loading="lazy"
@@ -253,6 +254,7 @@ import { useRoute } from 'vue-router';
 import { Close } from '@element-plus/icons-vue';
 import iconVoiceOff from '@/assets/img/icon_voiceoff.png';
 import iconVoiceOn from '@/assets/img/icon_voiceon.png';
+import { addWatermarkToCollectionImage } from "@/utils/index.js";
 const route = useRoute();
 const imguRL =  import.meta.env.VITE_COS_BASE_URL
 const modelUrl = import.meta.env.VITE_MODEL_URL
@@ -325,20 +327,27 @@ const imageCount = computed(() => {
 // 图片URL列表(用于预览器)
 const imageUrlList = computed(() => {
   const imgFiles = currentDisplayArtifact.value?.imgFiles || [];
-  return imgFiles.map(imgFile => `${imguRL}${imgFile}`);
+  return imgFiles.map((imgFile, index) => {
+    // 使用与imageList相同的ID生成方式,确保水印缓存一致性
+    const imageItem = imageList.value[index];
+    if (imageItem) {
+      // 获取带水印的图片URL,如果已处理过则返回水印图片,否则返回原图
+      const watermarkedUrl = getImageUrl(imageItem);
+      return watermarkedUrl;
+    }
+    // 如果imageList还没有生成,使用临时对象(保持ID一致性)
+    const item = {
+      id: `${currentDisplayArtifact.value.id}_image_${index}`, // 使用与imageList相同的ID生成逻辑
+      originalImage: `${imguRL}${imgFile}`,
+      src: `${imguRL}${imgFile}`
+    };
+    const watermarkedUrl = getImageUrl(item);
+    return watermarkedUrl;
+  });
 });
 
 const toggleDisplayMode = (isModel) => {
   showModel.value = isModel;
-  // if(isModel){
-  //   currentCarouselIndex.value = modelCount.value > 0 ? 1 : 0;
-  //   // 重置图片索引为1
-  //   currentImageIndex.value = imageCount.value > 0 ? 1 : 0;
-  // }else{
-  //   currentImageIndex.value = imageCount.value > 0 ? 1 : 0;
-  //   // 重置模型索引为1
-  //   currentCarouselIndex.value = modelCount.value > 0 ? 1 : 0;
-  // }
 };
 
 // 模型列表数据
@@ -353,15 +362,61 @@ const modelList = computed(() => {
     alt: `${currentDisplayArtifact.value.name} 3D模型${index + 1}`
   }));
 });
-console.log('modelList', modelList.value)
+
+// 处理图片水印
+const watermarkedImages = ref(new Map()); // 存储处理后的水印图片
+const processImageWithWatermark = async (item) => {
+  // 如果已经处理过,直接返回
+  if (watermarkedImages.value.has(item.id)) {
+    // console.log('图片已处理过,直接返回缓存结果');
+    return watermarkedImages.value.get(item.id);
+  }
+  // 如果正在处理,避免重复处理
+  if (item.watermarkProcessing) {
+    return item.originalImage;
+  }
+  try {
+    // 标记为处理中
+    item.watermarkProcessing = true;
+    // 处理水印
+    const watermarkedImageUrl = await addWatermarkToCollectionImage(item.originalImage, {
+      position: 'center',
+      opacity: 1,
+      scale: 0.7,
+      margin: 15
+    });
+    // 存储处理后的图片
+    watermarkedImages.value.set(item.id, watermarkedImageUrl);
+    // 更新图片地址
+    item.src = watermarkedImageUrl;
+    return watermarkedImageUrl;
+  } catch (error) {
+    console.error('水印处理失败:', error);
+    // 失败时使用原图
+    return item.originalImage;
+  } finally {
+    // 标记处理完成
+    item.watermarkProcessing = false;
+  }
+};
+// 获取图片地址(带水印处理)
+const getImageUrl = (item) => {
+  if (watermarkedImages.value.has(item.id)) {
+    return watermarkedImages.value.get(item.id);
+  }
+  processImageWithWatermark(item);
+  return item.originalImage;
+};
 // 图片列表数据
 const imageList = computed(() => {
   if (!currentDisplayArtifact.value) return [];
 
   const imgFiles = currentDisplayArtifact.value.imgFiles || [];
   return imgFiles.map((imgFile, index) => ({
+    id: `${currentDisplayArtifact.value.id}_image_${index}`, // 使用文物ID + 图片索引作为唯一ID
     type: 'image',
     src: `${imguRL}${imgFile}`,
+    originalImage: `${imguRL}${imgFile}`,
     alt: `${currentDisplayArtifact.value.name} 图片${index + 1}`
   }));
 });
@@ -450,6 +505,20 @@ const navigateArtifact = (direction) => {
 
   const newArtifact = effectiveArtifactList.value[newIndex];
 
+  // 清理当前文物的水印缓存,避免内存泄漏
+  const currentArtifactId = currentDisplayArtifact.value?.id;
+  if (currentArtifactId) {
+    // 清理当前文物相关的水印缓存
+    const keysToDelete = [];
+    watermarkedImages.value.forEach((value, key) => {
+      if (key.startsWith(`${currentArtifactId}_image_`)) {
+        keysToDelete.push(key);
+      }
+    });
+    keysToDelete.forEach(key => watermarkedImages.value.delete(key));
+    // console.log(`清理了 ${keysToDelete.length} 个水印缓存`);
+  }
+
   // 重置详情数据,触发新的详情加载
   artifactDetail.value = null;
 

+ 22 - 0
pc/src/views/collect/index.vue

@@ -17,6 +17,11 @@
       <div class="top-title" @click="goHome">
         <img src="@/assets/img/title.png" alt="标题" />
       </div>
+      <!-- 首页图标 -->
+      <div class="home-icon" @click="goHome">
+        <img src="@/assets/img/icon_home.png" alt="首页" />
+        首页
+      </div>
     </div>
 
     <!-- 主要内容区域 -->
@@ -306,6 +311,23 @@ onUnmounted(() => {
       height: auto;
     }
   }
+  .home-icon{
+    position: absolute;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+    top: 20px;
+    left: 100px;
+    z-index: 1000;
+    cursor: pointer;
+    color: #B49D7E;
+
+    img{
+      width: 25px;
+      height: 25px;
+    }
+  }
 }
 
 .main-container {