|
|
@@ -0,0 +1,944 @@
|
|
|
+# 分享功能改造(海报组件参数化 + 5 项分享入口)实施计划
|
|
|
+
|
|
|
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
|
+
|
|
|
+**Goal:** 通过参数化现有 Canvas 海报组件,为商品详情、文章详情、家庭邀请卡、财富中心 5 处分享入口落地统一的海报分享能力,并支持按五维维度自动配色。
|
|
|
+
|
|
|
+**Architecture:** 前端纯改动,后端零改动。改造 `ContentSharePoster.vue`(文章+商品共用海报,新增 dimensionCode/summary/priceText/ctaText/inviteText props + 五维配色 + 居中弹窗),改造 `SharePoster.vue`(家庭邀请卡,新增 familyName prop + 文案 + 字号缩小 2px),4 个页面接入。
|
|
|
+
|
|
|
+**Tech Stack:** uni-app (Vue 2 Options API) / 微信小程序 canvas 2d 绘制(uni.createCanvasContext)
|
|
|
+
|
|
|
+**验证方式:** 本项目 canvas 组件无单元测试框架,验证以 `npm run build:mp-weixin` 编译通过 + 微信开发者工具手测清单为主。每个任务末尾提交 commit。
|
|
|
+
|
|
|
+**关联设计文档:** `docs/superpowers/specs/2026-08-01-share-poster-redesign-design.md`
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 前置信息(工程师必读)
|
|
|
+
|
|
|
+### 项目关键事实
|
|
|
+
|
|
|
+- 前端目录:`/sc-data/cfc/cfc-frontend/`(本计划所有路径相对此目录)
|
|
|
+- Vue 2 Options API;`formatPriceWithSymbol(cents)` 全局挂载(`Vue.prototype`),价格单位是**分**,返回带 `¥` 的字符串
|
|
|
+- 五维色系映射(多个页面已有此映射,保持一致):
|
|
|
+ - `body` 身 `#FF8C42` / `mind` 心 `#FF6B9D` / `wisdom` 智 `#6366F1` / `action` 行 `#10B981` / `wealth` 富 `#F59E0B`
|
|
|
+- 后端通用二维码接口已存在:`POST /api/share/qrcode`,入参 `{ page, scene }`,返回 `{ qrCodeBase64 }`。前端封装:`getShareQrCode(page, scene)`(`utils/api.js:1456`)
|
|
|
+- 家庭邀请二维码接口:`getInviteQrCode()`(`utils/api.js:1452`)→ `POST /api/invite/qrcode`
|
|
|
+- 家庭信息接口:`getFamilyMembers()` → `POST /api/family/user/family-members`,返回 `data.familyName`
|
|
|
+- 分享 mixin:`components/share-mixin.js`,提供 `referralCode`、`loadReferralCode()`、`autoBindInviteCode(code)`、`getSharePath()`
|
|
|
+- 昵称/头像:`uni.getStorageSync('nickname')` / `uni.getStorageSync('avatar')`
|
|
|
+- 禁止:可选链 `?.`、`:key` 表达式、CSS Grid、中文 class 名(见 `AGENTS.md`)
|
|
|
+
|
|
|
+### 微信小程序码 scene 说明
|
|
|
+
|
|
|
+- `getwxacodeunlimit` 的 scene 最大 **32 可见字符**
|
|
|
+- 商品海报 scene 格式定案:`id=<商品id>&ref=<邀请码>`(如 `id=45&ref=ABC123` = 15 字符)
|
|
|
+- 扫码后小程序直接打开 `page` 指定的页面,`scene` 出现在该页面 `onLoad(options)` 的 `options.scene` 中(需 `decodeURIComponent`)
|
|
|
+- 从 scene 进入时 `options.id` **不存在**,id 要从 scene 里解析
|
|
|
+
|
|
|
+### Canvas 绘制注意
|
|
|
+
|
|
|
+- 使用 `ctx.draw()` 一次性刷屏(每个绘制方法的最后一行)
|
|
|
+- 异步绘制(头像/封面下载、二维码 base64 写文件)用回调链,`_drawBottomDecoration` 是最终渲染点
|
|
|
+- `_drawWrappedText(ctx, text, x, y, maxWidth, lineHeight, maxLines)` 已有,用于多行截断文本
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 文件结构
|
|
|
+
|
|
|
+| 文件 | 责任 | 改动 |
|
|
|
+|---|---|---|
|
|
|
+| `components/SharePoster.vue` | 家庭邀请卡海报 | +familyName prop、+邀请文案、字号-2px |
|
|
|
+| `components/ContentSharePoster.vue` | 文章/商品共用内容海报 | +5 props、+五维配色、+简介/邀请语/价格行、居中弹窗 |
|
|
|
+| `pages/article-center/article-detail.vue` | 文章详情页 | 去分享按钮、generatePoster 传新参数 |
|
|
|
+| `pages/discover-detail/product-detail/product-detail.vue` | 商品详情页 | +分享按钮、+海报组件、+scene解析、+shareMixin |
|
|
|
+| `pages/promotion/index.vue` | 财富中心首页 | +分享给好友按钮、+海报组件 |
|
|
|
+| `pages/profile-extra/family-members.vue` | 家庭成员页 | 传 familyName |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Task 1: SharePoster.vue — 家庭邀请卡(familyName + 文案 + 字号缩小2px)
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Modify: `components/SharePoster.vue`
|
|
|
+
|
|
|
+- [ ] **Step 1: 新增 familyName prop**
|
|
|
+
|
|
|
+在 `components/SharePoster.vue` 的 props 中(`referralCode` 行之后)添加:
|
|
|
+
|
|
|
+```js
|
|
|
+ referralCode: { type: String, default: '' },
|
|
|
+ familyName: { type: String, default: '' },
|
|
|
+ qrCodeBase64: { type: String, default: '' }
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 2: 字号全面缩小 2px**
|
|
|
+
|
|
|
+将文件中所有 `ctx.setFontSize(N)` 改为 `ctx.setFontSize(N-2)`。具体位置与改动:
|
|
|
+
|
|
|
+| 行号(当前) | 原值 | 新值 |
|
|
|
+|---|---|---|
|
|
|
+| 119 | 20 | 18 |
|
|
|
+| 125 | 14 | 12 |
|
|
|
+| 265 | 24 | 22 |
|
|
|
+| 272 | 17 | 15 |
|
|
|
+| 279 | 46 | 44 |
|
|
|
+| 303 | 14 | 12 |
|
|
|
+| 370 | 16 | 14 |
|
|
|
+| 405 | 19 | 17 |
|
|
|
+| 427 | 12 | 10 |
|
|
|
+
|
|
|
+- [ ] **Step 3: 二维码上方新增邀请文案**
|
|
|
+
|
|
|
+`_drawContentAfterAvatar` 方法中,当前 `hintY` 区块(约 302-307 行)绘制的是「长按识别二维码 · 一起加入浠艾福」。将其替换为邀请文案(**保留 QR 位置不变,避免布局溢出**):
|
|
|
+
|
|
|
+```js
|
|
|
+ // --- 邀请文案(二维码上方)---
|
|
|
+ var hintY = codeY + 80
|
|
|
+ ctx.setFontSize(14)
|
|
|
+ ctx.setFillStyle('#D6D3D1')
|
|
|
+ ctx.setTextAlign('center')
|
|
|
+ ctx.setTextBaseline('top')
|
|
|
+ var inviteText = ''
|
|
|
+ if (this.nickname && this.familyName) {
|
|
|
+ inviteText = this.nickname + '邀请你来' + this.familyName
|
|
|
+ } else if (this.nickname) {
|
|
|
+ inviteText = this.nickname + '邀请你加入浠艾福'
|
|
|
+ } else {
|
|
|
+ inviteText = '长按识别二维码 · 一起加入浠艾福'
|
|
|
+ }
|
|
|
+ ctx.fillText(inviteText, w / 2, hintY)
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 4: 验证构建**
|
|
|
+
|
|
|
+```bash
|
|
|
+cd /sc-data/cfc/cfc-frontend && npm run build:mp-weixin
|
|
|
+```
|
|
|
+Expected: 编译成功,无报错。
|
|
|
+
|
|
|
+- [ ] **Step 5: Commit**
|
|
|
+
|
|
|
+```bash
|
|
|
+cd /sc-data/cfc && git add cfc-frontend/components/SharePoster.vue && git commit -m "feat(cfc-frontend): SharePoster 家庭邀请卡加 familyName 文案与邀请语,字号缩小2px"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Task 2: ContentSharePoster.vue — 参数化 + 五维配色 + 居中弹窗
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Modify: `components/ContentSharePoster.vue`
|
|
|
+
|
|
|
+- [ ] **Step 1: 新增 5 个 props**
|
|
|
+
|
|
|
+在 props 中(`typeLabel` 之后)添加:
|
|
|
+
|
|
|
+```js
|
|
|
+ typeLabel: { type: String, default: '好物推荐' },
|
|
|
+ dimensionCode: { type: String, default: '' },
|
|
|
+ summary: { type: String, default: '' },
|
|
|
+ priceText: { type: String, default: '' },
|
|
|
+ ctaText: { type: String, default: '长按识别二维码 · 查看详情' },
|
|
|
+ inviteText: { type: String, default: '' }
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 2: 新增配色计算**
|
|
|
+
|
|
|
+在 `methods` 开头(`drawPoster()` 之前)新增:
|
|
|
+
|
|
|
+```js
|
|
|
+ getColors() {
|
|
|
+ var map = {
|
|
|
+ body: { main: '#FF8C42', dark: '#EA580C', light: '#FFF7ED' },
|
|
|
+ mind: { main: '#FF6B9D', dark: '#DB2777', light: '#FDF2F8' },
|
|
|
+ wisdom: { main: '#6366F1', dark: '#4F46E5', light: '#EEF2FF' },
|
|
|
+ action: { main: '#10B981', dark: '#059669', light: '#ECFDF5' },
|
|
|
+ wealth: { main: '#F59E0B', dark: '#D97706', light: '#FFFBEB' }
|
|
|
+ }
|
|
|
+ return map[this.dimensionCode] || { main: '#F97316', dark: '#EA580C', light: '#FFF7ED' }
|
|
|
+ },
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 3: drawPoster 使用动态配色**
|
|
|
+
|
|
|
+将 `drawPoster()` 中的背景渐变(第 38-43 行)改为:
|
|
|
+
|
|
|
+```js
|
|
|
+ drawPoster() {
|
|
|
+ var ctx = uni.createCanvasContext('contentPosterCanvas', this)
|
|
|
+ var w = 500
|
|
|
+ var h = 800
|
|
|
+ var colors = this.getColors()
|
|
|
+
|
|
|
+ var gradient = ctx.createLinearGradient(0, 0, w * 0.3, h)
|
|
|
+ gradient.addColorStop(0, colors.main)
|
|
|
+ gradient.addColorStop(0.55, colors.dark)
|
|
|
+ gradient.addColorStop(1, colors.dark)
|
|
|
+ ctx.setFillStyle(gradient)
|
|
|
+ ctx.fillRect(0, 0, w, h)
|
|
|
+
|
|
|
+ this._drawTopDecoration(ctx, w, colors)
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 4: _drawTopDecoration 接收 colors 参数**
|
|
|
+
|
|
|
+将 `_drawTopDecoration(ctx, w)` 改为 `_drawTopDecoration(ctx, w, colors)`,并把方法内所有硬编码橙色改为 colors 参数。具体:
|
|
|
+
|
|
|
+```js
|
|
|
+ _drawTopDecoration(ctx, w, colors) {
|
|
|
+ var centerX = w / 2
|
|
|
+ var dotColors = ['#FF8C42', '#6366F1', '#F59E0B', '#10B981', '#FF6B9D']
|
|
|
+ var dotY = 28
|
|
|
+ var dotR = 5
|
|
|
+ var dotSpacing = 26
|
|
|
+ var i, offsetX, offsetY
|
|
|
+
|
|
|
+ for (i = 0; i < 5; i++) {
|
|
|
+ offsetX = (i - 2) * dotSpacing
|
|
|
+ offsetY = Math.abs(i - 2) * 3
|
|
|
+ ctx.setFillStyle(dotColors[i])
|
|
|
+ ctx.beginPath()
|
|
|
+ ctx.arc(centerX + offsetX, dotY + offsetY, dotR, 0, 2 * Math.PI)
|
|
|
+ ctx.fill()
|
|
|
+ }
|
|
|
+
|
|
|
+ ctx.setFillStyle('#ffffff')
|
|
|
+ ctx.setFontSize(20)
|
|
|
+ ctx.setTextAlign('center')
|
|
|
+ ctx.setTextBaseline('top')
|
|
|
+ ctx.fillText('浠艾福', centerX, 52)
|
|
|
+
|
|
|
+ ctx.setFontSize(14)
|
|
|
+ ctx.setFillStyle('rgba(255,255,255,0.8)')
|
|
|
+ ctx.fillText('给全家的一站式幸福提案', centerX, 76)
|
|
|
+
|
|
|
+ ctx.setStrokeStyle('rgba(255,255,255,0.25)')
|
|
|
+ ctx.setLineWidth(1)
|
|
|
+ ctx.beginPath()
|
|
|
+ ctx.moveTo(80, 100)
|
|
|
+ ctx.lineTo(w - 80, 100)
|
|
|
+ ctx.stroke()
|
|
|
+ },
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 5: _drawContentCard 使用动态配色**
|
|
|
+
|
|
|
+将 `_drawContentCard(ctx, x, y, w, h)` 改为接收 colors 并改用动态色:
|
|
|
+
|
|
|
+```js
|
|
|
+ _drawContentCard(ctx, x, y, w, h, colors) {
|
|
|
+ ctx.setFillStyle('rgba(0,0,0,0.04)')
|
|
|
+ this._roundRect(ctx, x + 2, y + 3, w, h, 16)
|
|
|
+ ctx.fill()
|
|
|
+
|
|
|
+ ctx.setFillStyle(colors.light)
|
|
|
+ this._roundRect(ctx, x, y, w, h, 16)
|
|
|
+ ctx.fill()
|
|
|
+ },
|
|
|
+```
|
|
|
+
|
|
|
+并在 `drawPoster()` 中的调用处改为 `this._drawContentCard(ctx, cardX, cardY, cardW, cardH, colors)`。
|
|
|
+
|
|
|
+- [ ] **Step 6: 重写 _drawContentAfterCover(加入简介/邀请语/价格行)**
|
|
|
+
|
|
|
+将整个 `_drawContentAfterCover` 方法(第 137-203 行)替换为:
|
|
|
+
|
|
|
+```js
|
|
|
+ _drawContentAfterCover(ctx, w, h, cardX, cardW, coverY, coverH, colors) {
|
|
|
+ var self = this
|
|
|
+
|
|
|
+ var labelY = coverY + coverH + 16
|
|
|
+ ctx.setFillStyle(colors.main)
|
|
|
+ ctx.setFontSize(13)
|
|
|
+ ctx.setTextAlign('left')
|
|
|
+ ctx.setTextBaseline('top')
|
|
|
+ ctx.fillText(this.typeLabel, cardX + 20, labelY)
|
|
|
+
|
|
|
+ // 标题(2行截断)
|
|
|
+ var titleY = labelY + 22
|
|
|
+ ctx.setFillStyle('#1C1917')
|
|
|
+ ctx.setFontSize(18)
|
|
|
+ ctx.setTextAlign('left')
|
|
|
+ ctx.setTextBaseline('top')
|
|
|
+ this._drawWrappedText(ctx, this.title || '精彩内容', cardX + 20, titleY, cardW - 40, 24, 2)
|
|
|
+
|
|
|
+ // 价格行(商品用;空则跳过)
|
|
|
+ var priceY = titleY + 56
|
|
|
+ if (this.priceText) {
|
|
|
+ ctx.setFillStyle(colors.main)
|
|
|
+ ctx.setFontSize(17)
|
|
|
+ ctx.setTextAlign('left')
|
|
|
+ ctx.setTextBaseline('top')
|
|
|
+ ctx.fillText(this.priceText, cardX + 20, priceY)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 简介(1-2行灰字;空则跳过)
|
|
|
+ var summaryY = (this.priceText ? priceY + 34 : priceY)
|
|
|
+ if (this.summary) {
|
|
|
+ ctx.setFillStyle('#78716C')
|
|
|
+ ctx.setFontSize(13)
|
|
|
+ ctx.setTextAlign('left')
|
|
|
+ ctx.setTextBaseline('top')
|
|
|
+ this._drawWrappedText(ctx, this.summary, cardX + 20, summaryY, cardW - 40, 20, 2)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 邀请语(二维码上方)
|
|
|
+ var inviteY = (this.summary ? summaryY + 48 : summaryY)
|
|
|
+ ctx.setFillStyle('#A8A29E')
|
|
|
+ ctx.setFontSize(13)
|
|
|
+ ctx.setTextAlign('center')
|
|
|
+ ctx.setTextBaseline('top')
|
|
|
+ ctx.fillText(this.inviteText || '长按识别二维码 · 查看详情', w / 2, inviteY)
|
|
|
+
|
|
|
+ // 二维码
|
|
|
+ var qrSize = 150
|
|
|
+ var qrX = (w - qrSize) / 2
|
|
|
+ var qrY = inviteY + 24
|
|
|
+
|
|
|
+ if (this.qrCodeBase64) {
|
|
|
+ var base64Data = this.qrCodeBase64
|
|
|
+ if (base64Data.indexOf('data:image') === 0) {
|
|
|
+ var commaIdx = base64Data.indexOf(',')
|
|
|
+ if (commaIdx > -1) {
|
|
|
+ base64Data = base64Data.substring(commaIdx + 1)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ var fs = uni.getFileSystemManager()
|
|
|
+ var filePath = wx.env.USER_DATA_PATH + '/content_share_qr_tmp.png'
|
|
|
+
|
|
|
+ fs.writeFile({
|
|
|
+ filePath: filePath,
|
|
|
+ data: base64Data,
|
|
|
+ encoding: 'base64',
|
|
|
+ success: function() {
|
|
|
+ ctx.setFillStyle('#ffffff')
|
|
|
+ self._roundRect(ctx, qrX - 10, qrY - 10, qrSize + 20, qrSize + 20, 8)
|
|
|
+ ctx.fill()
|
|
|
+
|
|
|
+ ctx.setStrokeStyle('#E7E5E4')
|
|
|
+ ctx.setLineWidth(0.5)
|
|
|
+ self._roundRect(ctx, qrX - 10, qrY - 10, qrSize + 20, qrSize + 20, 8)
|
|
|
+ ctx.stroke()
|
|
|
+
|
|
|
+ ctx.drawImage(filePath, qrX, qrY, qrSize, qrSize)
|
|
|
+ self._drawBottomDecoration(ctx, w, h, qrY, qrSize, colors)
|
|
|
+ },
|
|
|
+ fail: function() {
|
|
|
+ self._drawQrPlaceholder(ctx, qrX, qrY, qrSize)
|
|
|
+ self._drawBottomDecoration(ctx, w, h, qrY, qrSize, colors)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ this._drawQrPlaceholder(ctx, qrX, qrY, qrSize)
|
|
|
+ this._drawBottomDecoration(ctx, w, h, qrY, qrSize, colors)
|
|
|
+ }
|
|
|
+ },
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 7: _drawBottomDecoration 使用动态配色 + ctaText**
|
|
|
+
|
|
|
+将 `_drawBottomDecoration(ctx, w, h, qrY, qrSize)` 改为接收 colors,并替换 CTA 文本和颜色:
|
|
|
+
|
|
|
+```js
|
|
|
+ _drawBottomDecoration(ctx, w, h, qrY, qrSize, colors) {
|
|
|
+ var centerX = w / 2
|
|
|
+ var bottomY = qrY + qrSize + 16
|
|
|
+
|
|
|
+ var diamondY = bottomY + 6
|
|
|
+ ctx.setFillStyle('rgba(0,0,0,0.06)')
|
|
|
+ ctx.beginPath()
|
|
|
+ ctx.moveTo(centerX - 76, diamondY)
|
|
|
+ ctx.lineTo(centerX - 72, diamondY - 4)
|
|
|
+ ctx.lineTo(centerX - 68, diamondY)
|
|
|
+ ctx.lineTo(centerX - 72, diamondY + 4)
|
|
|
+ ctx.closePath()
|
|
|
+ ctx.fill()
|
|
|
+ ctx.beginPath()
|
|
|
+ ctx.moveTo(centerX + 68, diamondY)
|
|
|
+ ctx.lineTo(centerX + 72, diamondY - 4)
|
|
|
+ ctx.lineTo(centerX + 76, diamondY)
|
|
|
+ ctx.lineTo(centerX + 72, diamondY + 4)
|
|
|
+ ctx.closePath()
|
|
|
+ ctx.fill()
|
|
|
+
|
|
|
+ ctx.setFillStyle(colors.main)
|
|
|
+ ctx.setFontSize(18)
|
|
|
+ ctx.setTextAlign('center')
|
|
|
+ ctx.setTextBaseline('top')
|
|
|
+ ctx.fillText(this.ctaText || '长按识别二维码 · 查看详情', centerX, bottomY)
|
|
|
+
|
|
|
+ var dotColors = ['#FF8C42', '#6366F1', '#F59E0B', '#10B981', '#FF6B9D']
|
|
|
+ var dotY = h - 22
|
|
|
+ var dotR = 4
|
|
|
+ var dotSpacing = 20
|
|
|
+ var i_b, offsetX_b
|
|
|
+
|
|
|
+ for (i_b = 0; i_b < 5; i_b++) {
|
|
|
+ offsetX_b = (i_b - 2) * dotSpacing
|
|
|
+ ctx.setFillStyle(dotColors[i_b])
|
|
|
+ ctx.beginPath()
|
|
|
+ ctx.arc(centerX + offsetX_b, dotY, dotR, 0, 2 * Math.PI)
|
|
|
+ ctx.fill()
|
|
|
+ }
|
|
|
+
|
|
|
+ ctx.setFillStyle('rgba(255,255,255,0.45)')
|
|
|
+ ctx.setFontSize(12)
|
|
|
+ ctx.setTextAlign('center')
|
|
|
+ ctx.setTextBaseline('top')
|
|
|
+ ctx.fillText('浠艾福 · 家庭健康俱乐部', centerX, dotY + 7)
|
|
|
+
|
|
|
+ ctx.draw()
|
|
|
+ },
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 8: 更新 drawPoster 中其他调用点**
|
|
|
+
|
|
|
+`drawPoster()` 中:
|
|
|
+- `this._drawContentCard(ctx, cardX, cardY, cardW, cardH)` → `this._drawContentCard(ctx, cardX, cardY, cardW, cardH, colors)`
|
|
|
+- 三处 `self._drawContentAfterCover(ctx, w, h, cardX, cardW, coverY, coverH)` → `self._drawContentAfterCover(ctx, w, h, cardX, cardW, coverY, coverH, colors)`
|
|
|
+
|
|
|
+- [ ] **Step 9: 弹窗样式 — 居中卡片 + 去掉取消按钮**
|
|
|
+
|
|
|
+模板(第 5-8 行)去掉「取消」按钮,只保留保存:
|
|
|
+
|
|
|
+```html
|
|
|
+ <view class="poster-actions">
|
|
|
+ <button class="poster-btn save" @click="savePoster">保存到相册</button>
|
|
|
+ </view>
|
|
|
+```
|
|
|
+
|
|
|
+样式(355-404 行)改为更紧凑的居中卡片:
|
|
|
+
|
|
|
+```css
|
|
|
+.poster-overlay {
|
|
|
+ position: fixed;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
+ background: rgba(0, 0, 0, 0.6);
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ z-index: 999;
|
|
|
+}
|
|
|
+.poster-container {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+.poster-canvas {
|
|
|
+ width: 620rpx;
|
|
|
+ height: 992rpx;
|
|
|
+ border-radius: 16rpx;
|
|
|
+}
|
|
|
+.poster-actions {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ width: 620rpx;
|
|
|
+ margin-top: 30rpx;
|
|
|
+}
|
|
|
+.poster-btn {
|
|
|
+ height: 80rpx;
|
|
|
+ line-height: 80rpx;
|
|
|
+ font-size: 30rpx;
|
|
|
+ border-radius: 40rpx;
|
|
|
+ text-align: center;
|
|
|
+ border: none;
|
|
|
+}
|
|
|
+.poster-btn::after { border: none; }
|
|
|
+.poster-btn.save {
|
|
|
+ background: #ffffff;
|
|
|
+ color: #F97316;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+.poster-btn.save:active { opacity: 0.85; }
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 10: 验证构建**
|
|
|
+
|
|
|
+```bash
|
|
|
+cd /sc-data/cfc/cfc-frontend && npm run build:mp-weixin
|
|
|
+```
|
|
|
+Expected: 编译成功,无报错。
|
|
|
+
|
|
|
+- [ ] **Step 11: Commit**
|
|
|
+
|
|
|
+```bash
|
|
|
+cd /sc-data/cfc && git add cfc-frontend/components/ContentSharePoster.vue && git commit -m "feat(cfc-frontend): ContentSharePoster 参数化五维配色+简介/邀请语/价格行+居中弹窗"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Task 3: 文章详情页 — 去分享按钮 + 传新参数
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Modify: `pages/article-center/article-detail.vue`
|
|
|
+
|
|
|
+- [ ] **Step 1: 去掉「分享」按钮**
|
|
|
+
|
|
|
+模板底部(约 43-46 行)删除 `open-type="share"` 的 button,只留海报按钮:
|
|
|
+
|
|
|
+```html
|
|
|
+ <!-- 底部 -->
|
|
|
+ <view class="detail-footer">
|
|
|
+ <view class="share-btn" @click="generatePoster">
|
|
|
+ <text class="share-btn-icon">🖼</text>
|
|
|
+ <text class="share-btn-text">海报</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 2: data 新增海报参数**
|
|
|
+
|
|
|
+在 `data()` 的 `showPoster: false, posterQrCode: ''` 行后添加:
|
|
|
+
|
|
|
+```js
|
|
|
+ showPoster: false, posterQrCode: '',
|
|
|
+ posterDimension: '', posterSummary: '', posterInviteText: '',
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 3: 新增 getMainDimension 方法**
|
|
|
+
|
|
|
+在 `parseDimensions` 方法后添加:
|
|
|
+
|
|
|
+```js
|
|
|
+ getMainDimension: function() {
|
|
|
+ var dims = this.article ? this.parseDimensions(this.article) : []
|
|
|
+ var best = null
|
|
|
+ var i
|
|
|
+ for (i = 0; i < dims.length; i++) {
|
|
|
+ if (!best || dims[i].weight > best.weight) best = dims[i]
|
|
|
+ }
|
|
|
+ return best ? best.code : ''
|
|
|
+ },
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 4: generatePoster 传新参数**
|
|
|
+
|
|
|
+将 `generatePoster` 方法改为:
|
|
|
+
|
|
|
+```js
|
|
|
+ async generatePoster() {
|
|
|
+ if (!this.articleId) return
|
|
|
+ uni.showLoading({ title: '生成海报中...' })
|
|
|
+ try {
|
|
|
+ var res = await getShareQrCode('pages/article-center/article-detail', 'id=' + this.articleId)
|
|
|
+ if (res && res.data) {
|
|
|
+ this.posterQrCode = res.data.qrCodeBase64 || ''
|
|
|
+ this.posterDimension = this.getMainDimension()
|
|
|
+ this.posterSummary = (this.article && this.article.summary) || ''
|
|
|
+ this.posterInviteText = '「' + (uni.getStorageSync('nickname') || '好友') + '」邀请你一起阅读这篇文章'
|
|
|
+ this.showPoster = true
|
|
|
+ }
|
|
|
+ } catch (e) { uni.showToast({ title: '生成失败', icon: 'none' }) }
|
|
|
+ finally { uni.hideLoading() }
|
|
|
+ },
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 5: 模板传参**
|
|
|
+
|
|
|
+将 `ContentSharePoster` 标签改为:
|
|
|
+
|
|
|
+```html
|
|
|
+ <ContentSharePoster :show="showPoster" :title="article && article.title" :coverImage="article && article.coverImage" :qrCodeBase64="posterQrCode" typeLabel="好文推荐" :dimensionCode="posterDimension" :summary="posterSummary" :inviteText="posterInviteText" @close="closePoster" />
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 6: 验证构建**
|
|
|
+
|
|
|
+```bash
|
|
|
+cd /sc-data/cfc/cfc-frontend && npm run build:mp-weixin
|
|
|
+```
|
|
|
+Expected: 编译成功,无报错。
|
|
|
+
|
|
|
+- [ ] **Step 7: Commit**
|
|
|
+
|
|
|
+```bash
|
|
|
+cd /sc-data/cfc && git add cfc-frontend/pages/article-center/article-detail.vue && git commit -m "feat(cfc-frontend): 文章详情去掉分享按钮,海报传维度/简介/邀请语"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Task 4: 商品详情页 — 加分享按钮 + 海报 + scene 解析
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Modify: `pages/discover-detail/product-detail/product-detail.vue`
|
|
|
+
|
|
|
+- [ ] **Step 1: 引入依赖**
|
|
|
+
|
|
|
+script 开头(`import MpHtml` 之后)添加:
|
|
|
+
|
|
|
+```js
|
|
|
+import { productDetail, productOrderCreate, productOrderPay, cartAdd, getShareQrCode } from '@/utils/api.js'
|
|
|
+import MpHtml from '@/components/mp-html/mp-html.vue'
|
|
|
+import ContentSharePoster from '@/components/ContentSharePoster.vue'
|
|
|
+import shareMixin from '../../../components/share-mixin.js'
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 2: 注册组件与 mixin**
|
|
|
+
|
|
|
+```js
|
|
|
+export default {
|
|
|
+ components: { MpHtml, ContentSharePoster },
|
|
|
+ mixins: [shareMixin],
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ product: {},
|
|
|
+ loading: false,
|
|
|
+ showPoster: false,
|
|
|
+ posterQrCode: '',
|
|
|
+ posterDimension: '',
|
|
|
+ posterInviteText: ''
|
|
|
+ }
|
|
|
+ },
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 3: 新增 priceText / productDimension 计算属性**
|
|
|
+
|
|
|
+在 `domainLabel()` 计算属性后添加:
|
|
|
+
|
|
|
+```js
|
|
|
+ priceText() {
|
|
|
+ if (!this.product || !this.product.price) return ''
|
|
|
+ var txt = this.formatPriceWithSymbol(this.product.price)
|
|
|
+ if (this.product.memberPrice) {
|
|
|
+ txt += ' 会员' + this.formatPriceWithSymbol(this.product.memberPrice)
|
|
|
+ }
|
|
|
+ return txt
|
|
|
+ },
|
|
|
+ productDimension() {
|
|
|
+ var p = this.product
|
|
|
+ if (!p) return ''
|
|
|
+ if (p.dimensionWeights) {
|
|
|
+ try {
|
|
|
+ var w = JSON.parse(p.dimensionWeights)
|
|
|
+ if (w && typeof w === 'object') {
|
|
|
+ var best = null
|
|
|
+ var bestWeight = -1
|
|
|
+ var keys = Object.keys(w)
|
|
|
+ for (var i = 0; i < keys.length; i++) {
|
|
|
+ if (w[keys[i]] > bestWeight) {
|
|
|
+ bestWeight = w[keys[i]]
|
|
|
+ best = keys[i]
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (best) return best
|
|
|
+ }
|
|
|
+ } catch (e) {}
|
|
|
+ }
|
|
|
+ if (p.domain) return p.domain
|
|
|
+ return ''
|
|
|
+ },
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 4: onLoad 解析 scene(id + ref)**
|
|
|
+
|
|
|
+将现有 onLoad 改为:
|
|
|
+
|
|
|
+```js
|
|
|
+ onLoad(options) {
|
|
|
+ var pid = options && options.id ? options.id : ''
|
|
|
+ if (options && options.scene) {
|
|
|
+ try {
|
|
|
+ var sceneDecoded = decodeURIComponent(options.scene)
|
|
|
+ var idMatch = sceneDecoded.match(/id=(\d+)/)
|
|
|
+ if (idMatch && idMatch[1]) pid = idMatch[1]
|
|
|
+ var refMatch = sceneDecoded.match(/ref=([^&]+)/)
|
|
|
+ if (refMatch && refMatch[1]) this.autoBindInviteCode(refMatch[1])
|
|
|
+ } catch (e) {}
|
|
|
+ }
|
|
|
+ if (pid) {
|
|
|
+ this.loadDetail(pid)
|
|
|
+ }
|
|
|
+ },
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 5: 新增 onShareProduct 方法**
|
|
|
+
|
|
|
+在 `onAddToCart` 方法后添加:
|
|
|
+
|
|
|
+```js
|
|
|
+ async onShareProduct() {
|
|
|
+ if (!this.product || !this.product.id) return
|
|
|
+ uni.showLoading({ title: '生成海报中...' })
|
|
|
+ try {
|
|
|
+ if (!this.referralCode) {
|
|
|
+ await this.loadReferralCode()
|
|
|
+ }
|
|
|
+ var scene = 'id=' + this.product.id + (this.referralCode ? '&ref=' + this.referralCode : '')
|
|
|
+ if (scene.length > 32) {
|
|
|
+ scene = scene.substring(0, 32)
|
|
|
+ }
|
|
|
+ var res = await getShareQrCode('pages/discover-detail/product-detail/product-detail', scene)
|
|
|
+ if (res && res.data) {
|
|
|
+ this.posterQrCode = res.data.qrCodeBase64 || ''
|
|
|
+ this.posterDimension = this.productDimension
|
|
|
+ this.posterInviteText = '「' + (uni.getStorageSync('nickname') || '好友') + '」邀请你一起看看这个好物'
|
|
|
+ this.showPoster = true
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ uni.showToast({ title: '生成失败', icon: 'none' })
|
|
|
+ } finally {
|
|
|
+ uni.hideLoading()
|
|
|
+ }
|
|
|
+ },
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 6: 底部栏加「分享」按钮**
|
|
|
+
|
|
|
+模板底部栏(约 47-52 行)在「加入购物车」前加分享按钮:
|
|
|
+
|
|
|
+```html
|
|
|
+ <view class="action-btns">
|
|
|
+ <view class="action-extra">
|
|
|
+ <button class="btn-share" @click="onShareProduct">分享</button>
|
|
|
+ </view>
|
|
|
+ <view class="action-extra">
|
|
|
+ <button class="btn-cart" @click="onAddToCart">加入购物车</button>
|
|
|
+ </view>
|
|
|
+ <button class="btn-buy" @click="onBuy">立即购买</button>
|
|
|
+ </view>
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 7: 底部栏外添加海报组件**
|
|
|
+
|
|
|
+在 `</scroll-view>` 之后、`<view v-else class="empty-wrap">` 之前(或模板末尾 `</view>` 之前)添加:
|
|
|
+
|
|
|
+```html
|
|
|
+ <ContentSharePoster :show="showPoster" :title="product.name" :coverImage="product.coverImage" :qrCodeBase64="posterQrCode" typeLabel="好物推荐" :dimensionCode="posterDimension" :priceText="priceText" :summary="product.intro" ctaText="长按识别二维码 · 查看商品" :inviteText="posterInviteText" @close="showPoster = false" />
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 8: 新增 .btn-share 样式**
|
|
|
+
|
|
|
+在 style 中 `.btn-cart` 样式后添加:
|
|
|
+
|
|
|
+```css
|
|
|
+.btn-share {
|
|
|
+ background: #fff;
|
|
|
+ color: #666;
|
|
|
+ border: 2rpx solid #ddd;
|
|
|
+ font-size: 26rpx;
|
|
|
+ padding: 0 24rpx;
|
|
|
+ height: 64rpx;
|
|
|
+ line-height: 64rpx;
|
|
|
+ border-radius: 32rpx;
|
|
|
+ margin-right: 16rpx;
|
|
|
+}
|
|
|
+.btn-share::after {
|
|
|
+ border: none;
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 9: 验证构建**
|
|
|
+
|
|
|
+```bash
|
|
|
+cd /sc-data/cfc/cfc-frontend && npm run build:mp-weixin
|
|
|
+```
|
|
|
+Expected: 编译成功,无报错。
|
|
|
+
|
|
|
+- [ ] **Step 10: Commit**
|
|
|
+
|
|
|
+```bash
|
|
|
+cd /sc-data/cfc && git add cfc-frontend/pages/discover-detail/product-detail/product-detail.vue && git commit -m "feat(cfc-frontend): 商品详情加分享海报按钮+scene邀请码解析"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Task 5: 财富中心首页 — 加「分享给好友」按钮 + 海报
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Modify: `pages/promotion/index.vue`
|
|
|
+
|
|
|
+- [ ] **Step 1: 引入依赖**
|
|
|
+
|
|
|
+script 部分 import(现有 `getReferralSummary` 等 import 行)后添加:
|
|
|
+
|
|
|
+```js
|
|
|
+import { getReferralSummary, getCommissionSummary, getFamilyEarningsSummary, getFamilyEarningsMembers, getFamilyEarningsRecords, distributeToMember, toggleMemberBalance, withdrawFamilyEarnings, getInviteQrCode, getFamilyMembers, getReferralCode } from '../../utils/api.js'
|
|
|
+import SharePoster from '@/components/SharePoster.vue'
|
|
|
+```
|
|
|
+
|
|
|
+(注意:将 `getInviteQrCode`, `getFamilyMembers`, `getReferralCode` 合并进现有 import 语句,若已有部分则去重。)
|
|
|
+
|
|
|
+- [ ] **Step 2: 注册组件 + data**
|
|
|
+
|
|
|
+```js
|
|
|
+export default {
|
|
|
+ components: { SharePoster },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ // ... 现有字段 ...
|
|
|
+ showPoster: false,
|
|
|
+ inviteQrCode: '',
|
|
|
+ familyName: '',
|
|
|
+ shareNickname: '',
|
|
|
+ shareAvatar: '',
|
|
|
+ shareReferralCode: ''
|
|
|
+ }
|
|
|
+ },
|
|
|
+```
|
|
|
+
|
|
|
+(在现有 data 字段后追加 5 个新字段,保留原有字段不变。)
|
|
|
+
|
|
|
+- [ ] **Step 3: 菜单区加「分享给好友」按钮**
|
|
|
+
|
|
|
+在「邀请好友」menu-item(约 167-171 行)之后添加:
|
|
|
+
|
|
|
+```html
|
|
|
+ <view class="menu-item" @click="shareToFriend">
|
|
|
+ <view class="menu-icon-wrap icon-share">
|
|
|
+ <text class="menu-icon-text">📤</text>
|
|
|
+ </view>
|
|
|
+ <text class="menu-text">分享给好友</text>
|
|
|
+ </view>
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 4: 新增 shareToFriend 方法**
|
|
|
+
|
|
|
+在 `goInvite()` 方法后添加:
|
|
|
+
|
|
|
+```js
|
|
|
+ async shareToFriend() {
|
|
|
+ uni.showLoading({ title: '生成中...' })
|
|
|
+ try {
|
|
|
+ if (!this.shareReferralCode) {
|
|
|
+ try {
|
|
|
+ var codeRes = await getReferralCode()
|
|
|
+ if (codeRes && codeRes.data) {
|
|
|
+ this.shareReferralCode = codeRes.data.referralCode || codeRes.data.code || ''
|
|
|
+ }
|
|
|
+ } catch (e) {}
|
|
|
+ }
|
|
|
+ var qrRes = await getInviteQrCode()
|
|
|
+ if (qrRes && qrRes.data) {
|
|
|
+ this.inviteQrCode = qrRes.data.qrCodeBase64 || ''
|
|
|
+ }
|
|
|
+ if (!this.familyName) {
|
|
|
+ try {
|
|
|
+ var famRes = await getFamilyMembers()
|
|
|
+ if (famRes.code === 200 && famRes.data && famRes.data.familyName) {
|
|
|
+ this.familyName = famRes.data.familyName
|
|
|
+ }
|
|
|
+ } catch (e) {}
|
|
|
+ }
|
|
|
+ this.shareNickname = uni.getStorageSync('nickname') || ''
|
|
|
+ this.shareAvatar = uni.getStorageSync('avatar') || ''
|
|
|
+ this.showPoster = true
|
|
|
+ } catch (e) {
|
|
|
+ uni.showToast({ title: '生成失败', icon: 'none' })
|
|
|
+ } finally {
|
|
|
+ uni.hideLoading()
|
|
|
+ }
|
|
|
+ },
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 5: 模板加海报组件**
|
|
|
+
|
|
|
+在 `</scroll-view>` 之前(模板末尾)添加:
|
|
|
+
|
|
|
+```html
|
|
|
+ <SharePoster :show="showPoster" :referralCode="shareReferralCode" :qrCodeBase64="inviteQrCode" :nickname="shareNickname" :userAvatar="shareAvatar" :familyName="familyName" @close="showPoster = false" />
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 6: 新增样式**
|
|
|
+
|
|
|
+在 `.icon-leaderboard` 样式后添加:
|
|
|
+
|
|
|
+```css
|
|
|
+.icon-share { background: #E0F2FE; }
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 7: 验证构建**
|
|
|
+
|
|
|
+```bash
|
|
|
+cd /sc-data/cfc/cfc-frontend && npm run build:mp-weixin
|
|
|
+```
|
|
|
+Expected: 编译成功,无报错。
|
|
|
+
|
|
|
+- [ ] **Step 8: Commit**
|
|
|
+
|
|
|
+```bash
|
|
|
+cd /sc-data/cfc && git add cfc-frontend/pages/promotion/index.vue && git commit -m "feat(cfc-frontend): 财富中心首页加分享给好友按钮与邀请海报"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Task 6: 家庭成员页 — 传 familyName
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Modify: `pages/profile-extra/family-members.vue`
|
|
|
+
|
|
|
+- [ ] **Step 1: 传 familyName**
|
|
|
+
|
|
|
+模板第 35 行的 SharePoster 标签改为:
|
|
|
+
|
|
|
+```html
|
|
|
+ <SharePoster :show="showPoster" :referralCode="inviteCode" :qrCodeBase64="inviteQrCode" :nickname="userNickname" :userAvatar="userAvatar" :familyName="familyName" @close="showPoster = false" />
|
|
|
+```
|
|
|
+
|
|
|
+(`familyName` 已在 data 和 loadFamilyInfo 中赋值,无需其他改动。)
|
|
|
+
|
|
|
+- [ ] **Step 2: 验证构建**
|
|
|
+
|
|
|
+```bash
|
|
|
+cd /sc-data/cfc/cfc-frontend && npm run build:mp-weixin
|
|
|
+```
|
|
|
+Expected: 编译成功,无报错。
|
|
|
+
|
|
|
+- [ ] **Step 3: Commit**
|
|
|
+
|
|
|
+```bash
|
|
|
+cd /sc-data/cfc && git add cfc-frontend/pages/profile-extra/family-members.vue && git commit -m "feat(cfc-frontend): 家庭成员邀请卡传入家庭名"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Task 7: 全量验证 + 手测清单
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- 无代码改动
|
|
|
+
|
|
|
+- [ ] **Step 1: 前端全量构建**
|
|
|
+
|
|
|
+```bash
|
|
|
+cd /sc-data/cfc/cfc-frontend && npm run build:mp-weixin
|
|
|
+```
|
|
|
+Expected: 编译成功,exit code 0。
|
|
|
+
|
|
|
+- [ ] **Step 2: 后端编译确认(零改动,确保不受影响)**
|
|
|
+
|
|
|
+```bash
|
|
|
+cd /sc-data/cfc/cfc-backend && mvn compile -q
|
|
|
+```
|
|
|
+Expected: BUILD SUCCESS(无输出即成功)。
|
|
|
+
|
|
|
+- [ ] **Step 3: 微信开发者工具手测清单**
|
|
|
+
|
|
|
+导入 `dist/dev/mp-weixin/`(或 `dist/build/mp-weixin/`)到微信开发者工具,逐项验证:
|
|
|
+
|
|
|
+| # | 场景 | 预期 |
|
|
|
+|---|---|---|
|
|
|
+| 1 | 文章详情页 | 只有「海报」按钮,无「分享」按钮;微信右上角转发带邀请码 |
|
|
|
+| 2 | 文章海报 | 弹窗居中、不遮全屏、点周边关闭;有简介、邀请语;配色随文章维度变化(身橙/心粉/智蓝/行绿/富金) |
|
|
|
+| 3 | 保存海报 | 保存到相册成功;无相册权限时弹出去设置引导 |
|
|
|
+| 4 | 商品详情页 | 底部有「分享」按钮;点击生成商品海报(封面+名称+价格+邀请语+二维码) |
|
|
|
+| 5 | 扫描商品海报二维码 | 打开商品详情页,且邀请码自动绑定(后端 400 说明已有邀请人则跳过) |
|
|
|
+| 6 | 家庭成员页「分享邀请卡」 | 海报文字缩小 2px;二维码上方有「{昵称}邀请你来{家庭名}」 |
|
|
|
+| 7 | 财富中心「分享给好友」 | 点击弹出家庭邀请卡海报,含家庭名 |
|
|
|
+| 8 | 无家庭名兜底 | 海报显示「{昵称}邀请你加入浠艾福」 |
|
|
|
+
|
|
|
+- [ ] **Step 4: 提交收尾(若有手测发现的小修复,单独提交)**
|
|
|
+
|
|
|
+```bash
|
|
|
+cd /sc-data/cfc && git status && git log --oneline -8
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 风险与回滚
|
|
|
+
|
|
|
+- 每个任务独立 commit,可单独 `git revert <commit>` 回滚
|
|
|
+- 最复杂的是 Task 2(ContentSharePoster 重构),canvas 布局改动集中,若真机显示异常优先检查 `_drawContentAfterCover` 的 Y 坐标计算
|
|
|
+- scene 32 字符限制:`id=<id>&ref=<code>` 中 id 为多位数字时也足够(id 上限 9 位数 + ref 6 位 = 约 20 字符),后端还有截断保护
|