新能源补能网络实战:HarmonyOS API 24下充电豹科技绿主题、7Tab两排布局与模拟地图桩位导航的完整拆解

续航焦虑是新能源车主最大的痛点,而一个好的充电服务App能让补能像加油一样简单。本文完整拆解一个基于鸿蒙 HarmonyOS API 24 与 ArkUI 声明式框架构建的「充电豹」新能源充电服务应用,覆盖科技绿主题配色系统、车控头部、爱车续航卡、模拟地图桩位导航、7Tab两排差异化布局、电站分类筛选、月度充电量渐变柱状图、钱包余额卡与充值套餐、会员等级权益、车主圈社交动态、车辆绑定增删改、三大弹窗复用设计等核心技术点,附完整源码与运行效果截图。


一、引言:新能源时代的补能焦虑

随着新能源汽车渗透率的持续攀升,充电已经从"偶尔为之"变成了车主的日常刚需。然而,找桩难、排队久、价格不透明、桩位故障无提示、支付流程繁琐,正成为新能源车主补能体验的现实痛点。一个优秀的充电服务App,不仅需要覆盖从"找桩"到"导航"再到"充电支付"的完整业务闭环,还要在地图导航、桩位实时状态、价格对比、会员权益、社交分享等维度做到精准而高效。

正是在这样的背景下,充电豹类新能源充电服务应用应运而生,并迅速成为新能源车主的必备工具。一个优秀的充电服务应用,需要以地图为核心入口,横向扩展出电站、订单、钱包、会员、车主圈、我的七大功能模块,每个模块采用完全不同的布局风格——地图=模拟地图+附近电站、电站=分类筛选+电站列表、订单=月度柱状图+充电订单、钱包=余额卡+充值套餐+流水、会员=等级权益、车主圈=动态列表、我的=车辆管理,形成了一个完整闭环的新能源补能生态。它不仅是一个功能完备的演示项目,更是一份典型的 ArkUI 工程实践范本,涵盖了状态管理、组件化设计、Stack弹窗叠加、双动画状态联动、科技绿视觉语言、模拟地图桩位导航、车辆绑定增删改、三大弹窗复用设计等关键技术点。

充电豹应用整体运行效果 - 车控头部与模拟地图带彩色桩位pin及附近电站列表


二、应用整体架构概览

整个应用采用了"单入口 + 7Tab两排差异化布局 + Stack弹窗叠加"的新能源充电应用架构。入口组件 Page818 作为整个应用的根容器,负责管理当前激活的 Tab 状态、双动画状态以及三个弹窗的显隐状态。

应用一共划分为七个一级页面:地图(MAP)、电站(STATION)、订单(ORDER)、钱包(WALLET)、会员(VIP)、车主圈(CIRCLE)以及我的(PROFILE)。与之前项目"5Tab单排或6Tab单排"不同,本应用独创了"7Tab两排(上4下3)"的底部导航布局——上排4个Tab(地图/电站/订单/钱包),下排3个Tab(会员/车主圈/我的),通过 tabRowItem @Builder 复用构建,wide 参数控制宽度(上排25%,下排33.3%),解决了7个Tab单排过于拥挤的问题。七个Tab每个布局完全不同——地图采用模拟地图+附近电站、电站采用分类横滑+电站卡列表、订单采用月度充电量柱状图+订单列表、钱包采用余额卡+充值套餐+流水记录、会员采用等级卡+权益列表+本月已省、车主圈采用电友横滑+动态列表、我的采用用户卡+车辆管理+设置菜单,这种"一页一型"的差异化设计最大程度还原了真实充电服务App的交互体验。

在数据层面,应用定义了一套完整的类型系统,包括色彩主题、电站元数据、充电订单元数据、车辆信息元数据、交易记录元数据、车主圈动态元数据,并通过常量数组将数据与 UI 表现层解耦。所有业务数据以 @Observed 装饰的可观察类为载体(StationItem/ChargeOrder/CarInfo/TxRecord/CirclePost),配合 6 个电站、7 个订单、2 辆车、6 条交易记录、6 条车主圈动态的模拟数据,模拟了一个真实的新能源充电数据环境。所有数据类都带有 constructor 构造函数,使用 new ClassName(...) 方式实例化。应用还定义了 12 个工具函数(plugColor桩类型颜色/orderStateColor订单状态四档颜色/speedLabel功率标签/rechargePlans充值套餐/vipRights会员权益/mapCells地图格子/mapCellColor格子颜色/mapPinPos桩位坐标/chargeBarH柱状图高度/updateCarPlate更新车牌/removeCarById删除车辆),将颜色映射、数据计算、地图生成、增删改逻辑从 UI 层抽离,实现数据与逻辑分离。

在交互层面,应用通过 Stack 布局叠加实现了三类弹窗的显示:绑定新车、修改车牌、解绑车辆。与之前项目"每个弹窗独立 Stack 叠加"一致,本应用三个弹窗各自独立 Stack 叠加,共享 modalOverlay 统一遮罩封装(全屏 mask 遮罩 + 点击关闭)。特别的是,本应用的弹窗数据状态设计十分精细——addModal 绑定新车包含 addPlate/addModel 两个输入状态,editModal 修改车牌包含 editCarId/editPlate 状态(editCarId 记录要修改的车辆ID,editPlate 预填原车牌),delModal 解绑车辆复用 editCarId 状态记录要删除的车辆ID,三个弹窗共享 editCarId 状态实现车辆ID的传递,减少了重复状态定义。绑定新车确认后 carList.push 新 CarInfo(Date.now() id,车牌默认浙A·新00000,车型默认新能源车,电池75.0kWh,续航400km,电量100%,图标🚗),修改车牌确认后调用 updateCarPlate(splice替换对象确保响应式刷新),解绑车辆确认后调用 removeCarById(倒序遍历按ID删除),形成了完整的车辆管理增删改闭环。

在视觉层面,本应用独创了"科技绿主题"设计——bg #EDF7F0 科技绿底,green #0FA958 科技绿为主色,greenD #0B7A3F 深墨绿为深色,teal #00B8A9 电光青为辅助色,title #12331E 深墨绿黑而非纯黑,与科技绿底形成高对比度的同时带有环保清新质感。应用的渐变设计是色彩系统的核心亮点——钱包余额卡使用 linearGradient 135 度从 greenD #0B7A3F 深墨绿到 teal #00B8A9 电光青的两色渐变,订单月度充电量柱状图使用 linearGradient 180 度从 teal #00B8A9 电光青到 greenD #0B7A3F 深墨绿的垂直渐变,会员等级卡使用 linearGradient 135 度从 title #12331E 深墨绿黑到 greenD #0B7A3F 深墨绿的两色渐变,会员成长值进度条使用 linearGradient 90 度从 green #0FA958 科技绿到 teal #00B8A9 电光青的水平渐变,四种不同角度和配色的科技绿渐变贯穿整个应用,形成了统一而丰富的科技绿渐变视觉语言,是本应用色彩设计的核心特色。车控头部采用圆形车头像+车牌车型+剩余续航电量+当前电量百分比的卡片布局,谷电时段提醒(23:00-07:00每度省¥0.30深墨绿字)体现充电省钱策略,是本应用头部的特色设计。应用还独创了"双动画状态联动"设计——aboutToAppear 中 setInterval 700ms 同时切换 breath 和 blink 两个布尔动画状态,breath 用于电站页"立即充电"按钮透明度动画(opacity 0.8/1,duration 500ms)和钱包页⚡图标旋转动画(rotate 10/-10度,duration 700ms EaseInOut),blink 用于订单页充电中订单的充电进度闪烁动画(opacity 0.5/1,duration 500ms),两个动画状态各司其职驱动三种不同的动效,形成整体协调的联动动效体系。

充电豹绑定新车弹窗运行效果 - 弹窗叠加我的页显示车牌号与车型输入及确认绑定按钮

下面逐页展示应用的七大功能模块运行效果。


三、地图页:模拟地图桩位导航与附近电站智能排序

地图页以"找桩导航"为核心,独创"模拟地图+附近电站"的复合布局。

地图模块 - 车控头部与模拟地图带彩色桩位pin及附近电站智能排序列表

