forked from angelo/web-retail-h5
				
			
		
			
	
	
		
			502 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Vue
		
	
	
	
		
		
			
		
	
	
			502 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Vue
		
	
	
	
|  | <template> | |||
|  |   <view ref="specialSharePage" class="special-share-page"> | |||
|  |     <!-- 特殊场景背景图片 --> | |||
|  |     <image | |||
|  |       class="share-bg-image" | |||
|  |       :src="specialBackgroundImage" | |||
|  |       mode="scaleToFill" | |||
|  |       crossorigin="anonymous" | |||
|  |       @load="onBackgroundImageLoad" | |||
|  |       @error="onBackgroundImageError" | |||
|  |       v-if="specialBackgroundImage" | |||
|  |     ></image> | |||
|  | 
 | |||
|  |     <!-- 加载状态 --> | |||
|  |     <view | |||
|  |       class="loading-container" | |||
|  |       v-if="!specialBackgroundImage || isLoadingBackground" | |||
|  |     > | |||
|  |       <view class="loader"></view> | |||
|  |       <text class="loading-text">{{ | |||
|  |         isLoadingBackground ? '正在获取背景图片...' : '正在加载分享背景...' | |||
|  |       }}</text> | |||
|  |     </view> | |||
|  | 
 | |||
|  |     <view | |||
|  |       class="share-wrapper" | |||
|  |       v-if="specialBackgroundImage && !isLoadingBackground" | |||
|  |     > | |||
|  |       <view class="portal-frame" :class="{ 'is-loaded': isLoaded }"> | |||
|  |         <view class="qr-code-outer special-qr-style"> | |||
|  |           <image | |||
|  |             class="qr-code" | |||
|  |             :src="qrCodeImage" | |||
|  |             mode="aspectFit" | |||
|  |             v-if="qrCodeImage" | |||
|  |           ></image> | |||
|  |           <view v-else class="qr-code-placeholder"> | |||
|  |             <view class="loader"></view> | |||
|  |           </view> | |||
|  |         </view> | |||
|  | 
 | |||
|  |         <text | |||
|  |           class="member-code-text special-member-code-style" | |||
|  |           style=" | |||
|  |             font-size: 30rpx; | |||
|  |             color: #005bac; | |||
|  |             font-weight: bold; | |||
|  |             margin-top: 20rpx; | |||
|  |           " | |||
|  |           >{{ desensitization(userInfo.memberCode) }}</text | |||
|  |         > | |||
|  |       </view> | |||
|  |     </view> | |||
|  |   </view> | |||
|  | </template> | |||
|  | 
 | |||
