fix(mp): F6 小程序体检 三态/lazy-load/分享兜底/安全区/断链图修复

- 401 自动续登重试:request.ts 1002+401→relogin(全局锁+30s冷却)→新token重试一次(既有实现,本次核验确认)
- 三态:app.wxss 新增 .error-state 共享样式;首页(全模块失败判定)/福利街/任务页(双路失败判定)加错误态+重试
- 图片:11 处列表/首屏下图加 lazy-load;首页 placeholder.png(不存在)→gift.png;生成缺失的 avatar.png 灰圆默认头像
- 分享兜底:share.ts 加 DEFAULT_SHARE_IMAGE/shareImage();12 个 onShareAppMessage 全部补 imageUrl 兜底
- iOS 安全区:.page 底距 calc(120rpx+env);详情 float-share/swipe-hint、签到 float-share、3 个 add-btn、问卷 footer-btns 补 env(safe-area-inset-bottom)
This commit is contained in:
WorkBuddy Ops
2026-10-04 01:32:56 +08:00
parent f114601d1f
commit beebf8c767
28 changed files with 137 additions and 39 deletions
+19 -1
View File
@@ -39,7 +39,8 @@ page {
.page {
min-height: 100vh;
background: var(--bg-page);
padding-bottom: 120rpx;
/* F6/T12:iOS 安全区——底部统一留出 Home 指示条高度 */
padding-bottom: calc(120rpx + env(safe-area-inset-bottom));
}
.page-safe {
padding: 0 var(--page-padding);
@@ -149,6 +150,23 @@ page {
font-size: var(--text-sm);
}
/* ===== F6/T12:列表错误三态之「错误态」(空/加载态沿用上方 empty/loading) ===== */
.error-state {
text-align: center;
padding: 100rpx var(--space-lg);
color: var(--text-secondary);
font-size: var(--text-base);
}
.error-state-retry {
display: inline-block;
margin-top: var(--space-md);
padding: 12rpx 48rpx;
border-radius: 32rpx;
border: 1rpx solid var(--primary);
color: var(--primary);
font-size: var(--text-sm);
}
/* ===== 鲸彩好运 V8 Design Tokens(Sprint1 新增,不影响上方旧变量) ===== */
page {
/* 色彩 */
Binary file not shown.

After

Width:  |  Height:  |  Size: 368 B

+1 -1
View File
@@ -5,4 +5,4 @@
.addr-actions { display: flex; gap: var(--space-lg); border-top: 1rpx solid var(--divider); padding-top: var(--space-sm); }
.action { font-size: var(--text-base); color: var(--info); }
.action.del { color: var(--error); }
.add-btn { position: fixed; bottom: 0; left: 0; right: 0; border-radius: 0; }
.add-btn { position: fixed; bottom: 0; left: 0; right: 0; border-radius: 0; padding-bottom: env(safe-area-inset-bottom); }
@@ -1,5 +1,5 @@
import { lotteryApi } from '../../../utils/lottery-api'
import { prepareShareToken, buildSharePath } from '../../../utils/share'
import { prepareShareToken, buildSharePath, shareImage } from '../../../utils/share'
import { reportShareVisit } from '../../../utils/visit'
import { formatDrawTime } from '../../../utils/format'
import { joinErrorMessage, JOIN_CODES } from '../../../utils/error-map'
@@ -253,6 +253,7 @@ Page({
return {
title: lottery?.title || '快来参与每日抽奖!',
path: buildSharePath(basePath, this.data.shareToken),
imageUrl: shareImage(lottery?.cover_image),
}
},
})
+2 -1
View File
@@ -1,7 +1,7 @@
import { drawboxApi } from '../../utils/drawbox-api'
import type { DrawboxDrawResult, DrawboxSlot } from '../../utils/drawbox-api'
import { abAssign } from '../../utils/ab-api'
import { prepareShareToken, buildSharePath } from '../../utils/share'
import { prepareShareToken, buildSharePath, shareImage } from '../../utils/share'
import { reportShareVisit } from '../../utils/visit'
const app = getApp<any>()
@@ -480,6 +480,7 @@ Page({
return {
title,
path: this.buildInvitePath(),
imageUrl: shareImage(),
}
},
+9 -5
View File
@@ -58,6 +58,8 @@ Page({
data: {
user: null as MpUser | null,
loading: true,
/** F6:关键模块全部失败时的错误态信息(空串=正常) */
loadError: '',
// 开屏广告:每次冷启动展示一次(用 storage 标记控制)
splashShow: false,
// V11.0 §11.1 微信原生开屏广告位 ID(从 /mp/config/ad 加载,空串=未开通走自建兜底)
@@ -279,11 +281,11 @@ Page({
* 其余模块渲染顺序与原版一致(仍通过 Promise.allSettled 并行加载)。
*/
async loadHomeData() {
this.setData({ loading: true })
this.setData({ loading: true, loadError: '' })
try {
// 关键路径:并行加载配置、官方推荐、手气广场等主模块(不含 drawbox)
await Promise.allSettled([
const results = await Promise.allSettled([
this.loadDailyConfig(),
this.loadFeaturedLotteries(),
this.loadDailyLotteries(),
@@ -295,10 +297,12 @@ Page({
this.loadCheckinCardConfig(),
])
this.setData({ loading: false })
// F6:关键模块全部失败(多为断网)→ 错误态 banner,可点击重试;
// 部分失败维持各模块既有降级(空态/隐藏),不打扰。
const allFailed = results.every((r) => r.status === 'rejected')
this.setData({ loading: false, loadError: allFailed ? '网络异常,首页加载失败' : '' })
} catch (e: any) {
this.setData({ loading: false })
wx.showToast({ title: e?.message || '加载失败', icon: 'none' })
this.setData({ loading: false, loadError: e?.message || '加载失败' })
}
// 命运盒子两路请求独立发起,互不阻塞,也不再拖慢首屏 loading 结束。
+11 -5
View File
@@ -1,4 +1,10 @@
<view class="page">
<!-- F6:错误态 banner(关键模块全部失败时出现,点击重试) -->
<view wx:if="{{loadError}}" class="error-state">
<view>{{loadError}}</view>
<view class="error-state-retry" bindtap="loadHomeData">重试</view>
</view>
<!-- 顶部区域:渐变背景 -->
<view class="header">
<!-- 导航栏占位(状态栏 + 胶囊按钮区域) -->
@@ -34,7 +40,7 @@
</swiper-item>
<swiper-item wx:for="{{featuredLotteries}}" wx:key="id">
<view class="featured-card" bindtap="onFeaturedClick" data-id="{{item.id}}">
<image class="featured-cover" src="{{item.cover_image || '/images/placeholder.png'}}" mode="aspectFit" />
<image class="featured-cover" src="{{item.cover_image || '/images/gift.png'}}" mode="aspectFit" lazy-load />
<view class="featured-info">
<text class="featured-title">{{item.title}}</text>
<view class="featured-btn btn btn-primary">参与抽奖</view>
@@ -76,7 +82,7 @@
<!-- 区域2 今日大奖 -->
<view class="db-today page-safe" wx:if="{{drawboxTodayPrize}}" bindtap="onDrawboxTodayPrize">
<view class="db-today-card">
<image class="db-today-img" src="{{drawboxTodayPrize.image || '/images/placeholder.png'}}" mode="aspectFill" />
<image class="db-today-img" src="{{drawboxTodayPrize.image || '/images/gift.png'}}" mode="aspectFill" lazy-load />
<view class="db-today-info">
<text class="db-today-label">今日大奖</text>
<text class="db-today-name">{{drawboxTodayPrize.name}}</text>
@@ -125,7 +131,7 @@
<scroll-view class="db-hot-scroll" scroll-x enhanced show-scrollbar="{{false}}">
<view class="db-hot-row">
<view wx:for="{{drawboxHotPrizes}}" wx:key="id" class="db-hot-card" data-id="{{item.id}}" bindtap="onDrawboxHotPrize">
<image class="db-hot-img" src="{{item.image || '/images/placeholder.png'}}" mode="aspectFill" />
<image class="db-hot-img" src="{{item.image || '/images/gift.png'}}" mode="aspectFill" lazy-load />
<text class="db-hot-name">{{item.name}}</text>
<text class="db-hot-claimed">已领 {{item.claimed_count}} 份</text>
</view>
@@ -172,7 +178,7 @@
<text class="today-subtitle">{{todayPrizeDesc}}</text>
</view>
<view class="today-card" bindtap="onTodayPrizeTap">
<image class="today-cover" src="{{todayPrize.cover_image || todayPrize.lottery.cover_image || '/images/placeholder.png'}}" mode="aspectFill" />
<image class="today-cover" src="{{todayPrize.cover_image || todayPrize.lottery.cover_image || '/images/gift.png'}}" mode="aspectFill" lazy-load />
<view class="today-info">
<text class="today-lottery-title">{{todayPrize.custom_title || todayPrize.lottery.title}}</text>
<text class="today-countdown {{todayStatusEnded ? 'today-ended' : ''}}" wx:if="{{todayStatusText}}">{{todayStatusText}}</text>
@@ -203,7 +209,7 @@
<view class="plaza-grid">
<view wx:for="{{dailyLotteries}}" wx:key="id"
class="plaza-card" bindtap="onPlazaCardClick" data-id="{{item.id}}">
<image class="plaza-cover" src="{{item.cover_image || '/images/placeholder.png'}}" mode="aspectFill" />
<image class="plaza-cover" src="{{item.cover_image || '/images/gift.png'}}" mode="aspectFill" lazy-load />
<view class="plaza-info">
<text class="plaza-title">{{item.title}}</text>
<view class="plaza-meta">
+2
View File
@@ -1,5 +1,6 @@
import { guaranteedCouponApi } from '@/utils/guaranteed-coupon-api'
import { fetchUserInfo } from '@/utils/auth'
import { shareImage } from '@/utils/share'
// 邀请好友页(V11.0 §5.10/§12.2):邀请新用户得必中券 + 我的必中券 + 历史记录。
const STATUS_TEXT: Record<string, string> = {
@@ -85,6 +86,7 @@ Page({
return {
title: '我在鲸彩好运抽大奖,邀请你一起抽!双方都得必中券',
path: code ? `/pages/index/index?invite_code=${encodeURIComponent(code)}` : '/pages/index/index',
imageUrl: shareImage(),
}
},
+3 -1
View File
@@ -5,6 +5,7 @@ import { questionnaireApi, type Questionnaire } from '../../../utils/questionnai
import { adApi } from '../../../utils/ad-api'
import { chooseAndUploadImage } from '../../../utils/upload'
import type { AppOption } from '../../../utils/auth'
import { shareImage } from '../../../utils/share'
interface PrizeForm {
name: string
@@ -952,9 +953,10 @@ Page({
return {
title: this.data.publishedTitle || '快来参与抽奖!',
path: `/pages/lottery/detail/index?id=${this.data.publishedId}`,
imageUrl: shareImage(),
}
}
return { title: '鲸彩好运 · 免费发起抽奖', path: '/pages/lottery/create/index' }
return { title: '鲸彩好运 · 免费发起抽奖', path: '/pages/lottery/create/index', imageUrl: shareImage() }
},
/** 生成朋友圈海报(canvas 绘制 → 临时文件 → 保存相册) */
+2 -1
View File
@@ -1,7 +1,7 @@
import { lotteryApi, type SettlementPreview } from '../../../utils/lottery-api'
import { lotteryQuestionnaireApi } from '../../../utils/questionnaire-api'
import { assistApi, type AssistStatus } from '../../../utils/assist-api'
import { prepareShareToken, buildSharePath } from '../../../utils/share'
import { prepareShareToken, buildSharePath, shareImage } from '../../../utils/share'
import { promoSlotApi } from '../../../utils/promo-slot-api'
import { adApi, AD_SCENE, type AdItem } from '../../../utils/ad-api'
import { formatTime } from '../../../utils/format'
@@ -1003,6 +1003,7 @@ Page({
return {
title: l?.title || '快来参与抽奖!',
path: buildSharePath(basePath, this.data.shareToken),
imageUrl: shareImage(l?.cover_image),
}
},
+3 -3
View File
@@ -265,7 +265,7 @@
<text class="participant-text" bindtap="goParticipants" hover-class="participant-text-hover">已有 {{lottery.current_count}} 人参与 ></text>
<view class="avatar-row">
<block wx:for="{{participants}}" wx:key="user_id">
<image class="small-avatar" src="{{item.avatar||'/images/avatar.png'}}" wx:if="{{index<6}}" catchtap="noopAvatar"/>
<image class="small-avatar" src="{{item.avatar||'/images/avatar.png'}}" wx:if="{{index<6}}" catchtap="noopAvatar" lazy-load/>
</block>
<view class="avatar-more" wx:if="{{lottery.current_count>6}}">+{{lottery.current_count-6}}</view>
</view>
@@ -276,7 +276,7 @@
<view class="section-title">中奖名单</view>
<block wx:for="{{winners}}" wx:key="id">
<view class="winner-item">
<image class="avatar" src="{{item.mp_user.avatar||'/images/avatar.png'}}"/>
<image class="avatar" src="{{item.mp_user.avatar||'/images/avatar.png'}}" lazy-load/>
<text class="nickname">{{item.mp_user.nickname||'匿名用户'}}</text>
<text class="prize">{{item.prize_name}}</text>
</view>
@@ -340,7 +340,7 @@
<!-- 上滑切换遮罩(转场动画) -->
<view class="switch-mask {{switching}}" wx:if="{{switching}}">
<image class="switch-cover" src="{{switchNext.cover_image||'/images/gift.png'}}" mode="aspectFill"/>
<image class="switch-cover" src="{{switchNext.cover_image||'/images/gift.png'}}" mode="aspectFill" lazy-load/>
<view class="switch-info">
<text class="switch-label">下一个抽奖</text>
<text class="switch-title">{{switchNext.title}}</text>
+2 -2
View File
@@ -115,7 +115,7 @@ page {
.winner-item .prize { font-size: var(--text-sm); color: var(--primary); }
/* Float share */
.float-share { position: fixed; right: var(--page-padding); bottom: 180rpx; background: var(--bg-card); border: 1rpx solid var(--primary); color: var(--primary); padding: var(--space-sm) var(--space-md); border-radius: 40rpx; font-size: var(--text-sm); display: flex; align-items: center; gap: var(--space-xs); z-index: 100; }
.float-share { position: fixed; right: var(--page-padding); bottom: calc(180rpx + env(safe-area-inset-bottom)); background: var(--bg-card); border: 1rpx solid var(--primary); color: var(--primary); padding: var(--space-sm) var(--space-md); border-radius: 40rpx; font-size: var(--text-sm); display: flex; align-items: center; gap: var(--space-xs); z-index: 100; }
/* open-type="share" 的 button 需清除默认样式,保持与原 view 一致的胶囊外观 */
button.float-share { line-height: 1.4; margin: 0; font-weight: normal; }
button.float-share::after { border: none; }
@@ -131,7 +131,7 @@ button.share-guide-share::after { border: none; }
.share-guide-close { font-size: var(--text-sm); color: var(--text-secondary); padding: var(--space-xs) var(--space-md); }
/* Swipe hint(上滑切换提示) */
.swipe-hint { position: fixed; bottom: 0; left: 0; right: 0; background: var(--bg-card); padding: var(--space-sm) var(--page-padding); display: flex; flex-direction: column; align-items: center; gap: 4rpx; font-size: var(--text-base); color: var(--text-secondary); border-top: 1rpx solid var(--divider); z-index: 100; }
.swipe-hint { position: fixed; bottom: 0; left: 0; right: 0; background: var(--bg-card); padding: var(--space-sm) var(--page-padding) calc(var(--space-sm) + env(safe-area-inset-bottom)); display: flex; flex-direction: column; align-items: center; gap: 4rpx; font-size: var(--text-base); color: var(--text-secondary); border-top: 1rpx solid var(--divider); z-index: 100; }
.swipe-hint-arrow { font-size: var(--text-lg); color: var(--primary); animation: swipe-bounce 1.2s ease-in-out infinite; }
@keyframes swipe-bounce {
0%, 100% { transform: translateY(0); opacity: 0.6; }
+2
View File
@@ -1,5 +1,6 @@
import { lotteryApi } from '../../../utils/lottery-api'
import { buildRegionExtra } from '../../../utils/location'
import { shareImage } from '../../../utils/share'
Page({
data: {
@@ -116,6 +117,7 @@ Page({
return {
title: `就差你了!一起组队抽「${(this.data.lottery && this.data.lottery.title) || '大奖'}」`,
path: `/pages/lottery/group/index?id=${this.data.id}&code=${this.data.shareCode}`,
imageUrl: shareImage(),
}
},
})
@@ -1,7 +1,7 @@
import { luckyLotteryApi } from '../../../utils/lucky-lottery-api'
import type { LuckyLottery, DrawResult, LuckySettlementPreview } from '../../../utils/lucky-lottery-api'
import { guaranteedCouponApi } from '@/utils/guaranteed-coupon-api'
import { prepareShareToken, buildSharePath } from '../../../utils/share'
import { prepareShareToken, buildSharePath, shareImage } from '../../../utils/share'
import { reportShareVisit } from '../../../utils/visit'
import { joinErrorMessage, JOIN_CODES } from '../../../utils/error-map'
@@ -245,6 +245,7 @@ Page({
return {
title: lottery?.title || '幸运大转盘',
path: buildSharePath('/pages/lucky-lottery/index/index', shareToken),
imageUrl: shareImage(lottery?.cover_image),
}
},
@@ -9,4 +9,4 @@
.profile-actions { display: flex; gap: var(--space-lg); border-top: 1rpx solid var(--divider); padding-top: var(--space-sm); }
.action { font-size: var(--text-base); color: var(--info); }
.action.del { color: var(--error); }
.add-btn { position: fixed; bottom: 0; left: 0; right: 0; border-radius: 0; }
.add-btn { position: fixed; bottom: 0; left: 0; right: 0; border-radius: 0; padding-bottom: env(safe-area-inset-bottom); }
+1 -1
View File
@@ -8,4 +8,4 @@
.question-actions { display: flex; gap: var(--space-lg); border-top: 1rpx solid var(--divider); padding-top: var(--space-sm); margin-top: var(--space-sm); }
.action { font-size: var(--text-base); color: var(--info); }
.action.del { color: var(--error); }
.add-btn { position: fixed; bottom: 0; left: 0; right: 0; border-radius: 0; }
.add-btn { position: fixed; bottom: 0; left: 0; right: 0; border-radius: 0; padding-bottom: env(safe-area-inset-bottom); }
@@ -5,6 +5,6 @@
.qn-actions { display: flex; gap: var(--space-lg); border-top: 1rpx solid var(--divider); padding-top: var(--space-sm); margin-top: var(--space-sm); }
.action { font-size: var(--text-base); color: var(--info); }
.action.del { color: var(--error); }
.footer-btns { position: fixed; bottom: 0; left: 0; right: 0; display: flex; }
.footer-btns { position: fixed; bottom: 0; left: 0; right: 0; display: flex; padding-bottom: env(safe-area-inset-bottom); }
.bank-btn { flex: 1; border-radius: 0; }
.add-btn { flex: 2; border-radius: 0; }
+2
View File
@@ -1,4 +1,5 @@
import { drawboxApi, type DrawboxPosterInfo } from '../../../utils/drawbox-api'
import { shareImage } from '../../../utils/share'
const POSTER_W = 750
const POSTER_H = 1334
@@ -375,6 +376,7 @@ Page({
return {
title,
path: '/pages/drawbox/index',
imageUrl: shareImage(),
}
},
})
@@ -1,5 +1,5 @@
import { signinLotteryApi, SigninLottery, MySigninStatus, SigninLotteryPrize } from '../../../utils/signin-lottery-api'
import { prepareShareToken, buildSharePath } from '../../../utils/share'
import { prepareShareToken, buildSharePath, shareImage } from '../../../utils/share'
import { reportShareVisit } from '../../../utils/visit'
import { formatDrawTime } from '../../../utils/format'
import { joinErrorMessage, JOIN_CODES } from '../../../utils/error-map'
@@ -248,6 +248,7 @@ Page({
return {
title: l?.title || '签到抽奖,签到 N 天即可参与!',
path: buildSharePath(basePath, this.data.shareToken),
imageUrl: shareImage(l?.cover_image),
}
},
})
@@ -60,7 +60,7 @@
.rich-content { font-size: var(--text-base, 28rpx); color: var(--text, #333); line-height: 1.6; word-break: break-word; }
/* Float share */
.float-share { position: fixed; right: var(--page-padding); bottom: 60rpx; background: var(--bg-card); border: 1rpx solid #ff5e8a; color: #ff5e8a; padding: var(--space-sm) var(--space-md); border-radius: 40rpx; font-size: var(--text-sm); display: flex; align-items: center; gap: var(--space-xs); z-index: 100; box-shadow: 0 4rpx 16rpx rgba(255, 94, 138, 0.2); }
.float-share { position: fixed; right: var(--page-padding); bottom: calc(60rpx + env(safe-area-inset-bottom)); background: var(--bg-card); border: 1rpx solid #ff5e8a; color: #ff5e8a; padding: var(--space-sm) var(--space-md); border-radius: 40rpx; font-size: var(--text-sm); display: flex; align-items: center; gap: var(--space-xs); z-index: 100; box-shadow: 0 4rpx 16rpx rgba(255, 94, 138, 0.2); }
/* Empty state overrides */
.empty-state .empty-icon { font-size: 100rpx; opacity: 0.6; }
+14 -2
View File
@@ -18,6 +18,7 @@ import type { BusinessError } from '../../../utils/request'
import { api } from '../../../utils/request'
import { lotteryApi } from '../../../utils/lottery-api'
import { syncTabBarSelected, TAB_INDEX } from '../../../utils/tabbar'
import { shareImage } from '../../../utils/share'
/** 视频激励广告位 ID 兜底占位常量;实际以后端 /mp/config/ad 下发的 rewarded_video_ad_unit_id 为准 */
const REWARDED_VIDEO_AD_UNIT_ID = 'adunit-task-video-placeholder'
@@ -62,6 +63,8 @@ Page({
data: {
tasks: [] as DisplayTask[],
loading: true,
/** F6:任务/汇总双路失败时的错误态信息(空串=正常) */
loadError: '',
totalChanceAvailable: 0, // 今日剩余可得抽奖次数(来自 summary 接口)
/** 逛福利 15 秒停留倒计时(显示给用户,<=0 时不再显示) */
welfareDwellSeconds: 0,
@@ -115,6 +118,11 @@ Page({
this.loadTasks(false).finally(() => wx.stopPullDownRefresh())
},
/** F6:错误态重试 */
retryLoad() {
this.loadTasks(false)
},
onUnload() {
this.clearWelfareTimer()
// 不销毁视频广告实例:跨页复用可避免重复创建的开销
@@ -125,7 +133,7 @@ Page({
* silent=true 时不重置 loading 标记,避免抖动。
*/
async loadTasks(silent: boolean = false) {
if (!silent) this.setData({ loading: true })
if (!silent) this.setData({ loading: true, loadError: '' })
try {
// 并行拉取任务与汇总;任一失败不影响另一接口
const [tasksRes, summaryRes] = await Promise.allSettled([
@@ -136,14 +144,17 @@ Page({
const summary = summaryRes.status === 'fulfilled' ? summaryRes.value : null
const display: DisplayTask[] = tasks.map((t) => this.toDisplay(t))
// F6:任务与汇总两路同时失败(多为断网)→ 错误态,可重试
const bothFailed = tasksRes.status === 'rejected' && summaryRes.status === 'rejected'
this.setData({
tasks: display,
totalChanceAvailable: (summary && summary.task_chance_available) || 0,
loading: false,
loadError: bothFailed ? '任务加载失败,请检查网络' : '',
})
} catch (e) {
console.error('load tasks failed', e)
this.setData({ loading: false })
this.setData({ loading: false, loadError: '任务加载失败,请检查网络' })
}
},
@@ -391,6 +402,7 @@ Page({
return {
title: '好运邀你一起抽',
path: '/pages/index/index',
imageUrl: shareImage(),
}
},
})
+7 -1
View File
@@ -1,6 +1,12 @@
<!-- 任务中心:鲸彩好运 V8 Sprint3 -->
<view class="tasks-page">
<!-- F6:错误态(双路加载失败,可重试) -->
<view wx:if="{{loadError}}" class="error-state">
<view>{{loadError}}</view>
<view class="error-state-retry" bindtap="retryLoad">重试</view>
</view>
<!-- 顶部信息卡 -->
<view class="hero page-safe">
<view class="hero-row">
@@ -23,7 +29,7 @@
</view>
<view class="assist-list">
<view wx:for="{{assistLotteries}}" wx:key="id" class="assist-card" data-id="{{item.id}}" bindtap="goAssist">
<image wx:if="{{item.cover_image}}" class="assist-cover" src="{{item.cover_image}}" mode="aspectFill" />
<image wx:if="{{item.cover_image}}" class="assist-cover" src="{{item.cover_image}}" mode="aspectFill" lazy-load />
<view wx:else class="assist-cover assist-cover-fallback">🎁</view>
<view class="assist-body">
<text class="assist-name">{{item.title}}</text>
+2 -1
View File
@@ -1,5 +1,6 @@
import { drawboxApi, type DrawboxTeamInfo, type DrawboxTeamMember } from '../../utils/drawbox-api'
import { fetchUserInfo, type MpUser } from '../../utils/auth'
import { shareImage } from '../../utils/share'
interface PageData {
loading: boolean
@@ -92,6 +93,6 @@ Page({
const title = `${nickname}邀请你加入${level},好运一起抽!`
// path 直接带 invite_code(小程序码 scene 解码后是 token,走 visit 链路识别邀请人)。
const path = code ? `/pages/drawbox/index?invite_code=${encodeURIComponent(code)}` : '/pages/drawbox/index'
return { title, path }
return { title, path, imageUrl: shareImage() }
},
})
+2 -2
View File
@@ -1,7 +1,7 @@
import { userApi } from '../../../utils/user-api'
import { dynamicApi } from '../../../utils/dynamic-api'
import { lotteryApi } from '../../../utils/lottery-api'
import { prepareShareToken, buildSharePath } from '../../../utils/share'
import { prepareShareToken, buildSharePath, shareImage } from '../../../utils/share'
import { reportShareVisit } from '../../../utils/visit'
import { formatTime } from '@/utils/format'
import type { AppOption } from '../../../utils/auth'
@@ -281,7 +281,7 @@ Page({
return {
title: p ? `${p.nickname || '用户'}的主页` : '来看看 TA 的主页',
path: buildSharePath(base, this.data.shareToken),
imageUrl: (p && p.avatar) || '',
imageUrl: shareImage(p && p.avatar),
}
},
})
+1 -1
View File
@@ -53,7 +53,7 @@
<!-- 奖品卡片(我即将参与的抽奖) -->
<view class="prize-card" bindtap="goPrizeCard">
<image class="prize-cover" src="{{prizeCard.cover}}" mode="aspectFill"/>
<image class="prize-cover" src="{{prizeCard.cover || '/images/gift.png'}}" mode="aspectFill" lazy-load/>
<view class="prize-info">
<view class="prize-title">奖品:{{prizeCard.title}}</view>
<view class="prize-list">{{prizeCard.prizesText}}</view>
+18 -3
View File
@@ -21,6 +21,8 @@ Page({
total: 0,
loading: false,
noMore: false,
/** F6:首屏加载错误信息(空串=正常;有值且列表空时展示错误态) */
loadError: '',
/** 商城购买渠道开关(后台控制,默认关闭);关闭时隐藏「全部商品」入口 */
purchaseEnabled: false,
@@ -73,7 +75,7 @@ Page({
async loadWelfare() {
if (this.data.loading) return
this.setData({ loading: true })
this.setData({ loading: true, loadError: '' })
try {
const res = await mallApi.getProducts(this.data.page, 20)
const list = (res.list || []).filter((p) => p.points_enabled && p.points_cost > 0)
@@ -85,8 +87,21 @@ Page({
loading: false,
})
} catch (e: any) {
this.setData({ loading: false })
wx.showToast({ title: e.message || '加载失败', icon: 'none' })
this.setData({ loading: false, loadError: e.message || '加载失败' })
}
},
/** F6:错误态重试 */
retryLoad() {
this.setData({ page: 1, noMore: false })
this.loadWelfare()
},
/** F6:图片加载失败 → 标记后走占位视图 */
onImgError(e: WechatMiniprogram.TouchEvent) {
const idx = Number(e.currentTarget.dataset.index)
if (!isNaN(idx) && this.data.welfareItems[idx]) {
this.setData({ [`welfareItems[${idx}]._imgErr`]: true })
}
},
+8 -2
View File
@@ -11,17 +11,23 @@
</view>
</view>
<view wx:if="{{welfareItems.length === 0 && !loading}}" class="empty-state">
<view wx:if="{{welfareItems.length === 0 && !loading && !loadError}}" class="empty-state">
暂无可兑换的福利
</view>
<!-- F6:错误态(首屏加载失败,可重试) -->
<view wx:if="{{loadError && welfareItems.length === 0}}" class="error-state">
<view>{{loadError}}</view>
<view class="error-state-retry" bindtap="retryLoad">重试</view>
</view>
<view class="welfare-grid">
<view
wx:for="{{welfareItems}}" wx:key="id"
class="welfare-card card card-shadow"
bindtap="goDetail" data-id="{{item.id}}"
>
<image wx:if="{{item.image}}" class="welfare-img" src="{{item.image}}" mode="aspectFill" />
<image wx:if="{{item.image && !item._imgErr}}" class="welfare-img" src="{{item.image}}" mode="aspectFill" lazy-load binderror="onImgError" data-index="{{index}}" />
<view wx:else class="welfare-img-placeholder">🎁</view>
<view class="welfare-info">
<text class="welfare-name">{{item.name}}</text>
+17
View File
@@ -51,3 +51,20 @@ export function buildSharePath(basePath: string, token: string): string {
const sep = basePath.includes('?') ? '&' : '?'
return `${basePath}${sep}st=${encodeURIComponent(token)}`
}
/**
* 分享卡片兜底(F6/T12):统一默认分享图与标题。
* 各页 onShareAppMessage 用 shareImage(url) 包一层,URL 为空时回退默认图,
* 避免微信侧展示空白缩略图。
*/
/** 默认分享卡片图(本地资源) */
export const DEFAULT_SHARE_IMAGE = '/images/gift.png'
/** 默认分享标题(页面未给标题时的品牌兜底) */
export const DEFAULT_SHARE_TITLE = '鲸彩好运 · 免费抽奖天天有'
/** 分享图兜底:传入有效 URL 用 URL,否则用默认图 */
export function shareImage(url?: string | null): string {
return url && url.trim() ? url : DEFAULT_SHARE_IMAGE
}