页面顶部第一部分是模拟地图,Stack 布局 alignContent TopStart,height 230:

  • 地图底图:Flex wrap 遍历 mapCells() 生成的 56 个格子,每个格子 width 11% height 28 圆角 4 margin 2,背景色由 mapCellColor© 动态生成(r开头#BFE3CD深绿/g开头#D8F0DF中绿/e开头#E3F3E9浅绿),三种绿色格子模拟地图街区,整体 padding 12 圆角 16 背景 darkL #C9E4D4。
  • 桩位pin:ForEach 遍历 6 个电站,每个 pin 是 Column 包裹⚡图标14号+空闲数8号白字加粗,padding 6 圆角 12,背景色由 plugColor(type) 动态生成(超充red红/快充orange橘/慢充teal电光青),position 由 mapPinPos(i) 返回固定坐标(xs=[40,130,220,60,160,250],ys=[40,90,50,150,130,70]),带 animation duration 600ms。三种颜色的pin在模拟地图上清晰区分桩类型,空闲数实时显示,是本应用地图页的核心亮点。

第二部分是地图控制按钮,Row space 8,justifyContent End:

  • 🎯定位按钮:padding 10 圆角 12 白底,带 animation duration 400ms,点击切换 mapZoom 状态。
  • 📍位置按钮:padding 10 圆角 12 白底。
  • 🔄刷新按钮:padding 10 圆角 12 白底。

第三部分是附近电站标题行,Row space 8:"⚡ 附近电站 · 智能排序"加粗13号 layoutWeight 1 + "筛选 >"green绿字9号。

第四部分是附近电站列表,遍历 6 个电站,每个电站横向布局,padding 12,白底圆角 14,border 1px line 色,alignItems Center,包含:

  • 左侧类型卡:Column space 4,width 56 height 52 圆角 14,背景色 plugColor(type)(超充红/快充橘/慢充青),justifyContent Center,包含类型名(白字10号加粗)+功率标签 speedLabel(type)(超充480kW/快充120kW/慢充7kW,白字0.85透明度7号)。
  • 中部信息列(layoutWeight 1,左对齐,space 4):电站名(title深墨绿黑11号加粗 maxLines 1)+ "⭐ " + 评分 + " · " + 距离 + " · " + 价格(sub灰9号)+ 服务标签行(ForEach 遍历 services,每个标签 teal电光青字8号,dark底圆角6 padding 左右6上下2)。
  • 右侧空闲列(右对齐,space 5):空闲数/总数(free>0时green绿字否则red红字,13号加粗)+ “空闲”(text3灰8号)。

6 个电站覆盖六大场景:充电豹·滨江超充中心(650m,超充,24桩9空闲,¥1.42/度,4.9分,休息室/咖啡/Wi-Fi)、充电豹·中央公园站(1.1km,快充,16桩5空闲,¥1.18/度,4.8分,便利店/洗车)、国家电网·城西站(1.8km,快充,12桩3空闲,¥1.25/度,4.6分,雨棚)、充电豹·高新物流园站(2.4km,慢充,30桩21空闲,¥0.92/度,4.7分,夜间折扣)、特来电·钱江世纪城站(3.2km,超充,20桩11空闲,¥1.55/度,4.8分,休息室/餐饮)、充电豹·机场枢纽站(5.6km,超充,40桩17空闲,¥1.38/度,4.9分,24h/餐饮/洗车)。距离从650m到5.6km,价格从¥0.92到¥1.55/度,空闲数从3到21,完整还原了附近电站的真实数据环境。模拟地图的三色pin(超充红/快充橘/慢充青)让用户一眼判断桩类型,空闲数实时显示避免跑空,智能排序按距离+空闲+评分综合排序,是本应用地图页交互设计的核心亮点。


四、电站页:分类筛选与三按钮操作卡

电站页以"全部电站"为核心,采用"分类横滑+电站卡列表"的布局。

电站模块 - 全部电站6家与分类横滑及电站卡三按钮导航立即充电预约

页面顶部第一部分是标题"⚡ 全部电站 · " + STATION_LIST.length + " 家"加粗13号 margin 下 12。

第二部分是分类横滑,Scroll horizontal 遍历 6 个分类(全部/超充站/快充站/慢充站/谷电站/带休息室),每个分类 Text 10 号,选中(c === ‘全部’)时白字 green 科技绿底,未选中时 sub 灰字白底 border 1px line 色,padding 左右 14 上下 7,圆角 16。分类横滑让用户快速筛选电站类型,选中态科技绿底高亮。

第三部分是电站卡列表,遍历 6 个电站,每个电站 Column 包裹 space 10,padding 12,白底圆角 14,border 1px line 色,包含:

  • 电站名行:Row space 10,电站名(title深墨绿黑12号加粗 layoutWeight 1 maxLines 1)+ 类型标签(plugColor(type) 动态着色9号加粗,chip底圆角8 padding 左右8上下3)。
  • 三按钮操作行:Row space 10,三个按钮各 layoutWeight 1,padding 上下8,圆角10:
    • 🧭 导航:greenD 深墨绿字10号加粗,dark 底。
    • ⚡ 立即充电:白字10号加粗,green 科技绿底,带 breath 透明度动画(this.breath ? 0.8 : 1,animation duration 500ms),呼吸动效吸引用户点击。
    • 📅 预约:blue 蓝字10号加粗,chip 底。

6 个电站卡与地图页的附近电站数据一致,但布局不同——电站页用大卡+三按钮操作(导航/立即充电/预约),地图页用横向信息卡+空闲数,两页差异化布局避免重复。"立即充电"按钮的 breath 呼吸动画让核心操作有了"吸引眼球"的动态感,三按钮覆盖了导航/充电/预约三大核心操作,是本应用电站页交互设计的亮点。


五、订单页:月度充电量渐变柱状图与充电中闪烁动画

订单页以"充电订单"为核心,采用"月度充电量柱状图+订单列表"的复合布局。

订单模块 - 充电订单与月度充电量电光青→深墨绿渐变柱状图及充电中闪烁进度

页面顶部第一部分是标题行,Row space 8:"📄 充电订单"加粗13号 layoutWeight 1 + "本月 6 单 · 共 ¥256.85"sub灰9号。

第二部分是月度充电量柱状图,Column 包裹 space 10:

  • 标题:"⚡ 月度充电量(度)"加粗11号。
  • 柱状图 Row space 8:遍历 MONTH_LABELS 6 个月(3月-8月),每栏 alignItems Center,包含:顶部数值(CHARGE_BARS[i],sub灰8号)+ 柱子(width 16,height = chargeBarH(v) = Math.round(v / 320 * 90) 动态计算,圆角 8,linearGradient 180 度从 teal #00B8A9 电光青到 greenD #0B7A3F 深墨绿的垂直渐变)+ 底部月份(text3灰8号)。柱状图卡片 padding 上下10下12,白底圆角14,justifyContent Center。

6 个月充电数据:3月186/4月224/5月165/6月280/7月240/8月310(最高),8月 310 度最高(暑期出行高峰),5月 165 最低。柱子高度 = v/320*90 动态计算,以 320 度为基准映射到 90px 高度,180 度电光青→深墨绿垂直渐变让柱子有了"从上到下由浅入深"的视觉感,是本应用数据可视化的核心亮点。

第三部分是订单列表,遍历 7 个订单,每个订单 Column 包裹 space 10,padding 12,白底圆角 14,border 1px line 色,包含:

  • 电站状态行:Row space 8,⚡图标14号 + 电站名(title深墨绿黑10号加粗 layoutWeight 1 maxLines 1)+ 状态(orderStateColor(status) 动态着色:已完成green绿/充电中orange橘/已预约blue蓝/其他text3灰,9号加粗)。
  • 日期操作行:Row space 8,"📅 " + 日期(text3灰8号 layoutWeight 1)+ 状态对应操作:
    • 充电中:"🟢 充电进度 68%"green绿字9号加粗,带 blink 闪烁动画(this.blink ? 0.5 : 1,animation duration 500ms),闪烁动效模拟充电中状态。
    • 已预约:"扫码开始 >"blue蓝字9号。
    • 已完成:"查看详情 >"text3灰9号。

7 个订单覆盖三大状态:5 个已完成(滨江超充¥60.49/中央公园¥36.82/钱江世纪城¥70.99/高新物流园¥35.33/城西站¥35.75)、1 个充电中(滨江超充¥17.47,进度68%)、1 个已预约(机场枢纽预估¥52)。金额从¥17.47到¥70.99,充电时长从11分钟到4小时12分,完整还原了充电订单的真实数据环境。订单状态三档着色(已完成绿/充电中橘/已预约蓝)让用户一眼判断订单状态,充电中订单的 blink 闪烁动画模拟充电中状态,是本应用订单页交互设计的核心亮点。


六、钱包页:余额卡渐变与充值套餐阶梯优惠

钱包页以"充电钱包"为核心,采用"余额卡+充值套餐+流水记录"的三层布局。

钱包模块 - 余额卡深墨绿→电光青渐变与充值套餐6个阶梯优惠及流水记录

页面顶部第一部分是余额卡,Column 包裹 space 12,padding 16,圆角 18,linearGradient 135 度从 greenD #0B7A3F 深墨绿到 teal #00B8A9 电光青的两色渐变:

  • 余额行:Row space 12,余额信息列(layoutWeight 1,左对齐):"¥ 286.40"白字26号加粗 + "钱包余额(元)"白字0.8透明度9号 + ⚡图标38号,带 breath 旋转动画(rotate this.breath ? 10 : -10度,animation duration 700ms EaseInOut),闪电图标旋转模拟"充电中"的动感。
  • 额度券行:Row space 8,“免费额度 28.6 度”#C8F5DC浅绿字9号,半透明白底rgba(255,255,255,0.18)圆角10 padding 左右10上下4 + “6 张充电券”#FFE082金字9号,半透明白底圆角10 + Column layoutWeight 1 + "明细 >"白字10号加粗。

余额卡的 135 度深墨绿→电光青渐变是本应用最具辨识度的视觉元素,半透明白底的额度/券标签在深色渐变上清晰可读,⚡图标旋转动画让余额卡有了"充电动感",是本应用钱包页视觉设计的核心亮点。

第二部分是充值套餐,标题"💰 充值套餐"加粗13号 + Flex wrap 遍历 rechargePlans() 生成的 6 个套餐,每个套餐 width 30%,padding 14,白底圆角14,margin 5,border 1px line 色,alignItems Center,包含:

  • 支付金额:“¥” + pay,green科技绿字16号加粗。
  • 到账金额:“得 ¥” + get,title深墨绿黑10号。
  • 赠送:gift > 0 时"送 " + gift + " 度券"orange橘字8号,否则"基础套餐"text3灰8号。

6 个充值套餐阶梯优惠:¥100得¥100(基础)/¥200得¥220送10度券/¥500得¥580送50度券/¥1000得¥1200送120度券/¥2000得¥2500送300度券/¥3000得¥3900送500度券。充值金额越高赠送比例越大(¥3000得¥3900相当于7折),赠送度券 incentivize 大额充值,阶梯优惠设计是本应用钱包页商业设计的亮点。

第三部分是流水记录,标题"流水记录"加粗13号 + 遍历 6 条交易记录,每条 Row space 12,padding 10,白底圆角12,alignItems Center,包含:

  • 图标:icon 22号,width 44 height 44 圆角22 dark底 textAlign Center。
  • 信息列(layoutWeight 1,左对齐):标题(title深墨绿黑11号加粗 maxLines 1)+ 时间(text3灰8号)。
  • 金额:amount 12号,income 为 true 时 green绿字(收入)否则 orange橘字(支出),加粗。

6 条流水覆盖收支两类:3 笔充电支付支出(滨江超充-¥60.49/中央公园-¥36.82/钱江世纪城-¥70.99)、3 笔收入(会员充值+¥500/邀请好友+¥20/夜间充电返现+¥8.60)。收支两色(收入绿/支出橘)让用户一眼判断资金流向,是本应用钱包页数据呈现的亮点。


七、我的页:车辆管理增删改与设置菜单

我的页以"个人中心+车辆管理"为核心,采用"用户卡+车辆列表+设置菜单"的三层布局。

我的模块 - 用户卡与我的车辆2个含改牌解绑按钮及设置菜单

页面顶部第一部分是用户卡,Row space 12,padding 14,白底圆角16,border 1px line 色:

  • 头像:🧑‍🔧 32号,width 58 height 58 圆角29 dark底 textAlign Center。
  • 信息列(layoutWeight 1,左对齐):"电豹侠 · 银豹Lv.3"title深墨绿黑14号加粗 + "累计充电 1,842 度 · 减碳 1.1 吨 🌱"sub灰9号。
  • 编辑:✏️ 18号。

第二部分是我的车辆标题行,Row space 8:"🚗 我的车辆"加粗13号 layoutWeight 1 + "➕ 绑定"green科技绿字10号加粗(点击触发 addModal 绑定新车弹窗)。

第三部分是车辆列表,遍历 carList 2 辆车,每辆车 Column 包裹 space 10,padding 12,白底圆角14,border 1px line 色,包含:

  • 车辆信息行:Row space 12,车图标30号(width 56 height 56 圆角16 dark底 textAlign Center)+ 信息列(layoutWeight 1,左对齐):车型(title深墨绿黑12号加粗)+ 车牌 + " · 电池 " + 电池容量(sub灰9号)+ 电量百分比(green科技绿字16号加粗,右对齐)。
  • 操作按钮行:Row space 10:
    • ✏️ 改牌:blue蓝字10号加粗,chip底圆角10 padding 左右14上下7,点击触发 editModal 修改车牌弹窗(editCarId=c.id,editPlate=c.plate 预填)。
    • 🗑️ 解绑:red红字10号加粗,dark底圆角10 padding 左右14上下7,点击触发 delModal 解绑车辆弹窗(editCarId=c.id)。
    • Column layoutWeight 1 + "设为默认"sub灰10号。

2 辆车:特斯拉 Model Y(浙A·D88216,78.4kWh,62%)、比亚迪 海豹(浙A·F03527,82.5kWh,85%)。改牌/解绑/设为默认三按钮覆盖了车辆管理的核心操作,改牌触发 editModal(预填原车牌),解绑触发 delModal(二次确认),绑定触发 addModal(输入新车牌+车型),形成了完整的车辆管理增删改闭环,是本应用我的页交互设计的核心亮点。

第四部分是设置菜单,遍历 5 个设置项(🏠 常用地址管理/🔐 账号与安全/🌙 夜间充电提醒/📞 客服中心/⚙️ 设置),每项 Row space 12,padding 13,白底圆角12:设置项文字(title深墨绿黑11号 layoutWeight 1)+ ">"text3灰12号。


八、色彩主题系统:科技绿主题与四种渐变视觉语言

8.1 色彩接口定义

任何一款设计精良的应用,背后都有一套严谨的色彩规范。该应用首先通过一个 ColorPalette 接口,将整个应用所需的色彩抽象为一个结构化的契约,共 19 个字段:

interface ColorPalette {
  bg: string; card: string; dark: string; darkL: string;
  title: string; sub: string; text3: string;
  green: string; greenD: string; teal: string;
  blue: string; orange: string; red: string; purple: string;
  line: string; tabOn: string; mask: string; chip: string;
}
  • bg:页面整体背景色 #EDF7F0,科技绿底,营造新能源充电的环保清新氛围。
  • green:主色调 #0FA958,科技绿,用于按钮、强调文字、激活Tab、空闲数、柱状图起始色等高优先级视觉元素,是新能源环保的标志性色彩(清新、环保、科技感)。
  • greenD:深主色 #0B7A3F,深墨绿,用于渐变结束色、柱状图底部色、导航按钮文字,与 green 形成同色系深浅对比。
  • teal:辅助色 #00B8A9,电光青,用于渐变结束色、柱状图顶部色、服务标签、余额卡渐变,与科技绿形成"绿青"的渐变组合,象征新能源的科技感与未来感。
  • title:文字主色 #12331E,深墨绿黑,用于主标题文字,与科技绿底形成高对比度的同时带有环保清新质感(非纯黑,更柔和)。
  • mask:遮罩色 rgba(18,51,30,0.55),深墨绿黑半透明,用于弹窗遮罩,与 title 同色系保证遮罩质感统一。
  • chip:标签底色 rgba(15,169,88,0.12),科技绿 12% 透明度,用于标签、数据卡底色、按钮底色等,与 green 同色系保证标签质感统一。

充电豹会员页运行效果 - 豹力会员银豹Lv.3与会员专属权益6个及本月已省

8.2 色彩实例化

const COLORS: ColorPalette = {
  bg: '#EDF7F0', card: '#FFFFFF', dark: '#DCEFE3', darkL: '#C9E4D4',
  title: '#12331E', sub: '#4E7359', text3: '#8FB3A0',
  green: '#0FA958', greenD: '#0B7A3F', teal: '#00B8A9',
  blue: '#2E7DD1', orange: '#F08A24', red: '#E05B4B', purple: '#8A5FC0',
  line: '#D9EDE1', tabOn: '#0FA958',
  mask: 'rgba(18,51,30,0.55)', chip: 'rgba(15,169,88,0.12)'
};

主色 #0FA958 是一种清新的科技绿,既契合"新能源充电"主题(环保、清新、科技感的标志性色彩),又具有高效、可信赖的气质。辅助色 #00B8A9 电光青与科技绿形成"绿青"的渐变组合,是本应用最具辨识度的色彩组合。bg #EDF7F0 科技绿底与主色同暖色系,营造出新能源充电的环保清新氛围。title #12331E 深墨绿黑而非纯黑,与科技绿底形成高对比度的同时带有环保清新质感,是本应用色彩设计的细致之处。

值得注意的是,本应用的渐变设计是色彩系统的核心亮点——钱包余额卡使用 linearGradient 135 度从 greenD #0B7A3F 深墨绿到 teal #00B8A9 电光青的两色渐变,订单月度充电量柱状图使用 linearGradient 180 度从 teal #00B8A9 电光青到 greenD #0B7A3F 深墨绿的垂直渐变,会员等级卡使用 linearGradient 135 度从 title #12331E 深墨绿黑到 greenD #0B7A3F 深墨绿的两色渐变,会员成长值进度条使用 linearGradient 90 度从 green #0FA958 科技绿到 teal #00B8A9 电光青的水平渐变。四种不同角度和配色的科技绿渐变贯穿整个应用(135度深色渐变用于余额卡/180度垂直渐变用于柱状图/135度深黑渐变用于会员卡/90度水平渐变用于进度条),形成了统一而丰富的科技绿渐变视觉语言,是本应用色彩设计的核心特色。

本应用的动态颜色逻辑也十分丰富——桩类型按 plugColor 分三色着色(超充red红/快充orange橘/慢充teal电光青),订单状态按 orderStateColor 分四档着色(已完成green绿/充电中orange橘/已预约blue蓝/其他text3灰),流水收支按 income 分两档着色(收入green绿/支出orange橘),空闲数按 free>0 分两档着色(有空闲green绿/无空闲red红),形成了"以色定桩类型、以色定订单状态、以色定收支、以色定空闲"的多层语义着色模式。地图模拟底图按 mapCellColor 分三色格子(深绿/中绿/浅绿)模拟街区,会员权益状态按 status 分两档着色(已领取green绿/待领取orange橘),形成了"以色定地图区块、以色定权益状态"的补充着色模式。这种将所有色彩集中管理、配合多维度渐变与动态着色的做法保证了全局一致性,降低了维护成本。

充电豹车主圈运行效果 - 电友动态与车主横滑头像卡及动态列表点赞评论收藏分享


九、类型定义:构建严谨的数据契约

应用通过一组带 constructor 的 @Observed 类,为每一类业务对象建立了明确的数据契约。

9.1 电站、订单、车辆、流水、车主圈元数据

@Observed export class StationItem {
  id: number; name: string; distance: string; type: string;
  total: number; free: number; price: string; score: string; services: string[];
  constructor(...) { ... }
}
@Observed export class ChargeOrder {
  id: number; station: string; date: string; kWh: string;
  amount: string; duration: string; status: string;
  constructor(...) { ... }
}
@Observed export class CarInfo {
  id: number; plate: string; model: string; battery: string;
  range: number; soc: number; icon: string;
  constructor(...) { ... }
}
@Observed export class TxRecord {
  id: number; title: string; amount: string; time: string; income: boolean; icon: string;
  constructor(...) { ... }
}
@Observed export class CirclePost {
  id: number; author: string; avatar: string; car: string;
  content: string; likes: number; comments: number; time: string;
  constructor(...) { ... }
}

StationItem 描述电站的 ID、名称、距离、类型(超充/快充/慢充)、总桩数、空闲桩数、价格、评分、服务标签数组,用 type 字段配合 plugColor 函数实现桩类型三色着色,services 数组字段体现电站配套服务。ChargeOrder 描述充电订单的 ID、电站名、日期、充电量、金额、时长、状态(已完成/充电中/已预约),用 status 字段配合 orderStateColor 函数实现订单状态四档着色。CarInfo 描述车辆的 ID、车牌、车型、电池容量、续航、电量百分比、图标,用 soc 字段显示电量,plate 字段支持修改(updateCarPlate)。TxRecord 描述交易记录的 ID、标题、金额、时间、收支标志(income boolean)、图标,用 income 字段实现收支两色着色(收入绿/支出橘)。CirclePost 描述车主圈动态的 ID、作者、头像、车型、内容、点赞数、评论数、时间,是本应用社交模块的数据类。

9.2 工具函数:颜色映射、数据计算、地图生成、增删改逻辑

function plugColor(type: string): string { ... }
function orderStateColor(s: string): string { ... }
function speedLabel(type: string): string { ... }
function rechargePlans(): RechargePlan[] { ... }
function vipRights(): VipRight[] { ... }
function mapCells(): string[] { ... }
function mapCellColor(c: string): string { ... }
function mapPinPos(i: number): Position { ... }
function chargeBarH(v: number): number { return Math.round(v / 320 * 90); }
function updateCarPlate(list: CarInfo[], id: number, plate: string): void { ... }
function removeCarById(list: CarInfo[], id: number): void { ... }

12 个工具函数将颜色映射、数据计算、地图生成、增删改逻辑从 UI 层抽离,实现数据与逻辑分离:

  • plugColor:桩类型着色,超充red/快充orange/慢充teal。
  • orderStateColor:订单状态着色,已完成green/充电中orange/已预约blue/其他text3。
  • speedLabel:功率标签,超充480kW/快充120kW/慢充7kW。
  • rechargePlans:充值套餐生成,6档阶梯优惠(¥100-¥3000)。
  • vipRights:会员权益生成,6项权益(充电92折/谷电加倍返/每月券包/贵宾休息室/免费洗车/电池检测)。
  • mapCells:地图格子生成,56个格子按 i%9=0/r、i%7=0/g、其他/e 分类。
  • mapCellColor:格子颜色,r深绿/g中绿/e浅绿。
  • mapPinPos:桩位坐标,6个pin的固定坐标(xs/ys数组)。
  • chargeBarH:柱状图高度计算,Math.round(v / 320 * 90),以320度为基准映射到90px。
  • updateCarPlate:更新车牌,遍历找到ID,splice替换对象(保留其他字段,更新plate)。
  • removeCarById:删除车辆,倒序遍历找到ID,splice删除。

所有数据类都带有 constructor 构造函数,使用 new ClassName(...) 方式实例化,配合 @Observed 装饰器实现响应式更新。12 个工具函数将逻辑从 UI 层抽离,实现数据与逻辑分离,特别是 updateCarPlate 采用 splice 替换对象的方式触发 @Observed 响应式更新(直接修改对象属性可能不触发刷新,替换整个对象会触发),是 ArkUI 响应式更新的关键技巧,体现了对框架机制的深入理解。mapCells/mapCellColor/mapPinPos 三个函数配合实现了模拟地图的生成,56个三色格子+6个彩色pin,是本应用地图页的核心技术亮点。


十、配置驱动:常量数据与模拟数据规模

10.1 Tab 导航与常量配置

const TAB_ROW_1: TabMeta[] = [
  { icon: '🗺️', label: '地图' }, { icon: '⚡', label: '电站' },
  { icon: '📄', label: '订单' }, { icon: '💰', label: '钱包' }
];
const TAB_ROW_2: TabMeta[] = [
  { icon: '👑', label: '会员' }, { icon: ' EV', label: '车主圈' }, { icon: '🚗', label: '我的' }
];
const MONTH_LABELS: string[] = ['3月', '4月', '5月', '6月', '7月', '8月'];
const CHARGE_BARS: number[] = [186, 224, 165, 280, 240, 310];

Tab 导航采用两排 TabMeta 对象数组配置,上排4个(地图/电站/订单/钱包),下排3个(会员/车主圈/我的),共7个Tab。底部 tabBar 通过 Column 包裹两个 Row,上排 Row 遍历 TAB_ROW_1 调用 tabRowItem(t, i, false)(width 25%),下排 Row 遍历 TAB_ROW_2 调用 tabRowItem(t, i+4, true)(width 33.3%),tabRowItem @Builder 复用构建每个Tab(图标17号+文字8号,激活态tabOn科技绿字加粗,非激活text3灰,padding 上7下6,点击切换tabIdx)。底部白底圆角16。7Tab两排布局解决了单排7个Tab过于拥挤的问题,是本应用导航设计的特色。MONTH_LABELS 常量定义6个月标签,CHARGE_BARS 常量定义月度充电量数据(186/224/165/280/240/310,8月最高)。

10.2 模拟数据规模

const STATION_LIST: StationItem[] = [ new StationItem(1, '充电豹·滨江超充中心', '650m', '超充', 24, 9, '¥1.42/度', '4.9', ['休息室', '咖啡', 'Wi-Fi']), ... ];
const ORDER_INIT: ChargeOrder[] = [ new ChargeOrder(1, '充电豹·滨江超充中心', '08-20 19:42', '42.6度', '¥60.49', '38分钟', '已完成'), ... ];
const CAR_INIT: CarInfo[] = [ new CarInfo(1, '浙A·D88216', '特斯拉 Model Y', '78.4kWh', 312, 62, '🚙'), ... ];
const TX_INIT: TxRecord[] = [ new TxRecord(1, '充电支付 · 滨江超充', '-¥60.49', '08-20 20:21', false, '⚡'), ... ];
const CIRCLE_INIT: CirclePost[] = [ new CirclePost(1, '电耗子', '🐭', 'Model 3', '滨江超充新上的480kW桩,10分钟补能400公里,真香!', 456, 62, '2小时前'), ... ];

七组常量数据承载了充电豹的全部业务信息:STATION_LIST 6 个电站(距离650m-5.6km,类型超充/快充/慢充,价格¥0.92-¥1.55/度,评分4.6-4.9,总桩12-40,空闲3-21)、ORDER_INIT 7 个订单(5已完成/1充电中/1已预约,金额¥17.47-¥70.99,充电量12.3-45.8度,时长11分钟-4小时12分)、CAR_INIT 2 辆车(特斯拉Model Y 62%/比亚迪海豹85%)、TX_INIT 6 条流水(3支出/3收入,金额-¥70.99到+¥500)、CIRCLE_INIT 6 条车主圈动态(点赞215-1203,评论33-240,覆盖Model 3/汉EV/理想L8/海豹/AION S/官方)、MONTH_LABELS+CHARGE_BARS 6 个月充电数据(186-310度)。所有 @Observed 类数据使用 new ClassName(...) 构造函数实例化,TabMeta 使用对象字面量。12 个工具函数处理颜色映射、数据计算、地图生成、增删改逻辑,是本应用唯一的逻辑层。


十一、双动画状态与动态颜色:数据驱动的视觉映射与联动动效

应用通过双动画状态和动态颜色逻辑,将业务数据转化为视觉色彩与动态效果,实现"数据即配色、状态即动效"。

11.1 动态颜色逻辑

本应用的动态颜色逻辑分布在多个维度:

  • 桩类型着色plugColor(type),超充red红/快充orange橘/慢充teal电光青,三色桩类型着色让用户在地图和列表中一眼判断桩类型。
  • 订单状态着色orderStateColor(s),已完成green绿/充电中orange橘/已预约blue蓝/其他text3灰,四档订单状态着色让用户一眼判断订单流转状态。
  • 流水收支着色t.income ? green : orange,收入green绿/支出orange橘,两档收支着色让用户一眼判断资金流向。
  • 空闲数着色s.free > 0 ? green : red,有空闲green绿/无空闲red红,两档空闲着色让用户一眼判断是否有桩可用。
  • 地图格子着色mapCellColor(c),r深绿#BFE3CD/g中绿#D8F0DF/e浅绿#E3F3E9,三色格子模拟地图街区。
  • 会员权益状态着色r.status === '已领取' ? green : orange,已领取green绿/待领取orange橘,两档权益状态着色。

11.2 双动画状态联动

aboutToAppear(): void {
  this.timer = setInterval(() => {
    this.breath = !this.breath;
    this.blink = !this.blink;
  }, 700);
}
aboutToDisappear(): void {
  if (this.timer > 0) { clearInterval(this.timer); }
}

aboutToAppear 中启动 setInterval 700ms 同时切换 breath 和 blink 两个布尔动画状态,aboutToDisappear 中判断 timer > 0 时 clearInterval 清理定时器避免内存泄漏。两个动画状态各司其职驱动三种不同的动效:

  • breath 用于电站页"立即充电"按钮透明度动画this.breath ? 0.8 : 1,animation duration 500ms,所有电站的"立即充电"按钮都有呼吸动画,营造出"核心操作吸引眼球"的动态感。
  • breath 用于钱包页⚡图标旋转动画.rotate({ angle: this.breath ? 10 : -10 }),animation duration 700ms EaseInOut,余额卡右侧的⚡图标随 breath 状态在 10 度和 -10 度之间旋转,营造出"闪电充电"的动感,与新能源充电主题高度契合。
  • blink 用于订单页充电中订单的充电进度闪烁动画this.blink ? 0.5 : 1,animation duration 500ms,仅充电中状态的订单显示"🟢 充电进度 68%“并带闪烁动画,模拟充电中状态的"呼吸感”,已完成和已预约订单无动画,视觉上区分订单状态。

两个动画状态(breath/blink)同时驱动三种动效(按钮呼吸/图标旋转/进度闪烁),700ms 周期同步切换,形成整体协调的联动动效体系。breath 同时驱动按钮透明度和图标旋转两种动效,blink 专门驱动充电进度闪烁,两个状态分工明确,让静态的页面有了"鲜活"的动态感,是本应用动效设计的核心亮点。车主圈动态的👍点赞文字带 animation duration 500ms,点击点赞时有"心动"的动效反馈,形成了完整的社交交互动效闭环。


十二、入口组件:应用的骨架与7Tab两排布局

12.1 组件声明与状态

@Entry
@Component
struct Page818 {
  @State tabIdx: number = 0;
  @State breath: boolean = false;
  @State blink: boolean = false;
  @State addModal: boolean = false;
  @State editModal: boolean = false;
  @State delModal: boolean = false;
  @State editCarId: number = 0;
  @State addPlate: string = '';
  @State addModel: string = '';
  @State editPlate: string = '';
  @State carList: CarInfo[] = CAR_INIT;
  @State txList: TxRecord[] = TX_INIT;
  @State orderList: ChargeOrder[] = ORDER_INIT;
  @State postList: CirclePost[] = CIRCLE_INIT;
  @State mapZoom: boolean = false;
  private timer: number = -1;
}

15 个 @State 状态分六类:导航状态(tabIdx)、动画状态(breath/blink)、弹窗控制状态(addModal/editModal/delModal)、弹窗数据状态(editCarId/addPlate/addModel/editPlate,editCarId 共享于 editModal 和 delModal 记录车辆ID,addPlate/addModel 用于绑定新车输入,editPlate 预填原车牌)、业务数据状态(carList/txList/orderList/postList,初始化为常量数组,支持增删改)、地图状态(mapZoom)。private 字段(STATION_LIST/ORDER_INIT/CAR_INIT/TX_INIT/CIRCLE_INIT/MONTH_LABELS/CHARGE_BARS/TAB_ROW_1/TAB_ROW_2/timer)存储静态模拟数据和定时器引用,不触发响应式更新。

12.2 生命周期与双动画联动

aboutToAppear(): void {
  this.timer = setInterval(() => {
    this.breath = !this.breath;
    this.blink = !this.blink;
  }, 700);
}
aboutToDisappear(): void {
  if (this.timer > 0) { clearInterval(this.timer); }
}

aboutToAppear 中启动 setInterval 700ms 同时切换 breath 和 blink 动画状态,实现双动画联动。aboutToDisappear 中判断 timer > 0 时 clearInterval 清理定时器,避免内存泄漏和重复启动。这种"生命周期管理定时器"的模式是 ArkUI 动效的标准实践。

12.3 车控头部(@Builder header)

顶部头部通过 @Builder header() 抽取,Column 包裹,space 12,padding 左右 14 上下 12:

  • 标题行:Row space 10,"⚡"图标 16 号 padding 6 圆角 12 chip 底 + "充电豹 · 让补能更简单"title 深墨绿黑 13 号加粗 + Column layoutWeight 1 + "🔗 2台车"sub 灰 10 号 + "🔔"消息 18 号。
  • 爱车续航卡:Row space 12,padding 12,白底圆角 14,border 1px line 色,包含:🚙车图标 34 号(width 62 height 62 圆角 31 dark 底 textAlign Center)+ 车辆信息列(layoutWeight 1,左对齐):"浙A·D88216 · Model Y"title 深墨绿黑 13 号加粗 + "剩余续航 312km · 电量 62%"sub 灰 9 号 + 电量列(右对齐):"62%"green 科技绿字 16 号加粗 + "当前电量"text3 灰 8 号。
  • 充电状态横条:Row space 8,"🔋 谷电时段 23:00-07:00 每度省¥0.30"greenD 深墨绿字 9 号,dark 底圆角 8 padding 左右 8 上下 4 + "🎫 满100减15券"orange 橘字 9 号,chip 底圆角 8 padding 左右 8 上下 4。

车控头部是本应用最具辨识度的视觉元素,圆形车头像+车牌车型+剩余续航电量+当前电量百分比的卡片布局,让用户一眼看到当前车辆的核心信息。谷电时段提醒(23:00-07:00每度省¥0.30深墨绿字)体现充电省钱策略,满减券提醒(满100减15橘字)体现优惠信息,与整体科技绿主题呼应。

12.4 7Tab两排底部导航与Stack弹窗

底部导航通过 @Builder tabBar() 构建,Column 包裹两个 Row:

  • 上排 Row:遍历 TAB_ROW_1 4个Tab,调用 tabRowItem(t, i, false)(width 25%)。
  • 下排 Row:遍历 TAB_ROW_2 3个Tab,调用 tabRowItem(t, i+4, true)(width 33.3%)。
    tabRowItem @Builder 构建每个Tab:Column space 3,图标17号+文字8号(激活态tabOn科技绿字加粗,非激活text3灰),width 由 wide 参数控制(false=25%,true=33.3%),padding 上7下6,点击切换tabIdx。底部白底圆角16。

build() 使用 Stack 布局,主 Column 包含 header + Scroll(if-else 切换 7 个 @Builder 页面:tabIdx 0-6 对应 mapTab/stationTab/orderTab/walletTab/vipTab/circleTab/profileTab)+ tabBar,width/height 100%,bg 科技绿底。三个弹窗条件渲染:每个弹窗独立 Stack 叠加,共享 modalOverlay 统一遮罩封装(全屏 mask 遮罩 + 点击关闭)。


十三、关键技术特性对比

特性维度 实现方式 技术要点 设计价值
色彩管理 ColorPalette 接口 + COLORS 常量 集中定义 19 种语义色,含科技绿主题色 保证视觉一致性,支持主题切换
科技绿主题 bg #EDF7F0 + green #0FA958 + teal #00B8A9 科技绿底+科技绿主色+电光青辅助,title深墨绿黑非纯黑 环保清新氛围,科技感未来感
四种科技绿渐变 四种不同角度 linearGradient 余额卡135度深墨绿→电光青/柱状图180度电光青→深墨绿/会员卡135度深黑→深墨绿/进度条90度科技绿→电光青 统一而丰富的渐变视觉语言
车控头部 圆形车头像+车牌车型+续航电量+电量百分比 爱车续航卡,谷电时段提醒省钱策略 车辆核心信息一目了然,充电省钱
7Tab两排布局 Column包裹两个Row,tabRowItem复用 上排4个25%宽,下排3个33.3%宽,wide参数控制 解决7Tab单排拥挤问题
模拟地图桩位导航 Flex wrap格子+Stack叠加pin 56个三色格子(mapCells/mapCellColor)+6个彩色pin(plugColor/mapPinPos),pin显示空闲数 纯代码模拟地图,桩类型三色区分
数据建模 @Observed 类 + constructor + 工具函数 5 个 @Observed 类(StationItem/ChargeOrder/CarInfo/TxRecord/CirclePost)+ 12 个工具函数 数据与逻辑分离,工程化设计
7Tab差异化 每页独立 @Builder 布局 地图模拟地图+电站/电站分类横滑+三按钮/订单柱状图+列表/钱包余额卡+套餐+流水/会员等级+权益/车主圈横滑+动态/我的车辆+设置 一页一型,还原真实充电App
双动画状态联动 breath+blink两个@State setInterval 700ms同时切换,breath驱动按钮呼吸+图标旋转,blink驱动充电进度闪烁 两状态驱动三动效,协调联动
充电中闪烁动画 blink opacity 0.5/1 仅充电中订单显示进度68%并闪烁,duration 500ms 模拟充电中状态,状态区分
立即充电呼吸动画 breath opacity 0.8/1 所有电站立即充电按钮呼吸,duration 500ms 核心操作吸引眼球
钱包⚡图标旋转 breath rotate 10/-10度 余额卡⚡图标旋转,duration 700ms EaseInOut 闪电充电动感,主题契合
桩类型三色着色 plugColor动态函数 超充red/快充orange/慢充teal,地图pin和列表类型卡统一着色 以色定桩类型,一眼判断
订单状态四档着色 orderStateColor动态函数 已完成green/充电中orange/已预约blue/其他text3 以色定订单状态,一眼判断
流水收支两色 income ? green : orange 收入green绿/支出orange橘 以色定收支,一眼判断
充值套餐阶梯优惠 rechargePlans函数生成6档 ¥100-¥3000,金额越高赠送比例越大,赠送度券 商业激励设计,促进大额充值
会员等级权益 vipRights函数生成6项 充电92折/谷电加倍返/券包/休息室/洗车/电池检测,状态已领取/待领取 会员体系完整,权益可追踪
车辆管理增删改 addModal/editModal/delModal 绑定新车push/改牌updateCarPlate(splice替换)/解绑removeCarById(倒序删除) 完整车辆管理闭环
弹窗共享editCarId 三个弹窗共享车辆ID状态 editCarId记录要修改/删除的车辆ID,editModal和delModal复用 减少重复状态定义
splice替换对象更新 updateCarPlate用splice替换 遍历找到ID,splice(i,1,new CarInfo)替换整个对象 触发@Observed响应式更新,关键技巧
三大弹窗 独立 Stack 叠加 + modalOverlay 绑定新车/修改车牌/解绑车辆,共享遮罩样式 完整增删改交互闭环
状态管理 @State + @Observed + private 15个@State分六类,private存静态数据不触发更新 细粒度状态控制,性能优化
组件化 @Component + @Builder 1个入口组件 + 7个页面@Builder + 3个弹窗 + header/tabBar/modalOverlay/tabRowItem/chargeStatOf 高内聚低耦合,代码复用率高
列表渲染 ForEach + 网格/横滑/列表/瀑布流 地图格子Flex wrap/分类横滑/电站列表/订单列表/充值套餐Flex wrap/流水列表/车主圈横滑+动态列表/车辆列表/设置列表 数据驱动渲染,布局灵活
数据可视化 柱状图 + 模拟地图 月度充电量渐变柱状图/模拟地图三色格子+彩色pin 多维度呈现,充电趋势直观
文本省略 maxLines + textOverflow 电站名maxLines1/订单电站名maxLines1/流水标题maxLines1 长文本优雅截断,布局整齐
生命周期 aboutToAppear/aboutToDisappear setInterval启动/clearInterval清理,timer判>0 健壮性强,无资源泄漏
布局系统 Column/Row + Stack + Scroll + Flex 弹性布局、Stack叠加、Scroll纵向滚动、Flex wrap换行 自适应布局,层次分明

完整代码

// ============================================================
// 818 充电豹 · 新能源充电服务(现代行业:新能源充电 / 补能网络)
// 头部样式:车控头部(爱车续航卡 + 充电状态横条)无动画
// 布局风格:7 个 tab 两排(上4下3),每个布局完全不同
//   地图=模拟地图+附近电站 / 电站=电站列表 / 订单=充电订单+月度柱状图
//   钱包=余额卡+充值套餐+流水 / 会员=等级权益 / 车主圈=动态列表 / 我的=车辆管理
// 底部 7 tab 两排(4 + 3)
// 主题:科技绿底 + 深墨绿 + 电光青
// ============================================================

interface ColorPalette {
  bg: string;
  card: string;
  dark: string;
  darkL: string;
  title: string;
  sub: string;
  text3: string;
  green: string;
  greenD: string;
  teal: string;
  blue: string;
  orange: string;
  red: string;
  purple: string;
  line: string;
  tabOn: string;
  mask: string;
  chip: string;
}

const COLORS: ColorPalette = {
  bg: '#EDF7F0',
  card: '#FFFFFF',
  dark: '#DCEFE3',
  darkL: '#C9E4D4',
  title: '#12331E',
  sub: '#4E7359',
  text3: '#8FB3A0',
  green: '#0FA958',
  greenD: '#0B7A3F',
  teal: '#00B8A9',
  blue: '#2E7DD1',
  orange: '#F08A24',
  red: '#E05B4B',
  purple: '#8A5FC0',
  line: '#D9EDE1',
  tabOn: '#0FA958',
  mask: 'rgba(18,51,30,0.55)',
  chip: 'rgba(15,169,88,0.12)'
};

interface TabMeta {
  icon: string;
  label: string;
}

const TAB_ROW_1: TabMeta[] = [
  { icon: '🗺️', label: '地图' },
  { icon: '⚡', label: '电站' },
  { icon: '📄', label: '订单' },
  { icon: '💰', label: '钱包' }
];

const TAB_ROW_2: TabMeta[] = [
  { icon: '👑', label: '会员' },
  { icon: ' EV', label: '车主圈' },
  { icon: '🚗', label: '我的' }
];

const MONTH_LABELS: string[] = ['3月', '4月', '5月', '6月', '7月', '8月'];
const CHARGE_BARS: number[] = [186, 224, 165, 280, 240, 310];

function plugColor(type: string): string {
  if (type === '超充') { return COLORS.red; }
  if (type === '快充') { return COLORS.orange; }
  return COLORS.teal;
}

function orderStateColor(s: string): string {
  if (s === '已完成') { return COLORS.green; }
  if (s === '充电中') { return COLORS.orange; }
  if (s === '已预约') { return COLORS.blue; }
  return COLORS.text3;
}

function speedLabel(type: string): string {
  if (type === '超充') { return '480kW'; }
  if (type === '快充') { return '120kW'; }
  return '7kW';
}

@Observed export class StationItem {
  id: number;
  name: string;
  distance: string;
  type: string;
  total: number;
  free: number;
  price: string;
  score: string;
  services: string[];
  constructor(id: number, name: string, distance: string, type: string, total: number, free: number, price: string, score: string, services: string[]) {
    this.id = id; this.name = name; this.distance = distance; this.type = type;
    this.total = total; this.free = free; this.price = price; this.score = score; this.services = services;
  }
}

@Observed export class ChargeOrder {
  id: number;
  station: string;
  date: string;
  kWh: string;
  amount: string;
  duration: string;
  status: string;
  constructor(id: number, station: string, date: string, kWh: string, amount: string, duration: string, status: string) {
    this.id = id; this.station = station; this.date = date; this.kWh = kWh; this.amount = amount; this.duration = duration; this.status = status;
  }
}

@Observed export class CarInfo {
  id: number;
  plate: string;
  model: string;
  battery: string;
  range: number;
  soc: number;
  icon: string;
  constructor(id: number, plate: string, model: string, battery: string, range: number, soc: number, icon: string) {
    this.id = id; this.plate = plate; this.model = model; this.battery = battery; this.range = range; this.soc = soc; this.icon = icon;
  }
}

@Observed export class TxRecord {
  id: number;
  title: string;
  amount: string;
  time: string;
  income: boolean;
  icon: string;
  constructor(id: number, title: string, amount: string, time: string, income: boolean, icon: string) {
    this.id = id; this.title = title; this.amount = amount; this.time = time; this.income = income; this.icon = icon;
  }
}

@Observed export class CirclePost {
  id: number;
  author: string;
  avatar: string;
  car: string;
  content: string;
  likes: number;
  comments: number;
  time: string;
  constructor(id: number, author: string, avatar: string, car: string, content: string, likes: number, comments: number, time: string) {
    this.id = id; this.author = author; this.avatar = avatar; this.car = car; this.content = content; this.likes = likes; this.comments = comments; this.time = time;
  }
}

const STATION_LIST: StationItem[] = [
  new StationItem(1, '充电豹·滨江超充中心', '650m', '超充', 24, 9, '¥1.42/度', '4.9', ['休息室', '咖啡', 'Wi-Fi']),
  new StationItem(2, '充电豹·中央公园站', '1.1km', '快充', 16, 5, '¥1.18/度', '4.8', ['便利店', '洗车']),
  new StationItem(3, '国家电网·城西站', '1.8km', '快充', 12, 3, '¥1.25/度', '4.6', ['雨棚']),
  new StationItem(4, '充电豹·高新物流园站', '2.4km', '慢充', 30, 21, '¥0.92/度', '4.7', ['夜间折扣']),
  new StationItem(5, '特来电·钱江世纪城站', '3.2km', '超充', 20, 11, '¥1.55/度', '4.8', ['休息室', '餐饮']),
  new StationItem(6, '充电豹·机场枢纽站', '5.6km', '超充', 40, 17, '¥1.38/度', '4.9', ['24h', '餐饮', '洗车'])
];

const ORDER_INIT: ChargeOrder[] = [
  new ChargeOrder(1, '充电豹·滨江超充中心', '08-20 19:42', '42.6度', '¥60.49', '38分钟', '已完成'),
  new ChargeOrder(2, '充电豹·中央公园站', '08-18 09:15', '31.2度', '¥36.82', '52分钟', '已完成'),
  new ChargeOrder(3, '特来电·钱江世纪城站', '08-15 21:08', '45.8度', '¥70.99', '35分钟', '已完成'),
  new ChargeOrder(4, '充电豹·高新物流园站', '08-12 23:30', '38.4度', '¥35.33', '4小时12分', '已完成'),
  new ChargeOrder(5, '国家电网·城西站', '08-09 14:22', '28.6度', '¥35.75', '48分钟', '已完成'),
  new ChargeOrder(6, '充电豹·滨江超充中心', '08-21 08:30', '12.3度', '¥17.47', '11分钟', '充电中'),
  new ChargeOrder(7, '充电豹·机场枢纽站', '08-23 10:00', '—', '预估¥52', '已预约', '已预约')
];

const CAR_INIT: CarInfo[] = [
  new CarInfo(1, '浙A·D88216', '特斯拉 Model Y', '78.4kWh', 312, 62, '🚙'),
  new CarInfo(2, '浙A·F03527', '比亚迪 海豹', '82.5kWh', 420, 85, '🚗')
];

const TX_INIT: TxRecord[] = [
  new TxRecord(1, '充电支付 · 滨江超充', '-¥60.49', '08-20 20:21', false, '⚡'),
  new TxRecord(2, '会员充值 · 送50度券', '+¥500.00', '08-19 10:02', true, '💰'),
  new TxRecord(3, '充电支付 · 中央公园站', '-¥36.82', '08-18 10:08', false, '⚡'),
  new TxRecord(4, '邀请好友奖励', '+¥20.00', '08-16 15:44', true, '🎁'),
  new TxRecord(5, '充电支付 · 钱江世纪城', '-¥70.99', '08-15 21:45', false, '⚡'),
  new TxRecord(6, '夜间充电返现', '+¥8.60', '08-13 08:00', true, '🌙')
];

const CIRCLE_INIT: CirclePost[] = [
  new CirclePost(1, '电耗子', '🐭', 'Model 3', '滨江超充新上的480kW桩,10分钟补能400公里,真香!', 456, 62, '2小时前'),
  new CirclePost(2, '绿牌老张', '👨', '汉EV', '夜间谷电充电攻略:23点后每度省3毛,一年省出一次保养', 892, 154, '5小时前'),
  new CirclePost(3, '高速奶爸', '🧔', '理想L8', '自驾千岛湖,服务区超充排队40分钟,建议错峰出行', 328, 88, '昨天'),
  new CirclePost(4, '海豹少女', '👧', '海豹', '第一次用充电豹的地图导航,直达空闲桩位,不绕路', 215, 33, '昨天'),
  new CirclePost(5, '网约车王师傅', '🚕', 'AION S', '跑一天车充两次电,钱包月账单帮我省了200多', 567, 71, '2天前'),
  new CirclePost(6, '充电豹小助手', '_bot', '官方', '8月充电节:每晚20点抢半价充电券,手慢无!', 1203, 240, '3天前')
];

@Entry
@Component
struct Page818 {
  @State tabIdx: number = 0;
  @State breath: boolean = false;
  @State blink: boolean = false;
  @State addModal: boolean = false;
  @State editModal: boolean = false;
  @State delModal: boolean = false;
  @State editCarId: number = 0;
  @State addPlate: string = '';
  @State addModel: string = '';
  @State editPlate: string = '';
  @State carList: CarInfo[] = CAR_INIT;
  @State txList: TxRecord[] = TX_INIT;
  @State orderList: ChargeOrder[] = ORDER_INIT;
  @State postList: CirclePost[] = CIRCLE_INIT;
  @State mapZoom: boolean = false;
  private timer: number = -1;

  aboutToAppear(): void {
    this.timer = setInterval(() => {
      this.breath = !this.breath;
      this.blink = !this.blink;
    }, 700);
  }

  aboutToDisappear(): void {
    if (this.timer > 0) { clearInterval(this.timer); }
  }

  @Builder
  header() {
    Column({ space: 12 }) {
      Row({ space: 10 }) {
        Text('⚡').fontSize(16).padding(6).borderRadius(12).backgroundColor(COLORS.chip)
        Text('充电豹 · 让补能更简单').fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
        Column().layoutWeight(1)
        Text('🔗 2台车').fontSize(10).fontColor(COLORS.sub)
        Text('🔔').fontSize(18)
      }
      .width('100%')

      Row({ space: 12 }) {
        Text('🚙').fontSize(34)
          .width(62).height(62).borderRadius(31).backgroundColor(COLORS.dark).textAlign(TextAlign.Center)
        Column({ space: 4 }) {
          Text('浙A·D88216 · Model Y').fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
          Text('剩余续航 312km · 电量 62%').fontSize(9).fontColor(COLORS.sub)
        }
        .alignItems(HorizontalAlign.Start).layoutWeight(1)
        Column({ space: 3 }) {
          Text('62%').fontSize(16).fontColor(COLORS.green).fontWeight(FontWeight.Bold)
          Text('当前电量').fontSize(8).fontColor(COLORS.text3)
        }
        .alignItems(HorizontalAlign.End)
      }
      .width('100%').padding(12).borderRadius(14).backgroundColor(COLORS.card)
      .borderWidth(1).borderColor(COLORS.line)

      Row({ space: 8 }) {
        Text('🔋 谷电时段 23:00-07:00 每度省¥0.30').fontSize(9).fontColor(COLORS.greenD)
          .padding({ left: 8, right: 8, top: 4, bottom: 4 }).borderRadius(8).backgroundColor(COLORS.dark)
        Text('🎫 满100减15券').fontSize(9).fontColor(COLORS.orange)
          .padding({ left: 8, right: 8, top: 4, bottom: 4 }).borderRadius(8).backgroundColor(COLORS.chip)
      }
      .width('100%')
    }
    .width('100%').padding({ left: 14, right: 14, top: 12, bottom: 12 })
  }

  @Builder
  mapTab() {
    Column({ space: 12 }) {
      Stack({ alignContent: Alignment.TopStart }) {
        Flex({ wrap: FlexWrap.Wrap }) {
          ForEach(mapCells(), (c: string) => {
            Column().width('11%').height(28).borderRadius(4).margin(2)
              .backgroundColor(mapCellColor(c))
          }, (c: string) => 'mc' + c)
        }
        .width('100%').padding(12).borderRadius(16).backgroundColor(COLORS.darkL)

        ForEach(STATION_LIST, (s: StationItem, i: number) => {
          Column({ space: 2 }) {
            Text('⚡').fontSize(14)
            Text(s.free.toString()).fontSize(8).fontColor('#FFFFFF').fontWeight(FontWeight.Bold)
          }
          .padding(6).borderRadius(12)
          .backgroundColor(plugColor(s.type))
          .position(mapPinPos(i))
          .animation({ duration: 600, iterations: 1 })
        }, (s: StationItem) => 'pin' + s.id.toString())
      }
      .width('100%').height(230)

      Row({ space: 8 }) {
        Column() {
          Text('🎯').fontSize(20)
            .animation({ duration: 400, iterations: 1 })
        }
        .padding(10).borderRadius(12).backgroundColor(COLORS.card)
        .onClick(() => { this.mapZoom = !this.mapZoom; })
        Column() { Text('📍').fontSize(20) }
        .padding(10).borderRadius(12).backgroundColor(COLORS.card)
        Text('🔄').fontSize(20).padding(10).borderRadius(12).backgroundColor(COLORS.card)
      }
      .width('100%').justifyContent(FlexAlign.End)

      Row({ space: 8 }) {
        Text('⚡ 附近电站 · 智能排序').fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
        Column().layoutWeight(1)
        Text('筛选 >').fontSize(9).fontColor(COLORS.green)
      }
      .width('100%')

      ForEach(STATION_LIST, (s: StationItem) => {
        Row({ space: 12 }) {
          Column({ space: 4 }) {
            Text(s.type).fontSize(10).fontColor('#FFFFFF').fontWeight(FontWeight.Bold)
            Text(speedLabel(s.type)).fontSize(7).fontColor('rgba(255,255,255,0.85)')
          }
          .width(56).height(52).borderRadius(14).backgroundColor(plugColor(s.type))
          .justifyContent(FlexAlign.Center)

          Column({ space: 4 }) {
            Text(s.name).fontSize(11).fontColor(COLORS.title).fontWeight(FontWeight.Bold).maxLines(1)
            Text('⭐ ' + s.score + ' · ' + s.distance + ' · ' + s.price).fontSize(9).fontColor(COLORS.sub)
            Row({ space: 6 }) {
              ForEach(s.services, (t: string) => {
                Text(t).fontSize(8).fontColor(COLORS.teal)
                  .padding({ left: 6, right: 6, top: 2, bottom: 2 }).borderRadius(6).backgroundColor(COLORS.dark)
              }, (t: string) => s.id.toString() + t)
            }
          }
          .alignItems(HorizontalAlign.Start).layoutWeight(1)

          Column({ space: 5 }) {
            Text(s.free + '/' + s.total).fontSize(13).fontColor(s.free > 0 ? COLORS.green : COLORS.red).fontWeight(FontWeight.Bold)
            Text('空闲').fontSize(8).fontColor(COLORS.text3)
          }
          .alignItems(HorizontalAlign.End)
        }
        .width('100%').padding(12).borderRadius(14).backgroundColor(COLORS.card)
        .alignItems(VerticalAlign.Center).borderWidth(1).borderColor(COLORS.line)
      }, (s: StationItem) => 'st' + s.id.toString())
    }
    .width('100%')
  }

  @Builder
  stationTab() {
    Column({ space: 12 }) {
      Text('⚡ 全部电站 · ' + STATION_LIST.length.toString() + ' 家').fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)

      Scroll() {
        Row({ space: 10 }) {
          ForEach(['全部', '超充站', '快充站', '慢充站', '谷电站', '带休息室'], (c: string) => {
            Text(c).fontSize(10).fontColor(c === '全部' ? '#FFFFFF' : COLORS.sub)
              .padding({ left: 14, right: 14, top: 7, bottom: 7 }).borderRadius(16)
              .backgroundColor(c === '全部' ? COLORS.green : COLORS.card)
              .borderWidth(c === '全部' ? 0 : 1).borderColor(COLORS.line)
          }, (c: string) => c)
        }
        .padding({ left: 4, right: 4 })
      }
      .scrollable(ScrollDirection.Horizontal).scrollBar(BarState.Off).width('100%')

      ForEach(STATION_LIST, (s: StationItem) => {
        Column({ space: 10 }) {
          Row({ space: 10 }) {
            Text(s.name).fontSize(12).fontColor(COLORS.title).fontWeight(FontWeight.Bold).layoutWeight(1).maxLines(1)
            Text(s.type).fontSize(9).fontColor(plugColor(s.type)).fontWeight(FontWeight.Bold)
              .padding({ left: 8, right: 8, top: 3, bottom: 3 }).borderRadius(8).backgroundColor(COLORS.chip)
          }
          .width('100%')

          Row({ space: 10 }) {
            Column() { Text('🧭 导航').fontSize(10).fontColor(COLORS.greenD).fontWeight(FontWeight.Bold) }
            .layoutWeight(1).padding({ top: 8, bottom: 8 }).borderRadius(10).backgroundColor(COLORS.dark)
            Column() { Text('⚡ 立即充电').fontSize(10).fontColor('#FFFFFF').fontWeight(FontWeight.Bold) }
            .layoutWeight(1).padding({ top: 8, bottom: 8 }).borderRadius(10).backgroundColor(COLORS.green)
            .opacity(this.breath ? 0.8 : 1).animation({ duration: 500, iterations: 1 })
            Column() { Text('📅 预约').fontSize(10).fontColor(COLORS.blue).fontWeight(FontWeight.Bold) }
            .layoutWeight(1).padding({ top: 8, bottom: 8 }).borderRadius(10).backgroundColor(COLORS.chip)
          }
          .width('100%')
        }
        .width('100%').padding(12).borderRadius(14).backgroundColor(COLORS.card).borderWidth(1).borderColor(COLORS.line)
      }, (s: StationItem) => 'sc' + s.id.toString())
    }
    .width('100%')
  }

  @Builder
  orderTab() {
    Column({ space: 12 }) {
      Row({ space: 8 }) {
        Text('📄 充电订单').fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
        Column().layoutWeight(1)
        Text('本月 6 单 · 共 ¥256.85').fontSize(9).fontColor(COLORS.sub)
      }
      .width('100%')

      Column({ space: 10 }) {
        Text('⚡ 月度充电量(度)').fontSize(11).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
        Row({ space: 8 }) {
          ForEach(MONTH_LABELS, (m: string, i: number) => {
            Column({ space: 6 }) {
              Text(CHARGE_BARS[i].toString()).fontSize(8).fontColor(COLORS.sub)
              Column().width(16).height(chargeBarH(CHARGE_BARS[i])).borderRadius(8)
                .linearGradient({ angle: 180, colors: [[COLORS.teal, 0], [COLORS.greenD, 1]] })
              Text(m).fontSize(8).fontColor(COLORS.text3)
            }
            .alignItems(HorizontalAlign.Center)
          }, (m: string) => 'cm' + m)
        }
        .width('100%').padding({ top: 10, bottom: 12 }).borderRadius(14).backgroundColor(COLORS.card)
        .justifyContent(FlexAlign.Center)
      }
      .width('100%')

      ForEach(this.orderList, (o: ChargeOrder) => {
        Column({ space: 10 }) {
          Row({ space: 8 }) {
            Text('⚡').fontSize(14)
            Text(o.station).fontSize(10).fontColor(COLORS.title).fontWeight(FontWeight.Bold).layoutWeight(1).maxLines(1)
            Text(o.status).fontSize(9).fontColor(orderStateColor(o.status)).fontWeight(FontWeight.Bold)
          }
          .width('100%')

          Row({ space: 8 }) {
            Text('📅 ' + o.date).fontSize(8).fontColor(COLORS.text3).layoutWeight(1)
            if (o.status === '充电中') {
              Text('🟢 充电进度 68%').fontSize(9).fontColor(COLORS.green).fontWeight(FontWeight.Bold)
                .opacity(this.blink ? 0.5 : 1).animation({ duration: 500, iterations: 1 })
            } else if (o.status === '已预约') {
              Text('扫码开始 >').fontSize(9).fontColor(COLORS.blue)
            } else {
              Text('查看详情 >').fontSize(9).fontColor(COLORS.text3)
            }
          }
          .width('100%')
        }
        .width('100%').padding(12).borderRadius(14).backgroundColor(COLORS.card).borderWidth(1).borderColor(COLORS.line)
      }, (o: ChargeOrder) => 'co' + o.id.toString())
    }
    .width('100%')
  }

  @Builder
  walletTab() {
    Column({ space: 12 }) {
      Column({ space: 12 }) {
        Row({ space: 12 }) {
          Column({ space: 4 }) {
            Text('¥ 286.40').fontSize(26).fontColor('#FFFFFF').fontWeight(FontWeight.Bold)
            Text('钱包余额(元)').fontSize(9).fontColor('rgba(255,255,255,0.8)')
          }
          .alignItems(HorizontalAlign.Start).layoutWeight(1)
          Text('⚡').fontSize(38)
            .rotate({ angle: this.breath ? 10 : -10 })
            .animation({ duration: 700, iterations: 1, curve: Curve.EaseInOut })
        }
        .width('100%')

        Row({ space: 8 }) {
          Text('免费额度 28.6 度').fontSize(9).fontColor('#C8F5DC')
            .padding({ left: 10, right: 10, top: 4, bottom: 4 }).borderRadius(10)
            .backgroundColor('rgba(255,255,255,0.18)')
          Text('6 张充电券').fontSize(9).fontColor('#FFE082')
            .padding({ left: 10, right: 10, top: 4, bottom: 4 }).borderRadius(10)
            .backgroundColor('rgba(255,255,255,0.18)')
          Column().layoutWeight(1)
          Text('明细 >').fontSize(10).fontColor('#FFFFFF').fontWeight(FontWeight.Bold)
        }
        .width('100%')
      }
      .width('100%').padding(16).borderRadius(18)
      .linearGradient({ angle: 135, colors: [[COLORS.greenD, 0], [COLORS.teal, 1]] })

      Text('💰 充值套餐').fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)

      Flex({ wrap: FlexWrap.Wrap }) {
        ForEach(rechargePlans(), (p: RechargePlan) => {
          Column({ space: 6 }) {
            Text('¥' + p.pay).fontSize(16).fontColor(COLORS.green).fontWeight(FontWeight.Bold)
            Text('得 ¥' + p.get).fontSize(10).fontColor(COLORS.title)
            if (p.gift > 0) {
              Text('送 ' + p.gift + ' 度券').fontSize(8).fontColor(COLORS.orange)
            } else {
              Text('基础套餐').fontSize(8).fontColor(COLORS.text3)
            }
          }
          .width('30%').padding(14).borderRadius(14).backgroundColor(COLORS.card).margin(5)
          .alignItems(HorizontalAlign.Center).borderWidth(1).borderColor(COLORS.line)
        }, (p: RechargePlan) => 'rp' + p.pay.toString())
      }
      .width('100%')

      Text('流水记录').fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)

      ForEach(this.txList, (t: TxRecord) => {
        Row({ space: 12 }) {
          Text(t.icon).fontSize(22)
            .width(44).height(44).borderRadius(22).backgroundColor(COLORS.dark).textAlign(TextAlign.Center)
          Column({ space: 3 }) {
            Text(t.title).fontSize(11).fontColor(COLORS.title).fontWeight(FontWeight.Bold).maxLines(1)
            Text(t.time).fontSize(8).fontColor(COLORS.text3)
          }
          .alignItems(HorizontalAlign.Start).layoutWeight(1)
          Text(t.amount).fontSize(12)
            .fontColor(t.income ? COLORS.green : COLORS.orange).fontWeight(FontWeight.Bold)
        }
        .width('100%').padding(10).borderRadius(12).backgroundColor(COLORS.card)
        .alignItems(VerticalAlign.Center)
      }, (t: TxRecord) => 'tx' + t.id.toString())
    }
    .width('100%')
  }

  @Builder
  vipTab() {
    Column({ space: 12 }) {
      Text('👑 豹力会员 · 银豹Lv.3').fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)

      Column({ space: 10 }) {
        Row({ space: 12 }) {
          Text('🐆').fontSize(36)
          Column({ space: 4 }) {
            Text('银豹会员 · 2027-01-15 到期').fontSize(13).fontColor('#FFFFFF').fontWeight(FontWeight.Bold)
            Text('再充 214 元升级金豹 · 享受 88 折电价').fontSize(9).fontColor('rgba(255,255,255,0.85)')
          }
          .alignItems(HorizontalAlign.Start).layoutWeight(1)
        }
        .width('100%')

        Column({ space: 6 }) {
          Row() {
            Column().height(6).borderRadius(3).layoutWeight(1)
              .linearGradient({ angle: 90, colors: [[COLORS.green, 0], [COLORS.teal, 1]] })
          }
          .width('100%')
          Text('成长值 786 / 1000').fontSize(8).fontColor('rgba(255,255,255,0.8)')
        }
        .width('100%')
      }
      .width('100%').padding(16).borderRadius(18)
      .linearGradient({ angle: 135, colors: [[COLORS.title, 0], [COLORS.greenD, 1]] })

      Text('🎖️ 会员专属权益').fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)

      ForEach(vipRights(), (r: VipRight) => {
        Row({ space: 12 }) {
          Text(r.icon).fontSize(24)
            .width(48).height(48).borderRadius(14).backgroundColor(COLORS.dark).textAlign(TextAlign.Center)
          Column({ space: 4 }) {
            Text(r.title).fontSize(12).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
            Text(r.desc).fontSize(9).fontColor(COLORS.sub).maxLines(1)
          }
          .alignItems(HorizontalAlign.Start).layoutWeight(1)
          Text(r.status).fontSize(9).fontColor(r.status === '已领取' ? COLORS.green : COLORS.orange)
            .fontWeight(FontWeight.Bold)
        }
        .width('100%').padding(12).borderRadius(14).backgroundColor(COLORS.card)
        .alignItems(VerticalAlign.Center).borderWidth(1).borderColor(COLORS.line)
      }, (r: VipRight) => 'vr' + r.title)

      Column({ space: 8 }) {
        Text('📈 本月已省').fontSize(10).fontColor(COLORS.sub)
        Text('¥ 86.42').fontSize(24).fontColor(COLORS.orange).fontWeight(FontWeight.Bold)
        Text('相当于 2.5 次免费快充').fontSize(9).fontColor(COLORS.text3)
      }
      .width('100%').padding(18).borderRadius(16).backgroundColor(COLORS.dark)
      .alignItems(HorizontalAlign.Center)
    }
    .width('100%')
  }

  @Builder
  circleTab() {
    Column({ space: 12 }) {
      Row({ space: 8 }) {
        Text(' EV 车主圈 · 电友动态').fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
        Column().layoutWeight(1)
        Text('✍️ 发帖').fontSize(10).fontColor(COLORS.green).fontWeight(FontWeight.Bold)
      }
      .width('100%')

      Scroll() {
        Row({ space: 12 }) {
          ForEach(this.postList, (p: CirclePost) => {
            Column({ space: 5 }) {
              Text(p.avatar).fontSize(24)
                .width(50).height(50).borderRadius(25).backgroundColor(COLORS.dark).textAlign(TextAlign.Center)
              Text(p.author).fontSize(9).fontColor(COLORS.title).fontWeight(FontWeight.Bold).maxLines(1)
            }
            .width(70).padding(8).borderRadius(14).backgroundColor(COLORS.card)
            .alignItems(HorizontalAlign.Center).borderWidth(1).borderColor(COLORS.line)
          }, (p: CirclePost) => 'pa' + p.id.toString())
        }
        .padding({ left: 4, right: 4 })
      }
      .scrollable(ScrollDirection.Horizontal).scrollBar(BarState.Off).width('100%')

      ForEach(this.postList, (p: CirclePost) => {
        Column({ space: 10 }) {
          Row({ space: 10 }) {
            Text(p.avatar).fontSize(22)
              .width(42).height(42).borderRadius(21).backgroundColor(COLORS.dark).textAlign(TextAlign.Center)
            Column({ space: 3 }) {
              Text(p.author).fontSize(11).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
              Text(p.car + ' · ' + p.time).fontSize(8).fontColor(COLORS.text3)
            }
            .alignItems(HorizontalAlign.Start).layoutWeight(1)
            Text('关注').fontSize(9).fontColor(COLORS.green).fontWeight(FontWeight.Bold)
              .padding({ left: 10, right: 10, top: 4, bottom: 4 }).borderRadius(10).backgroundColor(COLORS.chip)
          }
          .width('100%')

          Text(p.content).fontSize(11).fontColor(COLORS.title).lineHeight(16)

          Row({ space: 16 }) {
            Text('👍 ' + p.likes).fontSize(10).fontColor(COLORS.sub)
              .animation({ duration: 500, iterations: 1 })
            Text('💬 ' + p.comments).fontSize(10).fontColor(COLORS.sub)
            Text('⭐ 收藏').fontSize(10).fontColor(COLORS.sub)
            Column().layoutWeight(1)
            Text('分享').fontSize(10).fontColor(COLORS.green)
          }
          .width('100%')
        }
        .width('100%').padding(12).borderRadius(14).backgroundColor(COLORS.card)
        .alignItems(HorizontalAlign.Start).borderWidth(1).borderColor(COLORS.line)
      }, (p: CirclePost) => 'pc' + p.id.toString())
    }
    .width('100%')
  }

  @Builder
  profileTab() {
    Column({ space: 12 }) {
      Row({ space: 12 }) {
        Text('🧑‍🔧').fontSize(32)
          .width(58).height(58).borderRadius(29).backgroundColor(COLORS.dark).textAlign(TextAlign.Center)
        Column({ space: 4 }) {
          Text('电豹侠 · 银豹Lv.3').fontSize(14).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
          Text('累计充电 1,842 度 · 减碳 1.1 吨 🌱').fontSize(9).fontColor(COLORS.sub)
        }
        .alignItems(HorizontalAlign.Start).layoutWeight(1)
        Text('✏️').fontSize(18)
      }
      .width('100%').padding(14).borderRadius(16).backgroundColor(COLORS.card).borderWidth(1).borderColor(COLORS.line)

      Row({ space: 8 }) {
        Text('🚗 我的车辆').fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
        Column().layoutWeight(1)
        Text('➕ 绑定').fontSize(10).fontColor(COLORS.green).fontWeight(FontWeight.Bold)
          .onClick(() => { this.addModal = true; })
      }
      .width('100%')

      ForEach(this.carList, (c: CarInfo) => {
        Column({ space: 10 }) {
          Row({ space: 12 }) {
            Text(c.icon).fontSize(30)
              .width(56).height(56).borderRadius(16).backgroundColor(COLORS.dark).textAlign(TextAlign.Center)
            Column({ space: 4 }) {
              Text(c.model).fontSize(12).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
              Text(c.plate + ' · 电池 ' + c.battery).fontSize(9).fontColor(COLORS.sub)
            }
            .alignItems(HorizontalAlign.Start).layoutWeight(1)
            Text(c.soc.toString() + '%').fontSize(16).fontColor(COLORS.green).fontWeight(FontWeight.Bold)
          }
          .width('100%')

          Row({ space: 10 }) {
            Column() { Text('✏️ 改牌').fontSize(10).fontColor(COLORS.blue).fontWeight(FontWeight.Bold) }
            .padding({ left: 14, right: 14, top: 7, bottom: 7 }).borderRadius(10).backgroundColor(COLORS.chip)
            .onClick(() => {
              this.editCarId = c.id;
              this.editPlate = c.plate;
              this.editModal = true;
            })
            Column() { Text('🗑️ 解绑').fontSize(10).fontColor(COLORS.red).fontWeight(FontWeight.Bold) }
            .padding({ left: 14, right: 14, top: 7, bottom: 7 }).borderRadius(10).backgroundColor(COLORS.dark)
            .onClick(() => { this.delModal = true; this.editCarId = c.id; })
            Column().layoutWeight(1)
            Text('设为默认').fontSize(10).fontColor(COLORS.sub)
          }
          .width('100%')
        }
        .width('100%').padding(12).borderRadius(14).backgroundColor(COLORS.card).borderWidth(1).borderColor(COLORS.line)
      }, (c: CarInfo) => 'car' + c.id.toString())

      ForEach(['🏠 常用地址管理', '🔐 账号与安全', '🌙 夜间充电提醒', '📞 客服中心', '⚙️ 设置'], (s: string) => {
        Row({ space: 12 }) {
          Text(s).fontSize(11).fontColor(COLORS.title).layoutWeight(1)
          Text('>').fontSize(12).fontColor(COLORS.text3)
        }
        .width('100%').padding(13).borderRadius(12).backgroundColor(COLORS.card)
      }, (s: string) => 'set' + s)
    }
    .width('100%')
  }

  @Builder
  chargeStatOf(label: string, val: string, color: string) {
    Column({ space: 3 }) {
      Text(val).fontSize(11).fontColor(color).fontWeight(FontWeight.Bold)
      Text(label).fontSize(8).fontColor(COLORS.text3)
    }
    .layoutWeight(1).padding(8).borderRadius(10).backgroundColor(COLORS.dark)
    .alignItems(HorizontalAlign.Center)
  }

  @Builder
  tabRowItem(t: TabMeta, i: number, wide: boolean) {
    Column({ space: 3 }) {
      Text(t.icon).fontSize(17)
      Text(t.label).fontSize(8)
        .fontColor(this.tabIdx === i ? COLORS.tabOn : COLORS.text3)
        .fontWeight(this.tabIdx === i ? FontWeight.Bold : FontWeight.Normal)
    }
    .width(wide ? '33.3%' : '25%').padding({ top: 7, bottom: 6 })
    .onClick(() => { this.tabIdx = i; })
  }

  @Builder
  tabBar() {
    Column({ space: 0 }) {
      Row({ space: 0 }) {
        this.tabRowItem(TAB_ROW_1[0], 0, false)
        this.tabRowItem(TAB_ROW_1[1], 1, false)
        this.tabRowItem(TAB_ROW_1[2], 2, false)
        this.tabRowItem(TAB_ROW_1[3], 3, false)
      }
      .width('100%')

      Row({ space: 0 }) {
        this.tabRowItem(TAB_ROW_2[0], 4, true)
        this.tabRowItem(TAB_ROW_2[1], 5, true)
        this.tabRowItem(TAB_ROW_2[2], 6, true)
      }
      .width('100%')
    }
    .width('100%').backgroundColor(COLORS.card).borderRadius(16)
  }

  @Builder
  modalOverlay(onClose: () => void) {
    Column().width('100%').height('100%')
      .backgroundColor(COLORS.mask)
      .onClick(() => { onClose(); })
  }

  build() {
    Stack({ alignContent: Alignment.Center }) {
      Column() {
        this.header()
        Scroll() {
          Column({ space: 10 }) {
            if (this.tabIdx === 0) { this.mapTab() }
            else if (this.tabIdx === 1) { this.stationTab() }
            else if (this.tabIdx === 2) { this.orderTab() }
            else if (this.tabIdx === 3) { this.walletTab() }
            else if (this.tabIdx === 4) { this.vipTab() }
            else if (this.tabIdx === 5) { this.circleTab() }
            else { this.profileTab() }
          }
          .width('100%').padding({ left: 14, right: 14, top: 12, bottom: 16 })
        }
        .layoutWeight(1).scrollBar(BarState.Off).align(Alignment.Top)
        this.tabBar()
      }
      .width('100%').height('100%')

      if (this.addModal) {
        Stack({ alignContent: Alignment.Center }) {
          this.modalOverlay(() => { this.addModal = false; })
          Column({ space: 12 }) {
            Text('🚗 绑定新车').fontSize(15).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
            Text('绑定后可查看电量、预约充电桩').fontSize(10).fontColor(COLORS.sub)
            TextInput({ placeholder: '车牌号(如 浙A·D12345)', text: this.addPlate })
              .onChange((v: string) => { this.addPlate = v; })
              .fontSize(12).padding(10).borderRadius(10).backgroundColor(COLORS.dark)
            TextInput({ placeholder: '车型(如 小米 SU7)', text: this.addModel })
              .onChange((v: string) => { this.addModel = v; })
              .fontSize(12).padding(10).borderRadius(10).backgroundColor(COLORS.dark)
            Row({ space: 10 }) {
              Column() { Text('取消').fontSize(12).fontColor(COLORS.sub) }
              .layoutWeight(1).padding({ top: 10, bottom: 10 }).borderRadius(10).backgroundColor(COLORS.darkL)
              .onClick(() => { this.addModal = false; })
              Column() { Text('确认绑定').fontSize(12).fontColor('#FFFFFF').fontWeight(FontWeight.Bold) }
              .layoutWeight(1).padding({ top: 10, bottom: 10 }).borderRadius(10).backgroundColor(COLORS.green)
              .onClick(() => {
                this.carList.push(new CarInfo(
                  Date.now(),
                  this.addPlate.length > 0 ? this.addPlate : '浙A·新00000',
                  this.addModel.length > 0 ? this.addModel : '新能源车',
                  '75.0kWh', 400, 100, '🚗'));
                this.addModal = false;
              })
            }
            .width('100%')
          }
          .width('82%').constraintSize({ maxHeight: '80%' }).padding(16).borderRadius(16).backgroundColor(COLORS.card)
        }
        .width('100%').height('100%').zIndex(999)
      }

      if (this.editModal) {
        Stack({ alignContent: Alignment.Center }) {
          this.modalOverlay(() => { this.editModal = false; })
          Column({ space: 12 }) {
            Text('✏️ 修改车牌').fontSize(15).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
            Text('换牌后订单记录将自动关联').fontSize(10).fontColor(COLORS.sub)
            TextInput({ placeholder: '新车牌号', text: this.editPlate })
              .onChange((v: string) => { this.editPlate = v; })
              .fontSize(12).padding(10).borderRadius(10).backgroundColor(COLORS.dark)
            Row({ space: 10 }) {
              Column() { Text('取消').fontSize(12).fontColor(COLORS.sub) }
              .layoutWeight(1).padding({ top: 10, bottom: 10 }).borderRadius(10).backgroundColor(COLORS.darkL)
              .onClick(() => { this.editModal = false; })
              Column() { Text('保存').fontSize(12).fontColor('#FFFFFF').fontWeight(FontWeight.Bold) }
              .layoutWeight(1).padding({ top: 10, bottom: 10 }).borderRadius(10).backgroundColor(COLORS.green)
              .onClick(() => {
                updateCarPlate(this.carList, this.editCarId, this.editPlate);
                this.editModal = false;
              })
            }
            .width('100%')
          }
          .width('82%').constraintSize({ maxHeight: '80%' }).padding(16).borderRadius(16).backgroundColor(COLORS.card)
        }
        .width('100%').height('100%').zIndex(999)
      }

      if (this.delModal) {
        Stack({ alignContent: Alignment.Center }) {
          this.modalOverlay(() => { this.delModal = false; })
          Column({ space: 14 }) {
            Text('🗑️ 解绑车辆?').fontSize(15).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
            Text('解绑后将无法接收该车的充电提醒。').fontSize(10).fontColor(COLORS.sub)
            Row({ space: 10 }) {
              Column() { Text('再想想').fontSize(12).fontColor(COLORS.sub) }
              .layoutWeight(1).padding({ top: 10, bottom: 10 }).borderRadius(10).backgroundColor(COLORS.darkL)
              .onClick(() => { this.delModal = false; })
              Column() { Text('确认解绑').fontSize(12).fontColor('#FFFFFF') }
              .layoutWeight(1).padding({ top: 10, bottom: 10 }).borderRadius(10).backgroundColor(COLORS.red)
              .onClick(() => {
                removeCarById(this.carList, this.editCarId);
                this.delModal = false;
              })
            }
            .width('100%')
          }
          .width('78%').constraintSize({ maxHeight: '80%' }).padding(16).borderRadius(16).backgroundColor(COLORS.card)
        }
        .width('100%').height('100%').zIndex(999)
      }
    }
    .width('100%').height('100%').backgroundColor(COLORS.bg)
  }
}

