diff --git a/TUICallKit-Vue3/App.vue b/TUICallKit-Vue3/App.vue
index 06e86640..66fa355e 100644
--- a/TUICallKit-Vue3/App.vue
+++ b/TUICallKit-Vue3/App.vue
@@ -1,7 +1,6 @@
\ No newline at end of file
+
diff --git a/TUICallKit-Vue3/TUIKit/pages/chat/chat.vue b/TUICallKit-Vue3/TUIKit/pages/chat/chat.vue
index 3ae7a849..3e8e03d5 100644
--- a/TUICallKit-Vue3/TUIKit/pages/chat/chat.vue
+++ b/TUICallKit-Vue3/TUIKit/pages/chat/chat.vue
@@ -239,7 +239,6 @@
import messageEnhancerPlugin from '@tencentcloud/lite-chat/plugins/message-enhancer';
import richMediaMessagePlugin from '@tencentcloud/lite-chat/plugins/rich-media-message';
import groupPlugin from '@tencentcloud/lite-chat/plugins/group';
-import * as GenerateTestUserSig from "@/debug/GenerateTestUserSig-es.js";
import { TUICallKitAPI } from '@/TUICallKit/src/TUICallService/index';
import { TUIStore } from '@/TUICallKit/src/TUICallService/CallService/index';
import { StoreName, NAME, CallStatus } from '@/TUICallKit/src/TUICallService/const/index';
@@ -561,17 +560,12 @@ const { proxy } = getCurrentInstance()
},
}, false)
- const {userId } = res.data;
-
+ const { userId, userSig, sdkAppId: SDKAppID } = res.data;
patient_data.value=res.data.data
- const { userSig, SDKAppID,secretKey } = GenerateTestUserSig.genTestUserSig({
- userID:userId,
- });
getApp().globalData.userID = userId;
getApp().globalData.userSig = userSig;
getApp().globalData.SDKAppID = SDKAppID;
- getApp().globalData.secretKey = secretKey;
uni.setStorageSync('CallManager', userId)
// TUICallKit 必须在 TIM chat 登录成功后再 init,且必须传入 tim,否则无法接收通话信令(聊天正常、视频接不到)
}
diff --git a/TUICallKit-Vue3/doctor/pages/doctor/doctor.vue b/TUICallKit-Vue3/doctor/pages/doctor/doctor.vue
index 4d0ae3e8..36dad711 100644
--- a/TUICallKit-Vue3/doctor/pages/doctor/doctor.vue
+++ b/TUICallKit-Vue3/doctor/pages/doctor/doctor.vue
@@ -178,7 +178,6 @@
-
-
diff --git a/TUICallKit-Vue3/tongji/composables/useGameFx.js b/TUICallKit-Vue3/tongji/composables/useGameFx.js
deleted file mode 100644
index f35e92a9..00000000
--- a/TUICallKit-Vue3/tongji/composables/useGameFx.js
+++ /dev/null
@@ -1,188 +0,0 @@
-/**
- * 糖分突袭 · 视觉特效状态(飘字、粒子、连消横幅、震屏、闪屏)
- * 特效风格参考「开心消消乐」:糖果色星屑、弹性扩散光环、大消除闪屏。
- */
-import { ref } from 'vue'
-
-let fxSeq = 0
-function nextFxId(prefix) {
- fxSeq += 1
- return `${prefix}-${fxSeq}-${Date.now()}`
-}
-
-// 开心消消乐式糖果色板(彩虹爆裂用)
-const CANDY_COLORS = ['#FF5E9C', '#FFC93C', '#5BD16A', '#4D9DFF', '#FF8A3D', '#B57BFF', '#FF6F91', '#2FE6C8']
-// 星屑字形
-const STAR_GLYPHS = ['✦', '✧', '★', '✨']
-
-export function useGameFx() {
- const floatingTexts = ref([])
- const burstParticles = ref([])
- const ripples = ref([])
- const comboBanner = ref({ show: false, text: '', tier: 1 })
- const screenShaking = ref(false)
- const flash = ref({ active: false, color: '#ffffff', opacity: 0 })
-
- let comboBannerTimer = null
- let shakeTimer = null
- let flashTimer = null
- let flashFadeTimer = null
-
- function spawnFloatingText(payload) {
- const id = nextFxId('ft')
- floatingTexts.value.push({ id, ...payload })
- const duration = payload.danger ? 1500 : payload.isCombo ? 1400 : 1200
- setTimeout(() => {
- floatingTexts.value = floatingTexts.value.filter((f) => f.id !== id)
- }, duration)
- return id
- }
-
- /**
- * 糖果爆裂粒子(星屑 + 圆点混合,上抛后受重力下落,带旋转与缓动)。
- * @param {number} cx,cy 爆裂中心(屏幕坐标)
- * @param {string} color 基础颜色
- * @param {number} count 粒子数量
- * @param {object} opts { star, rainbow, gravity, sizeMin, sizeMax, distMin, distMax, life }
- */
- function spawnBurst(cx, cy, color, count = 8, opts = {}) {
- const {
- star = true,
- rainbow = false,
- gravity = 64,
- sizeMin = 8,
- sizeMax = 16,
- distMin = 36,
- distMax = 110,
- life = 850
- } = opts
- const dur = (life / 1000).toFixed(2)
-
- for (let i = 0; i < count; i++) {
- const id = nextFxId('p')
- const isStar = star && Math.random() < 0.65
- const pSize = Math.random() * (sizeMax - sizeMin) + sizeMin
- const angle = Math.random() * Math.PI * 2
- const distance = Math.random() * (distMax - distMin) + distMin
- const tx = Math.cos(angle) * distance
- // 先上抛、再叠加重力下落,形成喷泉抛物线
- const ty = Math.sin(angle) * distance - distance * 0.35 + gravity
- const half = pSize / 2
- const pColor = rainbow
- ? CANDY_COLORS[Math.floor(Math.random() * CANDY_COLORS.length)]
- : color
- const rot = Math.random() * 720 - 360
- const glyph = isStar ? STAR_GLYPHS[Math.floor(Math.random() * STAR_GLYPHS.length)] : ''
-
- const particle = {
- id,
- left: cx - half,
- top: cy - half,
- size: pSize,
- color: pColor,
- char: glyph,
- opacity: 1,
- transform: 'scale(0.4) rotate(0deg)',
- transition: 'none'
- }
- burstParticles.value.push(particle)
-
- setTimeout(() => {
- particle.transition =
- `left ${dur}s cubic-bezier(0.16,0.7,0.3,1), top ${dur}s cubic-bezier(0.3,0.1,0.4,1), opacity ${dur}s ease-in, transform ${dur}s ease-out`
- particle.left = cx - half + tx
- particle.top = cy - half + ty
- particle.opacity = 0
- particle.transform = `scale(${isStar ? 1.15 : 0.5}) rotate(${rot}deg)`
- }, 16)
-
- setTimeout(() => {
- burstParticles.value = burstParticles.value.filter((p) => p.id !== id)
- }, life + 80)
- }
- }
-
- /**
- * 弹性扩散发光光环(开心消消乐式的扩散光圈)。
- * @param {object} opts { thickness, life }
- */
- function spawnRipple(cx, cy, color, tier = 1, opts = {}) {
- const id = nextFxId('rp')
- const size = 36 + tier * 18
- const { thickness = Math.max(3, tier + 2), life = 600 } = opts
- ripples.value.push({
- id,
- left: cx,
- top: cy,
- size,
- color,
- thickness,
- opacity: 0.95
- })
- setTimeout(() => {
- ripples.value = ripples.value.filter((r) => r.id !== id)
- }, life)
- }
-
- /**
- * 全屏闪光(大消除瞬间的爆闪),先点亮再淡出。
- */
- function triggerFlash(color = '#ffffff', intensity = 0.5, life = 280) {
- if (flashFadeTimer) clearTimeout(flashFadeTimer)
- if (flashTimer) clearTimeout(flashTimer)
- flash.value = { active: true, color, opacity: intensity }
- flashFadeTimer = setTimeout(() => {
- flash.value = { ...flash.value, opacity: 0 }
- }, 24)
- flashTimer = setTimeout(() => {
- flash.value = { active: false, color, opacity: 0 }
- }, life)
- }
-
- function showComboBanner(chain, points) {
- if (chain <= 1) return
- const tier = chain >= 4 ? 3 : chain >= 3 ? 2 : 1
- comboBanner.value = {
- show: true,
- text: `连消 ×${chain} +${points}`,
- tier
- }
- if (comboBannerTimer) clearTimeout(comboBannerTimer)
- comboBannerTimer = setTimeout(() => {
- comboBanner.value = { ...comboBanner.value, show: false }
- }, 1100)
- }
-
- function triggerShake(intensity = 1) {
- screenShaking.value = true
- if (shakeTimer) clearTimeout(shakeTimer)
- shakeTimer = setTimeout(() => {
- screenShaking.value = false
- }, intensity >= 2 ? 420 : 280)
- }
-
- function clearAll() {
- floatingTexts.value = []
- burstParticles.value = []
- ripples.value = []
- comboBanner.value = { show: false, text: '', tier: 1 }
- screenShaking.value = false
- flash.value = { active: false, color: '#ffffff', opacity: 0 }
- }
-
- return {
- floatingTexts,
- burstParticles,
- ripples,
- comboBanner,
- screenShaking,
- flash,
- spawnFloatingText,
- spawnBurst,
- spawnRipple,
- showComboBanner,
- triggerShake,
- triggerFlash,
- clearAll
- }
-}
diff --git a/TUICallKit-Vue3/tongji/composables/useGameProgress.js b/TUICallKit-Vue3/tongji/composables/useGameProgress.js
deleted file mode 100644
index 530e1650..00000000
--- a/TUICallKit-Vue3/tongji/composables/useGameProgress.js
+++ /dev/null
@@ -1,112 +0,0 @@
-/**
- * 游戏进度持久化:关卡、星级、连胜、每日打卡
- */
-import { ref } from 'vue'
-import { calcLevelStars } from '../data/gameLevels.js'
-
-const STORAGE_KEY = 'tongji_game_progress_v1'
-
-const DEFAULT = {
- currentLevel: 1,
- maxUnlocked: 1,
- stars: {},
- winStreak: 0,
- dailyStreak: 0,
- lastPlayDate: '',
- totalWins: 0,
- bestCombo: 0,
- totalStars: 0
-}
-
-function todayStr() {
- const d = new Date()
- return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
-}
-
-function sumStars(starsMap) {
- return Object.values(starsMap || {}).reduce((s, n) => s + (Number(n) || 0), 0)
-}
-
-function loadRaw() {
- try {
- const raw = uni.getStorageSync(STORAGE_KEY)
- if (raw && typeof raw === 'object') return { ...DEFAULT, ...raw }
- } catch (_) {}
- return { ...DEFAULT }
-}
-
-function saveRaw(data) {
- try {
- uni.setStorageSync(STORAGE_KEY, data)
- } catch (_) {}
-}
-
-export function useGameProgress() {
- const progress = ref(loadRaw())
-
- function persist() {
- progress.value.totalStars = sumStars(progress.value.stars)
- saveRaw(progress.value)
- }
-
- /** 进入游戏时更新每日打卡 */
- function touchDailyPlay() {
- const today = todayStr()
- const last = progress.value.lastPlayDate
- if (last === today) return progress.value.dailyStreak
-
- const yesterday = new Date()
- yesterday.setDate(yesterday.getDate() - 1)
- const yStr = `${yesterday.getFullYear()}-${String(yesterday.getMonth() + 1).padStart(2, '0')}-${String(yesterday.getDate()).padStart(2, '0')}`
-
- if (last === yStr) {
- progress.value.dailyStreak = (progress.value.dailyStreak || 0) + 1
- } else {
- progress.value.dailyStreak = 1
- }
- progress.value.lastPlayDate = today
- persist()
- return progress.value.dailyStreak
- }
-
- function getStars(levelId) {
- return progress.value.stars[String(levelId)] || 0
- }
-
- function recordWin({ levelId, movesLeft, glucoseLevel, score, levelConfig, maxCombo }) {
- const stars = calcLevelStars({ movesLeft, glucoseLevel, score, levelConfig })
- const key = String(levelId)
- const prev = progress.value.stars[key] || 0
- if (stars > prev) progress.value.stars[key] = stars
-
- progress.value.winStreak = (progress.value.winStreak || 0) + 1
- progress.value.totalWins = (progress.value.totalWins || 0) + 1
- if (maxCombo > (progress.value.bestCombo || 0)) progress.value.bestCombo = maxCombo
-
- const next = levelId + 1
- if (next > progress.value.maxUnlocked) progress.value.maxUnlocked = next
- progress.value.currentLevel = next
- persist()
- return { stars, newBest: stars > prev }
- }
-
- function recordLoss() {
- progress.value.winStreak = 0
- persist()
- }
-
- function setCurrentLevel(levelId) {
- progress.value.currentLevel = Math.max(1, Math.min(progress.value.maxUnlocked, levelId))
- persist()
- }
-
- return {
- progress,
- touchDailyPlay,
- getStars,
- recordWin,
- recordLoss,
- setCurrentLevel,
- persist
- }
-}
diff --git a/TUICallKit-Vue3/tongji/composables/useGameSfx.js b/TUICallKit-Vue3/tongji/composables/useGameSfx.js
deleted file mode 100644
index b25f3327..00000000
--- a/TUICallKit-Vue3/tongji/composables/useGameSfx.js
+++ /dev/null
@@ -1,303 +0,0 @@
-/**
- * 糖分突袭 · 游戏音效(InnerAudioContext 池化,H5/小程序通用)
- * 每种操作独立音轨 + playbackRate 变调,避免“全是同一个声”
- */
-import { ref, onUnmounted } from 'vue'
-
-const STORAGE_KEY = 'tongji_game_sfx_enabled'
-const COS = 'https://gz-1349751149.cos.ap-guangzhou.myqcloud.com/uploads/file'
-
-/** 9 条基础素材(已上传 COS,小程序域名白名单内) */
-const SRC = {
- tap: `${COS}/20260526/20260526105128557ef8669.mp3`,
- swoosh: `${COS}/20260527/202605271539371ebe13672.mp3`,
- pop: `${COS}/20260526/202605261051282a0a94508.mp3`,
- buzz: `${COS}/20260527/202605271539376ff628472.mp3`,
- chime: `${COS}/20260528/202605280937077bad76716.mp3`,
- fanfare: `${COS}/20260528/20260528093707b021f5794.mp3`,
- blast: `${COS}/20260528/20260528093707ebd4d5733.mp3`,
- sparkle: `${COS}/20260528/20260528093707016f07427.mp3`,
- stinger: `${COS}/20260528/20260528093709c669f4659.mp3`
-}
-
-/**
- * 音效表:src / volume / rate(playbackRate) / pool
- * rate 不同可在同一素材上听出明显差异
- */
-const SFX = {
- // —— 交互 ——
- select: { src: SRC.tap, volume: 0.32, rate: 1.05, pool: 3 },
- deselect: { src: SRC.tap, volume: 0.22, rate: 0.82, pool: 2 },
- hint: { src: SRC.tap, volume: 0.26, rate: 1.28, pool: 2 },
-
- swap: { src: SRC.swoosh, volume: 0.38, rate: 1.0, pool: 3 },
- swapFail: { src: SRC.buzz, volume: 0.36, rate: 1.18, pool: 2 },
-
- drop: { src: SRC.pop, volume: 0.3, rate: 0.88, pool: 4 },
- dropLight: { src: SRC.pop, volume: 0.22, rate: 1.22, pool: 2 },
-
- // —— 消除(按连数)——
- match3: { src: SRC.chime, volume: 0.48, rate: 1.0, pool: 4 },
- match4: { src: SRC.fanfare, volume: 0.54, rate: 1.06, pool: 3 },
- match5: { src: SRC.blast, volume: 0.62, rate: 1.0, pool: 3 },
-
- // —— 消除(按 GI 主色)——
- matchLowGi: { src: SRC.chime, volume: 0.42, rate: 0.86, pool: 2 },
- matchMidGi: { src: SRC.chime, volume: 0.46, rate: 1.02, pool: 2 },
- matchHighGi: { src: SRC.buzz, volume: 0.44, rate: 0.92, pool: 2 },
-
- // —— 连消 ——
- combo2: { src: SRC.fanfare, volume: 0.56, rate: 1.12, pool: 2 },
- combo3: { src: SRC.blast, volume: 0.64, rate: 1.08, pool: 2 },
- comboMega: { src: SRC.blast, volume: 0.72, rate: 1.22, pool: 2 },
-
- // —— 道具 ——
- insulin: { src: SRC.sparkle, volume: 0.5, rate: 1.15, pool: 2 },
- fiber: { src: SRC.swoosh, volume: 0.48, rate: 1.32, pool: 2 },
- meal: { src: SRC.stinger, volume: 0.42, rate: 1.35, pool: 1 },
- reshuffle: { src: SRC.swoosh, volume: 0.46, rate: 0.72, pool: 2 },
-
- // —— 反馈 ——
- score: { src: SRC.tap, volume: 0.28, rate: 1.38, pool: 2 },
- goalTick: { src: SRC.chime, volume: 0.36, rate: 1.45, pool: 2 },
- meterWarn: { src: SRC.buzz, volume: 0.5, rate: 1.0, pool: 2 },
- meterDanger: { src: SRC.buzz, volume: 0.58, rate: 0.78, pool: 2 },
- meterStable: { src: SRC.pop, volume: 0.24, rate: 1.05, pool: 1 },
-
- win: { src: SRC.fanfare, volume: 0.66, rate: 1.0, pool: 1 },
- winStinger: { src: SRC.stinger, volume: 0.38, rate: 1.5, pool: 1 },
- lose: { src: SRC.buzz, volume: 0.52, rate: 0.68, pool: 1 },
-
- // 兼容旧名
- match: { src: SRC.chime, volume: 0.48, rate: 1.0, pool: 4 },
- combo: { src: SRC.fanfare, volume: 0.56, rate: 1.1, pool: 3 },
- heal: { src: SRC.pop, volume: 0.44, rate: 1.18, pool: 2 },
- boost: { src: SRC.sparkle, volume: 0.52, rate: 1.0, pool: 2 },
- invalid: { src: SRC.buzz, volume: 0.4, rate: 1.15, pool: 2 },
- danger: { src: SRC.blast, volume: 0.58, rate: 0.95, pool: 2 }
-}
-
-class SfxPool {
- constructor(src, size, volume, rate = 1) {
- this.src = src
- this.size = size
- this.volume = volume
- this.rate = rate
- this.list = []
- this.cursor = 0
- this.warmedUp = false
- }
-
- create() {
- for (let i = 0; i < this.size; i++) {
- const ctx = uni.createInnerAudioContext()
- ctx.src = this.src
- ctx.obeyMuteSwitch = false
- ctx.autoplay = false
- ctx.volume = this.volume
- try {
- ctx.playbackRate = this.rate
- } catch (_) {}
- this.list.push(ctx)
- }
- }
-
- warmUp() {
- if (this.warmedUp) return
- if (!this.list.length) this.create()
- this.list.forEach((ctx) => {
- try {
- ctx.volume = 0
- ctx.play()
- setTimeout(() => {
- try {
- ctx.stop()
- ctx.volume = this.volume
- } catch (_) {}
- }, 60)
- } catch (_) {}
- })
- this.warmedUp = true
- }
-
- play(scale = 1, rateMul = 1) {
- if (!this.list.length) this.create()
- const ctx = this.list[this.cursor % this.list.length]
- this.cursor += 1
- const vol = Math.min(1, this.volume * scale)
- const rate = Math.min(2, Math.max(0.5, this.rate * rateMul))
- try {
- ctx.volume = vol
- try {
- ctx.playbackRate = rate
- } catch (_) {}
- ctx.stop()
- ctx.seek(0)
- ctx.play()
- } catch (_) {
- try {
- ctx.play()
- } catch (e) {}
- }
- }
-
- destroy() {
- this.list.forEach((ctx) => {
- try {
- ctx.stop()
- ctx.destroy()
- } catch (_) {}
- })
- this.list = []
- this.warmedUp = false
- }
-}
-
-function dominantGi(tiles = []) {
- const cnt = { low: 0, mid: 0, high: 0 }
- tiles.forEach((t) => {
- const g = t?.type?.gi
- if (g && cnt[g] !== undefined) cnt[g] += 1
- })
- let best = 'mid'
- let max = 0
- Object.entries(cnt).forEach(([k, v]) => {
- if (v > max) {
- max = v
- best = k
- }
- })
- return best
-}
-
-export function useGameSfx() {
- const enabled = ref(true)
- const pools = {}
-
- try {
- const stored = uni.getStorageSync(STORAGE_KEY)
- if (stored === false || stored === '0' || stored === 0) {
- enabled.value = false
- }
- } catch (_) {}
-
- function ensureAudioOption() {
- // #ifdef MP-WEIXIN
- try {
- uni.setInnerAudioOption({
- obeyMuteSwitch: false,
- mixWithOther: true
- })
- } catch (_) {}
- // #endif
- }
-
- function getPool(name) {
- const cfg = SFX[name]
- if (!cfg) return null
- if (!pools[name]) {
- pools[name] = new SfxPool(cfg.src, cfg.pool, cfg.volume, cfg.rate ?? 1)
- }
- return pools[name]
- }
-
- function warmUp() {
- if (!enabled.value) return
- ensureAudioOption()
- Object.keys(SFX).forEach((name) => {
- getPool(name)?.warmUp()
- })
- }
-
- function play(name, scale = 1, rateMul = 1) {
- if (!enabled.value) return
- getPool(name)?.play(scale, rateMul)
- }
-
- function playLayer(primary, secondary, delayMs = 70, secScale = 0.6) {
- play(primary)
- if (secondary) {
- setTimeout(() => play(secondary, secScale), delayMs)
- }
- }
-
- function playMatch({ matchCount = 3, chain = 0, tiles = [] } = {}) {
- if (!enabled.value) return
- const giTrack = (() => {
- const gi = dominantGi(tiles)
- if (gi === 'low') return 'matchLowGi'
- if (gi === 'high') return 'matchHighGi'
- return 'matchMidGi'
- })()
-
- if (chain >= 3) {
- playLayer('comboMega', giTrack, 90, 0.55)
- return
- }
- if (chain === 2) {
- playLayer('combo3', giTrack, 80, 0.5)
- return
- }
- if (chain === 1) {
- playLayer('combo2', giTrack, 70, 0.45)
- return
- }
-
- if (matchCount >= 5) {
- playLayer('match5', 'matchHighGi', 85, 0.5)
- } else if (matchCount >= 4) {
- playLayer('match4', giTrack, 65, 0.48)
- } else {
- play(giTrack, 1, 1)
- setTimeout(() => play('match3', 0.85), 40)
- }
- }
-
- function playDrop(count = 1) {
- if (!enabled.value || count <= 0) return
- const name = count >= 4 ? 'drop' : 'dropLight'
- play(name, Math.min(1.2, 0.85 + count * 0.04), count >= 6 ? 0.9 : 1)
- }
-
- function playMeter(delta, level) {
- if (!enabled.value) return
- if (level > 70) play('meterDanger', 1 + (level - 70) * 0.02)
- else if (level < 30) play('meterWarn', 0.9, 0.95)
- else if (delta > 8) play('meterWarn', 0.75)
- else if (delta < -5) play('meterStable', 1.1)
- }
-
- function toggleEnabled() {
- enabled.value = !enabled.value
- try {
- uni.setStorageSync(STORAGE_KEY, enabled.value ? '1' : '0')
- } catch (_) {}
- if (enabled.value) {
- warmUp()
- play('select')
- }
- }
-
- function destroyAll() {
- Object.keys(pools).forEach((key) => {
- pools[key]?.destroy()
- delete pools[key]
- })
- }
-
- onUnmounted(() => {
- destroyAll()
- })
-
- return {
- enabled,
- play,
- playLayer,
- playMatch,
- playDrop,
- playMeter,
- warmUp,
- toggleEnabled,
- destroyAll
- }
-}
diff --git a/TUICallKit-Vue3/tongji/data/gameFoodArt.js b/TUICallKit-Vue3/tongji/data/gameFoodArt.js
deleted file mode 100644
index 33b33f1a..00000000
--- a/TUICallKit-Vue3/tongji/data/gameFoodArt.js
+++ /dev/null
@@ -1,654 +0,0 @@
-/**
- * 棋盘食材 · 仿真插画 SVG(渐变 + 高光 + 阴影,开心消消乐式 2.5D)
- */
-import { svgToDataUrl } from '../utils/svgDataUrl.js'
-
-function gid(key, suffix) {
- return `f-${String(key).replace(/[^a-z0-9]/gi, '')}-${suffix}`
-}
-
-function wrap(key, body) {
- return ``
-}
-
-function shadow(key) {
- const id = gid(key, 'sh')
- return ``
-}
-
-function shine(x, y, rx, ry) {
- return ``
-}
-
-/** 圆形水果 */
-function artRound(key, base, dark, light, extra = '') {
- const g = gid(key, 'g')
- return wrap(key, `
-
-
-
-
-
-
- ${shadow(key)}
-
-
-
- ${shine(24, 28, 7, 5)}
-
-
- ${extra}
- `)
-}
-
-/** 叶菜 */
-function artLeafy(key, light, mid, dark) {
- const g1 = gid(key, 'g1')
- const g2 = gid(key, 'g2')
- return wrap(key, `
-
-
-
-
-
-
-
- ${shadow(key)}
-
-
-
-
- ${shine(26, 24, 6, 8)}
- `)
-}
-
-/** 西兰花 */
-function artBroccoli(key) {
- const g = gid(key, 'g')
- return wrap(key, `
-
-
-
-
- ${shadow(key)}
-
-
-
-
-
- ${shine(28, 20, 5, 4)}
- `)
-}
-
-/** 长条(黄瓜/香蕉/玉米) */
-function artLong(key, base, dark, light, type = 'cucumber') {
- const g = gid(key, 'g')
- if (type === 'banana') {
- return wrap(key, `
-
-
- ${shadow(key)}
-
-
-
- ${shine(30, 26, 5, 3)}
- `)
- }
- if (type === 'corn') {
- return wrap(key, `
-
-
- ${shadow(key)}
-
-
- ${Array.from({ length: 5 }, (_, r) =>
- Array.from({ length: 4 }, (_, c) =>
- ``
- ).join('')
- ).join('')}
-
- ${shine(26, 26, 5, 8)}
- `)
- }
- return wrap(key, `
-
-
- ${shadow(key)}
-
-
-
- `)
-}
-
-/** 茄子 */
-function artEggplant(key) {
- const g = gid(key, 'g')
- return wrap(key, `
-
-
- ${shadow(key)}
-
-
-
- ${shine(26, 32, 5, 7)}
- `)
-}
-
-/** 番茄 */
-function artTomato(key) {
- return artRound(key, '#e53935', '#b71c1c', '#ef5350',
- '')
-}
-
-/** 甜椒 */
-function artPepper(key, color, dark) {
- const g = gid(key, 'g')
- return wrap(key, `
-
-
- ${shadow(key)}
-
-
-
- ${shine(28, 26, 5, 6)}
- `)
-}
-
-/** 洋葱 */
-function artOnion(key) {
- const g = gid(key, 'g')
- return wrap(key, `
-
-
- ${shadow(key)}
-
-
-
- ${shine(24, 30, 6, 5)}
- `)
-}
-
-/** 葡萄串 */
-function artGrapes(key) {
- const g = gid(key, 'g')
- return wrap(key, `
-
-
- ${shadow(key)}
-
-
-
-
-
-
- ${shine(28, 24, 3, 2)}
- `)
-}
-
-/** 樱桃 */
-function artCherry(key) {
- const g = gid(key, 'g')
- return wrap(key, `
-
-
- ${shadow(key)}
-
-
-
- ${shine(19, 29, 3, 2)}${shine(39, 29, 3, 2)}
- `)
-}
-
-/** 草莓 */
-function artStrawberry(key) {
- const g = gid(key, 'g')
- return wrap(key, `
-
-
- ${shadow(key)}
-
-
-
- ${shine(26, 28, 4, 6)}
- ${[28, 34, 38, 30, 36].map((x, i) =>
- ``
- ).join('')}
- `)
-}
-
-/** 豆类 */
-function artBeans(key) {
- const g = gid(key, 'g')
- return wrap(key, `
-
-
- ${shadow(key)}
-
-
-
-
- `)
-}
-
-/** 饮品杯 */
-function artCup(key, liquid, cup = '#eceff1', type = 'glass') {
- const g = gid(key, 'g')
- if (type === 'bottle') {
- return wrap(key, `
-
-
- ${shadow(key)}
-
-
-
- ${shine(28, 30, 4, 10)}
- `)
- }
- if (type === 'wine') {
- return wrap(key, `
-
-
- ${shadow(key)}
-
-
-
-
- `)
- }
- return wrap(key, `
-
-
- ${shadow(key)}
-
-
-
- ${shine(26, 32, 4, 8)}
- `)
-}
-
-/** 蘑菇 */
-function artMushroom(key) {
- const g = gid(key, 'g')
- return wrap(key, `
-
-
- ${shadow(key)}
-
-
-
-
-
- ${shine(24, 24, 6, 4)}
- `)
-}
-
-/** 芹菜 */
-function artCelery(key) {
- return artLeafy(key, '#aed581', '#7cb342', '#558b2f')
-}
-
-/** 四季豆 */
-function artGreenBean(key) {
- const g = gid(key, 'g')
- return wrap(key, `
-
-
- ${shadow(key)}
-
-
-
- `)
-}
-
-/** 饭碗 */
-function artRiceBowl(key, rice, bowl = '#e0e0e0', dark = '#bdbdbd') {
- const g = gid(key, 'g')
- return wrap(key, `
-
-
- ${shadow(key)}
-
-
-
- ${shine(24, 34, 6, 3)}
- `)
-}
-
-/** 薯类 */
-function artRoot(key, base, dark, light) {
- const g = gid(key, 'g')
- return wrap(key, `
-
-
- ${shadow(key)}
-
-
-
- ${shine(24, 30, 5, 6)}
- `)
-}
-
-/** 肉类 */
-function artMeat(key, base, dark, type = 'steak') {
- const g = gid(key, 'g')
- if (type === 'chicken') {
- return wrap(key, `
-
-
- ${shadow(key)}
-
-
-
-
- ${shine(26, 32, 5, 6)}
- `)
- }
- if (type === 'fish') {
- return wrap(key, `
-
-
- ${shadow(key)}
-
-
-
-
- ${shine(28, 30, 6, 4)}
- `)
- }
- return wrap(key, `
-
-
- ${shadow(key)}
-
-
-
- ${shine(26, 28, 6, 4)}
- `)
-}
-
-/** 虾/蟹 */
-function artSeafood(key, type = 'shrimp') {
- const g = gid(key, 'g')
- if (type === 'crab') {
- return wrap(key, `
-
-
- ${shadow(key)}
-
-
-
-
- `)
- }
- return wrap(key, `
-
-
- ${shadow(key)}
-
-
- ${shine(30, 28, 5, 4)}
- `)
-}
-
-/** 菠萝 */
-function artPineapple(key) {
- const g = gid(key, 'g')
- return wrap(key, `
-
-
- ${shadow(key)}
-
-
- ${[-8, 0, 8].map(o =>
- ``
- ).join('')}
-
- ${shine(26, 32, 5, 6)}
- `)
-}
-
-/** 奇异果 */
-function artKiwi(key) {
- const g = gid(key, 'g')
- return wrap(key, `
-
-
- ${shadow(key)}
-
-
-
-
- ${shine(26, 28, 4, 3)}
- `)
-}
-
-/** 蜜瓜 */
-function artMelon(key) {
- return artRound(key, '#aed581', '#689f38', '#c5e1a5', '')
-}
-
-/** 蜂蜜 */
-function artHoney(key) {
- return artCup(key, '#ffb300', '#ff8f00', 'bottle')
-}
-
-/** 面包类 */
-function artBread(key, base, dark, type = 'loaf') {
- const g = gid(key, 'g')
- if (type === 'donut') {
- return wrap(key, `
-
-
- ${shadow(key)}
-
-
-
-
- ${shine(24, 28, 5, 4)}
- `)
- }
- if (type === 'mantou') {
- return wrap(key, `
-
-
- ${shadow(key)}
-
-
-
- ${shine(24, 32, 6, 4)}
- `)
- }
- return wrap(key, `
-
-
- ${shadow(key)}
-
-
- ${shine(22, 28, 8, 5)}
- `)
-}
-
-/** 面条 */
-function artNoodle(key, soup = '#d7ccc8') {
- const g = gid(key, 'g')
- return wrap(key, `
-
-
- ${shadow(key)}
-
-
-
-
-
- `)
-}
-
-/** 南瓜 */
-function artPumpkin(key) {
- const g = gid(key, 'g')
- return wrap(key, `
-
-
- ${shadow(key)}
-
-
-
-
-
- ${shine(26, 28, 5, 6)}
- `)
-}
-
-/** 薯条 */
-function artFries(key) {
- const g = gid(key, 'g')
- return wrap(key, `
-
-
- ${shadow(key)}
-
-
- ${[26, 32, 38, 44].map(x => ``).join('')}
- `)
-}
-
-/** 西瓜 */
-function artWatermelon(key) {
- const g = gid(key, 'g')
- return wrap(key, `
-
-
- ${shadow(key)}
-
-
-
- ${[24, 32, 40].map(x => ``).join('')}
- ${shine(24, 30, 4, 3)}
- `)
-}
-
-/** 糖果 */
-function artCandy(key, base, dark, type = 'lollipop') {
- const g = gid(key, 'g')
- if (type === 'wrap') {
- return wrap(key, `
-
-
- ${shadow(key)}
-
-
-
- `)
- }
- return wrap(key, `
-
-
- ${shadow(key)}
-
-
-
- ${shine(26, 24, 4, 3)}
- `)
-}
-
-/** 糯米团 */
-function artDango(key) {
- const g = gid(key, 'g')
- return wrap(key, `
-
-
- ${shadow(key)}
-
-
-
-
-
- `)
-}
-
-/** 爆米花 */
-function artPopcorn(key) {
- const g = gid(key, 'g')
- return wrap(key, `
-
-
- ${shadow(key)}
-
-
-
-
- `)
-}
-
-const FOOD_ART = {
- lettuce: () => artLeafy('lettuce', '#c5e1a5', '#81c784', '#388e3c'),
- broccoli: () => artBroccoli('broccoli'),
- apple: () => artRound('apple', '#e53935', '#b71c1c', '#ef5350'),
- cucumber: () => artLong('cucumber', '#66bb6a', '#2e7d32', '#a5d6a7'),
- eggplant: () => artEggplant('eggplant'),
- tomato: () => artTomato('tomato'),
- pepper: () => artPepper('pepper', '#43a047', '#1b5e20'),
- onion: () => artOnion('onion'),
- pear: () => artRound('pear', '#c0ca33', '#827717', '#dce775', ''),
- orange: () => artRound('orange', '#fb8c00', '#e65100', '#ffb74d'),
- peach: () => artRound('peach', '#ff8a65', '#e64a19', '#ffab91'),
- cherry: () => artCherry('cherry'),
- grape: () => artGrapes('grape'),
- strawberry: () => artStrawberry('strawberry'),
- soybean: () => artBeans('soybean'),
- milk: () => artCup('milk', '#eceff1', '#b0bec5'),
- tea: () => artCup('tea', '#a1887f', '#6d4c41'),
- mushroom: () => artMushroom('mushroom'),
- celery: () => artCelery('celery'),
- greenBean: () => artGreenBean('greenBean'),
- brownRice: () => artRiceBowl('brownRice', '#bcaaa4', '#8d6e63', '#6d4c41'),
- sweetPotato: () => artRoot('sweetPotato', '#c75b39', '#8d2600', '#e57373'),
- taro: () => artRoot('taro', '#b39ddb', '#5e35b1', '#d1c4e9'),
- fish: () => artMeat('fish', '#4fc3f7', '#0277bd', 'fish'),
- chicken: () => artMeat('chicken', '#ffb74d', '#e65100', 'chicken'),
- beef: () => artMeat('beef', '#c0504d', '#7f0000', 'steak'),
- leanMeat: () => artMeat('leanMeat', '#b5654d', '#8d4000', 'steak'),
- shrimp: () => artSeafood('shrimp', 'shrimp'),
- crab: () => artSeafood('crab', 'crab'),
- banana: () => artLong('banana', '#fdd835', '#f9a825', '#fff176', 'banana'),
- mango: () => artRound('mango', '#ffb300', '#ff6f00', '#ffca28'),
- pineapple: () => artPineapple('pineapple'),
- kiwi: () => artKiwi('kiwi'),
- melon: () => artMelon('melon'),
- honey: () => artHoney('honey'),
- wine: () => artCup('wine', '#9b2c3b', '#4a0e16', 'wine'),
- beer: () => artCup('beer', '#ffb300', '#ff8f00'),
- coffee: () => artCup('coffee', '#6f4e37', '#3e2723'),
- corn: () => artLong('corn', '#f9c513', '#f57f17', '#fff176', 'corn'),
- udon: () => artNoodle('udon', '#d9b88f'),
- whiteRice: () => artRiceBowl('whiteRice', '#f5f5f5', '#eeeeee', '#bdbdbd'),
- whiteBread: () => artBread('whiteBread', '#d9a85c', '#a1887f', 'loaf'),
- mantou: () => artBread('mantou', '#ece0c8', '#bcaaa4', 'mantou'),
- youtiao: () => artBread('youtiao', '#d4943f', '#a1660a', 'loaf'),
- donut: () => artBread('donut', '#e87fb0', '#ad1457', 'donut'),
- popcorn: () => artPopcorn('popcorn'),
- ramen: () => artNoodle('ramen', '#e0a96d'),
- pumpkin: () => artPumpkin('pumpkin'),
- bakedPotato: () => artFries('bakedPotato'),
- watermelon: () => artWatermelon('watermelon'),
- sugar: () => artCandy('sugar', '#ff79b0', '#c2185b', 'lollipop'),
- maltose: () => artCandy('maltose', '#ffa726', '#e65100', 'wrap'),
- soda: () => artCup('soda', '#c62828', '#880e0e', 'bottle'),
- orangeJuice: () => artCup('orangeJuice', '#ff9f1c', '#e65100', 'bottle'),
- stickyRice: () => artDango('stickyRice')
-}
-
-const imgCache = {}
-
-/** 获取食材插画 data URL(带缓存) */
-export function getFoodImg(key) {
- if (imgCache[key]) return imgCache[key]
- const builder = FOOD_ART[key]
- if (!builder) return ''
- imgCache[key] = svgToDataUrl(builder())
- return imgCache[key]
-}
-
-export function warmUpFoodImgs(keys) {
- keys.forEach((k) => getFoodImg(k))
-}
diff --git a/TUICallKit-Vue3/tongji/data/gameFoodImg.js b/TUICallKit-Vue3/tongji/data/gameFoodImg.js
deleted file mode 100644
index 2c35bf60..00000000
--- a/TUICallKit-Vue3/tongji/data/gameFoodImg.js
+++ /dev/null
@@ -1,50 +0,0 @@
-/**
- * 食材图标 URL(Twemoji PNG,小程序 / H5 通用)
- * 微信小程序 不支持 SVG data URL,必须用 https PNG
- */
-const TWEMOJI_BASE = 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/72x72'
-
-/** emoji → Twemoji PNG 地址 */
-export function emojiToTwemojiUrl(emoji) {
- if (!emoji) return ''
- const parts = []
- for (let i = 0; i < emoji.length;) {
- const cp = emoji.codePointAt(i)
- if (!cp) break
- parts.push(cp.toString(16))
- i += cp > 0xffff ? 2 : 1
- }
- return `${TWEMOJI_BASE}/${parts.join('-')}.png`
-}
-
-const imgCache = {}
-
-export function getFoodImgUrl(emoji) {
- if (!emoji) return ''
- if (imgCache[emoji]) return imgCache[emoji]
- const url = emojiToTwemojiUrl(emoji)
- imgCache[emoji] = url
- return url
-}
-
-/** 预加载本局食材图标(小程序提前拉取,减少首屏空白) */
-export function warmUpFoodImgs(foods) {
- const list = Array.isArray(foods) ? foods : []
- list.forEach((f) => {
- const icon = typeof f === 'string' ? f : f?.icon
- // 优先预热自定义图片素材,缺省回退 emoji PNG
- const url = (typeof f === 'object' && f?.img) ? f.img : getFoodImgUrl(icon)
- if (!url) return
- // #ifdef MP-WEIXIN
- try {
- uni.getImageInfo({ src: url, fail: () => {} })
- } catch (_) {}
- // #endif
- // #ifdef H5
- try {
- const img = new Image()
- img.src = url
- } catch (_) {}
- // #endif
- })
-}
diff --git a/TUICallKit-Vue3/tongji/data/gameFoodLibrary.js b/TUICallKit-Vue3/tongji/data/gameFoodLibrary.js
deleted file mode 100644
index 4e8321a6..00000000
--- a/TUICallKit-Vue3/tongji/data/gameFoodLibrary.js
+++ /dev/null
@@ -1,205 +0,0 @@
-/**
- * 全量食材库:key 用于消除匹配,img 为 Twemoji PNG(小程序可用)
- */
-import { getFoodImgUrl } from './gameFoodImg.js'
-
-export const GI_LABEL = { low: '低血糖', mid: '中血糖', high: '高血糖' }
-
-/** 若实际食用时的升糖幅度(教育提示用,正值=升糖) */
-export function getEatVal(food) {
- if (food.eatVal != null) return food.eatVal
- if (food.gi === 'high') return Math.abs(food.val)
- if (food.gi === 'mid') return Math.max(1, food.val)
- return Math.max(1, Math.abs(food.val))
-}
-
-/** 从餐盘消除该食物后的血糖变化(负值=控糖有益) */
-export function getClearVal(food) {
- if (food.clearVal != null) return food.clearVal
- const eat = getEatVal(food)
- if (food.gi === 'high') return -Math.max(8, Math.round(eat * 0.5))
- if (food.gi === 'mid') return -1
- return 0
-}
-
-/** 点击食材时的升糖说明文案 */
-export function formatEatLabel(food) {
- if (food.gi === 'low') return '升糖低'
- return `若食用 +${getEatVal(food)}`
-}
-
-/** 含糖等级简称:低糖 / 中糖 / 高糖 */
-export const GI_SHORT = { low: '低糖', mid: '中糖', high: '高糖' }
-
-/** 含糖等级对应的提示主色(绿 / 橙 / 红) */
-export const GI_COLOR = { low: '#16a34a', mid: '#ea580c', high: '#dc2626' }
-
-/** 部分代表食物的专属科普文案(一句话,便于老人记忆) */
-const FOOD_TIP_OVERRIDE = {
- whiteRice: '精制主食,升糖快,建议小份',
- stickyRice: '糯米黏软,升糖很快,要少吃',
- whiteBread: '精制面食,升糖快',
- mantou: '白面馒头,升糖快,配菜一起吃更稳',
- youtiao: '油炸又高糖,少吃为好',
- sugar: '纯糖,升糖最快,尽量不吃',
- maltose: '糖分很高,升糖快',
- soda: '含糖饮料,升糖很快,不建议喝',
- orangeJuice: '果汁含糖高,吃整果更好',
- watermelon: '很甜,升糖快,一次别吃太多',
- donut: '又甜又油,升糖高',
- banana: '偏甜水果,适量吃',
- sweetPotato: '粗粮但有糖,可代替部分主食',
- corn: '粗粮主食,适量吃',
- honey: '本质是糖,别当健康品多吃',
- broccoli: '新鲜蔬菜,升糖很慢,可多吃',
- cucumber: '清爽蔬菜,几乎不升糖',
- lettuce: '绿叶菜,升糖很低,可多吃',
- celery: '高纤维蔬菜,升糖低',
- apple: '带皮整果,升糖较慢',
- milk: '低糖,含蛋白,适量喝好',
- fish: '优质蛋白,升糖低',
- beef: '蛋白为主,升糖低',
- milkOats: '燕麦加奶,升糖中等,选无糖燕麦更好',
- grainMantou: '杂粮做的,比白馒头稳,仍要控量',
- riceNoodleSoup: '米粉升糖快,汤粉要少吃',
- friedNoodles: '油多又是精面,升糖快',
- centuryEggCongee: '白粥熬得软烂,升糖很快,糖友要少喝',
- sandwich: '夹心面包精制碳多,升糖快',
- eightTreasureCongee: '八宝粥甜糯,升糖快,要少喝',
- milletCongee: '小米粥熬软升糖快,糖友控量',
- shandongPancake: '煎饼加油面,升糖中等偏快',
- cake: '蛋糕又甜又油,升糖快',
- biscuit: '饼干又甜又碎,升糖快',
- wonton: '馄饨面皮精白,升糖中等,别多吃',
- blackCoffee: '不加糖的黑咖啡,升糖很低'
-}
-
-/** 含糖等级通用科普文案 */
-const GI_TIP_DEFAULT = {
- low: '升糖慢,相对放心,可常吃',
- mid: '升糖中等,注意分量',
- high: '含糖高、升糖快,要少吃'
-}
-
-/** 点击食物时展示的一句话科普(认知教育用) */
-export function getFoodTip(food) {
- if (!food) return ''
- if (food.tip) return food.tip
- if (FOOD_TIP_OVERRIDE[food.key]) return FOOD_TIP_OVERRIDE[food.key]
- return GI_TIP_DEFAULT[food.gi] || ''
-}
-
-/** 自定义图片素材目录(腾讯云 COS,优先使用真实图片而非 emoji) */
-const GAMES_IMG_BASE = 'https://gz-1349751149.cos.ap-guangzhou.myqcloud.com/games'
-
-const RAW_FOODS = [
- // 低 GI
- { key: 'lettuce', icon: '🥬', name: '生菜', gi: 'low', val: -3, color: '#8BC34A' },
- { key: 'broccoli', icon: '🥦', name: '西兰花', gi: 'low', val: -3, color: '#4E8A3A' },
- { key: 'apple', icon: '🍎', name: '苹果', gi: 'low', val: -2, color: '#E53935' },
- { key: 'cucumber', icon: '🥒', name: '黄瓜', gi: 'low', val: -4, color: '#66A82F' },
- { key: 'eggplant', icon: '🍆', name: '茄子', gi: 'low', val: -3, color: '#7E57C2' },
- { key: 'tomato', icon: '🍅', name: '番茄', gi: 'low', val: -2, color: '#EF5350' },
- { key: 'pepper', icon: '🫑', name: '青椒', gi: 'low', val: -3, color: '#43A047' },
- { key: 'onion', icon: '🧅', name: '洋葱', gi: 'low', val: -2, color: '#CDA06B' },
- { key: 'pear', icon: '🍐', name: '雪梨', gi: 'low', val: -2, color: '#C0CA33' },
- { key: 'orange', icon: '🍊', name: '橙子', gi: 'low', val: -2, color: '#FB8C00' },
- { key: 'peach', icon: '🍑', name: '桃子', gi: 'low', val: -2, color: '#FF8A65' },
- { key: 'cherry', icon: '🍒', name: '樱桃', gi: 'low', val: -2, color: '#C2185B' },
- { key: 'grape', icon: '🍇', name: '葡萄', gi: 'low', val: -1, color: '#8E24AA' },
- { key: 'strawberry', icon: '🍓', name: '草莓', gi: 'low', val: -2, color: '#EC407A' },
- { key: 'soybean', icon: '🫘', name: '黄豆', gi: 'low', val: -3, color: '#C9A063' },
- { key: 'milk', icon: '🥛', name: '牛奶', gi: 'low', val: -2, color: '#B0BEC5' },
- { key: 'tea', icon: '🍵', name: '红茶', gi: 'low', val: -1, color: '#A1573B' },
- { key: 'mushroom', icon: '🍄', name: '香菇', gi: 'low', val: -3, color: '#8D6E63' },
- { key: 'celery', icon: '🌿', name: '芹菜', gi: 'low', val: -4, color: '#7CB342' },
- { key: 'greenBean', icon: '🫛', name: '四季豆', gi: 'low', val: -3, color: '#689F38' },
-
- // 中 GI
- { key: 'brownRice', icon: '🍙', name: '糙米饭', gi: 'mid', val: 4, color: '#C9A26B' },
- { key: 'sweetPotato', icon: '🍠', name: '番薯', gi: 'mid', val: 5, color: '#C75B39', img: `${GAMES_IMG_BASE}/%E7%95%AA%E8%96%AF.png` },
- { key: 'taro', icon: '🥔', name: '芋头', gi: 'mid', val: 5, color: '#B39DDB' },
- { key: 'fish', icon: '🐟', name: '鱼肉', gi: 'mid', val: 2, color: '#4FA3C7' },
- { key: 'chicken', icon: '🍗', name: '鸡肉', gi: 'mid', val: 3, color: '#D6A15A' },
- { key: 'beef', icon: '🥩', name: '牛肉', gi: 'mid', val: 3, color: '#C0504D' },
- { key: 'leanMeat', icon: '🍖', name: '瘦肉', gi: 'mid', val: 3, color: '#B5654D' },
- { key: 'shrimp', icon: '🦐', name: '虾仁', gi: 'mid', val: 2, color: '#FF7043' },
- { key: 'crab', icon: '🦀', name: '螃蟹', gi: 'mid', val: 2, color: '#F4623A' },
- { key: 'banana', icon: '🍌', name: '香蕉', gi: 'mid', val: 6, color: '#FDD835' },
- { key: 'mango', icon: '🥭', name: '芒果', gi: 'mid', val: 6, color: '#FFB300' },
- { key: 'pineapple', icon: '🍍', name: '菠萝', gi: 'mid', val: 6, color: '#FBC02D' },
- { key: 'kiwi', icon: '🥝', name: '奇异果', gi: 'mid', val: 4, color: '#9CCC65' },
- { key: 'melon', icon: '🍈', name: '哈密瓜', gi: 'mid', val: 6, color: '#AED581' },
- { key: 'honey', icon: '🍯', name: '蜂蜜', gi: 'mid', val: 6, color: '#F9A825' },
- { key: 'wine', icon: '🍷', name: '红酒', gi: 'mid', val: 4, color: '#9B2C3B' },
- { key: 'beer', icon: '🍺', name: '啤酒', gi: 'mid', val: 5, color: '#E0A83E' },
- { key: 'coffee', icon: '☕', name: '咖啡', gi: 'mid', val: 2, color: '#6F4E37' },
- { key: 'blackCoffee', icon: '☕', name: '黑咖啡', gi: 'low', val: -1, color: '#4E342E', img: `${GAMES_IMG_BASE}/%E9%BB%91%E5%92%96%E5%95%A1.png` },
- { key: 'wonton', icon: '🥟', name: '馄饨', gi: 'mid', val: 5, color: '#E8DCC8', img: `${GAMES_IMG_BASE}/%E9%A6%84%E9%A5%A8.png` },
- { key: 'milletCongee', icon: '🥣', name: '小米粥', gi: 'mid', val: 5, color: '#F5E6B8', img: `${GAMES_IMG_BASE}/%E5%B0%8F%E7%B1%B3%E7%B2%A5.png` },
- { key: 'shandongPancake', icon: '🥞', name: '山东煎饼', gi: 'high', val: 16, color: '#D4A574', img: `${GAMES_IMG_BASE}/%E5%B1%B1%E4%B8%9C%E7%85%8E%E9%A5%BC.png` },
- { key: 'corn', icon: '🌽', name: '玉米', gi: 'mid', val: 5, color: '#F9C513' },
- { key: 'udon', icon: '🍲', name: '乌冬面', gi: 'mid', val: 5, color: '#D9B88F' },
- { key: 'milkOats', icon: '🥣', name: '奶冲麦片', gi: 'mid', val: 6, color: '#E8D9B5', img: `${GAMES_IMG_BASE}/%E5%A5%B6%E5%86%B2%E9%BA%A6%E7%89%87.png` },
- { key: 'grainMantou', icon: '🫓', name: '杂粮馒头', gi: 'mid', val: 5, color: '#C8A878', img: `${GAMES_IMG_BASE}/%E6%9D%82%E7%B2%AE%E9%A6%92%E5%A4%B4.png` },
-
- // 高 GI
- { key: 'whiteRice', icon: '🍚', name: '白米饭', gi: 'high', val: 18, color: '#E0E0E0', img: `${GAMES_IMG_BASE}/%E7%B1%B3%E9%A5%AD.png` },
- { key: 'whiteBread', icon: '🍞', name: '白面包', gi: 'high', val: 16, color: '#D9A85C' },
- { key: 'mantou', icon: '🥯', name: '馒头', gi: 'high', val: 17, color: '#ECE0C8' },
- { key: 'youtiao', icon: '🥖', name: '油条', gi: 'high', val: 20, color: '#D4943F', img: `${GAMES_IMG_BASE}/%E6%B2%B9%E6%9D%A1.png` },
- { key: 'sandwich', icon: '🥪', name: '三明治', gi: 'high', val: 16, color: '#D9B88F', img: `${GAMES_IMG_BASE}/%E4%B8%89%E6%98%8E%E6%B2%BB.png` },
- { key: 'biscuit', icon: '🍪', name: '饼干', gi: 'high', val: 18, color: '#C9A063', img: `${GAMES_IMG_BASE}/%E9%A5%BC%E5%B9%B2.png` },
- { key: 'cake', icon: '🎂', name: '蛋糕', gi: 'high', val: 22, color: '#F48FB1', img: `${GAMES_IMG_BASE}/%E8%9B%8B%E7%B3%95.png` },
- { key: 'eightTreasureCongee', icon: '🥣', name: '八宝粥', gi: 'high', val: 17, color: '#E8C4A0', img: `${GAMES_IMG_BASE}/%E5%85%AB%E5%AE%9D%E7%B2%A5.png` },
- { key: 'donut', icon: '🍩', name: '甜甜圈', gi: 'high', val: 22, color: '#E87FB0' },
- { key: 'popcorn', icon: '🍿', name: '爆米花', gi: 'high', val: 18, color: '#F5E6B3' },
- { key: 'ramen', icon: '🍜', name: '拉面', gi: 'high', val: 17, color: '#E0A96D' },
- { key: 'pumpkin', icon: '🎃', name: '南瓜', gi: 'high', val: 15, color: '#EF6C00' },
- { key: 'bakedPotato', icon: '🍟', name: '焗薯', gi: 'high', val: 20, color: '#F2C14E' },
- { key: 'watermelon', icon: '🍉', name: '西瓜', gi: 'high', val: 16, color: '#F0506A' },
- { key: 'sugar', icon: '🍭', name: '砂糖', gi: 'high', val: 25, color: '#FF79B0' },
- { key: 'maltose', icon: '🍬', name: '麦芽糖', gi: 'high', val: 24, color: '#FFA726' },
- { key: 'soda', icon: '🥤', name: '汽水', gi: 'high', val: 25, color: '#C62828' },
- { key: 'orangeJuice', icon: '🧃', name: '柳橙汁', gi: 'high', val: 22, color: '#FF9F1C' },
- { key: 'stickyRice', icon: '🍡', name: '糯米饭', gi: 'high', val: 19, color: '#F2A7B8' },
- { key: 'riceNoodleSoup', icon: '🍜', name: '汤粉', gi: 'high', val: 16, color: '#E8C9A0', img: `${GAMES_IMG_BASE}/%E6%B1%A4%E7%B2%89.png` },
- { key: 'friedNoodles', icon: '🍝', name: '炒面', gi: 'high', val: 18, color: '#C8843C', img: `${GAMES_IMG_BASE}/%E7%82%92%E9%9D%A2.png` },
- { key: 'centuryEggCongee', icon: '🥣', name: '皮蛋瘦肉粥', gi: 'high', val: 15, color: '#D9CBB0', img: `${GAMES_IMG_BASE}/%E7%9A%AE%E8%9B%8B%E7%98%A6%E8%82%89%E7%B2%A5.png` }
-]
-
-export const FOOD_LIBRARY = RAW_FOODS.map((f, i) => {
- const eatVal = getEatVal(f)
- const clearVal = getClearVal({ ...f, eatVal })
- return {
- id: i + 1,
- ...f,
- eatVal,
- clearVal,
- giLabel: GI_LABEL[f.gi],
- get img() {
- // 优先使用自定义图片素材,缺省回退到 emoji PNG
- return f.img || getFoodImgUrl(f.icon)
- }
- }
-})
-
-/** 按 key 查找食材(目标栏等用) */
-export function getFoodByKey(key) {
- return FOOD_LIBRARY.find((f) => f.key === key) || null
-}
-
-/** 为棋盘 tile 使用的 plain 对象(img 预计算,避免 getter 在响应式里反复触发) */
-export function cloneFoodType(food) {
- return {
- id: food.id,
- key: food.key,
- icon: food.icon,
- name: food.name,
- gi: food.gi,
- giLabel: food.giLabel,
- val: food.val,
- color: food.color,
- img: food.img || getFoodImgUrl(food.icon)
- }
-}
diff --git a/TUICallKit-Vue3/tongji/data/gameLevels.js b/TUICallKit-Vue3/tongji/data/gameLevels.js
deleted file mode 100644
index 00f2f0fc..00000000
--- a/TUICallKit-Vue3/tongji/data/gameLevels.js
+++ /dev/null
@@ -1,60 +0,0 @@
-/**
- * 关卡配置:面向 45+ 的认知练习,节奏放慢、无失败惩罚。
- * 每组目标只比上一组略增,步数充裕(仅展示,不会因耗尽判负)。
- */
-/**
- * 指定关卡固定食材(按 key)。未配置的关卡走随机抽取逻辑。
- * 第一关固定为这 16 种带真实图片素材的食物。
- */
-const FIXED_LEVEL_FOODS = {
- 1: [
- 'sandwich',
- 'eightTreasureCongee',
- 'milkOats',
- 'milletCongee',
- 'shandongPancake',
- 'grainMantou',
- 'riceNoodleSoup',
- 'youtiao',
- 'friedNoodles',
- 'sweetPotato',
- 'centuryEggCongee',
- 'whiteRice',
- 'cake',
- 'biscuit',
- 'wonton',
- 'blackCoffee'
- ]
-}
-
-export function getLevelConfig(levelId) {
- const lv = Math.max(1, Math.min(999, Number(levelId) || 1))
- const tier = Math.floor((lv - 1) / 5)
-
- return {
- id: lv,
- // 步数充裕:仅作展示,认全目标即可通关
- moves: Math.max(40, 60 - tier * 2),
- // 认识目标平缓增长,避免给老人压力
- goalTargets: [6 + tier, 8 + tier],
- scoreTarget: 2000 + lv * 300,
- startGlucose: 50,
- // 固定食材(可选):存在时本关只用这些食物
- foods: FIXED_LEVEL_FOODS[lv] || null,
- boosters: {
- insulin: 3,
- fiber: 1,
- meal: 5
- }
- }
-}
-
-/** 根据本局表现计算 1~3 星:完成即得星,步数越省星越多(不依赖血糖) */
-export function calcLevelStars({ movesLeft, levelConfig }) {
- const totalMoves = levelConfig?.moves || 1
- const ratio = totalMoves > 0 ? movesLeft / totalMoves : 0
- let stars = 1
- if (ratio >= 0.3) stars = 2
- if (ratio >= 0.55) stars = 3
- return stars
-}
diff --git a/TUICallKit-Vue3/tongji/endless-game/README.md b/TUICallKit-Vue3/tongji/endless-game/README.md
index 1709868b..2f86a736 100644
--- a/TUICallKit-Vue3/tongji/endless-game/README.md
+++ b/TUICallKit-Vue3/tongji/endless-game/README.md
@@ -51,8 +51,7 @@ build: {
## 说明
-- 旧地址 `/tongji/pages/game` 和 `/tongji/pages/game-endless` 只是当前项目的兼容跳转,迁移时不需要复制。
-- 当前项目原来的“糖分突袭”源码保存在 `tongji/legacy-game/game.vue`,没有注册为页面,不参与打包。
+- 旧版“糖分突袭”页面及其独占依赖已经移除,项目只注册 `/tongji/endless-game/index` 这一款小游戏。
- 连消小目标按“连消×2”累计 2 次;连续 6 次普通消除没有连消时,下一次掉落会提供连消机会。“连消×3”保留为额外积分、驼乳粉与撒花惊喜,不阻挡主线任务。
- 横向 3 个与竖向 3 个相交形成 L/T 形五消时,会在交点生成带高对比 L 标记的范围爆破棋子;该棋子再次被消除时清除周围九格。
- 进入游戏时展示“本周 7 人同行榜”,包含真实平台昵称、前后名次、差距提示和可切换的同行/亲友鼓励;顶部“本周同行”可再次打开。后端部署方式见 `PLATFORM_INTEGRATION.md`。
diff --git a/TUICallKit-Vue3/tongji/endless-game/foodInlinePlugin.js b/TUICallKit-Vue3/tongji/endless-game/foodInlinePlugin.js
new file mode 100644
index 00000000..f4589e6d
--- /dev/null
+++ b/TUICallKit-Vue3/tongji/endless-game/foodInlinePlugin.js
@@ -0,0 +1,38 @@
+import { readdirSync, readFileSync } from 'node:fs'
+import { resolve } from 'node:path'
+
+const VIRTUAL_ID = 'virtual:tongji-endless-food-images'
+const RESOLVED_VIRTUAL_ID = `\0${VIRTUAL_ID}`
+
+/**
+ * 将游戏食物图内联到 tongji 分包代码。
+ * 微信代码质量会累计整个代码包内的图片/音频体积;源 JPG 继续保留,便于后续增补和迁移。
+ */
+export function endlessGameFoodInlinePlugin() {
+ return {
+ name: 'tongji-endless-game-food-inline',
+ enforce: 'pre',
+ resolveId(id) {
+ return id === VIRTUAL_ID ? RESOLVED_VIRTUAL_ID : null
+ },
+ load(id) {
+ if (id !== RESOLVED_VIRTUAL_ID) return null
+
+ const foodDir = resolve(process.cwd(), 'tongji/endless-game/assets/food')
+ const images = Object.fromEntries(
+ readdirSync(foodDir)
+ .filter((fileName) => fileName.toLowerCase().endsWith('.jpg'))
+ .sort()
+ .map((fileName) => {
+ const filePath = resolve(foodDir, fileName)
+ this.addWatchFile(filePath)
+ const key = fileName.replace(/\.jpg$/i, '')
+ const dataUrl = `data:image/jpeg;base64,${readFileSync(filePath).toString('base64')}`
+ return [key, dataUrl]
+ })
+ )
+
+ return `export default ${JSON.stringify(images)}`
+ },
+ }
+}
diff --git a/TUICallKit-Vue3/tongji/endless-game/index.vue b/TUICallKit-Vue3/tongji/endless-game/index.vue
index 4607b3a1..662af3e4 100644
--- a/TUICallKit-Vue3/tongji/endless-game/index.vue
+++ b/TUICallKit-Vue3/tongji/endless-game/index.vue
@@ -303,14 +303,7 @@ import TongjiIcon from './components/TongjiIcon.vue'
import { useGameSfx } from './composables/useGameSfx.js'
import { useGamePlatform } from './composables/useGamePlatform.js'
import { useGameAuth } from './composables/useGameAuth.js'
-
-const FOOD_IMAGE_MODULES = import.meta.glob('./assets/food/*.jpg', {
- eager: true,
- import: 'default'
-})
-const FOOD_IMAGES = Object.fromEntries(
- Object.entries(FOOD_IMAGE_MODULES).map(([path, url]) => [path.split('/').pop().replace(/\.jpg$/, ''), url])
-)
+import FOOD_IMAGES from 'virtual:tongji-endless-food-images'
const ROWS = 5
const COLS = 4
diff --git a/TUICallKit-Vue3/tongji/pages/game.vue b/TUICallKit-Vue3/tongji/pages/game.vue
deleted file mode 100644
index 783d9e4e..00000000
--- a/TUICallKit-Vue3/tongji/pages/game.vue
+++ /dev/null
@@ -1,2406 +0,0 @@
-
-
-
-
-
-
-
-
- {{ p.char }}
-
- {{ ft.text }}
-
-
- {{ comboBanner.text }}
-
-
-
-
-
-
- {{ foodCard.name }}
- {{ foodCard.giShort }}
-
- {{ foodCard.tip }}
-
-
-
-
-
-
-
-
-
- 高糖
- 红色标识 · 升糖快
-
- 已消除高糖食物
- {{ clearedHighFoodName }}
- 请记住:这类食物要少吃
-
-
-
-
-
-
-
- 高糖
- 已消除
-
- {{ resultTitle }}
-
- ★
-
- {{ resultMessage }}
-
-
- {{ row.label }}
-
- 高糖
- {{ row.value }}
-
-
-
-
- 认下一组
-
- 再认一遍
-
- 返回
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ lastClearedFood.name }}
-
- 含糖{{ lastClearedFood.giShort }}
-
-
- {{ lastClearedFood.tip }}
-
-
-
-
-
-
-
-
- 认糖:点击食物看含糖量 · 消除红色「高」即过关
-
-
-
-
-
-
-
-
-
-
- {{ hintTipText }}
-
-
-
-
- {{ hintArrowInfo.char }}
-
-
-
-
-
-
- {{ giChar(cell.tile.type.gi) }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/TUICallKit-Vue3/tongji/styles/game-stitch.scss b/TUICallKit-Vue3/tongji/styles/game-stitch.scss
deleted file mode 100644
index 6020ad01..00000000
--- a/TUICallKit-Vue3/tongji/styles/game-stitch.scss
+++ /dev/null
@@ -1,1568 +0,0 @@
-/**
- * game.vue — 血糖消和乐 v2 (stitch_sugar_balance_blast code.html)
- */
-
-.game-page {
- height: 100vh;
- height: 100dvh;
- /* 参考稿:顶部高光的薄荷渐变背景 */
- background: #e9f0e9;
- background-image: radial-gradient(circle at 50% -20%, #ffffff 0%, #e9f0e9 60%, #d5dcd5 100%);
- color: #171d19;
- overflow: hidden;
- position: relative;
- display: flex;
- flex-direction: column;
-}
-
-.game-page.is-screen-shake {
- animation: gg-screen-shake 0.5s cubic-bezier(0.36, 0.07, 0.19, 0.97) both;
-}
-
-@keyframes gg-screen-shake {
- 0%, 100% { transform: translate3d(0, 0, 0); }
- 10%, 30%, 50%, 70%, 90% { transform: translate3d(-8px, -8px, 0); }
- 20%, 40%, 60%, 80% { transform: translate3d(8px, 8px, 0); }
-}
-
-.game-page .gg-glass {
- background: rgba(255, 255, 255, 0.6);
- backdrop-filter: blur(20px);
- -webkit-backdrop-filter: blur(20px);
- border: 1px solid rgba(255, 255, 255, 0.8);
-}
-
-/* ===== Header / TopAppBar(参考稿玻璃顶栏) ===== */
-.game-page .gg-header {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- z-index: 50;
- background: rgba(255, 255, 255, 0.4);
- backdrop-filter: blur(24px);
- -webkit-backdrop-filter: blur(24px);
- border-bottom: 1px solid rgba(255, 255, 255, 0.6);
- box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.02);
-}
-
-.game-page .gg-hud-nav {
- display: flex;
- align-items: center;
- justify-content: space-between;
- position: relative;
- min-height: var(--menu-height, 64rpx);
- margin-bottom: 0;
-}
-
-.game-page .gg-hud-control-group {
- display: flex;
- align-items: center;
- gap: 16rpx;
-}
-
-/* 玻璃圆形图标按钮 */
-.game-page .gg-hud-btn {
- width: 84rpx;
- height: 84rpx;
- border-radius: 50%;
- background: rgba(255, 255, 255, 0.7);
- border: 1px solid rgba(255, 255, 255, 0.9);
- display: flex;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
- box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.04), inset 0 1px 2px rgba(255, 255, 255, 0.8);
- backdrop-filter: blur(8px);
- -webkit-backdrop-filter: blur(8px);
- transition: all 0.15s ease;
-}
-
-.game-page .gg-hud-btn--pressed {
- transform: scale(0.94);
- background: rgba(255, 255, 255, 0.95);
-}
-
-.game-page .gg-hud-btn.is-muted {
- opacity: 0.55;
-}
-
-.game-page .gg-hud-level-center {
- position: absolute;
- left: 50%;
- top: 50%;
- transform: translate(-50%, -50%);
- display: flex;
- flex-direction: column;
- align-items: center;
- z-index: 5;
-}
-
-.game-page .gg-hud-level-text {
- font-size: 44rpx;
- font-weight: 800;
- color: #006c4b;
- line-height: 1.1;
- letter-spacing: 2rpx;
- text-shadow: 0 2rpx 6rpx rgba(0, 108, 75, 0.12);
-}
-
-.game-page .gg-hud-score {
- display: flex;
- align-items: baseline;
- gap: 6rpx;
- margin-top: 2rpx;
-}
-
-.game-page .gg-hud-score-val {
- font-size: 68rpx;
- font-weight: 800;
- color: #006c4b;
- font-variant-numeric: tabular-nums;
- line-height: 1;
- letter-spacing: -2rpx;
- text-shadow: 0 2rpx 6rpx rgba(0, 108, 75, 0.12);
-}
-
-.game-page .gg-hud-score.is-pop .gg-hud-score-val {
- animation: gg-pop-score 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
-}
-
-.game-page .gg-hud-score-unit {
- font-size: 28rpx;
- font-weight: 700;
- color: #3d4a42;
-}
-
-/* ===== 信息横幅(参考稿玻璃卡 + 绿色 info 图标) ===== */
-.game-page .gg-info-banner {
- flex-shrink: 0;
- width: 100%;
- max-width: 750rpx;
- margin: 0 auto 20rpx;
- padding: 20rpx 24rpx;
- box-sizing: border-box;
- display: flex;
- align-items: center;
- gap: 20rpx;
- border-radius: 28rpx;
- background: rgba(255, 255, 255, 0.6);
- backdrop-filter: blur(20px);
- -webkit-backdrop-filter: blur(20px);
- border: 1px solid rgba(255, 255, 255, 0.8);
- box-shadow: 0 12rpx 32rpx rgba(0, 0, 0, 0.06), inset 0 1px 2px rgba(255, 255, 255, 0.9);
-}
-
-.game-page .gg-info-banner-icon {
- flex-shrink: 0;
- width: 80rpx;
- height: 80rpx;
- border-radius: 22rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- background: linear-gradient(135deg, #00a676, #006c4b);
- border: 1px solid rgba(255, 255, 255, 0.2);
- box-shadow: 0 8rpx 18rpx rgba(0, 108, 75, 0.28);
-}
-
-.game-page .gg-info-banner-body {
- flex: 1;
- min-width: 0;
-}
-
-.game-page .gg-info-banner-text {
- font-size: 28rpx;
- font-weight: 500;
- color: #171d19;
- line-height: 1.5;
-}
-
-.game-page .gg-info-banner-kw {
- font-size: 32rpx;
- font-weight: 800;
- color: #006c4b;
- letter-spacing: 1rpx;
-}
-
-/* —— 消除后:该食物的含糖 + 建议 —— */
-.game-page .gg-info-banner.is-food {
- gap: 18rpx;
- animation: gg-info-food-in 0.28s cubic-bezier(0.22, 1, 0.36, 1);
-}
-.game-page .gg-info-banner.is-food.is-low { border-color: rgba(22, 163, 74, 0.28); }
-.game-page .gg-info-banner.is-food.is-mid { border-color: rgba(234, 88, 12, 0.28); }
-.game-page .gg-info-banner.is-food.is-high { border-color: rgba(220, 38, 38, 0.3); }
-
-/* 食物图标:白底 + GI 彩色描边(覆盖默认绿色渐变) */
-.game-page .gg-info-banner-icon.is-food {
- background: #ffffff;
- box-shadow: 0 4rpx 12rpx rgba(15, 23, 42, 0.08), inset 0 1px 2px rgba(255, 255, 255, 0.9);
- border-width: 2rpx;
- border-style: solid;
-}
-.game-page .gg-info-banner-icon.is-food.is-low { border-color: rgba(22, 163, 74, 0.4); }
-.game-page .gg-info-banner-icon.is-food.is-mid { border-color: rgba(234, 88, 12, 0.4); }
-.game-page .gg-info-banner-icon.is-food.is-high { border-color: rgba(220, 38, 38, 0.45); }
-
-.game-page .gg-info-banner-icon.is-food .food-tile-icon--goal {
- width: 60rpx;
- height: 60rpx;
-}
-
-.game-page .gg-info-food-head {
- display: flex;
- align-items: center;
- gap: 14rpx;
- margin-bottom: 8rpx;
-}
-
-.game-page .gg-info-food-name {
- font-size: 34rpx;
- font-weight: 900;
- color: #171d19;
- line-height: 1.2;
- max-width: 320rpx;
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
-}
-
-.game-page .gg-info-food-gi {
- flex-shrink: 0;
- display: inline-flex;
- align-items: center;
- padding: 4rpx 16rpx;
- border-radius: 999rpx;
-}
-.game-page .gg-info-food-gi-text {
- font-size: 24rpx;
- font-weight: 800;
- color: #ffffff;
- letter-spacing: 1rpx;
-}
-.game-page .gg-info-food-gi.is-low { background: linear-gradient(135deg, #5bdda8, #00a676); box-shadow: 0 2rpx 8rpx rgba(0, 166, 118, 0.32); }
-.game-page .gg-info-food-gi.is-mid { background: linear-gradient(135deg, #fbbf24, #d97706); box-shadow: 0 2rpx 8rpx rgba(217, 119, 6, 0.32); }
-.game-page .gg-info-food-gi.is-high { background: linear-gradient(135deg, #f87171, #ba1a1a); box-shadow: 0 2rpx 8rpx rgba(186, 26, 26, 0.34); }
-
-.game-page .gg-info-food-tip {
- display: block;
- font-size: 26rpx;
- font-weight: 600;
- color: #3d4a42;
- line-height: 1.4;
-}
-.game-page .gg-info-food-tip.is-low { color: #15803d; }
-.game-page .gg-info-food-tip.is-mid { color: #c2410c; }
-.game-page .gg-info-food-tip.is-high { color: #b91c1c; }
-
-@keyframes gg-info-food-in {
- from { opacity: 0.35; transform: translateY(-6rpx); }
- to { opacity: 1; transform: translateY(0); }
-}
-
-/* ===== 次:上次消除(名称 + 含糖量),GI 三色编码学习卡 ===== */
-.game-page .gg-hud-last-food {
- flex-shrink: 0;
- width: 100%;
- max-width: 750rpx;
- margin: 0 auto 14rpx;
- padding: 16rpx 18rpx 18rpx;
- border-radius: 26rpx;
- background: rgba(255, 255, 255, 0.92);
- border: 1rpx solid rgba(0, 108, 73, 0.1);
- box-sizing: border-box;
- box-shadow: 0 6rpx 20rpx rgba(0, 108, 73, 0.07);
-}
-
-/* 头部:标题 + GI 状态胶囊 */
-.game-page .gg-hud-last-food-head {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 12rpx;
- margin-bottom: 12rpx;
-}
-
-.game-page .gg-hud-last-food-kicker {
- display: flex;
- align-items: center;
- font-size: 24rpx;
- font-weight: 800;
- color: #64748b;
- letter-spacing: 0.08em;
- line-height: 1.3;
- padding-left: 16rpx;
- position: relative;
-}
-
-.game-page .gg-hud-last-food-kicker::before {
- content: '';
- position: absolute;
- left: 0;
- top: 50%;
- transform: translateY(-50%);
- width: 6rpx;
- height: 24rpx;
- border-radius: 3rpx;
- background: #006c49;
-}
-
-/* 头部 GI 胶囊 */
-.game-page .gg-hud-last-food-chip {
- display: flex;
- align-items: center;
- gap: 8rpx;
- padding: 5rpx 16rpx 5rpx 12rpx;
- border-radius: 999rpx;
- font-weight: 800;
- line-height: 1;
- border: 1rpx solid transparent;
-}
-
-.game-page .gg-hud-last-food-chip-dot {
- width: 14rpx;
- height: 14rpx;
- border-radius: 50%;
- flex-shrink: 0;
-}
-
-.game-page .gg-hud-last-food-chip-text {
- font-size: 24rpx;
- font-weight: 800;
-}
-
-.game-page .gg-hud-last-food-chip.is-low {
- background: rgba(22, 163, 74, 0.12);
- border-color: rgba(22, 163, 74, 0.22);
-}
-.game-page .gg-hud-last-food-chip.is-low .gg-hud-last-food-chip-dot { background: #16a34a; }
-.game-page .gg-hud-last-food-chip.is-low .gg-hud-last-food-chip-text { color: #15803d; }
-
-.game-page .gg-hud-last-food-chip.is-mid {
- background: rgba(234, 88, 12, 0.12);
- border-color: rgba(234, 88, 12, 0.22);
-}
-.game-page .gg-hud-last-food-chip.is-mid .gg-hud-last-food-chip-dot { background: #ea580c; }
-.game-page .gg-hud-last-food-chip.is-mid .gg-hud-last-food-chip-text { color: #c2410c; }
-
-.game-page .gg-hud-last-food-chip.is-high {
- background: rgba(220, 38, 38, 0.12);
- border-color: rgba(220, 38, 38, 0.24);
-}
-.game-page .gg-hud-last-food-chip.is-high .gg-hud-last-food-chip-dot { background: #dc2626; }
-.game-page .gg-hud-last-food-chip.is-high .gg-hud-last-food-chip-text { color: #b91c1c; }
-
-/* 主卡:左侧 GI 强调条 + 图标 + 文案 */
-.game-page .gg-hud-last-food-card {
- position: relative;
- display: flex;
- align-items: center;
- gap: 18rpx;
- padding: 16rpx 18rpx 16rpx 24rpx;
- border-radius: 20rpx;
- overflow: hidden;
- background: #f8fafc;
- border: 1rpx solid rgba(15, 23, 42, 0.06);
- animation: gg-last-food-in 0.24s cubic-bezier(0.22, 1, 0.36, 1);
-}
-
-/* 左侧强调条(GI 三色) */
-.game-page .gg-hud-last-food-card::before {
- content: '';
- position: absolute;
- left: 0;
- top: 0;
- bottom: 0;
- width: 8rpx;
- border-radius: 8rpx;
- background: #cbd5e1;
-}
-
-@media (prefers-reduced-motion: reduce) {
- .game-page .gg-hud-last-food-card {
- animation: none;
- }
-}
-
-.game-page .gg-hud-last-food-card.is-high {
- background: linear-gradient(90deg, rgba(254, 226, 226, 0.85) 0%, rgba(254, 242, 242, 0.6) 60%);
- border-color: rgba(220, 38, 38, 0.18);
-}
-.game-page .gg-hud-last-food-card.is-high::before { background: #dc2626; }
-
-.game-page .gg-hud-last-food-card.is-mid {
- background: linear-gradient(90deg, rgba(255, 237, 213, 0.85) 0%, rgba(255, 251, 235, 0.6) 60%);
- border-color: rgba(234, 88, 12, 0.16);
-}
-.game-page .gg-hud-last-food-card.is-mid::before { background: #ea580c; }
-
-.game-page .gg-hud-last-food-card.is-low {
- background: linear-gradient(90deg, rgba(220, 252, 231, 0.85) 0%, rgba(240, 253, 244, 0.6) 60%);
- border-color: rgba(22, 163, 74, 0.16);
-}
-.game-page .gg-hud-last-food-card.is-low::before { background: #16a34a; }
-
-/* 图标:白底 + GI 彩色描边环 */
-.game-page .gg-hud-last-food-icon {
- width: 80rpx;
- height: 80rpx;
- flex-shrink: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- border-radius: 18rpx;
- background: #fff;
- border: 2rpx solid rgba(15, 23, 42, 0.06);
- box-shadow: 0 3rpx 10rpx rgba(15, 23, 42, 0.08);
-}
-
-.game-page .gg-hud-last-food-icon.is-low { border-color: rgba(22, 163, 74, 0.35); }
-.game-page .gg-hud-last-food-icon.is-mid { border-color: rgba(234, 88, 12, 0.35); }
-.game-page .gg-hud-last-food-icon.is-high { border-color: rgba(220, 38, 38, 0.4); }
-
-.game-page .gg-hud-last-food-icon .food-tile-icon--goal {
- width: 62rpx;
- height: 62rpx;
-}
-
-.game-page .gg-hud-last-food-body {
- flex: 1;
- min-width: 0;
- display: flex;
- flex-direction: column;
- gap: 10rpx;
-}
-
-.game-page .gg-hud-last-food-name {
- font-size: 36rpx;
- font-weight: 900;
- color: #0f172a;
- line-height: 1.2;
- word-break: break-all;
-}
-
-.game-page .gg-hud-last-food-sugar-row {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- gap: 8rpx 14rpx;
-}
-
-/* 含糖等级徽标:GI 三色实底 + 白字(保证对比度) */
-.game-page .gg-hud-last-food-gi {
- display: inline-flex;
- align-items: center;
- padding: 5rpx 16rpx;
- border-radius: 999rpx;
- line-height: 1.3;
-}
-
-.game-page .gg-hud-last-food-gi-text {
- font-size: 26rpx;
- font-weight: 800;
- color: #fff;
- letter-spacing: 0.02em;
-}
-
-.game-page .gg-hud-last-food-gi.is-low { background: #16a34a; box-shadow: 0 2rpx 8rpx rgba(22, 163, 74, 0.3); }
-.game-page .gg-hud-last-food-gi.is-mid { background: #ea580c; box-shadow: 0 2rpx 8rpx rgba(234, 88, 12, 0.3); }
-.game-page .gg-hud-last-food-gi.is-high { background: #dc2626; box-shadow: 0 2rpx 8rpx rgba(220, 38, 38, 0.32); }
-
-.game-page .gg-hud-last-food-sugar-hint {
- font-size: 26rpx;
- font-weight: 700;
- color: #475569;
- line-height: 1.35;
-}
-
-.game-page .gg-hud-last-food-card.is-high .gg-hud-last-food-sugar-hint { color: #b91c1c; }
-.game-page .gg-hud-last-food-card.is-mid .gg-hud-last-food-sugar-hint { color: #c2410c; }
-.game-page .gg-hud-last-food-card.is-low .gg-hud-last-food-sugar-hint { color: #15803d; }
-
-/* 空态占位 */
-.game-page .gg-hud-last-food-placeholder {
- display: flex;
- align-items: center;
- gap: 12rpx;
- padding: 18rpx 16rpx;
- border-radius: 20rpx;
- background: #f8fafc;
- border: 1rpx dashed rgba(15, 23, 42, 0.12);
-}
-
-.game-page .gg-hud-last-food-placeholder-dot {
- width: 16rpx;
- height: 16rpx;
- border-radius: 50%;
- flex-shrink: 0;
- background: #cbd5e1;
-}
-
-.game-page .gg-hud-last-food-placeholder text {
- font-size: 27rpx;
- font-weight: 600;
- color: #94a3b8;
- line-height: 1.45;
-}
-
-@keyframes gg-last-food-in {
- from {
- opacity: 0;
- transform: translateY(6rpx);
- }
- to {
- opacity: 1;
- transform: translateY(0);
- }
-}
-
-/* 血糖圆环(主区摘要用 mini) */
-.game-page .gg-glucose-ring {
- width: 88rpx;
- height: 88rpx;
- border-radius: 50%;
- padding: 6rpx;
- flex-shrink: 0;
- box-sizing: border-box;
- transition: all 0.3s ease;
-}
-
-.game-page .gg-glucose-ring-inner {
- width: 100%;
- height: 100%;
- border-radius: 50%;
- background: #fff;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- gap: 0;
- box-shadow: inset 0 0 0 1rpx rgba(0, 108, 73, 0.05), 0 2rpx 6rpx rgba(0, 108, 73, 0.04);
-}
-
-.game-page .gg-glucose-value {
- font-size: 30rpx;
- font-weight: 900;
- color: #0f172a;
- line-height: 1;
- font-variant-numeric: tabular-nums;
-}
-
-.game-page .gg-glucose-brief {
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- gap: 10rpx;
- min-width: 0;
-}
-
-.game-page .gg-glucose-title {
- font-size: 30rpx;
- font-weight: 800;
- color: #334155;
- line-height: 1.2;
-}
-
-.game-page .gg-glucose-status {
- font-size: 28rpx;
- font-weight: 800;
- color: #ea580c;
- padding: 6rpx 18rpx;
- border-radius: 999rpx;
- background: rgba(234, 88, 12, 0.1);
- white-space: nowrap;
- line-height: 1.2;
-}
-
-.game-page .gg-glucose-status.is-warn-low {
- color: #2563eb;
- background: rgba(59, 130, 246, 0.1);
-}
-
-.game-page .gg-glucose-status.is-warn-high {
- color: #dc2626;
- background: rgba(239, 68, 68, 0.1);
- animation: gg-alert-pulse 1.5s infinite;
-}
-
-.game-page .gg-glucose-ring--mini {
- width: 56rpx;
- height: 56rpx;
- padding: 4rpx;
-}
-
-.game-page .gg-glucose-ring--mini .gg-glucose-value {
- font-size: 22rpx;
-}
-
-.game-page .gg-glucose-status--mini {
- font-size: 22rpx;
- padding: 2rpx 10rpx;
-}
-
-@keyframes gg-alert-pulse {
- 0% { opacity: 0.8; }
- 50% { opacity: 1; box-shadow: 0 0 8rpx rgba(239, 68, 68, 0.3); }
- 100% { opacity: 0.8; }
-}
-
-@keyframes gg-pop-score {
- 0% { transform: scale(1); }
- 50% { transform: scale(1.12); color: #ea580c; }
- 100% { transform: scale(1); }
-}
-
-/* ===== 主:棋盘区 ===== */
-.game-page .gg-board-hero {
- flex: 1;
- width: 100%;
- min-height: 0;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
-}
-
-.game-page .gg-board-hint-inline {
- flex-shrink: 0;
- width: 100%;
- max-width: 750rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 10rpx;
- margin-bottom: 12rpx;
- padding: 0 8rpx;
- box-sizing: border-box;
-}
-
-.game-page .gg-hint-tag {
- font-size: 22rpx;
- font-weight: 800;
- color: #ffffff;
- background: #006c49;
- padding: 4rpx 12rpx;
- border-radius: 8rpx;
- flex-shrink: 0;
- line-height: 1.3;
-}
-
-.game-page .gg-board-hint-text {
- font-size: 26rpx;
- font-weight: 600;
- color: #475569;
- line-height: 1.35;
- text-align: center;
-}
-
-.game-page .gg-board-stack {
- display: flex;
- flex-direction: column;
- align-items: stretch;
- box-sizing: border-box;
-}
-
-.game-page .gg-board-zone {
- position: relative;
- width: 100%;
-}
-
-/* ===== Main / Board ===== */
-.game-page .gg-main {
- position: fixed;
- left: 0;
- right: 0;
- top: var(--board-area-top, 120px);
- bottom: var(--board-area-bottom, 180px);
- z-index: 10;
- width: 100%;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: flex-start;
- box-sizing: border-box;
- overflow: visible;
- padding: 14rpx 20rpx 0;
-}
-
-/* gg-hint-banner 已移除,提示直接显示在棋盘食物上 */
-
-/* 无可用移动时的小型提示条 */
-.game-page .gg-no-move-tip {
- display: flex;
- align-items: center;
- gap: 10rpx;
- padding: 12rpx 24rpx;
- margin-bottom: 12rpx;
- border-radius: 999rpx;
- background: rgba(234, 88, 12, 0.1);
- border: 1rpx solid rgba(234, 88, 12, 0.3);
- box-sizing: border-box;
-}
-
-.game-page .gg-no-move-tip-text {
- font-size: 28rpx;
- font-weight: 600;
- color: #c2410c;
-}
-
-.game-page .gg-board-wrap {
- /* 5 行 × 4 列棋盘宽高由 JS boardWrapStyle 注入 */
- flex-shrink: 0;
- position: relative;
- box-sizing: border-box;
-}
-
-.game-page .game-grid {
- display: flex;
- flex-direction: column;
- border-radius: 48rpx;
- background: rgba(255, 255, 255, 0.6);
- backdrop-filter: blur(20px);
- -webkit-backdrop-filter: blur(20px);
- box-shadow:
- 0 12rpx 32rpx rgba(0, 0, 0, 0.06),
- inset 0 1px 2px rgba(255, 255, 255, 0.9);
- border: 1px solid rgba(255, 255, 255, 0.8);
- box-sizing: border-box;
- overflow: visible;
- flex-shrink: 0;
-}
-
-.game-page .game-row {
- display: flex;
- flex-direction: row;
- flex-shrink: 0;
-}
-
-.game-page .game-cell {
- position: relative;
- flex-shrink: 0;
- overflow: visible;
- background: transparent;
- border-radius: 0;
- box-shadow: none;
-}
-
-.game-page .game-tile {
- position: absolute;
- inset: 0;
- width: 100%;
- height: 100%;
- border-radius: 28rpx;
- display: flex;
- align-items: stretch;
- justify-content: stretch;
- background: linear-gradient(135deg, rgba(255, 255, 255, 0.9), rgba(244, 251, 245, 0.6));
- box-shadow:
- 0 10rpx 20rpx rgba(0, 108, 75, 0.08),
- inset 0 2rpx 4rpx rgba(255, 255, 255, 0.8),
- inset 0 -2rpx 6rpx rgba(0, 0, 0, 0.02);
- border: 1px solid rgba(255, 255, 255, 0.9);
- transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.25s ease, opacity 0.2s;
- z-index: 1;
- box-sizing: border-box;
- overflow: visible;
-}
-
-.game-page .game-tile.is-dragging {
- z-index: 20;
- will-change: transform;
-}
-
-.game-page .game-tile.is-drag-neighbor {
- z-index: 15;
- will-change: transform;
-}
-
-.game-page .game-tile.is-dragging.game-tile--pressed {
- transform: none;
-}
-
-.game-page .game-tile.is-drop-start {
- opacity: 0;
- transform: translateY(-30%);
- transition: none;
-}
-
-.game-page .game-tile.is-drop-animate {
- opacity: 1;
- transform: translateY(0);
- transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.2s ease;
-}
-
-.game-page .game-tile.is-selected {
- transform: scale(1.08);
- z-index: 20;
- border: 2px solid #00a676;
- border-radius: 28rpx;
- box-shadow:
- 0 0 20rpx rgba(0, 166, 118, 0.35),
- inset 0 0 10rpx rgba(0, 166, 118, 0.12);
-}
-
-.game-page .game-tile--pressed {
- transform: scale(0.92);
-}
-
-.game-page .game-tile.is-selected.game-tile--pressed {
- transform: scale(1.06);
-}
-
-/* 3 连:糖果弹跳消除(先弹大再缩没,带高光) */
-.game-page .game-tile.is-matched.is-match-fx-3,
-.game-page .game-tile.is-matched:not(.is-match-fx-4):not(.is-match-fx-5) {
- animation: gg-candy-pop 0.42s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
- z-index: 6;
-}
-
-@keyframes gg-candy-pop {
- 0% { transform: scale(1); opacity: 1; filter: brightness(1); }
- 35% { transform: scale(1.35); opacity: 1; filter: brightness(1.7) saturate(1.4); }
- 60% { transform: scale(1.12); opacity: 0.9; }
- 100% { transform: scale(0); opacity: 0; filter: brightness(2); }
-}
-
-/* 4 连:弹性旋转 + 金色高光 */
-.game-page .game-tile.is-matched.is-match-fx-4 {
- animation: gg-candy-pop-4 0.6s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
- z-index: 7;
- box-shadow:
- 0 0 0 6rpx rgba(255, 213, 74, 0.6),
- 0 0 28px rgba(255, 200, 40, 0.95),
- 0 0 56px rgba(255, 170, 20, 0.5);
-}
-
-@keyframes gg-candy-pop-4 {
- 0% { transform: scale(1) rotate(0deg); opacity: 1; filter: brightness(1) saturate(1); }
- 25% { transform: scale(1.6) rotate(-10deg); opacity: 1; filter: brightness(1.9) saturate(1.8); }
- 50% { transform: scale(1.32) rotate(8deg); opacity: 0.95; filter: brightness(1.5) saturate(1.5); }
- 100% { transform: scale(0) rotate(20deg); opacity: 0; filter: brightness(2.4); }
-}
-
-/* 5 连+:彩虹爆闪 + 强光晕 */
-.game-page .game-tile.is-matched.is-match-fx-5 {
- animation: gg-candy-pop-5 0.72s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
- z-index: 8;
- box-shadow:
- 0 0 0 8rpx rgba(255, 255, 255, 0.7),
- 0 0 44px rgba(255, 215, 0, 1),
- 0 0 90px rgba(255, 90, 156, 0.6);
-}
-
-@keyframes gg-candy-pop-5 {
- 0% { transform: scale(1) rotate(0deg); opacity: 1; filter: brightness(1) saturate(1) hue-rotate(0deg); }
- 20% { transform: scale(2) rotate(-14deg); opacity: 1; filter: brightness(2.6) saturate(2.4) hue-rotate(30deg); }
- 45% { transform: scale(1.7) rotate(12deg); opacity: 0.9; filter: brightness(2) saturate(2) hue-rotate(-20deg); }
- 100% { transform: scale(0) rotate(26deg); opacity: 0; filter: brightness(3); }
-}
-
-/* ===== 提示高亮:方块增强发光 ===== */
-.game-page .game-tile.is-hint {
- border: 4rpx solid #10b981;
- z-index: 10;
- animation: gg-hint-glow 0.9s ease-in-out infinite alternate;
- box-shadow: 0 0 0 4rpx rgba(16, 185, 129, 0.3), 0 0 20rpx rgba(16, 185, 129, 0.4);
-}
-
-/* 提示标记角标 */
-.game-page .game-tile.is-hint::after {
- content: '✦';
- position: absolute;
- top: 4rpx;
- right: 6rpx;
- font-size: 18rpx;
- color: #006c49;
- line-height: 1;
- pointer-events: none;
- animation: gg-hint-badge-spin 1.8s linear infinite;
-}
-
-@keyframes gg-hint-badge-spin {
- from { transform: rotate(0deg) scale(1); opacity: 1; }
- 50% { transform: rotate(180deg) scale(1.3); opacity: 0.8; }
- to { transform: rotate(360deg) scale(1); opacity: 1; }
-}
-
-/* ===== 提示交换箭头(覆盖在两食物正中间) ===== */
-.game-page .gg-hint-arrow {
- position: absolute;
- width: 48rpx;
- height: 48rpx;
- background: linear-gradient(135deg, #006c49, #10b981);
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 26rpx;
- color: #fff;
- font-weight: 900;
- pointer-events: none;
- z-index: 30;
- transform: translate(-50%, -50%);
- box-shadow: 0 4rpx 16rpx rgba(0, 108, 73, 0.45);
- animation: gg-hint-arrow-pulse 0.7s ease-in-out infinite alternate;
-}
-
-@keyframes gg-hint-arrow-pulse {
- from {
- transform: translate(-50%, -50%) scale(1);
- box-shadow: 0 4rpx 16rpx rgba(0, 108, 73, 0.4);
- }
- to {
- transform: translate(-50%, -50%) scale(1.25);
- box-shadow: 0 4rpx 28rpx rgba(0, 108, 73, 0.7), 0 0 0 8rpx rgba(16, 185, 129, 0.15);
- }
-}
-
-/* 扩散发光光环(颜色/粗细/光晕由 JS 内联,弹性扩散) */
-.game-page .gg-match-ripple {
- position: absolute;
- border-radius: 50%;
- border-style: solid;
- border-color: rgba(255, 255, 255, 0.9);
- border-width: 4px;
- transform: translate(-50%, -50%) scale(0.2);
- animation: gg-ripple-burst 0.6s cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
- pointer-events: none;
- will-change: transform, opacity;
-}
-
-@keyframes gg-ripple-burst {
- 0% { transform: translate(-50%, -50%) scale(0.2); opacity: 0.95; }
- 55% { opacity: 0.55; }
- 100% { transform: translate(-50%, -50%) scale(2.9); opacity: 0; }
-}
-
-/* 糖果粒子:圆点用背景色,星屑用字形 + 发光 */
-.game-page .gg-particle {
- position: absolute;
- border-radius: 50%;
- pointer-events: none;
- display: flex;
- align-items: center;
- justify-content: center;
- line-height: 1;
- will-change: transform, opacity, left, top;
-}
-
-.game-page .gg-particle.is-star {
- border-radius: 0;
- background-color: transparent !important;
- text-shadow: 0 0 6px currentColor, 0 0 12px currentColor;
-}
-
-.game-page .gg-particle text {
- color: inherit;
- line-height: 1;
-}
-
-/* 大消除全屏闪光 */
-.game-page .gg-screen-flash {
- position: fixed;
- inset: 0;
- pointer-events: none;
- transition: opacity 0.28s ease-out;
-}
-
-@keyframes gg-pop-out {
- 0% { transform: scale(1); opacity: 1; }
- 50% { transform: scale(1.3); opacity: 0.8; }
- 100% { transform: scale(0); opacity: 0; }
-}
-
-@keyframes gg-hint-glow {
- from { transform: scale(1); filter: brightness(1.05); }
- to { transform: scale(1.1); filter: brightness(1.25) saturate(1.3); }
-}
-
-/* 棋盘内食材图标 — 撑满格子,避免 flex 居中导致小程序 image 贴顶 */
-.game-page .game-tile .food-tile-icon {
- pointer-events: none;
- z-index: 2;
- flex: 1;
- align-self: stretch;
- width: 100%;
- height: 100%;
- min-width: 0;
- min-height: 0;
-}
-
-.game-page .game-tile .food-tile-icon--tile .food-tile-icon__emoji {
- font-size: inherit;
-}
-
-.game-page .tile-icon {
- font-size: 48rpx;
- line-height: 1;
- pointer-events: none;
-}
-
-/* ===== 棋子含糖等级常驻角标(认知核心:一眼看出高/中/低糖) ===== */
-.game-page .gg-tile-gi {
- position: absolute;
- top: -12rpx;
- left: 50%;
- transform: translateX(-50%);
- z-index: 5;
- min-width: 40rpx;
- padding: 6rpx 16rpx;
- border-radius: 999rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 22rpx;
- font-weight: 800;
- color: #ffffff;
- line-height: 1;
- letter-spacing: 1rpx;
- pointer-events: none;
- border: 1px solid rgba(255, 255, 255, 0.6);
- text-shadow: 0 1rpx 2rpx rgba(0, 0, 0, 0.2);
- backdrop-filter: blur(8px);
- -webkit-backdrop-filter: blur(8px);
-}
-
-.game-page .gg-tile-gi.is-low {
- background: linear-gradient(135deg, rgba(91, 221, 168, 0.95), rgba(0, 166, 118, 0.95));
- box-shadow: 0 4rpx 12rpx rgba(0, 166, 118, 0.4), inset 0 2rpx 4rpx rgba(255, 255, 255, 0.4);
-}
-.game-page .gg-tile-gi.is-mid {
- background: linear-gradient(135deg, rgba(251, 191, 36, 0.95), rgba(217, 119, 6, 0.95));
- box-shadow: 0 4rpx 12rpx rgba(217, 119, 6, 0.4), inset 0 2rpx 4rpx rgba(255, 255, 255, 0.4);
-}
-.game-page .gg-tile-gi.is-high {
- background: linear-gradient(135deg, rgba(248, 113, 113, 0.95), rgba(186, 26, 26, 0.95));
- box-shadow: 0 4rpx 12rpx rgba(186, 26, 26, 0.4), inset 0 2rpx 4rpx rgba(255, 255, 255, 0.4);
-}
-
-/* ===== 点击食物的大字科普卡 ===== */
-.game-page .gg-food-card {
- position: fixed;
- left: 50%;
- top: 30%;
- transform: translateX(-50%);
- z-index: 150;
- width: 560rpx;
- max-width: 86vw;
- padding: 32rpx 36rpx;
- border-radius: 32rpx;
- background: #ffffff;
- border: 6rpx solid #16a34a;
- box-shadow: 0 20rpx 60rpx rgba(22, 29, 25, 0.22);
- pointer-events: none;
- animation: gg-food-card-in 0.28s cubic-bezier(0.34, 1.56, 0.64, 1);
-}
-
-.game-page .gg-food-card.is-mid { border-color: #ea580c; }
-.game-page .gg-food-card.is-high { border-color: #dc2626; }
-
-.game-page .gg-food-card-head {
- display: flex;
- align-items: center;
- gap: 20rpx;
- margin-bottom: 16rpx;
-}
-
-.game-page .gg-food-card-name {
- flex: 1;
- min-width: 0;
- font-size: 46rpx;
- font-weight: 900;
- color: #161d19;
- line-height: 1.2;
-}
-
-.game-page .gg-food-card-gi {
- flex-shrink: 0;
- font-size: 32rpx;
- font-weight: 900;
- color: #ffffff;
- padding: 8rpx 24rpx;
- border-radius: 999rpx;
- line-height: 1.2;
-}
-
-.game-page .gg-food-card-tip {
- display: block;
- font-size: 34rpx;
- font-weight: 600;
- color: #3c4a42;
- line-height: 1.5;
-}
-
-@keyframes gg-food-card-in {
- from { transform: translateX(-50%) scale(0.86); opacity: 0; }
- to { transform: translateX(-50%) scale(1); opacity: 1; }
-}
-
-/* ===== FX Layer ===== */
-.game-page .gg-fx-layer {
- position: fixed;
- inset: 0;
- pointer-events: none;
- z-index: 100;
-}
-
-.game-page .gg-floating-val {
- position: absolute;
- font-weight: 900;
- font-size: 44rpx;
- text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
- transform: translateX(-50%);
- animation: gg-float-up 1s ease-out forwards;
-}
-
-@keyframes gg-float-up {
- 0% { transform: translate(-50%, 0) scale(0.5); opacity: 0; }
- 20% { transform: translate(-50%, -20px) scale(1.2); opacity: 1; }
- 100% { transform: translate(-50%, -60px) scale(1); opacity: 0; }
-}
-
-/* ===== Footer boosters(辅,弱化) ===== */
-.game-page .gg-footer {
- position: fixed;
- left: 0;
- right: 0;
- bottom: 0;
- z-index: 40;
- border-radius: 64rpx 64rpx 0 0;
- border-top: 1px solid #ffffff;
- box-shadow: 0 -12rpx 40rpx rgba(0, 0, 0, 0.08);
- padding-top: 24rpx;
- padding-left: 24rpx;
- padding-right: 24rpx;
- background: rgba(255, 255, 255, 0.7);
- backdrop-filter: blur(28px);
- -webkit-backdrop-filter: blur(28px);
-}
-
-.game-page .gg-boosters {
- display: flex;
- justify-content: space-around;
- align-items: flex-start;
- max-width: 750rpx;
- margin: 0 auto;
-}
-
-/* 单个道具:图标卡 + 角标 + 文字 */
-.game-page .gg-booster-btn {
- display: flex;
- flex-direction: column;
- align-items: center;
- padding: 8rpx 0;
- width: 184rpx;
- background: transparent;
- border: none;
- transition: transform 0.2s ease;
-}
-
-.game-page .gg-booster-tile {
- position: relative;
- width: 112rpx;
- height: 112rpx;
- border-radius: 36rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- background: rgba(255, 255, 255, 0.85);
- border: 1px solid #ffffff;
- box-shadow: 0 8rpx 18rpx rgba(0, 0, 0, 0.08), inset 0 1px 2px rgba(255, 255, 255, 0.9);
- transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.2s ease;
-}
-
-.game-page .gg-booster-btn--pressed .gg-booster-tile {
- transform: scale(0.92);
- box-shadow: 0 4rpx 10rpx rgba(0, 0, 0, 0.06);
-}
-
-.game-page .gg-booster-btn.is-disabled {
- opacity: 0.4;
- filter: grayscale(100%);
- pointer-events: none !important;
-}
-
-.game-page .gg-booster-count {
- position: absolute;
- top: -12rpx;
- right: -12rpx;
- min-width: 50rpx;
- height: 50rpx;
- padding: 0 10rpx;
- border-radius: 26rpx;
- color: #fff;
- font-size: 28rpx;
- font-weight: 800;
- display: flex;
- align-items: center;
- justify-content: center;
- border: 3rpx solid #ffffff;
- box-shadow: 0 4rpx 10rpx rgba(0, 0, 0, 0.18);
- text-shadow: 0 1rpx 1rpx rgba(0, 0, 0, 0.2);
- z-index: 2;
-}
-
-.game-page .gg-booster-count.is-green { background: linear-gradient(135deg, #00a676, #006c4b); box-shadow: 0 4rpx 10rpx rgba(0, 166, 118, 0.3); }
-.game-page .gg-booster-count.is-orange { background: linear-gradient(135deg, #f59e0b, #d97706); box-shadow: 0 4rpx 10rpx rgba(217, 119, 6, 0.3); }
-.game-page .gg-booster-count.is-rose { background: linear-gradient(135deg, #ef4444, #ba1a1a); box-shadow: 0 4rpx 10rpx rgba(186, 26, 26, 0.3); }
-
-.game-page .gg-booster-icon {
- transition: transform 0.2s ease;
-}
-
-.game-page .gg-booster-label {
- margin-top: 14rpx;
- font-size: 30rpx;
- font-weight: 700;
- color: #3d4a42;
- letter-spacing: 1rpx;
-}
-
-/* ===== 高糖消除红色警示弹窗 ===== */
-.game-page .gg-high-alert-overlay {
- position: fixed;
- inset: 0;
- z-index: 210;
- background: rgba(127, 29, 29, 0.45);
- backdrop-filter: blur(16rpx);
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 48rpx;
- animation: gg-high-alert-fade-in 0.28s ease-out;
-}
-
-.game-page .gg-high-alert-card {
- width: 100%;
- max-width: 560rpx;
- padding: 48rpx 40rpx 44rpx;
- border-radius: 40rpx;
- background: linear-gradient(165deg, #fff5f5 0%, #ffffff 42%);
- border: 4rpx solid #dc2626;
- box-shadow:
- 0 0 0 8rpx rgba(220, 38, 38, 0.12),
- 0 24rpx 64rpx rgba(185, 28, 28, 0.35);
- display: flex;
- flex-direction: column;
- align-items: center;
- text-align: center;
- animation: gg-high-alert-pop 0.42s cubic-bezier(0.34, 1.45, 0.64, 1);
-}
-
-.game-page .gg-high-alert-icon-wrap {
- width: 96rpx;
- height: 96rpx;
- border-radius: 50%;
- background: linear-gradient(145deg, #ef4444, #b91c1c);
- display: flex;
- align-items: center;
- justify-content: center;
- margin-bottom: 24rpx;
- box-shadow: 0 12rpx 32rpx rgba(220, 38, 38, 0.45);
- animation: gg-high-badge-pulse 1.2s ease-in-out infinite;
-}
-
-.game-page .gg-high-alert-badge-row {
- display: flex;
- align-items: center;
- gap: 12rpx;
- margin-bottom: 16rpx;
- flex-wrap: wrap;
- justify-content: center;
-}
-
-.game-page .gg-high-alert-badge {
- font-size: 32rpx;
- font-weight: 900;
- color: #ffffff;
- background: #dc2626;
- padding: 8rpx 24rpx;
- border-radius: 12rpx;
- letter-spacing: 4rpx;
- box-shadow: 0 6rpx 16rpx rgba(220, 38, 38, 0.4);
-}
-
-.game-page .gg-high-alert-badge-sub {
- font-size: 24rpx;
- font-weight: 700;
- color: #b91c1c;
-}
-
-.game-page .gg-high-alert-title {
- font-size: 40rpx;
- font-weight: 900;
- color: #991b1b;
- line-height: 1.25;
- margin-bottom: 12rpx;
-}
-
-.game-page .gg-high-alert-name {
- font-size: 44rpx;
- font-weight: 900;
- color: #dc2626;
- line-height: 1.2;
- margin-bottom: 16rpx;
-}
-
-.game-page .gg-high-alert-tip {
- font-size: 30rpx;
- font-weight: 600;
- color: #7f1d1d;
- line-height: 1.45;
- opacity: 0.9;
-}
-
-@keyframes gg-high-alert-fade-in {
- from { opacity: 0; }
- to { opacity: 1; }
-}
-
-@keyframes gg-high-alert-pop {
- from {
- opacity: 0;
- transform: scale(0.82) translateY(24rpx);
- }
- to {
- opacity: 1;
- transform: scale(1) translateY(0);
- }
-}
-
-@keyframes gg-high-badge-pulse {
- 0%, 100% { transform: scale(1); box-shadow: 0 12rpx 32rpx rgba(220, 38, 38, 0.45); }
- 50% { transform: scale(1.06); box-shadow: 0 16rpx 40rpx rgba(220, 38, 38, 0.55); }
-}
-
-/* ===== Result overlay ===== */
-.game-page .gg-result-overlay {
- position: fixed;
- inset: 0;
- z-index: 200;
- background: rgba(22, 29, 25, 0.35);
- backdrop-filter: blur(20rpx);
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 48rpx;
-}
-
-.game-page .gg-result-card {
- width: 100%;
- max-width: 600rpx;
- border-radius: 48rpx;
- padding: 64rpx 48rpx;
- background: #ffffff;
- border: 1.5rpx solid rgba(255, 255, 255, 0.8);
- display: flex;
- flex-direction: column;
- align-items: center;
- text-align: center;
- box-shadow: 0 30rpx 80rpx rgba(22, 29, 25, 0.18);
-}
-
-.game-page .gg-result-card.is-high-clear {
- border: 4rpx solid #dc2626;
- background: linear-gradient(180deg, #fff5f5 0%, #ffffff 36%);
- box-shadow:
- 0 0 0 6rpx rgba(220, 38, 38, 0.1),
- 0 30rpx 80rpx rgba(185, 28, 28, 0.22);
-}
-
-.game-page .gg-result-high-head {
- display: flex;
- align-items: center;
- gap: 12rpx;
- margin-bottom: 20rpx;
-}
-
-.game-page .gg-result-high-badge {
- font-size: 30rpx;
- font-weight: 900;
- color: #fff;
- background: #dc2626;
- padding: 6rpx 20rpx;
- border-radius: 10rpx;
- letter-spacing: 3rpx;
- box-shadow: 0 4rpx 14rpx rgba(220, 38, 38, 0.35);
-}
-
-.game-page .gg-result-high-tag {
- font-size: 28rpx;
- font-weight: 800;
- color: #b91c1c;
- padding: 6rpx 16rpx;
- border-radius: 999rpx;
- background: rgba(254, 226, 226, 0.9);
- border: 2rpx solid rgba(220, 38, 38, 0.35);
-}
-
-.game-page .gg-result-card.is-high-clear .gg-result-title {
- color: #991b1b;
-}
-
-.game-page .gg-result-card.is-high-clear .gg-result-sub {
- color: #7f1d1d;
- font-weight: 600;
-}
-
-.game-page .gg-result-stats.is-high-clear {
- background: #fef2f2;
- border: 2rpx solid rgba(220, 38, 38, 0.2);
-}
-
-.game-page .gg-result-stat-val.is-high-val {
- color: #dc2626;
- display: flex;
- align-items: center;
- justify-content: flex-end;
- gap: 8rpx;
- flex-wrap: wrap;
-}
-
-.game-page .gg-result-inline-badge {
- font-size: 22rpx;
- font-weight: 900;
- color: #fff;
- background: #dc2626;
- padding: 2rpx 10rpx;
- border-radius: 6rpx;
- line-height: 1.3;
-}
-
-.game-page .gg-result-btn.is-high-primary {
- background: linear-gradient(135deg, #ef4444, #b91c1c);
- box-shadow: 0 10rpx 28rpx rgba(220, 38, 38, 0.35);
-}
-
-.game-page .gg-result-title {
- font-size: 44rpx;
- font-weight: 900;
- color: #161d19;
- margin-bottom: 24rpx;
-}
-
-.game-page .gg-result-sub {
- font-size: 34rpx;
- color: #2b3a32;
- margin-bottom: 24rpx;
- line-height: 1.6;
-}
-
-.game-page .gg-combo-banner {
- position: fixed;
- left: 50%;
- top: 38%;
- transform: translate(-50%, -50%) scale(0.6);
- z-index: 120;
- padding: 20rpx 48rpx;
- border-radius: 999rpx;
- background: linear-gradient(135deg, #fbbf24, #f59e0b);
- color: #fff;
- font-size: 40rpx;
- font-weight: 900;
- text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.2);
- box-shadow: 0 12rpx 40rpx rgba(245, 158, 11, 0.45);
- animation: gg-combo-pop 1.1s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
- pointer-events: none;
-}
-
-.game-page .gg-combo-banner.is-tier-2 {
- background: linear-gradient(135deg, #fb923c, #ea580c);
- font-size: 44rpx;
-}
-
-.game-page .gg-combo-banner.is-tier-3 {
- background: linear-gradient(135deg, #f472b6, #db2777);
- font-size: 48rpx;
- box-shadow: 0 16rpx 48rpx rgba(219, 39, 119, 0.5);
-}
-
-@keyframes gg-combo-pop {
- 0% { transform: translate(-50%, -50%) scale(0.4); opacity: 0; }
- 20% { transform: translate(-50%, -50%) scale(1.15); opacity: 1; }
- 70% { transform: translate(-50%, -50%) scale(1); opacity: 1; }
- 100% { transform: translate(-50%, -60%) scale(0.85); opacity: 0; }
-}
-
-.game-page .gg-result-stars {
- display: flex;
- gap: 16rpx;
- margin-bottom: 20rpx;
-}
-
-.game-page .gg-result-star {
- font-size: 56rpx;
- color: #e2e8f0;
- line-height: 1;
-}
-
-.game-page .gg-result-star.is-lit {
- color: #fbbf24;
- text-shadow: 0 4rpx 12rpx rgba(251, 191, 36, 0.5);
-}
-
-.game-page .gg-result-stats {
- width: 100%;
- margin-bottom: 32rpx;
- padding: 24rpx 32rpx;
- background: #f8faf9;
- border-radius: 24rpx;
-}
-
-.game-page .gg-result-stat {
- display: flex;
- justify-content: space-between;
- padding: 10rpx 0;
-}
-
-.game-page .gg-result-stat-label {
- font-size: 32rpx;
- color: #475569;
-}
-
-.game-page .gg-result-stat-val {
- font-size: 32rpx;
- font-weight: 800;
- color: #161d19;
-}
-
-.game-page .gg-result-actions {
- display: flex;
- flex-wrap: wrap;
- gap: 16rpx;
- width: 100%;
-}
-
-.game-page .gg-result-btn {
- flex: 1 1 40%;
- min-width: 180rpx;
- padding: 32rpx 0;
- border-radius: 999rpx;
- font-size: 36rpx;
- font-weight: 800;
- text-align: center;
- background: #eef6ee;
- color: #2b3a32;
- transition: all 0.2s;
-}
-
-.game-page .gg-result-btn.is-primary {
- background: linear-gradient(135deg, #10b981, #006c49);
- color: #fff;
- box-shadow: 0 10rpx 24rpx rgba(0, 108, 73, 0.2);
-}
-
-.game-page .gg-result-btn:active {
- transform: scale(0.97);
-}
-
-@media (prefers-reduced-motion: reduce) {
- .game-page.is-screen-shake,
- .game-page .game-tile.is-hint,
- .game-page .gg-food-card,
- .game-page .gg-combo-banner,
- .game-page .gg-floating-val {
- animation: none !important;
- }
-}
diff --git a/TUICallKit-Vue3/vite.config.ts b/TUICallKit-Vue3/vite.config.ts
index 99a36031..a958e2c2 100644
--- a/TUICallKit-Vue3/vite.config.ts
+++ b/TUICallKit-Vue3/vite.config.ts
@@ -1,11 +1,25 @@
import { defineConfig } from 'vite'
import uni from '@dcloudio/vite-plugin-uni'
+import Optimization from '@uni-ku/bundle-optimizer'
import { endlessGameAssetFileNames } from './tongji/endless-game/viteAssetOutput.js'
+import { endlessGameFoodInlinePlugin } from './tongji/endless-game/foodInlinePlugin.js'
// uni-app 工程根目录就是源码目录(HBuilderX 兼容)
// 编译产物默认输出到 ./dist//
export default defineConfig({
- plugins: [uni()],
+ plugins: [
+ endlessGameFoodInlinePlugin(),
+ uni(),
+ Optimization({
+ enable: {
+ optimization: true,
+ 'async-import': false,
+ 'async-component': false,
+ },
+ dts: false,
+ logger: false,
+ }),
+ ],
build: {
rollupOptions: {
output: {