|  | <script> | |||
|  | import { snapdom } from '@zumer/snapdom' | |||
|  | import html2canvas from 'html2canvas' | |||
|  | import { getSharedImg } from '@/config/login' | |||
|  | export default { | |||
|  |   name: 'SpecialSharePage', | |||
|  |   props: { | |||
|  |     qrCodeImage: { | |||
|  |       type: String, | |||
|  |       default: '', | |||
|  |     }, | |||
|  |     userInfo: { | |||
|  |       type: Object, | |||
|  |       default: () => ({}), | |||
|  |     }, | |||
|  |     isWechat: { | |||
|  |       type: Boolean, | |||
|  |       default: false, | |||
|  |     }, | |||
|  |     isLoaded: { | |||
|  |       type: Boolean, | |||
|  |       default: false, | |||
|  |     }, | |||
|  |     // 移除specialBackgroundImage prop,改为接口获取
 | |||
|  |     // 调试用:跳过图片验证
 | |||
|  |     skipImageVerification: { | |||
|  |       type: Boolean, | |||
|  |       default: false, | |||
|  |     }, | |||
|  |   }, | |||
|  |   data() { | |||
|  |     return { | |||
|  |       backgroundImageLoaded: false, | |||
|  |       imageLoadRetryCount: 0, | |||
|  |       maxRetryCount: 3, | |||
|  |       specialBackgroundImage: '', // 存储接口返回的背景图
 | |||
|  |       isLoadingBackground: false, // 是否正在加载背景图
 | |||
|  |     } | |||
|  |   }, | |||
|  |   created() { | |||
|  |     this.getBackgroundImage() | |||
|  |   }, | |||
|  |   methods: { | |||
|  |     desensitization(str) { | |||
|  |       if (!str) return '' | |||
|  |       if (str.length <= 8) return str.slice(0, 4) + '****' | |||
|  |       const len = str.length - 6 | |||
|  |       const placeholder = '*'.repeat(len) | |||
|  |       return str.slice(0, 4) + placeholder + str.slice(-2) | |||
|  |     }, | |||
|  | 
 | |||
|  |     // 背景图片加载成功
 | |||
|  |     onBackgroundImageLoad() { | |||
|  |       this.backgroundImageLoaded = true | |||
|  |       console.log('特殊场景背景图片加载成功') | |||
|  |     }, | |||
|  | 
 | |||
|  |     // 背景图片加载失败
 | |||
|  |     onBackgroundImageError(e) { | |||
|  |       console.error('特殊场景背景图片加载失败:', e) | |||
|  |       this.backgroundImageLoaded = false | |||
|  |       this.imageLoadRetryCount++ | |||
|  | 
 | |||
|  |       // 如果重试次数未达到上限,可以通知父组件重试
 | |||
|  |       if (this.imageLoadRetryCount < this.maxRetryCount) { | |||
|  |         console.log( | |||
|  |           `图片加载失败,重试中 (${this.imageLoadRetryCount}/${this.maxRetryCount})` | |||
|  |         ) | |||
|  |         this.$emit('background-image-retry', this.imageLoadRetryCount) | |||
|  |       } else { | |||
|  |         this.$emit('background-image-error', e) | |||
|  |       } | |||
|  |     }, | |||
|  | 
 | |||
|  |     // 获取特殊场景背景图片
 | |||
|  |     async getBackgroundImage() { | |||
|  |       if (this.skipImageVerification) { | |||
|  |         this.specialBackgroundImage = '' // 跳过验证时清空背景图
 | |||
|  |         this.backgroundImageLoaded = true | |||
|  |         return | |||
|  |       } | |||
|  | 
 | |||
|  |       if (this.specialBackgroundImage) { | |||
|  |         // 如果背景图已加载,则直接使用
 | |||
|  |         this.backgroundImageLoaded = true | |||
|  |         return | |||
|  |       } | |||
|  | 
 | |||
|  |       this.isLoadingBackground = true | |||
|  |       try { | |||
|  |         const result = await getSharedImg() | |||
|  |         if (result && result.code === 200 && result.data) { | |||
|  |           this.specialBackgroundImage = | |||
|  |             'data:image/png;base64,' + result.data.base64 | |||
|  |           this.backgroundImageLoaded = true | |||
|  |         } else { | |||
|  |           this.backgroundImageLoaded = false | |||
|  |           this.imageLoadRetryCount++ | |||
|  |         } | |||
|  |       } catch (error) { | |||
|  |         this.backgroundImageLoaded = false | |||
|  |         this.imageLoadRetryCount++ | |||
|  |         if (this.imageLoadRetryCount < this.maxRetryCount) { | |||
|  |           this.$emit('background-image-retry', this.imageLoadRetryCount) | |||
|  |         } else { | |||
|  |           this.$emit('background-image-error', error) | |||
|  |         } | |||
|  |       } finally { | |||
|  |         this.isLoadingBackground = false | |||
|  |       } | |||
|  |       return this.specialBackgroundImage | |||
|  |     }, | |||
|  | 
 | |||
|  |     async generateShareImage() { | |||
|  |       // await this.getBackgroundImage()
 | |||
|  |       try { | |||
|  |         this.$nextTick(() => { | |||
|  |           const info = uni.getSystemInfoSync() | |||
|  |           console.log(info.platform, '.....info') | |||
|  |           if (info.platform === 'ios') { | |||
|  |             console.log('iOS detected, using html2canvas for capturing.') | |||
|  |             this.capturePageWithHtml2Canvas() | |||
|  |           } else { | |||
|  |             console.log('Non-iOS detected, using snapdom for capturing.') | |||
|  |             this.capturePageWithSnapDom() | |||
|  |           } | |||
|  |         }) | |||
|  |       } catch (error) { | |||
|  |         uni.hideLoading() | |||
|  |         uni.showToast({ title: '图片生成失败,请稍后重试', icon: 'none' }) | |||
|  |       } | |||
|  |     }, | |||
|  | 
 | |||
|  |     // 新增:使用html2canvas截取整个页面 (For iOS)
 | |||
|  |     async capturePageWithHtml2Canvas() { | |||
|  |       return new Promise(async (resolve, reject) => { | |||
|  |         // 确保所有图片都已加载完成
 | |||
|  |         const waitForImages = async () => { | |||
|  |           // 检查是否正在加载背景图片
 | |||
|  |           if (this.isLoadingBackground) { | |||
|  |             console.log('等待背景图片接口调用完成...') | |||
|  |             setTimeout(waitForImages, 100) | |||
|  |             return | |||
|  |           } | |||
|  | 
 | |||
|  |           // 检查背景图片是否加载完成
 | |||
|  |           if (!this.backgroundImageLoaded) { | |||
|  |             console.log('等待背景图片加载完成...') | |||
|  |             setTimeout(waitForImages, 100) | |||
|  |             return | |||
|  |           } | |||
|  | 
 | |||
|  |           // 检查二维码是否存在
 | |||
|  |           if (!this.qrCodeImage) { | |||
|  |             console.log('等待二维码生成...') | |||
|  |             setTimeout(waitForImages, 100) | |||
|  |             return | |||
|  |           } | |||
|  | 
 | |||
|  |           // 检查特殊背景图片参数是否存在
 | |||
|  |           if (!this.specialBackgroundImage) { | |||
|  |             console.log('等待特殊背景图片参数...') | |||
|  |             setTimeout(waitForImages, 100) | |||
|  |             return | |||
|  |           } | |||
|  | 
 | |||
|  |           console.log('所有图片准备就绪,开始使用html2canvas截图...') | |||
|  | 
 | |||
|  |           // 等待确保渲染完成
 | |||
|  |           setTimeout(() => { | |||
|  |             const element = this.$el | |||
|  |             if (!element) { | |||
|  |               uni.hideLoading() | |||
|  |               reject(new Error('找不到组件容器')) | |||
|  |               return | |||
|  |             } | |||
|  | 
 | |||
|  |             html2canvas(element, { | |||
|  |               useCORS: true, | |||
|  |               allowTaint: true, | |||
|  |               backgroundColor: null, | |||
|  |               scale: 2, // 提高清晰度
 | |||
|  |               dpi: 300, | |||
|  |               width: element.offsetWidth, | |||
|  |               height: element.offsetHeight, | |||
|  |             }) | |||
|  |               .then(canvas => { | |||
|  |                 const dataUrl = canvas.toDataURL('image/jpeg', 1.0) | |||
|  |                 uni.hideLoading() | |||
|  |                 // this.handleShareGenerated(dataUrl)
 | |||
|  |                 this.$emit('share-generated', dataUrl) | |||
|  |                 resolve() | |||
|  |               }) | |||
|  |               .catch(err => { | |||
|  |                 uni.hideLoading() | |||
|  |                 console.error('html2canvas截取失败:', err) | |||
|  |                 uni.showToast({ | |||
|  |                   title: '图片生成失败,请稍后重试', | |||
|  |                   icon: 'none', | |||
|  |                 }) | |||
|  |                 reject(err) | |||
|  |               }) | |||
|  |           }, 1000) | |||
|  |         } | |||
|  | 
 | |||
|  |         // 开始等待图片加载
 | |||
|  |         waitForImages() | |||
|  |       }) | |||
|  |     }, | |||
|  | 
 | |||
|  |     // 使用snapDOM截取整个页面
 | |||
|  |     async capturePageWithSnapDom() { | |||
|  |       return new Promise(async (resolve, reject) => { | |||
|  |         // 确保所有图片都已加载完成
 | |||
|  |         const waitForImages = async () => { | |||
|  |           // 检查是否正在加载背景图片
 | |||
|  |           if (this.isLoadingBackground) { | |||
|  |             console.log('等待背景图片接口调用完成...') | |||
|  |             setTimeout(waitForImages, 100) | |||
|  |             return | |||
|  |           } | |||
|  | 
 | |||
|  |           // 检查背景图片是否加载完成
 | |||
|  |           if (!this.backgroundImageLoaded) { | |||
|  |             console.log('等待背景图片加载完成...') | |||
|  |             setTimeout(waitForImages, 100) | |||
|  |             return | |||
|  |           } | |||
|  | 
 | |||
|  |           // 检查二维码是否存在
 | |||
|  |           if (!this.qrCodeImage) { | |||
|  |             console.log('等待二维码生成...') | |||
|  |             setTimeout(waitForImages, 100) | |||
|  |             return | |||
|  |           } | |||
|  | 
 | |||
|  |           // 检查特殊背景图片参数是否存在
 | |||
|  |           if (!this.specialBackgroundImage) { | |||
|  |             console.log('等待特殊背景图片参数...') | |||
|  |             setTimeout(waitForImages, 100) | |||
|  |             return | |||
|  |           } | |||
|  | 
 | |||
|  |           console.log('所有图片准备就绪,开始截图...') | |||
|  | 
 | |||
|  |           // 等待确保渲染完成
 | |||
|  |           setTimeout(async () => { | |||
|  |             const element = this.$el | |||
|  |             if (!element) { | |||
|  |               reject(new Error('找不到组件容器')) | |||
|  |               return | |||
|  |             } | |||
|  | 
 | |||
|  |             try { | |||
|  |               // 获取元素的所有可能尺寸
 | |||
|  |               const rect = element.getBoundingClientRect() | |||
|  |               const offsetWidth = element.offsetWidth | |||
|  |               const offsetHeight = element.offsetHeight | |||
|  |               const scrollWidth = element.scrollWidth | |||
|  |               const scrollHeight = element.scrollHeight | |||
|  |               const clientWidth = element.clientWidth | |||
|  |               const clientHeight = element.clientHeight | |||
|  | 
 | |||
|  |               // 计算实际需要的尺寸(取最大值确保完整)
 | |||
|  |               const elementWidth = Math.max( | |||
|  |                 offsetWidth, | |||
|  |                 scrollWidth, | |||
|  |                 clientWidth, | |||
|  |                 rect.width | |||
|  |               ) | |||
|  |               const elementHeight = Math.max( | |||
|  |                 offsetHeight, | |||
|  |                 scrollHeight, | |||
|  |                 clientHeight, | |||
|  |                 rect.height | |||
|  |               ) | |||
|  | 
 | |||
|  |               setTimeout(async () => { | |||
|  |                 try { | |||
|  |                   const result = await snapdom(element, { | |||
|  |                     width: elementWidth, | |||
|  |                     height: elementHeight, | |||
|  |                     quality: 1, | |||
|  |                     compress: false, | |||
|  | 
 | |||
|  |                     // useProxy: true,
 | |||
|  |                   }) | |||
|  |                   const canvas = await result.toCanvas() | |||
|  | 
 | |||
|  |                   const dataUrl = canvas.toDataURL('image/jpeg') | |||
|  | 
 | |||
|  |                   uni.hideLoading() | |||
|  |                   // this.handleShareGenerated(dataUrl)
 | |||
|  |                   this.$emit('share-generated', dataUrl) | |||
|  |                   resolve() | |||
|  |                 } catch (err) { | |||
|  |                   uni.hideLoading() | |||
|  |                   console.error('snapDOM截取失败:', err) | |||
|  |                   uni.showToast({ | |||
|  |                     title: '图片生成失败,请稍后重试', | |||
|  |                     icon: 'none', | |||
|  |                   }) | |||
|  |                   reject(err) | |||
|  |                 } | |||
|  |               }, 500) | |||
|  |             } catch (err) { | |||
|  |               uni.hideLoading() | |||
|  |               console.error('snapDOM初始化失败:', err) | |||
|  |               uni.showToast({ | |||
|  |                 title: '图片生成失败,请稍后重试', | |||
|  |                 icon: 'none', | |||
|  |               }) | |||
|  |               reject(err) | |||
|  |             } | |||
|  |           }, 1000) | |||
|  |         } | |||
|  | 
 | |||
|  |         // 开始等待图片加载
 | |||
|  |         waitForImages() | |||
|  |       }) | |||
|  |     }, | |||
|  |   }, | |||
|  | } | |||
|  | </script> | |||
|  | 
 | |||