interface RechargePlan {
  pay: number;
  get: number;
  gift: number;
}

function rechargePlans(): RechargePlan[] {
  const plans: RechargePlan[] = [
    { pay: 100, get: 100, gift: 0 },
    { pay: 200, get: 220, gift: 10 },
    { pay: 500, get: 580, gift: 50 },
    { pay: 1000, get: 1200, gift: 120 },
    { pay: 2000, get: 2500, gift: 300 },
    { pay: 3000, get: 3900, gift: 500 }
  ];
  return plans;
}

interface VipRight {
  icon: string;
  title: string;
  desc: string;
  status: string;
}

function vipRights(): VipRight[] {
  const rights: VipRight[] = [
    { icon: '⚡', title: '充电 92 折', desc: '全平台电站通用,每月封顶省 60 元', status: '生效中' },
    { icon: '🌙', title: '谷电加倍返', desc: '夜间充电返现比例翻倍至 8%', status: '生效中' },
    { icon: '🎫', title: '每月充电券包', desc: '含 4 张满 50 减 8 券', status: '已领取' },
    { icon: '🛋️', title: '贵宾休息室', desc: '超充站专属休息室免费使用权', status: '已领取' },
    { icon: '🚿', title: '免费洗车券', desc: '每月 1 次合作门店洗车', status: '待领取' },
    { icon: '🛡️', title: '电池健康检测', desc: '每季度 1 次电池 SOH 检测', status: '待领取' }
  ];
  return rights;
}

