基于HarmonyOS ArkTS API 24 Tab采用了瀑布流布局——通过filter方法将数据按奇偶索引分割为左右两列,每列独立渲染,实现了不等高的瀑布流视觉效果
引言

桌游作为一种经典的社交娱乐方式,近年来在中国市场迎来了显著的复苏与增长。从卡坦岛到狼人杀,从璀璨宝石到龙与地下城,桌游以其独特的面对面社交属性和深度的策略博弈体验,吸引了越来越多的年轻群体参与。在这一市场背景下,开发一款集桌游信息库、在线拼桌、积分排行、战队管理与社区交流于一体的移动应用,具有重要的社交价值与商业意义。多多桌游聚玩应用正是基于这一业务愿景构建的HarmonyOS原生应用,旨在为桌游爱好者提供从游戏发现到组队对战的全方位数字化服务。
在技术架构层面,本应用基于HarmonyOS ArkTS声明式UI开发框架构建,采用模块化组件设计模式。主入口组件DuoDuoBoardGameApp负责全局状态管理与Tab路由调度,六个业务Tab组件分别承载游戏库、拼桌、排行、战队、社区与个人中心的功能模块。应用通过@State装饰器管理响应式状态数据,@Builder装饰器构建独立的弹窗组件,bindContentCover方法绑定全屏覆盖弹窗的显示与隐藏。interface类型定义确保了数据类型的编译期安全,常量数组初始化提供了完整的离线业务数据。整个应用的组件间通信通过回调函数实现,子组件通过事件回调将用户操作传递给父组件,父组件再通过状态变更驱动UI响应式更新,形成了清晰的单向数据流。
在业务设计层面,多多桌游聚玩应用覆盖了桌游社交的六大核心场景。游戏库Tab以分类网格、本周热门横滑与双列游戏列表的多层次布局展示桌游信息。拼桌Tab整合了附近拼桌列表、报名进度条与详情查看功能,支持用户按游戏类型、时间与地点筛选拼桌活动。排行Tab通过柱状图、周冠军展示与玩家排名列表,构建了完整的竞技排名体系。战队Tab展示了战队列表、战绩统计与招募信息,支持玩家加入或创建战队。社区Tab构建了桌游攻略分享与桌友互动的内容生态。个人中心集成了玩家统计、成就徽章、订单管理与收藏列表等功能。应用视觉采用桌游紫(#4A148C)与骰子橙(#FF6F00)的双色调主题,底色为神秘桌游紫调(#F3E5F5),营造出兼具策略深度与社交趣味的氛围。
一、类型定义与数据模型架构

本应用通过ArkTS的interface机制定义了覆盖桌游全业务域的类型体系,包括桌游信息、游戏分类、拼桌活动、排名玩家、柱状图数据、战队信息、战队招募、社区帖子、话题、订单、收藏、成就徽章、玩家统计与游戏选择等十四种数据类型。
// 桌游信息类型定义
interface BoardGame79 {
id: number
name: string
players: string
duration: string
difficulty: number
rating: number
plays: number
tags: string[]
color: string
desc: string
category: string
}
// 游戏分类类型定义
interface GameCategory79 {
label: string
icon: string
color: string
bg: string
count: number
}
// 拼桌活动类型定义
interface PlayTable79 {
id: number
game: string
store: string
distance: string
date: string
time: string
currentPlayers: number
maxPlayers: number
host: string
level: string
status: string
color: string
fee: number
}
// 排名玩家类型定义
interface RankPlayer79 {
id: number
rank: number
name: string
level: string
score: number
games: number
winRate: number
avatarColor: string
badge: string
}
// 柱状图数据类型定义
interface BarData79 {
label: string
value: number
color: string
}
// 战队信息类型定义
interface TeamItem79 {
id: number
name: string
members: number
wins: number
losses: number
winRate: number
level: string
color: string
desc: string
captain: string
}
// 战队招募类型定义
interface TeamRecruit79 {
id: number
team: string
role: string
requirement: string
level: string
date: string
color: string
}
// 社区帖子类型定义
interface CommunityPost79 {
id: number
author: string
avatarColor: string
content: string
likes: number
comments: number
shares: number
images: number
timeAgo: string
topic: string
game: string
}
// 成就徽章类型定义
interface Achievement79 {
id: number
name: string
desc: string
icon: string
unlocked: boolean
color: string
}
// 玩家统计类型定义
interface PlayerStat79 {
totalGames: number
winRate: number
bestStreak: number
rankScore: number
favoriteGame: string
}
// 游戏选择类型定义
interface GameSelect79 {
id: number
name: string
players: string
color: string
}
类型定义体系的设计体现了对桌游社交业务场景的深入理解。BoardGame79包含了difficulty(难度)、players(人数范围)、duration(时长)与category(分类)四个桌游特有的属性字段,这些信息对桌游玩家的游戏选择具有关键决策价值。PlayTable79的currentPlayers和maxPlayers字段支持报名进度的精确计算与展示,status字段覆盖了"报名中"和"已满员"两种状态。RankPlayer79的badge字段存储排名前三的特殊徽章符号(👑🥈🥉),avatarColor字段驱动头像背景色的个性化渲染。Achievement79的unlocked布尔字段控制徽章的解锁状态与视觉表现,PlayerStat79作为单例对象存储当前玩家的综合统计数据。
二、静态数据初始化与业务内容构建

应用通过常量数组定义了丰富的静态业务数据,覆盖游戏分类、桌游列表、拼桌活动、排名玩家、排行柱状图、战队信息、战队招募、社区帖子、热门话题、订单、收藏、成就徽章、游戏选择与玩家统计等全部业务场景。
// 游戏分类数据
const GAME_CATS_79: GameCategory79[] = [
{ label: '策略', icon: '♟️', color: '#4A148C', bg: '#F3E5F5', count: 128 },
{ label: '派对', icon: '🎉', color: '#FF6F00', bg: '#FFF3E0', count: 96 },
{ label: '卡牌', icon: '🃏', color: '#1565C0', bg: '#E3F2FD', count: 85 },
{ label: '推理', icon: '🔍', color: '#00897B', bg: '#E0F2F1', count: 72 },
{ label: '角色扮演', icon: '🎭', color: '#C2185B', bg: '#FCE4EC', count: 64 },
{ label: '模拟', icon: '🏗️', color: '#6A1B9A', bg: '#F3E5F5', count: 58 },
{ label: '聚会', icon: '🍻', color: '#E65100', bg: '#FFF3E0', count: 45 },
{ label: '合作', icon: '🤝', color: '#2E7D32', bg: '#E8F5E9', count: 38 }
]
// 桌游列表数据
const BOARD_GAMES_79: BoardGame79[] = [
{ id: 1, name: '卡坦岛', players: '3-4人', duration: '60-90分', difficulty: 3, rating: 4.8, plays: 8542, tags: ['策略', '经典'], color: '#4A148C', desc: '资源管理+交易建设,入门策略首选', category: '策略' },
{ id: 2, name: '狼人杀', players: '8-18人', duration: '30-60分', difficulty: 2, rating: 4.7, plays: 12653, tags: ['派对', '推理'], color: '#FF6F00', desc: '身份推理+口才博弈,聚会必备', category: '派对' },
{ id: 3, name: '璀璨宝石', players: '2-4人', duration: '30-45分', difficulty: 2, rating: 4.6, plays: 6234, tags: ['策略', '入门'], color: '#1565C0', desc: '宝石收集+引擎构筑,轻策略之王', category: '策略' },
{ id: 4, name: '阿瓦隆', players: '5-10人', duration: '30-60分', difficulty: 2, rating: 4.8, plays: 9856, tags: ['推理', '阵营'], color: '#00897B', desc: '隐藏身份+任务推理,比狼人杀更深', category: '推理' },
{ id: 5, name: '大富翁', players: '2-6人', duration: '60-120分', difficulty: 1, rating: 4.5, plays: 15423, tags: ['经典', '模拟'], color: '#C2185B', desc: '买卖地产+收租致富,童年回忆', category: '模拟' },
{ id: 6, name: '万智牌', players: '2人', duration: '30-60分', difficulty: 4, rating: 4.9, plays: 4321, tags: ['卡牌', '竞技'], color: '#6A1B9A', desc: '全球首款TCG,深度策略卡牌', category: '卡牌' },
{ id: 7, name: '龙与地下城', players: '3-6人', duration: '120-240分', difficulty: 4, rating: 4.9, plays: 2876, tags: ['RPG', '高难度'], color: '#2E7D32', desc: '经典TRPG,角色扮演冒险', category: '角色扮演' }
]
// 拼桌活动数据
const PLAY_TABLES_79: PlayTable79[] = [
{ id: 1, game: '卡坦岛', store: '骰子工坊·朝阳店', distance: '0.8km', date: '今天', time: '19:00', currentPlayers: 3, maxPlayers: 4, host: '桌游达人', level: '进阶', status: '报名中', color: '#4A148C', fee: 30 },
{ id: 2, game: '狼人杀', store: '狼人酒馆·海淀店', distance: '1.2km', date: '今天', time: '20:00', currentPlayers: 8, maxPlayers: 12, host: '法官小明', level: '入门', status: '报名中', color: '#FF6F00', fee: 35 },
{ id: 3, game: '阿瓦隆', store: '桌游星球·西城店', distance: '2.1km', date: '明天', time: '14:00', currentPlayers: 5, maxPlayers: 10, host: '亚瑟王', level: '进阶', status: '报名中', color: '#00897B', fee: 30 },
{ id: 4, game: '龙与地下城', store: '冒险者公会·东城店', distance: '3.5km', date: '周六', time: '13:00', currentPlayers: 4, maxPlayers: 6, host: 'DM老王', level: '高级', status: '报名中', color: '#2E7D32', fee: 80 },
{ id: 7, game: '密室逃脱桌游', store: '迷踪密室·丰台店', distance: '4.2km', date: '周六', time: '18:00', currentPlayers: 6, maxPlayers: 6, host: '密室设计者', level: '进阶', status: '已满员', color: '#E65100', fee: 50 }
]
// 排名玩家数据
const RANK_PLAYERS_79: RankPlayer79[] = [
{ id: 1, rank: 1, name: '棋王降临', level: '大师III', score: 9850, games: 1256, winRate: 78, avatarColor: '#FFD700', badge: '👑' },
{ id: 2, rank: 2, name: '策略之神', level: '大师II', score: 9420, games: 1156, winRate: 75, avatarColor: '#4A148C', badge: '🥈' },
{ id: 3, rank: '骰子猎人', level: '大师I', score: 9180, games: 1098, winRate: 73, avatarColor: '#FF6F00', badge: '🥉' },
{ id: 4, rank: 4, name: '卡牌大师', level: '钻石III', score: 8750, games: 987, winRate: 70, avatarColor: '#1565C0', badge: '' },
{ id: 5, rank: 5, name: '推理专家', level: '钻石II', score: 8320, games: 876, winRate: 68, avatarColor: '#00897B', badge: '' }
]
// 排行柱状图数据
const BAR_DATA_RANK_79: BarData79[] = [
{ label: '1周', value: 850, color: '#CE93D8' },
{ label: '2周', value: 920, color: '#CE93D8' },
{ label: '3周', value: 780, color: '#CE93D8' },
{ label: '4周', value: 1050, color: '#CE93D8' },
{ label: '5周', value: 1180, color: '#CE93D8' },
{ label: '6周', value: 1320, color: '#4A148C' },
{ label: '7周', value: 1450, color: '#4A148C' },
{ label: '8周', value: 1380, color: '#4A148C' }
]
// 战队信息数据
const TEAMS_79: TeamItem79[] = [
{ id: 1, name: '紫金骰子队', members: 12, wins: 156, losses: 48, winRate: 76, level: '冠军联赛', color: '#4A148C', desc: '策略桌游强队,连续3届联赛冠军', captain: '棋王降临' },
{ id: 2, name: '橙色火焰', members: 10, wins: 128, losses: 52, winRate: 71, level: '超级联赛', color: '#FF6F00', desc: '派对桌游专精,气氛组担当', captain: '派对之王' },
{ id: 3, name: '蓝色风暴', members: 8, wins: 98, losses: 62, winRate: 61, level: '甲级联赛', color: '#1565C0', desc: '卡牌竞技战队,万智牌强队', captain: '卡牌大师' },
{ id: 4, name: '绿色军团', members: 15, wins: 142, losses: 58, winRate: 71, level: '超级联赛', color: '#00897B', desc: '推理桌游战队,阿瓦隆王者', captain: '推理专家' }
]
// 战队招募数据
const TEAM_RECRUITS_79: TeamRecruit79[] = [
{ id: 1, team: '紫金骰子队', role: '策略主力', requirement: '钻石以上+策略类专精', level: '高级', date: '08-24', color: '#4A148C' },
{ id: 2, team: '橙色火焰', role: '气氛担当', requirement: '热爱派对桌游+活跃度高', level: '不限', date: '08-23', color: '#FF6F00' },
{ id: 3, team: '蓝色风暴', role: '卡牌选手', requirement: '万智牌/游戏王经验', level: '中级', date: '08-22', color: '#1565C0' },
{ id: 4, team: '绿色军团', role: '推理高手', requirement: '阿瓦隆/狼人杀胜率60%+', level: '中级', date: '08-21', color: '#00897B' }
]
// 成就徽章数据
const ACHIEVEMENTS_79: Achievement79[] = [
{ id: 1, name: '百战不殆', desc: '完成100场对局', icon: '🎮', unlocked: true, color: '#4A148C' },
{ id: 2, name: '常胜将军', desc: '连胜10场', icon: '🏆', unlocked: true, color: '#FFD700' },
{ id: 3, name: '策略大师', desc: '策略类胜率70%+', icon: '♟️', unlocked: true, color: '#1565C0' },
{ id: 4, name: '派对达人', desc: '参加50场派对局', icon: '🎉', unlocked: true, color: '#FF6F00' },
{ id: 5, name: '推理专家', desc: '推理类胜率65%+', icon: '🔍', unlocked: false, color: '#00897B' },
{ id: 6, name: '跑团勇士', desc: '完成10次DND冒险', icon: '⚔️', unlocked: false, color: '#2E7D32' }
]
// 玩家统计数据
const PLAYER_STAT_79: PlayerStat79 = {
totalGames: 356,
winRate: 68,
bestStreak: 12,
rankScore: 7320,
favoriteGame: '卡坦岛'
}
静态数据的设计充分体现了桌游行业的业务特点。BOARD_GAMES_79中每款桌游都标注了difficulty难度值(1-4)、players人数范围、duration时长与category分类四个维度,这些信息构成了桌游选择的核心决策依据。PLAY_TABLES_79的currentPlayers与maxPlayers比值直接驱动了报名进度条的渲染,status字段控制"已满员"状态的按钮禁用。RANK_PLAYERS_79的badge字段为前三名玩家赋予了特殊符号标识,这种视觉差异化增强了排名的荣誉感。BAR_DATA_RANK_79的色彩区分——前5周使用浅紫色#CE93D8,后3周使用深紫色#4A148C——通过颜色深浅变化传达了积分增长趋势。
三、主入口组件与Tab路由架构

主入口组件DuoDuoBoardGameApp是整个应用的状态中枢,管理着Tab切换、弹窗显示状态、选中项ID与表单输入等全局状态数据。
@Entry
@Component
struct DuoDuoBoardGameApp {
@State currentTab: number = 0
@State showBookDialog: boolean = false
@State showCancelDialog: boolean = false
@State showEditPlayerDialog: boolean = false
@State showDeleteFavDialog: boolean = false
@State selectedTableId: number = 0
@State selectedGameId: number = 1
@State tableCount: number = 1
@State bookTime: string = ''
@State bookNotes: string = ''
@State editPlayerName: string = ''
@State editPlayerLevel: string = ''
@State editPlayerGames: string = ''
@State editPlayerBio: string = ''
@State cancelTargetId: number = 0
private tabs: string[] = ['游戏库', '拼桌', '排行', '战队', '社区', '我的']
private timeSlots: string[] = ['14:00', '16:00', '18:00', '19:00', '20:00', '21:00']
build() {
Column() {
// 顶部头部区域
Column() {
Row() {
Column() {
Text('多多桌游')
.fontSize(22)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Bold)
Text('骰子一掷·好友相聚')
.fontSize(11)
.fontColor('rgba(255,255,255,0.8)')
.margin({ top: 2 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
Row() {
Text('🎲').fontSize(18).fontColor('#FFFFFF')
}
.width(36).height(36).justifyContent(FlexAlign.Center)
.backgroundColor('rgba(255,255,255,0.2)')
.borderRadius(18)
}
.width('100%').height(56).padding({ left: 16, right: 16 })
.alignItems(VerticalAlign.Center)
Row() {
Text('搜桌游、拼桌、战队...').fontSize(13).fontColor('rgba(255,255,255,0.6)').layoutWeight(1)
Text('🔍').fontSize(16).fontColor('rgba(255,255,255,0.6)')
}
.width('100%').height(36).margin({ top: 4 })
.padding({ left: 16, right: 16 })
.backgroundColor('rgba(255,255,255,0.15)')
.borderRadius(20)
.alignItems(VerticalAlign.Center)
}
.width('100%')
.linearGradient({ angle: 135, colors: [['#4A148C', 0], ['#311B92', 1]] })
.padding({ top: 8, bottom: 12, left: 16, right: 16 })
// Tab内容区
Stack({ alignContent: Alignment.TopStart }) {
Column() {
if (this.currentTab === 0) {
GameLibTab79({
onBook: (id: number) => {
this.selectedTableId = id
this.showBookDialog = true
}
})
}
if (this.currentTab === 1) {
PlayTableTab79({
onBook: (id: number) => {
this.selectedTableId = id
this.showBookDialog = true
}
})
}
if (this.currentTab === 2) {
RankTab79()
}
if (this.currentTab === 3) {
TeamTab79()
}
if (this.currentTab === 4) {
CommunityTab79()
}
if (this.currentTab === 5) {
ProfileTab79({
onEditPlayer: () => {
this.showEditPlayerDialog = true
}, onCancelOrder: (id: number) => {
this.cancelTargetId = id
this.showCancelDialog = true
}, onDeleteFav: (id: number) => {
this.cancelTargetId = id
this.showDeleteFavDialog = true
}
})
}
}
.width('100%').height('100%')
}
.layoutWeight(1)
.width('100%')
// 底部Tab栏
Row() {
ForEach(this.tabs, (tab: string, idx: number) => {
Column() {
Text(this.getTabIcon(idx))
.fontSize(20)
.fontColor(this.currentTab === idx ? '#4A148C' : '#999999')
Text(tab)
.fontSize(10)
.fontColor(this.currentTab === idx ? '#4A148C' : '#999999')
.margin({ top: 2 })
}
.layoutWeight(1)
.height(56)
.justifyContent(FlexAlign.Center)
.onClick(() => {
this.currentTab = idx
})
}, (tab: string) => tab)
}
.width('100%')
.height(56)
.backgroundColor('#FFFFFF')
.border({ width: 1, color: '#E0E0E0' })
}
.width('100%').height('100%')
.backgroundColor('#F3E5F5')
.bindContentCover(this.showCancelDialog, this.buildCancelCover())
.bindContentCover(this.showDeleteFavDialog, this.buildDeleteFavCover())
}
private getTabIcon(idx: number): string {
const icons: string[] = ['🎲', '🪑', '🏆', '🛡️', '💬', '👤']
return idx < icons.length ? icons[idx] : '📋'
}
}
主入口组件的头部区域采用从#4A148C到#311B92的135度线性渐变,营造出深紫色的桌游品牌视觉基调。头部布局将"多多桌游"品牌名称与"骰子一掷·好友相聚"宣传语以Column嵌套方式垂直排列,右侧的骰子emoji图标按钮与品牌色形成呼应。搜索栏的placeholder"搜桌游、拼桌、战队…"通过列举核心搜索对象引导用户的搜索行为。Tab内容区的Stack容器使用了layoutWeight(1)占据剩余空间,确保底部Tab栏始终固定在屏幕底部。
Tab路由机制通过条件渲染if语句实现六个业务Tab的按需加载。与宠物和文创应用不同的是,桌游应用的Tab名称更具社交娱乐特色——游戏库、拼桌、排行、战队、社区、我的。底部Tab栏的激活色为#4A148C桌游紫色,每个Tab项的图标与文字在激活与未激活状态间的颜色切换,通过currentTab === idx的条件判断实现。bindContentCover绑定了取消预约弹窗与删除收藏弹窗,确保这两个确认操作的全屏覆盖效果。
四、拼桌预约弹窗与预约流程

拼桌预约弹窗是本应用的核心交互组件,集成了游戏选择、桌数调节、时间选择、备注填写与费用计算等完整的预约流程。
@Builder
buildBookSheet() {
Column() {
Row() {
Text('拼桌预约').fontSize(18).fontWeight(FontWeight.Bold).fontColor('#333333')
Text('').layoutWeight(1)
Text('✕').fontSize(20).fontColor('#999999').onClick(() => { this.showBookDialog = false })
}
.width('100%').padding(16)
Scroll() {
Column() {
// 选择游戏
Text('选择游戏').fontSize(14).fontWeight(FontWeight.Bold).fontColor('#333333')
Scroll() {
Row() {
ForEach(GAME_SELECTS_79, (game: GameSelect79) => {
Column() {
Text(game.name).fontSize(12).fontColor(this.selectedGameId === game.id ? '#FFFFFF' : '#666666')
Text(game.players).fontSize(9).fontColor(this.selectedGameId === game.id ? 'rgba(255,255,255,0.8)' : '#AAAAAA').margin({ top: 2 })
}
.padding(10).margin({ right: 8 })
.borderRadius(10)
.backgroundColor(this.selectedGameId === game.id ? game.color : '#F5F5F5')
.onClick(() => { this.selectedGameId = game.id })
}, (game: GameSelect79) => game.id.toString())
}
}
.scrollable(ScrollDirection.Horizontal)
.width('100%').margin({ top: 8 })
// 桌数
Row() {
Text('预约桌数').fontSize(14).fontColor('#333333')
Text('').layoutWeight(1)
Row() {
Button() { Text('-').fontSize(16).fontColor('#666666') }
.width(32).height(32).backgroundColor('#F5F5F5').borderRadius(16)
.onClick(() => { if (this.tableCount > 1) { this.tableCount-- } })
Text(this.tableCount.toString()).fontSize(14).fontColor('#333333').width(40).textAlign(TextAlign.Center)
Button() { Text('+').fontSize(16).fontColor('#666666') }
.width(32).height(32).backgroundColor('#F5F5F5').borderRadius(16)
.onClick(() => { this.tableCount++ })
}
}
.width('100%').margin({ top: 16 })
// 时间
Text('选择时间').fontSize(14).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ top: 16 })
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(this.timeSlots, (slot: string, idx: number) => {
Text(slot)
.fontSize(12).fontColor(this.bookTime === slot ? '#FFFFFF' : '#666666')
.padding({ left: 16, right: 16, top: 8, bottom: 8 })
.margin({ right: 8, top: 8 })
.borderRadius(20)
.backgroundColor(this.bookTime === slot ? '#4A148C' : '#F5F5F5')
.onClick(() => { this.bookTime = slot })
}, (slot: string, idx: number) => idx.toString())
}
.width('100%').margin({ top: 8 })
// 备注
Text('备注').fontSize(14).fontColor('#333333').margin({ top: 16 })
TextArea({ text: this.bookNotes, placeholder: '请输入特殊需求,如新手教学、规则讲解等...' })
.width('100%').height(70).margin({ top: 8 })
.borderRadius(10).backgroundColor('#F5F5F5')
.onChange((val: string) => { this.bookNotes = val })
// 费用
Row() {
Text('预约费用').fontSize(13).fontColor('#666666')
Text('').layoutWeight(1)
Text('¥' + (30 * this.tableCount)).fontSize(20).fontColor('#FF6F00').fontWeight(FontWeight.Bold)
}
.width('100%').margin({ top: 20, bottom: 12 })
}
.width('100%').padding({ left: 16, right: 16, bottom: 16 })
}
.constraintSize({ maxHeight: '58%' })
Row() {
Button() {
Text('确认预约').fontSize(16).fontColor('#FFFFFF').fontWeight(FontWeight.Medium)
}
.layoutWeight(1).height(48)
.backgroundColor('#4A148C')
.borderRadius(24)
.onClick(() => { this.showBookDialog = false })
}
.width('100%').padding(16)
}
.width('100%')
}
拼桌预约弹窗的设计体现了社交娱乐场景下的预约交互特点。游戏选择区域采用横向滚动的Scroll容器,每个游戏选项以卡片形式展示游戏名称与人数范围,选中状态使用游戏自身的颜色作为背景。这种横向滚动设计在游戏选项较多时仍能保持良好的交互体验。桌数调节器采用减号、数字、加号的三段式布局,减号按钮在tableCount为1时阻止进一步减少。
时间选择区域使用Flex容器配合FlexWrap.Wrap实现自动换行布局,六个时间段以胶囊形按钮的形式展示。每个时间段在选中时背景色变为桌游紫色#4A148C,未选中时为浅灰色#F5F5F5。费用计算逻辑非常直接——单桌费用30元乘以桌数tableCount,这种简单的定价模型适合桌游拼桌的低单价场景。备注输入框的placeholder"请输入特殊需求,如新手教学、规则讲解等…"提供了具体的输入引导,帮助用户明确备注的用途。Scroll组件的maxHeight设为58%,确保弹窗内容超出屏幕时可滚动且底部确认按钮始终可见。
五、游戏库Tab与分类导航

