基于HarmonyOS ArkTS API 24使用SheetSize.LARGE设定面板尺寸,并通过detents属性定义可拖拽的尺寸档位
引言

集换式卡牌游戏(TCG,Trading Card Game)作为数字娱乐产业的重要分支,融合了收藏、策略对战和社交交易三大核心玩法。在HarmonyOS生态中,如何利用ArkTS声明式UI框架构建一个完整的TCG应用,是一个兼具技术挑战与业务复杂度的课题。本文将深入剖析一个名为"多多卡牌"的HarmonyOS ArkTS应用,该应用以清新潮玩青绿调为设计主题,采用潮玩青(#00838F)与稀有金(#FFB300)的双色搭配方案,底色使用淡青绿(#E0F2F1),构建出一个集卡包购买、集换交易、竞技对战、图鉴收集、社区交流和个人管理于一体的卡牌生态平台。
从技术架构角度来看,该应用基于HarmonyOS ArkTS声明式开发范式,通过@Entry和@Component装饰器构建组件化体系。整个应用由主入口组件DuoDuoCardGameApp和六个Tab子组件构成,分别对应卡包、集换、竞技、图鉴、社区和我的六大业务模块。主组件通过@State装饰器管理当前Tab索引、弹窗显示状态、选中卡包ID、购买数量、开包方式等十余项响应式状态,同时通过@Builder装饰器定义了购买弹窗、取消交易弹窗、编辑卡组弹窗和删除收藏确认弹窗四个自定义构建器。
在业务设计层面,该应用构建了一套完整的TCG生命周期管理。卡包购买支持邮寄到家、线上开包和门店自提三种方式,集换市场提供按稀有度筛选的交易列表和买卖记录追踪,竞技模块包含积分趋势柱状图、赛事报名进度条和对战历史记录,图鉴模块实现了按稀有度分级的收藏进度统计和三列网格展示。此外,卡组管理系统支持卡组名称、核心卡牌和战术备注的编辑维护,为竞技对战提供策略支持。整个应用的数据模型定义了十五个接口类型,覆盖了从卡包、交易、赛事到社区、订单、收藏的全部业务实体。
一、类型定义体系:构建卡牌世界数据模型

ArkTS的接口(interface)机制为应用提供了强类型的数据约束。该应用定义了十五个接口类型,构成了完整的TCG业务数据模型。每个接口都以数字后缀"81"结尾,实现了跨文件的命名空间隔离。
interface CardPack81 {
id: number
name: string
series: string
price: number
originalPrice: number
cards: number
rarity: string
stock: number
rating: number
sales: number
tags: string[]
color: string
releaseDate: string
desc: string
}
interface TradeItem81 {
id: number
cardName: string
rarity: string
seller: string
price: number
condition: string
edition: string
status: string
color: string
desc: string
}
interface Tournament81 {
id: number
name: string
format: string
date: string
venue: string
participants: number
maxParticipants: number
prize: string
level: string
status: string
color: string
desc: string
}
interface CollectionCard81 {
id: number
name: string
rarity: string
series: string
owned: boolean
count: number
color: string
power: number
}
interface RarityStat81 {
rarity: string
total: number
owned: number
color: string
}
CardPack81定义了卡包实体结构,包含名称、系列、价格、原价、卡片数量、稀有度保底信息、库存、评分、销量、标签数组、主题色、发布日期和描述等字段。其中rarity字段存储保底规则(如"SR以上保底"、“SSR保底”),直接决定卡包的价值定位。TradeItem81定义了集换市场中的单卡交易信息,包含卡名、稀有度、卖家、价格、品相、版本、状态等字段,status字段区分"出售中"和"已预订"两种交易状态。
Tournament81定义了赛事信息,特别值得注意的是participants和maxParticipants两个字段,前端利用这两个数值的比例渲染报名进度条。CollectionCard81定义了图鉴中的卡牌信息,owned布尔字段标识是否拥有,count字段记录拥有数量,power字段存储攻击力数值。RarityStat81定义了稀有度统计信息,total和owned的比值即为收藏进度。
二、稀有度体系与静态数据层

TCG应用的核心在于稀有度体系的设计。该应用定义了从UR到R共六个稀有度等级,每个等级对应不同的色彩标识和获取难度。
const CARD_PACKS_81: CardPack81[] = [
{ id: 1, name: '星海觉醒·第一弹', series: '星海觉醒', price: 35, originalPrice: 48, cards: 8, rarity: 'SR以上保底', stock: 3562, rating: 4.9, sales: 12543, tags: ['热销', '新品'], color: '#00838F', releaseDate: '08-20', desc: '每包含8张卡,SR以上保底1张' },
{ id: 2, name: '龙之试炼·典藏包', series: '龙之试炼', price: 88, originalPrice: 128, cards: 15, rarity: 'SSR保底', stock: 854, rating: 5.0, sales: 6234, tags: ['典藏', 'SSR'], color: '#FFB300', releaseDate: '08-15', desc: '含15张卡,SSR保底1张+闪卡1张' },
{ id: 5, name: '神域降临·典藏包', series: '神域降临', price: 128, originalPrice: 188, cards: 20, rarity: 'SSR+UR保底', stock: 156, rating: 5.0, sales: 2876, tags: ['典藏', 'UR'], color: '#6A1B9A', releaseDate: '08-05', desc: '含20张卡,SSR+UR保底+全闪卡' }
]
const RARITY_STATS_81: RarityStat81[] = [
{ rarity: 'UR', total: 12, owned: 3, color: '#6A1B9A' },
{ rarity: 'SSR闪', total: 24, owned: 8, color: '#FFB300' },
{ rarity: 'SSR', total: 48, owned: 12, color: '#C2185B' },
{ rarity: 'SR闪', total: 96, owned: 35, color: '#FF6F00' },
{ rarity: 'SR', total: 192, owned: 87, color: '#00838F' },
{ rarity: 'R', total: 384, owned: 256, color: '#2E7D32' }
]
const COLLECTION_CARDS_81: CollectionCard81[] = [
{ id: 1, name: '星海之龙', rarity: 'UR', series: '星海觉醒', owned: true, count: 2, color: '#00838F', power: 9500 },
{ id: 2, name: '龙之帝王', rarity: 'UR', series: '龙之试炼', owned: true, count: 1, color: '#FFB300', power: 9800 },
{ id: 7, name: '龙之公主', rarity: 'UR', series: '龙之试炼', owned: false, count: 0, color: '#FFB300', power: 9600 },
{ id: 12, name: '神域之主', rarity: 'UR', series: '神域降临', owned: false, count: 0, color: '#6A1B9A', power: 9900 }
]
const BATTLE_RECORDS_81: BattleRecord81[] = [
{ id: 1, opponent: '棋王降临', result: '胜利', deck: '星海觉醒·龙骑', date: '08-22', turn: 8, duration: '25分钟', color: '#4CAF50' },
{ id: 2, opponent: '卡牌大师', result: '失败', deck: '机械纪元·战神', date: '08-20', turn: 12, duration: '35分钟', color: '#FF5252' },
{ id: 3, opponent: '幻兽猎人', result: '胜利', deck: '幻兽图鉴·白虎', date: '08-18', turn: 6, duration: '18分钟', color: '#4CAF50' }
]
CARD_PACKS_81数组包含十组卡包数据,覆盖了从18元新手包到188元大礼包的完整价格梯度。每个卡包的rarity字段定义了保底规则——从最低的"R保底"到最高的"SSR+UR保底",保底规则越高级,卡包价格越高。color字段为每个卡包分配了独立的主色调,从潮玩青到稀有金再到深紫,色彩与稀有度形成视觉映射。
RARITY_STATS_81数组是图鉴收藏进度的核心数据源。UR(Ultra Rare)总量仅12张、已拥有3张,是最高稀有度;R(Rare)总量384张、已拥有256张,是基础稀有度。每个稀有度等级使用不同的主题色——UR用深紫、SSR闪用金色、SSR用粉红、SR闪用橙色、SR用潮玩青、R用绿色,形成从稀有到常见的色彩光谱。
COLLECTION_CARDS_81数组中,owned字段为false的卡牌(如龙之公主、神域之主)在图鉴网格中以半透明灰色显示,标识"未拥有"状态。power字段存储的攻击力数值从6200到9900不等,为卡组构筑提供策略参考。
三、主入口组件:状态管理与Tab路由

主入口组件DuoDuoCardGameApp是整个应用的调度中枢,通过十四个@State变量管理全局状态。
@Entry
@Component
struct DuoDuoCardGameApp {
@State currentTab: number = 0
@State showBuyDialog: boolean = false
@State showCancelDialog: boolean = false
@State showEditDeckDialog: boolean = false
@State showDeleteFavDialog: boolean = false
@State selectedPackId: number = 1
@State buyQuantity: number = 1
@State openMethod: number = 0
@State buyNotes: string = ''
@State editDeckName: string = ''
@State editDeckCore: string = ''
@State editDeckStrategy: string = ''
@State cancelTargetId: number = 0
private tabs: string[] = ['卡包', '集换', '竞技', '图鉴', '社区', '我的']
private openMethods: string[] = ['邮寄到家', '线上开包', '门店自提+开包']
build() {
Column() {
// 顶部头部
Column() {
Row() {
Column() {
Text('多多卡牌')
.fontSize(22)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Bold)
Text('TCG·集换·竞技精选')
.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)
}
.width('100%')
.linearGradient({ angle: 135, colors: [['#00838F', 0], ['#006064', 1]] })
.padding({ top: 8, bottom: 12, left: 16, right: 16 })
// Tab内容区
Stack({ alignContent: Alignment.TopStart }) {
Column() {
if (this.currentTab === 0) {
PackTab81({
onBuy: (id: number) => {
this.selectedPackId = id
this.showBuyDialog = true
}
})
}
if (this.currentTab === 1) {
TradeTab81({
onTrade: (id: number) => {
this.cancelTargetId = id
this.showCancelDialog = true
}
})
}
if (this.currentTab === 2) {
CompetitiveTab81()
}
if (this.currentTab === 3) {
CollectionTab81()
}
if (this.currentTab === 4) {
CommunityTab81()
}
if (this.currentTab === 5) {
ProfileTab81({
onEditDeck: () => {
this.showEditDeckDialog = 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 ? '#00838F' : '#999999')
Text(tab)
.fontSize(10)
.fontColor(this.currentTab === idx ? '#00838F' : '#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('#E0F2F1')
.bindContentCover(this.showCancelDialog, this.buildCancelCover())
.bindContentCover(this.showDeleteFavDialog, this.buildDeleteFavCover())
}
private getTabIcon(idx: number): string {
const icons: string[] = ['📦', '🔄', '🏆', '📚', '💬', '👤']
return idx < icons.length ? icons.length ? icons[idx] : '📋' : '📋'
}
}
主组件的状态管理覆盖了弹窗控制(四个布尔状态)、选中项(selectedPackId)、数量调节(buyQuantity)、开包方式(openMethod)、表单数据(buyNotes、editDeckName、editDeckCore、editDeckStrategy)和操作目标ID(cancelTargetId)六大维度。这种集中式状态管理确保了各子Tab和弹窗之间的数据一致性。
Tab路由采用if条件渲染实现页面切换。每个Tab子组件接收回调函数作为参数——PackTab81接收onBuy回调,TradeTab81接收onTrade回调,ProfileTab81接收onEditDeck、onCancelOrder和onDeleteFav三个回调。当子组件内部触发相应操作时,通过回调函数将事件和数据传递给主组件,主组件更新状态后触发弹窗显示。这种"回调上传+状态下发"的通信模式是ArkTS组件间协作的标准范式。
顶部头部使用135度角渐变,从潮玩青#00838F过渡到深青#006064,底部Tab栏的选中色同样使用#00838F,保持了整体色彩的统一性。六个Tab图标使用Emoji字符——📦(卡包)、🔄(集换)、🏆(竞技)、📚(图鉴)、💬(社区)、👤(我的),每个图标都与其业务含义高度契合。
四、卡包Tab:新卡首发与双列展示

卡包Tab是应用的首屏,包含分类标签、新卡首发大卡和双列卡包列表三个区域,是用户购买卡包的入口。
@Component
struct PackTab81 {
onBuy: (id: number) => void = () => {}
build() {
Scroll() {
Column() {
// 分类标签
Text('卡包分类').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ left: 16, top: 12 })
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(PACK_TAGS_81, (tag: PackTag81) => {
Row() {
Text(tag.label).fontSize(12).fontColor(tag.color)
Text(tag.count + '款').fontSize(9).fontColor('#AAAAAA').margin({ left: 4 })
}
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.margin({ right: 8, top: 8 })
.backgroundColor(tag.color + '15')
.borderRadius(16)
.border({ width: 1, color: tag.color + '30' })
}, (tag: PackTag81) => tag.label)
}
.width('100%').padding({ left: 16, right: 16 })
// 新卡首发大卡
Row() {
Text('新卡首发').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#333333')
Text('').layoutWeight(1)
Text('更多 >').fontSize(12).fontColor('#00838F')
}
.width('100%').padding({ left: 16, right: 16, top: 16 })
Column() {
Row() {
Column() {
Text('星海觉醒·第一弹').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#FFFFFF')
Text('8张/包 · SR以上保底').fontSize(11).fontColor('rgba(255,255,255,0.8)').margin({ top: 4 })
Row() {
Text('¥').fontSize(12).fontColor('#FFFFFF')
Text('35').fontSize(22).fontColor('#FFFFFF').fontWeight(FontWeight.Bold)
Text('¥48').fontSize(11).fontColor('rgba(255,255,255,0.6)')
.decoration({ type: TextDecorationType.LineThrough }).margin({ left: 4 })
Text('').layoutWeight(1)
Text('08-20首发').fontSize(10).fontColor('#FFFFFF')
.backgroundColor('rgba(255,255,255,0.25)')
.padding({ left: 8, right: 8, top: 2, bottom: 2 })
.borderRadius(10)
}
.width('100%').margin({ top: 12 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
Text('🃏').fontSize(48)
}
.width('100%').padding(16)
}
.linearGradient({ angle: 135, colors: [['#00838F', 0], ['#006064', 1]] })
.borderRadius(16)
// 双列卡包
Text('全部卡包').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ left: 16, top: 16 })
Grid() {
ForEach(CARD_PACKS_81, (pack: CardPack81) => {
GridItem() {
Column() {
Column() {
Text('🃏').fontSize(32)
}
.width('100%').height(64)
.backgroundColor(pack.color + '20')
.borderRadius({ topLeft: 12, topRight: 12 })
.justifyContent(FlexAlign.Center)
Column() {
Text(pack.name).fontSize(12).fontColor('#333333').fontWeight(FontWeight.Bold).maxLines(2)
Text(pack.series).fontSize(9).fontColor('#AAAAAA').margin({ top: 2 })
Text(pack.rarity).fontSize(9).fontColor(pack.color)
.backgroundColor(pack.color + '15')
.padding({ left: 4, right: 4, top: 1, bottom: 1 })
.borderRadius(4).margin({ top: 4 })
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(pack.tags, (tag: string) => {
Text(tag).fontSize(9).fontColor(pack.color)
.backgroundColor(pack.color + '15')
.padding({ left: 4, right: 4, top: 1, bottom: 1 })
.borderRadius(4).margin({ right: 4, top: 4 })
}, (tag: string) => tag)
}
Row() {
Text(pack.cards + '张').fontSize(9).fontColor('#AAAAAA')
Text('库存' + pack.stock).fontSize(9).fontColor('#AAAAAA').margin({ left: 8 })
}.margin({ top: 4 })
Row() {
Text('★').fontSize(10).fontColor('#FFB300')
Text(pack.rating.toString()).fontSize(10).fontColor('#FFB300').margin({ left: 2 })
Text(pack.sales + '人购买').fontSize(9).fontColor('#AAAAAA').margin({ left: 8 })
}.margin({ top: 4 })
Row() {
Text('¥').fontSize(11).fontColor('#FFB300')
Text(pack.price.toString()).fontSize(15).fontColor('#FFB300').fontWeight(FontWeight.Bold)
Text('¥' + pack.originalPrice).fontSize(9).fontColor('#CCCCCC')
.decoration({ type: TextDecorationType.LineThrough }).margin({ left: 4 })
}.margin({ top: 4 })
Button() {
Text('购买').fontSize(11).fontColor('#FFFFFF')
}
.width('100%').height(26).margin({ top: 6 })
.backgroundColor(pack.color)
.borderRadius(13)
.onClick(() => { this.onBuy(pack.id) })
}
.padding(8)
}
.backgroundColor('#FFFFFF')
.borderRadius(12)
.border({ width: 1, color: '#F0F0F0' })
}
}, (pack: CardPack81) => pack.id.toString())
}
.columnsTemplate('1fr 1fr')
.rowsGap(12).columnsGap(12)
.padding(16)
}
}
.scrollable(ScrollDirection.Vertical)
.width('100%').height('100%')
.backgroundColor('#E0F2F1')
}
}
分类标签区域使用Flex({ wrap: FlexWrap.Wrap })实现自动换行布局。每个标签的背景色使用tag.color + '15'(约8%透明度),边框使用tag.color + '30'(约19%透明度),通过颜色拼接实现数据驱动的动态配色。
新卡首发大卡是一个独立的渐变卡片,使用与顶部头部相同的135度潮玩青渐变。卡内展示首发卡包的名称、保底规则、价格对比和首发日期标签。原价使用TextDecorationType.LineThrough添加删除线,与现价形成促销对比。
双列卡包列表使用Grid配合columnsTemplate('1fr 1fr')实现两列等宽布局。每个卡包卡片的信息层次丰富——从上到下依次为卡牌Emoji图标区(使用pack.color + '20'背景色)、名称系列、保底标签、业务标签、卡片数量和库存、评分和销量、价格对比和购买按钮。Flex容器内嵌的标签列表使用FlexWrap.Wrap实现自动换行,确保标签数量变化时不会溢出。
五、集换Tab:稀有度筛选与交易记录

集换Tab是卡牌交易市场的核心模块,包含稀有度筛选、在售卡牌列表和交易记录三个区域。
@Component
struct TradeTab81 {
onTrade: (id: number) => void = () => {}
build() {
Scroll() {
Column() {
// 稀有度筛选
Text('稀有度筛选').fontSize(14).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ left: 16, top: 12 })
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(['全部', 'UR', 'SSR闪卡', 'SSR', 'SR闪卡', 'SR'], (rarity: string) => {
Text(rarity).fontSize(12).fontColor('#00838F')
.padding({ left: 10, right: 10, top: 6, bottom: 6 })
.margin({ right: 8, top: 8 })
.backgroundColor('#E0F2F1')
.borderRadius(16)
}, (rarity: string) => rarity)
}
.width('100%').padding({ left: 16, right: 16 })
// 集换市场列表
Text('在售卡牌').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ left: 16, top: 16 })
Column() {
ForEach(TRADE_ITEMS_81, (item: TradeItem81) => {
Row() {
Column() {
Text('🃏').fontSize(28)
}
.width(72).height(72)
.backgroundColor(item.color + '20')
.borderRadius(12)
.justifyContent(FlexAlign.Center)
Column() {
Row() {
Text(item.cardName).fontSize(13).fontColor('#333333').fontWeight(FontWeight.Bold).maxLines(1).layoutWeight(1)
Text(item.rarity).fontSize(9).fontColor('#FFFFFF')
.backgroundColor(item.color)
.padding({ left: 4, right: 4, top: 1, bottom: 1 })
.borderRadius(4)
}
Text(item.desc).fontSize(10).fontColor('#999999').margin({ top: 2 }).maxLines(2)
Row() {
Text('品相:' + item.condition).fontSize(10).fontColor('#AAAAAA')
Text('版本:' + item.edition).fontSize(10).fontColor('#AAAAAA').margin({ left: 8 })
}.margin({ top: 4 })
Row() {
Text('卖家:' + item.seller).fontSize(10).fontColor('#AAAAAA')
Text(item.status).fontSize(10)
.fontColor(item.status === '出售中' ? '#4CAF50' : '#FF9800')
.margin({ left: 8 })
}.margin({ top: 4 })
Row() {
Text('¥').fontSize(12).fontColor(item.color)
Text(item.price.toString()).fontSize(16).fontColor(item.color).fontWeight(FontWeight.Bold)
Text('').layoutWeight(1)
Button() {
Text(item.status === '出售中' ? '买入' : '已预订').fontSize(11).fontColor('#FFFFFF')
}
.height(28)
.backgroundColor(item.status === '出售中' ? item.color : '#BDBDBD')
.borderRadius(14)
.onClick(() => { if (item.status === '出售中') { this.onTrade(item.id) } })
}
.width('100%').margin({ top: 4 })
}
.margin({ left: 10 })
.layoutWeight(1)
}
.width('100%').padding(10).margin({ top: 8, left: 16, right: 16 })
.backgroundColor('#FFFFFF')
.borderRadius(12)
.border({ width: 1, color: '#F0F0F0' })
}, (item: TradeItem81) => item.id.toString())
}
// 交易记录
Text('交易记录').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ left: 16, top: 16 })
Column() {
ForEach(TRADE_RECORDS_81, (record: TradeRecord81) => {
Row() {
Column() {
Text(record.cardName).fontSize(12).fontColor('#333333').fontWeight(FontWeight.Medium).maxLines(1)
Row() {
Text(record.type).fontSize(10).fontColor(record.color)
Text(record.date).fontSize(10).fontColor('#AAAAAA').margin({ left: 8 })
}.margin({ top: 2 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
Column() {
Text((record.type === '买入' ? '-' : '+') + '¥' + record.amount)
.fontSize(14).fontColor(record.color).fontWeight(FontWeight.Bold)
Text(record.status).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' })
}, (record: TradeRecord81) => record.id.toString())
}
}
}
.scrollable(ScrollDirection.Vertical)
.width('100%').height('100%')
.backgroundColor('#E0F2F1')
}
}
稀有度筛选区域以按钮组形式展示六个稀有度选项,所有按钮使用统一的潮玩青色调和淡青绿背景。在实际应用中,这些筛选按钮可以配合@State变量实现点击过滤逻辑,当前实现展示了静态UI结构。
在售卡牌列表项采用左右布局——左侧72x72的Emoji图标区域使用item.color + '20'背景色,右侧信息区域包含卡名和稀有度标签(稀有度标签使用主题色实色背景白字,视觉冲击力强)、描述文字、品相和版本信息、卖家和状态信息、价格和买入按钮。买入按钮的状态根据item.status动态变化——"出售中"时显示主题色背景和"买入"文字,"已预订"时显示灰色背景和"已预订"文字,并禁用点击事件。
交易记录区域展示了用户的买卖历史。每条记录的金额使用三元运算符record.type === '买入' ? '-' : '+'判断前缀符号——买入显示减号(支出),卖出显示加号(收入)。金额颜色通过record.color绑定,买入记录使用潮玩青、卖出记录使用不同色系,使收支方向一目了然。
六、竞技Tab:积分柱状图与赛事进度

竞技Tab集成了积分趋势柱状图、赛事列表和对战记录三大模块,是TCG应用竞技属性的集中体现。
@Component
struct CompetitiveTab81 {
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_81, (bar: BarData81) => {
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: BarData81) => bar.label)
}
.width('100%').height(120)
.alignItems(VerticalAlign.Bottom)
}
.backgroundColor('#FFFFFF')
.borderRadius(12)
.padding(12)
// 赛事列表
Text('赛事列表').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ left: 16, top: 16 })
Column() {
ForEach(TOURNAMENTS_81, (tourn: Tournament81) => {
Column() {
Row() {
Column() {
Text('🏆').fontSize(28)
}
.width(48).height(48).borderRadius(12)
.backgroundColor(tourn.color + '20')
Column() {
Row() {
Text(tourn.name).fontSize(13).fontColor('#333333').fontWeight(FontWeight.Bold).layoutWeight(1)
Text(tourn.level).fontSize(9).fontColor('#FFFFFF')
.backgroundColor(tourn.color)
.padding({ left: 4, right: 4, top: 1, bottom: 1 })
.borderRadius(4)
}
Text(tourn.format + ' · ' + tourn.prize).fontSize(10).fontColor('#AAAAAA').margin({ top: 2 })
Row() {
Text('📅').fontSize(10)
Text(tourn.date).fontSize(10).fontColor('#666666').margin({ left: 2 })
Text('📍').fontSize(10).margin({ left: 8 })
Text(tourn.venue).fontSize(10).fontColor('#AAAAAA').margin({ left: 2 }).maxLines(1)
}.margin({ top: 4 })
}
.margin({ left: 10 })
.layoutWeight(1)
Text(tourn.status).fontSize(10)
.fontColor(tourn.status === '报名中' ? '#4CAF50' :
(tourn.status === '即将开放' ? '#FF9800' : '#2196F3'))
}
Text(tourn.desc).fontSize(11).fontColor('#999999').margin({ top: 8 })
// 报名进度
Row() {
Text('参赛').fontSize(10).fontColor('#666666')
Text(tourn.participants + '/' + tourn.maxParticipants).fontSize(10).fontColor(tourn.color).margin({ left: 4 })
Text('').layoutWeight(1)
Row() {
Column() {}
.layoutWeight(tourn.participants).height(4)
.backgroundColor(tourn.color)
.borderRadius({ topLeft: 2, bottomLeft: 2 })
Column() {}
.layoutWeight(tourn.maxParticipants - tourn.participants).height(4)
.backgroundColor('#E0E0E0')
.borderRadius({ topRight: 2, bottomRight: 2 })
}
.width('40%')
}.margin({ top: 8 })
Button() {
Text(tourn.status === '报名中' ? '立即报名' :
(tourn.status === '即将开放' ? '预约提醒' : '查看详情'))
.fontSize(12).fontColor('#FFFFFF')
}
.width('100%').height(32).margin({ top: 8 })
.backgroundColor(tourn.color)
.borderRadius(16)
}
.backgroundColor('#FFFFFF')
.borderRadius(12)
.border({ width: 1, color: '#F0F0F0' })
}, (tourn: Tournament81) => tourn.id.toString())
}
// 对战记录
Text('对战记录').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ left: 16, top: 16 })
Column() {
ForEach(BATTLE_RECORDS_81, (battle: BattleRecord81) => {
Row() {
Column() {
Text('⚔️').fontSize(20)
}
.width(36).height(36).borderRadius(18)
.backgroundColor(battle.color + '20')
Column() {
Text('VS ' + battle.opponent).fontSize(12).fontColor('#333333').fontWeight(FontWeight.Medium)
Text(battle.deck).fontSize(10).fontColor('#AAAAAA').margin({ top: 2 })
Row() {
Text(battle.date).fontSize(9).fontColor('#AAAAAA')
Text(battle.turn + '回合').fontSize(9).fontColor('#AAAAAA').margin({ left: 8 })
Text(battle.duration).fontSize(9).fontColor('#AAAAAA').margin({ left: 8 })
}.margin({ top: 2 })
}
.margin({ left: 8 })
.layoutWeight(1)
Text(battle.result).fontSize(13).fontColor(battle.color).fontWeight(FontWeight.Bold)
}
.backgroundColor('#FFFFFF')
.borderRadius(10)
.border({ width: 1, color: '#F0F0F0' })
}, (battle: BattleRecord81) => battle.id.toString())
}
}
}
.scrollable(ScrollDirection.Vertical)
.width('100%').height('100%')
.backgroundColor('#E0F2F1')
}
}
积分趋势柱状图通过ForEach遍历八周数据,每根柱子的高度通过bar.value / 15计算得出。前四周数据使用浅青色#4DB6AC,后四周使用主色#00838F,通过颜色深浅区分近期和远期数据。父级Row设置alignItems(VerticalAlign.Bottom)确保柱子底部对齐,形成标准的柱状图视觉效果。
赛事列表中的报名进度条是该组件的技术亮点。通过两个Column组件分别使用layoutWeight(tourn.participants)和layoutWeight(tourn.maxParticipants - tourn.participants),按比例分割进度条宽度。已报名部分使用赛事主题色,未满部分使用灰色,通过borderRadius的定向圆角实现左右不同方向的圆角效果。这种利用layoutWeight实现比例进度条的技巧,在ArkTS中无需额外计算像素值即可实现动态进度展示。
赛事状态的颜色采用嵌套三元运算符:tourn.status === '报名中' ? '#4CAF50' : (tourn.status === '即将开放' ? '#FF9800' : '#2196F3'),三种状态对应绿色、橙色和蓝色。按钮文字同样使用三元运算符动态生成——“报名中"显示"立即报名”,“即将开放"显示"预约提醒”,其他状态显示"查看详情"。
对战记录列表展示每场对战的结果。battle.color字段在数据层即被定义为胜利对应绿色#4CAF50、失败对应红色#FF5252,使得结果标签和图标背景色都直接由数据驱动。每条记录还包含使用的卡组名称、对战回合数和持续时间,为用户复盘对战策略提供数据支持。
卡牌生命周期流程图
以下流程图展示了从购买卡包到竞技对战的完整卡牌生命周期:
七、图鉴Tab:收藏进度与三列网格
图鉴Tab是收藏管理的核心页面,包含按稀有度分级的收藏进度统计和三列卡牌图鉴网格两个区域。
@Component
struct CollectionTab81 {
build() {
Scroll() {
Column() {
Text('卡牌图鉴').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ left: 16, top: 12 })
// 收藏进度
Text('收藏进度').fontSize(14).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ left: 16, top: 12 })
Column() {
ForEach(RARITY_STATS_81, (stat: RarityStat81) => {
Row() {
Text(stat.rarity).fontSize(12).fontColor('#333333')
Text(stat.owned + '/' + stat.total).fontSize(11).fontColor(stat.color).margin({ left: 8 })
Text((stat.owned / stat.total * 100).toFixed(0) + '%').fontSize(10).fontColor('#AAAAAA').margin({ left: 8 })
Text('').layoutWeight(1)
Row() {
Column() {}
.layoutWeight(stat.owned).height(6)
.backgroundColor(stat.color)
.borderRadius({ topLeft: 3, bottomLeft: 3 })
Column() {}
.layoutWeight(stat.total - stat.owned).height(6)
.backgroundColor('#E0E0E0')
.borderRadius({ topRight: 3, bottomRight: 3 })
}
.width('30%')
}
.backgroundColor('#FFFFFF')
.borderRadius(8)
.border({ width: 1, color: '#F0F0F0' })
}, (stat: RarityStat81) => stat.rarity)
}
.backgroundColor('#FFFFFF')
.borderRadius(12)
.padding(8)
// 卡牌图鉴网格
Text('卡牌图鉴').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ left: 16, top: 16 })
Grid() {
ForEach(COLLECTION_CARDS_81, (card: CollectionCard81) => {
GridItem() {
Column() {
Column() {
Text('🃏').fontSize(28)
}
.width('100%').height(60)
.backgroundColor(card.owned ? card.color + '20' : '#F5F5F5')
.borderRadius({ topLeft: 10, topRight: 10 })
.justifyContent(FlexAlign.Center)
.opacity(card.owned ? 1 : 0.4)
Column() {
Text(card.name).fontSize(10).fontColor(card.owned ? '#333333' : '#CCCCCC').fontWeight(FontWeight.Medium).maxLines(1)
Text(card.rarity).fontSize(8).fontColor(card.owned ? card.color : '#CCCCCC').margin({ top: 2 })
Text('攻击力 ' + card.power).fontSize(8).fontColor(card.owned ? '#AAAAAA' : '#CCCCCC').margin({ top: 2 })
Text(card.owned ? '×' + card.count : '未拥有').fontSize(8)
.fontColor(card.owned ? '#4CAF50' : '#CCCCCC').margin({ top: 2 })
}
.padding(6)
}
.backgroundColor('#FFFFFF')
.borderRadius(10)
.border({ width: 1, color: '#F0F0F0' })
}
}, (card: CollectionCard81) => card.id.toString())
}
.columnsTemplate('1fr 1fr 1fr')
.rowsGap(8).columnsGap(8)
.padding(16)
}
}
.scrollable(ScrollDirection.Vertical)
.width('100%').height('100%')
.backgroundColor('#E0F2F1')
}
}
收藏进度区域遍历RARITY_STATS_81数组,为每个稀有度等级渲染一行进度条。进度条的实现方式与赛事报名进度条相同——使用两个Column配合layoutWeight按比例分割。百分比通过(stat.owned / stat.total * 100).toFixed(0)动态计算,保留整数显示。进度条宽度限制为30%,通过.width('30%')控制,避免在不同稀有度数值差异较大时进度条长度不一的问题。
卡牌图鉴网格使用columnsTemplate('1fr 1fr 1fr')实现三列等宽布局。未拥有的卡牌通过opacity(0.4)降低不透明度,同时图标区域使用灰色背景#F5F5F5而非主题色背景,文字颜色全部使用#CCCCCC灰色,从视觉上明确标识"未拥有"状态。已拥有的卡牌显示拥有数量(如"×2"),未拥有的显示"未拥有"文字,这一设计使图鉴的收藏状态一目了然。
八、购买弹窗与卡组编辑
购买弹窗和卡组编辑弹窗是应用的两大核心交互组件,分别通过@Builder装饰器定义为buildBuySheet和buildEditDeckSheet。
@Builder
buildBuySheet() {
Column() {
Row() {
Text('购买卡包').fontSize(18).fontWeight(FontWeight.Bold).fontColor('#333333')
Text('').layoutWeight(1)
Text('✕').fontSize(20).fontColor('#999999').onClick(() => { this.showBuyDialog = false })
}
.width('100%').padding(16)
Scroll() {
Column() {
// 选择卡包
Text('选择卡包').fontSize(14).fontWeight(FontWeight.Bold).fontColor('#333333')
Scroll() {
Row() {
ForEach(PACK_SELECTS_81, (pack: PackSelect81) => {
Column() {
Text(pack.name).fontSize(11)
.fontColor(this.selectedPackId === pack.id ? '#FFFFFF' : '#666666').maxLines(1)
Text(pack.cards + '张/包').fontSize(9)
.fontColor(this.selectedPackId === pack.id ? 'rgba(255,255,255,0.8)' : '#AAAAAA').margin({ top: 2 })
Text('¥' + pack.price).fontSize(12)
.fontColor(this.selectedPackId === pack.id ? '#FFFFFF' : pack.color)
.fontWeight(FontWeight.Bold).margin({ top: 2 })
}
.padding(8).margin({ right: 8 })
.borderRadius(10)
.backgroundColor(this.selectedPackId === pack.id ? pack.color : '#F5F5F5')
.onClick(() => { this.selectedPackId = pack.id })
}, (pack: PackSelect81) => pack.id.toString())
}
}
.scrollable(ScrollDirection.Horizontal)
// 开包方式
Text('开包方式').fontSize(14).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ top: 16 })
Column() {
ForEach(this.openMethods, (method: string, idx: number) => {
Row() {
Radio({ value: idx.toString(), group: 'open' })
.checked(this.openMethod === idx)
.onChange((checked: boolean) => { if (checked) { this.openMethod = idx } })
Text(method).fontSize(12).fontColor('#333333').margin({ left: 8 })
Text('').layoutWeight(1)
Text(this.openMethod === idx ? '✓' : '').fontSize(14).fontColor('#00838F')
}
.borderRadius(8)
.backgroundColor(this.openMethod === idx ? '#E0F2F1' : '#F5F5F5')
}, (method: string, idx: number) => idx.toString())
}
// 数量
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.buyQuantity > 1) { this.buyQuantity-- } })
Text(this.buyQuantity.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(() => { if (this.buyQuantity < 50) { this.buyQuantity++ } })
}
}
.margin({ top: 16 })
// 备注
Text('备注').fontSize(14).fontColor('#333333').margin({ top: 16 })
TextArea({ text: this.buyNotes, placeholder: '如有特殊需求请备注...' })
.width('100%').height(60).margin({ top: 8 })
.borderRadius(10).backgroundColor('#F5F5F5')
.onChange((val: string) => { this.buyNotes = val })
// 合计
Row() {
Text('合计').fontSize(13).fontColor('#666666')
Text('').layoutWeight(1)
Text('¥' + this.getTotalPrice()).fontSize(20).fontColor('#FFB300').fontWeight(FontWeight.Bold)
}
.margin({ top: 20, bottom: 12 })
}
}
.constraintSize({ maxHeight: '55%' })
Row() {
Button() {
Text('确认购买').fontSize(16).fontColor('#FFFFFF').fontWeight(FontWeight.Medium)
}
.layoutWeight(1).height(48)
.backgroundColor('#00838F')
.borderRadius(24)
.onClick(() => { this.showBuyDialog = false })
}
.padding(16)
}
}
private getTotalPrice(): number {
let pack: PackSelect81 | undefined = PACK_SELECTS_81.find((p: PackSelect81) => p.id === this.selectedPackId)
let packPrice: number = pack ? pack.price : 0
return packPrice * this.buyQuantity
}
购买弹窗的卡包选择区域使用横向滚动的卡片列表,选中项使用卡包主题色作为背景色配合白色文字,未选中项使用灰色背景配合深色文字。开包方式选择使用Radio组件实现单选,选中项使用淡青绿#E0F2F1背景并显示勾选标记。购买数量使用减号和加号按钮控制,上限设为50(this.buyQuantity < 50),下限为1。
getTotalPrice方法通过find函数从PACK_SELECTS_81数组中查找当前选中的卡包,计算单价乘以数量。这里使用了TypeScript的联合类型PackSelect81 | undefined和空值检查pack ? pack.price : 0,确保类型安全。
@Builder
buildEditDeckSheet() {
Column() {
Row() {
Text('编辑卡组').fontSize(18).fontWeight(FontWeight.Bold).fontColor('#333333')
Text('').layoutWeight(1)
Text('✕').fontSize(20).fontColor('#999999').onClick(() => { this.showEditDeckDialog = false })
}
.width('100%').padding(16)
Scroll() {
Column() {
Text('卡组名称').fontSize(14).fontColor('#666666').margin({ top: 8 })
TextInput({ text: this.editDeckName, placeholder: '请输入卡组名称' })
.width('100%').height(44).margin({ top: 8 })
.borderRadius(10).backgroundColor('#F5F5F5')
.onChange((val: string) => { this.editDeckName = val })
Text('核心卡牌').fontSize(14).fontColor('#666666').margin({ top: 12 })
TextInput({ text: this.editDeckCore, placeholder: '如:星海之龙×3' })
.width('100%').height(44).margin({ top: 8 })
.borderRadius(10).backgroundColor('#F5F5F5')
.onChange((val: string) => { this.editDeckCore = val })
Text('战术备注').fontSize(14).fontColor('#666666').margin({ top: 12 })
TextArea({ text: this.editDeckStrategy, placeholder: '请输入战术思路和注意事项...' })
.width('100%').height(80).margin({ top: 8 })
.borderRadius(10).backgroundColor('#F5F5F5')
.onChange((val: string) => { this.editDeckStrategy = val })
Text('已有卡组').fontSize(14).fontColor('#666666').margin({ top: 16 })
Column() {
ForEach(DECKS_81, (deck: DeckItem81) => {
Column() {
Row() {
Text(deck.name).fontSize(13).fontColor('#333333').fontWeight(FontWeight.Medium)
Text('').layoutWeight(1)
Text(deck.format).fontSize(9).fontColor(deck.color)
.backgroundColor(deck.color + '15')
.padding({ left: 4, right: 4, top: 1, bottom: 1 })
.borderRadius(4)
}
Text(deck.desc).fontSize(11).fontColor('#AAAAAA').margin({ top: 4 })
Row() {
Text(deck.cards + '张').fontSize(10).fontColor('#AAAAAA')
Text('胜率' + deck.winRate + '%').fontSize(10).fontColor(deck.color).margin({ left: 12 })
Text('').layoutWeight(1)
Text('编辑').fontSize(10).fontColor('#00838F')
}.margin({ top: 4 })
}
.backgroundColor('#FFFFFF')
.borderRadius(8)
.border({ width: 1, color: '#F0F0F0' })
}, (deck: DeckItem81) => deck.id.toString())
}
}
}
.constraintSize({ maxHeight: '50%' })
Row() {
Button() {
Text('保存卡组').fontSize(16).fontColor('#FFFFFF').fontWeight(FontWeight.Medium)
}
.layoutWeight(1).height(48)
.backgroundColor('#00838F')
.borderRadius(24)
.onClick(() => { this.showEditDeckDialog = false })
}
.padding(16)
}
}
卡组编辑弹窗包含三个表单字段——卡组名称使用TextInput单行输入框,核心卡牌使用TextInput并给出占位提示"如:星海之龙×3",战术备注使用TextArea多行输入框,高度设为80px以容纳较长的战术说明。表单下方展示已有卡组列表,每个卡组项包含名称、赛制标签、描述、卡牌数量和胜率信息,胜率使用主题色显示,"编辑"文字使用潮玩青色作为操作入口。
九、我的Tab:段位展示与消费统计
个人中心Tab集成了渐变头部、消费统计、段位展示、卡组列表、订单管理和收藏管理六大模块。
@Component
struct ProfileTab81 {
onEditDeck: () => 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)')
Column() {
Text('卡牌玩家').fontSize(18).fontColor('#FFFFFF').fontWeight(FontWeight.Bold)
Text('ID: CG20260824').fontSize(11).fontColor('rgba(255,255,255,0.8)').margin({ top: 4 })
Row() {
Text('铂金I').fontSize(10).fontColor('#FFFFFF')
.backgroundColor('rgba(255,255,255,0.25)')
.padding({ left: 6, right: 6, top: 1, bottom: 1 })
.borderRadius(4)
Text('积分 7980').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.onEditDeck() })
}
}
.linearGradient({ angle: 135, colors: [['#00838F', 0], ['#006064', 1]] })
// 消费统计
Row() {
Column() {
Text('¥3,860').fontSize(20).fontColor('#00838F').fontWeight(FontWeight.Bold)
Text('总消费').fontSize(10).fontColor('#AAAAAA').margin({ top: 2 })
}
.layoutWeight(1).alignItems(HorizontalAlign.Center)
Column() {
Text('48').fontSize(20).fontColor('#FFB300').fontWeight(FontWeight.Bold)
Text('拥有卡牌').fontSize(10).fontColor('#AAAAAA').margin({ top: 2 })
}
.layoutWeight(1).alignItems(HorizontalAlign.Center)
Column() {
Text('12').fontSize(20).fontColor('#C2185B').fontWeight(FontWeight.Bold)
Text('SSR以上').fontSize(10).fontColor('#AAAAAA').margin({ top: 2 })
}
.layoutWeight(1).alignItems(HorizontalAlign.Center)
}
.backgroundColor('#FFFFFF')
.borderRadius(12)
// 段位展示
Row() {
Column() {
Text('🏆').fontSize(32)
}
.width(56).height(56).borderRadius(28)
.backgroundColor('#FFB300')
Column() {
Text('铂金I').fontSize(16).fontColor('#333333').fontWeight(FontWeight.Bold)
Text('距钻石 1020分').fontSize(11).fontColor('#FFB300').margin({ top: 4 })
}
.margin({ left: 12 })
.layoutWeight(1)
Text('查看 >').fontSize(11).fontColor('#00838F')
}
.backgroundColor('#FFFFFF')
.borderRadius(12)
// 我的卡组
Column() {
ForEach(DECKS_81, (deck: DeckItem81) => {
Row() {
Column() {
Text('🃏').fontSize(20)
}
.width(36).height(36).borderRadius(8)
.backgroundColor(deck.color + '20')
Column() {
Text(deck.name).fontSize(13).fontColor('#333333').fontWeight(FontWeight.Medium)
Text(deck.format + ' · ' + deck.cards + '张').fontSize(10).fontColor('#AAAAAA').margin({ top: 2 })
Text(deck.desc).fontSize(10).fontColor('#999999').margin({ top: 2 }).maxLines(1)
}
.margin({ left: 8 })
.layoutWeight(1)
Column() {
Text(deck.winRate + '%').fontSize(14).fontColor(deck.color).fontWeight(FontWeight.Bold)
Text('胜率').fontSize(9).fontColor('#AAAAAA')
}
.alignItems(HorizontalAlign.End)
}
.backgroundColor('#FFFFFF')
.borderRadius(10)
.border({ width: 1, color: '#F0F0F0' })
}, (deck: DeckItem81) => deck.id.toString())
}
// 订单列表
Column() {
ForEach(ORDERS_81, (order: OrderItem81) => {
Column() {
Row() {
Text(order.product).fontSize(13).fontColor('#333333').fontWeight(FontWeight.Medium).maxLines(1).layoutWeight(1)
Text(order.status).fontSize(11).fontColor(order.color)
}
Row() {
Text(order.date + ' · ' + order.quantity + '件').fontSize(10).fontColor('#AAAAAA')
Text('').layoutWeight(1)
Text('¥' + order.amount).fontSize(14).fontColor('#FFB300').fontWeight(FontWeight.Bold)
}.margin({ top: 4 })
Row() {
Text('').layoutWeight(1)
if (order.status === '待发货' || order.status === '已报名') {
Text('取消').fontSize(10).fontColor('#FF5252').onClick(() => { this.onCancelOrder(order.id) })
}
}.margin({ top: 4 })
}
.backgroundColor('#FFFFFF')
.borderRadius(10)
.border({ width: 1, color: '#F0F0F0' })
}, (order: OrderItem81) => order.id.toString())
}
// 收藏列表
Column() {
ForEach(FAVORITES_81, (fav: FavoriteItem81) => {
Row() {
Column() {
Text('⭐').fontSize(20)
}
.width(40).height(40).borderRadius(8)
.backgroundColor(fav.color + '15')
Column() {
Text(fav.name).fontSize(13).fontColor('#333333')
Text(fav.rarity).fontSize(10).fontColor('#AAAAAA').margin({ top: 2 })
}
.margin({ left: 8 })
.layoutWeight(1)
Text('删除').fontSize(11).fontColor('#FF5252').onClick(() => { this.onDeleteFav(fav.id) })
}
.backgroundColor('#FFFFFF')
.borderRadius(10)
.border({ width: 1, color: '#F0F0F0' })
}, (fav: FavoriteItem81) => fav.id.toString())
}
}
}
.scrollable(ScrollDirection.Vertical)
.backgroundColor('#E0F2F1')
}
}
渐变头部使用135度潮玩青渐变,与顶部导航栏保持一致。头部右侧的"编辑卡组"按钮使用半透明白色背景,点击触发onEditDeck回调打开卡组编辑弹窗。
消费统计区域使用三等分布局展示总消费(¥3,860)、拥有卡牌数(48)和SSR以上数量(12)三个关键指标。每个指标使用不同的主题色——总消费用潮玩青、卡牌数用稀有金、SSR数用粉红色,形成视觉对比。
段位展示区域以金色奖杯Emoji为核心视觉元素,展示当前段位"铂金I"和距下一段位"钻石"的积分差距。我的卡组列表中每张卡组显示胜率,使用deck.color绑定颜色,胜率以百分比形式用加粗大字号展示。
订单列表中的"取消"操作仅在状态为"待发货"或"已报名"时显示,通过if条件判断实现状态感知的UI渲染。收藏列表中的"删除"操作触发onDeleteFav回调,弹出删除确认弹窗。
十、社区Tab:开包实况与攻略分享
社区Tab是卡牌玩家交流的核心场所,包含热门话题横滑和最新动态信息流。
@Component
struct CommunityTab81 {
build() {
Scroll() {
Column() {
Column() {
Text('卡牌社区').fontSize(18).fontWeight(FontWeight.Bold).fontColor('#333333')
Text('分享开包,交流战术').fontSize(12).fontColor('#999999').margin({ top: 4 })
}
.alignItems(HorizontalAlign.Center)
Text('热门话题').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ left: 16 })
Scroll() {
Row() {
ForEach(TOPICS_81, (topic: TopicItem81) => {
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: TopicItem81) => topic.id.toString())
}
}
.scrollable(ScrollDirection.Horizontal)
Text('最新动态').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ left: 16, top: 16 })
Column() {
ForEach(POSTS_81, (post: CommunityPost81) => {
Column() {
Row() {
Column() {
Text('🃏').fontSize(20)
}
.width(36).height(36).borderRadius(18)
.backgroundColor(post.avatarColor + '20')
Column() {
Text(post.author).fontSize(13).fontColor('#333333').fontWeight(FontWeight.Medium)
Text(post.timeAgo).fontSize(10).fontColor('#AAAAAA').margin({ top: 2 })
}
.margin({ left: 8 })
.layoutWeight(1)
Text(post.topic).fontSize(10).fontColor('#00838F')
.backgroundColor('#E0F2F1')
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
.borderRadius(4)
}
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)
}
}, (idx: number) => idx.toString())
}
}
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('#00838F')
}.margin({ top: 12 })
}
.backgroundColor('#FFFFFF')
.borderRadius(12)
.border({ width: 1, color: '#F0F0F0' })
}, (post: CommunityPost81) => post.id.toString())
}
}
}
.scrollable(ScrollDirection.Vertical)
.backgroundColor('#E0F2F1')
}
}
社区Tab的结构与典型社交媒体的信息流布局一致。热门话题区使用横向滚动展示,热门话题通过topic.hot条件渲染"HOT"标签。话题卡片的边框颜色使用topic.color + '30',将话题主题色融入视觉标识。
动态信息流中每条帖子的话题标签使用潮玩青色文字和淡青绿背景,与应用整体色彩保持一致。图片预览区域使用if条件判断结合嵌套ForEach实现,最多展示三张图片。互动按钮区域使用三个Row容器水平排列点赞、评论和分享按钮,每个按钮包含Emoji图标和数量文本,间距通过margin({ left: 24 })控制。
技术点对比表格
| 技术维度 | 卡包Tab | 集换Tab | 竞技Tab | 图鉴Tab | 社区Tab | 我的Tab |
|---|---|---|---|---|---|---|
| 主布局方式 | 双列Grid+横向轮播 | 纵向列表+交易记录 | 柱状图+赛事列表+对战记录 | 进度条+三列Grid | 横向话题+纵向信息流 | 渐变头部+多模块纵向 |
| 核心数据结构 | CardPack81 | TradeItem81+TradeRecord81 | Tournament81+BattleRecord81 | CollectionCard81+RarityStat81 | CommunityPost81 | OrderItem81+DeckItem81 |
| 进度条实现 | 无 | 无 | layoutWeight报名进度 | layoutWeight收藏进度 | 无 | 无 |
| 价格展示 | 现价+原价删除线 | 单卡价格+状态 | 赛事奖金 | 无 | 无 | 订单金额+取消入口 |
| 交互回调 | onBuy(id) | onTrade(id) | 无 | 无 | 无 | onEditDeck/onCancelOrder/onDeleteFav |
| 视觉特色 | 渐变首发大卡 | 稀有度标签着色 | 8周积分柱状图 | 未拥有卡牌半透明 | 图片预览+互动按钮 | 三色统计+段位奖杯 |
| 数据驱动配色 | pack.color拼接透明度 | item.color稀有度映射 | tourn.color赛事色 | card.owned条件渲染 | post.avatarColor | deck.color胜率色 |
安装DevEco Studio程序