function mapCells(): string[] {
  const cells: string[] = [];
  for (let i = 0; i < 56; i++) {
    if (i % 9 === 0) { cells.push('r' + i.toString()); }
    else if (i % 7 === 0) { cells.push('g' + i.toString()); }
    else { cells.push('e' + i.toString()); }
  }
  return cells;
}

function mapCellColor(c: string): string {
  if (c.indexOf('r') === 0) { return '#BFE3CD'; }
  if (c.indexOf('g') === 0) { return '#D8F0DF'; }
  return '#E3F3E9';
}

function mapPinPos(i: number): Position {
  const xs: number[] = [40, 130, 220, 60, 160, 250];
  const ys: number[] = [40, 90, 50, 150, 130, 70];
  return { x: xs[i], y: ys[i] };
}

function chargeBarH(v: number): number {
  return Math.round(v / 320 * 90);
}

function updateCarPlate(list: CarInfo[], id: number, plate: string): void {
  for (let i = 0; i < list.length; i++) {
    if (list[i].id === id) {
      const o: CarInfo = list[i];
      list.splice(i, 1, new CarInfo(o.id, plate, o.model, o.battery, o.range, o.soc, o.icon));
    }
  }
}

function removeCarById(list: CarInfo[], id: number): void {
  for (let i = list.length - 1; i >= 0; i--) {
    if (list[i].id === id) { list.splice(i, 1); }
  }
}


