From 7836c121a722745e17d5f74a0fb2b8fc2bcb9e1f Mon Sep 17 00:00:00 2001 From: woody Date: Mon, 14 Jul 2025 14:32:08 +0800 Subject: [PATCH] =?UTF-8?q?feat(share-image):=20=E6=A0=B9=E6=8D=AE?= =?UTF-8?q?=E4=B8=8D=E5=90=8C=E4=BD=93=E7=B3=BB=E5=B1=95=E7=A4=BA=E4=B8=8D?= =?UTF-8?q?=E5=90=8C=E7=9A=84=E5=88=86=E4=BA=AB=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- App.vue | 2 + components/share/DefaultSharePage.vue | 320 +++++++++++++++++ components/share/README.md | 178 ++++++++++ components/share/SpecialSharePage.vue | 468 +++++++++++++++++++++++++ config/login.js | 4 + config/request.js | 2 +- config/share.js | 6 +- pages/mine/share/index.vue | 485 ++++++-------------------- 8 files changed, 1085 insertions(+), 380 deletions(-) create mode 100644 components/share/DefaultSharePage.vue create mode 100644 components/share/README.md create mode 100644 components/share/SpecialSharePage.vue diff --git a/App.vue b/App.vue index 8f98eb1..12ce878 100644 --- a/App.vue +++ b/App.vue @@ -1,5 +1,7 @@ + + diff --git a/components/share/README.md b/components/share/README.md new file mode 100644 index 0000000..b278567 --- /dev/null +++ b/components/share/README.md @@ -0,0 +1,178 @@ +# 分享组件说明 + +## SpecialSharePage 组件 + +特殊场景分享页面组件,通过接口获取base64格式的背景图片。 + +### 主要特性 + +- 📱 **响应式**:适配各种屏幕尺寸 +- 🎨 **可定制**:支持样式自定义 +- 🔄 **重试机制**:支持接口调用失败时的重试 +- 🌐 **接口获取**:自动通过接口获取背景图片 +- 🖼️ **Base64支持**:接口返回base64格式的背景图片 + +### 使用方法 + +```vue + + + +``` + +### Props + +| 参数 | 类型 | 默认值 | 说明 | +| ----------------------- | ------- | ------ | ------------------------ | +| `qrCodeImage` | String | '' | 二维码图片(base64格式) | +| `userInfo` | Object | {} | 用户信息对象 | +| `isWechat` | Boolean | false | 是否为微信环境 | +| `isLoaded` | Boolean | false | 是否加载完成 | +| `skipImageVerification` | Boolean | false | 跳过图片验证(调试用) | + +### Events + +| 事件名 | 参数 | 说明 | +| ------------------------ | ---------- | ---------------- | +| `share-generated` | dataUrl | 分享图片生成成功 | +| `background-image-error` | error | 背景图片加载错误 | +| `background-image-retry` | retryCount | 背景图片重试 | + +### 方法 + +| 方法名 | 说明 | +| ------------------------------- | ------------------------------------ | +| `generateShareImage()` | 生成分享图片 | +| `retryGetBackgroundImage()` | 手动重试获取背景图 | +| `getStatusInfo()` | 获取当前状态信息(调试用) | +| `handleShareGenerated(dataUrl)` | 处理图片生成成功后的逻辑(内部方法) | + +### 接口配置 + +组件使用 `getSharedImg` 接口获取背景图片: + +```javascript +// 在 config/login.js 中配置接口 +export const getSharedImg = () => { + return http.get('/api/share/background') +} +``` + +接口应返回以下格式: + +```javascript +{ + code: 200, + data: 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD...', + message: 'success' +} +``` + +### 调试 + +```javascript +// 生成分享图片 +this.$refs.specialSharePage.generateShareImage() + +// 手动重试获取背景图 +this.$refs.specialSharePage.retryGetBackgroundImage() + +// 获取当前状态信息 +const status = this.$refs.specialSharePage.getStatusInfo() +console.log('组件状态:', status) +/* +输出示例: +{ + specialBackgroundImage: '123456 bytes', + qrCodeImage: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEU...', + backgroundImageLoaded: true, + isLoadingBackground: false, + imageLoadRetryCount: 0, + isReady: true +} +*/ +``` + +### 工作流程 + +1. **组件挂载**:自动调用 `getSharedImg` 接口获取背景图 +2. **显示加载**:显示"正在获取背景图片..."状态 +3. **接口响应**:处理接口返回的base64背景图数据 +4. **准备就绪**:背景图和二维码都准备好后可以生成分享图片 +5. **生成图片**:使用html2canvas截取页面生成分享图片 +6. **处理结果**: + - 关闭loading状态 + - 显示成功提示("图片生成成功,请长按保存") + - 调用内部 `handleShareGenerated` 方法处理 + - 通过 `share-generated` 事件通知父组件 + +### 图片生成流程 + +``` +用户点击生成按钮 + ↓ +检查前置条件(二维码、背景图) + ↓ +显示 "生成图片中..." loading + ↓ +使用 html2canvas 截取页面 + ↓ +生成成功 → 关闭loading → 显示成功提示 → 发送事件 + ↓ +生成失败 → 关闭loading → 显示错误提示 +``` + +### 错误处理 + +- **接口调用失败**:自动重试最多3次 +- **重试机制**:通过 `background-image-retry` 事件通知重试状态 +- **最终失败**:通过 `background-image-error` 事件通知错误 + +### 注意事项 + +1. 确保 `getSharedImg` 接口返回完整的base64格式图片 +2. 组件会在mounted生命周期自动调用接口 +3. 支持手动重试机制,适用于网络不稳定的情况 +4. 生成的图片为JPEG格式,质量为100% +5. 组件支持长按保存分享图片 +6. 接口失败时会显示相应的错误状态 +7. **图片生成成功后会自动显示提示信息**,无需在父组件中重复处理 +8. **组件内部和外部都会收到图片生成事件**,可根据需要在父组件中添加额外逻辑 diff --git a/components/share/SpecialSharePage.vue b/components/share/SpecialSharePage.vue new file mode 100644 index 0000000..530ce5e --- /dev/null +++ b/components/share/SpecialSharePage.vue @@ -0,0 +1,468 @@ + + + + + diff --git a/config/login.js b/config/login.js index 07318e8..bc7ef01 100644 --- a/config/login.js +++ b/config/login.js @@ -147,3 +147,7 @@ export const agreement_expire = params => //创客空间收益 export const markBonus = params => http.post('/bonus/api/bonus/query-mark-bonus-detail', params) + +//获取分享图片 +export const getSharedImg = params => + http.get('/retail-member/api/retail-member/get-shared-image', { params }) diff --git a/config/request.js b/config/request.js index 87a834b..a7de8a3 100644 --- a/config/request.js +++ b/config/request.js @@ -20,7 +20,7 @@ module.exports = vm => { //#ifdef DEV_SERVER console.log('DEV_SERVER') - config.baseURL = '/prod-api' + config.baseURL = 'http://192.168.0.86:8080' //#endif //#ifdef QA_SERVER diff --git a/config/share.js b/config/share.js index 03161ac..4afa020 100644 --- a/config/share.js +++ b/config/share.js @@ -13,10 +13,14 @@ export const getPhoneCode = params => http.get('/member/api/share/share-sms-code', { params }) // 注册 - export const getRegister = data => http.post('/member/api/share/share-register', data) // 自动登录 export const autoLogin = data => http.post('/retail-member/api/retail-auth/auto-login', data) + +// // 获取特殊场景背景图 +// // TODO: 用户需要根据实际接口修改路径和参数 +// export const getSpecialBackground = params => +// http.get('/member/api/share/get-special-background', { params }) diff --git a/pages/mine/share/index.vue b/pages/mine/share/index.vue index 4311f2e..8ac931a 100644 --- a/pages/mine/share/index.vue +++ b/pages/mine/share/index.vue @@ -4,64 +4,44 @@ style="display: flex; flex-direction: column; height: 100vh" > - - + + + + + + - + /> @@ -69,33 +49,41 @@ @@ -336,190 +213,33 @@ export default { align-items: center; } -/* 背景图片样式 */ -.share-bg-image { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 1; -} - -.share-bg { - width: 100%; - height: 100%; - img { - width: 100%; - height: 100%; - } -} -.share-wrapper { - position: absolute; - z-index: 2; - bottom: 22%; - display: flex; - flex-direction: column; - align-items: center; -} -.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 { - opacity: 1; - transform: translateY(0); -} - -.title { - font-size: 44rpx; - font-weight: 500; - color: #1e1e1e; - margin-bottom: 60rpx; -} - -/* The single white card for the QR code */ -.qr-code-outer { - width: 400rpx; /* 从400rpx缩小到320rpx */ - height: 400rpx; /* 从400rpx缩小到320rpx */ - background: rgba(255, 255, 255, 0.98); - border-radius: 20rpx; /* 从24rpx减小到20rpx */ - box-shadow: 0px 8rpx 20rpx rgba(50, 50, 90, 0.06); - border: 1px solid #f0f0f0; - display: flex; - align-items: center; - justify-content: center; - padding: 12rpx; /* 从16rpx减小到12rpx */ - box-sizing: border-box; -} - -/* The image and the placeholder both live inside the outer card */ -.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); - } -} - -.tip { - font-size: 30rpx; - color: #888; -} - -/* Unified style for both the real button and the fake one (which is a view) */ -.share-button { - margin-top: 28rpx; /* 从32rpx减小到28rpx */ - width: 280rpx; /* 设置固定宽度 */ - height: 72rpx; /* 从88rpx减小到72rpx */ - line-height: 72rpx; - color: #fff; - border-radius: 36rpx; /* 从44rpx减小到36rpx */ - font-size: 26rpx; /* 从30rpx减小到26rpx */ - font-weight: 500; - /* 更精细的渐变 */ - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - box-shadow: 0 5rpx 12rpx 0 rgba(102, 126, 234, 0.2); - border: none; - padding: 0; - text-align: center; - transition: all 0.3s ease; - letter-spacing: 1rpx; -} - -/* The real button needs to override some uni-app defaults */ -button.share-button { - padding: 0; - line-height: 72rpx; /* 更新行高到72rpx */ - border: none; -} - -.share-button:active { - transform: translateY(1rpx); - 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; /* 移除背景色 */ + height: 100%; + background-color: transparent; z-index: 999; display: flex; flex-direction: column; - align-items: stretch; /* 拉伸对齐 */ - justify-content: stretch; /* 拉伸对齐 */ + align-items: stretch; + justify-content: stretch; padding: 0; margin: 0; } .fullscreen-image { width: 100vw; - height: 100%; /* 减去tab栏高度 */ - object-fit: cover; /* 覆盖整个容器,可能会裁剪 */ + height: 100%; + 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; @@ -529,8 +249,17 @@ button.share-button { :v-deep .u-tabbar--fixed { height: 100px !important; } - // ::v-deep .u-safe-area-inset-bottom { - // display: none !important; - // } +} + +@media screen and (max-height: 667px) { + .tabbar { + height: 70px !important; + } +} + +@media screen and (min-height: 812px) { + .tabbar { + height: 80px !important; + } }