选择目标安装目录:

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

新建一个空白模板:

设置API为24的模板项目:

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

完整代码:
// 主题:潮玩青 #00838F × 稀有金 #FFB300,底色 #E0F2F1(清新潮玩青绿调)
// 布局差异:分类标签+新卡大卡+双列卡包 / 稀有度筛选+集换列表+交易记录 / 排行柱状图+赛事+对战记录 / 图鉴网格+稀有度统计+收藏进度 / 卡牌动态+热门话题 / 渐变头+消费统计+段位+订单+收藏
// ============ 类型定义 ============
interface CardPack81 {
id: number
name: string
series: string
price: number
originalPrice: number
cards: number
rarity: string
stock: number
rating: number
sales: number
tags: string[]
color: string
releaseDate: string
desc: string
}
interface PackTag81 {
label: string
color: string
count: number
}
interface TradeItem81 {
id: number
cardName: string
rarity: string
seller: string
price: number
condition: string
edition: string
status: string
color: string
desc: string
}
interface TradeRecord81 {
id: number
cardName: string
type: string
amount: number
date: string
status: string
color: string
}
interface Tournament81 {
id: number
name: string
format: string
date: string
venue: string
participants: number
maxParticipants: number
prize: string
level: string
status: string
color: string
desc: string
}
interface BattleRecord81 {
id: number
opponent: string
result: string
deck: string
date: string
turn: number
duration: string
color: string
}
interface BarData81 {
label: string
value: number
color: string
}
interface CollectionCard81 {
id: number
name: string
rarity: string
series: string
owned: boolean
count: number
color: string
power: number
}
interface RarityStat81 {
rarity: string
total: number
owned: number
color: string
}
interface CommunityPost81 {
id: number
author: string
avatarColor: string
content: string
likes: number
comments: number
shares: number
images: number
timeAgo: string
topic: string
}
interface TopicItem81 {
id: number
title: string
posts: number
hot: boolean
color: string
}
interface OrderItem81 {
id: number
product: string
date: string
amount: number
status: string
quantity: number
color: string
}
interface FavoriteItem81 {
id: number
name: string
rarity: string
color: string
}
interface DeckItem81 {
id: number
name: string
format: string
cards: number
winRate: number
color: string
desc: string
}
interface PackSelect81 {
id: number
name: string
price: number
cards: number
color: string
}
// ============ 静态数据 ============
const PACK_TAGS_81: PackTag81[] = [
{ label: 'TCG', color: '#00838F', count: 128 },
{ label: '盲盒卡', color: '#FFB300', count: 96 },
{ label: '限量版', color: '#C2185B', count: 85 },
{ label: '新手包', color: '#2E7D32', count: 72 },
{ label: '纪念版', color: '#6A1B9A', count: 64 },
{ label: '典藏版', color: '#8D6E63', count: 58 }
]
const CARD_PACKS_81: CardPack81[] = [
{ id: 1, name: '星海觉醒·第一弹', series: '星海觉醒', price: 35, originalPrice: 48, cards: 8, rarity: 'SR以上保底', stock: 3562, rating: 4.9, sales: 12543, tags: ['热销', '新品'], color: '#00838F', releaseDate: '08-20', desc: '每包含8张卡,SR以上保底1张' },
{ id: 2, name: '龙之试炼·典藏包', series: '龙之试炼', price: 88, originalPrice: 128, cards: 15, rarity: 'SSR保底', stock: 854, rating: 5.0, sales: 6234, tags: ['典藏', 'SSR'], color: '#FFB300', releaseDate: '08-15', desc: '含15张卡,SSR保底1张+闪卡1张' },
{ id: 3, name: '机械纪元·新手包', series: '机械纪元', price: 18, originalPrice: 25, cards: 5, rarity: 'R保底', stock: 5682, rating: 4.6, sales: 18543, tags: ['新手', '平价'], color: '#2E7D32', releaseDate: '07-01', desc: '新手入门首选,5张卡R保底' },
{ id: 4, name: '幻兽图鉴·限定包', series: '幻兽图鉴', price: 68, originalPrice: 98, cards: 12, rarity: 'SR保底+闪卡', stock: 423, rating: 4.8, sales: 4567, tags: ['限定', '闪卡'], color: '#C2185B', releaseDate: '08-10', desc: '限定版含12张卡+闪卡1张' },
{ id: 5, name: '神域降临·典藏包', series: '神域降临', price: 128, originalPrice: 188, cards: 20, rarity: 'SSR+UR保底', stock: 156, rating: 5.0, sales: 2876, tags: ['典藏', 'UR'], color: '#6A1B9A', releaseDate: '08-05', desc: '含20张卡,SSR+UR保底+全闪卡' },
{ id: 6, name: '万灵序曲·纪念包', series: '万灵序曲', price: 48, originalPrice: 68, cards: 10, rarity: 'SR保底', stock: 2876, rating: 4.7, sales: 8543, tags: ['纪念', '热销'], color: '#8D6E63', releaseDate: '07-20', desc: '纪念版含10张卡+SR保底' },
{ id: 7, name: '星海觉醒·补充包', series: '星海觉醒', price: 25, originalPrice: 35, cards: 6, rarity: 'R保底', stock: 6854, rating: 4.7, sales: 15623, tags: ['热销', '补充'], color: '#00838F', releaseDate: '08-20', desc: '6张卡R保底,日常开包首选' },
{ id: 8, name: '龙之试炼·大礼包', series: '龙之试炼', price: 188, originalPrice: 288, cards: 40, rarity: 'SSR+3闪卡', stock: 89, rating: 4.9, sales: 1542, tags: ['超值', '大包'], color: '#FFB300', releaseDate: '08-15', desc: '40张卡+SSR+3闪卡+收藏盒' },
{ id: 9, name: '机械纪元·精英包', series: '机械纪元', price: 58, originalPrice: 78, cards: 10, rarity: 'SR保底', stock: 2345, rating: 4.8, sales: 6754, tags: ['精英'], color: '#1565C0', releaseDate: '07-01', desc: '10张卡SR保底+闪卡概率' },
{ id: 10, name: '幻兽图鉴·稀有包', series: '幻兽图鉴', price: 98, originalPrice: 138, cards: 15, rarity: 'SSR保底+闪卡', stock: 678, rating: 4.9, sales: 3456, tags: ['稀有'], color: '#C2185B', releaseDate: '08-10', desc: '15张卡SSR保底+闪卡2张' }
]
const TRADE_ITEMS_81: TradeItem81[] = [
{ id: 1, cardName: '星海之龙·SSR闪', rarity: 'SSR闪卡', seller: '卡牌收藏家', price: 188, condition: '完美', edition: '初版', status: '出售中', color: '#FFB300', desc: '初版SSR闪卡,品相完美' },
{ id: 2, cardName: '龙之帝王·UR', rarity: 'UR', seller: '神域玩家', price: 588, condition: '完美', edition: '限定版', status: '出售中', color: '#6A1B9A', desc: '限定UR卡,仅出1000张' },
{ id: 3, cardName: '机械神官·SR闪', rarity: 'SR闪卡', seller: '机械大师', price: 45, condition: '近完美', edition: '第一弹', status: '出售中', color: '#00838F', desc: 'SR闪卡,近完美状态' },
{ id: 4, cardName: '幻兽·白虎·SSR', rarity: 'SSR', seller: '幻兽猎人', price: 128, condition: '完美', edition: '限定版', status: '出售中', color: '#C2185B', desc: '限定版SSR,四大神兽系列' },
{ id: 5, cardName: '万灵·凤凰·SSR闪', rarity: 'SSR闪卡', seller: '万灵使', price: 268, condition: '完美', edition: '纪念版', status: '出售中', color: '#FF6F00', desc: '纪念版SSR闪卡' },
{ id: 6, cardName: '星海之龙·SR', rarity: 'SR', seller: '星海玩家', price: 28, condition: '良好', edition: '初版', status: '出售中', color: '#00838F', desc: '初版SR卡,良好状态' },
{ id: 7, cardName: '龙之公主·UR闪', rarity: 'UR闪卡', seller: '龙族守护', price: 888, condition: '完美', edition: '限定版', status: '已预订', color: '#FFB300', desc: '限定UR闪卡,极稀有' },
{ id: 8, cardName: '机械战神·SSR', rarity: 'SSR', seller: '机械工厂', price: 98, condition: '完美', edition: '第二弹', status: '出售中', color: '#1565C0', desc: '第二弹SSR,完美品相' }
]
const TRADE_RECORDS_81: TradeRecord81[] = [
{ id: 1, cardName: '星海之龙·SR', type: '买入', amount: 28, date: '08-22', status: '已完成', color: '#00838F' },
{ id: 2, cardName: '机械神官·SR闪', type: '卖出', amount: 45, date: '08-20', status: '已完成', color: '#FF6F00' },
{ id: 3, cardName: '幻兽·白虎·SSR', type: '买入', amount: 128, date: '08-18', status: '已完成', color: '#C2185B' },
{ id: 4, cardName: '龙之公主·UR闪', type: '卖出', amount: 888, date: '08-15', status: '已完成', color: '#FFB300' },
{ id: 5, cardName: '万灵·凤凰·SSR闪', type: '买入', amount: 268, date: '08-12', status: '运输中', color: '#FF6F00' },
{ id: 6, cardName: '星海之龙·SSR闪', type: '卖出', amount: 188, date: '08-10', status: '已完成', color: '#00838F' }
]
const TOURNAMENTS_81: Tournament81[] = [
{ id: 1, name: '2026全国TCG公开赛', format: '标准赛', date: '09-15~09-16', venue: '上海展览中心', participants: 512, maxParticipants: 1024, prize: '10万元', level: 'S级', status: '报名中', color: '#00838F', desc: '全国最大TCG赛事,总奖金10万' },
{ id: 2, name: '星海觉醒·地区赛', format: '限定赛', date: '09-08', venue: '北京·国贸商城', participants: 128, maxParticipants: 256, prize: '2万元', level: 'A级', status: '报名中', color: '#FFB300', desc: '星海觉醒系列限定赛' },
{ id: 3, name: '龙之试炼·杯赛', format: '淘汰赛', date: '09-01', venue: '广州·天河城', participants: 64, maxParticipants: 128, prize: '5千元', level: 'B级', status: '报名中', color: '#C2185B', desc: '龙之试炼主题杯赛' },
{ id: 4, name: '新手友谊赛', format: '友谊赛', date: '08-30', venue: '线上赛', participants: 256, maxParticipants: 512, prize: '卡包奖励', level: 'C级', status: '报名中', color: '#2E7D32', desc: '新手友好,线上进行' },
{ id: 5, name: '机械纪元·团战赛', format: '团队赛', date: '09-22', venue: '成都·IFS', participants: 32, maxParticipants: 64, prize: '3万元', level: 'A级', status: '即将开放', color: '#1565C0', desc: '3人组队团队赛' },
{ id: 6, name: '万灵序曲·纪念赛', format: '标准赛', date: '10-05', venue: '杭州·大厦', participants: 0, maxParticipants: 128, prize: '1万元', level: 'B级', status: '预告中', color: '#8D6E63', desc: '万灵序曲纪念赛事' }
]
const BATTLE_RECORDS_81: BattleRecord81[] = [
{ id: 1, opponent: '棋王降临', result: '胜利', deck: '星海觉醒·龙骑', date: '08-22', turn: 8, duration: '25分钟', color: '#4CAF50' },
{ id: 2, opponent: '卡牌大师', result: '失败', deck: '机械纪元·战神', date: '08-20', turn: 12, duration: '35分钟', color: '#FF5252' },
{ id: 3, opponent: '幻兽猎人', result: '胜利', deck: '幻兽图鉴·白虎', date: '08-18', turn: 6, duration: '18分钟', color: '#4CAF50' },
{ id: 4, opponent: '神域玩家', result: '胜利', deck: '星海觉醒·龙骑', date: '08-15', turn: 10, duration: '30分钟', color: '#4CAF50' },
{ id: 5, opponent: '龙族守护', result: '失败', deck: '龙之试炼·帝王', date: '08-12', turn: 15, duration: '42分钟', color: '#FF5252' },
{ id: 6, opponent: '万灵使', result: '胜利', deck: '幻兽图鉴·白虎', date: '08-10', turn: 7, duration: '20分钟', color: '#4CAF50' },
{ id: 7, opponent: '星海玩家', result: '胜利', deck: '机械纪元·战神', date: '08-08', turn: 9, duration: '28分钟', color: '#4CAF50' },
{ id: 8, opponent: '机械工厂', result: '失败', deck: '机械纪元·战神', date: '08-05', turn: 11, duration: '33分钟', color: '#FF5252' }
]
const BAR_DATA_RANK_81: BarData81[] = [
{ label: '1周', value: 650, color: '#4DB6AC' },
{ label: '2周', value: 820, color: '#4DB6AC' },
{ label: '3周', value: 780, color: '#4DB6AC' },
{ label: '4周', value: 950, color: '#4DB6AC' },
{ label: '5周', value: 1080, color: '#4DB6AC' },
{ label: '6周', value: 1320, color: '#00838F' },
{ label: '7周', value: 1450, color: '#00838F' },
{ label: '8周', value: 1280, color: '#00838F' }
]
const COLLECTION_CARDS_81: CollectionCard81[] = [
{ id: 1, name: '星海之龙', rarity: 'UR', series: '星海觉醒', owned: true, count: 2, color: '#00838F', power: 9500 },
{ id: 2, name: '龙之帝王', rarity: 'UR', series: '龙之试炼', owned: true, count: 1, color: '#FFB300', power: 9800 },
{ id: 3, name: '机械神官', rarity: 'SSR', series: '机械纪元', owned: true, count: 3, color: '#00838F', power: 8200 },
{ id: 4, name: '幻兽·白虎', rarity: 'SSR', series: '幻兽图鉴', owned: true, count: 1, color: '#C2185B', power: 8500 },
{ id: 5, name: '万灵·凤凰', rarity: 'SSR', series: '万灵序曲', owned: true, count: 2, color: '#FF6F00', power: 8400 },
{ id: 6, name: '机械战神', rarity: 'SSR', series: '机械纪元', owned: true, count: 1, color: '#1565C0', power: 8800 },
{ id: 7, name: '龙之公主', rarity: 'UR', series: '龙之试炼', owned: false, count: 0, color: '#FFB300', power: 9600 },
{ id: 8, name: '星海守护者', rarity: 'SR', series: '星海觉醒', owned: true, count: 4, color: '#00838F', power: 6500 },
{ id: 9, name: '机械士兵', rarity: 'SR', series: '机械纪元', owned: true, count: 6, color: '#00838F', power: 6200 },
{ id: 10, name: '幻兽·朱雀', rarity: 'SSR', series: '幻兽图鉴', owned: false, count: 0, color: '#C2185B', power: 8300 },
{ id: 11, name: '万灵·玄武', rarity: 'SSR', series: '万灵序曲', owned: true, count: 1, color: '#FF6F00', power: 8600 },
{ id: 12, name: '神域之主', rarity: 'UR', series: '神域降临', owned: false, count: 0, color: '#6A1B9A', power: 9900 }
]
const RARITY_STATS_81: RarityStat81[] = [
{ rarity: 'UR', total: 12, owned: 3, color: '#6A1B9A' },
{ rarity: 'SSR闪', total: 24, owned: 8, color: '#FFB300' },
{ rarity: 'SSR', total: 48, owned: 12, color: '#C2185B' },
{ rarity: 'SR闪', total: 96, owned: 35, color: '#FF6F00' },
{ rarity: 'SR', total: 192, owned: 87, color: '#00838F' },
{ rarity: 'R', total: 384, owned: 256, color: '#2E7D32' }
]
const POSTS_81: CommunityPost81[] = [
{ id: 1, author: '卡牌收藏家', avatarColor: '#00838F', content: '星海觉醒第一弹开包实况!15包连开出3张SSR+1张UR闪!附单卡评级和卡组搭配建议~', likes: 892, comments: 156, shares: 67, images: 5, timeAgo: '2小时前', topic: '#开包实况#' },
{ id: 2, author: '神域玩家', avatarColor: '#6A1B9A', content: '龙之试炼典藏包开箱测评:开出龙之帝王UR闪!品相评分9.8分,附详细概率分析', likes: 768, comments: 134, shares: 45, images: 6, timeAgo: '5小时前', topic: '#开箱测评#' },
{ id: 3, author: '机械大师', avatarColor: '#1565C0', content: '机械纪元新版卡组构筑分享:以机械战神为核心的t1卡组,胜率78%!附详细攻略', likes: 945, comments: 187, shares: 78, images: 4, timeAgo: '8小时前', topic: '#卡组攻略#' },
{ id: 4, author: '幻兽猎人', avatarColor: '#C2185B', content: '幻兽图鉴限定包开到白虎SSR闪卡了!四大神兽系列已收集齐3张,只差朱雀了~', likes: 1234, comments: 234, shares: 89, images: 4, timeAgo: '12小时前', topic: '#收集分享#' },
{ id: 5, author: '万灵使', avatarColor: '#FF6F00', content: '集换市场攻略:如何用低价收高价值卡?分享3个捡漏技巧和2个防骗指南', likes: 678, comments: 123, shares: 45, images: 3, timeAgo: '1天前', topic: '#集换攻略#' },
{ id: 6, author: '龙族守护', avatarColor: '#FFB300', content: '全国TCG公开赛备战分享:当前meta分析+主流卡组克制+参赛准备清单', likes: 856, comments: 167, shares: 56, images: 5, timeAgo: '2天前', topic: '#赛事备战#' }
]
const TOPICS_81: TopicItem81[] = [
{ id: 1, title: '星海觉醒开包', posts: 2156, hot: true, color: '#00838F' },
{ id: 2, title: 'SSR闪卡交易', posts: 1876, hot: true, color: '#FFB300' },
{ id: 3, title: '全国赛备战', posts: 1562, hot: true, color: '#6A1B9A' },
{ id: 4, title: '卡组构筑', posts: 854, hot: false, color: '#1565C0' },
{ id: 5, title: '卡牌评级', posts: 654, hot: false, color: '#C2185B' },
{ id: 6, title: '限定卡收集', posts: 432, hot: false, color: '#FF6F00' }
]
const ORDERS_81: OrderItem81[] = [
{ id: 1, product: '星海觉醒·第一弹', date: '08-22', amount: 105, status: '已发货', quantity: 3, color: '#FF9800' },
{ id: 2, product: '龙之试炼·典藏包', date: '08-18', amount: 88, status: '已签收', quantity: 1, color: '#4CAF50' },
{ id: 3, product: '星海之龙·SR闪', date: '08-22', amount: 28, status: '已完成', quantity: 1, color: '#4CAF50' },
{ id: 4, product: '幻兽·白虎·SSR', date: '08-18', amount: 128, status: '已完成', quantity: 1, color: '#4CAF50' },
{ id: 5, product: '机械纪元·精英包', date: '09-01', amount: 116, status: '待发货', quantity: 2, color: '#2196F3' },
{ id: 6, product: '全国赛报名费', date: '09-15', amount: 50, status: '已报名', quantity: 1, color: '#2196F3' }
]
const FAVORITES_81: FavoriteItem81[] = [
{ id: 1, name: '星海觉醒·第一弹', rarity: '卡包', color: '#00838F' },
{ id: 2, name: '龙之帝王·UR', rarity: 'UR', color: '#FFB300' },
{ id: 3, name: '幻兽·朱雀·SSR', rarity: 'SSR', color: '#C2185B' },
{ id: 4, name: '全国TCG公开赛', rarity: '赛事', color: '#00838F' },
{ id: 5, name: '机械战神·SSR', rarity: 'SSR', color: '#1565C0' }
]
const DECKS_81: DeckItem81[] = [
{ id: 1, name: '星海龙骑', format: '标准赛', cards: 40, winRate: 72, color: '#00838F', desc: '以星海之龙为核心的中速卡组' },
{ id: 2, name: '机械战神', format: '标准赛', cards: 40, winRate: 68, color: '#1565C0', desc: '以机械战神为核心的快攻卡组' },
{ id: 3, name: '幻兽白虎', format: '限定赛', cards: 40, winRate: 65, color: '#C2185B', desc: '幻兽图鉴限定中速卡组' }
]
const PACK_SELECTS_81: PackSelect81[] = [
{ id: 1, name: '星海觉醒·第一弹', price: 35, cards: 8, color: '#00838F' },
{ id: 2, name: '龙之试炼·典藏包', price: 88, cards: 15, color: '#FFB300' },
{ id: 3, name: '机械纪元·精英包', price: 58, cards: 10, color: '#1565C0' },
{ id: 4, name: '幻兽图鉴·限定包', price: 68, cards: 12, color: '#C2185B' },
{ id: 5, name: '神域降临·典藏包', price: 128, cards: 20, color: '#6A1B9A' }
]
// ============ 主入口组件 ============
@Entry
@Component
struct DuoDuoCardGameApp {
@State currentTab: number = 0
@State showBuyDialog: boolean = false
@State showCancelDialog: boolean = false
@State showEditDeckDialog: boolean = false
@State showDeleteFavDialog: boolean = false
@State selectedPackId: number = 1
@State buyQuantity: number = 1
@State openMethod: number = 0
@State buyNotes: string = ''
@State editDeckName: string = ''
@State editDeckCore: string = ''
@State editDeckStrategy: string = ''
@State cancelTargetId: number = 0
private tabs: string[] = ['卡包', '集换', '竞技', '图鉴', '社区', '我的']
private openMethods: string[] = ['邮寄到家', '线上开包', '门店自提+开包']
build() {
Column() {
// ====== 顶部头部 ======
Column() {
Row() {
Column() {
Text('多多卡牌')
.fontSize(22)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Bold)
Text('TCG·集换·竞技精选')
.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: [['#00838F', 0], ['#006064', 1]] })
.padding({ top: 8, bottom: 12, left: 16, right: 16 })
// ====== Tab内容区 ======
Stack({ alignContent: Alignment.TopStart }) {
Column() {
if (this.currentTab === 0) {
PackTab81({
onBuy: (id: number) => {
this.selectedPackId = id
this.showBuyDialog = true
}
})
}
if (this.currentTab === 1) {
TradeTab81({
onTrade: (id: number) => {
this.cancelTargetId = id
this.showCancelDialog = true
}
})
}
if (this.currentTab === 2) {
CompetitiveTab81()
}
if (this.currentTab === 3) {
CollectionTab81()
}
if (this.currentTab === 4) {
CommunityTab81()
}
if (this.currentTab === 5) {
ProfileTab81({
onEditDeck: () => {
this.showEditDeckDialog = 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 ? '#00838F' : '#999999')
Text(tab)
.fontSize(10)
.fontColor(this.currentTab === idx ? '#00838F' : '#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('#E0F2F1')
.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
buildBuySheet() {
Column() {
Row() {
Text('购买卡包').fontSize(18).fontWeight(FontWeight.Bold).fontColor('#333333')
Text('').layoutWeight(1)
Text('✕').fontSize(20).fontColor('#999999').onClick(() => { this.showBuyDialog = false })
}
.width('100%').padding(16)
Scroll() {
Column() {
Text('选择卡包').fontSize(14).fontWeight(FontWeight.Bold).fontColor('#333333')
Scroll() {
Row() {
ForEach(PACK_SELECTS_81, (pack: PackSelect81) => {
Column() {
Text(pack.name).fontSize(11).fontColor(this.selectedPackId === pack.id ? '#FFFFFF' : '#666666').maxLines(1)
Text(pack.cards + '张/包').fontSize(9).fontColor(this.selectedPackId === pack.id ? 'rgba(255,255,255,0.8)' : '#AAAAAA').margin({ top: 2 })
Text('¥' + pack.price).fontSize(12).fontColor(this.selectedPackId === pack.id ? '#FFFFFF' : pack.color).fontWeight(FontWeight.Bold).margin({ top: 2 })
}
.padding(8).margin({ right: 8 })
.borderRadius(10)
.backgroundColor(this.selectedPackId === pack.id ? pack.color : '#F5F5F5')
.onClick(() => { this.selectedPackId = pack.id })
}, (pack: PackSelect81) => pack.id.toString())
}
}
.scrollable(ScrollDirection.Horizontal)
.width('100%').margin({ top: 8 })
Text('开包方式').fontSize(14).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ top: 16 })
Column() {
ForEach(this.openMethods, (method: string, idx: number) => {
Row() {
Radio({ value: idx.toString(), group: 'open' })
.checked(this.openMethod === idx)
.onChange((checked: boolean) => { if (checked) { this.openMethod = idx } })
Text(method).fontSize(12).fontColor('#333333').margin({ left: 8 })
Text('').layoutWeight(1)
Text(this.openMethod === idx ? '✓' : '').fontSize(14).fontColor('#00838F')
}
.width('100%').padding(10).margin({ top: 4 })
.borderRadius(8)
.backgroundColor(this.openMethod === idx ? '#E0F2F1' : '#F5F5F5')
}, (method: string, idx: number) => idx.toString())
}
.width('100%')
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.buyQuantity > 1) { this.buyQuantity-- } })
Text(this.buyQuantity.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(() => { if (this.buyQuantity < 50) { this.buyQuantity++ } })
}
}
.width('100%').margin({ top: 16 })
Text('备注').fontSize(14).fontColor('#333333').margin({ top: 16 })
TextArea({ text: this.buyNotes, placeholder: '如有特殊需求请备注...' })
.width('100%').height(60).margin({ top: 8 })
.borderRadius(10).backgroundColor('#F5F5F5')
.onChange((val: string) => { this.buyNotes = val })
Row() {
Text('合计').fontSize(13).fontColor('#666666')
Text('').layoutWeight(1)
Text('¥' + this.getTotalPrice()).fontSize(20).fontColor('#FFB300').fontWeight(FontWeight.Bold)
}
.width('100%').margin({ top: 20, bottom: 12 })
}
.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('#00838F')
.borderRadius(24)
.onClick(() => { this.showBuyDialog = 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
buildEditDeckSheet() {
Column() {
Row() {
Text('编辑卡组').fontSize(18).fontWeight(FontWeight.Bold).fontColor('#333333')
Text('').layoutWeight(1)
Text('✕').fontSize(20).fontColor('#999999').onClick(() => { this.showEditDeckDialog = false })
}
.width('100%').padding(16)
Scroll() {
Column() {
Text('卡组名称').fontSize(14).fontColor('#666666').margin({ top: 8 })
TextInput({ text: this.editDeckName, placeholder: '请输入卡组名称' })
.width('100%').height(44).margin({ top: 8 })
.borderRadius(10).backgroundColor('#F5F5F5')
.onChange((val: string) => { this.editDeckName = val })
Text('核心卡牌').fontSize(14).fontColor('#666666').margin({ top: 12 })
TextInput({ text: this.editDeckCore, placeholder: '如:星海之龙×3' })
.width('100%').height(44).margin({ top: 8 })
.borderRadius(10).backgroundColor('#F5F5F5')
.onChange((val: string) => { this.editDeckCore = val })
Text('战术备注').fontSize(14).fontColor('#666666').margin({ top: 12 })
TextArea({ text: this.editDeckStrategy, placeholder: '请输入战术思路和注意事项...' })
.width('100%').height(80).margin({ top: 8 })
.borderRadius(10).backgroundColor('#F5F5F5')
.onChange((val: string) => { this.editDeckStrategy = val })
Text('已有卡组').fontSize(14).fontColor('#666666').margin({ top: 16 })
Column() {
ForEach(DECKS_81, (deck: DeckItem81) => {
Column() {
Row() {
Text(deck.name).fontSize(13).fontColor('#333333').fontWeight(FontWeight.Medium)
Text('').layoutWeight(1)
Text(deck.format).fontSize(9).fontColor(deck.color).backgroundColor(deck.color + '15').padding({ left: 4, right: 4, top: 1, bottom: 1 }).borderRadius(4)
}
.width('100%')
Text(deck.desc).fontSize(11).fontColor('#AAAAAA').margin({ top: 4 })
Row() {
Text(deck.cards + '张').fontSize(10).fontColor('#AAAAAA')
Text('胜率' + deck.winRate + '%').fontSize(10).fontColor(deck.color).margin({ left: 12 })
Text('').layoutWeight(1)
Text('编辑').fontSize(10).fontColor('#00838F')
}
.width('100%').margin({ top: 4 })
}
.width('100%').padding(10).margin({ top: 6 })
.backgroundColor('#FFFFFF')
.borderRadius(8)
.border({ width: 1, color: '#F0F0F0' })
}, (deck: DeckItem81) => deck.id.toString())
}
.width('100%')
}
.width('100%').padding({ left: 16, right: 16, bottom: 16 })
}
.constraintSize({ maxHeight: '50%' })
Row() {
Button() {
Text('保存卡组').fontSize(16).fontColor('#FFFFFF').fontWeight(FontWeight.Medium)
}
.layoutWeight(1).height(48)
.backgroundColor('#00838F')
.borderRadius(24)
.onClick(() => { this.showEditDeckDialog = 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)')
}
private getTotalPrice(): number {
let pack: PackSelect81 | undefined = PACK_SELECTS_81.find((p: PackSelect81) => p.id === this.selectedPackId)
let packPrice: number = pack ? pack.price : 0
return packPrice * this.buyQuantity
}
}
// ============ 卡包Tab ============
@Component
struct PackTab81 {
onBuy: (id: number) => void = () => {}
build() {
Scroll() {
Column() {
// 分类标签
Text('卡包分类').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ left: 16, top: 12 })
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(PACK_TAGS_81, (tag: PackTag81) => {
Row() {
Text(tag.label).fontSize(12).fontColor(tag.color)
Text(tag.count + '款').fontSize(9).fontColor('#AAAAAA').margin({ left: 4 })
}
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.margin({ right: 8, top: 8 })
.backgroundColor(tag.color + '15')
.borderRadius(16)
.border({ width: 1, color: tag.color + '30' })
}, (tag: PackTag81) => tag.label)
}
.width('100%').padding({ left: 16, right: 16 })
// 新卡首发大卡
Row() {
Text('新卡首发').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#333333')
Text('').layoutWeight(1)
Text('更多 >').fontSize(12).fontColor('#00838F')
}
.width('100%').padding({ left: 16, right: 16, top: 16 })
Column() {
Row() {
Column() {
Text('星海觉醒·第一弹').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#FFFFFF')
Text('8张/包 · SR以上保底').fontSize(11).fontColor('rgba(255,255,255,0.8)').margin({ top: 4 })
Row() {
Text('¥').fontSize(12).fontColor('#FFFFFF')
Text('35').fontSize(22).fontColor('#FFFFFF').fontWeight(FontWeight.Bold)
Text('¥48').fontSize(11).fontColor('rgba(255,255,255,0.6)').decoration({ type: TextDecorationType.LineThrough }).margin({ left: 4 })
Text('').layoutWeight(1)
Text('08-20首发').fontSize(10).fontColor('#FFFFFF').backgroundColor('rgba(255,255,255,0.25)').padding({ left: 8, right: 8, top: 2, bottom: 2 }).borderRadius(10)
}
.width('100%').margin({ top: 12 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
Text('🃏').fontSize(48)
}
.width('100%').padding(16)
}
.width('100%').margin({ left: 16, right: 16, top: 8 })
.linearGradient({ angle: 135, colors: [['#00838F', 0], ['#006064', 1]] })
.borderRadius(16)
// 双列卡包
Text('全部卡包').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ left: 16, top: 16 })
Grid() {
ForEach(CARD_PACKS_81, (pack: CardPack81) => {
GridItem() {
Column() {
Column() {
Text('🃏').fontSize(32)
}
.width('100%').height(64)
.backgroundColor(pack.color + '20')
.borderRadius({ topLeft: 12, topRight: 12 })
.justifyContent(FlexAlign.Center)
Column() {
Text(pack.name).fontSize(12).fontColor('#333333').fontWeight(FontWeight.Bold).maxLines(2)
Text(pack.series).fontSize(9).fontColor('#AAAAAA').margin({ top: 2 })
Text(pack.rarity).fontSize(9).fontColor(pack.color).backgroundColor(pack.color + '15').padding({ left: 4, right: 4, top: 1, bottom: 1 }).borderRadius(4).margin({ top: 4 })
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(pack.tags, (tag: string) => {
Text(tag).fontSize(9).fontColor(pack.color).backgroundColor(pack.color + '15').padding({ left: 4, right: 4, top: 1, bottom: 1 }).borderRadius(4).margin({ right: 4, top: 4 })
}, (tag: string) => tag)
}
.margin({ top: 4 })
Row() {
Text(pack.cards + '张').fontSize(9).fontColor('#AAAAAA')
Text('库存' + pack.stock).fontSize(9).fontColor('#AAAAAA').margin({ left: 8 })
}
.margin({ top: 4 })
Row() {
Text('★').fontSize(10).fontColor('#FFB300')
Text(pack.rating.toString()).fontSize(10).fontColor('#FFB300').margin({ left: 2 })
Text(pack.sales + '人购买').fontSize(9).fontColor('#AAAAAA').margin({ left: 8 })
}
.margin({ top: 4 })
Row() {
Text('¥').fontSize(11).fontColor('#FFB300')
Text(pack.price.toString()).fontSize(15).fontColor('#FFB300').fontWeight(FontWeight.Bold)
Text('¥' + pack.originalPrice).fontSize(9).fontColor('#CCCCCC').decoration({ type: TextDecorationType.LineThrough }).margin({ left: 4 })
Text('').layoutWeight(1)
}
.margin({ top: 4 })
Button() {
Text('购买').fontSize(11).fontColor('#FFFFFF')
}
.width('100%').height(26).margin({ top: 6 })
.backgroundColor(pack.color)
.borderRadius(13)
.onClick(() => { this.onBuy(pack.id) })
}
.padding(8)
}
.backgroundColor('#FFFFFF')
.borderRadius(12)
.border({ width: 1, color: '#F0F0F0' })
}
}, (pack: CardPack81) => pack.id.toString())
}
.columnsTemplate('1fr 1fr')
.rowsGap(12).columnsGap(12)
.padding(16)
}
.width('100%')
}
.scrollable(ScrollDirection.Vertical)
.width('100%').height('100%')
.backgroundColor('#E0F2F1')
}
}
总结

本文详细解析了一个基于HarmonyOS ArkTS的集换式卡牌游戏应用,该应用以"多多卡牌"为品牌名,覆盖了卡包购买、集换交易、竞技对战、图鉴收集、社区交流和个人管理六大功能模块。从十五个接口类型定义到十余组静态数据数组,从主入口组件的十四项状态管理到六个Tab子组件的差异化布局实现,从购买弹窗的多步骤交互到卡组编辑弹窗的表单处理,整个应用展现了一个完整的TCG生态系统的技术架构。
在架构设计上,应用延续了"主组件状态管理+子组件事件回调"的经典模式,但针对TCG业务特点做了多处优化。集换Tab的onTrade回调仅在卡牌状态为"出售中"时触发,避免了已预订卡牌的误操作。竞技Tab的赛事报名进度和对战记录完全由数据驱动,layoutWeight的比例分割技巧使得进度条无需额外计算像素值即可动态渲染。图鉴Tab通过owned布尔字段和opacity透明度控制,实现了已拥有和未拥有卡牌的视觉区分。
每个等级的收藏进度通过layoutWeight进度条可视化展示。积分趋势柱状图通过颜色深浅区分近期和远期数据,赛事状态通过嵌套三元运算符实现三色动态渲染。卡组管理模块支持核心卡牌和战术备注的编辑,为竞技对战提供策略支持。整体而言,该应用为HarmonyOS平台上的TCG类应用开发提供了丰富的技术参考和设计范例。
更多推荐




所有评论(0)