十四、开发环境搭建

14.1 安装 DevEco Studio

DevEco Studio 是 HarmonyOS 应用开发的官方 IDE。下载安装包后双击运行,进入安装欢迎界面,点击「下一步」继续。

DevEco Studio 安装欢迎界面

14.2 选择安装路径

选择目标安装目录,建议安装到非系统盘,确认所需空间后点击「下一步」。

选择安装位置

14.3 配置安装选项

设置环境变量,勾选创建桌面快捷方式、将 bin 文件夹添加到 PATH、添加上下文菜单,但是需要重启一下电脑使环境变量生效。

安装选项配置

14.4 选择项目模板

新建一个空白模板,在模板市场中选择 Empty Ability。

选择 Empty Ability 模板

14.5 配置项目信息

设置 API 为 24 的模板项目,项目名称 kfschool,Compatible SDK 6.1.1(24),设备类型勾选 Phone,点击完成后初始化项目,自动下载相关依赖。

配置项目信息


十五、总结

通过对这款充电豹新能源充电服务应用的深度拆解,我们可以看到,一个优秀的移动端应用远不止是功能的堆砌,它更是架构设计、数据建模、视觉系统、交互逻辑、动效设计、数据计算等多维度能力的综合体现。

在架构层面,该应用采用了"配置驱动 + 组件化 + 状态管理"的三层设计模式。配置层将所有可变的数据与视觉属性抽离为常量数组和元数据接口,使得业务逻辑与表现层彻底解耦;组件层将七个功能页面拆分为独立的 @Builder 构建器,每个页面采用完全不同的布局风格,实现"一页一型"的差异化设计;状态层利用 ArkUI 的 @State、@Observed 机制,实现了数据变化到 UI 刷新的自动响应,特别是弹窗数据状态的精细设计(editCarId 共享于 editModal 和 delModal 记录车辆ID,addPlate/addModel 用于绑定新车输入,editPlate 预填原车牌),三个弹窗共享 editCarId 状态实现车辆ID的传递,减少了重复状态定义,体现了对状态管理的精准理解和弹窗复用设计的工程化思维。

