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:
+19
-1
@@ -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 |
@@ -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),
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -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 结束。
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -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 绘制 → 临时文件 → 保存相册) */
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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); }
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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() }
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 })
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user