Compare commits
	
		
			3 Commits
		
	
	
		
			2f37181c1d
			...
			3e3399c1ed
		
	
	| Author | SHA1 | Date | 
|---|---|---|
|  | 3e3399c1ed | |
|  | 1ea89b0c68 | |
|  | f0ba57a7a4 | 
|  | @ -240,4 +240,33 @@ export default { | ||||||
|     // margin-bottom: 30px; |     // margin-bottom: 30px; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /* iPhone安全区域适配 */ | ||||||
|  | 
 | ||||||
|  | /* 绿色主题的安全区域适配 */ | ||||||
|  | .greenEd { | ||||||
|  |   ::v-deep .u-tabbar__content { | ||||||
|  |     background: linear-gradient(to bottom, #fff, #b6fdda); | ||||||
|  | 
 | ||||||
|  |     /* 绿色主题的安全区域背景 */ | ||||||
|  |     &::after { | ||||||
|  |       background: #b6fdda; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* 针对不同屏幕尺寸的优化 */ | ||||||
|  | @media screen and (max-height: 667px) { | ||||||
|  |   /* iPhone SE等小屏设备 */ | ||||||
|  |   ::v-deep .u-tabbar__content__item-wrapper { | ||||||
|  |     height: 70px; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @media screen and (min-height: 812px) { | ||||||
|  |   /* iPhone X及以上机型 */ | ||||||
|  |   ::v-deep .u-tabbar__content__item-wrapper { | ||||||
|  |     height: 80px; | ||||||
|  |   } | ||||||
|  | } | ||||||
| </style> | </style> | ||||||
|  |  | ||||||
|  | @ -19,6 +19,7 @@ | ||||||
|         "qrcodejs2": "0.0.2", |         "qrcodejs2": "0.0.2", | ||||||
|         "swiper": "^3.4.2", |         "swiper": "^3.4.2", | ||||||
|         "uqrcodejs": "^4.0.7", |         "uqrcodejs": "^4.0.7", | ||||||
|  |         "vconsole": "^3.15.1", | ||||||
|         "vue-clipboard2": "^0.3.3", |         "vue-clipboard2": "^0.3.3", | ||||||
|         "vue-i18n": "^9.2.2", |         "vue-i18n": "^9.2.2", | ||||||
|         "vue-tree-color": "^2.3.2", |         "vue-tree-color": "^2.3.2", | ||||||
|  | @ -64,6 +65,15 @@ | ||||||
|         "node": ">=6.0.0" |         "node": ">=6.0.0" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "node_modules/@babel/runtime": { | ||||||
|  |       "version": "7.27.6", | ||||||
|  |       "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz", | ||||||
|  |       "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==", | ||||||
|  |       "license": "MIT", | ||||||
|  |       "engines": { | ||||||
|  |         "node": ">=6.9.0" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "node_modules/@babel/types": { |     "node_modules/@babel/types": { | ||||||
|       "version": "7.27.0", |       "version": "7.27.0", | ||||||
|       "resolved": "https://mirrors.cloud.tencent.com/npm/@babel/types/-/types-7.27.0.tgz", |       "resolved": "https://mirrors.cloud.tencent.com/npm/@babel/types/-/types-7.27.0.tgz", | ||||||
|  | @ -1821,6 +1831,29 @@ | ||||||
|         "node": ">=0.10.0" |         "node": ">=0.10.0" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "node_modules/copy-text-to-clipboard": { | ||||||
|  |       "version": "3.2.0", | ||||||
|  |       "resolved": "https://registry.npmjs.org/copy-text-to-clipboard/-/copy-text-to-clipboard-3.2.0.tgz", | ||||||
|  |       "integrity": "sha512-RnJFp1XR/LOBDckxTib5Qjr/PMfkatD0MUCQgdpqS8MdKiNUzBjAQBEN6oUy+jW7LI93BBG3DtMB2KOOKpGs2Q==", | ||||||
|  |       "license": "MIT", | ||||||
|  |       "engines": { | ||||||
|  |         "node": ">=12" | ||||||
|  |       }, | ||||||
|  |       "funding": { | ||||||
|  |         "url": "https://github.com/sponsors/sindresorhus" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "node_modules/core-js": { | ||||||
|  |       "version": "3.43.0", | ||||||
|  |       "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.43.0.tgz", | ||||||
|  |       "integrity": "sha512-N6wEbTTZSYOY2rYAn85CuvWWkCK6QweMn7/4Nr3w+gDBeBhk/x4EJeY6FPo4QzDoJZxVTv8U7CMvgWk6pOHHqA==", | ||||||
|  |       "hasInstallScript": true, | ||||||
|  |       "license": "MIT", | ||||||
|  |       "funding": { | ||||||
|  |         "type": "opencollective", | ||||||
|  |         "url": "https://opencollective.com/core-js" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "node_modules/core-util-is": { |     "node_modules/core-util-is": { | ||||||
|       "version": "1.0.3", |       "version": "1.0.3", | ||||||
|       "resolved": "https://mirrors.cloud.tencent.com/npm/core-util-is/-/core-util-is-1.0.3.tgz", |       "resolved": "https://mirrors.cloud.tencent.com/npm/core-util-is/-/core-util-is-1.0.3.tgz", | ||||||
|  | @ -4357,6 +4390,11 @@ | ||||||
|       "dev": true, |       "dev": true, | ||||||
|       "peer": true |       "peer": true | ||||||
|     }, |     }, | ||||||
|  |     "node_modules/mutation-observer": { | ||||||
|  |       "version": "1.0.3", | ||||||
|  |       "resolved": "https://registry.npmjs.org/mutation-observer/-/mutation-observer-1.0.3.tgz", | ||||||
|  |       "integrity": "sha512-M/O/4rF2h776hV7qGMZUH3utZLO/jK7p8rnNgGkjKUw8zCGjRQPxB8z6+5l8+VjRUQ3dNYu4vjqXYLr+U8ZVNA==" | ||||||
|  |     }, | ||||||
|     "node_modules/nan": { |     "node_modules/nan": { | ||||||
|       "version": "2.22.2", |       "version": "2.22.2", | ||||||
|       "resolved": "https://mirrors.cloud.tencent.com/npm/nan/-/nan-2.22.2.tgz", |       "resolved": "https://mirrors.cloud.tencent.com/npm/nan/-/nan-2.22.2.tgz", | ||||||
|  | @ -6469,6 +6507,18 @@ | ||||||
|         "base64-arraybuffer": "^1.0.2" |         "base64-arraybuffer": "^1.0.2" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "node_modules/vconsole": { | ||||||
|  |       "version": "3.15.1", | ||||||
|  |       "resolved": "https://registry.npmjs.org/vconsole/-/vconsole-3.15.1.tgz", | ||||||
|  |       "integrity": "sha512-KH8XLdrq9T5YHJO/ixrjivHfmF2PC2CdVoK6RWZB4yftMykYIaXY1mxZYAic70vADM54kpMQF+dYmvl5NRNy1g==", | ||||||
|  |       "license": "MIT", | ||||||
|  |       "dependencies": { | ||||||
|  |         "@babel/runtime": "^7.17.2", | ||||||
|  |         "copy-text-to-clipboard": "^3.0.1", | ||||||
|  |         "core-js": "^3.11.0", | ||||||
|  |         "mutation-observer": "^1.0.3" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "node_modules/vm-browserify": { |     "node_modules/vm-browserify": { | ||||||
|       "version": "1.1.2", |       "version": "1.1.2", | ||||||
|       "resolved": "https://mirrors.cloud.tencent.com/npm/vm-browserify/-/vm-browserify-1.1.2.tgz", |       "resolved": "https://mirrors.cloud.tencent.com/npm/vm-browserify/-/vm-browserify-1.1.2.tgz", | ||||||
|  |  | ||||||
|  | @ -14,6 +14,7 @@ | ||||||
|     "qrcodejs2": "0.0.2", |     "qrcodejs2": "0.0.2", | ||||||
|     "swiper": "^3.4.2", |     "swiper": "^3.4.2", | ||||||
|     "uqrcodejs": "^4.0.7", |     "uqrcodejs": "^4.0.7", | ||||||
|  |     "vconsole": "^3.15.1", | ||||||
|     "vue-clipboard2": "^0.3.3", |     "vue-clipboard2": "^0.3.3", | ||||||
|     "vue-i18n": "^9.2.2", |     "vue-i18n": "^9.2.2", | ||||||
|     "vue-tree-color": "^2.3.2", |     "vue-tree-color": "^2.3.2", | ||||||
|  |  | ||||||
|  | @ -30,12 +30,18 @@ | ||||||
|                 userInfo.pkMaxAwardsVal || userInfo.pkAwardsVal || '无' |                 userInfo.pkMaxAwardsVal || userInfo.pkAwardsVal || '无' | ||||||
|               }}</text> |               }}</text> | ||||||
|             </view> |             </view> | ||||||
|             <view class="award-tag"> |             <view style="display: flex; gap: 10rpx"> | ||||||
|               <!-- <u-icon name="star-fill" color="#FAD65A" size="14"></u-icon> --> |               <view class="award-tag"> | ||||||
|               <text class="award-label">当月奖衔:</text> |                 <!-- <u-icon name="star-fill" color="#FAD65A" size="14"></u-icon> --> | ||||||
|               <text class="award-value">{{ |                 <text class="award-label">当月奖衔:</text> | ||||||
|                 userInfo.pkAwardsVal || '无' |                 <text class="award-value">{{ | ||||||
|               }}</text> |                   userInfo.pkAwardsVal || '无' | ||||||
|  |                 }}</text> | ||||||
|  |               </view> | ||||||
|  |               <view v-if="userInfo.pkRangeAwardsVal" class="award-tag"> | ||||||
|  |                 <text class="award-label">分红奖衔:</text> | ||||||
|  |                 <text class="award-value">{{ userInfo.pkRangeAwardsVal }}</text> | ||||||
|  |               </view> | ||||||
|             </view> |             </view> | ||||||
|           </view> |           </view> | ||||||
|         </view> |         </view> | ||||||
|  | @ -402,13 +408,13 @@ export default { | ||||||
|           menuKey: 'selfHelp', |           menuKey: 'selfHelp', | ||||||
|           ifshow: true, |           ifshow: true, | ||||||
|         }, |         }, | ||||||
|         { |         // { | ||||||
|           url: '/pages/mine/share/index', |         //   url: '/pages/mine/share/index', | ||||||
|           name: '个人推广', |         //   name: '个人推广', | ||||||
|           imgurl: '../../static/images/list.svg', |         //   imgurl: '../../static/images/list.svg', | ||||||
|           menuKey: 'share', |         //   menuKey: 'share', | ||||||
|           ifshow: false, |         //   ifshow: false, | ||||||
|         }, |         // }, | ||||||
|         { |         { | ||||||
|           url: '/pages/userSecure/index', |           url: '/pages/userSecure/index', | ||||||
|           name: '账号安全', |           name: '账号安全', | ||||||
|  | @ -1017,7 +1023,7 @@ export default { | ||||||
|   padding: 40rpx 0; |   padding: 40rpx 0; | ||||||
|   color: #ffffff; |   color: #ffffff; | ||||||
|   position: relative; |   position: relative; | ||||||
|   padding-bottom: 30rpx; // Add some bottom padding |   padding-bottom: 0; // Add some bottom padding | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .user-info-wrapper { | .user-info-wrapper { | ||||||
|  | @ -1290,6 +1296,7 @@ export default { | ||||||
|   box-shadow: 0rpx 6rpx 24rpx rgba(0, 0, 0, 0.06); |   box-shadow: 0rpx 6rpx 24rpx rgba(0, 0, 0, 0.06); | ||||||
|   border-radius: 24rpx; |   border-radius: 24rpx; | ||||||
|   padding: 20rpx; |   padding: 20rpx; | ||||||
|  |   margin-top: 20rpx; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .info-grid { | .info-grid { | ||||||
|  |  | ||||||
|  | @ -1,37 +1,70 @@ | ||||||
| <template> | <template> | ||||||
|   <view id="shareContainer" class="share-container"> |   <view | ||||||
|     <!-- Portal Frame: The main content to be saved. --> |     class="share-page" | ||||||
|     <view class="portal-frame" :class="{ 'is-loaded': isLoaded }"> |     style="display: flex; flex-direction: column; height: 100vh" | ||||||
|       <!-- The single, robust QR Code card --> |   > | ||||||
|       <view class="qr-code-outer"> |     <view id="shareContainer" class="share-container"> | ||||||
|  |       <!-- 微信环境:只有在未生成分享图时才显示原始内容 --> | ||||||
|  |       <template v-if="!isWechat || (isWechat && !generatedImageUrl)"> | ||||||
|  |         <!-- 背景图片,替代CSS background --> | ||||||
|         <image |         <image | ||||||
|           class="qr-code" |           class="share-bg-image" | ||||||
|           :src="qrCodeImage" |           src="/static/images/share-bg.jpg" | ||||||
|           mode="aspectFit" |           mode="scaleToFill" | ||||||
|           v-if="qrCodeImage" |           crossorigin="anonymous" | ||||||
|  |           @load="onBackgroundImageLoad" | ||||||
|  |           @error="onBackgroundImageError" | ||||||
|         ></image> |         ></image> | ||||||
|         <view v-else class="qr-code-placeholder"> |         <view class="share-wrapper"> | ||||||
|           <view class="loader"></view> |           <view class="portal-frame" :class="{ 'is-loaded': isLoaded }"> | ||||||
|  |             <view class="qr-code-outer"> | ||||||
|  |               <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 | ||||||
|  |               style=" | ||||||
|  |                 font-size: 30rpx; | ||||||
|  |                 color: #fff; | ||||||
|  |                 font-weight: bold; | ||||||
|  |                 margin-top: 20rpx; | ||||||
|  |               " | ||||||
|  |               >{{ desensitization(userInfo.memberCode) }}</text | ||||||
|  |             > | ||||||
|  |             <button class="share-button" @click="sharePage"> | ||||||
|  |               {{ isWechat ? '长按保存图片' : '保存图片并分享' }} | ||||||
|  |             </button> | ||||||
|  |           </view> | ||||||
|  |           <!-- <image | ||||||
|  |             class="share-bg-logo" | ||||||
|  |             src="/static/images/share-logo.png" | ||||||
|  |             mode="scaleToFill" | ||||||
|  |           ></image> --> | ||||||
|         </view> |         </view> | ||||||
|  |       </template> | ||||||
|  | 
 | ||||||
|  |       <view | ||||||
|  |         class="wechat-fullscreen-overlay" | ||||||
|  |         v-show="isWechat && generatedImageUrl" | ||||||
|  |         @click="closeFullscreenImage" | ||||||
|  |       > | ||||||
|  |         <image | ||||||
|  |           class="fullscreen-image" | ||||||
|  |           :src="generatedImageUrl" | ||||||
|  |           mode="scaleToFill" | ||||||
|  |           @load="onGeneratedImageLoad" | ||||||
|  |           @error="onGeneratedImageError" | ||||||
|  |           @click.stop="" | ||||||
|  |         ></image> | ||||||
|       </view> |       </view> | ||||||
| 
 |  | ||||||
|       <!-- The real, clickable button --> |  | ||||||
|       <button class="share-button" @click="sharePage" v-show="shareButtonShow"> |  | ||||||
|         保存图片并分享 |  | ||||||
|       </button> |  | ||||||
|     </view> |     </view> | ||||||
| 
 |     <cl-tabbar class="tabbar" :current="2" /> | ||||||
|     <!-- Canvas for generating the share image, positioned off-screen --> |  | ||||||
|     <canvas |  | ||||||
|       canvas-id="shareCanvas" |  | ||||||
|       :style="{ |  | ||||||
|         width: canvasWidth + 'px', |  | ||||||
|         height: canvasHeight + 'px', |  | ||||||
|         position: 'fixed', |  | ||||||
|         left: '200%', |  | ||||||
|       }" |  | ||||||
|     /> |  | ||||||
|     <cl-tabbar :current="2" /> |  | ||||||
|   </view> |   </view> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
|  | @ -39,6 +72,7 @@ | ||||||
| import html2canvas from 'html2canvas' | import html2canvas from 'html2canvas' | ||||||
| import { getShareCode } from '@/config/share' | import { getShareCode } from '@/config/share' | ||||||
| import clTabbar from '@/components/cl-tabbar.vue' | import clTabbar from '@/components/cl-tabbar.vue' | ||||||
|  | 
 | ||||||
| export default { | export default { | ||||||
|   name: 'ShareQRCode', |   name: 'ShareQRCode', | ||||||
|   components: { |   components: { | ||||||
|  | @ -52,16 +86,20 @@ export default { | ||||||
|       canvasHeight: 800, |       canvasHeight: 800, | ||||||
|       isLoaded: false, |       isLoaded: false, | ||||||
|       shareButtonShow: true, |       shareButtonShow: true, | ||||||
|  |       isWechat: false, // 是否微信环境 | ||||||
|  |       generatedImageUrl: '', // 生成的图片URL,用于微信长按保存 | ||||||
|  |       backgroundImageLoaded: false, // 背景图片是否加载完成 | ||||||
|  |       userInfo: uni.getStorageSync('User'), | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   onLoad() { |   onLoad() { | ||||||
|  |     this.checkWechatEnvironment() | ||||||
|     this.handleGetShareCode() |     this.handleGetShareCode() | ||||||
|     // Get screen width to set canvas width dynamically |     // Get screen width to set canvas width dynamically | ||||||
|     uni.getSystemInfo({ |     uni.getSystemInfo({ | ||||||
|       success: res => { |       success: res => { | ||||||
|         this.canvasWidth = res.windowWidth |         this.canvasWidth = res.windowWidth | ||||||
|         // Set canvas height to a fixed 800px as requested |         this.canvasHeight = res.windowHeight | ||||||
|         this.canvasHeight = 800 |  | ||||||
|       }, |       }, | ||||||
|     }) |     }) | ||||||
|   }, |   }, | ||||||
|  | @ -72,6 +110,20 @@ export default { | ||||||
|     }, 100) |     }, 100) | ||||||
|   }, |   }, | ||||||
|   methods: { |   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) | ||||||
|  |     }, | ||||||
|  |     // 检测微信环境 | ||||||
|  |     checkWechatEnvironment() { | ||||||
|  |       const ua = navigator.userAgent.toLowerCase() | ||||||
|  |       this.isWechat = ua.includes('micromessenger') | ||||||
|  |       console.log('微信环境检测:', this.isWechat) | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|     handleGetShareCode() { |     handleGetShareCode() { | ||||||
|       // Don't show loading toast, use the placeholder loader instead |       // Don't show loading toast, use the placeholder loader instead | ||||||
|       // uni.showLoading({ title: '加载中...' }) |       // uni.showLoading({ title: '加载中...' }) | ||||||
|  | @ -80,6 +132,11 @@ export default { | ||||||
|           // The screenshot shows the base64 string is in data.datStr |           // The screenshot shows the base64 string is in data.datStr | ||||||
|           if (res.code === 200 && res.data && res.data.dataStr) { |           if (res.code === 200 && res.data && res.data.dataStr) { | ||||||
|             this.qrCodeImage = 'data:image/png;base64,' + res.data.dataStr |             this.qrCodeImage = 'data:image/png;base64,' + res.data.dataStr | ||||||
|  |             this.$nextTick(() => { | ||||||
|  |               if (this.isWechat) { | ||||||
|  |                 this.sharePage() | ||||||
|  |               } | ||||||
|  |             }) | ||||||
|           } else { |           } else { | ||||||
|             uni.showToast({ |             uni.showToast({ | ||||||
|               title: '获取分享码失败', |               title: '获取分享码失败', | ||||||
|  | @ -104,388 +161,164 @@ export default { | ||||||
|         }) |         }) | ||||||
|         return |         return | ||||||
|       } |       } | ||||||
|       this.shareButtonShow = false | 
 | ||||||
|       uni.showLoading({ title: '正在生成图片...' }) |       // 统一使用html2canvas生成图片 | ||||||
|  |       uni.showLoading({ title: '加载中...' }) | ||||||
| 
 | 
 | ||||||
|       try { |       try { | ||||||
|         // --- Button Swap Logic --- |         // 隐藏按钮,等待DOM更新 | ||||||
|         this.shareButtonShow = false |         this.shareButtonShow = false | ||||||
|         // Wait for DOM to update with the fake button |  | ||||||
|         await this.$nextTick() |         await this.$nextTick() | ||||||
| 
 | 
 | ||||||
|         // #ifdef H5 |         // 使用html2canvas截取页面 | ||||||
|         // Capture the entire container as requested |         await this.capturePageWithHtml2Canvas() | ||||||
|         await this.captureWithHtml2Canvas() |  | ||||||
|         // #endif |  | ||||||
| 
 |  | ||||||
|         // #ifndef H5 |  | ||||||
|         // Draw the entire container using Canvas |  | ||||||
|         await this.generateImageWithCanvas() |  | ||||||
|         // #endif |  | ||||||
|       } catch (error) { |       } catch (error) { | ||||||
|         uni.hideLoading() |         uni.hideLoading() | ||||||
|         uni.showToast({ title: '图片生成失败,请稍后重试', icon: 'none' }) |         uni.showToast({ title: '图片生成失败,请稍后重试', icon: 'none' }) | ||||||
|         console.error('sharePage error:', error) |         console.error('sharePage error:', error) | ||||||
|       } finally { |       } finally { | ||||||
|         // --- Always swap back to the real button --- |         // 恢复按钮显示 | ||||||
|         this.shareButtonShow = true |         this.shareButtonShow = true | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
| 
 | 
 | ||||||
|     // --- H5-specific method --- |     // 生成的图片加载成功 | ||||||
|     captureWithHtml2Canvas() { |     onGeneratedImageLoad() { | ||||||
|       return new Promise((resolve, reject) => { |       console.log('生成的图片加载成功') | ||||||
|         // #ifdef H5 |     }, | ||||||
|         // Target the entire #shareContainer as per instruction |  | ||||||
|         const element = document.getElementById('shareContainer') |  | ||||||
|         if (!element) { |  | ||||||
|           uni.hideLoading() |  | ||||||
|           return reject(new Error('Share container element not found')) |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         html2canvas(element, { |     // 生成的图片加载失败 | ||||||
|           useCORS: true, |     onGeneratedImageError(e) { | ||||||
|           allowTaint: true, |       console.error('生成的图片加载失败:', e) | ||||||
|           backgroundColor: null, |       uni.showToast({ | ||||||
|           scale: window.devicePixelRatio || 2, |         title: '图片显示失败', | ||||||
|           logging: false, |         icon: 'none', | ||||||
|         }) |  | ||||||
|           .then(canvas => { |  | ||||||
|             this.saveImageToAlbum(canvas.toDataURL('image/png', 1.0)) |  | ||||||
|             resolve() |  | ||||||
|           }) |  | ||||||
|           .catch(err => { |  | ||||||
|             console.error('html2canvas capture failed:', err) |  | ||||||
|             reject(err) |  | ||||||
|           }) |  | ||||||
|         // #endif |  | ||||||
|       }) |       }) | ||||||
|     }, |     }, | ||||||
| 
 | 
 | ||||||
|     // A robust canvas-based image generation for all platforms |     // 关闭全屏图片 | ||||||
|     async generateImageWithCanvas() { |     closeFullscreenImage() { | ||||||
|       try { |       this.generatedImageUrl = '' | ||||||
|         // Load all required images before drawing |       console.log('关闭全屏图片') | ||||||
|         const [bgImageTempPath, qrCodeTempPath] = await Promise.all([ |     }, | ||||||
|           this.getLocalImageTempPath('/static/images/share-bg.png'), |  | ||||||
|           this.base64ToTempFilePath(this.qrCodeImage), |  | ||||||
|         ]) |  | ||||||
| 
 | 
 | ||||||
|         if (!bgImageTempPath || !qrCodeTempPath) { |     // 背景图片加载成功 | ||||||
|           throw new Error('Image resource failed to load') |     onBackgroundImageLoad() { | ||||||
|         } |       this.backgroundImageLoaded = true | ||||||
|  |       console.log('背景图片加载成功') | ||||||
|  |     }, | ||||||
| 
 | 
 | ||||||
|         const ctx = uni.createCanvasContext('shareCanvas', this) |     // 背景图片加载失败 | ||||||
|         // Draw the entire scene to the canvas |     onBackgroundImageError(e) { | ||||||
|         this.drawSceneToCanvas(ctx, bgImageTempPath, qrCodeTempPath) |       console.error('背景图片加载失败:', e) | ||||||
|  |     }, | ||||||
| 
 | 
 | ||||||
|         return new Promise(resolve => { |     // 使用html2canvas截取整个页面 | ||||||
|  |     async capturePageWithHtml2Canvas() { | ||||||
|  |       console.log('开始使用html2canvas截取页面') | ||||||
|  | 
 | ||||||
|  |       return new Promise((resolve, reject) => { | ||||||
|  |         // 确保所有图片都已加载完成 | ||||||
|  |         const waitForImages = () => { | ||||||
|  |           if (!this.backgroundImageLoaded || !this.qrCodeImage) { | ||||||
|  |             setTimeout(waitForImages, 100) | ||||||
|  |             return | ||||||
|  |           } | ||||||
|  | 
 | ||||||
|  |           // 额外等待确保渲染完成 | ||||||
|           setTimeout(() => { |           setTimeout(() => { | ||||||
|             ctx.draw(false, () => { |             const element = document.getElementById('shareContainer') | ||||||
|               setTimeout(() => { |             if (!element) { | ||||||
|                 this.saveCanvasToAlbum() |               reject(new Error('找不到页面容器')) | ||||||
|                 resolve() |               return | ||||||
|               }, 500) |  | ||||||
|             }) |  | ||||||
|           }, 100) |  | ||||||
|         }) |  | ||||||
|       } catch (error) { |  | ||||||
|         console.error('generateImageWithCanvas error:', error) |  | ||||||
|         throw error |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
| 
 |  | ||||||
|     // Main drawing function to replicate the entire scene on canvas |  | ||||||
|     drawSceneToCanvas(ctx, bgPath, qrPath) { |  | ||||||
|       const canvasWidth = this.canvasWidth |  | ||||||
|       const canvasHeight = this.canvasHeight // Fixed at 800px |  | ||||||
| 
 |  | ||||||
|       // 1. Draw background image |  | ||||||
|       ctx.drawImage(bgPath, 0, 0, canvasWidth, canvasHeight) |  | ||||||
| 
 |  | ||||||
|       // 2. Draw portal frame (calculations for centering) |  | ||||||
|       const portalWidth = canvasWidth * 0.85 |  | ||||||
|       const portalPadding = 20 |  | ||||||
|       // The white card's width is the portal's inner width |  | ||||||
|       const cardWidth = portalWidth - portalPadding * 2 |  | ||||||
|       // The card is a square, so its height is its width |  | ||||||
|       const cardHeight = cardWidth |  | ||||||
|       const buttonHeight = 50 |  | ||||||
|       const buttonMargin = 20 |  | ||||||
|       const portalHeight = |  | ||||||
|         portalPadding * 2 + cardHeight + buttonMargin + buttonHeight |  | ||||||
|       const portalX = (canvasWidth - portalWidth) / 2 |  | ||||||
|       const portalY = (canvasHeight - portalHeight) / 2 |  | ||||||
|       this.drawPortalFrame(ctx, portalX, portalY, portalWidth, portalHeight) |  | ||||||
| 
 |  | ||||||
|       // 3. Draw the white QR card |  | ||||||
|       const cardX = portalX + portalPadding |  | ||||||
|       const cardY = portalY + portalPadding |  | ||||||
|       this.drawQrCodeCard(ctx, cardX, cardY, cardWidth, cardHeight) |  | ||||||
| 
 |  | ||||||
|       // 4. Draw the FAKE button to match the CSS |  | ||||||
|       const buttonX = cardX |  | ||||||
|       const buttonY = cardY + cardHeight + buttonMargin |  | ||||||
|       this.drawStyledButton( |  | ||||||
|         ctx, |  | ||||||
|         buttonX, |  | ||||||
|         buttonY, |  | ||||||
|         cardWidth, |  | ||||||
|         buttonHeight, |  | ||||||
|         '保存图片并分享' |  | ||||||
|       ) |  | ||||||
| 
 |  | ||||||
|       // 5. Draw the actual QR image (or loader placeholder) on top |  | ||||||
|       // If qrPath is null/empty, this block is skipped, leaving the white card empty (like a placeholder) |  | ||||||
|       if (qrPath) { |  | ||||||
|         // Padding inside the white card |  | ||||||
|         const imagePadding = cardWidth * 0.1 |  | ||||||
|         const qrCodeSize = cardWidth - imagePadding * 2 |  | ||||||
|         const qrCodeX = cardX + imagePadding |  | ||||||
|         const qrCodeY = cardY + imagePadding |  | ||||||
|         ctx.drawImage(qrPath, qrCodeX, qrCodeY, qrCodeSize, qrCodeSize) |  | ||||||
|       } else { |  | ||||||
|         // Draw loader manually if QR is not available |  | ||||||
|         // This part is complex, for now we show an empty card which is better than broken UI |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
| 
 |  | ||||||
|     // Helper: Draws the portal frame to match CSS |  | ||||||
|     drawPortalFrame(ctx, x, y, width, height) { |  | ||||||
|       const radius = 25 // 50rpx |  | ||||||
|       ctx.save() |  | ||||||
|       this.drawRoundedRect(ctx, x, y, width, height, radius) |  | ||||||
|       // Frosted glass effect |  | ||||||
|       const portalGradient = ctx.createLinearGradient(x, y, x, y + height) |  | ||||||
|       portalGradient.addColorStop(0, 'rgba(255, 255, 255, 0.4)') |  | ||||||
|       portalGradient.addColorStop(1, 'rgba(255, 255, 255, 0.2)') |  | ||||||
|       ctx.fillStyle = portalGradient |  | ||||||
|       ctx.fill() |  | ||||||
|       // Inner glow border |  | ||||||
|       ctx.shadowColor = 'rgba(255, 255, 255, 0.5)' |  | ||||||
|       ctx.shadowBlur = 10 |  | ||||||
|       ctx.strokeStyle = 'rgba(255, 255, 255, 0.6)' |  | ||||||
|       ctx.lineWidth = 1.5 |  | ||||||
|       ctx.stroke() |  | ||||||
|       ctx.restore() |  | ||||||
|     }, |  | ||||||
| 
 |  | ||||||
|     // New/Refactored Helper: Draws the single white QR Code card |  | ||||||
|     drawQrCodeCard(ctx, x, y, width, height) { |  | ||||||
|       const borderRadius = 20 // 40rpx |  | ||||||
| 
 |  | ||||||
|       ctx.save() |  | ||||||
|       this.drawRoundedRect(ctx, x, y, width, height, borderRadius) |  | ||||||
|       // Card background |  | ||||||
|       ctx.fillStyle = 'rgba(255, 255, 255, 0.98)' |  | ||||||
|       // Card shadow |  | ||||||
|       ctx.shadowColor = 'rgba(50, 50, 90, 0.1)' |  | ||||||
|       ctx.shadowBlur = 30 |  | ||||||
|       ctx.shadowOffsetY = 15 |  | ||||||
|       ctx.fill() |  | ||||||
|       // Card border |  | ||||||
|       ctx.strokeStyle = '#f0f0f0' |  | ||||||
|       ctx.lineWidth = 1 |  | ||||||
|       ctx.stroke() |  | ||||||
|       ctx.restore() |  | ||||||
|     }, |  | ||||||
| 
 |  | ||||||
|     // Helper: Draws the styled button |  | ||||||
|     drawStyledButton(ctx, x, y, width, height, text) { |  | ||||||
|       const radius = height / 2 |  | ||||||
| 
 |  | ||||||
|       ctx.save() |  | ||||||
|       this.drawRoundedRect(ctx, x, y, width, height, radius) |  | ||||||
|       // Background gradient |  | ||||||
|       const gradient = ctx.createLinearGradient(x, y, x + width, y) |  | ||||||
|       gradient.addColorStop(0, '#0ff0fc') |  | ||||||
|       gradient.addColorStop(0.5, '#0072ff') |  | ||||||
|       gradient.addColorStop(1, '#00c6ff') |  | ||||||
|       ctx.fillStyle = gradient |  | ||||||
|       // Shadow |  | ||||||
|       ctx.shadowColor = 'rgba(0, 114, 255, 0.35)' |  | ||||||
|       ctx.shadowBlur = 20 |  | ||||||
|       ctx.shadowOffsetY = 8 |  | ||||||
|       ctx.fill() |  | ||||||
|       ctx.restore() |  | ||||||
| 
 |  | ||||||
|       // Text |  | ||||||
|       ctx.save() |  | ||||||
|       ctx.fillStyle = '#ffffff' |  | ||||||
|       ctx.font = '17px sans-serif' |  | ||||||
|       ctx.textAlign = 'center' |  | ||||||
|       ctx.textBaseline = 'middle' |  | ||||||
|       ctx.fillText(text, x + width / 2, y + height / 2) |  | ||||||
|       ctx.restore() |  | ||||||
|     }, |  | ||||||
| 
 |  | ||||||
|     // Helper: Draw a rectangle with rounded corners |  | ||||||
|     drawRoundedRect(ctx, x, y, width, height, radius) { |  | ||||||
|       if (width < 2 * radius) radius = width / 2 |  | ||||||
|       if (height < 2 * radius) radius = height / 2 |  | ||||||
|       ctx.beginPath() |  | ||||||
|       ctx.moveTo(x + radius, y) |  | ||||||
|       ctx.arcTo(x + width, y, x + width, y + height, radius) |  | ||||||
|       ctx.arcTo(x + width, y + height, x, y + height, radius) |  | ||||||
|       ctx.arcTo(x, y + height, x, y, radius) |  | ||||||
|       ctx.arcTo(x, y, x + width, y, radius) |  | ||||||
|       ctx.closePath() |  | ||||||
|     }, |  | ||||||
| 
 |  | ||||||
|     saveCanvasToAlbum() { |  | ||||||
|       try { |  | ||||||
|         // 统一的canvas转换参数 |  | ||||||
|         const options = { |  | ||||||
|           canvasId: 'shareCanvas', |  | ||||||
|           fileType: 'png', |  | ||||||
|           quality: 1, |  | ||||||
|           success: res => { |  | ||||||
|             console.log('canvas转换成功:', res) |  | ||||||
|             if (res.tempFilePath) { |  | ||||||
|               this.saveImageToAlbum(res.tempFilePath) |  | ||||||
|             } else { |  | ||||||
|               uni.hideLoading() |  | ||||||
|               uni.showToast({ title: '图片生成失败', icon: 'none' }) |  | ||||||
|             } |             } | ||||||
|           }, | 
 | ||||||
|           fail: err => { |             console.log( | ||||||
|             console.error('canvas转换失败:', err) |               '开始html2canvas截取,容器尺寸:', | ||||||
|             uni.hideLoading() |               element.offsetWidth, | ||||||
|             uni.showToast({ title: '图片转换失败', icon: 'none' }) |               'x', | ||||||
|           }, |               element.offsetHeight | ||||||
|  |             ) | ||||||
|  | 
 | ||||||
|  |             html2canvas(element, { | ||||||
|  |               useCORS: true, | ||||||
|  |               allowTaint: true, | ||||||
|  |               backgroundColor: null, | ||||||
|  |               scale: 2, | ||||||
|  |               dpi: 400, | ||||||
|  |               logging: true, // 开启日志便于调试 | ||||||
|  |               width: element.offsetWidth, | ||||||
|  |               height: element.offsetHeight, | ||||||
|  |               windowWidth: element.offsetWidth, | ||||||
|  |               windowHeight: element.offsetHeight, | ||||||
|  |               scrollX: 0, | ||||||
|  |               scrollY: 0, | ||||||
|  |               x: 0, | ||||||
|  |               y: 0, | ||||||
|  |             }) | ||||||
|  |               .then(canvas => { | ||||||
|  |                 const dataUrl = canvas.toDataURL('image/jpeg', 1) | ||||||
|  | 
 | ||||||
|  |                 // 根据环境处理结果 | ||||||
|  |                 if (this.isWechat) { | ||||||
|  |                   // 微信环境:设置图片供长按保存 | ||||||
|  |                   this.generatedImageUrl = dataUrl | ||||||
|  |                   uni.hideLoading() | ||||||
|  |                 } else { | ||||||
|  |                   // 普通浏览器:直接下载图片 | ||||||
|  |                   this.downloadImage(dataUrl) | ||||||
|  |                   uni.hideLoading() | ||||||
|  |                   uni.showToast({ | ||||||
|  |                     title: '图片已开始下载', | ||||||
|  |                     icon: 'success', | ||||||
|  |                   }) | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 resolve() | ||||||
|  |               }) | ||||||
|  |               .catch(err => { | ||||||
|  |                 console.error('html2canvas截取失败:', err) | ||||||
|  |                 reject(err) | ||||||
|  |               }) | ||||||
|  |           }, 1000) // 增加等待时间到1000ms | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // 调用canvas转换API |         // 开始等待图片加载 | ||||||
|         uni.canvasToTempFilePath(options, this) |         waitForImages() | ||||||
|       } catch (error) { |       }) | ||||||
|         console.error('saveCanvasToAlbum error:', error) |  | ||||||
|         uni.hideLoading() |  | ||||||
|         uni.showToast({ title: '保存失败', icon: 'none' }) |  | ||||||
|       } |  | ||||||
|     }, |     }, | ||||||
| 
 | 
 | ||||||
|     // 新增:统一的图片保存方法 |     // 原生图片加载器 | ||||||
|     saveImageToAlbum(filePath) { |     loadImage(src) { | ||||||
|       // #ifdef H5 |       return new Promise((resolve, reject) => { | ||||||
|       // For H5, trigger download instead of saving to album |         const img = new Image() | ||||||
|  |         img.crossOrigin = 'anonymous' | ||||||
|  | 
 | ||||||
|  |         img.onload = () => { | ||||||
|  |           console.log( | ||||||
|  |             `图片加载成功: ${src.substring(0, 50)}...`, | ||||||
|  |             `${img.width}x${img.height}` | ||||||
|  |           ) | ||||||
|  |           resolve(img) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         img.onerror = error => { | ||||||
|  |           console.error(`图片加载失败: ${src}`, error) | ||||||
|  |           reject(new Error(`图片加载失败: ${src}`)) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         img.src = src | ||||||
|  |       }) | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     // 下载图片 | ||||||
|  |     downloadImage(dataUrl) { | ||||||
|       const link = document.createElement('a') |       const link = document.createElement('a') | ||||||
|       link.href = filePath |       link.href = dataUrl | ||||||
|       link.download = `share_page_${Date.now()}.png` |       link.download = `share_page_${Date.now()}.png` | ||||||
|       document.body.appendChild(link) |       document.body.appendChild(link) | ||||||
|       link.click() |       link.click() | ||||||
|       document.body.removeChild(link) |       document.body.removeChild(link) | ||||||
|       uni.hideLoading() |  | ||||||
|       uni.showToast({ |  | ||||||
|         title: '图片已开始下载', |  | ||||||
|         icon: 'success', |  | ||||||
|       }) |  | ||||||
|       // #endif |  | ||||||
| 
 |  | ||||||
|       // #ifndef H5 |  | ||||||
|       // For App and Mini Programs |  | ||||||
|       uni.saveImageToPhotosAlbum({ |  | ||||||
|         filePath: filePath, |  | ||||||
|         success: () => { |  | ||||||
|           uni.hideLoading() |  | ||||||
|           uni.showToast({ |  | ||||||
|             title: '图片已保存到相册', |  | ||||||
|             icon: 'success', |  | ||||||
|           }) |  | ||||||
|         }, |  | ||||||
|         fail: err => { |  | ||||||
|           uni.hideLoading() |  | ||||||
|           if ( |  | ||||||
|             err.errMsg && |  | ||||||
|             (err.errMsg.includes('auth deny') || |  | ||||||
|               err.errMsg.includes('auth denied')) |  | ||||||
|           ) { |  | ||||||
|             uni.showModal({ |  | ||||||
|               title: '提示', |  | ||||||
|               content: '需要您授权保存相册', |  | ||||||
|               showCancel: false, |  | ||||||
|               success: () => { |  | ||||||
|                 uni.openSetting({ |  | ||||||
|                   success(settingdata) { |  | ||||||
|                     if (settingdata.authSetting['scope.writePhotosAlbum']) { |  | ||||||
|                       uni.showToast({ |  | ||||||
|                         title: '授权成功,请重试', |  | ||||||
|                         icon: 'none', |  | ||||||
|                       }) |  | ||||||
|                     } else { |  | ||||||
|                       uni.showToast({ |  | ||||||
|                         title: '获取权限失败', |  | ||||||
|                         icon: 'none', |  | ||||||
|                       }) |  | ||||||
|                     } |  | ||||||
|                   }, |  | ||||||
|                 }) |  | ||||||
|               }, |  | ||||||
|             }) |  | ||||||
|           } else { |  | ||||||
|             uni.showToast({ title: '保存失败', icon: 'none' }) |  | ||||||
|             console.error('saveImageToPhotosAlbum fail:', err) |  | ||||||
|           } |  | ||||||
|         }, |  | ||||||
|       }) |  | ||||||
|       // #endif |  | ||||||
|     }, |  | ||||||
| 
 |  | ||||||
|     base64ToTempFilePath(base64) { |  | ||||||
|       return new Promise((resolve, reject) => { |  | ||||||
|         try { |  | ||||||
|           // #ifdef H5 |  | ||||||
|           // For H5, we load the base64 into an Image to ensure it's valid, |  | ||||||
|           // but resolve with the base64 string to avoid Uniapp's internal errors |  | ||||||
|           // when its functions expect a string path instead of an Image object. |  | ||||||
|           const image = new Image() |  | ||||||
|           // Resolve CORS issue for QR code from different origin |  | ||||||
|           image.crossOrigin = 'Anonymous' |  | ||||||
|           image.src = base64 |  | ||||||
|           image.onload = () => { |  | ||||||
|             // Resolve with the string, not the object. |  | ||||||
|             resolve(base64) |  | ||||||
|           } |  | ||||||
|           image.onerror = err => { |  | ||||||
|             console.error('Failed to load image for canvas on H5', err) |  | ||||||
|             reject(new Error('H5图片加载失败')) |  | ||||||
|           } |  | ||||||
|           // #endif |  | ||||||
| 
 |  | ||||||
|           // #ifndef H5 |  | ||||||
|           // For App and Mini Programs, write to a temp file and return the path. |  | ||||||
|           if (!base64 || typeof base64 !== 'string') { |  | ||||||
|             reject(new Error('无效的base64数据')) |  | ||||||
|             return |  | ||||||
|           } |  | ||||||
| 
 |  | ||||||
|           const formattedBase64 = base64.replace(/^data:image\/\w+;base64,/, '') |  | ||||||
|           if (!formattedBase64) { |  | ||||||
|             reject(new Error('base64数据格式错误')) |  | ||||||
|             return |  | ||||||
|           } |  | ||||||
| 
 |  | ||||||
|           // Use a standard path for user data directory. |  | ||||||
|           const filePath = `${uni.env.USER_DATA_PATH}/share_${Date.now()}.png` |  | ||||||
|           const fileManager = uni.getFileSystemManager() |  | ||||||
| 
 |  | ||||||
|           fileManager.writeFile({ |  | ||||||
|             filePath, |  | ||||||
|             data: formattedBase64, |  | ||||||
|             encoding: 'base64', |  | ||||||
|             success: () => { |  | ||||||
|               resolve(filePath) |  | ||||||
|             }, |  | ||||||
|             fail: err => { |  | ||||||
|               console.error('Failed to write temp file', err) |  | ||||||
|               reject(new Error('临时文件写入失败')) |  | ||||||
|             }, |  | ||||||
|           }) |  | ||||||
|           // #endif |  | ||||||
|         } catch (error) { |  | ||||||
|           console.error('base64ToTempFilePath error:', error) |  | ||||||
|           reject(new Error('图片处理失败')) |  | ||||||
|         } |  | ||||||
|       }) |  | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
| } | } | ||||||
|  | @ -493,9 +326,8 @@ export default { | ||||||
| 
 | 
 | ||||||
| <style lang="scss" scoped> | <style lang="scss" scoped> | ||||||
| .share-container { | .share-container { | ||||||
|   background: url('@/static/images/share-bg.png') no-repeat center center; |   flex: 1; | ||||||
|   background-size: 100% 100%; |   height: 0; | ||||||
|   height: calc(100vh - 80rpx); |  | ||||||
|   box-sizing: border-box; |   box-sizing: border-box; | ||||||
|   position: relative; |   position: relative; | ||||||
|   overflow: hidden; |   overflow: hidden; | ||||||
|  | @ -503,6 +335,17 @@ export default { | ||||||
|   flex-direction: column; |   flex-direction: column; | ||||||
|   align-items: center; |   align-items: center; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /* 背景图片样式 */ | ||||||
|  | .share-bg-image { | ||||||
|  |   position: absolute; | ||||||
|  |   top: 0; | ||||||
|  |   left: 0; | ||||||
|  |   width: 100%; | ||||||
|  |   height: 100%; | ||||||
|  |   z-index: 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .share-bg { | .share-bg { | ||||||
|   width: 100%; |   width: 100%; | ||||||
|   height: 100%; |   height: 100%; | ||||||
|  | @ -511,26 +354,30 @@ export default { | ||||||
|     height: 100%; |     height: 100%; | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | .share-wrapper { | ||||||
| .portal-frame { |   position: absolute; | ||||||
|   /* Restyled to match the ideal screenshot */ |   z-index: 2; | ||||||
|   background: rgba(255, 255, 255, 0.25); |   bottom: 260rpx; | ||||||
|   backdrop-filter: blur(20px); |  | ||||||
|   -webkit-backdrop-filter: blur(20px); |  | ||||||
|   padding: 40rpx; |  | ||||||
|   margin-top: 450rpx; |  | ||||||
|   border-radius: 50rpx; |  | ||||||
|   border: 1.5px solid rgba(255, 255, 255, 0.6); |  | ||||||
|   box-shadow: |  | ||||||
|     0 0 0 1.5px rgba(255, 255, 255, 0.4) inset, |  | ||||||
|     0 15rpx 40rpx rgba(0, 0, 0, 0.1); |  | ||||||
|   display: flex; |   display: flex; | ||||||
|   width: 90%; |  | ||||||
|   box-sizing: border-box; |  | ||||||
| 
 |  | ||||||
|   flex-direction: column; |   flex-direction: column; | ||||||
|   align-items: center; |   align-items: center; | ||||||
|   /* Removed margin-top, parent container handles centering */ | } | ||||||
|  | .share-bg-logo { | ||||||
|  |   width: 100%; | ||||||
|  |   height: 360rpx; | ||||||
|  |   // margin-top: -60rpx; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .portal-frame { | ||||||
|  |   padding: 32rpx; | ||||||
|  |   width: 520rpx; | ||||||
|  |   border-radius: 40rpx; | ||||||
|  | 
 | ||||||
|  |   display: flex; | ||||||
|  |   box-sizing: border-box; | ||||||
|  |   flex-direction: column; | ||||||
|  |   align-items: center; | ||||||
|  |   margin: 0 auto; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .portal-frame.is-loaded { | .portal-frame.is-loaded { | ||||||
|  | @ -547,17 +394,16 @@ export default { | ||||||
| 
 | 
 | ||||||
| /* The single white card for the QR code */ | /* The single white card for the QR code */ | ||||||
| .qr-code-outer { | .qr-code-outer { | ||||||
|   width: 100%; |   width: 400rpx; /* 从400rpx缩小到320rpx */ | ||||||
|   /* This creates a responsive square that's always perfect */ |   height: 400rpx; /* 从400rpx缩小到320rpx */ | ||||||
|   aspect-ratio: 1 / 1; |  | ||||||
|   background: rgba(255, 255, 255, 0.98); |   background: rgba(255, 255, 255, 0.98); | ||||||
|   border-radius: 40rpx; |   border-radius: 20rpx; /* 从24rpx减小到20rpx */ | ||||||
|   box-shadow: 0px 15rpx 30rpx rgba(50, 50, 90, 0.1); |   box-shadow: 0px 8rpx 20rpx rgba(50, 50, 90, 0.06); | ||||||
|   border: 1px solid #f0f0f0; |   border: 1px solid #f0f0f0; | ||||||
|   display: flex; |   display: flex; | ||||||
|   align-items: center; |   align-items: center; | ||||||
|   justify-content: center; |   justify-content: center; | ||||||
|   padding: 30rpx; /* Padding inside the white card */ |   padding: 12rpx; /* 从16rpx减小到12rpx */ | ||||||
|   box-sizing: border-box; |   box-sizing: border-box; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -600,31 +446,91 @@ export default { | ||||||
| 
 | 
 | ||||||
| /* Unified style for both the real button and the fake one (which is a view) */ | /* Unified style for both the real button and the fake one (which is a view) */ | ||||||
| .share-button { | .share-button { | ||||||
|   margin-top: 40rpx; |   margin-top: 28rpx; /* 从32rpx减小到28rpx */ | ||||||
|   width: 100%; /* Button takes full width of portal frame padding */ |   width: 280rpx; /* 设置固定宽度 */ | ||||||
|   height: 100rpx; |   height: 72rpx; /* 从88rpx减小到72rpx */ | ||||||
|   line-height: 100rpx; |   line-height: 72rpx; | ||||||
|   color: #fff; |   color: #fff; | ||||||
|   border-radius: 50rpx; |   border-radius: 36rpx; /* 从44rpx减小到36rpx */ | ||||||
|   font-size: 34rpx; |   font-size: 26rpx; /* 从30rpx减小到26rpx */ | ||||||
|   /* Match the gradient from canvas drawing */ |   font-weight: 500; | ||||||
|   background: linear-gradient(100deg, #0ff0fc 0%, #0072ff 50%, #00c6ff 100%); |   /* 更精细的渐变 */ | ||||||
|   box-shadow: 0 8rpx 20rpx 0 rgba(0, 114, 255, 0.35); |   background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | ||||||
|  |   box-shadow: 0 5rpx 12rpx 0 rgba(102, 126, 234, 0.2); | ||||||
|   border: none; |   border: none; | ||||||
|   padding: 0; |   padding: 0; | ||||||
|   text-align: center; |   text-align: center; | ||||||
|   /* Add transition for the button itself if needed */ |   transition: all 0.3s ease; | ||||||
|  |   letter-spacing: 1rpx; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* The real button needs to override some uni-app defaults */ | /* The real button needs to override some uni-app defaults */ | ||||||
| button.share-button { | button.share-button { | ||||||
|   padding: 0; |   padding: 0; | ||||||
|   line-height: 100rpx; /* Ensure text is centered vertically */ |   line-height: 72rpx; /* 更新行高到72rpx */ | ||||||
|   border: none; |   border: none; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .share-button:active { | .share-button:active { | ||||||
|   transform: translateY(2rpx); |   transform: translateY(1rpx); | ||||||
|   box-shadow: 0 6rpx 12rpx rgba(0, 114, 255, 0.3); |   box-shadow: 0 4rpx 10rpx rgba(102, 126, 234, 0.4); | ||||||
|  |   background: linear-gradient( | ||||||
|  |     135deg, | ||||||
|  |     #5a67d8 0%, | ||||||
|  |     #6b46c1 100% | ||||||
|  |   ); /* 按下时的渐变 */ | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* 微信环境全屏覆盖样式 */ | ||||||
|  | .wechat-fullscreen-overlay { | ||||||
|  |   position: absolute; | ||||||
|  |   top: 0; | ||||||
|  |   left: 0; | ||||||
|  |   width: 100vw; | ||||||
|  |   height: 100%; /* 减去tab栏高度 */ | ||||||
|  | 
 | ||||||
|  |   background-color: transparent; /* 移除背景色 */ | ||||||
|  |   z-index: 999; | ||||||
|  |   display: flex; | ||||||
|  |   flex-direction: column; | ||||||
|  |   align-items: stretch; /* 拉伸对齐 */ | ||||||
|  |   justify-content: stretch; /* 拉伸对齐 */ | ||||||
|  |   padding: 0; | ||||||
|  |   margin: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .fullscreen-image { | ||||||
|  |   width: 100vw; | ||||||
|  |   height: 100%; /* 减去tab栏高度 */ | ||||||
|  |   object-fit: cover; /* 覆盖整个容器,可能会裁剪 */ | ||||||
|  |   margin: 0; | ||||||
|  |   padding: 0; | ||||||
|  |   border: none; | ||||||
|  |   display: block; | ||||||
|  | } | ||||||
|  | @media screen and (max-height: 667px) { | ||||||
|  |   .tabbar { | ||||||
|  |     height: 70px !important; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @media screen and (min-height: 812px) { | ||||||
|  |   /* iPhone X及以上机型 */ | ||||||
|  |   .tabbar { | ||||||
|  |     height: 80px !important; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | .tabbar { | ||||||
|  |   position: static; | ||||||
|  |   bottom: 0; | ||||||
|  |   z-index: 1000; | ||||||
|  |   width: 100%; | ||||||
|  |   height: 50px; | ||||||
|  |   :v-deep .u-tabbar--fixed { | ||||||
|  |     height: 100px !important; | ||||||
|  |   } | ||||||
|  |   // ::v-deep .u-safe-area-inset-bottom { | ||||||
|  |   //   display: none !important; | ||||||
|  |   // } | ||||||
| } | } | ||||||
| </style> | </style> | ||||||
|  |  | ||||||
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 372 KiB After Width: | Height: | Size: 985 KiB | 
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 26 KiB | 
		Loading…
	
		Reference in New Issue