|  | <style lang="scss" scoped> | |||
|  | .special-share-page { | |||
|  |   position: relative; | |||
|  |   width: 100%; | |||
|  |   height: 100%; | |||
|  |   display: flex; | |||
|  |   flex-direction: column; | |||
|  |   align-items: center; | |||
|  | } | |||
|  | 
 | |||
|  | /* 背景图片样式 */ | |||
|  | .share-bg-image { | |||
|  |   position: absolute; | |||
|  |   top: 0; | |||
|  |   left: 0; | |||
|  |   width: 100%; | |||
|  |   height: 100%; | |||
|  |   z-index: 1; | |||
|  | } | |||
|  | 
 | |||
|  | /* 加载状态 */ | |||
|  | .loading-container { | |||
|  |   position: absolute; | |||
|  |   top: 50%; | |||
|  |   left: 50%; | |||
|  |   transform: translate(-50%, -50%); | |||
|  |   display: flex; | |||
|  |   flex-direction: column; | |||
|  |   align-items: center; | |||
|  |   z-index: 2; | |||
|  | } | |||
|  | 
 | |||
|  | .loading-text { | |||
|  |   margin-top: 20rpx; | |||
|  |   font-size: 28rpx; | |||
|  |   color: #666; | |||
|  | } | |||
|  | 
 | |||
