feat(tongji): 接入小游戏真实周榜与微信分享

This commit is contained in:
大哥大哥的大哥哥
2026-07-17 18:02:00 +08:00
parent 62467dc01c
commit 7192c4db8c
8 changed files with 1014 additions and 32 deletions
+131 -31
View File
@@ -197,9 +197,9 @@
<view>
<view class="eg-weekly-title-line">
<text class="eg-weekly-title">本周7人同行榜</text>
<text class="eg-weekly-preview">界面预览</text>
<text class="eg-weekly-preview" :class="{ 'is-offline': !platformConnected }">{{ weeklyStatusLabel }}</text>
</view>
<text class="eg-weekly-sub">{{ weeklyRangeLabel }} · 每周一更新</text>
<text class="eg-weekly-sub">{{ weeklyRangeLabel }} · {{ weeklySexLabel }} · {{ weeklyMemberCount }}/7人</text>
</view>
<view class="eg-weekly-medal">{{ weeklyMyRank }}</view>
</view>
@@ -225,7 +225,10 @@
:class="{ 'is-me': player.isMe }"
>
<text class="eg-weekly-place">{{ player.rank }}</text>
<view class="eg-weekly-avatar" :class="`is-tone-${player.tone}`">{{ player.avatar }}</view>
<view class="eg-weekly-avatar" :class="`is-tone-${player.tone}`">
<image v-if="player.avatarUrl" class="eg-weekly-avatar-image" :src="player.avatarUrl" mode="aspectFill" />
<text v-else>{{ player.avatar }}</text>
</view>
<text class="eg-weekly-name">{{ player.name }}</text>
<text v-if="player.isMe" class="eg-weekly-me">我</text>
<text class="eg-weekly-count">{{ player.count }}</text>
@@ -242,7 +245,8 @@
</view>
<view class="eg-weekly-start" @tap="closeWeeklyRank">开始消除</view>
<text class="eg-weekly-footnote">当前为排行效果预览,真实好友数据接入后替换</text>
<button class="eg-weekly-share" open-type="share">邀请亲友一起认糖</button>
<text class="eg-weekly-footnote">使用平台昵称参加真实周榜,每周一重新分组;分享不会自动绑定家庭关系</text>
</view>
</view>
@@ -284,6 +288,7 @@
<view class="eg-result-score"><text>本局得分</text><text>{{ score }}</text></view>
<view class="eg-result-best"><text>历史最高</text><text>{{ bestScore }}</text></view>
<view class="eg-primary-btn" @tap="startGame">再玩一局</view>
<button class="eg-share-btn" open-type="share">分享本局认糖成绩</button>
<view class="eg-secondary-btn" @tap="goBack">返回血糖周报</view>
</view>
</view>
@@ -292,10 +297,12 @@
</template>
<script setup>
import { computed, ref } from 'vue'
import { onLoad, onUnload } from '@dcloudio/uni-app'
import { computed, getCurrentInstance, ref } from 'vue'
import { onLoad, onShareAppMessage, onShareTimeline, onShow, onUnload } from '@dcloudio/uni-app'
import TongjiIcon from './components/TongjiIcon.vue'
import { useGameSfx } from './composables/useGameSfx.js'
import { useGamePlatform } from './composables/useGamePlatform.js'
import { useTongjiAuth } from '../composables/useTongjiAuth.js'
const FOOD_IMAGE_MODULES = import.meta.glob('./assets/food/*.jpg', {
eager: true,
@@ -318,20 +325,6 @@ const COMBO_PITY_TURNS = 6
const MEGA_COMBO_SCORE = 300
const DANGER_DROP_CHANCE = 0.05 + GAME_DIFFICULTY * 0.01
const COLUMN_GROUP_BIAS = 0.86 - GAME_DIFFICULTY * 0.028
const WEEKLY_PREVIEW_BASE = 208
const WEEKLY_PREVIEW_PLAYERS = [
{ id: 'li', name: '李阿姨', avatar: '李', count: 286, tone: 1 },
{ id: 'health', name: '健康每一天', avatar: '健', count: 248, tone: 2 },
{ id: 'wang', name: '王师傅', avatar: '王', count: 226, tone: 3 },
{ id: 'me', name: '我', avatar: '我', count: 0, tone: 4, isMe: true },
{ id: 'happy', name: '快乐生活', avatar: '乐', count: 191, tone: 5 },
{ id: 'chen', name: '陈叔', avatar: '陈', count: 174, tone: 6 },
{ id: 'sunny', name: '天天好心情', avatar: '晴', count: 152, tone: 7 }
]
const WEEKLY_START_RANK = WEEKLY_PREVIEW_PLAYERS
.map(player => ({ ...player, count: player.isMe ? WEEKLY_PREVIEW_BASE : player.count }))
.sort((a, b) => b.count - a.count)
.findIndex(player => player.isMe) + 1
const CHEER_MESSAGES = [
{ source: '同行鼓励', text: '你需要加油哦!再向前一点,就能超过上一名。', tone: 'warm' },
{ source: '同行鼓励', text: '你太棒了!今天也在认真认识食物。', tone: 'bright' },
@@ -404,6 +397,23 @@ const {
warmUp: warmUpSfx
} = useGameSfx()
const { proxy } = getCurrentInstance()
const { ensureLoggedIn } = useTongjiAuth(proxy)
const {
connected: platformConnected,
loading: platformLoading,
syncStatus: platformSyncStatus,
leaderboard: platformLeaderboard,
confirmedSessionLearned,
connect: connectPlatform,
beginSession: beginPlatformSession,
queueProgress: queuePlatformProgress,
flushProgress: flushPlatformProgress,
syncAndRefresh: syncAndRefreshPlatform,
recordShare: recordPlatformShare,
dispose: disposePlatform
} = useGamePlatform(proxy, ensureLoggedIn)
const board = ref([])
const selected = ref(-1)
const score = ref(0)
@@ -441,7 +451,6 @@ const reshuffleDone = ref(false)
const reshuffling = ref(false)
const confettiVisible = ref(false)
const weeklyRankVisible = ref(false)
const weeklyPreviewProgress = ref(0)
const cheerIndex = ref(0)
const CONFETTI_COLORS = ['#ff3154', '#ffd928', '#28c76f', '#4d8dff', '#a855f7', '#ff7a1a']
const confettiPieces = Array.from({ length: 34 }, (_, index) => ({
@@ -466,6 +475,7 @@ let taskRewardTimer = null
let pendingThemeMatchCount = 0
let taskRewardStatusUntil = 0
let sfxGesturePrimed = false
let incomingInviteCode = ''
const currentTheme = computed(() => FOOD_GROUPS[themeIndex.value % FOOD_GROUPS.length])
const currentTask = computed(() => {
@@ -483,13 +493,38 @@ const highCount = computed(() => board.value.filter(tile => tile && foodOf(tile.
const boardRows = computed(() => Array.from({ length: ROWS }, (_, row) => (
board.value.slice(row * COLS, row * COLS + COLS).map((tile, col) => ({ ...tile, index: row * COLS + col }))
)))
const weeklyMyCount = computed(() => WEEKLY_PREVIEW_BASE + weeklyPreviewProgress.value)
const weeklyRankRows = computed(() => WEEKLY_PREVIEW_PLAYERS
.map(player => ({ ...player, count: player.isMe ? weeklyMyCount.value : player.count }))
.sort((a, b) => b.count - a.count)
.map((player, index) => ({ ...player, rank: index + 1 })))
const weeklyMyRank = computed(() => weeklyRankRows.value.find(player => player.isMe)?.rank || 7)
const weeklyRankRise = computed(() => Math.max(0, WEEKLY_START_RANK - weeklyMyRank.value))
const pendingWeeklyLearned = computed(() => Math.max(0, learned.value - confirmedSessionLearned.value))
const weeklyMyCount = computed(() => Number(platformLeaderboard.value.me?.count || 0) + pendingWeeklyLearned.value)
const weeklyRankRows = computed(() => {
const remote = Array.isArray(platformLeaderboard.value.players)
? platformLeaderboard.value.players
: []
const userData = uni.getStorageSync('userData') || {}
const fallbackName = String(userData.nickname || '我')
const source = remote.length
? remote.map((player, index) => ({
...player,
isMe: !!(player.is_me || player.isMe),
avatarUrl: player.avatar || '',
avatar: String(player.name || '同行').slice(0, 1),
tone: index % 7 + 1,
count: player.is_me || player.isMe ? weeklyMyCount.value : Number(player.count || 0)
}))
: [{
id: 'local-me',
name: fallbackName,
avatar: fallbackName.slice(0, 1) || '我',
avatarUrl: userData.avatar || '',
count: weeklyMyCount.value,
tone: 1,
isMe: true
}]
return source
.sort((a, b) => b.count - a.count || Number(b.best_score || 0) - Number(a.best_score || 0))
.map((player, index) => ({ ...player, rank: index + 1 }))
})
const weeklyMyRank = computed(() => weeklyRankRows.value.find(player => player.isMe)?.rank || 1)
const weeklyRankRise = computed(() => 0)
const weeklyIsFirst = computed(() => weeklyMyRank.value === 1)
const weeklyDistance = computed(() => {
const mineIndex = weeklyRankRows.value.findIndex(player => player.isMe)
@@ -497,17 +532,60 @@ const weeklyDistance = computed(() => {
return Math.max(1, weeklyRankRows.value[mineIndex - 1].count - weeklyMyCount.value + 1)
})
const currentCheer = computed(() => CHEER_MESSAGES[cheerIndex.value % CHEER_MESSAGES.length])
const weeklyRangeLabel = computed(() => getWeekRangeLabel(new Date()))
const weeklyRangeLabel = computed(() => {
const start = platformLeaderboard.value.week_start
const end = platformLeaderboard.value.week_end
if (!start || !end) return getWeekRangeLabel(new Date())
return `${formatWeekDate(start)}—${formatWeekDate(end)}`
})
const weeklySexLabel = computed(() => platformLeaderboard.value.sex_label || '同行')
const weeklyMemberCount = computed(() => Math.max(1, Number(platformLeaderboard.value.member_count || weeklyRankRows.value.length)))
const weeklyStatusLabel = computed(() => {
if (platformLoading.value) return '连接中'
if (!platformConnected.value) return '离线记录中'
if (platformSyncStatus.value === 'pending' || platformSyncStatus.value === 'syncing') return '保存中'
return '真实周榜'
})
onLoad(() => {
onShareAppMessage(() => {
recordPlatformShare()
return {
title: gameShareTitle(),
path: gameSharePath()
}
})
onShareTimeline(() => {
recordPlatformShare()
const inviteCode = String(platformLeaderboard.value.invite_code || '')
return {
title: gameShareTitle(),
query: inviteCode ? `from=share&invite_code=${encodeURIComponent(inviteCode)}` : 'from=share'
}
})
onLoad((options = {}) => {
const info = uni.getSystemInfoSync()
safeTop.value = info.safeAreaInsets?.top || info.statusBarHeight || 20
bestScore.value = Number(uni.getStorageSync('tongji_endless_best') || 0)
incomingInviteCode = String(options.invite_code || '').trim()
startGame()
weeklyRankVisible.value = true
connectPlatform(incomingInviteCode)
})
onShow(() => {
connectPlatform(incomingInviteCode).then((ok) => {
if (ok) flushPlatformProgress()
})
})
onUnload(() => {
if (learned.value > 0 || gameOver.value) {
queuePlatformProgress(learned.value, score.value, gameOver.value)
flushPlatformProgress()
}
disposePlatform()
if (hintTimer) clearTimeout(hintTimer)
if (jackpotTimer) clearTimeout(jackpotTimer)
if (confettiTimer) clearTimeout(confettiTimer)
@@ -534,6 +612,25 @@ function getWeekRangeLabel(now) {
return `${label(monday)}—${label(sunday)}`
}
function formatWeekDate(value) {
const parts = String(value).split('-').map(Number)
if (parts.length !== 3) return String(value)
return `${parts[1]}月${parts[2]}日`
}
function gameShareTitle() {
if (learned.value > 0) {
return `我在控糖消消乐认识了${learned.value}份食物,一起来挑战吧`
}
return '控糖消消乐:边玩边认识常见食物'
}
function gameSharePath() {
const inviteCode = String(platformLeaderboard.value.invite_code || '')
const query = inviteCode ? `?from=share&invite_code=${encodeURIComponent(inviteCode)}` : '?from=share'
return `/tongji/endless-game/index${query}`
}
function openWeeklyRank() {
primeSfxFromGesture()
if (gameOver.value) return
@@ -542,6 +639,7 @@ function openWeeklyRank() {
return
}
weeklyRankVisible.value = true
syncAndRefreshPlatform(learned.value, score.value)
playSfx('select', .72)
}
@@ -560,6 +658,7 @@ function cycleCheer() {
function startGame() {
if (hintTimer) clearTimeout(hintTimer)
beginPlatformSession()
themeIndex.value = 0
board.value = buildPlayableBoard(openingKeysForTheme(currentTheme.value))
selected.value = -1
@@ -798,6 +897,7 @@ async function trySwap(a, b, soundPlayed = false) {
endedFood.value = foodOf(highTile.key)
saveBest()
gameOver.value = true
queuePlatformProgress(learned.value, score.value, true)
busy.value = false
return
}
@@ -872,10 +972,10 @@ async function resolveMatches(initialGroups, movedIndex) {
vibrate(largestMatch >= 5 || wave >= 3 ? 'medium' : 'light')
turnConsumed += removedNormal.length
learned.value += removedNormal.length
weeklyPreviewProgress.value += removedNormal.length
updateFoodTask(removedNormal)
const multiplier = feastMoves.value > 0 ? 2 : 1
score.value += (removedNormal.length * 12 + Math.max(0, wave - 1) * 20 + madeSpecial * 35) * multiplier
queuePlatformProgress(learned.value, score.value, false)
await wait(specialEffects.hasFire ? 330 : 210)
const next = [...board.value]
remove.forEach(index => { next[index] = null })