一、技术前言

在这里插入图片描述

在智慧物业管理领域,安全巡检是保障建筑运行的第一道防线。从消防通道占用复查到配电间红外测温,从喷淋管网压力巡检到水泵启动试运行测试,每一项巡检任务都需要精确的流程管控、清晰的进度追踪和即时的异常告警能力。传统巡检应用往往面临三大痛点:取证影像模糊导致责任争议、告警铃声单一导致响应迟缓、巡检路径复杂导致界面交互割裂。

在这里插入图片描述
HarmonyOS ArkUI 框架以其声明式 UI 范式为这些问题提供了系统级的解决方案。ArkUI 基于 TypeScript 扩展的 ArkTS 语言,通过 @Component 装饰器封装可复用组件,通过 @State@Observed 等状态管理装饰器实现数据驱动渲染,通过 @Builder 方法将复杂的 UI 结构拆分为可组合的构建块。这种架构天然适合巡检场景中"数据-视图-交互"紧耦合的需求。

在这里插入图片描述
本平台深度融合了 HarmonyOS 6.1.1 的三大前沿特性。Camera Kit 提供了 VideoSession 的 AUTO_FRAMING(影随人动)能力链——通过 isControlCenterSupportedgetSupportedEffectTypesenableControlCenter 三步实现巡检取景时人员始终居中;同时 PhotoSession 的手动对焦三接口 isFocusDistanceSupportedsetFocusDistancegetFocusDistance 实现铭牌近拍到机房全景的精确对焦控制。Notification Kit 实现了沙箱自定义铃声链路——通过 buildWavBytes 生成正弦波 PCM 音频,写入 EL1 沙箱 filesDir,再以 'uri::' + fileUri.getUriFromPath(沙箱路径) 填入 NotificationRequest.sound,让不同等级告警拥有差异化铃声。Tabs 嵌套滚动 通过 nestedScroll(TabsNestedScrollMode) 让内层检查项列表滑到边缘后联动外层楼宇频道,实现巡检路径的自然流转。

在这里插入图片描述

二、整体架构流程图

Page1201 主组件

headerBanner 头部渐变 Banner

内容区 7 Tab 切换

tabBar 底部导航

modalOverlay 弹窗遮罩

Tab0 任务
完成率统计+进度条清单+月度柱状图

Tab1 相机
XComponent预览+影随人动能力链

Tab2 对焦
能力查询+三档预设+Slider+读回校验

Tab3 频道
楼宇×检查项双层Tabs嵌套滚动

Tab4 日志
nestedScroll翻页时间轴

Tab5 告警
上报表单+授权卡+铃声库+发布历史

Tab6 我的
巡检员渐变大卡+绩效清单

Camera Kit
AUTO_FRAMING影随人动

Camera Kit
手动对焦三接口

Tabs嵌套滚动
nestedScroll模式

Notification Kit
沙箱自定义铃声

panelAdd 新建巡检任务

panelEdit 编辑巡检进度

panelDel 删除确认

整体架构以 Page1201 为根组件,采用 Stack 容器实现页面层叠:底层是 Column 纵向布局的头部 Banner + 内容区 + 底部 Tab 栏,顶层是全屏弹窗遮罩。内容区通过 currentTab 状态索引在 7 个 @Builder 方法间切换,每个 Tab 拥有完全独立的布局结构。四大特性(Camera Kit 影随人动、手动对焦、Tabs 嵌套滚动、Notification 沙箱铃声)分别挂载在相机、对焦、频道、告警四个 Tab 上,但它们的状态变量统一声明在组件顶层,实现跨 Tab 数据共享。

在这里插入图片描述

三、色彩体系设计

3.1 ColorPalette 接口定义

平台采用深色安全蓝灰主题,通过 ColorPalette 接口集中声明全部颜色字段:

interface ColorPalette {
  bg: string;      // 页面背景(安全蓝灰黑)
  card: string;    // 卡片底色(深蓝灰)
  title: string;   // 主标题(冷白)
  sub: string;     // 副标题(蓝灰)
  text3: string;   // 三级弱文本(暗蓝灰)
  orange: string;  // 警示橙(主色)
  orangeD: string; // 警示橙深色(渐变起点)
  blue: string;    // 信息蓝(电气 / 内层日志)
  green: string;   // 合格绿(消防 / 已生效)
  red: string;     // 警示红(失败 / 删除)
  line: string;    // 分割线
  tabOn: string;   // Tab 选中色
  mask: string;    // 弹窗遮罩
  onMain: string;  // 橙底文字色(深暖黑)
}

3.2 COLORS 常量逐色分析

const COLORS: ColorPalette = {
  bg: '#14171C',      // 极深蓝灰黑,模拟夜间巡检环境
  card: '#1E232B',    // 卡片底色,比背景略亮一档
  dark: '#283039',    // 次级容器底色(统计格 / 进度条底)
  title: '#EDF1F5',    // 冷白色标题,高对比度保证暗光可读
  sub: '#ADBAC7',     // 蓝灰副标题,层次柔和过渡
  text3: '#74818E',   // 暗蓝灰弱文本,辅助信息不抢视觉
  orange: '#FF8A2B',  // 警示橙主色,巡检进度的视觉锚点
  orangeD: '#E06A10', // 深橙渐变起点,头部Banner到背景的过渡
  blue: '#4E9BE3',    // 信息蓝,电气检查项与内层日志标识
  green: '#34C98A',   // 合格绿,已完成状态与读回校验通过
  red: '#E85555',     // 警示红,逾期任务与删除操作
  line: '#2A313A',    // 分割线,低对比度不干扰内容
  tabOn: '#FF8A2B',   // Tab 选中色与主色一致
  mask: 'rgba(0,0,0,0.6)', // 半透黑遮罩
  onMain: '#231507'   // 橙底深字,保证按钮文字对比度
};

色彩设计遵循"安全警示"原则:橙绿蓝红四色分别对应"巡检中/已合格/信息参考/危险警告"四种语义状态,使用户在深色环境下凭颜色即可快速识别任务优先级。头部 Banner 的 linearGradientorangeDbg 实现警示橙到蓝灰黑的自然过渡,底部 7 Tab 栏选中态使用 orange 高亮,未选中态使用 text3 暗蓝灰弱化。

在这里插入图片描述

四、Tab 元数据与辅助数据

4.1 底部导航 Tab 定义

const TAB_LIST: TabMeta[] = [
  { icon: '📋', label: '任务' },
  { icon: '📷', label: '相机' },
  { icon: '🎯', label: '对焦' },
  { icon: '🌀', label: '频道' },
  { icon: '📜', label: '日志' },
  { icon: '🚨', label: '告警' },
  { icon: '👤', label: '我的' }
];

7 个 Tab 单排排列,从任务管理到个人中心覆盖巡检全流程。每个 Tab 的图标与其功能语义紧密对应:📋 代表任务清单,📷 代表取证拍照,🎯 代表对焦精准,🌀 代表频道流转,📜 代表日志记录,🚨 代表告警上报,👤 代表巡检员中心。