|  | .share-wrapper { | |||
|  |   position: absolute; | |||
|  |   z-index: 2; | |||
|  |   bottom: 22%; | |||
|  |   display: flex; | |||
|  |   flex-direction: column; | |||
|  |   align-items: center; | |||
|  | } | |||
|  | 
 | |||
|  | .portal-frame { | |||
|  |   padding: 32rpx; | |||
|  |   width: 520rpx; | |||
|  |   border-radius: 40rpx; | |||
|  |   display: flex; | |||
|  |   box-sizing: border-box; | |||
|  |   flex-direction: column; | |||
|  |   align-items: center; | |||
|  |   margin: 0 auto; | |||
|  |   opacity: 0; | |||
|  |   transform: translateY(20rpx); | |||
|  |   transition: all 0.3s ease; | |||
|  | } | |||
|  | 
 | |||
|  | .portal-frame.is-loaded { | |||
|  |   opacity: 1; | |||
|  |   transform: translateY(0); | |||
|  | } | |||
|  | 
 | |||
|  | /* 特殊场景二维码样式区域 - 用户自定义样式位置 */ | |||
|  | .qr-code-outer { | |||
|  |   width: 400rpx; | |||
|  |   height: 400rpx; | |||
|  |   border-radius: 20rpx; | |||
|  |   display: flex; | |||
|  |   align-items: center; | |||
|  |   justify-content: center; | |||
|  |   padding: 12rpx; | |||
|  |   box-sizing: border-box; | |||
|  | } | |||
|  | 
 | |||