游戏库Tab是应用的首页内容,通过分类网格、本周热门横滑与双列游戏列表的多层次布局,为用户提供了丰富的桌游信息发现体验。
@Component
struct GameLibTab79 {
onBook: (id: number) => void = () => {}
build() {
Scroll() {
Column() {
// 分类网格
Text('游戏分类').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ left: 16, top: 12 })
Grid() {
ForEach(GAME_CATS_79, (cat: GameCategory79) => {
GridItem() {
Column() {
Text(cat.icon).fontSize(26)
Text(cat.label).fontSize(11).fontColor('#333333').margin({ top: 4 })
Text(cat.count + '款').fontSize(9).fontColor('#AAAAAA')
}
.width('100%').padding({ top: 10, bottom: 10 })
.backgroundColor(cat.bg)
.borderRadius(12)
.alignItems(HorizontalAlign.Center)
}
}, (cat: GameCategory79) => cat.label)
}
.columnsTemplate('4fr 4fr 4fr 4fr')
.rowsGap(8).columnsGap(8)
.padding(16)
.height(210)
// 本周热门横滑
Row() {
Text('本周热门').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#333333')
Text('').layoutWeight(1)
Text('更多 >').fontSize(12).fontColor('#4A148C')
}
.width('100%').padding({ left: 16, right: 16 })
Scroll() {
Row() {
ForEach(BOARD_GAMES_79, (game: BoardGame79) => {
Column() {
Column() {
Text('🎲').fontSize(32)
}
.width(160).height(70)
.backgroundColor(game.color)
.borderRadius({ topLeft: 12, topRight: 12 })
.justifyContent(FlexAlign.Center)
Column() {
Text(game.name).fontSize(13).fontColor('#333333').fontWeight(FontWeight.Bold)
Text(game.players + ' · ' + game.duration).fontSize(9).fontColor('#AAAAAA').margin({ top: 2 })
Row() {
Text('★').fontSize(10).fontColor('#FFB300')
Text(game.rating.toString()).fontSize(10).fontColor('#FFB300').margin({ left: 2 })
Text(game.plays + '场').fontSize(9).fontColor('#AAAAAA').margin({ left: 8 })
}
.margin({ top: 4 })
Row() {
ForEach(game.tags, (tag: string) => {
Text(tag).fontSize(9).fontColor(game.color).backgroundColor(game.color + '15').padding({ left: 4, right: 4, top: 1, bottom: 1 }).borderRadius(4).margin({ right: 4 })
}, (tag: string) => tag)
}
.margin({ top: 4 })
Button() {
Text('预约').fontSize(11).fontColor('#FFFFFF')
}
.width('100%').height(26).margin({ top: 6 })
.backgroundColor(game.color)
.borderRadius(13)
.onClick(() => { this.onBook(game.id) })
}
.padding(8)
}
.width(160).margin({ right: 12 })
.backgroundColor('#FFFFFF')
.borderRadius(12)
.border({ width: 1, color: '#F0F0F0' })
}, (game: BoardGame79) => game.id.toString())
}
.padding({ left: 16, right: 16 })
}
.scrollable(ScrollDirection.Horizontal)
.width('100%').margin({ top: 8 })
// 双列游戏列表
Text('全部游戏').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ left: 16, top: 16 })
Grid() {
ForEach(BOARD_GAMES_79, (game: BoardGame79) => {
GridItem() {
Column() {
Column() {
Text('🎲').fontSize(28)
}
.width('100%').height(64)
.backgroundColor(game.color + '20')
.borderRadius({ topLeft: 12, topRight: 12 })
.justifyContent(FlexAlign.Center)
Column() {
Text(game.name).fontSize(12).fontColor('#333333').fontWeight(FontWeight.Bold).maxLines(1)
Text(game.category).fontSize(9).fontColor('#AAAAAA').margin({ top: 2 })
Text(game.desc).fontSize(9).fontColor('#999999').margin({ top: 4 }).maxLines(2)
Row() {
Text('难度').fontSize(9).fontColor('#AAAAAA')
ForEach([0, 1, 2, 3, 4], (idx: number) => {
Text(idx < game.difficulty ? '●' : '○').fontSize(8).fontColor(idx < game.difficulty ? game.color : '#E0E0E0').margin({ left: 2 })
}, (idx: number) => idx.toString())
}
.margin({ top: 4 })
Row() {
Text('★').fontSize(10).fontColor('#FFB300')
Text(game.rating.toString()).fontSize(10).fontColor('#FFB300').margin({ left: 2 })
Text(game.plays + '场').fontSize(9).fontColor('#AAAAAA').margin({ left: 8 })
Text('').layoutWeight(1)
Text(game.players).fontSize(9).fontColor('#AAAAAA')
}
.margin({ top: 4 })
Button() {
Text('拼桌').fontSize(11).fontColor('#FFFFFF')
}
.width('100%').height(26).margin({ top: 6 })
.backgroundColor(game.color)
.borderRadius(13)
.onClick(() => { this.onBook(game.id) })
}
.padding(8)
}
.backgroundColor('#FFFFFF')
.borderRadius(12)
.border({ width: 1, color: '#F0F0F0' })
}
}, (game: BoardGame79) => game.id.toString())
}
.columnsTemplate('1fr 1fr')
.rowsGap(12).columnsGap(12)
.padding(16)
}
.width('100%')
}
.scrollable(ScrollDirection.Vertical)
.width('100%').height('100%')
.backgroundColor('#F3E5F5')
}
}
分类网格区域采用columnsTemplate(‘4fr 4fr 4fr 4fr’)的四列等分布局,八个分类(策略、派对、卡牌、推理、角色扮演、模拟、聚会、合作)各自携带独立的颜色与背景色配置。每个分类卡片使用分类自身的bg背景色配合emoji图标,形成了直观的分类导航入口。本周热门横滑区域以固定宽度160的卡片展示热门桌游,每个卡片包含图片占位区、游戏名称、人数时长、评分销量、标签集合与预约按钮六个信息层级。
双列游戏列表是游戏库Tab的核心内容区域,每个游戏卡片的信息展示密度较高。难度指示器是本区域的视觉创新点——通过ForEach遍历[0, 1, 2, 3, 4]数组,根据idx < game.difficulty的条件判断渲染实心圆●或空心圆○,实心圆使用游戏颜色,空心圆使用灰色#E0E0E0。这种五级难度可视化方式直观地传达了桌游的复杂程度。图片占位区使用游戏颜色加’20’透明度作为背景,与横滑区域的纯色背景形成层次区分。
六、拼桌Tab与报名进度条