在这里插入图片描述

4.2 Camera Kit 效果枚举

const EFFECT_INFOS: EffectInfo[] = [
  { type: 0, name: 'BEAUTY', desc: '美颜 · since 20' },
  { type: 1, name: 'PORTRAIT', desc: '人像 · since 20' },
  { type: 2, name: 'AUTO_FRAMING', desc: '影随人动 · 6.1.1 新增' }
];

三效果枚举展示了 ControlCenterEffectType 的完整谱系。BEAUTY 和 PORTRAIT 自 API 20 起就存在,AUTO_FRAMING 是 6.1.1 新增能力,本平台正是利用这一新特性实现巡检跟拍时人员始终居中。

4.3 对焦预设三档

const FOCUS_PRESETS: FocusPreset[] = [
  { label: '铭牌近拍', distance: 0.1, scene: '0.1 · 铭牌/序列号' },
  { label: '设备中距', distance: 0.5, scene: '0.5 · 配电柜整柜' },
  { label: '环境远距', distance: 0.9, scene: '0.9 · 机房全景' }
];

对焦距离值范围 0.0(最近)到 1.0(最远),三档预设覆盖巡检取证的三个典型景别:0.1 用于拍摄设备铭牌和出厂序列号,0.5 用于拍摄配电柜整体和管线走向,0.9 用于拍摄机房全景和疏散通道。

4.4 嵌套滚动频道数据

const OUTER_CHANNELS: ChannelItem[] = [
  { name: '1 号楼', icon: '🏢' },
  { name: '2 号楼', icon: '🏬' },
  { name: '地下车库', icon: '🅿️' },
  { name: '配电房', icon: '⚡' },
  { name: '消防泵房', icon: '🚒' }
];
const INNER_TABS: string[] = ['消防', '电气', '管道', '通道', '监控'];

外层 5 个楼宇频道代表物业巡检的责任分区,内层 5 个检查项类别覆盖消防、电气、管道、通道、监控五大专业领域。两层 Tabs 嵌套形成 25 个检查矩阵,每个矩阵下有 8 条检查卡片,共 200 个检查点位。

4.5 月度隐患柱状图数据

const MONTH_NAME: string[] = ['03', '04', '05', '06', '07', '08'];
const MONTH_HAZARD: number[] = [12, 9, 15, 7, 11, 6];
const MONTH_MAX: number = 16;

近 6 个月隐患发现数据呈下降趋势(15→7→6),反映巡检整改效果。满刻度 16 处用于柱高归一化换算,柱状图随呼吸动画在 ±6% 区间交替波动。

五、工具函数

5.1 时间戳生成