|  | .qr-code, | |||
|  | .qr-code-placeholder { | |||
|  |   width: 100%; | |||
|  |   height: 100%; | |||
|  | } | |||
|  | 
 | |||
|  | .qr-code { | |||
|  |   border-radius: 16rpx; | |||
|  | } | |||
|  | 
 | |||
|  | .qr-code-placeholder { | |||
|  |   display: flex; | |||
|  |   align-items: center; | |||
|  |   justify-content: center; | |||
|  | } | |||
|  | 
 | |||
|  | .loader { | |||
|  |   width: 100rpx; | |||
|  |   height: 100rpx; | |||
|  |   border: 8rpx solid rgba(0, 0, 0, 0.1); | |||
|  |   border-left-color: #0072ff; | |||
|  |   border-radius: 50%; | |||
|  |   animation: spin 1s linear infinite; | |||
|  | } | |||
|  | 
 | |||
|  | @keyframes spin { | |||
|  |   to { | |||
|  |     transform: rotate(360deg); | |||
|  |   } | |||
|  | } | |||
|  | 
 | |||
|  | /* 特殊场景会员编号样式区域 - 用户自定义样式位置 */ | |||
|  | .member-code-text { | |||
|  |   /* 用户可以在这里自定义会员编号的样式 */ | |||
|  | } | |||
|  | 
 | |||
|  | /* 特殊场景下的会员编号样式 */ | |||
|  | .special-member-code-style { | |||
|  |   /* 用户可以在这里自定义特殊场景下的会员编号样式 */ | |||
|  |   /* 例如:不同的位置、颜色、字体等 */ | |||
|  | } | |||
|  | </style> |