拼桌Tab是本应用最具特色的模块之一,通过附近拼桌列表与报名进度条的可视化展示,为用户提供了直观的拼桌决策信息。
@Component
struct PlayTableTab79 {
onBook: (id: number) => void = () => {}
build() {
Scroll() {
Column() {
Text('附近拼桌').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ left: 16, top: 12 })
Column() {
ForEach(PLAY_TABLES_79, (table: PlayTable79) => {
Column() {
Row() {
Column() {
Text('🎲').fontSize(24)
}
.width(48).height(48).borderRadius(12)
.backgroundColor(table.color + '20')
.justifyContent(FlexAlign.Center)
Column() {
Row() {
Text(table.game).fontSize(14).fontColor('#333333').fontWeight(FontWeight.Bold)
Text(table.level).fontSize(9).fontColor(table.color).backgroundColor(table.color + '15').padding({ left: 4, right: 4, top: 1, bottom: 1 }).borderRadius(4).margin({ left: 8 })
}
Text(table.store + ' · ' + table.distance).fontSize(10).fontColor('#AAAAAA').margin({ top: 2 })
Row() {
Text('📅').fontSize(10)
Text(table.date + ' ' + table.time).fontSize(10).fontColor('#666666').margin({ left: 2 })
Text('💰').fontSize(10).margin({ left: 12 })
Text('¥' + table.fee).fontSize(10).fontColor('#FF6F00').margin({ left: 2 })
}
.margin({ top: 4 })
Text('主持:' + table.host).fontSize(10).fontColor('#AAAAAA').margin({ top: 2 })
}
.margin({ left: 10 })
.layoutWeight(1)
}
.width('100%')
// 拼桌进度
Column() {
Row() {
Text('已报名').fontSize(10).fontColor('#666666')
Text(table.currentPlayers + '/' + table.maxPlayers + '人').fontSize(10).fontColor(table.color).fontWeight(FontWeight.Medium).margin({ left: 4 })
Text('').layoutWeight(1)
Text(table.status).fontSize(10).fontColor(table.status === '报名中' ? '#4CAF50' : '#FF5252')
}
.width('100%')
Row() {
Column() {
}
.layoutWeight(table.currentPlayers).height(6)
.backgroundColor(table.color)
.borderRadius({ topLeft: 3, bottomLeft: 3 })
Column() {
}
.layoutWeight(table.maxPlayers - table.currentPlayers).height(6)
.backgroundColor('#E0E0E0')
.borderRadius({ topRight: 3, bottomRight: 3 })
}
.width('100%').margin({ top: 4 })
}
.width('100%').margin({ top: 8 })
Row() {
Button() {
Text(table.status === '已满员' ? '已满' : '立即报名').fontSize(12).fontColor('#FFFFFF')
}
.height(32)
.backgroundColor(table.status === '已满员' ? '#BDBDBD' : table.color)
.borderRadius(16)
.onClick(() => { if (table.status !== '已满员') { this.onBook(table.id) } })
Button() {
Text('详情').fontSize(12).fontColor(table.color)
}
.height(32).margin({ left: 8 })
.backgroundColor(table.color + '15')
.borderRadius(16)
}
.margin({ top: 8 })
}
.width('100%').padding(12).margin({ top: 8, left: 16, right: 16 })
.backgroundColor('#FFFFFF')
.borderRadius(12)
.border({ width: 1, color: '#F0F0F0' })
}, (table: PlayTable79) => table.id.toString())
}
.width('100%')
}
.width('100%')
}
.scrollable(ScrollDirection.Vertical)
.width('100%').height('100%')
.backgroundColor('#F3E5F5')
}
}
拼桌进度条是本Tab的核心视觉创新。它通过两个Column组件的layoutWeight比例分配实现了报名进度的可视化——已报名部分使用table.currentPlayers作为layoutWeight值并填充桌游颜色,剩余部分使用table.maxPlayers - table.currentPlayers作为layoutWeight值并填充灰色#E0E0E0。两个部分的borderRadius分别设置了左侧和右侧的圆角,形成了一个完整的进度条效果。这种基于layoutWeight的进度条实现方式简洁高效,无需额外的Canvas或Progress组件即可实现可视化效果。
拼桌卡片的信息展示层次清晰。顶部为游戏图标、游戏名称、难度标签、门店距离、日期时间、费用与主持人信息;中部为报名进度条与状态文字;底部为操作按钮。状态文字根据status值动态切换颜色——"报名中"显示绿色#4CAF50,"已满员"显示红色#FF5252。操作按钮也根据status值动态切换——已满员时按钮文字为"已满"且背景为灰色#BDBDBD,点击事件被条件判断阻止;报名中时按钮文字为"立即报名"且背景为桌游颜色,点击触发onBook回调。
七、排行Tab与柱状图可视化
排行Tab通过柱状图、周冠军展示与玩家排名列表三大板块,构建了完整的竞技排名展示体系。
@Component
struct RankTab79 {
build() {
Scroll() {
Column() {
Text('积分排行').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ left: 16, top: 12 })
// 柱状图
Text('近8周积分趋势').fontSize(14).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ left: 16, top: 12 })
Column() {
Row() {
ForEach(BAR_DATA_RANK_79, (bar: BarData79) => {
Column() {
Text(bar.value.toString()).fontSize(8).fontColor('#999999')
Column() {
}
.width(18).height(bar.value / 15)
.backgroundColor(bar.color)
.borderRadius({ topLeft: 3, topRight: 3 })
Text(bar.label).fontSize(8).fontColor('#999999').margin({ top: 4 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
}, (bar: BarData79) => bar.label)
}
.width('100%').height(120)
.padding({ top: 8, bottom: 8 })
.alignItems(VerticalAlign.Bottom)
}
.width('100%').margin({ top: 8, left: 16, right: 16 })
.backgroundColor('#FFFFFF')
.borderRadius(12)
.padding(12)
// 周冠军展示
Row() {
Column() {
Text('👑').fontSize(40)
}
.width(64).height(64).borderRadius(32)
.backgroundColor('#FFD700')
.justifyContent(FlexAlign.Center)
Column() {
Text('本周冠军').fontSize(12).fontColor('#AAAAAA')
Text('棋王降临').fontSize(16).fontColor('#333333').fontWeight(FontWeight.Bold).margin({ top: 2 })
Text('积分 9850 · 胜率 78%').fontSize(11).fontColor('#FF6F00').margin({ top: 2 })
}
.margin({ left: 12 })
.layoutWeight(1)
Text('🏆').fontSize(32)
}
.width('100%').padding(16).margin({ top: 12, left: 16, right: 16 })
.linearGradient({ angle: 90, colors: [['#FFF8E1', 0], ['#FFFDE7', 1]] })
.borderRadius(16)
.border({ width: 2, color: '#FFD700' })
// 玩家排名列表
Text('玩家排行').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ left: 16, top: 16 })
Column() {
ForEach(RANK_PLAYERS_79, (player: RankPlayer79) => {
Row() {
Row() {
if (player.badge.length > 0) {
Text(player.badge).fontSize(18)
} else {
Text(player.rank.toString()).fontSize(16).fontColor('#999999').fontWeight(FontWeight.Bold)
}
}
.width(36).height(36).justifyContent(FlexAlign.Center)
Column() {
Text('🎮').fontSize(20)
}
.width(40).height(40).borderRadius(20)
.backgroundColor(player.avatarColor + '20')
.justifyContent(FlexAlign.Center)
Column() {
Text(player.name).fontSize(13).fontColor('#333333').fontWeight(FontWeight.Medium)
Text(player.level).fontSize(10).fontColor(player.avatarColor).margin({ top: 2 })
}
.margin({ left: 8 })
.layoutWeight(1)
Column() {
Text(player.score.toString()).fontSize(14).fontColor('#FF6F00').fontWeight(FontWeight.Bold)
Text('胜率' + player.winRate + '%').fontSize(9).fontColor('#AAAAAA').margin({ top: 2 })
}
.alignItems(HorizontalAlign.End)
}
.width('100%').padding(10).margin({ top: 6, left: 16, right: 16 })
.backgroundColor('#FFFFFF')
.borderRadius(10)
.border({ width: 1, color: '#F0F0F0' })
}, (player: RankPlayer79) => player.id.toString())
}
.width('100%')
}
.width('100%')
}
.scrollable(ScrollDirection.Vertical)
.width('100%').height('100%')
.backgroundColor('#F3E5F5')
}
}
柱状图区域通过ForEach遍历BAR_DATA_RANK_79数组渲染八周的积分趋势。每个柱子的height属性设置为bar.value / 15,将积分值等比例映射为像素高度。柱子宽度固定为18,背景色根据数据中的color字段区分——前5周使用浅紫色#CE93D8,后3周使用深紫色#4A148C。Row容器的alignItems设置为VerticalAlign.Bottom确保所有柱子底部对齐,形成标准的柱状图视觉效果。
周冠军展示区域是排行Tab的视觉焦点。它采用90度水平线性渐变(从#FFF8E1到#FFFDE7)营造金色光晕效果,配合2像素宽的金色#FFD700边框,营造出冠军荣誉感。左侧的皇冠emoji头像使用64x64的圆形金色背景,右侧的奖杯emoji作为装饰元素。玩家排名列表中,前三名玩家通过badge字段显示特殊徽章符号(👑🥈🥉),第四名及以后的玩家显示纯数字排名,这种视觉差异化增强了排名的层次感。
八、战队Tab与招募信息
战队Tab展示了战队列表、战绩统计与招募信息,为桌游玩家提供了战队发现与加入的渠道。
@Component
struct TeamTab79 {
build() {
Scroll() {
Column() {
Text('桌游战队').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ left: 16, top: 12 })
// 战队列表
Column() {
ForEach(TEAMS_79, (team: TeamItem79) => {
Column() {
Row() {
Column() {
Text('🛡️').fontSize(28)
}
.width(48).height(48).borderRadius(12)
.backgroundColor(team.color + '20')
.justifyContent(FlexAlign.Center)
Column() {
Text(team.name).fontSize(14).fontColor('#333333').fontWeight(FontWeight.Bold)
Text(team.level + ' · ' + team.members + '人').fontSize(10).fontColor('#AAAAAA').margin({ top: 2 })
Text('队长:' + team.captain).fontSize(10).fontColor('#AAAAAA').margin({ top: 2 })
}
.margin({ left: 10 })
.layoutWeight(1)
Column() {
Text(team.winRate + '%').fontSize(16).fontColor(team.color).fontWeight(FontWeight.Bold)
Text('胜率').fontSize(9).fontColor('#AAAAAA')
}
.alignItems(HorizontalAlign.End)
}
.width('100%')
Text(team.desc).fontSize(11).fontColor('#999999').margin({ top: 8 })
Row() {
Text('胜').fontSize(10).fontColor('#4CAF50')
Text(team.wins.toString()).fontSize(10).fontColor('#4CAF50').margin({ left: 2 })
Text('负').fontSize(10).fontColor('#FF5252').margin({ left: 12 })
Text(team.losses.toString()).fontSize(10).fontColor('#FF5252').margin({ left: 2 })
Text('总场').fontSize(10).fontColor('#AAAAAA').margin({ left: 12 })
Text((team.wins + team.losses).toString()).fontSize(10).fontColor('#AAAAAA').margin({ left: 2 })
Text('').layoutWeight(1)
Button() { Text('查看').fontSize(11).fontColor(team.color) }
.height(26).backgroundColor(team.color + '15').borderRadius(13)
}
.width('100%').margin({ top: 8 })
}
.width('100%').padding(12).margin({ top: 8, left: 16, right: 16 })
.backgroundColor('#FFFFFF')
.borderRadius(12)
.border({ width: 1, color: '#F0F0F0' })
}, (team: TeamItem79) => team.id.toString())
}
.width('100%')
// 战队招募
Text('战队招募').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ left: 16, top: 16 })
Column() {
ForEach(TEAM_RECRUITS_79, (recruit: TeamRecruit79) => {
Row() {
Column() {
Text('📢').fontSize(20)
}
.width(36).height(36).borderRadius(18)
.backgroundColor(recruit.color + '20')
.justifyContent(FlexAlign.Center)
Column() {
Row() {
Text(recruit.team).fontSize(12).fontColor('#333333').fontWeight(FontWeight.Medium)
Text(recruit.role).fontSize(10).fontColor(recruit.color).backgroundColor(recruit.color + '15').padding({ left: 4, right: 4, top: 1, bottom: 1 }).borderRadius(4).margin({ left: 8 })
}
Text(recruit.requirement).fontSize(10).fontColor('#AAAAAA').margin({ top: 4 })
Row() {
Text('等级要求:' + recruit.level).fontSize(9).fontColor('#AAAAAA')
Text('发布:' + recruit.date).fontSize(9).fontColor('#AAAAAA').margin({ left: 12 })
Text('').layoutWeight(1)
Button() { Text('申请').fontSize(10).fontColor('#FFFFFF') }
.height(24).backgroundColor(recruit.color).borderRadius(12)
}
.width('100%').margin({ top: 4 })
}
.margin({ left: 8 })
.layoutWeight(1)
}
.width('100%').padding(10).margin({ top: 6, left: 16, right: 16 })
.backgroundColor('#FFFFFF')
.borderRadius(10)
.border({ width: 1, color: '#F0F0F0' })
}, (recruit: TeamRecruit79) => recruit.id.toString())
}
.width('100%')
}
.width('100%')
}
.scrollable(ScrollDirection.Vertical)
.width('100%').height('100%')
.backgroundColor('#F3E5F5')
}
}
战队列表的卡片设计信息层次丰富。顶部为战队图标、名称、联赛等级、成员数量与队长信息,配合右侧的胜率百分比展示。战绩统计行采用三组色彩编码的数据展示——胜场使用绿色#4CAF50、负场使用红色#FF5252、总场次使用灰色#AAAAAA,这种色彩编码使战绩数据一目了然。战队描述文字以灰色小号字体展示,为用户提供了战队的定性介绍。查看按钮使用战队颜色加’15’透明度作为背景,形成描边按钮的视觉效果。
战队招募区域的设计体现了社交招募场景的信息展示需求。每条招募信息包含战队名称、角色标签、要求描述、等级要求与发布日期。角色标签使用招募颜色加’15’透明度背景,与战队列表中的难度标签风格保持一致。申请按钮使用招募颜色的实心背景,视觉层级高于查看按钮,引导用户执行申请操作。招募信息中的requirement字段提供了具体的招募条件,如"钻石以上+策略类专精"或"万智牌/游戏王经验",这些详细的要求有助于提高招募匹配的精准度。
九、个人中心与成就系统
个人中心Tab集成了玩家信息展示、统计数据、成就徽章、订单管理与收藏列表五大功能模块,是玩家数据管理的核心入口。
@Component
struct ProfileTab79 {
onEditPlayer: () => void = () => {}
onCancelOrder: (id: number) => void = () => {}
onDeleteFav: (id: number) => void = () => {}
build() {
Scroll() {
Column() {
// 渐变头部
Column() {
Row() {
Column() {
Text('🎲').fontSize(40)
}
.width(64).height(64).borderRadius(32)
.backgroundColor('rgba(255,255,255,0.3)')
.justifyContent(FlexAlign.Center)
Column() {
Text('桌游玩家').fontSize(18).fontColor('#FFFFFF').fontWeight(FontWeight.Bold)
Text('ID: BG20260824').fontSize(11).fontColor('rgba(255,255,255,0.8)').margin({ top: 4 })
Row() {
Text('铂金II').fontSize(10).fontColor('#FFFFFF').backgroundColor('rgba(255,255,255,0.25)').padding({ left: 6, right: 6, top: 1, bottom: 1 }).borderRadius(4)
Text('积分 ' + PLAYER_STAT_79.rankScore).fontSize(10).fontColor('rgba(255,255,255,0.8)').margin({ left: 8 })
}
.margin({ top: 4 })
}
.margin({ left: 12 })
.layoutWeight(1)
Text('编辑').fontSize(11).fontColor('#FFFFFF').backgroundColor('rgba(255,255,255,0.25)').padding({ left: 8, right: 8, top: 4, bottom: 4 }).borderRadius(12)
.onClick(() => { this.onEditPlayer() })
}
.width('100%').padding(16)
}
.width('100%')
.linearGradient({ angle: 135, colors: [['#4A148C', 0], ['#311B92', 1]] })
// 玩家统计
Text('玩家统计').fontSize(14).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ left: 16, top: 16 })
Row() {
Column() {
Text(PLAYER_STAT_79.totalGames.toString()).fontSize(20).fontColor('#4A148C').fontWeight(FontWeight.Bold)
Text('总场次').fontSize(10).fontColor('#AAAAAA').margin({ top: 2 })
}
.layoutWeight(1).alignItems(HorizontalAlign.Center)
Column() {
Text(PLAYER_STAT_79.winRate + '%').fontSize(20).fontColor('#FF6F00').fontWeight(FontWeight.Bold)
Text('胜率').fontSize(10).fontColor('#AAAAAA').margin({ top: 2 })
}
.layoutWeight(1).alignItems(HorizontalAlign.Center)
Column() {
Text(PLAYER_STAT_79.bestStreak.toString()).fontSize(20).fontColor('#4CAF50').fontWeight(FontWeight.Bold)
Text('最佳连胜').fontSize(10).fontColor('#AAAAAA').margin({ top: 2 })
}
.layoutWeight(1).alignItems(HorizontalAlign.Center)
}
.width('100%').padding(12).margin({ top: 8, left: 16, right: 16 })
.backgroundColor('#FFFFFF')
.borderRadius(12)
Row() {
Text('最爱游戏:' + PLAYER_STAT_79.favoriteGame).fontSize(12).fontColor('#666666').margin({ left: 16, top: 8 })
Text('').layoutWeight(1)
}
.width('100%')
// 成就徽章
Text('成就徽章').fontSize(14).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ left: 16, top: 12 })
Grid() {
ForEach(ACHIEVEMENTS_79, (ach: Achievement79) => {
GridItem() {
Column() {
Text(ach.icon).fontSize(28).opacity(ach.unlocked ? 1 : 0.3)
Text(ach.name).fontSize(10).fontColor(ach.unlocked ? '#333333' : '#CCCCCC').margin({ top: 4 })
Text(ach.desc).fontSize(8).fontColor('#AAAAAA').margin({ top: 2 }).maxLines(1)
Text(ach.unlocked ? '已获得' : '未解锁').fontSize(8).fontColor(ach.unlocked ? ach.color : '#CCCCCC').margin({ top: 2 })
}
.width('100%').padding(8)
.backgroundColor(ach.unlocked ? ach.color + '10' : '#F5F5F5')
.borderRadius(10)
.alignItems(HorizontalAlign.Center)
}
}, (ach: Achievement79) => ach.id.toString())
}
.columnsTemplate('3fr 3fr 3fr')
.rowsGap(8).columnsGap(8)
.padding(16)
// 订单列表
Row() {
Text('我的订单').fontSize(14).fontWeight(FontWeight.Bold).fontColor('#333333')
Text('').layoutWeight(1)
Text('全部 >').fontSize(11).fontColor('#999999')
}
.width('100%').padding({ left: 16, right: 16 })
Column() {
ForEach(ORDERS_79, (order: OrderItem79) => {
Column() {
Row() {
Text(order.service).fontSize(13).fontColor('#333333').fontWeight(FontWeight.Medium)
Text('').layoutWeight(1)
Text(order.status).fontSize(11).fontColor(order.color)
}
.width('100%')
Row() {
Text(order.store + ' · ' + order.date).fontSize(10).fontColor('#AAAAAA')
Text('').layoutWeight(1)
Text('¥' + order.amount).fontSize(14).fontColor('#FF6F00').fontWeight(FontWeight.Bold)
}
.width('100%').margin({ top: 4 })
Row() {
Text('').layoutWeight(1)
if (order.status === '待开始' || order.status === '已报名') {
Text('取消').fontSize(10).fontColor('#FF5252').onClick(() => { this.onCancelOrder(order.id) })
}
}
.width('100%').margin({ top: 4 })
}
.width('100%').padding(12).margin({ top: 8, left: 16, right: 16 })
.backgroundColor('#FFFFFF')
.borderRadius(10)
.border({ width: 1, color: '#F0F0F0' })
}, (order: OrderItem79) => order.id.toString())
}
.width('100%')
}
.width('100%')
}
.scrollable(ScrollDirection.Vertical)
.width('100%').height('100%')
.backgroundColor('#F3E5F5')
}
}
个人中心头部的编辑按钮以半透明白色背景的胶囊形按钮形式展示在右侧,与宠物和文创应用的头部设计形成差异——后者未在头部提供编辑入口。玩家统计区域采用三列等宽布局,分别展示总场次、胜率与最佳连胜三个核心数据。每个统计项的数字使用不同的颜色——总场次使用桌游紫色#4A148C、胜率使用骰子橙#FF6F00、最佳连胜使用绿色#4CAF50,这种色彩编码使三项数据在视觉上形成区分。
成就徽章区域是个人中心的视觉亮点。Grid采用columnsTemplate(‘3fr 3fr 3fr’)的三列布局,九个成就徽章以网格形式排列。每个徽章的视觉表现根据unlocked布尔字段动态切换——已解锁的徽章图标opacity为1,背景使用成就颜色加’10’透明度,文字颜色为深色;未解锁的徽章图标opacity降为0.3,背景为浅灰色#F5F5F5,文字颜色为灰色#CCCCCC。这种视觉差异清晰地传达了成就的解锁状态,激励玩家通过游戏行为解锁更多成就。
十、应用整体业务流程
十一、取消预约与删除收藏弹窗
应用通过bindContentCover绑定了两个全屏覆盖确认弹窗,采用统一的弹窗视觉规范与交互模式。
@Builder
buildCancelCover() {
Column() {
Column() {
Text('取消预约').fontSize(18).fontWeight(FontWeight.Bold).fontColor('#333333')
Text('确认取消此拼桌预约?取消后可能需要重新排队').fontSize(13).fontColor('#999999').margin({ top: 12 }).textAlign(TextAlign.Center)
Row() {
Button() { Text('再想想').fontSize(14).fontColor('#666666') }
.layoutWeight(1).height(44).backgroundColor('#F5F5F5').borderRadius(22).margin({ right: 12 })
.onClick(() => { this.showCancelDialog = false })
Button() { Text('确认取消').fontSize(14).fontColor('#FFFFFF') }
.layoutWeight(1).height(44).backgroundColor('#FF5252').borderRadius(22)
.onClick(() => { this.showCancelDialog = false })
}
.width('100%').margin({ top: 24 })
}
.width('80%').padding(24).backgroundColor('#FFFFFF').borderRadius(20)
}
.width('100%').height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.backgroundColor('rgba(0,0,0,0.5)')
}
@Builder
buildDeleteFavCover() {
Column() {
Column() {
Text('删除收藏').fontSize(18).fontWeight(FontWeight.Bold).fontColor('#333333')
Text('确认从收藏列表中移除?').fontSize(13).fontColor('#999999').margin({ top: 12 })
Row() {
Button() { Text('取消').fontSize(14).fontColor('#666666') }
.layoutWeight(1).height(44).backgroundColor('#F5F5F5').borderRadius(22).margin({ right: 12 })
.onClick(() => { this.showDeleteFavDialog = false })
Button() { Text('删除').fontSize(14).fontColor('#FFFFFF') }
.layoutWeight(1).height(44).backgroundColor('#FF5252').borderRadius(22)
.onClick(() => { this.showDeleteFavDialog = false })
}
.width('100%').margin({ top: 24 })
}
.width('80%').padding(24).backgroundColor('#FFFFFF').borderRadius(20)
}
.width('100%').height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.backgroundColor('rgba(0,0,0,0.5)')
}
取消预约弹窗的提示文字"确认取消此拼桌预约?取消后可能需要重新排队"包含了取消后果的说明,这种文案设计在拼桌场景中能够减少用户对取消操作后果的疑虑。两个弹窗的结构完全一致——外层半透明黑色遮罩、内层白色圆角卡片、标题与提示文字、双按钮操作行。按钮的配色遵循统一的视觉规范——取消/再想想按钮使用灰色背景,确认/删除按钮使用红色#FF5252背景,通过色彩对比引导用户谨慎执行破坏性操作。
十二、编辑玩家资料弹窗
编辑玩家资料弹窗提供了昵称、当前段位、擅长游戏与个人简介的编辑功能,是个人中心玩家管理的交互入口。
@Builder
buildEditPlayerSheet() {
Column() {
Row() {
Text('编辑玩家资料').fontSize(18).fontWeight(FontWeight.Bold).fontColor('#333333')
Text('').layoutWeight(1)
Text('✕').fontSize(20).fontColor('#999999').onClick(() => { this.showEditPlayerDialog = false })
}
.width('100%').padding(16)
Scroll() {
Column() {
Text('昵称').fontSize(14).fontColor('#666666').margin({ top: 8 })
TextInput({ text: this.editPlayerName, placeholder: '请输入玩家昵称' })
.width('100%').height(44).margin({ top: 8 })
.borderRadius(10).backgroundColor('#F5F5F5')
.onChange((val: string) => { this.editPlayerName = val })
Text('当前段位').fontSize(14).fontColor('#666666').margin({ top: 12 })
TextInput({ text: this.editPlayerLevel, placeholder: '如:铂金II' })
.width('100%').height(44).margin({ top: 8 })
.borderRadius(10).backgroundColor('#F5F5F5')
.onChange((val: string) => { this.editPlayerLevel = val })
Text('擅长游戏').fontSize(14).fontColor('#666666').margin({ top: 12 })
TextInput({ text: this.editPlayerGames, placeholder: '如:卡坦岛、阿瓦隆、璀璨宝石' })
.width('100%').height(44).margin({ top: 8 })
.borderRadius(10).backgroundColor('#F5F5F5')
.onChange((val: string) => { this.editPlayerGames = val })
Text('个人简介').fontSize(14).fontColor('#666666').margin({ top: 12 })
TextArea({ text: this.editPlayerBio, placeholder: '介绍一下你的桌游经历和风格...' })
.width('100%').height(80).margin({ top: 8 })
.borderRadius(10).backgroundColor('#F5F5F5')
.onChange((val: string) => { this.editPlayerBio = val })
}
.width('100%').padding({ left: 16, right: 16, bottom: 16 })
}
.constraintSize({ maxHeight: '55%' })
Row() {
Button() {
Text('保存资料').fontSize(16).fontColor('#FFFFFF').fontWeight(FontWeight.Medium)
}
.layoutWeight(1).height(48)
.backgroundColor('#4A148C')
.borderRadius(24)
.onClick(() => { this.showEditPlayerDialog = false })
}
.width('100%').padding(16)
}
.width('100%')
}
编辑弹窗的表单字段设计体现了桌游社交场景的特点。昵称字段使用TextInput单行输入,段位字段的placeholder"如:铂金II"提供了具体的格式参考。擅长游戏字段的placeholder"如:卡坦岛、阿瓦隆、璀璨宝石"列举了三款热门桌游作为示例,帮助用户理解输入格式。个人简介使用TextArea多行输入框,placeholder"介绍一下你的桌游经历和风格…"引导用户分享自己的桌游背景。所有输入框统一使用#F5F5F5浅灰色背景与10的圆角值,高度固定为44(TextInput)或80(TextArea),保证触控友好性。保存按钮使用桌游紫色#4A148C作为背景色,与品牌主题保持一致。
技术点对比总结
| 技术维度 | 游戏库Tab | 拼桌Tab | 排行Tab | 战队Tab | 社区Tab | 个人中心 |
|---|---|---|---|---|---|---|
| 布局方式 | Grid四列+横滑+双列Grid | 列表+进度条 | 柱状图+冠军卡+列表 | 列表+招募列表 | 横滑+列表 | 统计+成就Grid+列表 |
| 数据驱动 | GAME_CATS+BOARD_GAMES | PLAY_TABLES | BAR_DATA+RANK_PLAYERS | TEAMS+RECRUITS | TOPICS+POSTS | PLAYER_STAT+ACHIEVEMENTS+ORDERS |
| 交互回调 | onBook预约 | onBook报名 | 无操作回调 | 无操作回调 | 无操作回调 | onEditPlayer/onCancel/onDelete |
| 视觉特色 | 五级难度指示器 | layoutWeight进度条 | 金色渐变冠军卡 | 胜负色彩编码 | 游戏关联动态 | 成就解锁状态可视化 |
| 状态管理 | 无独立状态 | 无独立状态 | 无独立状态 | 无独立状态 | 无独立状态 | 通过回调触发父组件状态 |
| 弹窗触发 | 预约弹窗 | 预约弹窗 | 无弹窗 | 无弹窗 | 无弹窗 | 编辑/取消/删除弹窗 |
| 滚动方式 | Vertical+Horizontal | Vertical | Vertical | Vertical | Vertical+Horizontal | Vertical |
安装DevEco Studio程序