function nowTime(): string {
  const d = new Date();
  return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}`;
}

nowTime 函数为 SwipeLog、FocusRecord、NoticeLog 三类时间线提供统一的时间戳格式化,使用 padStart(2, '0') 保证时分秒始终两位数,格式为 HH:mm:ss

5.2 嵌套模式文案映射

function modeLabel(mode: TabsNestedScrollMode): string {
  return mode === TabsNestedScrollMode.SELF_FIRST
    ? 'SELF_FIRST·先内后外' : 'SELF_ONLY·仅内层';
}
function modeShort(mode: TabsNestedScrollMode): string {
  return mode === TabsNestedScrollMode.SELF_FIRST ? '先内后外' : '仅内层';
}

两个函数将 TabsNestedScrollMode 枚举翻译为中文文案。modeLabel 返回完整技术文案用于日志记录,modeShort 返回短文案用于头部状态胶囊和模式切换 chips。SELF_FIRST 模式下内层滑到边缘会接力触发外层切换,SELF_ONLY 模式下内层滑动不联动外层。

5.3 影随人动状态配色

function framingStateColor(s: string): string {
  if (s === '影随人动已启用') return COLORS.green;
  if (s === '控制中心不支持' || s === 'AUTO_FRAMING 未声明') return COLORS.blue;
  if (s.indexOf('失败') >= 0 || s.indexOf('被拒') >= 0 || s.indexOf('未就绪') >= 0) return COLORS.red;
  return COLORS.text3;
}

该函数将影随人动能力链的各阶段结果映射为颜色:已启用为合格绿(表示巡检跟拍已就位),能力不支持为信息蓝(设备限制非故障),失败/被拒为警示红(需要处理),未查询为暗蓝灰弱化。

5.4 任务状态配色

function taskStatusColor(s: string): string {
  if (s === '已完成') return COLORS.green;
  if (s === '进行中') return COLORS.orange;
  if (s === '待复查') return COLORS.blue;
  if (s === '已逾期') return COLORS.red;
  return COLORS.text3;
}

四种巡检任务状态对应四种颜色语义:已完成→合格绿、进行中→警示橙(正在执行)、待复查→信息蓝(需要复核)、已逾期→警示红(超时未完成)。

5.5 WAV 音频生成

function buildWavBytes(freq: number, durationMs: number): ArrayBuffer {
  const sampleRate = 44100;
  const numSamples = Math.floor(sampleRate * durationMs / 1000);
  const dataSize = numSamples * 2;
  const buf = new ArrayBuffer(44 + dataSize);
  const view = new DataView(buf);
  // ... 44 字节 WAV 头写入 ...
  for (let i = 0; i < numSamples; i++) {
    const t = i / sampleRate;
    const env = Math.min(1, i / (sampleRate * 0.02));       // 起音包络
    const decay = Math.max(0, 1 - t / (durationMs / 1000)); // 自然衰减
    view.setInt16(44 + i * 2, Math.round(Math.sin(2 * Math.PI * freq * t) * 0.5 * env * decay * 32767), true);
  }
  return buf;
}

buildWavBytes 是 Notification Kit 沙箱铃声链路的起点。它使用 DataView 手工构造 44 字节 WAV 文件头(RIFF/WAVE/fmt /data 四段),再按频率参数生成 16bit 单声道 PCM 采样数据。起音包络 env 在前 20ms 内从 0 线性升到 1 避免爆音,自然衰减 decay 从 1 线性降到 0 模拟铃声渐弱。

5.6 检查要点池

function checkPoint(tabName: string, i: number): string {
  const pool: string[] = tabName === '消防'
    ? ['灭火器压力表指针', '消火栓水带卡扣', '疏散指示标识灯', ...]
    : tabName === '电气'
      ? ['配电柜母排温度', '断路器接线端子', ...]
      : // ... 管道/通道/监控各有 8 项 ...
  return pool[Math.min(pool.length - 1, Math.max(0, i - 1))];
}

checkPoint 函数为嵌套频道的 5 类检查项各提供 8 个行业语义化检查要点,如消防类的"灭火器压力表指针"、电气类的"配电柜母排温度"等。通过 tabName 分发到对应池,再按 i 索引取出,保证检查卡片内容真实可信。

六、数据模型层

6.1 TaskItem 巡检任务模型

@Observed export class TaskItem {
  building: string;  // 楼宇 / 分区名
  item: string;      // 巡检项目名
  progress: number;  // 进度(0~100)
  status: string;    // 状态(已完成/进行中/待复查/已逾期)
  constructor(building: string, item: string, progress: number, status: string) {
    this.building = building; this.item = item;
    this.progress = progress; this.status = status;
  }
}

TaskItem 是任务 Tab 的核心实体,使用 @Observed 装饰器使其字段变化能被 UI 感知。7 条 Mock 数据覆盖了从 1 号楼消防通道复查到 5 号楼应急照明断电测试的典型巡检场景,状态涵盖已完成、进行中、待复查、已逾期四种,进度值从 0% 到 100% 不等。

6.2 FocusRecord 对焦记录模型

@Observed export class FocusRecord {
  time: string;      // 操作时间戳
  distance: number;  // 设置的对焦距离(0.0~1.0)
  readback: number;  // 读回值(-1 表示调用失败)
  ok: string;        // 校验结论(已生效/读回偏差/失败)
  constructor(distance: number, readback: number, ok: string) {
    this.time = nowTime(); this.distance = distance;
    this.readback = readback; this.ok = ok;
  }
}

FocusRecord 记录每次手动对焦操作的完整链路:设置值 → 读回值 → 校验结论。readback 为 -1 时表示 getFocusDistance 调用失败,ok 字段为"已生效"时表示差值 < 0.01,为"读回偏差"时表示差值超出阈值。初始注入了 3 条种子记录(0.9/0.5/0.1 三档各一条)。

6.3 InnerCard 检查项卡片模型

@Observed export class InnerCard {
  id: string;     // ForEach 键
  tag: string;    // 检查项类别名
  title: string;  // 检查点标题
  desc: string;   // 检查内容描述
}

InnerCard 是嵌套频道内层的列表条目,由 innerMockData 生成器批量创建,每个频道×检查项组合产生 8 条,内容包含楼宇名、检查类别、检查点标题和描述。

6.4 SwipeLog 翻页日志模型

@Observed export class SwipeLog {
  layer: string;    // '外层楼宇' / '内层检查项'
  tabName: string;  // 切换到的页签名
  fromIdx: number;  // 起始索引
  toIdx: number;    // 目标索引
  mode: string;     // 事发时的嵌套模式
  time: string;     // 时间戳
}

SwipeLog 记录两层 Tabs 翻页事件,layer 区分外层楼宇和内层检查项,mode 记录事发时的嵌套模式(SELF_FIRST 或 SELF_ONLY),用于验证"内层滑到边缘是否接力外层"。

6.5 RingItem 与 NoticeLog 模型

@Observed export class RingItem {
  name: string;       // 铃声名
  file: string;       // 沙箱文件名
  freq: number;       // 生成频率 Hz
  duration: number;   // 时长 ms
  size: string;       // 文件大小展示
  inSandbox: boolean; // 是否已写入沙箱
}

@Observed export class NoticeLog {
  title: string;   // 通知标题
  text: string;    // 通知正文(含发布结果)
  time: string;    // 发布时间戳
}

RingItem 管理 4 条告警铃声(疏散警报/消防长鸣/门禁提示/周界蜂鸣),初始均未导入沙箱。NoticeLog 记录通知发布历史,成功和失败均记一条,初始注入 2 条种子数据。

七、组件主体结构

7.1 @State 状态变量总览

组件 Page1201 声明了大量状态变量,按功能分为五组:

基础 UI 状态currentTab 控制当前 Tab 索引,breath 驱动呼吸动画,timer 管理定时器句柄。

弹窗状态addModaleditModaldelModal 三态开关,editIdxdelIdx 操作索引,绑定 TaskItem 实体。

Camera Kit 成员previewController(XComponent 控制器)、cameraInputpreviewOutputvideoSession(影随人动宿主)、photoSession(手动对焦宿主)、surfaceReadysessionMode(idle/video/photo)、framingStatefocusSupportedfocusDistancefocusRecordspermState

Notification Kit 成员grantednotifyIdringListcurrentRingIdxnoticeLogsalarmTitlealarmLevelalarmDesc

Tabs 嵌套滚动成员nestedModeouterIndexinnerIndexswipeLogs

7.2 生命周期

aboutToAppear() {
  notificationManager.isNotificationEnabled().then((enabled: boolean) => {
    this.granted = enabled;
  }).catch(() => {});
  this.focusRecords.unshift(new FocusRecord(0.9, 0.9, '已生效'));
  this.focusRecords.unshift(new FocusRecord(0.5, 0.51, '已生效'));
  this.focusRecords.unshift(new FocusRecord(0.1, 0.12, '读回偏差'));
  this.timer = setInterval(() => {
    this.breath = !this.breath;
  }, 1000);
}

aboutToDisappear() {
  clearInterval(this.timer);
  this.releaseSession();
}

aboutToAppear 做三件事:查询通知授权状态、注入 3 条种子对焦记录、启动 1000ms 呼吸动画定时器。aboutToDisappear 清理定时器并释放相机资源,防止后台占用摄像头。

7.3 build() 根构建

build() {
  Stack({ alignContent: Alignment.Center }) {
    Column() {
      this.headerBanner()
      Divider().strokeWidth(1).color(COLORS.line)
      Column() {
        if (this.currentTab === 0) { this.tabTask() }
        else if (this.currentTab === 1) { this.tabCamera() }
        // ... 其余 Tab 分支 ...
      }.layoutWeight(1).width('100%')
      this.tabBar()
    }.width('100%').height('100%')
    if (this.addModal || this.editModal || this.delModal) {
      this.modalOverlay(() => { this.closeAllModals(); })
    }
  }.width('100%').height('100%').backgroundColor(COLORS.bg)
}

根构建使用 Stack 容器实现层叠:底层 Column 纵向排列头部 Banner、分割线、内容区(layoutWeight(1) 占满中间)、底部 Tab 栏;顶层是条件渲染的弹窗遮罩。内容区通过 if-else if 链在 7 个 Tab Builder 间切换。

八、头部区域详解

头部 headerBanner Builder 是全页视觉锚点,采用 linearGradientorangeDbg 的 160° 渐变:

@Builder headerBanner() {
  Column({ space: 10 }) {
    Row() {
      Column({ space: 4 }) {
        Text('巡检卫士 · 物业安全巡检').fontSize(20).fontWeight(FontWeight.Bold)
          .fontColor(COLORS.onMain)
        Text(this.currentTab === 0 ? `任务 · 今日 ${this.taskList.length} 项巡检`
          : this.currentTab === 1 ? '相机 · 影随人动取证预览'
          // ... Tab 联动副标题 ...
          : '巡检员中心').fontSize(11).fontColor(COLORS.onMain).opacity(0.85)
      }.alignItems(HorizontalAlign.Start).layoutWeight(1)
      Circle({ width: 10, height: 10 }).fill(COLORS.onMain)
        .opacity(this.breath ? 0.9 : 0.45)
    }.width('100%')
    // 状态胶囊行 ...
  }.padding({ left: 16, right: 16, top: 12, bottom: 12 })
  .width('100%')
  .linearGradient({ angle: 160, colors: [[COLORS.orangeD, 0], [COLORS.bg, 1]] })
}

头部包含两行内容。第一行是应用标题"巡检卫士 · 物业安全巡检"和 Tab 联动副标题,副标题通过 currentTab 索引切换 7 种文案,右侧是呼吸圆点(breath 翻转驱动透明度 0.9/0.45 交替)。

第二行是 4 个状态胶囊:任务胶囊显示巡检项数、相机会话胶囊显示 sessionMode(idle/video/photo,配以灰/绿/橙三色圆点)、通知授权胶囊显示 granted 状态(绿/红圆点)、嵌套滚动胶囊显示 modeShort 结果。胶囊均使用 mask 半透黑背景,与橙渐变头部形成层次。

九、Tab0 任务 — 完成率统计与进度条清单

任务 Tab 是巡检工作的首页,纵向 Scroll 布局包含四段内容。

第一段:完成率统计大卡。46px 的橙色 monospace 大数字显示完成率百分比,搭配渐变进度条(linearGradient 从 orange 到 green)和三格统计(进行中橙、待复查蓝、已逾期红)。完成率通过 taskDone() 方法遍历 taskList 统计 progress >= 100 的条目数计算得出。

第二段:任务清单头。显示"📋 今日巡检任务 · N 项"标题和"+ 新增"入口按钮,点击触发 openAdd() 打开新建弹窗。

第三段:进度条清单。使用 ForEach 遍历 taskList,每条渲染为 taskCard Builder。卡片包含楼宇徽标、状态徽章、巡检项目名、Progress 线性进度条(color 绑定 taskStatusColor)和编辑/删除操作按钮。

第四段:月度隐患柱状图chartCard Builder 使用 Column + ForEach 实现传统柱状图,6 根渐变柱(orange→orangeD)的柱高由 barHeight(i) 方法计算,breath 状态控制奇偶柱 ±6% 交替波动。

十、Tab1 相机 — 影随人动取证预览

相机 Tab 是 Camera Kit 影随人动特性的主舞台,采用独占高度布局。

授权卡:显示 CAMERA 权限状态(permState)和 Surface 就绪状态。点击"申请相机权限"调用 requestCameraPermission(),该方法通过 abilityAccessCtrl.createAtManager().requestPermissionsFromUser() 动态申请 ohos.permission.CAMERA 权限,检查 authResults[0] === 0 判断授权结果。

XComponent 预览本体:使用 Stack 叠加 XComponent(SURFACE 类型)和会话模式标签。onLoad 回调置 surfaceReady = true,后续会话创建依赖此标志。XComponentControllergetXComponentSurfaceId() 为 PreviewOutput 提供 Surface ID。

影随人动能力链startVideoMode() 方法完整实现了 AUTO_FRAMING 三步链路。首先通过 getCameraManager(ctx) 获取相机管理器,遍历 getSupportedCameras() 找到后摄,调用 getSupportedOutputCapability 获取预览 Profile。然后创建 CameraInput → open → 创建 PreviewOutput(传入 SurfaceId)→ 创建 VideoSession → beginConfig → addInput + addOutput → commitConfig。最后调用 queryFraming(this.videoSession) 执行能力链三步:

queryFraming(session: camera.VideoSession) {
  if (!session.isControlCenterSupported()) {
    this.framingState = '控制中心不支持';
    this.framingSupported = false;
    return;
  }
  const effects = session.getSupportedEffectTypes();
  this.framingSupported = effects.includes(camera.ControlCenterEffectType.AUTO_FRAMING);
  if (!this.framingSupported) { this.framingState = 'AUTO_FRAMING 未声明'; return; }
  session.enableControlCenter(true);
  this.framingState = '影随人动已启用';
}

第一步 isControlCenterSupported() 检查本机是否有控制中心,第二步 getSupportedEffectTypes() 获取支持的特效列表并检查是否包含 AUTO_FRAMING,第三步 enableControlCenter(true) 请求系统接管构图。三步任一失败均有降级文案,不会崩溃。

十一、Tab2 对焦 — 手动对焦三接口

对焦 Tab 展示 Camera Kit 手动对焦的三个接口,使用 PhotoSession(与 VideoSession 互斥)。

能力查询卡queryFocusSupport() 调用 this.photoSession.isFocusDistanceSupported() 同步方法判断本机是否支持设置对焦距离。未启动拍照会话时显示"未启动拍照会话",已启动但不支持时显示"不支持设置对焦距离"。

三档预设ForEach(FOCUS_PRESETS) 渲染三个等宽卡片,点击设置 focusDistance 为对应预设值,选中态使用橙色背景高亮。

焦距滑杆Slider 组件范围 0~1 步进 0.01,onChange 实时更新 focusDistance,下方显示 distanceLabel 景别文案。

设置并读回校验applyFocus() 方法是手动对焦三接口的完整演示:

applyFocus() {
  this.photoSession.setFocusDistance(this.focusDistance);      // 设置
  const readBack = this.photoSession.getFocusDistance();       // 读回
  const ok = Math.abs(readBack - this.focusDistance) < 0.01 ? '已生效' : '读回偏差';
  this.focusRecords.unshift(new FocusRecord(this.focusDistance, readBack, ok));
}

setFocusDistance 设置对焦距离,getFocusDistance 立即读回,差值 < 0.01 判为已生效。结果以 FocusRecord 实体 unshift 置顶时间线。

对焦记录时间线ForEach(this.focusRecords) 渲染时间线行,每行显示时间戳、设置值、箭头、读回值和校验结论徽章。focusOkColor 函数将"已生效"映射为绿色、"失败"映射为红色、其他映射为橙色。

十二、Tab3 频道 — 楼宇×检查项双层 Tabs 嵌套滚动

频道 Tab 是 Tabs 嵌套滚动特性的核心演示区。

模式切换 chipsForEach([TabsNestedScrollMode.SELF_ONLY, TabsNestedScrollMode.SELF_FIRST]) 渲染两个可点击 chips,点击切换 nestedMode。选中态橙色背景白字,未选中态卡片底色暗字。

双层位置说明行:橙色圆点 + “外层 X 号楼” + 蓝色圆点 + “内层 Y(第 N/5 页)”,实时显示两层 Tabs 的当前位置。

外层宿主 TabsTabs({ barPosition: BarPosition.Start }) 包含 5 个 TabContent,每个 tabBar 为楼宇图标+名称。barMode(BarMode.Scrollable) 支持横滑页签。onChange 回调记录外层翻页日志(layer=‘外层楼宇’),更新 outerIndex

内层 TabsinnerTabs(channel) Builder 为每个外层频道创建内层 Tabs。5 个检查项子页签,每个 TabContent 内是 List + ForEach(innerMockData(channel, name)),8 条检查卡片保证内容超一屏。关键代码:

.nestedScroll(this.nestedMode)   // 嵌套滚动挂载点

nestedScroll 挂载在被嵌套的内层 Tabs 上。在 SELF_FIRST 模式下,内层滑到边缘后继续滑动会接力触发外层 onChange;在 SELF_ONLY 模式下则不会。内层 onChange 同样记录翻页日志(layer=‘内层检查项’)。

十三、Tab4 日志 — nestedScroll 翻页时间轴

日志 Tab 以固定行高 72px 的时间轴展示两层翻页事件。

顶部计数 + 清空按钮:显示"已记录 N 次翻页",Button('清空日志')enabled 绑定 swipeLogs.length > 0

图例说明行:橙色圆点"外层楼宇翻页" + 蓝色圆点"内层检查项翻页" + 当前嵌套模式文案。

时间轴列表ForEach(this.swipeLogs) 渲染每条日志。每行分三列:时间列(固定宽 52,时间 + OUT/IN 标识)、竖线列(固定宽 3,外层橙色内层蓝色)、内容卡(层级徽章 + 页签名 + fromIdx→toIdx + 模式+时间)。空态显示引导文案"去「频道」Tab 滑动内外层页签"。

十四、Tab5 告警 — 沙箱自定义铃声全链路

告警 Tab 是 Notification Kit 特性的压缩单 Tab 展示,包含五个区块。

区块① 异常上报表单:标题输入框 + 等级 chips(一般/严重/紧急,配以蓝/橙/红三色) + 描述文本域 + 发布按钮。点击"🔔 发布告警通知"调用 submitAlarm(),组合等级与描述后走 publishNotice()

区块② 通知授权状态卡:显示 granted 状态,"重新查询"调用 isNotificationEnabled(),"申请授权"调用 requestAuth()requestAuth() 方法先尝试 requestEnableNotification(hostCtx),若返回 1600004(曾被拒绝)则降级调用 openNotificationSettings(hostCtx) 拉起系统通知设置页。

区块③ 告警铃声行ForEach(this.ringList) 渲染 4 条铃声,每行显示名称、频率/时长/大小/沙箱状态。点击"生成"调用 importRing(idx),该方法调用 saveRingToSandbox 将 WAV 写入 EL1 filesDir。点击"设默认"调用 setCurrentRing(idx) 切换 currentRingIdx

sound 实时值预览getSoundValue() 方法实时拼接 'uri::' + fileUri.getUriFromPath(沙箱路径),让用户在发布前预览通知 sound 字段的实际值。

区块⑤ 发布历史ForEach(this.noticeLogs) 渲染 NoticeLog 时间线,成功记录标题为标题色,失败记录标题为红色。

通知发布核心方法

publishNotice(title: string, text: string) {
  const ring = this.ringList[this.currentRingIdx];
  if (!ring.inSandbox) { this.importRing(this.currentRingIdx); }
  const appCtx = hostCtx.getApplicationContext();
  appCtx.area = contextConstant.AreaMode.EL1;
  const sandboxPath = appCtx.filesDir + '/' + ring.file;
  const uri = fileUri.getUriFromPath(sandboxPath);
  const soundVal = 'uri::' + uri;
  const request: notificationManager.NotificationRequest = {
    id: this.notifyId++,
    notificationSlotType: notificationManager.SlotType.SOCIAL_COMMUNICATION,
    content: { /* ... */ },
    sound: soundVal    // 沙箱 uri 填入 sound
  };
  notificationManager.publish(request);
}

关键链路:沙箱铃声必须在 EL1 区域 → 取 filesDir 路径 → fileUri.getUriFromPath 转为 uri → 'uri::' 前缀拼接 → 填入 NotificationRequest.sound。这是 6.1.1 新特性,之前 sound 只能传 rawfile 文件名。

十五、Tab6 我的 — 巡检员渐变大卡与绩效清单

我的 Tab 展示巡检员个人信息和月度绩效。

渐变大卡linearGradientorangeDorange 的 140° 渐变,包含巡检员姓名、工号、班次、三列统计(本月点位 312、整改闭环 54、巡检里程 46.8km)和责任区说明。

绩效清单ForEach(PERF_ROWS) 渲染 6 行绩效数据,每行包含序号徽章、绩效名称、说明文案和数值。前三行数值使用橙色(重点指标),后三行使用副标题色。

十六、底部 Tab 栏

@Builder tabBar() {
  Row() {
    ForEach(TAB_LIST, (tab: TabMeta, index: number) => {
      Column({ space: 3 }) {
        Text(tab.icon).fontSize(17)
        Text(tab.label).fontSize(9)
          .fontColor(this.currentTab === index ? COLORS.tabOn : COLORS.text3)
      }.justifyContent(FlexAlign.Center)
      .layoutWeight(1)
      .padding({ top: 7, bottom: 7 })
      .onClick(() => { this.switchTab(index); })
    }, (tab: TabMeta) => tab.label)
  }.width('100%').backgroundColor(COLORS.card)
  .border({ width: { top: 1 }, color: COLORS.line })
}

底部 7 Tab 单排,选中态 tabOn(警示橙),未选中态 text3(暗蓝灰)。点击调用 switchTab(idx),该方法在离开相机/对焦页时释放会话(cameraInput 同一时间只能绑一个 session),防止摄像头后台占用。

十七、弹窗系统

17.1 全屏遮罩 modalOverlay

@Builder modalOverlay(onClose: () => void) {
  Column() {
    Column().width('100%').layoutWeight(1)
      .onClick(() => { onClose(); })
    if (this.addModal) { this.panelAdd(onClose) }
    else if (this.editModal) { this.panelEdit(onClose) }
    else if (this.delModal) { this.panelDel(onClose) }
  }.width('100%').height('100%').backgroundColor(COLORS.mask)
  .justifyContent(FlexAlign.End)
}

遮罩层使用 Column 布局,空白区 layoutWeight(1) 占满上方,面板在底部弹出。backgroundColor(COLORS.mask) 为半透黑遮罩,点击空白区调用 onClose 关闭弹窗。面板根据三态标志(addModal/editModal/delModal)三选一渲染。

17.2 新建巡检任务 panelAdd

新建面板包含楼宇输入框、巡检项目输入框、初始进度 Slider(0~100 步进 5)和取消/创建按钮。点击"创建"调用 confirmAdd(),该方法将表单数据封装为 TaskItemunshift 置顶任务清单,初始进度 >= 100 时状态置为"已完成",否则置为"进行中"。

17.3 编辑巡检进度 panelEdit

编辑面板显示当前任务的楼宇/项目/状态信息,提供进度 Slider。点击"保存"调用 confirmEdit(),更新 taskList[editIdx]progressstatus(100% 自动置为"已完成")。

17.4 删除确认 panelDel

删除面板显示待删除任务的楼宇和项目名,点击"删除"调用 confirmDel(),使用 splice(delIdx, 1) 从列表中移除。

十八、功能模块对比表

维度任务 Tab相机 Tab对焦 Tab频道 Tab日志 Tab告警 Tab我的 Tab
布局方式Scroll 纵向四段独占高度+StackScroll 纵向五段双层 Tabs 嵌套固定行高时间轴Scroll 五区块Scroll 纵向
数据模型TaskItemFocusRecordInnerCardSwipeLogRingItem/NoticeLogPerfRow
字段数44456/33
核心操作新增/编辑/删除任务影随人动开关/停止三档预设/Slider/校验模式切换/双层翻页清空日志上报/授权/铃声/发布
动画效果呼吸柱状图波动呼吸圆点嵌套接力滚动呼吸圆点渐变大卡
状态颜色已完成绿/进行中橙/待复查蓝/已逾期红idle灰/video绿/photo橙已生效绿/偏差橙/失败红外层橙/内层蓝外层橙/内层蓝一般蓝/严重橙/紧急红前三行橙/后三行副色
数据量7 条3 效果枚举3 种子记录5×5×8=200 检查卡动态增长(封顶40)4 铃声+2 种子日志6 绩效行
特殊组件Progress 进度条XComponent+StackSlider 滑杆Tabs.nestedScrollList 固定行高TextInput/TextArealinearGradient 渐变

深化解析:从代码结构到业务闭环

布局方式与数据流

物业巡检页面需要把任务、取证、日志、告警和人员绩效串成闭环。任务卡不只是展示进度,它还是后续相机取证与告警记录的业务入口;日志用于说明操作何时发生;状态颜色帮助巡检员优先处理逾期和严重隐患。分析这些模块时,重点要观察同一个任务实体如何被列表、弹窗、统计卡与进度图共同消费,以及修改后哪些区域会随状态更新。

页面根结构通常由头部、内容区和底部 Tab 栏组成。头部负责展示当前业务状态,内容区根据索引选择不同的 @Builder,底部导航负责修改索引。这样的结构把“当前显示什么”收敛为一个明确状态:用户点击 Tab 后先更新索引,ArkUI 再重新计算相关分支。各个 Builder 虽然共享主题色和页面级数据,却可以采用完全不同的布局方式;高密度列表适合纵向 Scroll,概览数据适合横向统计卡或双列 Flex,实时预览类组件需要独占有界高度,历史事件则适合时间轴或固定行高 List。

数据模型层承担界面与业务之间的契约。使用 @Observed 的实体保存可编辑字段,页面级 @State 数组负责驱动 ForEach。新增时创建新实体并插入数组,编辑时修改目标实体,删除时移除对应项。为了让列表差分稳定,key 应来自不会改变的唯一标识,不宜使用标题等可编辑字段。统计数字、完成比例和分类数量属于派生信息,可以从数组即时计算,避免同时维护两份状态后出现卡片已经更新、图表仍显示旧值的情况。

弹窗表单使用独立缓存是必要的。打开新增弹窗时清空缓存,打开编辑弹窗时复制目标字段,用户确认后才写回正式模型。这样点击取消不会污染列表数据。若直接把 TextInput 双向绑定到列表实体,用户尚未保存时卡片就可能跟着变化,破坏“确认提交”的交互语义。删除弹窗还需要保存目标索引或唯一标识,并在确认时再次校验目标存在,避免列表变化后误删其他项。

核心代码与状态驱动机制

@State 的价值不是简单替代普通变量,而是建立状态与界面之间的依赖关系。当前 Tab、筛选条件、动画开关、弹窗显隐、下载进度或能力状态发生变化时,只有读取这些变量的组件需要刷新。代码段中连续的修饰器调用分别控制尺寸、间距、背景、字体和事件,它们共同构成声明式描述;阅读时应从容器方向、子项分布、状态绑定和交互回调四个层面理解,而不是逐个孤立翻译属性名称。

ForEach 负责把数组映射为重复 UI。回调中的 item 提供业务字段,index 适合显示顺序,但不适合作为长期身份。列表发生新增或删除时,稳定 key 可以让框架复用未变化节点,减少重建。若直接修改对象属性后界面没有按预期刷新,可在保持实体身份的前提下替换数组引用;但不应为了刷新把所有元素都重新构造,否则会增加无意义渲染并丢失局部状态。

条件渲染体现了页面状态机。空闲时展示引导,准备中展示进度,成功时展示结果,失败时展示原因和重试入口。相比一个布尔值,四态文案更能覆盖异步能力。系统接口调用前先检查权限、设备支持和会话状态,调用后再读取结果校验。异常处理除了记录错误码,还要把可理解的反馈写入响应式状态,让用户知道失败发生在哪一步。

动画效果与颜色使用策略

呼吸动画通常由定时器周期翻转 breath,再把该状态映射为透明度、柱高或圆点半径的小幅变化。它适合表达“正在运行”或让统计图保持生命感,但幅度应克制,不能改变核心数据含义。柱状图的基础高度仍由真实数值计算,动画只能在很小范围内偏移;进度环的角度仍由完成比例决定,不能为了视觉效果显示超过真实进度的结果。页面离开时必须清理定时器,避免后台继续刷新。

颜色常量应按语义使用。主色承担选中态和主要操作,辅助色突出数据或次级动作,绿色表达完成与可用,橙色表达进行中或需要注意,红色只用于失败、逾期和删除等高风险场景。弱文本与分割线降低视觉权重,遮罩色用于聚焦弹窗。颜色不能成为唯一的状态信息,还要配合文字、图标或进度值,保证色觉差异用户也能理解。

渐变更适合头部大卡、核心指标或柱状图,不宜在每个小元素上重复使用。深色主题要检查正文与卡片背景的对比度,浅色主题则要避免辅助文字过淡。选中和未选中 Tab 除颜色差异外,还可以通过字重、图标透明度或底部指示器区分。这样既保持主题统一,又能建立清晰的信息层级。

各 Tab 之间的交互联动

各 Tab 不应只共享一个导航索引,还应围绕业务对象建立必要联动。列表页新增或编辑数据后,头部计数、图表和个人统计要同步更新;网页或地图产生的结果应写入记录模型,供下载、日志或我的页面继续展示;通知、字幕、相机等系统能力的状态应在头部胶囊或对应 Tab 中保持一致。跨 Tab 跳转时先更新必要参数,再修改当前索引,可以避免目标页面读取到旧条件。

切换离开重型组件时需要处理资源边界。相机输入、地图监听、字幕控制器、Web 下载代理和定时器都不能只创建不释放。可以在统一的 switchTab 方法中判断来源与目标,离开能力页时解除监听或停止会话;页面销毁时再执行兜底释放。释放方法应允许重复调用,并对每个资源独立判空,确保一次异常不会阻止后续清理。

交互反馈要覆盖成功与失败。按钮点击后先进入处理中状态并防止重复提交;成功后更新模型、关闭弹窗并显示结果;失败后保留用户输入,展示错误原因和重试入口。权限拒绝、能力不支持、网络失败、文件不存在和输入非法都属于正常业务分支。通过状态卡或行内提示展示这些分支,比只在控制台打印更符合完整产品体验。

边界场景与验证思路

空列表时应显示占位说明和新增入口,不能只留下空白。长标题需要限制行数并使用省略号,数字字段需要限定上下界,文本提交前要去除首尾空格。筛选后无结果应保留清除条件的入口。删除最后一项后,当前选择索引要回退到有效范围。异步搜索连续触发时,应防止较早请求晚返回后覆盖新结果。

验证数据链路时,可以依次检查新增、编辑、删除和筛选:新增后列表条数、统计数字和图表是否同时变化;编辑取消后正式数据是否保持不变;删除后 ForEach key 是否稳定;切换 Tab 再返回时必要数据是否仍在。验证系统能力时分别模拟支持、拒绝和异常,确认界面都有明确状态。验证动画时检查页面离开后是否停止,低性能设备上是否仍保持流畅。

视觉验收需要检查不同屏幕宽度、系统字体放大、深浅背景对比和长文本换行。表格中的布局方式、模型、字段数、核心操作、动画、状态颜色、数据量和特殊组件应与正文一致。Mermaid 图则需要对应真实的数据流和能力链路,节点文字加引号以避免中文或特殊字符导致解析失败。

组件化设计的进一步理解

参数化 Builder 适合抽取重复的统计格、状态行、标签和按钮组。参数只传入渲染所需数据和事件,不让子构建器直接依赖过多页面变量,可以降低耦合。业务复杂后,可把模型与系统能力封装为独立控制器,页面只负责组合 UI 和响应状态。这样既保留声明式代码的直观性,也能让权限、错误码翻译和资源释放得到集中管理。

当前单页面集中展示完整源码,便于博文逐段讲解。若演进为正式项目,可以按领域拆分组件:导航和页面框架位于容器层,列表、图表和弹窗位于展示层,数据读写和 Kit 接入位于服务层。组件之间通过参数、回调、@Link@ObjectLink 传递状态,不使用全局变量代替清晰的数据流。

性能优化首先来自减少不必要刷新。派生数据不要重复存储,动画状态不要进入列表 key,长列表使用稳定标识,Canvas 只在数据或尺寸变化时重绘。其次是控制资源生命周期,页面不可见时停止高成本任务。最后才是微调阴影、渐变和绘制细节。这样的优先级能保证页面在功能增加后仍然可维护。

通过以上补充,可以看到 ArkUI 的声明式模式并非只让布局语法更简洁,它更重要的价值是把数据变化、界面刷新和交互反馈连接为可追踪链路。理解每个代码段读取什么状态、写入什么状态、影响哪些组件,才能真正掌握文章中多个 Tab、图表、弹窗和系统能力协同工作的原理。

巡检任务状态闭环补充分析

任务 Tab 中的进度、状态与弹窗操作构成最核心的数据闭环。新增任务时,楼宇和巡检项目决定任务身份,初始进度决定首次状态;编辑时 Slider 修改的是表单缓存,确认后才写回 TaskItem。当进度达到百分之百,状态自动切换为已完成;低于百分之百时仍保持进行中。这里不能只修改进度条宽度而遗漏状态文字,否则同一卡片会同时出现“已完成”和未满进度的矛盾信息。删除任务后,统计卡的任务总数、完成数量和柱状图数据也应从最新数组重新推导。

相机与对焦两个 Tab 共享硬件输入,却依赖不同会话类型。进入相机页时创建 VideoSession 承载影随人动,进入对焦页时创建 PhotoSession 承载焦距设置;切换之前先释放旧会话,可以避免同一 CameraInput 被重复绑定。能力查询、效果枚举和启用控制中心属于前后依赖的三步链路,任一步失败都应终止后续调用,并把状态卡更新为具体失败阶段。手动对焦也要在设置后读回数值,通过差值判断镜头是否真正到位,而不能只把调用成功当作最终结果。

频道与日志 Tab 用来观察嵌套滚动行为。SELF_FIRST 表达内层到边缘后由外层接力,SELF_ONLY 表达手势只作用于内层;每次切换都写入 SwipeLog,使抽象的滚动策略变成可检查时间线。告警 Tab 则把铃声沙箱路径、授权状态和通知历史连接起来。发布前确认授权和文件存在,发布成功后追加记录,才能让界面形成从隐患发现到通知留痕的完整巡检链路。

从颜色策略看,任务状态不能直接散落在卡片代码的多个三元表达式中,最好统一经过状态颜色函数映射。已完成使用绿色、进行中使用橙色、待复查使用蓝色、已逾期使用红色,进度条、徽章和提示文字读取同一结果,才能避免语义不一致。我的 Tab 虽然不提供编辑操作,却复用了任务统计结果,因此它也是验证数据链路的重要位置:任务变化后,个人绩效概览需要同步反映最新数量。底部导航的选中态则用警示橙与暗蓝灰区分,既呼应物业巡检主题,也让用户明确当前所处模块。

十九、总结与展望

本平台以"安全蓝灰 + 警示橙"的深色视觉体系为基底,将 HarmonyOS 6.1.1 的四大前沿特性——Camera Kit 影随人动、Camera Kit 手动对焦三接口、Notification Kit 沙箱自定义铃声、Tabs 嵌套滚动——有机融合进物业安全巡检的 7 个业务场景中。

在技术架构上,平台采用"状态集中声明 + Builder 分散渲染"的模式。所有 @State 变量统一声明在组件顶层,确保跨 Tab 数据共享(如相机 Surface 在相机和对焦两个 Tab 间共用);而每个 Tab 的 UI 结构封装在独立的 @Builder 方法中,保证布局互不相同。弹窗系统采用"三态标志 + 全屏遮罩 + 底部面板"的统一模式,通过 if-else if 链三选一渲染,点空白关闭、按钮确认,交互逻辑简洁一致。

在特性集成深度上,影随人动能力链的三步查询(isControlCenterSupportedgetSupportedEffectTypesenableControlCenter)保证了从设备能力到运行时状态的完整验证;手动对焦三接口(isFocusDistanceSupportedsetFocusDistancegetFocusDistance)实现了"设置-读回-校验"闭环;沙箱铃声链路(buildWavBytessaveRingToSandbox EL1 → fileUri.getUriFromPath'uri::' 前缀 → NotificationRequest.sound)打通了从音频生成到通知发布的完整管道;Tabs 嵌套滚动(nestedScroll(TabsNestedScrollMode))通过 SELF_FIRST/SELF_ONLY 双模式切换,让内外层滑动接力行为可观测、可记录。

展望未来,本平台可在以下方向继续深化:引入 GIS 地图组件实现巡检路径可视化追踪;接入蓝牙信标实现室内定位自动打卡;利用 AI 识别引擎对取证照片进行隐患自动分类;通过分布式能力实现多巡检员协同任务分发。HarmonyOS 的分布式架构和 AI 能力为这些扩展提供了坚实的技术底座。

附录:DevEco Studio 创建新项目与查看 SDK 版本

本章节演示如何使用 DevEco Studio 创建一个 HarmonyOS 新项目,并查看当前 IDE 已安装的 SDK 版本,适合作为其他技术博文的补充操作指南。


一、创建新项目

1.1 进入欢迎界面

启动 DevEco Studio 后,首先看到的是欢迎界面。左侧导航栏默认选中 “项目”,右侧提供三个主要入口:

  • 新建项目:从头创建新项目
  • 打开项目:打开本地已有项目
  • 克隆仓库:从 Git 等版本控制拉取代码

点击 “新建项目” 按钮,进入项目创建向导。

在这里插入图片描述

1.2 选择项目模板

在弹出的"新建项目"对话框中,左侧分类标签提供了两种项目类型:

类型说明
应用(Application)开发标准的 HarmonyOS 应用,具备完整的 Ability 生命周期
元服务(Atomic Service)开发轻量级的原子化服务,无需安装即可使用

选择 “应用” 标签后,右侧展示多种模板。对于大多数场景,推荐选择 “Empty Ability” —— 这是一个最基础的入门模板,仅包含 Hello World 功能,适合从零开始构建应用。

在这里插入图片描述

1.3 配置项目信息

点击 “下一步” 后,进入项目配置界面,需要填写以下核心参数:

配置项示例值说明
项目名称(Project name)rollboat应用的项目名称,建议使用英文命名
包名(Bundle name)com.rollboat.myapplication应用唯一标识,采用反向域名格式
保存路径(Save location)D:\CodeFactory\rollboat项目本地存储路径,避免使用中文和空格
兼容 SDK(Compatible SDK)6.1.1(24)目标 HarmonyOS API 版本,点击"查看参考"可了解各版本差异
模块名称(Module name)entry主模块名称,默认 entry 为应用入口模块
设备类型(Device types)☑ Phone勾选目标设备:Phone / Tablet / 2in1 / Car / Wearable / TV

右侧预览区会实时展示当前模板的默认效果 —— 一个居中显示的 “Hello World” 文本。

在这里插入图片描述

1.4 完成创建

确认配置无误后,点击右下角 “完成” 按钮,IDE 将自动执行以下操作:

  1. 生成项目骨架(Stage 模型目录结构)
  2. 执行 ohpm install 安装依赖
  3. 运行 Hvigor 构建初始化(Build Init

构建日志中显示 “退出代码为 0” 表示项目初始化成功。

在这里插入图片描述

1.5 项目结构概览

创建完成后,左侧项目面板展示的是标准的 Stage 模型 目录结构:

rollboat/
├── .hvigor/                   # Hvigor 构建工具缓存
├── .idea/                     # IDE 配置文件
├── AppScope/                  # 应用级全局配置
│   └── app.json5
├── entry/                     # 主模块(入口模块)
│   ├── src/main/ets/
│   │   ├── entryability/      # Ability 生命周期管理
│   │   │   └── EntryAbility.ets
│   │   └── pages/             # UI 页面
│   │       └── Index.ets      # 首页(默认 Hello World)
│   ├── src/main/resources/    # 资源文件
│   ├── module.json5           # 模块配置
│   └── build-profile.json5    # 构建配置
├── oh_modules/                # OHPM 依赖包
├── build-profile.json5        # 工程构建配置
├── hvigorfile.ts              # Hvigor 构建脚本
└── oh-package.json5           # 包管理配置

核心文件 Index.ets 的默认代码如下,采用 ArkTS 声明式 UI 语法:

@Entry
@Component
struct Index {
  @State message: string = 'Hello World';

  build() {
    RelativeContainer() {
      Text(this.message)
        .id('HelloWorld')
        .fontSize($r('app.float.page_text_font_size'))
        .fontWeight(FontWeight.Bold)
        .alignRules({
          center: { anchor: '__container__', align: VerticalAlign.Center },
          middle: { anchor: '__container__', align: HorizontalAlign.Center }
        })
        .onClick(() => {
          this.message = 'Welcome';
        })
    }
    .height('100%')
    .width('100%')
  }
}
关键语法作用
@Entry标记为页面入口,可用于路由跳转
@Component声明为自定义组件
@State状态变量,数据变更时自动触发 UI 刷新
RelativeContainer相对布局容器,替代传统线性布局
.onClick()点击事件,此处点击后文本变为 “Welcome”

打开右侧 Previewer(预览器),选择 Phone 设备,即可实时预览 Hello World 效果,无需连接真机或启动模拟器。

在这里插入图片描述


二、查看 SDK 版本

2.1 查看 HarmonyOS SDK

DevEco Studio 安装时已内置 HarmonyOS SDK,无需单独下载。通过以下路径查看:

文件 → 设置 → HarmonyOS SDK(或快捷键 Ctrl + Alt + S 搜索 “HarmonyOS SDK”)

在设置面板中,可以看到当前已安装的 SDK 版本信息:

名称阶段状态
HarmonyOS 6.1.1Release✅ 已安装

界面顶部提示:“HarmonyOS SDK 已经包含在 IDE,无需单独安装”,省去了手动配置 SDK 的繁琐步骤。

在这里插入图片描述

2.2 查看 ArkUI-X SDK(跨平台扩展)

如果项目需要将 ArkUI 框架扩展到多个 OS 平台(Android / iOS / OpenHarmony),还需要配置 ArkUI-X SDK。路径如下:

文件 → 设置 → 语言和框架 → ArkUI-X

在这里可以查看已安装和可选的 ArkUI-X SDK 版本:

版本SDK 版本号阶段状态
API Version 246.1.1.100Release✅ 已安装
API Version 236.1.0.28Beta1未安装
API Version 226.0.2.112Release未安装

安装路径示例:D:\DevTools\ArkUI-X\sdk

说明:ArkUI-X 允许开发者使用一套 ArkTS 主代码,同时构建多平台应用。如果仅开发 HarmonyOS 原生应用,无需额外安装 ArkUI-X SDK。

在这里插入图片描述


三、小结

步骤操作关键点
创建项目欢迎页 → 新建项目 → 选择 Empty Ability 模板 → 配置项目信息 → 完成使用 Stage 模型 + ArkTS 语言
查看 SDK设置 → HarmonyOS SDKSDK 已内置,无需手动安装
跨平台扩展设置 → ArkUI-X根据需要安装对应 API 版本

至此,DevEco Studio 的项目创建与 SDK 环境确认全部完成,可以开始 HarmonyOS 应用的功能开发。


本文基于 DevEco Studio 6.1.1 Release 版本编写,不同版本界面可能存在细微差异。

Logo

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

更多推荐