在HarmonyOS 6.1.1生态持续繁荣的背景下,ArkTS声明式UI框架为各类垂直领域应用提供了强大的开发能力。本文将深入剖析一款手工皮具定制工坊应用的技术实现,从复古美学设计到完整的电商功能,全方位展示HarmonyOS ArkTS API 24的开发魅力。

马鞍棕与焦糖橙交织的复古色调,手缝工艺与做旧质感的完美融合,这款皮具定制应用将传统工匠精神与现代移动技术相结合,为用户打造沉浸式的手工皮具定制与选购体验。

本文将从接口设计、主题系统、数据建模、动画引擎、组件架构、六大Tab实现等多个维度,系统拆解这款皮具工坊应用的技术内核,为HarmonyOS开发者提供复古风格应用的设计范式与开发参考。

引言

在这里插入图片描述

随着HarmonyOS 6.1.1版本的发布,ArkTS声明式UI框架日臻完善,为开发者提供了更加高效、优雅的应用构建体验。这款手工皮具定制工坊应用正是基于HarmonyOS ArkTS API 24打造的一款垂直领域电商应用,它以"手缝工艺·专属定制"为核心理念,将复古美学与现代交互完美融合。

应用整体采用"LT"前缀的命名规范,以马鞍棕(#8B4513)为主色调,焦糖橙(#D2691E)为强调色,搭配做旧米(#F5EDE0)背景与深棕色文字,营造出浓郁的复古手作氛围。从皮革光泽动画到缝线纹理效果,每一个视觉细节都经过精心打磨,充分展现了ArkTS在自定义动画和视觉效果方面的强大能力。

从业务架构来看,这是一款集皮具选购、专属定制、匠人作品、技艺学习、社区交流于一体的综合性手工皮具平台。六大核心Tab页签——首页、钱包、包袋、表带、护理、我的——构建了完整的用户旅程,从浏览选购到定制服务,从护理保养到社区分享,全方位满足皮具爱好者的需求。

技术实现方面,应用采用了模块化的分层架构:底层是TypeScript接口定义与常量数据,中间层是纯函数工具库(包含动画函数、过滤函数、计算函数、查找函数等),上层是ArkTS组件体系(包含主组件、首页组件、各分类Tab组件、个人中心组件)。这种清晰的分层设计不仅保证了代码的可维护性,也充分利用了ArkTS响应式更新的性能优势。

接口定义与数据模型

在这里插入图片描述

主题与导航接口

应用的接口定义从主题系统开始,为整个应用的视觉一致性奠定了坚实基础。

interface LtTheme {
  bg: string
  primary: string
  accent: string
  card: string
  text: string
  sub: string
  line: string
  glow: string
  dark: string
}

interface LtTab {
  key: string
  label: string
  icon: Resource
}

interface LtWallet {
  name: string
  brand: string
  leather: string
  color: string
  size: number
  pockets: number
  price: number
  stock: number
  sold: number
  tag: string
}

LtTheme接口定义了9个颜色令牌,从背景到文字、从主色到辉光,构成了完整的设计系统。LtTab接口定义了底部导航项的数据结构,包含唯一标识、显示标签和图标资源。LtWallet接口则是钱包产品的数据模型,包含名称、品牌、皮料、颜色、尺寸、卡位、价格、库存、销量和标签等10个属性,全面覆盖了皮具产品的核心特征。

接口驱动开发(Interface-Driven Development)是ArkTS项目的最佳实践之一。先定义数据接口,再实现业务逻辑和UI组件,能够有效减少类型错误,提升代码质量和开发效率。

多品类数据接口

在这里插入图片描述

interface LtBag {
  name: string
  brand: string
  leather: string
  type: string
  capacity: number
  weight: number
  price: number
  stock: number
  sold: number
  tag: string
}

interface LtStrap {
  name: string
  brand: string
  leather: string
  width: number
  length: number
  buckle: string
  price: number
  stock: number
  sold: number
}

interface LtCare {
  name: string
  type: string
  volume: number
  use: string
  price: number
  stock: number
  sold: number
}

除了钱包接口外,应用还定义了包袋(LtBag)、表带(LtStrap)和护理产品(LtCare)三个核心品类接口。每个接口都根据产品特性设计了专属属性:包袋有容量和重量属性,表带有宽度、长度和表扣类型,护理产品有容量和用途。这种领域驱动的接口设计使得数据模型更加精准,也为后续的UI展示和数据处理提供了清晰的语义。

订单、收藏和月度消费等辅助接口则支撑起个人中心的完整功能,形成了从商品展示到交易记录的完整数据闭环。

主题系统与配色设计

在这里插入图片描述

复古配色方案

const LT: LtTheme = {
  bg: '#F5EDE0',
  primary: '#8B4513',
  accent: '#D2691E',
  card: '#FFFFFF',
  text: '#3E2723',
  sub: '#8D6E63',
  line: '#D7CCC8',
  glow: '#BCAAA4',
  dark: '#2E1A0E'
}

const LT_TABS: LtTab[] = [
  { key: 'home', label: '首页', icon: $r('app.media.layered_image') },
  { key: 'wallet', label: '钱包', icon: $r('app.media.layered_image') },
  { key: 'bag', label: '包袋', icon: $r('app.media.layered_image') },
  { key: 'strap', label: '表带', icon: $r('app.media.layered_image') },
  { key: 'care', label: '护理', icon: $r('app.media.layered_image') },
  { key: 'mine', label: '我的', icon: $r('app.media.layered_image') }
]

主题常量LT定义了完整的复古配色体系。背景色采用做旧米(#F5EDE0),温暖而不刺眼;主色马鞍棕(#8B4513)沉稳大气,象征着优质皮革的原色;强调色焦糖橙(#D2691E)活泼温暖,用于突出重要信息和操作按钮;辉光色(#BCAAA4)则为皮革质感增添了层次感。

配色方案的选择直接决定了应用的气质。马鞍棕与焦糖橙的组合不仅符合皮具产品的材质特征,更传递出一种经典、耐用、值得信赖的品牌感觉,这种视觉心理学的运用是产品设计成功的关键因素之一。

Tab配置包含六个导航项,从首页到个人中心,覆盖了完整的用户使用路径。每个Tab都有明确的功能定位,信息架构清晰合理。

数据层设计与业务数据

在这里插入图片描述

产品数据建模

const LT_WALLETS: LtWallet[] = [
  { name: '马鞍长款', brand: '手工坊', leather: '马鞍革', color: '深棕', size: 19, pockets: 12, price: 2680, stock: 8, sold: 156, tag: '经典' },
  { name: '短款两折', brand: '手工坊', leather: '植鞣革', color: '焦糖', size: 11, pockets: 6, price: 1280, stock: 12, sold: 234, tag: '入门' },
  { name: '拉链短款', brand: '皮匠铺', leather: '铬鞣革', color: '黑色', size: 12, pockets: 8, price: 1680, stock: 10, sold: 198, tag: '实用' },
  { name: '三折大容量', brand: '手工坊', leather: '马鞍革', color: '咖啡', size: 14, pockets: 14, price: 3280, stock: 6, sold: 128, tag: '高端' },
  { name: '极简卡包', brand: '皮匠铺', leather: '植鞣革', color: '原色', size: 10, pockets: 5, price: 880, stock: 18, sold: 312, tag: '入门' },
  // ... 更多产品数据
]

钱包数据包含10条记录,涵盖了从入门到高端的完整价格带。皮料类型丰富(马鞍革、植鞣革、铬鞣革、油蜡皮、编织革),标签体系完整(经典、入门、实用、高端、复古、工艺),为筛选功能提供了数据基础。价格从680元到3680元,覆盖了不同消费层级的用户需求。

包袋数据有8条记录,包含手提、斜挎、双肩、手拿、腰包等多种类型;表带数据8条,展示不同宽度、长度和表扣类型的产品;护理产品6条,涵盖保养、护理、防护、清洁、上色、封蜡等全品类。

高质量的模拟数据是前端开发效率的重要保障。在后端接口尚未就绪时,结构完整、数据合理的Mock数据能够让前端开发独立推进,同时也为UI设计提供了真实的展示环境。

动画函数库解析

在这里插入图片描述

皮革光泽与缝线动画

function ltShine(wave: number, i: number): number {
  let phase: number = (wave + i * 40) % 100
  return 0.3 + phase / 100 * 0.7
}

function ltStitch(wave: number, i: number): number {
  let phase: number = (wave + i * 50) % 100
  return Math.min(phase / 100 * 100, 100)
}

function ltVintage(wave: number, i: number): number {
  let v: number = (wave + i * 30) % 200
  return v < 100 ? 1.0 : 0.5
}

function ltBuckleRotate(wave: number): number {
  return Math.sin(wave / 30) * 45
}

function ltFloatY(wave: number, i: number): number {
  return Math.sin(wave / 40 + i) * 6
}

function ltBlink(wave: number, i: number): number {
  let v: number = (wave + i * 50) % 200
  return v < 100 ? 1.0 : 0.4
}

这款皮具应用拥有6个精心设计的动画函数,是四款应用中动画数量较多的之一。ltShine函数模拟皮革表面的光泽流动效果,透明度在0.3到1.0之间平滑变化;ltStitch函数模拟缝线的渐进效果,呈现出手工缝制的视觉韵律;ltVintage函数则产生复古闪烁效果,模拟旧物的质感。

ltBuckleRotate是一个特色动画函数,它利用正弦函数使表扣产生±45度的旋转摆动,为表带页面增添了生动的交互感。ltFloatY函数实现上下浮动效果,ltBlink函数则产生闪烁呼吸效果。

动画设计应当服务于产品主题,而不是为了动画而动画。皮革光泽、缝线韵律、表扣摆动这些动画效果都与皮具产品的特性紧密相关,能够有效增强用户对产品质感的感知,提升品牌形象。

业务工具函数

在这里插入图片描述

function ltFilterWallets(tag: string): LtWallet[] {
  let r: LtWallet[] = []
  for (let i = 0; i < LT_WALLETS.length; i++) {
    if (tag === '全部' || LT_WALLETS[i].tag === tag) {
      r.push(LT_WALLETS[i])
    }
  }
  return r
}

function ltSoldRank(): LtWallet[] {
  let r: LtWallet[] = LT_WALLETS.slice()
  r.sort((a: LtWallet, b: LtWallet) => b.sold - a.sold)
  return r.slice(0, 5)
}

function ltPocketW(pockets: number): number {
  return Math.min(pockets / 16 * 100, 100)
}

function ltStatusColor(status: string): string {
  if (status === '已签收') {
    return LT.primary
  }
  if (status === '定制中') {
    return LT.accent
  }
  if (status === '已退换') {
    return '#999999'
  }
  return LT.sub
}

业务工具函数层承担了数据处理的核心职责。筛选函数ltFilterWallets按标签过滤钱包列表,排序函数ltSoldRank按销量降序返回前5名,计算函数ltPocketW将卡位数转换为百分比宽度用于可视化。

值得关注的是ltStatusColor函数中的订单状态设计:“已签收”、“定制中”、"已退换"三种状态分别对应主色、强调色和灰色。其中"定制中"是这款皮具应用特有的状态,体现了定制业务的核心特征——用户下单后产品进入手工制作阶段,需要一定的制作周期。

订单状态设计是电商应用的核心业务逻辑之一。不同行业的订单状态差异很大,标准化的状态体系能够提升用户体验,减少客服沟通成本。

主组件架构与首页实现

在这里插入图片描述

主组件状态管理

@Entry
@Component
struct LtApp {
  @State tab: string = 'home'
  @State showHomeCustom: boolean = false
  @State showHomeCourse: boolean = false
  @State showHomeCraftsman: boolean = false
  @State showHomeCommunity: boolean = false

  @Builder modalOverlay() {
    Column() {
      Column().width('100%').height('100%').backgroundColor('rgba(46,26,14,0.62)')
    }
    .width('100%').height('100%').position({ x: 0, y: 0 }).zIndex(999)
    .onClick(() => {
      this.showHomeCustom = false
      this.showHomeCourse = false
      this.showHomeCraftsman = false
      this.showHomeCommunity = false
    })
  }

主组件LtApp管理5个状态变量:1个Tab切换状态和4个弹框显示状态。弹框功能包括皮具定制、皮具课堂、匠人作品和皮具社区,这四个功能对应首页的四个快捷入口。遮罩层使用深棕色调的半透明背景(rgba(46,26,14,0.62)),与整体复古色调保持一致。

遮罩层的颜色选择是一个容易被忽略但非常重要的细节。使用与主色调一致的半透明色,而不是通用的黑色半透明,能够让弹框出现时整体画面更加和谐,进一步强化品牌视觉识别。

皮革光泽Banner

          // 皮革光泽 banner
          Stack() {
            Column().width('100%').height(120)
              .linearGradient({ angle: 135, colors: [[LT.primary, 0], [LT.dark, 1]] })
            Column() {
              Text('手缝工艺').fontSize(28).fontWeight(900).fontColor('#F5EDE0').letterSpacing(3)
              Text('手工皮具 · 专属定制').fontSize(12).fontColor('rgba(245,237,224,0.8)').margin({ top: 4 })
            }
            .alignItems(HorizontalAlign.Center).margin({ top: 20 })
            // 光泽流动效果
            Row() {
              ForEach([0, 1, 2, 3], (i: number) => {
                Column().width(40).height(40).borderRadius(20).backgroundColor('rgba(245,237,224,0.15)')
                  .opacity(ltShine(this.wave, i))
                  .margin({ left: 8, right: 8 })
              }, (i: number) => i.toString())
            }
            .margin({ top: 60 })
          }
          .width('100%').height(120)

首页Banner采用皮革光泽效果设计,底层是马鞍棕到深棕色的135度渐变,模拟优质皮革的色彩过渡。标题"手缝工艺"采用大号粗体字,配合字间距调整,传递出工匠精神。光泽流动效果通过四个圆形光斑的透明度变化实现,ltShine函数为每个光斑计算不同的相位,形成流动的光泽感。

做旧纹理仪表盘

          // 做旧纹理仪表盘
          Row() {
            Stack() {
              Column().width(100).height(100).borderRadius(50).border({ width: 4, color: LT.line })
              Column().width(80).height(80).borderRadius(40).backgroundColor(LT.card)
              Column() {
                Text('卡位').fontSize(10).fontColor(LT.sub)
                Text('16').fontSize(24).fontWeight(900).fontColor(LT.primary).margin({ top: 2 })
                Text('个').fontSize(10).fontColor(LT.sub)
              }
              .alignItems(HorizontalAlign.Center)
            }
            .width(110).height(110)
            Column() {
              Text('镇店之宝').fontSize(11).fontColor(LT.sub)
              Text('商务长款').fontSize(20).fontWeight(800).fontColor(LT.dark).margin({ top: 4 })
              Text('马鞍革 | 手缝 | 16卡位').fontSize(11).fontColor(LT.sub).margin({ top: 4 })
              Text('定制价 ¥3,680').fontSize(11).fontColor(LT.accent).fontWeight(600).margin({ top: 2 })
            }
            .alignItems(HorizontalAlign.Start).layoutWeight(1).padding({ left: 12 })
          }
          .width('100%').padding(16).backgroundColor(LT.card).margin({ top: 8 })

镇店之宝仪表盘展示了一款商务长款钱包的核心信息。左侧同心圆设计中,中心显示"16个卡位"的核心卖点,右侧是产品名称、材质、工艺和价格等详细信息。"手缝"二字突出了手工工艺的价值,"定制价"则暗示了这款产品支持个性化定制服务。

应用架构流程图

home

wallet

bag

strap

care

mine

切换

应用入口 LtApp

加载主题与Tab配置

渲染品牌头部

渲染搜索条

Tab状态判断

首页组件

钱包Tab

包袋Tab

表带Tab

护理Tab

我的Tab

皮革光泽Banner

做旧纹理仪表盘

四格统计卡

功能宫格

热销榜单

匠人作品横滑

首页弹框层

皮具定制弹框

皮具课堂弹框

匠人作品弹框

皮具社区弹框

筛选Chips

双列网格

卡位数对比图

价格柱状图

分类弹框层

详情弹框

新增弹框

编辑弹框

删除弹框

会员卡

消费统计

月度消费图

订单列表

收藏列表

设置项

底部Tab栏

钱包Tab与定制功能

筛选与双列网格布局

          // 筛选 chips
          Scroll() {
            Row({ space: 8 }) {
              ForEach(['全部', '经典', '入门', '高端', '复古', '工艺'], (t: string) => {
                Text(t).fontSize(12).fontColor(this.filterTag === t ? LT.card : LT.text)
                  .padding({ left: 16, right: 16, top: 6, bottom: 6 })
                  .borderRadius(16)
                  .backgroundColor(this.filterTag === t ? LT.primary : LT.bg)
                  .onClick(() => {
                    this.filterTag = t
                    this.list = ltFilterWallets(t)
                  })
              }, (t: string) => t)
            }
            .padding({ left: 12, right: 12 })
          }
          .scrollable(ScrollDirection.Horizontal)
          .scrollBar(BarState.Off)
          .width('100%')
          .height(36)

钱包Tab的筛选Chips提供了"全部、经典、入门、高端、复古、工艺"六个标签选项,覆盖了皮具产品的主要分类维度。选中状态通过马鞍棕背景+米白文字的组合来标识,未选中状态则是米色背景+深棕文字,整体色调统一和谐。

筛选标签的设计需要平衡两个目标:一是覆盖用户可能的筛选维度,二是避免标签过多造成选择困难。六个标签的数量处于合理范围,既提供了足够的筛选粒度,又不会让用户感到困惑。

双列网格产品卡片

          // 双列网格
          Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween }) {
            ForEach(this.list, (w: LtWallet, i: number) => {
              Column() {
                Stack() {
                  Column().width('100%').height(70).borderRadius(12)
                    .linearGradient({ angle: 135, colors: [[LT.glow, 0], [LT.primary, 1]] })
                  Column().width(28).height(28).borderRadius(14).backgroundColor('rgba(245,237,224,0.3)')
                    .opacity(ltShine(this.wave, i))
                }
                .width('100%').height(70)
                Text(w.name).fontSize(13).fontWeight(700).fontColor(LT.text).margin({ top: 8 })
                Text(w.leather + ' · ' + w.color).fontSize(10).fontColor(LT.sub).margin({ top: 2 })
                Row() {
                  Text(w.tag).fontSize(10).fontColor(LT.accent).fontWeight(600)
                  Column().layoutWeight(1)
                  Text(w.pockets + '卡位').fontSize(10).fontColor(LT.sub)
                }
                .width('100%').margin({ top: 4 })
                Row() {
                  Text('¥').fontSize(10).fontColor(LT.sub)
                  Text(w.price.toString()).fontSize(14).fontColor(LT.accent).fontWeight(700)
                  Column().layoutWeight(1)
                  Text('库存 ' + w.stock.toString()).fontSize(10).fontColor(LT.sub)
                }
                .width('100%').margin({ top: 4 })
              }
              .width('48.5%')
              .padding(10)
              .backgroundColor(LT.card)
              .borderRadius(12)
              .margin({ bottom: 8 })
              .onClick(() => {
                this.selName = w.name
                this.showDetail = true
              })
            }, (w: LtWallet) => w.name)
          }
          .width('100%').padding({ left: 12, right: 12 })

产品卡片采用双列网格布局,使用Flex组件的wrap: FlexWrap.Wrap属性实现自动换行。每张卡片包含渐变预览图、产品名称、皮料颜色、标签卡位、价格库存等信息,信息密度适中,层次分明。

卡片顶部的渐变区域模拟皮革产品的视觉效果,中心的圆形光斑通过ltShine函数驱动透明度变化,形成皮革光泽流动的视觉效果。这种微动画设计虽然 subtle,但能有效提升界面的精致感和品质感。

产品卡片是电商应用中最常见的UI组件之一,其设计质量直接影响用户的购买决策。信息层级的合理安排、价格的突出展示、库存的适度提示,都是经过深思熟虑的设计决策。

定制功能弹框深度解析

      if (this.showHomeCustom) {
        Stack() {
          this.modalOverlay()
          Column() {
            Text('皮具定制').fontSize(18).fontWeight(700).fontColor(LT.dark).margin({ top: 20 })
            Text('专属定制您的皮具作品').fontSize(12).fontColor(LT.sub).margin({ top: 4 })
            Column() {
              Text('皮料:').fontSize(13).fontColor(LT.text).fontWeight(600).margin({ bottom: 8 })
              Row() {
                Text('马鞍革').fontSize(12).fontColor(LT.card).padding(8).borderRadius(8).backgroundColor(LT.primary)
                Text('植鞣革').fontSize(12).fontColor(LT.text).padding(8).borderRadius(8).backgroundColor(LT.bg).margin({ left: 8 })
                Text('油蜡皮').fontSize(12).fontColor(LT.text).padding(8).borderRadius(8).backgroundColor(LT.bg).margin({ left: 8 })
                Text('编织革').fontSize(12).fontColor(LT.text).padding(8).borderRadius(8).backgroundColor(LT.bg).margin({ left: 8 })
              }
              .width('100%')
            }
            .width('100%').margin({ top: 16 })
            Column() {
              Text('颜色:').fontSize(13).fontColor(LT.text).fontWeight(600).margin({ bottom: 8 })
              Row() {
                Text('深棕').fontSize(12).fontColor(LT.card).padding(8).borderRadius(8).backgroundColor(LT.accent)
                Text('焦糖').fontSize(12).fontColor(LT.text).padding(8).borderRadius(8).backgroundColor(LT.bg).margin({ left: 8 })
                Text('黑棕').fontSize(12).fontColor(LT.text).padding(8).borderRadius(8).backgroundColor(LT.bg).margin({ left: 8 })
                Text('酒红').fontSize(12).fontColor(LT.text).padding(8).borderRadius(8).backgroundColor(LT.bg).margin({ left: 8 })
              }
              .width('100%')
            }
            .width('100%').margin({ top: 12 })
            Column() {
              Text('缝线工艺:').fontSize(13).fontColor(LT.text).fontWeight(600).margin({ bottom: 8 })
              Row() {
                Text('手缝').fontSize(12).fontColor(LT.card).padding(8).borderRadius(8).backgroundColor(LT.primary)
                Text('机缝').fontSize(12).fontColor(LT.text).padding(8).borderRadius(8).backgroundColor(LT.bg).margin({ left: 8 })
              }
              .width('100%')
            }
            .width('100%').margin({ top: 12 })
            Row() {
              Text('定制费:').fontSize(13).fontColor(LT.text).fontWeight(600)
              Text('¥800起').fontSize(18).fontWeight(800).fontColor(LT.accent)
              Column().layoutWeight(1)
              Text('立即定制').fontSize(13).fontColor(LT.card).padding({ left: 20, right: 20, top: 8, bottom: 8 }).borderRadius(20).backgroundColor(LT.primary)
            }
            .width('100%').margin({ top: 20 })
          }
          .width('86%').backgroundColor(LT.card).borderRadius(16).padding(20)
          .constraintSize({ maxHeight: '80%' }).zIndex(1000)
        }
        .width('100%').height('100%')
      }

皮具定制弹框是应用的核心功能之一,体现了手工皮具的业务特色。弹框包含三个核心定制维度:皮料选择(马鞍革、植鞣革、油蜡皮、编织革)、颜色选择(深棕、焦糖、黑棕、酒红)和缝线工艺(手缝、机缝)。每个维度都提供多个选项,用户可以根据自己的喜好进行组合定制。

定制费采用"¥800起"的定价方式,既给出了价格预期,又为复杂定制留出了议价空间。"立即定制"按钮使用主色调,引导用户进行下一步操作。

定制功能的UI设计需要在灵活性和易用性之间找到平衡。提供太多选项会让用户不知所措,选项太少又无法满足个性化需求。三个维度、每维度3-4个选项是一个比较合理的配置,既能体现定制感,又不会过度增加决策负担。

"我的"Tab与会员体系

会员卡与消费统计

          // 会员卡
          Stack() {
            Column().width('100%').height(120).borderRadius(16)
              .linearGradient({ angle: 135, colors: [[LT.dark, 0], [LT.primary, 1]] })
            Column() {
              Text('LEATHER VIP').fontSize(22).fontWeight(900).fontColor('#F5EDE0').letterSpacing(2)
              Text('手工皮具会员').fontSize(12).fontColor('rgba(245,237,224,0.7)').margin({ top: 4 })
              Row() {
                Text('积分').fontSize(11).fontColor('rgba(245,237,224,0.7)')
                Text('8,680').fontSize(20).fontWeight(800).fontColor('#F5EDE0').margin({ left: 8 })
              }
              .margin({ top: 12 })
            }
            .alignItems(HorizontalAlign.Center).margin({ top: 24 })
            Column().width(60).height(60).borderRadius(30).backgroundColor('rgba(245,237,224,0.15)')
              .opacity(ltVintage(this.wave, 0))
              .position({ x: 280, y: 16 })
          }
          .width('100%').height(120).padding({ left: 16, right: 16, top: 16 })

会员卡设计延续了整体的复古风格,采用深棕到马鞍棕的渐变背景,配合"LEATHER VIP"的英文字样,传递出高端会员的尊贵感。积分数字"8,680"使用大号白色字体,清晰醒目。右上角的圆形光晕使用ltVintage函数驱动,产生复古闪烁的效果。

会员体系是电商应用提高用户留存的重要手段。通过积分、等级、专属权益等设计,可以有效提升用户的粘性和复购率。在UI层面,会员卡的视觉设计直接影响用户对会员价值的感知。

订单与个人设置

          // 订单列表
          Column() {
            Text('我的订单').fontSize(13).fontWeight(700).fontColor(LT.dark).margin({ bottom: 8 })
            ForEach(this.orders, (o: LtOrder, i: number) => {
              Row() {
                Column() {
                  Text(o.item).fontSize(13).fontWeight(600).fontColor(LT.text)
                  Text(o.id + ' · ' + o.date).fontSize(10).fontColor(LT.sub).margin({ top: 2 })
                }
                .alignItems(HorizontalAlign.Start).layoutWeight(1)
                Column() {
                  Text('¥' + o.amount.toString()).fontSize(13).fontWeight(700).fontColor(LT.text)
                  Text(o.status).fontSize(10).fontColor(ltStatusColor(o.status)).margin({ top: 2 })
                }
                .alignItems(HorizontalAlign.End)
              }
              .width('100%').padding({ top: 8, bottom: 8 })
              .border({ width: { bottom: 1 }, color: LT.line })
            }, (o: LtOrder) => o.id)
          }
          .width('94%').padding(16).backgroundColor(LT.card).borderRadius(12).margin({ top: 8 })

订单列表采用经典的左右布局,左侧显示商品名称和订单号,右侧显示金额和状态。状态颜色通过ltStatusColor函数动态计算,"定制中"状态使用焦糖橙色,既醒目又与整体色调协调。

设置列表包含昵称、收货地址、关于我们三个选项,采用标准的列表项样式,点击后分别弹出对应的设置弹框。

技术点对比表格

技术维度 皮具工坊 (LT) 盆景艺术 (BN) 纹身潮流 (IW) 玻璃艺术 (VG)
设计风格 复古手作风 新中式禅意 潮流纹身风 彩绘玻璃风
背景色系 做旧米 #F5EDE0 米白 #F5F2E8 深紫黑 #0F0A1A 淡紫 #F1EFF7
主色调 马鞍棕 #8B4513 苔藓绿 #5B7C5A 荧光青 #00FFFF 靛蓝 #4338CA
强调色 焦糖橙 #D2691E 陶土棕 #B5651D 电粉 #FF1493 琥珀金 #D97706
动画数量 6个 4个 6个 4个
特色动画 皮革光泽/缝线/表扣旋转 水墨晕染/摇摆 墨水滴落/旋转辉光 光线折射/玻璃光泽
商品品类 钱包/包袋/表带/护理 松柏/杂木/山水/盆器 纹身贴/工具/护理/图案 玻璃/作品/工具/课程
首页功能 定制/课堂/匠人/社区 展览/课堂/拍卖/社区 定制/课堂/店铺/社区 (多样化入口)
订单状态 已签收/定制中/已退换 已签收/养护中/已退换 已签收/运输中/已退换 已完成/制作中/待开课
特色亮点 手工定制+匠人体系 东方禅意+艺术展览 深色主题+荧光效果 彩色玻璃+光效

安装DevEco Studio程序

在这里插入图片描述
选择目标安装目录:

在这里插入图片描述
设置环境变量,但是需要重启一下:

在这里插入图片描述
新建一个空白模板:

在这里插入图片描述
设置API为24的模板项目:
在这里插入图片描述
初始化项目,自动下载相关依赖:

在这里插入图片描述


完整代码:

// LT 前缀 | 马鞍棕×焦糖橙×做旧米 | 复古手作风 | 浅色暖调 | 6 tab

// ============ 接口定义 ============
interface LtTheme {
  bg: string
  primary: string
  accent: string
  card: string
  text: string
  sub: string
  line: string
  glow: string
  dark: string
}

interface LtTab {
  key: string
  label: string
  icon: Resource
}

interface LtWallet {
  name: string
  brand: string
  leather: string
  color: string
  size: number
  pockets: number
  price: number
  stock: number
  sold: number
  tag: string
}

interface LtBag {
  name: string
  brand: string
  leather: string
  type: string
  capacity: number
  weight: number
  price: number
  stock: number
  sold: number
  tag: string
}

interface LtStrap {
  name: string
  brand: string
  leather: string
  width: number
  length: number
  buckle: string
  price: number
  stock: number
  sold: number
}

interface LtCare {
  name: string
  type: string
  volume: number
  use: string
  price: number
  stock: number
  sold: number
}

interface LtOrder {
  id: string
  item: string
  date: string
  status: string
  amount: number
}

interface LtCollect {
  name: string
  brand: string
  price: number
  tag: string
}

interface LtMonth {
  month: string
  amount: number
}

// ============ 主题常量 ============
const LT: LtTheme = {
  bg: '#F5EDE0',
  primary: '#8B4513',
  accent: '#D2691E',
  card: '#FFFFFF',
  text: '#3E2723',
  sub: '#8D6E63',
  line: '#D7CCC8',
  glow: '#BCAAA4',
  dark: '#2E1A0E'
}

const LT_TABS: LtTab[] = [
  { key: 'home', label: '首页', icon: $r('app.media.layered_image') },
  { key: 'wallet', label: '钱包', icon: $r('app.media.layered_image') },
  { key: 'bag', label: '包袋', icon: $r('app.media.layered_image') },
  { key: 'strap', label: '表带', icon: $r('app.media.layered_image') },
  { key: 'care', label: '护理', icon: $r('app.media.layered_image') },
  { key: 'mine', label: '我的', icon: $r('app.media.layered_image') }
]

// ============ 数据:钱包 10 条 ============
const LT_WALLETS: LtWallet[] = [
  { name: '马鞍长款', brand: '手工坊', leather: '马鞍革', color: '深棕', size: 19, pockets: 12, price: 2680, stock: 8, sold: 156, tag: '经典' },
  { name: '短款两折', brand: '手工坊', leather: '植鞣革', color: '焦糖', size: 11, pockets: 6, price: 1280, stock: 12, sold: 234, tag: '入门' },
  { name: '拉链短款', brand: '皮匠铺', leather: '铬鞣革', color: '黑色', size: 12, pockets: 8, price: 1680, stock: 10, sold: 198, tag: '实用' },
  { name: '三折大容量', brand: '手工坊', leather: '马鞍革', color: '咖啡', size: 14, pockets: 14, price: 3280, stock: 6, sold: 128, tag: '高端' },
  { name: '极简卡包', brand: '皮匠铺', leather: '植鞣革', color: '原色', size: 10, pockets: 5, price: 880, stock: 18, sold: 312, tag: '入门' },
  { name: '商务长款', brand: '手工坊', leather: '马鞍革', color: '黑棕', size: 20, pockets: 16, price: 3680, stock: 5, sold: 89, tag: '高端' },
  { name: '复古双折', brand: '皮匠铺', leather: '油蜡皮', color: '复古棕', size: 12, pockets: 7, price: 1480, stock: 14, sold: 167, tag: '复古' },
  { name: '编织短款', brand: '手工坊', leather: '编织革', color: '蜜色', size: 11, pockets: 6, price: 1880, stock: 9, sold: 145, tag: '工艺' },
  { name: '手抓长款', brand: '皮匠铺', leather: '植鞣革', color: '焦糖', size: 18, pockets: 10, price: 2280, stock: 7, sold: 112, tag: '经典' },
  { name: '迷你零钱包', brand: '手工坊', leather: '铬鞣革', color: '酒红', size: 9, pockets: 3, price: 680, stock: 20, sold: 378, tag: '入门' }
]

// ============ 数据:包袋 8 条 ============
const LT_BAGS: LtBag[] = [
  { name: '托特手提', brand: '手工坊', leather: '马鞍革', type: '手提', capacity: 15, weight: 0.85, price: 4680, stock: 6, sold: 128, tag: '经典' },
  { name: '单肩斜挎', brand: '皮匠铺', leather: '植鞣革', type: '斜挎', capacity: 8, weight: 0.52, price: 2680, stock: 10, sold: 198, tag: '日常' },
  { name: '商务公文', brand: '手工坊', leather: '铬鞣革', type: '手提', capacity: 20, weight: 1.2, price: 5880, stock: 4, sold: 78, tag: '高端' },
  { name: '邮差包', brand: '皮匠铺', leather: '油蜡皮', type: '斜挎', capacity: 12, weight: 0.68, price: 3280, stock: 8, sold: 156, tag: '复古' },
  { name: '双肩背包', brand: '手工坊', leather: '马鞍革', type: '双肩', capacity: 25, weight: 1.5, price: 5280, stock: 5, sold: 89, tag: '高端' },
  { name: '手拿信封', brand: '皮匠铺', leather: '植鞣革', type: '手拿', capacity: 5, weight: 0.3, price: 1880, stock: 12, sold: 234, tag: '入门' },
  { name: '腰包小包', brand: '手工坊', leather: '编织革', type: '腰包', capacity: 4, weight: 0.25, price: 1480, stock: 15, sold: 267, tag: '实用' },
  { name: '波士顿包', brand: '皮匠铺', leather: '油蜡皮', type: '手提', capacity: 18, weight: 1.1, price: 4280, stock: 6, sold: 112, tag: '经典' }
]

// ============ 数据:表带 8 条 ============
const LT_STRAPS: LtStrap[] = [
  { name: '经典马鞍带', brand: '手工坊', leather: '马鞍革', width: 22, length: 120, buckle: '针扣', price: 880, stock: 14, sold: 234 },
  { name: '植鞣原色带', brand: '皮匠铺', leather: '植鞣革', width: 20, length: 115, buckle: '针扣', price: 680, stock: 18, sold: 312 },
  { name: '油蜡复古带', brand: '手工坊', leather: '油蜡皮', width: 22, length: 120, buckle: '蝴蝶扣', price: 980, stock: 10, sold: 178 },
  { name: '编织手环带', brand: '皮匠铺', leather: '编织革', width: 18, length: 110, buckle: '针扣', price: 780, stock: 12, sold: 198 },
  { name: '宽版军装带', brand: '手工坊', leather: '马鞍革', width: 26, length: 125, buckle: '针扣', price: 1080, stock: 8, sold: 156 },
  { name: '细款女装带', brand: '皮匠铺', leather: '植鞣革', width: 14, length: 105, buckle: '珠宝扣', price: 880, stock: 15, sold: 224 },
  { name: '做旧咖啡带', brand: '手工坊', leather: '油蜡皮', width: 22, length: 120, buckle: '蝴蝶扣', price: 1180, stock: 7, sold: 134 },
  { name: '极简黑棕带', brand: '皮匠铺', leather: '铬鞣革', width: 20, length: 115, buckle: '针扣', price: 780, stock: 16, sold: 267 }
]

// ============ 数据:护理 6 条 ============
const LT_CARE: LtCare[] = [
  { name: '蜂蜡保养膏', type: '保养', volume: 50, use: '滋润防水', price: 168, stock: 30, sold: 456 },
  { name: '貂油护理液', type: '护理', volume: 100, use: '深层滋养', price: 128, stock: 25, sold: 378 },
  { name: '防水喷雾', type: '防护', volume: 200, use: '防水防污', price: 98, stock: 35, sold: 312 },
  { name: '清洁泡沫', type: '清洁', volume: 150, use: '温和清洁', price: 88, stock: 28, sold: 289 },
  { name: '上色蜡', type: '上色', volume: 30, use: '补色修复', price: 148, stock: 20, sold: 198 },
  { name: '做旧封蜡', type: '封蜡', volume: 40, use: '固定做旧', price: 188, stock: 15, sold: 156 }
]

// ============ 数据:订单 6 条 ============
const LT_ORDERS: LtOrder[] = [
  { id: 'LT20260812', item: '马鞍长款钱包', date: '08-12', status: '已签收', amount: 2680 },
  { id: 'LT20260808', item: '蜂蜡保养膏', date: '08-08', status: '已签收', amount: 168 },
  { id: 'LT20260730', item: '托特手提包', date: '07-30', status: '定制中', amount: 4680 },
  { id: 'LT20260722', item: '经典马鞍表带', date: '07-22', status: '已签收', amount: 880 },
  { id: 'LT20260715', item: '商务公文包', date: '07-15', status: '已签收', amount: 5880 },
  { id: 'LT20260701', item: '极简卡包', date: '07-01', status: '已退换', amount: 880 }
]

// ============ 数据:收藏 6 条 ============
const LT_COLLECTS: LtCollect[] = [
  { name: '商务长款钱包', brand: '手工坊', price: 3680, tag: '高端' },
  { name: '双肩背包', brand: '手工坊', price: 5280, tag: '高端' },
  { name: '做旧咖啡表带', brand: '手工坊', price: 1180, tag: '复古' },
  { name: '商务公文包', brand: '手工坊', price: 5880, tag: '高端' },
  { name: '三折大容量钱包', brand: '手工坊', price: 3280, tag: '高端' },
  { name: '波士顿包', brand: '皮匠铺', price: 4280, tag: '经典' }
]

// ============ 数据:月度消费 6 条 ============
const LT_MONTHS: LtMonth[] = [
  { month: '03月', amount: 2680 },
  { month: '04月', amount: 1480 },
  { month: '05月', amount: 4680 },
  { month: '06月', amount: 3280 },
  { month: '07月', amount: 6440 },
  { month: '08月', amount: 2848 }
]

// ============ 全局纯函数 ============
function ltShine(wave: number, i: number): number {
  let phase: number = (wave + i * 40) % 100
  return 0.3 + phase / 100 * 0.7
}

function ltStitch(wave: number, i: number): number {
  let phase: number = (wave + i * 50) % 100
  return Math.min(phase / 100 * 100, 100)
}

function ltVintage(wave: number, i: number): number {
  let v: number = (wave + i * 30) % 200
  return v < 100 ? 1.0 : 0.5
}

function ltBuckleRotate(wave: number): number {
  return Math.sin(wave / 30) * 45
}

function ltFloatY(wave: number, i: number): number {
  return Math.sin(wave / 40 + i) * 6
}

function ltBlink(wave: number, i: number): number {
  let v: number = (wave + i * 50) % 200
  return v < 100 ? 1.0 : 0.4
}

function ltFilterWallets(tag: string): LtWallet[] {
  let r: LtWallet[] = []
  for (let i = 0; i < LT_WALLETS.length; i++) {
    if (tag === '全部' || LT_WALLETS[i].tag === tag) {
      r.push(LT_WALLETS[i])
    }
  }
  return r
}

function ltFilterBags(tag: string): LtBag[] {
  let r: LtBag[] = []
  for (let i = 0; i < LT_BAGS.length; i++) {
    if (tag === '全部' || LT_BAGS[i].tag === tag) {
      r.push(LT_BAGS[i])
    }
  }
  return r
}

function ltSoldRank(): LtWallet[] {
  let r: LtWallet[] = LT_WALLETS.slice()
  r.sort((a: LtWallet, b: LtWallet) => b.sold - a.sold)
  return r.slice(0, 5)
}

function ltPocketW(pockets: number): number {
  return Math.min(pockets / 16 * 100, 100)
}

function ltWidthW(width: number): number {
  return Math.min(width / 26 * 100, 100)
}

function ltLengthW(length: number): number {
  return Math.min(length / 125 * 100, 100)
}

function ltPriceW(p: number): number {
  return Math.min(p / 5880 * 100, 100)
}

function ltCapacityW(c: number): number {
  return Math.min(c / 25 * 100, 100)
}

function ltVolumeW(v: number): number {
  return Math.min(v / 200 * 100, 100)
}

function ltMonthH(amount: number): number {
  let max: number = 6440
  return Math.max(amount / max * 120, 8)
}

function ltTopN(i: number): string {
  return ['1', '2', '3', '4', '5'][i] || ''
}

function ltStatusColor(status: string): string {
  if (status === '已签收') {
    return LT.primary
  }
  if (status === '定制中') {
    return LT.accent
  }
  if (status === '已退换') {
    return '#999999'
  }
  return LT.sub
}

function ltWalletByName(name: string): LtWallet {
  let idx: number = 0
  for (let i = 0; i < LT_WALLETS.length; i++) {
    if (LT_WALLETS[i].name === name) {
      idx = i
    }
  }
  return LT_WALLETS[idx]
}

function ltBagByName(name: string): LtBag {
  let idx: number = 0
  for (let i = 0; i < LT_BAGS.length; i++) {
    if (LT_BAGS[i].name === name) {
      idx = i
    }
  }
  return LT_BAGS[idx]
}

function ltStrapByName(name: string): LtStrap {
  let idx: number = 0
  for (let i = 0; i < LT_STRAPS.length; i++) {
    if (LT_STRAPS[i].name === name) {
      idx = i
    }
  }
  return LT_STRAPS[idx]
}

function ltCareByName(name: string): LtCare {
  let idx: number = 0
  for (let i = 0; i < LT_CARE.length; i++) {
    if (LT_CARE[i].name === name) {
      idx = i
    }
  }
  return LT_CARE[idx]
}

// ============ 主组件 ============
@Entry
@Component
struct LtApp {
  @State tab: string = 'home'
  @State showHomeCustom: boolean = false
  @State showHomeCourse: boolean = false
  @State showHomeCraftsman: boolean = false
  @State showHomeCommunity: boolean = false

  @Builder modalOverlay() {
    Column() {
      Column().width('100%').height('100%').backgroundColor('rgba(46,26,14,0.62)')
    }
    .width('100%').height('100%').position({ x: 0, y: 0 }).zIndex(999)
    .onClick(() => {
      this.showHomeCustom = false
      this.showHomeCourse = false
      this.showHomeCraftsman = false
      this.showHomeCommunity = false
    })
  }

  build() {
    Stack() {
      Column() {
        // 头部品牌栏
        Row() {
          Column() {
            Text('皮具').fontSize(22).fontWeight(900).fontColor(LT.dark)
            Text('LEATHER WORKS').fontSize(10).fontColor(LT.sub).letterSpacing(2)
          }
          .alignItems(HorizontalAlign.Start)
          Column().layoutWeight(1)
          Row() {
            Text('手缝工艺').fontSize(12).fontColor(LT.sub)
            Column().width(2).height(12).backgroundColor(LT.line).margin({ left: 8, right: 8 })
            Text('马鞍革').fontSize(10).fontColor(LT.accent).fontWeight(700)
          }
        }
        .width('100%').height(56).padding({ left: 16, right: 16 }).backgroundColor(LT.card)

        // 搜索条
        Row() {
          Row() {
            Text('🔍').fontSize(14).fontColor(LT.sub).margin({ left: 12, right: 8 })
            Text('搜索钱包 / 包袋 / 表带...').fontSize(13).fontColor(LT.sub)
            Column().layoutWeight(1)
            Text().width(28).height(28).borderRadius(14).backgroundColor(LT.primary).onClick(() => {})
          }
          .width('100%').height(40).backgroundColor(LT.bg).borderRadius(20).padding({ left: 4, right: 4 })
        }
        .width('100%').padding({ left: 12, right: 12, top: 8, bottom: 8 })

        // tab 内容区
        Column() {
          if (this.tab === 'home') {
            LtHomeTab({
              onCustom: () => { this.showHomeCustom = true },
              onCourse: () => { this.showHomeCourse = true },
              onCraftsman: () => { this.showHomeCraftsman = true },
              onCommunity: () => { this.showHomeCommunity = true }
            })
          } else if (this.tab === 'wallet') {
            LtWalletTab()
          } else if (this.tab === 'bag') {
            LtBagTab()
          } else if (this.tab === 'strap') {
            LtStrapTab()
          } else if (this.tab === 'care') {
            LtCareTab()
          } else {
            LtMineTab()
          }
        }
        .layoutWeight(1)

        // 底部 tab 栏
        Row() {
          ForEach(LT_TABS, (t: LtTab) => {
            Column() {
              Text(t.icon).width(20).height(20).fontColor(this.tab === t.key ? LT.primary : LT.sub)
              Text(t.label).fontSize(10).fontColor(this.tab === t.key ? LT.primary : LT.sub).fontWeight(this.tab === t.key ? 700 : 400)
            }
            .layoutWeight(1)
            .padding({ top: 6, bottom: 6 })
            .onClick(() => {
              this.tab = t.key
            })
          }, (t: LtTab) => t.key)
        }
        .width('100%').height(52).backgroundColor(LT.card)
        .border({ width: { top: 1 }, color: LT.line })
      }
      .width('100%').height('100%').backgroundColor(LT.bg)

      // 首页弹框
      if (this.showHomeCustom) {
        Stack() {
          this.modalOverlay()
          Column() {
            Text('皮具定制').fontSize(18).fontWeight(700).fontColor(LT.dark).margin({ top: 20 })
            Text('专属定制您的皮具作品').fontSize(12).fontColor(LT.sub).margin({ top: 4 })
            Column() {
              Text('皮料:').fontSize(13).fontColor(LT.text).fontWeight(600).margin({ bottom: 8 })
              Row() {
                Text('马鞍革').fontSize(12).fontColor(LT.card).padding(8).borderRadius(8).backgroundColor(LT.primary)
                Text('植鞣革').fontSize(12).fontColor(LT.text).padding(8).borderRadius(8).backgroundColor(LT.bg).margin({ left: 8 })
                Text('油蜡皮').fontSize(12).fontColor(LT.text).padding(8).borderRadius(8).backgroundColor(LT.bg).margin({ left: 8 })
                Text('编织革').fontSize(12).fontColor(LT.text).padding(8).borderRadius(8).backgroundColor(LT.bg).margin({ left: 8 })
              }
              .width('100%')
            }
            .width('100%').margin({ top: 16 })
            Column() {
              Text('颜色:').fontSize(13).fontColor(LT.text).fontWeight(600).margin({ bottom: 8 })
              Row() {
                Text('深棕').fontSize(12).fontColor(LT.card).padding(8).borderRadius(8).backgroundColor(LT.accent)
                Text('焦糖').fontSize(12).fontColor(LT.text).padding(8).borderRadius(8).backgroundColor(LT.bg).margin({ left: 8 })
                Text('黑棕').fontSize(12).fontColor(LT.text).padding(8).borderRadius(8).backgroundColor(LT.bg).margin({ left: 8 })
                Text('酒红').fontSize(12).fontColor(LT.text).padding(8).borderRadius(8).backgroundColor(LT.bg).margin({ left: 8 })
              }
              .width('100%')
            }
            .width('100%').margin({ top: 12 })
            Column() {
              Text('缝线工艺:').fontSize(13).fontColor(LT.text).fontWeight(600).margin({ bottom: 8 })
              Row() {
                Text('手缝').fontSize(12).fontColor(LT.card).padding(8).borderRadius(8).backgroundColor(LT.primary)
                Text('机缝').fontSize(12).fontColor(LT.text).padding(8).borderRadius(8).backgroundColor(LT.bg).margin({ left: 8 })
              }
              .width('100%')
            }
            .width('100%').margin({ top: 12 })
            Row() {
              Text('定制费:').fontSize(13).fontColor(LT.text).fontWeight(600)
              Text('¥800起').fontSize(18).fontWeight(800).fontColor(LT.accent)
              Column().layoutWeight(1)
              Text('立即定制').fontSize(13).fontColor(LT.card).padding({ left: 20, right: 20, top: 8, bottom: 8 }).borderRadius(20).backgroundColor(LT.primary)
            }
            .width('100%').margin({ top: 20 })
          }
          .width('86%').backgroundColor(LT.card).borderRadius(16).padding(20)
          .constraintSize({ maxHeight: '80%' }).zIndex(1000)
        }
        .width('100%').height('100%')
      }

      if (this.showHomeCourse) {
        Stack() {
          this.modalOverlay()
          Column() {
            Text('皮具课堂').fontSize(18).fontWeight(700).fontColor(LT.dark).margin({ top: 20 })
            Text('学习手工皮具制作技艺').fontSize(12).fontColor(LT.sub).margin({ top: 4 })
            Column() {
              Text('课程一:基础裁切与缝线').fontSize(13).fontColor(LT.text).fontWeight(600).margin({ bottom: 4 })
              Text('讲师:陈师傅 | 3小时 | ¥580').fontSize(11).fontColor(LT.sub)
            }
            .width('100%').padding(12).backgroundColor(LT.bg).borderRadius(12).margin({ top: 16 })
            Column() {
              Text('课程二:皮料选择与鉴别').fontSize(13).fontColor(LT.text).fontWeight(600).margin({ bottom: 4 })
              Text('讲师:周师傅 | 2小时 | ¥480').fontSize(11).fontColor(LT.sub)
            }
            .width('100%').padding(12).backgroundColor(LT.bg).borderRadius(12).margin({ top: 8 })
            Column() {
              Text('课程三:做旧与封蜡技巧').fontSize(13).fontColor(LT.text).fontWeight(600).margin({ bottom: 4 })
              Text('讲师:王师傅 | 1.5小时 | ¥380').fontSize(11).fontColor(LT.sub)
            }
            .width('100%').padding(12).backgroundColor(LT.bg).borderRadius(12).margin({ top: 8 })
            Row() {
              Text().width(28).height(28).borderRadius(14).backgroundColor(LT.sub).onClick(() => { this.showHomeCourse = false })
              Column().layoutWeight(1)
              Text('预约全部').fontSize(13).fontColor(LT.card).padding({ left: 20, right: 20, top: 8, bottom: 8 }).borderRadius(20).backgroundColor(LT.primary)
            }
            .width('100%').margin({ top: 20 })
          }
          .width('86%').backgroundColor(LT.card).borderRadius(16).padding(20)
          .constraintSize({ maxHeight: '80%' }).zIndex(1000)
        }
        .width('100%').height('100%')
      }

      if (this.showHomeCraftsman) {
        Stack() {
          this.modalOverlay()
          Column() {
            Text('匠人作品').fontSize(18).fontWeight(700).fontColor(LT.dark).margin({ top: 20 })
            Text('精选匠人定制系列').fontSize(12).fontColor(LT.sub).margin({ top: 4 })
            Row() {
              ForEach(['#8B4513', '#D2691E', '#BCAAA4', '#2E1A0E'], (c: string) => {
                Column().width(36).height(36).borderRadius(18).backgroundColor(c).margin({ left: 6, right: 6 })
              }, (c: string, i: number) => `${c}_${i}`)
            }
            .margin({ top: 16 })
            Column() {
              Text('当前作品:商务长款钱包').fontSize(14).fontColor(LT.text).fontWeight(600).margin({ bottom: 4 })
              Text('马鞍革 | 手缝 | 16卡位').fontSize(11).fontColor(LT.sub)
            }
            .width('100%').padding(12).backgroundColor(LT.bg).borderRadius(12).margin({ top: 16 })
            Row() {
              Text('定制价:').fontSize(13).fontColor(LT.text).fontWeight(600)
              Text('¥3,680').fontSize(20).fontWeight(800).fontColor(LT.accent)
              Column().layoutWeight(1)
              Text('预约定制').fontSize(13).fontColor(LT.card).padding({ left: 20, right: 20, top: 8, bottom: 8 }).borderRadius(20).backgroundColor(LT.accent)
            }
            .width('100%').margin({ top: 20 })
          }
          .width('86%').backgroundColor(LT.card).borderRadius(16).padding(20)
          .constraintSize({ maxHeight: '80%' }).zIndex(1000)
        }
        .width('100%').height('100%')
      }

      if (this.showHomeCommunity) {
        Stack() {
          this.modalOverlay()
          Column() {
            Text('皮具社区').fontSize(18).fontWeight(700).fontColor(LT.dark).margin({ top: 20 })
            Text('与皮具爱好者交流分享').fontSize(12).fontColor(LT.sub).margin({ top: 4 })
            Column() {
              Text('"养了三个月的马鞍革长款,包浆越来越漂亮"').fontSize(13).fontColor(LT.text).margin({ bottom: 4 })
              Text('@皮革控 | 2小时前').fontSize(11).fontColor(LT.sub)
            }
            .width('100%').padding(12).backgroundColor(LT.bg).borderRadius(12).margin({ top: 16 })
            Column() {
              Text('"第一次尝试手缝,针脚不太均匀但很有成就感"').fontSize(13).fontColor(LT.text).margin({ bottom: 4 })
              Text('@手作新人 | 5小时前').fontSize(11).fontColor(LT.sub)
            }
            .width('100%').padding(12).backgroundColor(LT.bg).borderRadius(12).margin({ top: 8 })
            Column() {
              Text('"油蜡皮做旧效果绝了,复古感拉满"').fontSize(13).fontColor(LT.text).margin({ bottom: 4 })
              Text('@老皮匠 | 1天前').fontSize(11).fontColor(LT.sub)
            }
            .width('100%').padding(12).backgroundColor(LT.bg).borderRadius(12).margin({ top: 8 })
            Row() {
              Text().width(28).height(28).borderRadius(14).backgroundColor(LT.sub).onClick(() => { this.showHomeCommunity = false })
              Column().layoutWeight(1)
              Text('加入社区').fontSize(13).fontColor(LT.card).padding({ left: 20, right: 20, top: 8, bottom: 8 }).borderRadius(20).backgroundColor(LT.primary)
            }
            .width('100%').margin({ top: 20 })
          }
          .width('86%').backgroundColor(LT.card).borderRadius(16).padding(20)
          .constraintSize({ maxHeight: '80%' }).zIndex(1000)
        }
        .width('100%').height('100%')
      }
    }
    .width('100%').height('100%')
  }
}

// ============ Tab 1: 首页 ============
@Component
struct LtHomeTab {
  @State wave: number = 0
  @State walletList: LtWallet[] = []
  @State rankList: LtWallet[] = []
  private timer: number = -1
  @State showDetail: boolean = false
  @State selName: string = ''
  onCustom: () => void = () => {}
  onCourse: () => void = () => {}
  onCraftsman: () => void = () => {}
  onCommunity: () => void = () => {}

  aboutToAppear() {
    this.walletList = LT_WALLETS.slice(0, 4)
    this.rankList = ltSoldRank()
    this.timer = setInterval(() => {
      this.wave += 1
    }, 60)
  }

  aboutToDisappear() {
    if (this.timer !== -1) {
      clearInterval(this.timer)
    }
  }

  @Builder modalOverlay() {
    Column() {
      Column().width('100%').height('100%').backgroundColor('rgba(46,26,14,0.62)')
    }
    .width('100%').height('100%').position({ x: 0, y: 0 }).zIndex(999)
    .onClick(() => {
      this.showDetail = false
    })
  }

  build() {
    Stack() {
      Scroll() {
        Column() {
          // 皮革光泽 banner
          Stack() {
            Column().width('100%').height(120)
              .linearGradient({ angle: 135, colors: [[LT.primary, 0], [LT.dark, 1]] })
            Column() {
              Text('手缝工艺').fontSize(28).fontWeight(900).fontColor('#F5EDE0').letterSpacing(3)
              Text('手工皮具 · 专属定制').fontSize(12).fontColor('rgba(245,237,224,0.8)').margin({ top: 4 })
            }
            .alignItems(HorizontalAlign.Center).margin({ top: 20 })
            // 光泽流动效果
            Row() {
              ForEach([0, 1, 2, 3], (i: number) => {
                Column().width(40).height(40).borderRadius(20).backgroundColor('rgba(245,237,224,0.15)')
                  .opacity(ltShine(this.wave, i))
                  .margin({ left: 8, right: 8 })
              }, (i: number) => i.toString())
            }
            .margin({ top: 60 })
          }
          .width('100%').height(120)

          // 做旧纹理仪表盘
          Row() {
            Stack() {
              Column().width(100).height(100).borderRadius(50).border({ width: 4, color: LT.line })
              Column().width(80).height(80).borderRadius(40).backgroundColor(LT.card)
              Column() {
                Text('卡位').fontSize(10).fontColor(LT.sub)
                Text('16').fontSize(24).fontWeight(900).fontColor(LT.primary).margin({ top: 2 })
                Text('个').fontSize(10).fontColor(LT.sub)
              }
              .alignItems(HorizontalAlign.Center)
            }
            .width(110).height(110)
            Column() {
              Text('镇店之宝').fontSize(11).fontColor(LT.sub)
              Text('商务长款').fontSize(20).fontWeight(800).fontColor(LT.dark).margin({ top: 4 })
              Text('马鞍革 | 手缝 | 16卡位').fontSize(11).fontColor(LT.sub).margin({ top: 4 })
              Text('定制价 ¥3,680').fontSize(11).fontColor(LT.accent).fontWeight(600).margin({ top: 2 })
            }
            .alignItems(HorizontalAlign.Start).layoutWeight(1).padding({ left: 12 })
          }
          .width('100%').padding(16).backgroundColor(LT.card).margin({ top: 8 })

          // 4 格统计卡
          Row() {
            Column() {
              Text('82').fontSize(22).fontWeight(800).fontColor(LT.primary)
              Text('钱包').fontSize(11).fontColor(LT.sub).margin({ top: 2 })
            }
            .layoutWeight(1).alignItems(HorizontalAlign.Center).padding({ top: 12, bottom: 12 })
            Column().width(1).height(36).backgroundColor(LT.line)
            Column() {
              Text('48').fontSize(22).fontWeight(800).fontColor(LT.accent)
              Text('包袋').fontSize(11).fontColor(LT.sub).margin({ top: 2 })
            }
            .layoutWeight(1).alignItems(HorizontalAlign.Center).padding({ top: 12, bottom: 12 })
            Column().width(1).height(36).backgroundColor(LT.line)
            Column() {
              Text('64').fontSize(22).fontWeight(800).fontColor(LT.primary)
              Text('表带').fontSize(11).fontColor(LT.sub).margin({ top: 2 })
            }
            .layoutWeight(1).alignItems(HorizontalAlign.Center).padding({ top: 12, bottom: 12 })
            Column().width(1).height(36).backgroundColor(LT.line)
            Column() {
              Text('36').fontSize(22).fontWeight(800).fontColor(LT.accent)
              Text('护理').fontSize(11).fontColor(LT.sub).margin({ top: 2 })
            }
            .layoutWeight(1).alignItems(HorizontalAlign.Center).padding({ top: 12, bottom: 12 })
          }
          .width('100%').backgroundColor(LT.card).margin({ top: 8 })

          // 功能宫格
          Column() {
            Text('快捷功能').fontSize(14).fontWeight(700).fontColor(LT.dark).margin({ bottom: 12 })
            Row() {
              Column() {
                Column().width(44).height(44).borderRadius(22).backgroundColor(LT.primary)
                Text('定制').fontSize(11).fontColor(LT.text).margin({ top: 4 })
              }
              .layoutWeight(1).alignItems(HorizontalAlign.Center)
              .onClick(() => { this.onCustom() })
              Column() {
                Column().width(44).height(44).borderRadius(22).backgroundColor(LT.accent)
                Text('课堂').fontSize(11).fontColor(LT.text).margin({ top: 4 })
              }
              .layoutWeight(1).alignItems(HorizontalAlign.Center)
              .onClick(() => { this.onCourse() })
              Column() {
                Column().width(44).height(44).borderRadius(22).backgroundColor(LT.glow)
                Text('匠人').fontSize(11).fontColor(LT.text).margin({ top: 4 })
              }
              .layoutWeight(1).alignItems(HorizontalAlign.Center)
              .onClick(() => { this.onCraftsman() })
              Column() {
                Column().width(44).height(44).borderRadius(22).backgroundColor(LT.dark)
                Text('社区').fontSize(11).fontColor(LT.text).margin({ top: 4 })
              }
              .layoutWeight(1).alignItems(HorizontalAlign.Center)
              .onClick(() => { this.onCommunity() })
            }
            .width('100%')
          }
          .width('100%').padding(16).backgroundColor(LT.card).margin({ top: 8 })

          // 热销榜单
          Column() {
            Row() {
              Text('热销榜单').fontSize(14).fontWeight(700).fontColor(LT.dark)
              Column().layoutWeight(1)
              Text('查看全部').fontSize(12).fontColor(LT.sub)
            }
            .width('100%').margin({ bottom: 12 })
            ForEach(this.rankList, (w: LtWallet, i: number) => {
              Row() {
                Text(ltTopN(i)).fontSize(18).fontWeight(900).fontColor(i < 3 ? LT.accent : LT.sub).width(28)
                Column() {
                  Text(w.name).fontSize(14).fontWeight(600).fontColor(LT.text)
                  Text(w.leather + ' · ' + w.color + ' · ' + w.pockets + '卡位').fontSize(11).fontColor(LT.sub).margin({ top: 2 })
                }
                .alignItems(HorizontalAlign.Start).layoutWeight(1)
                Column() {
                  Text('已售 ' + w.sold.toString()).fontSize(12).fontColor(LT.accent).fontWeight(700)
                  Text('库存 ' + w.stock.toString()).fontSize(10).fontColor(LT.sub).margin({ top: 2 })
                }
                .alignItems(HorizontalAlign.End)
              }
              .width('100%').padding({ top: 8, bottom: 8 })
              .border({ width: { bottom: 1 }, color: LT.line })
              .onClick(() => {
                this.selName = w.name
                this.showDetail = true
              })
            }, (w: LtWallet) => w.name)
          }
          .width('100%').padding(16).backgroundColor(LT.card).margin({ top: 8 })

          // 名家横滑
          Column() {
            Text('匠人作品').fontSize(14).fontWeight(700).fontColor(LT.dark).margin({ bottom: 8 })
            Scroll() {
              Row({ space: 12 }) {
                ForEach(LT_WALLETS.slice(5, 10), (w: LtWallet) => {
                  Column() {
                    Column().width(140).height(80).borderRadius(12)
                      .linearGradient({ angle: 135, colors: [[LT.glow, 0], [LT.primary, 1]] })
                      .padding(12)
                    Text(w.name).fontSize(12).fontWeight(600).fontColor(LT.text).margin({ top: 6 })
                    Text(w.leather + ' · ' + w.tag).fontSize(10).fontColor(LT.accent).margin({ top: 2 })
                  }
                  .width(140).alignItems(HorizontalAlign.Start)
                }, (w: LtWallet) => w.name)
              }
              .padding({ left: 4, right: 4 })
            }
            .scrollable(ScrollDirection.Horizontal)
            .scrollBar(BarState.Off)
          }
          .width('100%').padding(16).backgroundColor(LT.card).margin({ top: 8, bottom: 8 })
        }
        .width('100%')
      }
      .scrollBar(BarState.Off)
      .layoutWeight(1)
      .width('100%')

      // 钱包详情弹框
      if (this.showDetail) {
        Stack() {
          this.modalOverlay()
          Column() {
            Text('皮具详情').fontSize(18).fontWeight(700).fontColor(LT.dark).margin({ top: 20 })
            Text(ltWalletByName(this.selName).name).fontSize(16).fontWeight(600).fontColor(LT.text).margin({ top: 8 })
            Text(ltWalletByName(this.selName).leather + ' · ' + ltWalletByName(this.selName).color + ' · ' + ltWalletByName(this.selName).tag)
              .fontSize(12).fontColor(LT.sub).margin({ top: 4 })
            Column() {
              Text('卡位数').fontSize(12).fontColor(LT.sub).margin({ bottom: 4 })
              Stack({ alignContent: Alignment.Start }) {
                Column().width('100%').height(8).borderRadius(4).backgroundColor(LT.bg)
                Column().width(ltPocketW(ltWalletByName(this.selName).pockets).toString() + '%').height(8).borderRadius(4).backgroundColor(LT.primary)
              }
              Text(ltWalletByName(this.selName).pockets.toString() + ' 个').fontSize(12).fontColor(LT.text).fontWeight(600).margin({ top: 4 })
            }
            .width('100%').margin({ top: 16 })
            Column() {
              Text('尺寸').fontSize(12).fontColor(LT.sub).margin({ bottom: 4 })
              Stack({ alignContent: Alignment.Start }) {
                Column().width('100%').height(8).borderRadius(4).backgroundColor(LT.bg)
                Column().width(ltWidthW(ltWalletByName(this.selName).size).toString() + '%').height(8).borderRadius(4).backgroundColor(LT.accent)
              }
              Text(ltWalletByName(this.selName).size.toString() + ' cm').fontSize(12).fontColor(LT.text).fontWeight(600).margin({ top: 4 })
            }
            .width('100%').margin({ top: 12 })
            Row() {
              Column() { Text('价格').fontSize(11).fontColor(LT.sub); Text('¥' + ltWalletByName(this.selName).price.toString()).fontSize(14).fontWeight(700).fontColor(LT.accent) }
              .layoutWeight(1).alignItems(HorizontalAlign.Center)
              Column() { Text('库存').fontSize(11).fontColor(LT.sub); Text(ltWalletByName(this.selName).stock.toString()).fontSize(14).fontWeight(700).fontColor(LT.text) }
              .layoutWeight(1).alignItems(HorizontalAlign.Center)
              Column() { Text('已售').fontSize(11).fontColor(LT.sub); Text(ltWalletByName(this.selName).sold.toString()).fontSize(14).fontWeight(700).fontColor(LT.accent) }
              .layoutWeight(1).alignItems(HorizontalAlign.Center)
            }
            .width('100%').padding(12).backgroundColor(LT.bg).borderRadius(12).margin({ top: 12 })
            Row() {
              Text().width(28).height(28).borderRadius(14).backgroundColor(LT.sub).onClick(() => { this.showDetail = false })
              Column().layoutWeight(1)
              Text('加入收藏').fontSize(13).fontColor(LT.card).padding({ left: 20, right: 20, top: 8, bottom: 8 }).borderRadius(20).backgroundColor(LT.primary)
            }
            .width('100%').margin({ top: 16 })
          }
          .width('86%').backgroundColor(LT.card).borderRadius(16).padding(20)
          .constraintSize({ maxHeight: '80%' }).zIndex(1000)
        }
        .width('100%').height('100%')
      }
    }
    .width('100%').height('100%')
  }
}

// ============ Tab 2: 钱包 ============
@Component
struct LtWalletTab {
  @State wave: number = 0
  @State list: LtWallet[] = []
  @State filterTag: string = '全部'
  private timer: number = -1
  @State showDetail: boolean = false
  @State showAdd: boolean = false
  @State showEdit: boolean = false
  @State showDelete: boolean = false
  @State selName: string = ''

  aboutToAppear() {
    this.list = ltFilterWallets('全部')
    this.timer = setInterval(() => {
      this.wave += 1
    }, 60)
  }

  aboutToDisappear() {
    if (this.timer !== -1) {
      clearInterval(this.timer)
    }
  }

  @Builder modalOverlay() {
    Column() {
      Column().width('100%').height('100%').backgroundColor('rgba(46,26,14,0.62)')
    }
    .width('100%').height('100%').position({ x: 0, y: 0 }).zIndex(999)
    .onClick(() => {
      this.showDetail = false
      this.showAdd = false
      this.showEdit = false
      this.showDelete = false
    })
  }

  build() {
    Stack() {
      Scroll() {
        Column() {
          // 筛选 chips
          Scroll() {
            Row({ space: 8 }) {
              ForEach(['全部', '经典', '入门', '高端', '复古', '工艺'], (t: string) => {
                Text(t).fontSize(12).fontColor(this.filterTag === t ? LT.card : LT.text)
                  .padding({ left: 16, right: 16, top: 6, bottom: 6 })
                  .borderRadius(16)
                  .backgroundColor(this.filterTag === t ? LT.primary : LT.bg)
                  .onClick(() => {
                    this.filterTag = t
                    this.list = ltFilterWallets(t)
                  })
              }, (t: string) => t)
            }
            .padding({ left: 12, right: 12 })
          }
          .scrollable(ScrollDirection.Horizontal)
          .scrollBar(BarState.Off)
          .width('100%')
          .height(36)

          // 双列网格
          Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween }) {
            ForEach(this.list, (w: LtWallet, i: number) => {
              Column() {
                Stack() {
                  Column().width('100%').height(70).borderRadius(12)
                    .linearGradient({ angle: 135, colors: [[LT.glow, 0], [LT.primary, 1]] })
                  Column().width(28).height(28).borderRadius(14).backgroundColor('rgba(245,237,224,0.3)')
                    .opacity(ltShine(this.wave, i))
                }
                .width('100%').height(70)
                Text(w.name).fontSize(13).fontWeight(700).fontColor(LT.text).margin({ top: 8 })
                Text(w.leather + ' · ' + w.color).fontSize(10).fontColor(LT.sub).margin({ top: 2 })
                Row() {
                  Text(w.tag).fontSize(10).fontColor(LT.accent).fontWeight(600)
                  Column().layoutWeight(1)
                  Text(w.pockets + '卡位').fontSize(10).fontColor(LT.sub)
                }
                .width('100%').margin({ top: 4 })
                Row() {
                  Text('¥').fontSize(10).fontColor(LT.sub)
                  Text(w.price.toString()).fontSize(14).fontColor(LT.accent).fontWeight(700)
                  Column().layoutWeight(1)
                  Text('库存 ' + w.stock.toString()).fontSize(10).fontColor(LT.sub)
                }
                .width('100%').margin({ top: 4 })
              }
              .width('48.5%')
              .padding(10)
              .backgroundColor(LT.card)
              .borderRadius(12)
              .margin({ bottom: 8 })
              .onClick(() => {
                this.selName = w.name
                this.showDetail = true
              })
            }, (w: LtWallet) => w.name)
          }
          .width('100%').padding({ left: 12, right: 12 })

          // 卡位数对比
          Column() {
            Text('卡位数对比').fontSize(13).fontWeight(700).fontColor(LT.dark).margin({ bottom: 12 })
            ForEach(this.list.slice(0, 5), (w: LtWallet, i: number) => {
              Row() {
                Text(w.name.substring(0, 6)).fontSize(10).fontColor(LT.sub).width(60)
                Stack({ alignContent: Alignment.Start }) {
                  Column().width('100%').height(10).borderRadius(5).backgroundColor(LT.bg)
                  Column().width(ltPocketW(w.pockets).toString() + '%').height(10).borderRadius(5).backgroundColor(LT.primary)
                }
                .layoutWeight(1)
                Text(w.pockets.toString()).fontSize(10).fontColor(LT.text).fontWeight(600).width(28)
              }
              .width('100%').margin({ bottom: 6 })
            }, (w: LtWallet) => w.name)
          }
          .width('100%').padding(16).backgroundColor(LT.card).margin({ top: 8 })

          // 价格柱状图
          Column() {
            Text('价格对比 (¥)').fontSize(13).fontWeight(700).fontColor(LT.dark).margin({ bottom: 12 })
            Row({ space: 4 }) {
              ForEach(this.list.slice(0, 6), (w: LtWallet, i: number) => {
                Column() {
                  Column().width(24).height(ltPriceW(w.price)).borderRadius(4).backgroundColor(LT.accent)
                  Text(w.price.toString()).fontSize(8).fontColor(LT.sub).margin({ top: 4 })
                }
                .alignItems(HorizontalAlign.Center)
              }, (w: LtWallet) => w.name)
            }
            .width('100%').justifyContent(FlexAlign.SpaceAround)
          }
          .width('100%').padding(16).backgroundColor(LT.card).margin({ top: 8, bottom: 8 })
        }
        .width('100%')
      }
      .scrollBar(BarState.Off)
      .layoutWeight(1)
      .width('100%')

      // 钱包详情弹框
      if (this.showDetail) {
        Stack() {
          this.modalOverlay()
          Column() {
            Text('钱包详情').fontSize(18).fontWeight(700).fontColor(LT.dark).margin({ top: 20 })
            Text(ltWalletByName(this.selName).name).fontSize(16).fontWeight(600).fontColor(LT.text).margin({ top: 8 })
            Text(ltWalletByName(this.selName).leather + ' · ' + ltWalletByName(this.selName).color + ' · ' + ltWalletByName(this.selName).tag)
              .fontSize(12).fontColor(LT.sub).margin({ top: 4 })
            Column() {
              Text('卡位数').fontSize(12).fontColor(LT.sub).margin({ bottom: 4 })
              Stack({ alignContent: Alignment.Start }) {
                Column().width('100%').height(8).borderRadius(4).backgroundColor(LT.bg)
                Column().width(ltPocketW(ltWalletByName(this.selName).pockets).toString() + '%').height(8).borderRadius(4).backgroundColor(LT.primary)
              }
              Text(ltWalletByName(this.selName).pockets.toString() + ' 个').fontSize(12).fontColor(LT.text).fontWeight(600).margin({ top: 4 })
            }
            .width('100%').margin({ top: 16 })
            Column() {
              Text('尺寸').fontSize(12).fontColor(LT.sub).margin({ bottom: 4 })
              Stack({ alignContent: Alignment.Start }) {
                Column().width('100%').height(8).borderRadius(4).backgroundColor(LT.bg)
                Column().width(ltWidthW(ltWalletByName(this.selName).size).toString() + '%').height(8).borderRadius(4).backgroundColor(LT.accent)
              }
              Text(ltWalletByName(this.selName).size.toString() + ' cm').fontSize(12).fontColor(LT.text).fontWeight(600).margin({ top: 4 })
            }
            .width('100%').margin({ top: 12 })
            Row() {
              Column() { Text('价格').fontSize(11).fontColor(LT.sub); Text('¥' + ltWalletByName(this.selName).price.toString()).fontSize(14).fontWeight(700).fontColor(LT.accent) }
              .layoutWeight(1).alignItems(HorizontalAlign.Center)
              Column() { Text('库存').fontSize(11).fontColor(LT.sub); Text(ltWalletByName(this.selName).stock.toString()).fontSize(14).fontWeight(700).fontColor(LT.text) }
              .layoutWeight(1).alignItems(HorizontalAlign.Center)
              Column() { Text('已售').fontSize(11).fontColor(LT.sub); Text(ltWalletByName(this.selName).sold.toString()).fontSize(14).fontWeight(700).fontColor(LT.accent) }
              .layoutWeight(1).alignItems(HorizontalAlign.Center)
            }
            .width('100%').padding(12).backgroundColor(LT.bg).borderRadius(12).margin({ top: 12 })
            Row() {
              Text().width(28).height(28).borderRadius(14).backgroundColor(LT.sub).onClick(() => { this.showDetail = false })
              Column().layoutWeight(1)
              Text('编辑').fontSize(12).fontColor(LT.card).padding({ left: 14, right: 14, top: 8, bottom: 8 }).borderRadius(16).backgroundColor(LT.accent)
                .onClick(() => { this.showDetail = false; this.showEdit = true })
              Text('删除').fontSize(12).fontColor('#FFFFFF').padding({ left: 14, right: 14, top: 8, bottom: 8 }).borderRadius(16).backgroundColor('#CC3333')
                .margin({ left: 8 }).onClick(() => { this.showDetail = false; this.showDelete = true })
            }
            .width('100%').margin({ top: 16 })
          }
          .width('86%').backgroundColor(LT.card).borderRadius(16).padding(20)
          .constraintSize({ maxHeight: '80%' }).zIndex(1000)
        }
        .width('100%').height('100%')
      }

      // 新增弹框
      if (this.showAdd) {
        Stack() {
          this.modalOverlay()
          Column() {
            Text('新增钱包').fontSize(18).fontWeight(700).fontColor(LT.dark).margin({ top: 20 })
            Column() {
              Text('名称').fontSize(12).fontColor(LT.sub).margin({ bottom: 4 })
              Row() {
                Text('请输入钱包名称').fontSize(12).fontColor(LT.sub)
                Column().layoutWeight(1)
              }
              .width('100%').height(36).backgroundColor(LT.bg).borderRadius(8).padding({ left: 12, right: 12 })
            }
            .width('100%').margin({ top: 16 })
            Column() {
              Text('皮料').fontSize(12).fontColor(LT.sub).margin({ bottom: 4 })
              Row() {
                Text('马鞍革').fontSize(12).fontColor(LT.card).padding(8).borderRadius(8).backgroundColor(LT.primary)
                Text('植鞣革').fontSize(12).fontColor(LT.text).padding(8).borderRadius(8).backgroundColor(LT.bg).margin({ left: 8 })
                Text('油蜡皮').fontSize(12).fontColor(LT.text).padding(8).borderRadius(8).backgroundColor(LT.bg).margin({ left: 8 })
                Text('编织革').fontSize(12).fontColor(LT.text).padding(8).borderRadius(8).backgroundColor(LT.bg).margin({ left: 8 })
              }
            }
            .width('100%').margin({ top: 12 })
            Column() {
              Text('颜色').fontSize(12).fontColor(LT.sub).margin({ bottom: 4 })
              Row() {
                Text('深棕').fontSize(12).fontColor(LT.card).padding(8).borderRadius(8).backgroundColor(LT.accent)
                Text('焦糖').fontSize(12).fontColor(LT.text).padding(8).borderRadius(8).backgroundColor(LT.bg).margin({ left: 8 })
                Text('黑棕').fontSize(12).fontColor(LT.text).padding(8).borderRadius(8).backgroundColor(LT.bg).margin({ left: 8 })
                Text('酒红').fontSize(12).fontColor(LT.text).padding(8).borderRadius(8).backgroundColor(LT.bg).margin({ left: 8 })
              }
            }
            .width('100%').margin({ top: 12 })
            Row() {
              Text().width(28).height(28).borderRadius(14).backgroundColor(LT.sub).onClick(() => { this.showAdd = false })
              Column().layoutWeight(1)
              Text('确认新增').fontSize(13).fontColor(LT.card).padding({ left: 20, right: 20, top: 8, bottom: 8 }).borderRadius(20).backgroundColor(LT.primary)
                .onClick(() => { this.showAdd = false })
            }
            .width('100%').margin({ top: 20 })
          }
          .width('86%').backgroundColor(LT.card).borderRadius(16).padding(20)
          .constraintSize({ maxHeight: '80%' }).zIndex(1000)
        }
        .width('100%').height('100%')
      }

      // 编辑弹框
      if (this.showEdit) {
        Stack() {
          this.modalOverlay()
          Column() {
            Text('编辑钱包').fontSize(18).fontWeight(700).fontColor(LT.dark).margin({ top: 20 })
            Text('当前:' + this.selName).fontSize(12).fontColor(LT.sub).margin({ top: 8 })
            Column() {
              Text('修改名称').fontSize(12).fontColor(LT.sub).margin({ bottom: 4 })
              Row() {
                Text(this.selName).fontSize(12).fontColor(LT.text)
                Column().layoutWeight(1)
              }
              .width('100%').height(36).backgroundColor(LT.bg).borderRadius(8).padding({ left: 12, right: 12 })
            }
            .width('100%').margin({ top: 16 })
            Column() {
              Text('修改皮料').fontSize(12).fontColor(LT.sub).margin({ bottom: 4 })
              Row() {
                Text('马鞍革').fontSize(12).fontColor(LT.card).padding(8).borderRadius(8).backgroundColor(LT.accent)
                Text('植鞣革').fontSize(12).fontColor(LT.text).padding(8).borderRadius(8).backgroundColor(LT.bg).margin({ left: 8 })
                Text('油蜡皮').fontSize(12).fontColor(LT.text).padding(8).borderRadius(8).backgroundColor(LT.bg).margin({ left: 8 })
              }
            }
            .width('100%').margin({ top: 12 })
            Row() {
              Text().width(28).height(28).borderRadius(14).backgroundColor(LT.sub).onClick(() => { this.showEdit = false })
              Column().layoutWeight(1)
              Text('保存修改').fontSize(13).fontColor(LT.card).padding({ left: 20, right: 20, top: 8, bottom: 8 }).borderRadius(20).backgroundColor(LT.primary)
                .onClick(() => { this.showEdit = false })
            }
            .width('100%').margin({ top: 20 })
          }
          .width('86%').backgroundColor(LT.card).borderRadius(16).padding(20)
          .constraintSize({ maxHeight: '80%' }).zIndex(1000)
        }
        .width('100%').height('100%')
      }

      // 删除弹框
      if (this.showDelete) {
        Stack() {
          this.modalOverlay()
          Column() {
            Text('确认删除').fontSize(18).fontWeight(700).fontColor('#CC3333').margin({ top: 20 })
            Text('删除后不可恢复').fontSize(12).fontColor(LT.sub).margin({ top: 8 })
            Column() {
              Text('删除目标').fontSize(12).fontColor(LT.sub).margin({ bottom: 4 })
              Text(this.selName).fontSize(16).fontWeight(700).fontColor(LT.text)
            }
            .width('100%').padding(16).backgroundColor('#FFF8F0').borderRadius(12)
            .border({ width: 1, color: '#E0C0A0' }).margin({ top: 16 })
            Row() {
              Text().width(28).height(28).borderRadius(14).backgroundColor(LT.sub).onClick(() => { this.showDelete = false })
              Column().layoutWeight(1)
              Text('取消').fontSize(13).fontColor(LT.text).padding({ left: 20, right: 20, top: 8, bottom: 8 }).borderRadius(20).backgroundColor(LT.bg)
                .onClick(() => { this.showDelete = false })
              Text('确认删除').fontSize(13).fontColor('#FFFFFF').padding({ left: 20, right: 20, top: 8, bottom: 8 }).borderRadius(20).backgroundColor('#CC3333')
                .margin({ left: 8 }).onClick(() => { this.showDelete = false })
            }
            .width('100%').margin({ top: 20 })
          }
          .width('86%').backgroundColor(LT.card).borderRadius(16).padding(20)
          .constraintSize({ maxHeight: '80%' }).zIndex(1000)
        }
        .width('100%').height('100%')
      }
    }
    .width('100%').height('100%')
  }
}