选择目标安装目录:

设置环境变量,但是需要重启一下:

新建一个空白模板:

设置API为24的模板项目:

初始化项目,自动下载相关依赖:

完整代码:
// 主题:桌游紫 #4A148C × 骰子橙 #FF6F00,底色 #F3E5F5(神秘桌游紫调)
// 布局差异:分类网格+本周热门+双列游戏 / 拼桌列表+详情+进度 / 排行柱状图+排名+周冠军 / 战队列表+招募+战绩 / 社区动态+热门话题 / 渐变头+游戏统计+成就徽章+订单+收藏
// ============ 类型定义 ============
interface BoardGame79 {
id: number
name: string
players: string
duration: string
difficulty: number
rating: number
plays: number
tags: string[]
color: string
desc: string
category: string
}
interface GameCategory79 {
label: string
icon: string
color: string
bg: string
count: number
}
interface PlayTable79 {
id: number
game: string
store: string
distance: string
date: string
time: string
currentPlayers: number
maxPlayers: number
host: string
level: string
status: string
color: string
fee: number
}
interface RankPlayer79 {
id: number
rank: number
name: string
level: string
score: number
games: number
winRate: number
avatarColor: string
badge: string
}
interface BarData79 {
label: string
value: number
color: string
}
interface TeamItem79 {
id: number
name: string
members: number
wins: number
losses: number
winRate: number
level: string
color: string
desc: string
captain: string
}
interface TeamRecruit79 {
id: number
team: string
role: string
requirement: string
level: string
date: string
color: string
}
interface CommunityPost79 {
id: number
author: string
avatarColor: string
content: string
likes: number
comments: number
shares: number
images: number
timeAgo: string
topic: string
game: string
}
interface TopicItem79 {
id: number
title: string
posts: number
hot: boolean
color: string
}
interface OrderItem79 {
id: number
service: string
date: string
amount: number
status: string
store: string
color: string
}
interface FavoriteItem79 {
id: number
name: string
type: string
color: string
}
interface Achievement79 {
id: number
name: string
desc: string
icon: string
unlocked: boolean
color: string
}
interface PlayerStat79 {
totalGames: number
winRate: number
bestStreak: number
rankScore: number
favoriteGame: string
}
interface GameSelect79 {
id: number
name: string
players: string
color: string
}
// ============ 静态数据 ============
const GAME_CATS_79: GameCategory79[] = [
{ label: '策略', icon: '♟️', color: '#4A148C', bg: '#F3E5F5', count: 128 },
{ label: '派对', icon: '🎉', color: '#FF6F00', bg: '#FFF3E0', count: 96 },
{ label: '卡牌', icon: '🃏', color: '#1565C0', bg: '#E3F2FD', count: 85 },
{ label: '推理', icon: '🔍', color: '#00897B', bg: '#E0F2F1', count: 72 },
{ label: '角色扮演', icon: '🎭', color: '#C2185B', bg: '#FCE4EC', count: 64 },
{ label: '模拟', icon: '🏗️', color: '#6A1B9A', bg: '#F3E5F5', count: 58 },
{ label: '聚会', icon: '🍻', color: '#E65100', bg: '#FFF3E0', count: 45 },
{ label: '合作', icon: '🤝', color: '#2E7D32', bg: '#E8F5E9', count: 38 }
]
const BOARD_GAMES_79: BoardGame79[] = [
{ id: 1, name: '卡坦岛', players: '3-4人', duration: '60-90分', difficulty: 3, rating: 4.8, plays: 8542, tags: ['策略', '经典'], color: '#4A148C', desc: '资源管理+交易建设,入门策略首选', category: '策略' },
{ id: 2, name: '狼人杀', players: '8-18人', duration: '30-60分', difficulty: 2, rating: 4.7, plays: 12653, tags: ['派对', '推理'], color: '#FF6F00', desc: '身份推理+口才博弈,聚会必备', category: '派对' },
{ id: 3, name: '璀璨宝石', players: '2-4人', duration: '30-45分', difficulty: 2, rating: 4.6, plays: 6234, tags: ['策略', '入门'], color: '#1565C0', desc: '宝石收集+引擎构筑,轻策略之王', category: '策略' },
{ id: 4, name: '阿瓦隆', players: '5-10人', duration: '30-60分', difficulty: 2, rating: 4.8, plays: 9856, tags: ['推理', '阵营'], color: '#00897B', desc: '隐藏身份+任务推理,比狼人杀更深', category: '推理' },
{ id: 5, name: '大富翁', players: '2-6人', duration: '60-120分', difficulty: 1, rating: 4.5, plays: 15423, tags: ['经典', '模拟'], color: '#C2185B', desc: '买卖地产+收租致富,童年回忆', category: '模拟' },
{ id: 6, name: '万智牌', players: '2人', duration: '30-60分', difficulty: 4, rating: 4.9, plays: 4321, tags: ['卡牌', '竞技'], color: '#6A1B9A', desc: '全球首款TCG,深度策略卡牌', category: '卡牌' },
{ id: 7, name: '密室逃脱桌游版', players: '2-6人', duration: '60-90分', difficulty: 3, rating: 4.7, plays: 3654, tags: ['推理', '合作'], color: '#E65100', desc: '解谜逃脱+团队协作,沉浸体验', category: '推理' },
{ id: 8, name: '龙与地下城', players: '3-6人', duration: '120-240分', difficulty: 4, rating: 4.9, plays: 2876, tags: ['RPG', '高难度'], color: '#2E7D32', desc: '经典TRPG,角色扮演冒险', category: '角色扮演' },
{ id: 9, name: 'UNO', players: '2-10人', duration: '15-30分', difficulty: 1, rating: 4.6, plays: 18765, tags: ['派对', '入门'], color: '#FF6F00', desc: '颜色数字配对,聚会万能牌', category: '聚会' },
{ id: 10, name: '七大奇迹', players: '3-7人', duration: '30-45分', difficulty: 2, rating: 4.7, plays: 5432, tags: ['策略', '文明'], color: '#4A148C', desc: '文明建设+卡牌轮抽,快节奏策略', category: '策略' }
]
const PLAY_TABLES_79: PlayTable79[] = [
{ id: 1, game: '卡坦岛', store: '骰子工坊·朝阳店', distance: '0.8km', date: '今天', time: '19:00', currentPlayers: 3, maxPlayers: 4, host: '桌游达人', level: '进阶', status: '报名中', color: '#4A148C', fee: 30 },
{ id: 2, game: '狼人杀', store: '狼人酒馆·海淀店', distance: '1.2km', date: '今天', time: '20:00', currentPlayers: 8, maxPlayers: 12, host: '法官小明', level: '入门', status: '报名中', color: '#FF6F00', fee: 35 },
{ id: 3, game: '阿瓦隆', store: '桌游星球·西城店', distance: '2.1km', date: '明天', time: '14:00', currentPlayers: 5, maxPlayers: 10, host: '亚瑟王', level: '进阶', status: '报名中', color: '#00897B', fee: 30 },
{ id: 4, game: '璀璨宝石', store: '骰子工坊·朝阳店', distance: '0.8km', date: '明天', time: '15:00', currentPlayers: 2, maxPlayers: 4, host: '宝石收藏家', level: '入门', status: '报名中', color: '#1565C0', fee: 25 },
{ id: 5, game: '龙与地下城', store: '冒险者公会·东城店', distance: '3.5km', date: '周六', time: '13:00', currentPlayers: 4, maxPlayers: 6, host: 'DM老王', level: '高级', status: '报名中', color: '#2E7D32', fee: 80 },
{ id: 6, game: '七大奇迹', store: '桌游星球·西城店', distance: '2.1km', date: '周日', time: '16:00', currentPlayers: 3, maxPlayers: 7, host: '文明建造者', level: '进阶', status: '报名中', color: '#4A148C', fee: 35 },
{ id: 7, game: '密室逃脱桌游', store: '迷踪密室·丰台店', distance: '4.2km', date: '周六', time: '18:00', currentPlayers: 4, maxPlayers: 6, host: '密室设计者', level: '进阶', status: '已满员', color: '#E65100', fee: 50 },
{ id: 8, game: '万智牌赛事', store: '卡牌竞技馆·通州店', distance: '5.0km', date: '周日', time: '10:00', currentPlayers: 16, maxPlayers: 32, host: '裁判老张', level: '高级', status: '报名中', color: '#6A1B9A', fee: 100 }
]
const RANK_PLAYERS_79: RankPlayer79[] = [
{ id: 1, rank: 1, name: '棋王降临', level: '大师III', score: 9850, games: 1256, winRate: 78, avatarColor: '#FFD700', badge: '👑' },
{ id: 2, rank: 2, name: '策略之神', level: '大师II', score: 9420, games: 1156, winRate: 75, avatarColor: '#4A148C', badge: '🥈' },
{ id: 3, rank: 3, name: '骰子猎人', level: '大师I', score: 9180, games: 1098, winRate: 73, avatarColor: '#FF6F00', badge: '🥉' },
{ id: 4, rank: 4, name: '卡牌大师', level: '钻石III', score: 8750, games: 987, winRate: 70, avatarColor: '#1565C0', badge: '' },
{ id: 5, rank: 5, name: '推理专家', level: '钻石II', score: 8320, games: 876, winRate: 68, avatarColor: '#00897B', badge: '' },
{ id: 6, rank: 6, name: '文明建造者', level: '钻石I', score: 7980, games: 765, winRate: 65, avatarColor: '#C2185B', badge: '' },
{ id: 7, rank: 7, name: '派对之王', level: '铂金III', score: 7650, games: 654, winRate: 62, avatarColor: '#6A1B9A', badge: '' },
{ id: 8, rank: 8, name: '密室达人', level: '铂金II', score: 7320, games: 543, winRate: 60, avatarColor: '#2E7D32', badge: '' }
]
const BAR_DATA_RANK_79: BarData79[] = [
{ label: '1周', value: 850, color: '#CE93D8' },
{ label: '2周', value: 920, color: '#CE93D8' },
{ label: '3周', value: 780, color: '#CE93D8' },
{ label: '4周', value: 1050, color: '#CE93D8' },
{ label: '5周', value: 1180, color: '#CE93D8' },
{ label: '6周', value: 1320, color: '#4A148C' },
{ label: '7周', value: 1450, color: '#4A148C' },
{ label: '8周', value: 1380, color: '#4A148C' }
]
const TEAMS_79: TeamItem79[] = [
{ id: 1, name: '紫金骰子队', members: 12, wins: 156, losses: 48, winRate: 76, level: '冠军联赛', color: '#4A148C', desc: '策略桌游强队,连续3届联赛冠军', captain: '棋王降临' },
{ id: 2, name: '橙色火焰', members: 10, wins: 128, losses: 52, winRate: 71, level: '超级联赛', color: '#FF6F00', desc: '派对桌游专精,气氛组担当', captain: '派对之王' },
{ id: 3, name: '蓝色风暴', members: 8, wins: 98, losses: 62, winRate: 61, level: '甲级联赛', color: '#1565C0', desc: '卡牌竞技战队,万智牌强队', captain: '卡牌大师' },
{ id: 4, name: '绿色军团', members: 15, wins: 142, losses: 58, winRate: 71, level: '超级联赛', color: '#00897B', desc: '推理桌游战队,阿瓦隆王者', captain: '推理专家' },
{ id: 5, name: '红色猎手', members: 6, wins: 76, losses: 44, winRate: 63, level: '甲级联赛', color: '#C2185B', desc: '模拟经营专精,大富翁称霸', captain: '文明建造者' },
{ id: 6, name: '紫色幻影', members: 9, wins: 85, losses: 55, winRate: 61, level: '乙级联赛', color: '#6A1B9A', desc: '角色扮演战队,DMD冒险团', captain: '密室达人' }
]
const TEAM_RECRUITS_79: TeamRecruit79[] = [
{ id: 1, team: '紫金骰子队', role: '策略主力', requirement: '钻石以上+策略类专精', level: '高级', date: '08-24', color: '#4A148C' },
{ id: 2, team: '橙色火焰', role: '气氛担当', requirement: '热爱派对桌游+活跃度高', level: '不限', date: '08-23', color: '#FF6F00' },
{ id: 3, team: '蓝色风暴', role: '卡牌选手', requirement: '万智牌/游戏王经验', level: '中级', date: '08-22', color: '#1565C0' },
{ id: 4, team: '绿色军团', role: '推理高手', requirement: '阿瓦隆/狼人杀胜率60%+', level: '中级', date: '08-21', color: '#00897B' },
{ id: 5, team: '紫色幻影', role: 'RPG玩家', requirement: 'DND跑团经验优先', level: '不限', date: '08-20', color: '#6A1B9A' }
]
const POSTS_79: CommunityPost79[] = [
{ id: 1, author: '棋王降临', avatarColor: '#FFD700', content: '本周卡坦岛锦标赛冠军!分享一套新的资源管理策略,3号位起手 wheat+ore 太强了~', likes: 892, comments: 156, shares: 67, images: 4, timeAgo: '2小时前', topic: '#赛事战报#', game: '卡坦岛' },
{ id: 2, author: '策略之神', avatarColor: '#4A148C', content: '璀璨宝石新思路:从贵族路线转向引擎流,胜率提升了15%!附详细攻略和回合分析', likes: 768, comments: 134, shares: 45, images: 5, timeAgo: '5小时前', topic: '#攻略分享#', game: '璀璨宝石' },
{ id: 3, author: '骰子猎人', avatarColor: '#FF6F00', content: '昨天在骰子工坊拼了一局七大奇迹,7人局节奏太紧凑了!绿科学流果然暴力', likes: 534, comments: 89, shares: 23, images: 3, timeAgo: '8小时前', topic: '#游戏复盘#', game: '七大奇迹' },
{ id: 4, author: '推理专家', avatarColor: '#00897B', content: '阿瓦隆进阶攻略:如何用梅林的信息差打出完美开局?附5局实战分析', likes: 945, comments: 187, shares: 78, images: 6, timeAgo: '12小时前', topic: '#推理攻略#', game: '阿瓦隆' },
{ id: 5, author: '密室达人', avatarColor: '#2E7D32', content: 'DND跑团分享:新DM的第一场冒险复盘,从NPC设计到战斗平衡的踩坑记录', likes: 678, comments: 123, shares: 45, images: 4, timeAgo: '1天前', topic: '#跑团分享#', game: '龙与地下城' },
{ id: 6, author: '卡牌大师', avatarColor: '#1565C0', content: '万智牌新系列预组分析:哪些卡值得入手?什么套路适合新手过渡?', likes: 456, comments: 78, shares: 34, images: 3, timeAgo: '2天前', topic: '#卡牌攻略#', game: '万智牌' }
]
const TOPICS_79: TopicItem79[] = [
{ id: 1, title: '卡坦岛锦标赛', posts: 2156, hot: true, color: '#4A148C' },
{ id: 2, title: '狼人杀高配局', posts: 1876, hot: true, color: '#FF6F00' },
{ id: 3, title: 'DND跑团招募', posts: 1562, hot: true, color: '#2E7D32' },
{ id: 4, title: '阿瓦隆进阶', posts: 854, hot: false, color: '#00897B' },
{ id: 5, title: '万智牌新系列', posts: 654, hot: false, color: '#1565C0' },
{ id: 6, title: '桌游收纳攻略', posts: 432, hot: false, color: '#6A1B9A' }
]
const ORDERS_79: OrderItem79[] = [
{ id: 1, service: '拼桌·卡坦岛', date: '08-24', amount: 30, status: '已完成', store: '骰子工坊·朝阳店', color: '#4CAF50' },
{ id: 2, service: '拼桌·狼人杀', date: '08-22', amount: 35, status: '已完成', store: '狼人酒馆·海淀店', color: '#4CAF50' },
{ id: 3, service: '拼桌·阿瓦隆', date: '08-25', amount: 30, status: '待开始', store: '桌游星球·西城店', color: '#FF9800' },
{ id: 4, service: '万智牌赛事', date: '09-01', amount: 100, status: '已报名', store: '卡牌竞技馆', color: '#2196F3' },
{ id: 5, service: 'DND跑团', date: '09-07', amount: 80, status: '已报名', store: '冒险者公会', color: '#2196F3' },
{ id: 6, service: '会员月卡', date: '09-01', amount: 199, status: '生效中', store: '骰子工坊', color: '#4CAF50' }
]
const FAVORITES_79: FavoriteItem79[] = [
{ id: 1, name: '卡坦岛', type: '策略桌游', color: '#4A148C' },
{ id: 2, name: '骰子工坊·朝阳店', type: '桌游店', color: '#FF6F00' },
{ id: 3, name: '紫金骰子队', type: '战队', color: '#4A148C' },
{ id: 4, name: '龙与地下城', type: 'RPG桌游', color: '#2E7D32' },
{ id: 5, name: '阿瓦隆', type: '推理桌游', color: '#00897B' }
]
const ACHIEVEMENTS_79: Achievement79[] = [
{ id: 1, name: '百战不殆', desc: '完成100场对局', icon: '🎮', unlocked: true, color: '#4A148C' },
{ id: 2, name: '常胜将军', desc: '连胜10场', icon: '🏆', unlocked: true, color: '#FFD700' },
{ id: 3, name: '策略大师', desc: '策略类胜率70%+', icon: '♟️', unlocked: true, color: '#1565C0' },
{ id: 4, name: '派对达人', desc: '参加50场派对局', icon: '🎉', unlocked: true, color: '#FF6F00' },
{ id: 5, name: '推理专家', desc: '推理类胜率65%+', icon: '🔍', unlocked: false, color: '#00897B' },
{ id: 6, name: '跑团勇士', desc: '完成10次DND冒险', icon: '⚔️', unlocked: false, color: '#2E7D32' },
{ id: 7, name: '收藏家', desc: '拥有20款桌游', icon: '📚', unlocked: true, color: '#6A1B9A' },
{ id: 8, name: '战队核心', desc: '加入战队并参赛', icon: '🛡️', unlocked: true, color: '#C2185B' },
{ id: 9, name: '赛事冠军', desc: '获得赛事冠军', icon: '👑', unlocked: false, color: '#FFD700' }
]
const GAME_SELECTS_79: GameSelect79[] = [
{ id: 1, name: '卡坦岛', players: '3-4人', color: '#4A148C' },
{ id: 2, name: '狼人杀', players: '8-18人', color: '#FF6F00' },
{ id: 3, name: '阿瓦隆', players: '5-10人', color: '#00897B' },
{ id: 4, name: '璀璨宝石', players: '2-4人', color: '#1565C0' },
{ id: 5, name: '七大奇迹', players: '3-7人', color: '#4A148C' }
]
const PLAYER_STAT_79: PlayerStat79 = {
totalGames: 356,
winRate: 68,
bestStreak: 12,
rankScore: 7320,
favoriteGame: '卡坦岛'
}
// ============ 主入口组件 ============
@Entry
@Component
struct DuoDuoBoardGameApp {
@State currentTab: number = 0
@State showBookDialog: boolean = false
@State showCancelDialog: boolean = false
@State showEditPlayerDialog: boolean = false
@State showDeleteFavDialog: boolean = false
@State selectedTableId: number = 0
@State selectedGameId: number = 1
@State tableCount: number = 1
@State bookTime: string = ''
@State bookNotes: string = ''
@State editPlayerName: string = ''
@State editPlayerLevel: string = ''
@State editPlayerGames: string = ''
@State editPlayerBio: string = ''
@State cancelTargetId: number = 0
private tabs: string[] = ['游戏库', '拼桌', '排行', '战队', '社区', '我的']
private timeSlots: string[] = ['14:00', '16:00', '18:00', '19:00', '20:00', '21:00']
build() {
Column() {
// ====== 顶部头部 ======
Column() {
Row() {
Column() {
Text('多多桌游')
.fontSize(22)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Bold)
Text('骰子一掷·好友相聚')
.fontSize(11)
.fontColor('rgba(255,255,255,0.8)')
.margin({ top: 2 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
Row() {
Text('🎲').fontSize(18).fontColor('#FFFFFF')
}
.width(36).height(36).justifyContent(FlexAlign.Center)
.backgroundColor('rgba(255,255,255,0.2)')
.borderRadius(18)
}
.width('100%').height(56).padding({ left: 16, right: 16 })
.alignItems(VerticalAlign.Center)
Row() {
Text('搜桌游、拼桌、战队...').fontSize(13).fontColor('rgba(255,255,255,0.6)').layoutWeight(1)
Text('🔍').fontSize(16).fontColor('rgba(255,255,255,0.6)')
}
.width('100%').height(36).margin({ top: 4 })
.padding({ left: 16, right: 16 })
.backgroundColor('rgba(255,255,255,0.15)')
.borderRadius(20)
.alignItems(VerticalAlign.Center)
}
.width('100%')
.linearGradient({ angle: 135, colors: [['#4A148C', 0], ['#311B92', 1]] })
.padding({ top: 8, bottom: 12, left: 16, right: 16 })
// ====== Tab内容区 ======
Stack({ alignContent: Alignment.TopStart }) {
Column() {
if (this.currentTab === 0) {
GameLibTab79({
onBook: (id: number) => {
this.selectedTableId = id
this.showBookDialog = true
}
})
}
if (this.currentTab === 1) {
PlayTableTab79({
onBook: (id: number) => {
this.selectedTableId = id
this.showBookDialog = true
}
})
}
if (this.currentTab === 2) {
RankTab79()
}
if (this.currentTab === 3) {
TeamTab79()
}
if (this.currentTab === 4) {
CommunityTab79()
}
if (this.currentTab === 5) {
ProfileTab79({
onEditPlayer: () => {
this.showEditPlayerDialog = true
}, onCancelOrder: (id: number) => {
this.cancelTargetId = id
this.showCancelDialog = true
}, onDeleteFav: (id: number) => {
this.cancelTargetId = id
this.showDeleteFavDialog = true
}
})
}
}
.width('100%').height('100%')
}
.layoutWeight(1)
.width('100%')
// ====== 底部Tab栏 ======
Row() {
ForEach(this.tabs, (tab: string, idx: number) => {
Column() {
Text(this.getTabIcon(idx))
.fontSize(20)
.fontColor(this.currentTab === idx ? '#4A148C' : '#999999')
Text(tab)
.fontSize(10)
.fontColor(this.currentTab === idx ? '#4A148C' : '#999999')
.margin({ top: 2 })
}
.layoutWeight(1)
.height(56)
.justifyContent(FlexAlign.Center)
.onClick(() => {
this.currentTab = idx
})
}, (tab: string) => tab)
}
.width('100%')
.height(56)
.backgroundColor('#FFFFFF')
.border({ width: 1, color: '#E0E0E0' })
}
.width('100%').height('100%')
.backgroundColor('#F3E5F5')
.bindContentCover(this.showCancelDialog, this.buildCancelCover())
.bindContentCover(this.showDeleteFavDialog, this.buildDeleteFavCover())
}
private getTabIcon(idx: number): string {
const icons: string[] = ['🎲', '🪑', '🏆', '🛡️', '💬', '👤']
return idx < icons.length ? icons[idx] : '📋'
}
@Builder
buildBookSheet() {
Column() {
Row() {
Text('拼桌预约').fontSize(18).fontWeight(FontWeight.Bold).fontColor('#333333')
Text('').layoutWeight(1)
Text('✕').fontSize(20).fontColor('#999999').onClick(() => { this.showBookDialog = false })
}
.width('100%').padding(16)
Scroll() {
Column() {
// 选择游戏
Text('选择游戏').fontSize(14).fontWeight(FontWeight.Bold).fontColor('#333333')
Scroll() {
Row() {
ForEach(GAME_SELECTS_79, (game: GameSelect79) => {
Column() {
Text(game.name).fontSize(12).fontColor(this.selectedGameId === game.id ? '#FFFFFF' : '#666666')
Text(game.players).fontSize(9).fontColor(this.selectedGameId === game.id ? 'rgba(255,255,255,0.8)' : '#AAAAAA').margin({ top: 2 })
}
.padding(10).margin({ right: 8 })
.borderRadius(10)
.backgroundColor(this.selectedGameId === game.id ? game.color : '#F5F5F5')
.onClick(() => { this.selectedGameId = game.id })
}, (game: GameSelect79) => game.id.toString())
}
}
.scrollable(ScrollDirection.Horizontal)
.width('100%').margin({ top: 8 })
// 桌数
Row() {
Text('预约桌数').fontSize(14).fontColor('#333333')
Text('').layoutWeight(1)
Row() {
Button() { Text('-').fontSize(16).fontColor('#666666') }
.width(32).height(32).backgroundColor('#F5F5F5').borderRadius(16)
.onClick(() => { if (this.tableCount > 1) { this.tableCount-- } })
Text(this.tableCount.toString()).fontSize(14).fontColor('#333333').width(40).textAlign(TextAlign.Center)
Button() { Text('+').fontSize(16).fontColor('#666666') }
.width(32).height(32).backgroundColor('#F5F5F5').borderRadius(16)
.onClick(() => { this.tableCount++ })
}
}
.width('100%').margin({ top: 16 })
// 时间
Text('选择时间').fontSize(14).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ top: 16 })
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(this.timeSlots, (slot: string, idx: number) => {
Text(slot)
.fontSize(12).fontColor(this.bookTime === slot ? '#FFFFFF' : '#666666')
.padding({ left: 16, right: 16, top: 8, bottom: 8 })
.margin({ right: 8, top: 8 })
.borderRadius(20)
.backgroundColor(this.bookTime === slot ? '#4A148C' : '#F5F5F5')
.onClick(() => { this.bookTime = slot })
}, (slot: string, idx: number) => idx.toString())
}
.width('100%').margin({ top: 8 })
// 备注
Text('备注').fontSize(14).fontColor('#333333').margin({ top: 16 })
TextArea({ text: this.bookNotes, placeholder: '请输入特殊需求,如新手教学、规则讲解等...' })
.width('100%').height(70).margin({ top: 8 })
.borderRadius(10).backgroundColor('#F5F5F5')
.onChange((val: string) => { this.bookNotes = val })
// 费用
Row() {
Text('预约费用').fontSize(13).fontColor('#666666')
Text('').layoutWeight(1)
Text('¥' + (30 * this.tableCount)).fontSize(20).fontColor('#FF6F00').fontWeight(FontWeight.Bold)
}
.width('100%').margin({ top: 20, bottom: 12 })
}
.width('100%').padding({ left: 16, right: 16, bottom: 16 })
}
.constraintSize({ maxHeight: '58%' })
Row() {
Button() {
Text('确认预约').fontSize(16).fontColor('#FFFFFF').fontWeight(FontWeight.Medium)
}
.layoutWeight(1).height(48)
.backgroundColor('#4A148C')
.borderRadius(24)
.onClick(() => { this.showBookDialog = false })
}
.width('100%').padding(16)
}
.width('100%')
}
@Builder
buildCancelCover() {
Column() {
Column() {
Text('取消预约').fontSize(18).fontWeight(FontWeight.Bold).fontColor('#333333')
Text('确认取消此拼桌预约?取消后可能需要重新排队').fontSize(13).fontColor('#999999').margin({ top: 12 }).textAlign(TextAlign.Center)
Row() {
Button() { Text('再想想').fontSize(14).fontColor('#666666') }
.layoutWeight(1).height(44).backgroundColor('#F5F5F5').borderRadius(22).margin({ right: 12 })
.onClick(() => { this.showCancelDialog = false })
Button() { Text('确认取消').fontSize(14).fontColor('#FFFFFF') }
.layoutWeight(1).height(44).backgroundColor('#FF5252').borderRadius(22)
.onClick(() => { this.showCancelDialog = false })
}
.width('100%').margin({ top: 24 })
}
.width('80%').padding(24).backgroundColor('#FFFFFF').borderRadius(20)
}
.width('100%').height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.backgroundColor('rgba(0,0,0,0.5)')
}
@Builder
buildEditPlayerSheet() {
Column() {
Row() {
Text('编辑玩家资料').fontSize(18).fontWeight(FontWeight.Bold).fontColor('#333333')
Text('').layoutWeight(1)
Text('✕').fontSize(20).fontColor('#999999').onClick(() => { this.showEditPlayerDialog = false })
}
.width('100%').padding(16)
Scroll() {
Column() {
Text('昵称').fontSize(14).fontColor('#666666').margin({ top: 8 })
TextInput({ text: this.editPlayerName, placeholder: '请输入玩家昵称' })
.width('100%').height(44).margin({ top: 8 })
.borderRadius(10).backgroundColor('#F5F5F5')
.onChange((val: string) => { this.editPlayerName = val })
Text('当前段位').fontSize(14).fontColor('#666666').margin({ top: 12 })
TextInput({ text: this.editPlayerLevel, placeholder: '如:铂金II' })
.width('100%').height(44).margin({ top: 8 })
.borderRadius(10).backgroundColor('#F5F5F5')
.onChange((val: string) => { this.editPlayerLevel = val })
Text('擅长游戏').fontSize(14).fontColor('#666666').margin({ top: 12 })
TextInput({ text: this.editPlayerGames, placeholder: '如:卡坦岛、阿瓦隆、璀璨宝石' })
.width('100%').height(44).margin({ top: 8 })
.borderRadius(10).backgroundColor('#F5F5F5')
.onChange((val: string) => { this.editPlayerGames = val })
Text('个人简介').fontSize(14).fontColor('#666666').margin({ top: 12 })
TextArea({ text: this.editPlayerBio, placeholder: '介绍一下你的桌游经历和风格...' })
.width('100%').height(80).margin({ top: 8 })
.borderRadius(10).backgroundColor('#F5F5F5')
.onChange((val: string) => { this.editPlayerBio = val })
}
.width('100%').padding({ left: 16, right: 16, bottom: 16 })
}
.constraintSize({ maxHeight: '55%' })
Row() {
Button() {
Text('保存资料').fontSize(16).fontColor('#FFFFFF').fontWeight(FontWeight.Medium)
}
.layoutWeight(1).height(48)
.backgroundColor('#4A148C')
.borderRadius(24)
.onClick(() => { this.showEditPlayerDialog = false })
}
.width('100%').padding(16)
}
.width('100%')
}
@Builder
buildDeleteFavCover() {
Column() {
Column() {
Text('删除收藏').fontSize(18).fontWeight(FontWeight.Bold).fontColor('#333333')
Text('确认从收藏列表中移除?').fontSize(13).fontColor('#999999').margin({ top: 12 })
Row() {
Button() { Text('取消').fontSize(14).fontColor('#666666') }
.layoutWeight(1).height(44).backgroundColor('#F5F5F5').borderRadius(22).margin({ right: 12 })
.onClick(() => { this.showDeleteFavDialog = false })
Button() { Text('删除').fontSize(14).fontColor('#FFFFFF') }
.layoutWeight(1).height(44).backgroundColor('#FF5252').borderRadius(22)
.onClick(() => { this.showDeleteFavDialog = false })
}
.width('100%').margin({ top: 24 })
}
.width('80%').padding(24).backgroundColor('#FFFFFF').borderRadius(20)
}
.width('100%').height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.backgroundColor('rgba(0,0,0,0.5)')
}
}
// ============ 游戏库Tab ============
@Component
struct GameLibTab79 {
onBook: (id: number) => void = () => {}
build() {
Scroll() {
Column() {
// 分类网格
Text('游戏分类').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ left: 16, top: 12 })
Grid() {
ForEach(GAME_CATS_79, (cat: GameCategory79) => {
GridItem() {
Column() {
Text(cat.icon).fontSize(26)
Text(cat.label).fontSize(11).fontColor('#333333').margin({ top: 4 })
Text(cat.count + '款').fontSize(9).fontColor('#AAAAAA')
}
.width('100%').padding({ top: 10, bottom: 10 })
.backgroundColor(cat.bg)
.borderRadius(12)
.alignItems(HorizontalAlign.Center)
}
}, (cat: GameCategory79) => cat.label)
}
.columnsTemplate('4fr 4fr 4fr 4fr')
.rowsGap(8).columnsGap(8)
.padding(16)
.height(210)
// 本周热门横滑
Row() {
Text('本周热门').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#333333')
Text('').layoutWeight(1)
Text('更多 >').fontSize(12).fontColor('#4A148C')
}
.width('100%').padding({ left: 16, right: 16 })
Scroll() {
Row() {
ForEach(BOARD_GAMES_79, (game: BoardGame79) => {
Column() {
Column() {
Text('🎲').fontSize(32)
}
.width(160).height(70)
.backgroundColor(game.color)
.borderRadius({ topLeft: 12, topRight: 12 })
.justifyContent(FlexAlign.Center)
Column() {
Text(game.name).fontSize(13).fontColor('#333333').fontWeight(FontWeight.Bold)
Text(game.players + ' · ' + game.duration).fontSize(9).fontColor('#AAAAAA').margin({ top: 2 })
Row() {
Text('★').fontSize(10).fontColor('#FFB300')
Text(game.rating.toString()).fontSize(10).fontColor('#FFB300').margin({ left: 2 })
Text(game.plays + '场').fontSize(9).fontColor('#AAAAAA').margin({ left: 8 })
}
.margin({ top: 4 })
Row() {
ForEach(game.tags, (tag: string) => {
Text(tag).fontSize(9).fontColor(game.color).backgroundColor(game.color + '15').padding({ left: 4, right: 4, top: 1, bottom: 1 }).borderRadius(4).margin({ right: 4 })
}, (tag: string) => tag)
}
.margin({ top: 4 })
Button() {
Text('预约').fontSize(11).fontColor('#FFFFFF')
}
.width('100%').height(26).margin({ top: 6 })
.backgroundColor(game.color)
.borderRadius(13)
.onClick(() => { this.onBook(game.id) })
}
.padding(8)
}
.width(160).margin({ right: 12 })
.backgroundColor('#FFFFFF')
.borderRadius(12)
.border({ width: 1, color: '#F0F0F0' })
}, (game: BoardGame79) => game.id.toString())
}
.padding({ left: 16, right: 16 })
}
.scrollable(ScrollDirection.Horizontal)
.width('100%').margin({ top: 8 })
// 双列游戏列表
Text('全部游戏').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ left: 16, top: 16 })
Grid() {
ForEach(BOARD_GAMES_79, (game: BoardGame79) => {
GridItem() {
Column() {
Column() {
Text('🎲').fontSize(28)
}
.width('100%').height(64)
.backgroundColor(game.color + '20')
.borderRadius({ topLeft: 12, topRight: 12 })
.justifyContent(FlexAlign.Center)
Column() {
Text(game.name).fontSize(12).fontColor('#333333').fontWeight(FontWeight.Bold).maxLines(1)
Text(game.category).fontSize(9).fontColor('#AAAAAA').margin({ top: 2 })
Text(game.desc).fontSize(9).fontColor('#999999').margin({ top: 4 }).maxLines(2)
Row() {
Text('难度').fontSize(9).fontColor('#AAAAAA')
ForEach([0, 1, 2, 3, 4], (idx: number) => {
Text(idx < game.difficulty ? '●' : '○').fontSize(8).fontColor(idx < game.difficulty ? game.color : '#E0E0E0').margin({ left: 2 })
}, (idx: number) => idx.toString())
}
.margin({ top: 4 })
Row() {
Text('★').fontSize(10).fontColor('#FFB300')
Text(game.rating.toString()).fontSize(10).fontColor('#FFB300').margin({ left: 2 })
Text(game.plays + '场').fontSize(9).fontColor('#AAAAAA').margin({ left: 8 })
Text('').layoutWeight(1)
Text(game.players).fontSize(9).fontColor('#AAAAAA')
}
.margin({ top: 4 })
Button() {
Text('拼桌').fontSize(11).fontColor('#FFFFFF')
}
.width('100%').height(26).margin({ top: 6 })
.backgroundColor(game.color)
.borderRadius(13)
.onClick(() => { this.onBook(game.id) })
}
.padding(8)
}
.backgroundColor('#FFFFFF')
.borderRadius(12)
.border({ width: 1, color: '#F0F0F0' })
}
}, (game: BoardGame79) => game.id.toString())
}
.columnsTemplate('1fr 1fr')
.rowsGap(12).columnsGap(12)
.padding(16)
}
.width('100%')
}
.scrollable(ScrollDirection.Vertical)
.width('100%').height('100%')
.backgroundColor('#F3E5F5')
}
}
// ============ 拼桌Tab ============
@Component
struct PlayTableTab79 {
onBook: (id: number) => void = () => {}
build() {
Scroll() {
Column() {
Text('附近拼桌').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ left: 16, top: 12 })
Column() {
ForEach(PLAY_TABLES_79, (table: PlayTable79) => {
Column() {
Row() {
Column() {
Text('🎲').fontSize(24)
}
.width(48).height(48).borderRadius(12)
.backgroundColor(table.color + '20')
.justifyContent(FlexAlign.Center)
Column() {
Row() {
Text(table.game).fontSize(14).fontColor('#333333').fontWeight(FontWeight.Bold)
Text(table.level).fontSize(9).fontColor(table.color).backgroundColor(table.color + '15').padding({ left: 4, right: 4, top: 1, bottom: 1 }).borderRadius(4).margin({ left: 8 })
}
Text(table.store + ' · ' + table.distance).fontSize(10).fontColor('#AAAAAA').margin({ top: 2 })
Row() {
Text('📅').fontSize(10)
Text(table.date + ' ' + table.time).fontSize(10).fontColor('#666666').margin({ left: 2 })
Text('💰').fontSize(10).margin({ left: 12 })
Text('¥' + table.fee).fontSize(10).fontColor('#FF6F00').margin({ left: 2 })
}
.margin({ top: 4 })
Text('主持:' + table.host).fontSize(10).fontColor('#AAAAAA').margin({ top: 2 })
}
.margin({ left: 10 })
.layoutWeight(1)
}
.width('100%')
// 拼桌进度
Column() {
Row() {
Text('已报名').fontSize(10).fontColor('#666666')
Text(table.currentPlayers + '/' + table.maxPlayers + '人').fontSize(10).fontColor(table.color).fontWeight(FontWeight.Medium).margin({ left: 4 })
Text('').layoutWeight(1)
Text(table.status).fontSize(10).fontColor(table.status === '报名中' ? '#4CAF50' : '#FF5252')
}
.width('100%')
Row() {
Column() {
}
.layoutWeight(table.currentPlayers).height(6)
.backgroundColor(table.color)
.borderRadius({ topLeft: 3, bottomLeft: 3 })
Column() {
}
.layoutWeight(table.maxPlayers - table.currentPlayers).height(6)
.backgroundColor('#E0E0E0')
.borderRadius({ topRight: 3, bottomRight: 3 })
}
.width('100%').margin({ top: 4 })
}
.width('100%').margin({ top: 8 })
Row() {
Button() {
Text(table.status === '已满员' ? '已满' : '立即报名').fontSize(12).fontColor('#FFFFFF')
}
.height(32)
.backgroundColor(table.status === '已满员' ? '#BDBDBD' : table.color)
.borderRadius(16)
.onClick(() => { if (table.status !== '已满员') { this.onBook(table.id) } })
Button() {
Text('详情').fontSize(12).fontColor(table.color)
}
.height(32).margin({ left: 8 })
.backgroundColor(table.color + '15')
.borderRadius(16)
}
.margin({ top: 8 })
}
.width('100%').padding(12).margin({ top: 8, left: 16, right: 16 })
.backgroundColor('#FFFFFF')
.borderRadius(12)
.border({ width: 1, color: '#F0F0F0' })
}, (table: PlayTable79) => table.id.toString())
}
.width('100%')
}
.width('100%')
}
.scrollable(ScrollDirection.Vertical)
.width('100%').height('100%')
.backgroundColor('#F3E5F5')
}
}
// ============ 排行Tab ============
@Component
struct RankTab79 {
build() {
Scroll() {
Column() {
Text('积分排行').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ left: 16, top: 12 })
// 柱状图
Text('近8周积分趋势').fontSize(14).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ left: 16, top: 12 })
Column() {
Row() {
ForEach(BAR_DATA_RANK_79, (bar: BarData79) => {
Column() {
Text(bar.value.toString()).fontSize(8).fontColor('#999999')
Column() {
}
.width(18).height(bar.value / 15)
.backgroundColor(bar.color)
.borderRadius({ topLeft: 3, topRight: 3 })
Text(bar.label).fontSize(8).fontColor('#999999').margin({ top: 4 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
}, (bar: BarData79) => bar.label)
}
.width('100%').height(120)
.padding({ top: 8, bottom: 8 })
.alignItems(VerticalAlign.Bottom)
}
.width('100%').margin({ top: 8, left: 16, right: 16 })
.backgroundColor('#FFFFFF')
.borderRadius(12)
.padding(12)
// 周冠军展示
Row() {
Column() {
Text('👑').fontSize(40)
}
.width(64).height(64).borderRadius(32)
.backgroundColor('#FFD700')
.justifyContent(FlexAlign.Center)
Column() {
Text('本周冠军').fontSize(12).fontColor('#AAAAAA')
Text('棋王降临').fontSize(16).fontColor('#333333').fontWeight(FontWeight.Bold).margin({ top: 2 })
Text('积分 9850 · 胜率 78%').fontSize(11).fontColor('#FF6F00').margin({ top: 2 })
}
.margin({ left: 12 })
.layoutWeight(1)
Text('🏆').fontSize(32)
}
.width('100%').padding(16).margin({ top: 12, left: 16, right: 16 })
.linearGradient({ angle: 90, colors: [['#FFF8E1', 0], ['#FFFDE7', 1]] })
.borderRadius(16)
.border({ width: 2, color: '#FFD700' })
// 玩家排名列表
Text('玩家排行').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ left: 16, top: 16 })
Column() {
ForEach(RANK_PLAYERS_79, (player: RankPlayer79) => {
Row() {
Row() {
if (player.badge.length > 0) {
Text(player.badge).fontSize(18)
} else {
Text(player.rank.toString()).fontSize(16).fontColor('#999999').fontWeight(FontWeight.Bold)
}
}
.width(36).height(36).justifyContent(FlexAlign.Center)
Column() {
Text('🎮').fontSize(20)
}
.width(40).height(40).borderRadius(20)
.backgroundColor(player.avatarColor + '20')
.justifyContent(FlexAlign.Center)
Column() {
Text(player.name).fontSize(13).fontColor('#333333').fontWeight(FontWeight.Medium)
Text(player.level).fontSize(10).fontColor(player.avatarColor).margin({ top: 2 })
}
.margin({ left: 8 })
.layoutWeight(1)
Column() {
Text(player.score.toString()).fontSize(14).fontColor('#FF6F00').fontWeight(FontWeight.Bold)
Text('胜率' + player.winRate + '%').fontSize(9).fontColor('#AAAAAA').margin({ top: 2 })
}
.alignItems(HorizontalAlign.End)
}
.width('100%').padding(10).margin({ top: 6, left: 16, right: 16 })
.backgroundColor('#FFFFFF')
.borderRadius(10)
.border({ width: 1, color: '#F0F0F0' })
}, (player: RankPlayer79) => player.id.toString())
}
.width('100%')
}
.width('100%')
}
.scrollable(ScrollDirection.Vertical)
.width('100%').height('100%')
.backgroundColor('#F3E5F5')
}
}
// ============ 战队Tab ============
@Component
struct TeamTab79 {
build() {
Scroll() {
Column() {
Text('桌游战队').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ left: 16, top: 12 })
// 战队列表
Column() {
ForEach(TEAMS_79, (team: TeamItem79) => {
Column() {
Row() {
Column() {
Text('🛡️').fontSize(28)
}
.width(48).height(48).borderRadius(12)
.backgroundColor(team.color + '20')
.justifyContent(FlexAlign.Center)
Column() {
Text(team.name).fontSize(14).fontColor('#333333').fontWeight(FontWeight.Bold)
Text(team.level + ' · ' + team.members + '人').fontSize(10).fontColor('#AAAAAA').margin({ top: 2 })
Text('队长:' + team.captain).fontSize(10).fontColor('#AAAAAA').margin({ top: 2 })
}
.margin({ left: 10 })
.layoutWeight(1)
Column() {
Text(team.winRate + '%').fontSize(16).fontColor(team.color).fontWeight(FontWeight.Bold)
Text('胜率').fontSize(9).fontColor('#AAAAAA')
}
.alignItems(HorizontalAlign.End)
}
.width('100%')
Text(team.desc).fontSize(11).fontColor('#999999').margin({ top: 8 })
Row() {
Text('胜').fontSize(10).fontColor('#4CAF50')
Text(team.wins.toString()).fontSize(10).fontColor('#4CAF50').margin({ left: 2 })
Text('负').fontSize(10).fontColor('#FF5252').margin({ left: 12 })
Text(team.losses.toString()).fontSize(10).fontColor('#FF5252').margin({ left: 2 })
Text('总场').fontSize(10).fontColor('#AAAAAA').margin({ left: 12 })
Text((team.wins + team.losses).toString()).fontSize(10).fontColor('#AAAAAA').margin({ left: 2 })
Text('').layoutWeight(1)
Button() { Text('查看').fontSize(11).fontColor(team.color) }
.height(26).backgroundColor(team.color + '15').borderRadius(13)
}
.width('100%').margin({ top: 8 })
}
.width('100%').padding(12).margin({ top: 8, left: 16, right: 16 })
.backgroundColor('#FFFFFF')
.borderRadius(12)
.border({ width: 1, color: '#F0F0F0' })
}, (team: TeamItem79) => team.id.toString())
}
.width('100%')
// 战队招募
Text('战队招募').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ left: 16, top: 16 })
Column() {
ForEach(TEAM_RECRUITS_79, (recruit: TeamRecruit79) => {
Row() {
Column() {
Text('📢').fontSize(20)
}
.width(36).height(36).borderRadius(18)
.backgroundColor(recruit.color + '20')
.justifyContent(FlexAlign.Center)
Column() {
Row() {
Text(recruit.team).fontSize(12).fontColor('#333333').fontWeight(FontWeight.Medium)
Text(recruit.role).fontSize(10).fontColor(recruit.color).backgroundColor(recruit.color + '15').padding({ left: 4, right: 4, top: 1, bottom: 1 }).borderRadius(4).margin({ left: 8 })
}
Text(recruit.requirement).fontSize(10).fontColor('#AAAAAA').margin({ top: 4 })
Row() {
Text('等级要求:' + recruit.level).fontSize(9).fontColor('#AAAAAA')
Text('发布:' + recruit.date).fontSize(9).fontColor('#AAAAAA').margin({ left: 12 })
Text('').layoutWeight(1)
Button() { Text('申请').fontSize(10).fontColor('#FFFFFF') }
.height(24).backgroundColor(recruit.color).borderRadius(12)
}
.width('100%').margin({ top: 4 })
}
.margin({ left: 8 })
.layoutWeight(1)
}
.width('100%').padding(10).margin({ top: 6, left: 16, right: 16 })
.backgroundColor('#FFFFFF')
.borderRadius(10)
.border({ width: 1, color: '#F0F0F0' })
}, (recruit: TeamRecruit79) => recruit.id.toString())
}
.width('100%')
}
.width('100%')
}
.scrollable(ScrollDirection.Vertical)
.width('100%').height('100%')
.backgroundColor('#F3E5F5')
}
}
// ============ 社区Tab ============
@Component
struct CommunityTab79 {
build() {
Scroll() {
Column() {
Column() {
Text('桌游社区').fontSize(18).fontWeight(FontWeight.Bold).fontColor('#333333')
Text('分享攻略,寻找桌友').fontSize(12).fontColor('#999999').margin({ top: 4 })
}
.width('100%').padding(16)
.alignItems(HorizontalAlign.Center)
Text('热门话题').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ left: 16 })
Scroll() {
Row() {
ForEach(TOPICS_79, (topic: TopicItem79) => {
Column() {
Row() {
if (topic.hot) {
Text('HOT').fontSize(8).fontColor('#FFFFFF').backgroundColor('#FF5252').padding({ left: 4, right: 4, top: 1, bottom: 1 }).borderRadius(4)
}
Text(topic.title).fontSize(12).fontColor('#333333').fontWeight(FontWeight.Medium).margin({ left: topic.hot ? 4 : 0 })
}
Text(topic.posts + '人参与').fontSize(9).fontColor('#AAAAAA').margin({ top: 4 })
}
.padding(12).margin({ right: 8 })
.backgroundColor('#FFFFFF')
.borderRadius(10)
.border({ width: 1, color: topic.color + '30' })
}, (topic: TopicItem79) => topic.id.toString())
}
.padding({ left: 16, right: 16 })
}
.scrollable(ScrollDirection.Horizontal)
.width('100%').margin({ top: 8 })
Text('最新动态').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ left: 16, top: 16 })
Column() {
ForEach(POSTS_79, (post: CommunityPost79) => {
Column() {
Row() {
Column() {
Text('🎲').fontSize(20)
}
.width(36).height(36).borderRadius(18)
.backgroundColor(post.avatarColor + '20')
.justifyContent(FlexAlign.Center)
Column() {
Text(post.author).fontSize(13).fontColor('#333333').fontWeight(FontWeight.Medium)
Text(post.game + ' · ' + post.timeAgo).fontSize(10).fontColor('#AAAAAA').margin({ top: 2 })
}
.margin({ left: 8 })
.layoutWeight(1)
Text(post.topic).fontSize(10).fontColor('#4A148C').backgroundColor('#F3E5F5').padding({ left: 6, right: 6, top: 2, bottom: 2 }).borderRadius(4)
}
.width('100%')
Text(post.content).fontSize(12).fontColor('#333333').margin({ top: 10 })
if (post.images > 0) {
Row() {
ForEach([0, 1, 2], (idx: number) => {
if (idx < post.images) {
Column() {
Text('🖼️').fontSize(18)
}
.width(72).height(72).margin({ right: 8, top: 8 })
.backgroundColor('#F5F5F5')
.borderRadius(8)
.justifyContent(FlexAlign.Center)
}
}, (idx: number) => idx.toString())
}
.width('100%')
}
Row() {
Row() {
Text('❤').fontSize(14).fontColor('#E91E63')
Text(post.likes.toString()).fontSize(11).fontColor('#999999').margin({ left: 4 })
}
Row() {
Text('💬').fontSize(14).fontColor('#999999')
Text(post.comments.toString()).fontSize(11).fontColor('#999999').margin({ left: 4 })
}
.margin({ left: 24 })
Row() {
Text('📤').fontSize(14).fontColor('#999999')
Text(post.shares.toString()).fontSize(11).fontColor('#999999').margin({ left: 4 })
}
.margin({ left: 24 })
Text('').layoutWeight(1)
Text('关注').fontSize(11).fontColor('#4A148C')
}
.width('100%').margin({ top: 12 })
}
.width('100%').padding(14).margin({ top: 8, left: 16, right: 16 })
.backgroundColor('#FFFFFF')
.borderRadius(12)
.border({ width: 1, color: '#F0F0F0' })
}, (post: CommunityPost79) => post.id.toString())
}
.width('100%')
}
.width('100%')
}
.scrollable(ScrollDirection.Vertical)
.width('100%').height('100%')
.backgroundColor('#F3E5F5')
}
}
// ============ 我的Tab ============
@Component
struct ProfileTab79 {
onEditPlayer: () => void = () => {}
onCancelOrder: (id: number) => void = () => {}
onDeleteFav: (id: number) => void = () => {}
build() {
Scroll() {
Column() {
// 渐变头部
Column() {
Row() {
Column() {
Text('🎲').fontSize(40)
}
.width(64).height(64).borderRadius(32)
.backgroundColor('rgba(255,255,255,0.3)')
.justifyContent(FlexAlign.Center)
Column() {
Text('桌游玩家').fontSize(18).fontColor('#FFFFFF').fontWeight(FontWeight.Bold)
Text('ID: BG20260824').fontSize(11).fontColor('rgba(255,255,255,0.8)').margin({ top: 4 })
Row() {
Text('铂金II').fontSize(10).fontColor('#FFFFFF').backgroundColor('rgba(255,255,255,0.25)').padding({ left: 6, right: 6, top: 1, bottom: 1 }).borderRadius(4)
Text('积分 ' + PLAYER_STAT_79.rankScore).fontSize(10).fontColor('rgba(255,255,255,0.8)').margin({ left: 8 })
}
.margin({ top: 4 })
}
.margin({ left: 12 })
.layoutWeight(1)
Text('编辑').fontSize(11).fontColor('#FFFFFF').backgroundColor('rgba(255,255,255,0.25)').padding({ left: 8, right: 8, top: 4, bottom: 4 }).borderRadius(12)
.onClick(() => { this.onEditPlayer() })
}
.width('100%').padding(16)
}
.width('100%')
.linearGradient({ angle: 135, colors: [['#4A148C', 0], ['#311B92', 1]] })
// 玩家统计
Text('玩家统计').fontSize(14).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ left: 16, top: 16 })
Row() {
Column() {
Text(PLAYER_STAT_79.totalGames.toString()).fontSize(20).fontColor('#4A148C').fontWeight(FontWeight.Bold)
Text('总场次').fontSize(10).fontColor('#AAAAAA').margin({ top: 2 })
}
.layoutWeight(1).alignItems(HorizontalAlign.Center)
Column() {
Text(PLAYER_STAT_79.winRate + '%').fontSize(20).fontColor('#FF6F00').fontWeight(FontWeight.Bold)
Text('胜率').fontSize(10).fontColor('#AAAAAA').margin({ top: 2 })
}
.layoutWeight(1).alignItems(HorizontalAlign.Center)
Column() {
Text(PLAYER_STAT_79.bestStreak.toString()).fontSize(20).fontColor('#4CAF50').fontWeight(FontWeight.Bold)
Text('最佳连胜').fontSize(10).fontColor('#AAAAAA').margin({ top: 2 })
}
.layoutWeight(1).alignItems(HorizontalAlign.Center)
}
.width('100%').padding(12).margin({ top: 8, left: 16, right: 16 })
.backgroundColor('#FFFFFF')
.borderRadius(12)
Row() {
Text('最爱游戏:' + PLAYER_STAT_79.favoriteGame).fontSize(12).fontColor('#666666').margin({ left: 16, top: 8 })
Text('').layoutWeight(1)
}
.width('100%')
// 成就徽章
Text('成就徽章').fontSize(14).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ left: 16, top: 12 })
Grid() {
ForEach(ACHIEVEMENTS_79, (ach: Achievement79) => {
GridItem() {
Column() {
Text(ach.icon).fontSize(28).opacity(ach.unlocked ? 1 : 0.3)
Text(ach.name).fontSize(10).fontColor(ach.unlocked ? '#333333' : '#CCCCCC').margin({ top: 4 })
Text(ach.desc).fontSize(8).fontColor('#AAAAAA').margin({ top: 2 }).maxLines(1)
Text(ach.unlocked ? '已获得' : '未解锁').fontSize(8).fontColor(ach.unlocked ? ach.color : '#CCCCCC').margin({ top: 2 })
}
.width('100%').padding(8)
.backgroundColor(ach.unlocked ? ach.color + '10' : '#F5F5F5')
.borderRadius(10)
.alignItems(HorizontalAlign.Center)
}
}, (ach: Achievement79) => ach.id.toString())
}
.columnsTemplate('3fr 3fr 3fr')
.rowsGap(8).columnsGap(8)
.padding(16)
// 订单列表
Row() {
Text('我的订单').fontSize(14).fontWeight(FontWeight.Bold).fontColor('#333333')
Text('').layoutWeight(1)
Text('全部 >').fontSize(11).fontColor('#999999')
}
.width('100%').padding({ left: 16, right: 16 })
Column() {
ForEach(ORDERS_79, (order: OrderItem79) => {
Column() {
Row() {
Text(order.service).fontSize(13).fontColor('#333333').fontWeight(FontWeight.Medium)
Text('').layoutWeight(1)
Text(order.status).fontSize(11).fontColor(order.color)
}
.width('100%')
Row() {
Text(order.store + ' · ' + order.date).fontSize(10).fontColor('#AAAAAA')
Text('').layoutWeight(1)
Text('¥' + order.amount).fontSize(14).fontColor('#FF6F00').fontWeight(FontWeight.Bold)
}
.width('100%').margin({ top: 4 })
Row() {
Text('').layoutWeight(1)
if (order.status === '待开始' || order.status === '已报名') {
Text('取消').fontSize(10).fontColor('#FF5252').onClick(() => { this.onCancelOrder(order.id) })
}
}
.width('100%').margin({ top: 4 })
}
.width('100%').padding(12).margin({ top: 8, left: 16, right: 16 })
.backgroundColor('#FFFFFF')
.borderRadius(10)
.border({ width: 1, color: '#F0F0F0' })
}, (order: OrderItem79) => order.id.toString())
}
.width('100%')
// 收藏列表
Row() {
Text('我的收藏').fontSize(14).fontWeight(FontWeight.Bold).fontColor('#333333')
Text('').layoutWeight(1)
Text('管理 >').fontSize(11).fontColor('#999999')
}
.width('100%').padding({ left: 16, right: 16, top: 16 })
Column() {
ForEach(FAVORITES_79, (fav: FavoriteItem79) => {
Row() {
Column() {
Text('⭐').fontSize(20)
}
.width(40).height(40).borderRadius(8)
.backgroundColor(fav.color + '15')
.justifyContent(FlexAlign.Center)
Column() {
Text(fav.name).fontSize(13).fontColor('#333333')
Text(fav.type).fontSize(10).fontColor('#AAAAAA').margin({ top: 2 })
}
.margin({ left: 8 })
.layoutWeight(1)
Text('删除').fontSize(11).fontColor('#FF5252').onClick(() => { this.onDeleteFav(fav.id) })
}
.width('100%').padding(12).margin({ top: 6, left: 16, right: 16 })
.backgroundColor('#FFFFFF')
.borderRadius(10)
.border({ width: 1, color: '#F0F0F0' })
}, (fav: FavoriteItem79) => fav.id.toString())
}
.width('100%')
}
.width('100%')
}
.scrollable(ScrollDirection.Vertical)
.width('100%').height('100%')
.backgroundColor('#F3E5F5')
}
}
总结

