feat(share): 启用全站系统分享 + 完善分享文案配置

- 各 Page 补齐 onShareAppMessage/onShareTimeline,修复右上角转发/朋友圈按钮灰色
- ui-btn 透传 open-type,完成弹窗接入分享按钮
- config.share 集中维护文案,动态标题用 titleTemplate 占位符
- leaderboard 的 myEntry.rank 缺失时回退静态标题,避免出现"排第 undefined"
- 完成弹窗分享按钮高度对齐 88rpx,与同排按钮等高

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
liucheng
2026-07-25 11:03:02 +08:00
parent 3e1d3bd05e
commit e712741425
9 changed files with 190 additions and 3 deletions
+7 -1
View File
@@ -6,7 +6,13 @@ Component({
block: { type: Boolean, value: false }, block: { type: Boolean, value: false },
disabled: { type: Boolean, value: false }, disabled: { type: Boolean, value: false },
iconSrc: { type: String, value: '' }, iconSrc: { type: String, value: '' },
customStyle: { type: String, value: '' } customStyle: { type: String, value: '' },
// 透传微信原生 button 的 open-type:
// share — 触发 Page.onShareAppMessage(分享给朋友)
// contact / feedback / shareToPhone … 等
// 设置后会在底层 button 上挂 open-type="{{openType}}",配合 Page 的
// onShareAppMessage 即可让自定义按钮真正调起原生分享面板。
openType: { type: String, value: '' }
}, },
methods: { methods: {
onTap() { onTap() {
+1
View File
@@ -3,6 +3,7 @@
style="{{customStyle}}" style="{{customStyle}}"
bindtap="onTap" bindtap="onTap"
disabled="{{disabled}}" disabled="{{disabled}}"
open-type="{{openType}}"
hover-class="none" hover-class="none"
> >
<image <image
+51 -1
View File
@@ -52,5 +52,55 @@ module.exports = {
], ],
/** 姿势要领每条显示的秒数 */ /** 姿势要领每条显示的秒数 */
tipInterval: 10 tipInterval: 10,
/**
* 小程序分享配置。微信分享能力依赖每个 Page 实现 onShareAppMessage /
* onShareTimeline,这里集中维护所有场景的分享文案,改这里即可。
*
* 各 Page 根据自身场景从 share 字典里取对应的文案:
* - share.default 默认(打开小程序时的转发)
* - share.timer 训练完成时(最常见的分享节点)
* - share.records 训练记录页(晒成绩)
* - share.leaderboard 排行榜页(榜单拉人)
* - share.settings 设置页(基本不会分享)
*
* 每个场景的 title 是静态兜底文案;带动态数据的场景额外提供
* titleTemplate,用 {占位符} 标记运行时替换的位置,页面 replace 后使用:
* - timer.titleTemplate {elapsed} 实际撑的秒数
* - timer.timelineTitleTemplate {elapsed} 朋友圈晒成绩
* - records.titleTemplate {minutes} 累计训练分钟数
* - leaderboard.titleTemplate {period}/{rank} 榜单周期与名次
* 数据缺失时页面回退到静态 title,避免分享卡片出现裸占位符。
*
* timelineTitle 用于非 timer 页的 onShareTimeline(分享到朋友圈)。
* shareImageUrl 为空时微信自动用页面截图,无需提前准备图片。
*/
share: {
default: {
title: '平板支撑训练 — 每天一分钟,撑出好体态',
path: '/pages/index/index'
},
timer: {
title: '我刚完成一次平板支撑训练,一起来打卡!',
titleTemplate: '我刚撑了 {elapsed} 秒!一起来平板支撑打卡',
timelineTitleTemplate: '今天平板支撑 {elapsed} 秒,你能超过我吗?',
path: '/pages/index/index'
},
records: {
title: '看看我的平板支撑训练记录',
titleTemplate: '累计平板支撑 {minutes} 分钟,你也来试试',
path: '/pages/records/records'
},
leaderboard: {
title: '平板支撑训练排行榜,看看你能撑多久?',
titleTemplate: '我在平板支撑{period}榜排第 {rank},你能超过我吗?',
path: '/pages/leaderboard/leaderboard'
},
settings: {
title: '平板支撑训练小程序',
path: '/pages/settings/settings'
},
timelineTitle: '平板支撑训练 — 一起打卡'
}
} }
+25
View File
@@ -3,6 +3,7 @@ const planMod = require('../../utils/plan')
const util = require('../../utils/util') const util = require('../../utils/util')
const themeMod = require('../../utils/theme') const themeMod = require('../../utils/theme')
const iconsMod = require('../../utils/icons') const iconsMod = require('../../utils/icons')
const config = require('../../config')
const MAX_CUSTOM_DURATION = 3600 // 1 hour max const MAX_CUSTOM_DURATION = 3600 // 1 hour max
@@ -190,5 +191,29 @@ Page({
} }
this.setData({ showPicker: false }) this.setData({ showPicker: false })
wx.navigateTo({ url: `/pages/timer/timer?free=${dur}` }) wx.navigateTo({ url: `/pages/timer/timer?free=${dur}` })
},
/**
* 分享给朋友。微信在用户点击右上角"···"菜单里的"转发给朋友"、
* 或者页面内任何 `<button open-type="share">` / `<button open-type="share">`
* 时,都会调用这个钩子,返回值即分享卡片的内容。
* 没声明这个方法时分享按钮就是灰色的,见上轮分析。
*/
onShareAppMessage() {
const s = config.share.default
return {
title: s.title,
path: s.path
}
},
/**
* 分享到朋友圈。基础库 2.11.3+ 可用。同样不实现就让"分享到朋友圈"按钮
* 灰掉。各页统一用 config.share.timelineTitle 作为朋友圈文案。
*/
onShareTimeline() {
return {
title: config.share.timelineTitle
}
} }
}) })
+27
View File
@@ -120,5 +120,32 @@ Page({
} else { } else {
this.setData({ loading: false, empty: true }) this.setData({ loading: false, empty: true })
} }
},
/**
* 分享给朋友 — 排行榜页。把用户当前名次写进标题,有"攀比"属性,
* 分享欲望会强一些。
*/
onShareAppMessage() {
const me = this.data.myEntry
const s = config.share.leaderboard
// myEntry.rank 依赖云函数返回,缺失时回退静态 title,避免出现"排第 undefined"
if (me && me.rank) {
const periodLabel = { day: '日', month: '月', year: '年' }[this.data.activePeriod] || ''
return {
title: s.titleTemplate.replace('{period}', periodLabel).replace('{rank}', me.rank),
path: s.path
}
}
return { title: s.title, path: s.path }
},
/**
* 分享到朋友圈。
*/
onShareTimeline() {
return {
title: config.share.timelineTitle
}
} }
}) })
+24
View File
@@ -2,6 +2,7 @@ const storage = require('../../utils/storage')
const util = require('../../utils/util') const util = require('../../utils/util')
const themeMod = require('../../utils/theme') const themeMod = require('../../utils/theme')
const iconsMod = require('../../utils/icons') const iconsMod = require('../../utils/icons')
const config = require('../../config')
Page({ Page({
data: { data: {
@@ -163,5 +164,28 @@ Page({
} }
} }
}) })
},
/**
* 分享给朋友 — 训练记录页。把累计训练时长带进标题,
* 让分享卡片有点击欲(看到对方"已经练了 X 小时"会想点开)。
*/
onShareAppMessage() {
const stats = storage.getTotalStats()
const minutes = Math.round((stats.totalDuration || 0) / 60)
const s = config.share.records
return {
title: s.titleTemplate.replace('{minutes}', minutes),
path: s.path
}
},
/**
* 分享到朋友圈。
*/
onShareTimeline() {
return {
title: config.share.timelineTitle
}
} }
}) })
+21
View File
@@ -542,5 +542,26 @@ Page({
wx.showToast({ title: '已恢复默认', icon: 'success', duration: 1200 }) wx.showToast({ title: '已恢复默认', icon: 'success', duration: 1200 })
} }
}) })
},
/**
* 分享给朋友 — 设置页(很少有人会从这里分享,但微信要求每个 Page 都
* 实现钩子,否则对应页面的右上角菜单的转发按钮就是灰色的)。
*/
onShareAppMessage() {
const s = config.share.settings
return {
title: s.title,
path: s.path
}
},
/**
* 分享到朋友圈。
*/
onShareTimeline() {
return {
title: config.share.timelineTitle
}
} }
}) })
+24 -1
View File
@@ -470,5 +470,28 @@ Page({
// No-op handler for catchtouchmove. Swallows move events that would // No-op handler for catchtouchmove. Swallows move events that would
// otherwise bubble to underlying timer page controls (causing missed // otherwise bubble to underlying timer page controls (causing missed
// taps on the completion modal). // taps on the completion modal).
onNoop() { /* swallow */ } onNoop() { /* swallow */ },
/**
* 分享给朋友。训练完成弹窗里的分享按钮会走到这里,标题里拼上用户
* 刚才撑了多久,转化率更高;右上角"···"菜单也共用同一份文案。
*/
onShareAppMessage() {
const elapsed = this.data.completionElapsed || this.data.duration
const s = config.share.timer
return {
title: s.titleTemplate.replace('{elapsed}', elapsed),
path: s.path
}
},
/**
* 分享到朋友圈,同样带上刚完成的时长晒成绩。
*/
onShareTimeline() {
const elapsed = this.data.completionElapsed || this.data.duration
return {
title: config.share.timer.timelineTitleTemplate.replace('{elapsed}', elapsed)
}
}
}) })
+10
View File
@@ -164,6 +164,16 @@
<view class="completion-btn" bindtap="onViewRecords"> <view class="completion-btn" bindtap="onViewRecords">
<text>查看记录</text> <text>查看记录</text>
</view> </view>
<!-- openType="share" 会触发 Page.onShareAppMessage(已实现),
没有这个属性 + onShareAppMessage 时分享按钮就是灰色的。
ui-btn 透传 openType 到内部原生 button。 -->
<ui-btn
variant="ghost"
size="md"
customStyle="flex:1; height:88rpx;"
text="分享"
openType="share"
></ui-btn>
</view> </view>
<view class="completion-btn completion-btn--text" bindtap="onCloseCompletion"> <view class="completion-btn completion-btn--text" bindtap="onCloseCompletion">
<text>回到首页</text> <text>回到首页</text>