在数据层面,五个 @Observed 数据类(StationItem/ChargeOrder/CarInfo/TxRecord/CirclePost)以 constructor 构造函数方式实例化,完整描述了充电豹的全部业务信息。特别的是,应用定义了 12 个工具函数(plugColor/orderStateColor/speedLabel/rechargePlans/vipRights/mapCells/mapCellColor/mapPinPos/chargeBarH/updateCarPlate/removeCarById),将颜色映射、数据计算、地图生成、增删改逻辑从 UI 层抽离,实现数据与逻辑分离,是本应用工程化设计的核心亮点。chargeBarH 函数将充电量动态映射为柱状图像素高度(v/320*90),mapCells/mapCellColor/mapPinPos 三个函数配合实现了纯代码模拟地图的生成(56个三色格子+6个彩色pin),updateCarPlate 采用 splice 替换对象的方式触发 @Observed 响应式更新(因为直接修改 @Observed 对象属性可能不触发响应式刷新,而替换整个对象会触发),这是 ArkUI 响应式更新的关键技巧,体现了对框架机制的深入理解。removeCarById 采用倒序遍历按ID删除,避免splice导致的索引偏移问题,是数组删除的标准实践。

在视觉层面,本应用独创了"科技绿主题"设计——主色 #0FA958 科技绿搭配辅助色 #00B8A9 电光青,bg #EDF7F0 科技绿底,title #12331E 深墨绿黑而非纯黑,与科技绿底形成高对比度的同时带有环保清新质感。应用的渐变设计是色彩系统的核心亮点——钱包余额卡使用 135 度深墨绿→电光青渐变,订单月度充电量柱状图使用 180 度电光青→深墨绿垂直渐变,会员等级卡使用 135 度深墨绿黑→深墨绿渐变,会员成长值进度条使用 90 度科技绿→电光青水平渐变,四种不同角度和配色的科技绿渐变贯穿整个应用,形成了统一而丰富的科技绿渐变视觉语言。车控头部采用圆形车头像+车牌车型+剩余续航电量+当前电量百分比的卡片布局,谷电时段提醒(23:00-07:00每度省¥0.30深墨绿字)体现充电省钱策略,是本应用头部的特色设计。应用的动态颜色逻辑也十分丰富——桩类型三色着色(超充红/快充橘/慢充青)、订单状态四档着色(已完成绿/充电中橘/已预约蓝/其他灰)、流水收支两色(收入绿/支出橘)、空闲数两色(有空闲绿/无空闲红)、地图格子三色(深绿/中绿/浅绿)、会员权益状态两色(已领取绿/待领取橘),形成了"以色定桩类型、以色定订单状态、以色定收支、以色定空闲、以色定地图区块、以色定权益状态"的多层语义着色模式。统一的色彩系统是整个应用的视觉基石,科技绿主色搭配电光青辅助色,配合科技绿底和四种科技绿渐变,营造出既环保清新又科技未来的新能源充电氛围。