// ============ Tab 3: 包袋 ============
@Component
struct LtBagTab {
  @State wave: number = 0
  @State list: LtBag[] = []
  @State filterTag: string = '全部'
  private timer: number = -1
  @State showDetail: boolean = false
  @State showAdd: boolean = false
  @State showEdit: boolean = false
  @State selName: string = ''

  aboutToAppear() {
    this.list = ltFilterBags('全部')
    this.timer = setInterval(() => {
      this.wave += 1
    }, 60)
  }

  aboutToDisappear() {
    if (this.timer !== -1) {
      clearInterval(this.timer)
    }
  }

  @Builder modalOverlay() {
    Column() {
      Column().width('100%').height('100%').backgroundColor('rgba(46,26,14,0.62)')
    }
    .width('100%').height('100%').position({ x: 0, y: 0 }).zIndex(999)
    .onClick(() => {
      this.showDetail = false
      this.showAdd = false
      this.showEdit = false
    })
  }

  build() {
    Stack() {
      Scroll() {
        Column() {
          // 排行榜
          Column() {
            Text('包袋热销榜').fontSize(14).fontWeight(700).fontColor(LT.dark).margin({ bottom: 12 })
            ForEach(this.list, (b: LtBag, i: number) => {
              Row() {
                Text(ltTopN(i)).fontSize(20).fontWeight(900).fontColor(i < 3 ? LT.accent : LT.sub).width(28)
                Column() {
                  Column().width(56).height(56).borderRadius(12).backgroundColor(LT.bg)
                    .border({ width: 1, color: LT.line })
                  Text(b.type).fontSize(10).fontColor(LT.primary).fontWeight(600).margin({ top: 2 })
                }
                .width(56).alignItems(HorizontalAlign.Center)
                Column() {
                  Text(b.name).fontSize(14).fontWeight(700).fontColor(LT.text)
                  Text(b.brand + ' · ' + b.leather).fontSize(10).fontColor(LT.sub).margin({ top: 2 })
                  Row() {
                    Text(b.tag).fontSize(10).fontColor(LT.primary).padding({ left: 6, right: 6, top: 2, bottom: 2 }).borderRadius(4).backgroundColor(LT.bg)
                    Column().layoutWeight(1)
                    Text(b.capacity + 'L').fontSize(10).fontColor(LT.sub)
                  }
                  .width('100%').margin({ top: 4 })
                  Row() {
                    Text('¥').fontSize(10).fontColor(LT.sub)
                    Text(b.price.toString()).fontSize(12).fontColor(LT.accent).fontWeight(700)
                    Column().layoutWeight(1)
                    Text(b.weight + 'kg').fontSize(10).fontColor(LT.sub)
                  }
                  .width('100%').margin({ top: 4 })
                }
                .alignItems(HorizontalAlign.Start).layoutWeight(1).padding({ left: 12 })
                Column() {
                  Text('已售').fontSize(10).fontColor(LT.sub)
                  Text(b.sold.toString()).fontSize(14).fontWeight(700).fontColor(LT.accent)
                }
                .alignItems(HorizontalAlign.End)
              }
              .width('100%').padding(12).backgroundColor(LT.card).borderRadius(12).margin({ bottom: 8 })
              .onClick(() => {
                this.selName = b.name
                this.showDetail = true
              })
            }, (b: LtBag) => b.name)
          }
          .width('100%').padding(16).backgroundColor(LT.bg)

          // 容量对比
          Column() {
            Text('容量对比 (L)').fontSize(13).fontWeight(700).fontColor(LT.dark).margin({ bottom: 12 })
            ForEach(this.list.slice(0, 5), (b: LtBag, i: number) => {
              Row() {
                Text(b.name.substring(0, 6)).fontSize(10).fontColor(LT.sub).width(56)
                Stack({ alignContent: Alignment.Start }) {
                  Column().width('100%').height(10).borderRadius(5).backgroundColor(LT.bg)
                  Column().width(ltCapacityW(b.capacity).toString() + '%').height(10).borderRadius(5).backgroundColor(LT.primary)
                }
                .layoutWeight(1)
                Text(b.capacity.toString()).fontSize(10).fontColor(LT.text).fontWeight(600).width(28)
              }
              .width('100%').margin({ bottom: 6 })
            }, (b: LtBag) => b.name)
          }
          .width('100%').padding(16).backgroundColor(LT.card).margin({ top: 8 })

          // 重量对比
          Column() {
            Text('重量对比 (kg)').fontSize(13).fontWeight(700).fontColor(LT.dark).margin({ bottom: 12 })
            Row({ space: 6 }) {
              ForEach(this.list, (b: LtBag, i: number) => {
                Column() {
                  Column().width(28).height(b.weight * 40).borderRadius(4).backgroundColor(LT.accent)
                  Text(b.weight.toString()).fontSize(9).fontColor(LT.sub).margin({ top: 4 })
                }
                .alignItems(HorizontalAlign.Center)
              }, (b: LtBag) => b.name)
            }
            .width('100%').justifyContent(FlexAlign.SpaceAround)
          }
          .width('100%').padding(16).backgroundColor(LT.card).margin({ top: 8, bottom: 8 })
        }
        .width('100%')
      }
      .scrollBar(BarState.Off)
      .layoutWeight(1)
      .width('100%')

      // 包袋详情弹框
      if (this.showDetail) {
        Stack() {
          this.modalOverlay()
          Column() {
            Text('包袋详情').fontSize(18).fontWeight(700).fontColor(LT.dark).margin({ top: 20 })
            Text(ltBagByName(this.selName).name).fontSize(16).fontWeight(600).fontColor(LT.text).margin({ top: 8 })
            Text(ltBagByName(this.selName).leather + ' · ' + ltBagByName(this.selName).type + ' · ' + ltBagByName(this.selName).tag)
              .fontSize(12).fontColor(LT.sub).margin({ top: 4 })
            Column() {
              Text('容量').fontSize(12).fontColor(LT.sub).margin({ bottom: 4 })
              Stack({ alignContent: Alignment.Start }) {
                Column().width('100%').height(8).borderRadius(4).backgroundColor(LT.bg)
                Column().width(ltCapacityW(ltBagByName(this.selName).capacity).toString() + '%').height(8).borderRadius(4).backgroundColor(LT.primary)
              }
              Text(ltBagByName(this.selName).capacity.toString() + ' L').fontSize(12).fontColor(LT.text).fontWeight(600).margin({ top: 4 })
            }
            .width('100%').margin({ top: 16 })
            Row() {
              Column() { Text('价格').fontSize(11).fontColor(LT.sub); Text('¥' + ltBagByName(this.selName).price.toString()).fontSize(14).fontWeight(700).fontColor(LT.accent) }
              .layoutWeight(1).alignItems(HorizontalAlign.Center)
              Column() { Text('重量').fontSize(11).fontColor(LT.sub); Text(ltBagByName(this.selName).weight.toString() + 'kg').fontSize(14).fontWeight(700).fontColor(LT.text) }
              .layoutWeight(1).alignItems(HorizontalAlign.Center)
              Column() { Text('库存').fontSize(11).fontColor(LT.sub); Text(ltBagByName(this.selName).stock.toString()).fontSize(14).fontWeight(700).fontColor(LT.text) }
              .layoutWeight(1).alignItems(HorizontalAlign.Center)
              Column() { Text('已售').fontSize(11).fontColor(LT.sub); Text(ltBagByName(this.selName).sold.toString()).fontSize(14).fontWeight(700).fontColor(LT.accent) }
              .layoutWeight(1).alignItems(HorizontalAlign.Center)
            }
            .width('100%').padding(12).backgroundColor(LT.bg).borderRadius(12).margin({ top: 12 })
            Row() {
              Text().width(28).height(28).borderRadius(14).backgroundColor(LT.sub).onClick(() => { this.showDetail = false })
              Column().layoutWeight(1)
              Text('编辑').fontSize(12).fontColor(LT.card).padding({ left: 14, right: 14, top: 8, bottom: 8 }).borderRadius(16).backgroundColor(LT.accent)
                .onClick(() => { this.showDetail = false; this.showEdit = true })
            }
            .width('100%').margin({ top: 16 })
          }
          .width('86%').backgroundColor(LT.card).borderRadius(16).padding(20)
          .constraintSize({ maxHeight: '80%' }).zIndex(1000)
        }
        .width('100%').height('100%')
      }

      // 新增弹框
      if (this.showAdd) {
        Stack() {
          this.modalOverlay()
          Column() {
            Text('新增包袋').fontSize(18).fontWeight(700).fontColor(LT.dark).margin({ top: 20 })
            Column() {
              Text('名称').fontSize(12).fontColor(LT.sub).margin({ bottom: 4 })
              Row() {
                Text('请输入包袋名称').fontSize(12).fontColor(LT.sub)
                Column().layoutWeight(1)
              }
              .width('100%').height(36).backgroundColor(LT.bg).borderRadius(8).padding({ left: 12, right: 12 })
            }
            .width('100%').margin({ top: 16 })
            Column() {
              Text('类型').fontSize(12).fontColor(LT.sub).margin({ bottom: 4 })
              Row() {
                Text('手提').fontSize(12).fontColor(LT.card).padding(8).borderRadius(8).backgroundColor(LT.primary)
                Text('斜挎').fontSize(12).fontColor(LT.text).padding(8).borderRadius(8).backgroundColor(LT.bg).margin({ left: 8 })
                Text('双肩').fontSize(12).fontColor(LT.text).padding(8).borderRadius(8).backgroundColor(LT.bg).margin({ left: 8 })
                Text('腰包').fontSize(12).fontColor(LT.text).padding(8).borderRadius(8).backgroundColor(LT.bg).margin({ left: 8 })
              }
            }
            .width('100%').margin({ top: 12 })
            Row() {
              Text().width(28).height(28).borderRadius(14).backgroundColor(LT.sub).onClick(() => { this.showAdd = false })
              Column().layoutWeight(1)
              Text('确认新增').fontSize(13).fontColor(LT.card).padding({ left: 20, right: 20, top: 8, bottom: 8 }).borderRadius(20).backgroundColor(LT.primary)
                .onClick(() => { this.showAdd = false })
            }
            .width('100%').margin({ top: 20 })
          }
          .width('86%').backgroundColor(LT.card).borderRadius(16).padding(20)
          .constraintSize({ maxHeight: '80%' }).zIndex(1000)
        }
        .width('100%').height('100%')

  }
}


总结

在这里插入图片描述

这款手工皮具定制工坊应用充分展示了HarmonyOS ArkTS API 24在构建垂直领域电商应用方面的强大能力。从复古风格的视觉设计到完整的定制功能,从丰富的动画效果到精细的数据可视化,每一个细节都体现了匠心精神。

应用的技术架构具有很强的可复制性。接口先行的类型设计、设计令牌化的主题系统、纯函数驱动的动画引擎、组件化的页面结构、数据驱动的列表渲染,这些技术模式不仅适用于皮具应用,也可以轻松迁移到其他垂直领域的电商应用中。开发者只需调整主题色、数据模型和文案内容,就能快速构建出另一款风格迥异的应用。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