feat(game): 新增酒桌小游戏功能并优化图标显示

- 添加了完整的酒桌小游戏页面,包含数字炸弹、猜拳大战、骰子大话和命运转盘四个游戏
- 实现了成就徽章组件的图标路径判断逻辑,支持图片和文字图标的动态切换
- 更新了饮酒分类常量中的白酒图标为酒杯样式,并修改成就图标为SVG路径
- 重构了动态卡片组件中的分类表情符号显示方式,支持SVG图标渲染
- 添加了游戏页面的路由配置和完整的交互功能实现
This commit is contained in:
cg
2026-07-20 23:25:55 +08:00
parent fabbb167cf
commit 49ef6eedb8
7 changed files with 1361 additions and 21 deletions
+2 -2
View File
@@ -2,7 +2,7 @@
// 酒类分类 // 酒类分类
export const DRINK_CATEGORIES = [ export const DRINK_CATEGORIES = [
{ id: 'baijiu', name: '白酒', emoji: '⚱️', icon: '/static/icons/baijiu.svg', defaultDegree: 52 }, { id: 'baijiu', name: '白酒', emoji: '🥃', icon: '/static/icons/baijiu.svg', defaultDegree: 52 },
{ id: 'beer', name: '啤酒', emoji: '🍺', defaultDegree: 5 }, { id: 'beer', name: '啤酒', emoji: '🍺', defaultDegree: 5 },
{ id: 'wine', name: '红酒', emoji: '🍷', defaultDegree: 13 }, { id: 'wine', name: '红酒', emoji: '🍷', defaultDegree: 13 },
{ id: 'whisky', name: '洋酒', emoji: '🥂', defaultDegree: 40 }, { id: 'whisky', name: '洋酒', emoji: '🥂', defaultDegree: 40 },
@@ -176,7 +176,7 @@ export const ACHIEVEMENTS = [
id: 'baijiu_master', id: 'baijiu_master',
name: '白酒达人', name: '白酒达人',
desc: '白酒累计打卡20次', desc: '白酒累计打卡20次',
icon: '⚱️', icon: '/static/icons/baijiu.svg',
condition: { type: 'category_records', category: 'baijiu', value: 20 } condition: { type: 'category_records', category: 'baijiu', value: 20 }
}, },
{ {
+11 -2
View File
@@ -5,7 +5,8 @@
@click="$emit('click')" @click="$emit('click')"
> >
<view class="badge-icon-wrap"> <view class="badge-icon-wrap">
<text class="badge-icon">{{ achievement.icon }}</text> <image v-if="isIconPath(achievement.icon)" :src="achievement.icon" class="badge-icon-img" mode="aspectFit"></image>
<text v-else class="badge-icon">{{ achievement.icon }}</text>
<view v-if="!achievement.unlocked" class="badge-lock"> <view v-if="!achievement.unlocked" class="badge-lock">
<text class="badge-lock-icon">🔒</text> <text class="badge-lock-icon">🔒</text>
</view> </view>
@@ -15,11 +16,14 @@
</template> </template>
<script> <script>
import { isIconPath } from '../common/utils'
export default { export default {
props: { props: {
achievement: { type: Object, required: true } achievement: { type: Object, required: true }
}, },
emits: ['click'] emits: ['click'],
methods: { isIconPath }
} }
</script> </script>
@@ -74,6 +78,11 @@ export default {
font-size: 48rpx; font-size: 48rpx;
} }
.badge-icon-img {
width: 52rpx;
height: 52rpx;
}
.badge-lock { .badge-lock {
position: absolute; position: absolute;
bottom: -4rpx; bottom: -4rpx;
+12 -5
View File
@@ -20,7 +20,8 @@
<!-- 饮酒标签 --> <!-- 饮酒标签 -->
<view class="feed-drinks" v-if="feed.drinks && feed.drinks.length"> <view class="feed-drinks" v-if="feed.drinks && feed.drinks.length">
<view class="feed-drink-tag" v-for="(d, i) in feed.drinks" :key="i"> <view class="feed-drink-tag" v-for="(d, i) in feed.drinks" :key="i">
<text class="feed-drink-emoji">{{ categoryEmoji(d.category) }}</text> <image v-if="isIconPath(getCatIcon(d.category))" :src="getCatIcon(d.category)" class="feed-drink-icon-img" mode="aspectFit"></image>
<text v-else class="feed-drink-emoji">{{ getCatIcon(d.category) }}</text>
<text class="feed-drink-name">{{ d.name }} {{ d.amount }}</text> <text class="feed-drink-name">{{ d.name }} {{ d.amount }}</text>
</view> </view>
</view> </view>
@@ -60,16 +61,16 @@
</template> </template>
<script> <script>
import { getCatIcon, isIconPath } from '../common/utils'
export default { export default {
name: 'FeedCard', name: 'FeedCard',
props: { props: {
feed: { type: Object, default: () => ({}) } feed: { type: Object, default: () => ({}) }
}, },
methods: { methods: {
categoryEmoji(cat) { getCatIcon,
const map = { baijiu: '🏺', beer: '🍺', wine: '🍷', whisky: '🥃', huangjiu: '🫖', sake: '🍶', fruit: '🍹', cocktail: '🍸' } isIconPath,
return map[cat] || '🍻'
},
feelingLabel(f) { feelingLabel(f) {
const map = { sober: '😌 清醒', buzzed: '😊 微醺', tipsy: '😄 小醉', drunk: '🥴 喝多了' } const map = { sober: '😌 清醒', buzzed: '😊 微醺', tipsy: '😄 小醉', drunk: '🥴 喝多了' }
return map[f] || '🍻 喝了点' return map[f] || '🍻 喝了点'
@@ -163,6 +164,12 @@ export default {
font-size: $fs-sm; font-size: $fs-sm;
} }
.feed-drink-icon-img {
width: 32rpx;
height: 32rpx;
flex-shrink: 0;
}
.feed-drink-name { .feed-drink-name {
font-size: $fs-xs; font-size: $fs-xs;
color: $text-secondary; color: $text-secondary;
+7
View File
@@ -90,6 +90,13 @@
"navigationStyle": "custom", "navigationStyle": "custom",
"navigationBarTitleText": "" "navigationBarTitleText": ""
} }
},
{
"path": "pages/games/games",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": ""
}
} }
], ],
"globalStyle": { "globalStyle": {
File diff suppressed because it is too large Load Diff
+4
View File
@@ -142,6 +142,10 @@ export default {
uni.showToast({ title: '请先同意用户协议和隐私政策', icon: 'none', duration: 2000 }) uni.showToast({ title: '请先同意用户协议和隐私政策', icon: 'none', duration: 2000 })
return return
} }
if (!this.nickname.trim()) {
uni.showToast({ title: '请先填写昵称', icon: 'none', duration: 2000 })
return
}
this.loginLoading = true this.loginLoading = true
try { try {
// 1. 获取微信登录 code // 1. 获取微信登录 code
+43 -12
View File
@@ -78,7 +78,8 @@
<view class="persona-glow"></view> <view class="persona-glow"></view>
<view class="persona-content"> <view class="persona-content">
<view class="persona-header"> <view class="persona-header">
<text class="persona-emoji">{{ personaEmoji }}</text> <image v-if="isIconPath(personaEmoji)" :src="personaEmoji" class="persona-icon-img" mode="aspectFit"></image>
<text v-else class="persona-emoji">{{ personaEmoji }}</text>
<view class="persona-text"> <view class="persona-text">
<text class="persona-title">{{ personaTitle }}</text> <text class="persona-title">{{ personaTitle }}</text>
<text class="persona-desc">{{ personaDesc }}</text> <text class="persona-desc">{{ personaDesc }}</text>
@@ -118,17 +119,17 @@
<!-- 操作入口 --> <!-- 操作入口 -->
<view class="actions-section"> <view class="actions-section">
<!-- 友圈 --> <!-- 桌小游戏 -->
<view class="action-card" @click="goCircle"> <view class="action-card" @click="goGames">
<view class="action-icon-wrap action-icon-circle"> <view class="action-icon-wrap action-icon-games">
<text class="action-icon">🍻</text> <text class="action-icon">🎲</text>
</view> </view>
<view class="action-text"> <view class="action-text">
<view class="action-title-row"> <view class="action-title-row">
<text class="action-title">友圈</text> <text class="action-title">桌小游戏</text>
<view class="action-new-tag">NEW</view> <view class="action-hot-tag">HOT</view>
</view> </view>
<text class="action-desc">看看酒友们今晚都在喝什么</text> <text class="action-desc">数字炸弹·猜拳·骰子·转盘今晚谁喝</text>
</view> </view>
<text class="action-arrow"></text> <text class="action-arrow"></text>
</view> </view>
@@ -148,7 +149,8 @@
<view v-if="showDetail" class="detail-overlay" @click="showDetail = false"> <view v-if="showDetail" class="detail-overlay" @click="showDetail = false">
<view class="detail-card" @click.stop> <view class="detail-card" @click.stop>
<view class="detail-icon-ring" :class="detailAchievement.unlocked ? 'ring-unlocked' : 'ring-locked'"> <view class="detail-icon-ring" :class="detailAchievement.unlocked ? 'ring-unlocked' : 'ring-locked'">
<text class="detail-emoji">{{ detailAchievement.icon }}</text> <image v-if="isIconPath(detailAchievement.icon)" :src="detailAchievement.icon" class="detail-icon-img" mode="aspectFit"></image>
<text v-else class="detail-emoji">{{ detailAchievement.icon }}</text>
</view> </view>
<text class="detail-name">{{ detailAchievement.name }}</text> <text class="detail-name">{{ detailAchievement.name }}</text>
<text class="detail-desc">{{ detailAchievement.desc }}</text> <text class="detail-desc">{{ detailAchievement.desc }}</text>
@@ -164,7 +166,7 @@
<script> <script>
import AchievementBadge from '../../components/AchievementBadge.vue' import AchievementBadge from '../../components/AchievementBadge.vue'
import client, { clearAuth } from '../../common/api' import client, { clearAuth } from '../../common/api'
import { checkAchievements } from '../../common/utils' import { checkAchievements, getCatIcon, isIconPath } from '../../common/utils'
import { ACHIEVEMENTS, DRINK_CATEGORIES } from '../../common/constants' import { ACHIEVEMENTS, DRINK_CATEGORIES } from '../../common/constants'
import themeMixin from '../../common/theme-mixin' import themeMixin from '../../common/theme-mixin'
@@ -217,8 +219,8 @@ export default {
}, },
personaEmoji() { personaEmoji() {
const fav = this.stats.favCategory const fav = this.stats.favCategory
const map = { baijiu: '⚱️', beer: '🍺', wine: '🍷', whisky: '🥂', huangjiu: '🫖', sake: '🍶', fruit: '🍹', cocktail: '🍸' } if (fav) return getCatIcon(fav)
return map[fav] || '🍻' return '🍻'
}, },
personaTitle() { personaTitle() {
const fav = this.stats.favCategory const fav = this.stats.favCategory
@@ -261,6 +263,7 @@ export default {
} }
}, },
methods: { methods: {
isIconPath,
// 构建分享名片的 query 参数(携带公开档案数据) // 构建分享名片的 query 参数(携带公开档案数据)
buildShareQuery() { buildShareQuery() {
const parts = [`nick=${encodeURIComponent(this.user.nickname || '酒友')}`] const parts = [`nick=${encodeURIComponent(this.user.nickname || '酒友')}`]
@@ -351,6 +354,9 @@ export default {
goCircle() { goCircle() {
uni.switchTab({ url: '/pages/circle/circle' }) uni.switchTab({ url: '/pages/circle/circle' })
}, },
goGames() {
uni.navigateTo({ url: '/pages/games/games' })
},
goHome() { goHome() {
uni.switchTab({ url: '/pages/index/index' }) uni.switchTab({ url: '/pages/index/index' })
}, },
@@ -541,6 +547,7 @@ export default {
background: rgba(232,168,56,0.10); background: rgba(232,168,56,0.10);
border: 1rpx solid rgba(232,168,56,0.20); border: 1rpx solid rgba(232,168,56,0.20);
border-radius: $radius-full; border-radius: $radius-full;
margin-left: 20rpx;
} }
.hero-level-text { .hero-level-text {
@@ -720,6 +727,12 @@ export default {
font-size: 56rpx; font-size: 56rpx;
} }
.persona-icon-img {
width: 64rpx;
height: 64rpx;
flex-shrink: 0;
}
.persona-text { .persona-text {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -838,6 +851,10 @@ export default {
background: rgba(139,133,184,0.12); background: rgba(139,133,184,0.12);
} }
.action-icon-games {
background: rgba(232,168,56,0.12);
}
.action-icon-timeline { .action-icon-timeline {
background: rgba(94,198,160,0.12); background: rgba(94,198,160,0.12);
} }
@@ -872,6 +889,15 @@ export default {
border-radius: $radius-full; border-radius: $radius-full;
} }
.action-hot-tag {
font-size: 18rpx;
font-weight: $fw-bold;
color: #fff;
background: linear-gradient(135deg, $coral, #e04848);
padding: 2rpx 10rpx;
border-radius: $radius-full;
}
.action-desc { .action-desc {
display: block; display: block;
font-size: $fs-xs; font-size: $fs-xs;
@@ -955,6 +981,11 @@ export default {
font-size: 64rpx; font-size: 64rpx;
} }
.detail-icon-img {
width: 72rpx;
height: 72rpx;
}
.detail-name { .detail-name {
display: block; display: block;
font-size: $fs-xl; font-size: $fs-xl;