在交互层面,应用独创了"双动画状态联动"设计——aboutToAppear 中 setInterval 700ms 同时切换 breath 和 blink 两个布尔动画状态,breath 同时用于电站页"立即充电"按钮透明度动画(opacity 0.8/1,duration 500ms)和钱包页⚡图标旋转动画(rotate 10/-10度,duration 700ms EaseInOut),blink 用于订单页充电中订单的充电进度闪烁动画(opacity 0.5/1,duration 500ms),两个动画状态各司其职驱动三种不同的动效,形成整体协调的联动动效体系。"立即充电"按钮的呼吸动画让核心操作有了"吸引眼球"的动态感,钱包⚡图标旋转动画让余额卡有了"闪电充电"的动感,充电中订单的闪烁动画模拟充电中状态,三种动效各司其职,让静态的页面有了"鲜活"的动态感。模拟地图桩位导航是本应用最具技术特色的交互——纯代码用 Flex wrap 生成56个三色格子模拟地图街区,Stack 叠加6个彩色pin(超充红/快充橘/慢充青)显示空闲桩数,mapPinPos 函数返回固定坐标实现pin的精确定位,无需任何地图SDK即可实现模拟地图导航效果,是本应用技术实现的亮点。7Tab两排布局(上4下3)通过 tabRowItem @Builder 复用构建,wide 参数控制宽度,解决了7个Tab单排过于拥挤的问题,是本应用导航设计的特色。三个弹窗(绑定新车/修改车牌/解绑车辆)各自独立 Stack 叠加,共享 modalOverlay 统一遮罩封装,editCarId 状态共享于修改和解绑弹窗实现车辆ID传递,绑定新车确认后 push 新车辆,改牌确认后 updateCarPlate(splice替换对象),解绑确认后 removeCarById(倒序删除),形成了完整的车辆管理增删改闭环。

充电豹应用整体运行效果 - 车控头部与模拟地图带彩色桩位pin及附近电站列表


标签: HarmonyOS · API 24 · ArkTS · 华为 · 新能源充电 · 补能网络 · 充电桩 · 模拟地图 · 移动端开发

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