本文对基于HarmonyOS ArkTS声明式UI范式的多多桌游聚玩应用进行了全面而深入的技术解析。该应用以六大业务Tab为核心架构,通过游戏库、拼桌、排行、战队、社区与个人中心的模块化设计,构建了一套覆盖桌游社交全场景的移动应用平台。应用充分利用了ArkTS的类型系统优势,通过interface定义了十四种数据类型,实现了从桌游信息到拼桌活动、从排名玩家到成就徽章、从战队信息到玩家统计的完整数据模型。在静态数据初始化方面,常量数组提供了涵盖八大游戏分类、十款桌游、八条拼桌活动、八位排名玩家、六支战队、五条招募信息、九个成就徽章等丰富的业务数据,确保了应用在离线状态下的完整功能展示。
从交互设计的角度来看,该应用展现了社交娱乐场景下的创新UI实践。拼桌进度条通过两个Column组件的layoutWeight比例分配实现了报名进度的可视化,这种实现方式简洁高效且无需额外组件支持。五级难度指示器通过ForEach遍历与条件判断渲染实心圆和空心圆,直观地传达了桌游的复杂程度。排行Tab的金色渐变冠军卡通过linearGradient与金色边框的组合,营造出荣誉感与仪式感。成就徽章的解锁状态通过opacity、背景色与文字色彩的三重切换实现了清晰的可视化反馈。这些视觉创新都体现了ArkTS声明式UI在数据驱动渲染方面的灵活性与表现力。
从interface类型定义到常量数据初始化,从主入口组件的Tab路由到各业务Tab的差异化布局,从拼桌预约的流程交互到成就系统的状态可视化,每一个技术细节都体现了ArkTS声明式UI的开发优势。应用的游戏库、拼桌、排行、战队、社区与个人中心六大模块,虽然各自聚焦不同的业务场景,但在代码结构、视觉风格与交互模式上保持了高度的一致性。难度指示器、进度条、柱状图与成就网格等视觉组件的实现方式,为HarmonyOS ArkTS开发者提供了丰富的参考案例。对于希望学习HarmonyOS社交娱乐应用开发的开发者而言,该应用的组件拆分策略、状态管理模式与数据可视化技术都具有重要的实践指导意义。
更多推荐




所有评论(0)