一、技术前言

在文博文旅数字化转型的浪潮中,文化遗产保护与公众探索之间始终存在一道认知壁垒。从大雁塔的楼阁式砖塔到小雁塔的密檐式遗存,从西安城墙的明代城垣到兴教寺塔的玄奘遗骨,每一处长安古迹都承载着不同朝代营造法式与保护等级的双重身份。传统文博应用面临三大工程挑战:地图标注与长按事件无法分离监听导致交互信息丢失、下载文件来源无法双链溯源导致文物资源版权存疑、WebP 图像元数据无法读写校验导致老照片数字化存档不可控。

HarmonyOS ArkUI 框架为这些挑战提供了系统级解决方案。ArkUI 的声明式 UI 范式通过 @Component 封装可复用组件、@State 管理响应式状态、@Builder 拆分复杂 UI 结构,天然适合"地图-搜索-百科-工坊"多 Tab 架构。@Observed 装饰器让数据模型字段级变化被 UI 感知,实现"事件触发即日志刷新"的流畅体验。@Entry 装饰器将页面根组件注册到路由栈,配合 aboutToAppear/aboutToDisappear 生命周期完成地图回调装配与定时器清理。

本平台深度融合 HarmonyOS 6.1.1 的三大前沿特性。Map Kit 提供了 MapComponent 原生地图渲染与 MapEventManager 的双长按事件监听能力链——通过 onMarkerLongClick 捕获标注点长按并读取 marker.getPosition() 经纬度,通过 onPoiLongClick 捕获底图 POI 长按并读取 poi.namepoi.position,同时 site.searchByTextreliability 相关性分数字段让搜索结果质量可量化展示。ArkWeb 引入了 WebDownloadDelegate 的四回调下载委托与 6.1.1 双 URL 溯源——onBeforeDownload 内调用 item.start() 提供沙箱路径避免任务停滞在 PENDING 状态,onDownloadFinish 内通过 getOriginalUrl()getReferrerUrl() 双字段实现文件来源与引用页的双链溯源,startDownload 支持应用侧主动发起下载无需网页内点击。ImageKit 实现了 WebP 元数据五字段的完整读写校验链——readImageMetadataByType 配合 MetadataType.WEBP_METADATA 类型化读取画布宽高、帧延迟(钳制/未钳制)与循环次数,writeImageMetadata 以字面量构造 WebPMetadata 写回后立即重建 ImageSource 回读比对,确保元数据操作可追溯。

二、整体架构流程图

弹窗系统

三大特性引擎

七大功能页签

Page1280 根组件

headerMain 品牌头部

内容区 7 Tab 切换

tabBar 底部导航

弹窗系统 add/edit/del

Tab0 首页
统计行+双列古迹卡+月度柱状图

Tab1 地图
双长按Toggle+MapComponent+事件日志流

Tab2 搜索
搜索框+reliability分数结果列表

Tab3 百科
地址栏+快捷站点+Web组件+主动下载

Tab4 下载
进度任务卡+双URL溯源记录列表

Tab5 工坊
四区块WebP元数据读写校验

Tab6 我的
渐变大卡+足迹清单+说明行

Map Kit
Marker/POI双长按+reliability评分

ArkWeb
四回调委托+双URL溯源

ImageKit
WebP五字段读写回读

panelAdd 收录新古迹

panelEdit 修订古迹档案

panelDel 移出名录确认

收录徽章+快捷收录按钮

7 Tab 单排 选中态鎏金

架构以 Page1280 为根组件,使用 Column 容器纵向排列:头部品牌行、分割线、内容区和底部 Tab 栏。内容区通过 currentTab 状态在 7 个 Builder 方法间切换,其中地图 Tab 和百科 Tab 因需要全屏高度不套 Scroll,其余 Tab 走 Scroll 分支。三大特性分散在地图(双长按事件)、百科/下载(双 URL 溯源)和工坊(WebP 元数据)三个 Tab 上,状态变量统一声明在组件顶层实现跨 Tab 共享。弹窗系统采用 Stack 层叠遮罩加内容卡片的方式,三个独立弹窗各自条件渲染。

三、色彩体系设计

3.1 ColorPalette 接口定义

interface ColorPalette {
  bg: string;      // 页面背景(深褐)
  card: string;    // 卡片底色(深木)
  title: string;   // 标题(宣纸暖白)
  sub: string;     // 副文(绢帛黄)
  text3: string;   // 三级弱文(陶土灰)
  bronze: string;  // 青铜绿主色
  bronzeD: string; // 青铜深色
  gold: string;    // 鎏金
  red: string;     // 朱砂红(国保徽章/删除)
  blue: string;    // 青蓝
  line: string;    // 分割线
  tabOn: string;   // Tab 选中色
  mask: string;    // 弹窗遮罩
}

3.2 COLORS 常量逐色分析

const COLORS: ColorPalette = {
  bg: '#1B150F',      // 深褐墨色,模拟博物馆暗光环境
  card: '#282017',    // 深木色卡片底,比背景亮一档
  dark: '#332A1E',    // 次级容器底色(统计格/标签底)
  title: '#F5EDDE',   // 宣纸暖白标题,暗光高对比
  sub: '#C8B99C',     // 绢帛黄副文,层次柔和
  text3: '#8F8266',   // 陶土灰弱文本,辅助信息不抢视觉
  bronze: '#5E9C8B',  // 青铜绿主色,地图标注与按钮主色
  bronzeD: '#46786A', // 青铜深色,渐变起点与深色徽章
  gold: '#D9A441',    // 鎏金强调色,文保分/等级/选中态
  red: '#C0392B',     // 朱砂红,国保徽章与删除操作
  blue: '#6B8FBF',    // 青蓝,POI 命中标记
  line: '#3A3122',    // 褐褐分割线,低对比不干扰
  tabOn: '#D9A441',   // Tab 选中色为鎏金(非主色青铜绿)
  mask: 'rgba(0,0,0,0.6)' // 半透黑遮罩
};

色彩体系以"青铜绿 + 鎏金"为核心对比,取意青铜器千年锈蚀与宫廷鎏金工艺的视觉对话。深褐墨色背景模拟博物馆暗光陈列环境,宣纸暖白标题提供暗光下的高对比阅读体验,绢帛黄副文层次柔和而不喧宾夺主。值得注意的是 Tab 选中色使用 gold(鎏金)而非 bronze(青铜绿),这是因为金色在深褐背景上对比度更高,用户视觉定位更迅速。朱砂红专用于全国重点文物保护单位徽章与删除操作,在视觉语义上形成了"国保=珍贵=朱砂"的色彩隐喻。我的 Tab 探访者渐变大卡使用 linearGradientbronzeDdark 的 135 度渐变,模拟青铜器从铜绿到氧化的色彩过渡。

四、Tab 元数据与常量定义

4.1 底部导航 Tab 定义

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

const TAB_LIST: TabMeta[] = [
  { icon: '🏛️', label: '首页' },
  { icon: '🗺️', label: '地图' },
  { icon: '🔍', label: '搜索' },
  { icon: '🌐', label: '百科' },
  { icon: '📥', label: '下载' },
  { icon: '🧪', label: '工坊' },
  { icon: '👤', label: '我的' }
];

7 个 Tab 单排排列,每个 Tab 的 emoji 图标与中文标签语义高度契合。与常见的 5 Tab 布局不同,7 Tab 设计将"搜索"与"百科"分离——搜索 Tab 专注 POI 相关性分数展示,百科 Tab 专注 ArkWeb 浏览与下载溯源,实现了"查询入口"与"资源获取"的职责解耦。下载 Tab 独立于百科 Tab,将下载进度追踪与完成记录溯源提升为一级功能页签。

4.2 核心常量矩阵

// 城市中心点(西安钟楼一带)
const CITY_CENTER: mapCommon.LatLng = { latitude: 34.3416, longitude: 108.9398 };

// 6 处长安古迹标注点
const MARKER_SPOTS: SpotItem[] = [
  { name: '大雁塔', lat: 34.2185, lng: 108.9640, tag: '唐 · 楼阁式砖塔' },
  { name: '小雁塔', lat: 34.2411, lng: 108.9434, tag: '唐 · 密檐式砖塔' },
  { name: '西安城墙', lat: 34.2569, lng: 108.9424, tag: '明 · 城垣遗存' },
  { name: '碑林博物馆', lat: 34.2555, lng: 108.9313, tag: '唐 · 石刻碑林' },
  { name: '大明宫遗址', lat: 34.2763, lng: 108.9420, tag: '唐 · 宫殿遗址' },
  { name: '兴教寺塔', lat: 34.1166, lng: 108.9570, tag: '唐 · 玄奘墓塔' }
];

// 百科快捷站点(文博类真实站点)
const QUICK_SITES: string[] = [
  'https://www.ncha.gov.cn',   // 国家文物局
  'https://www.dpm.org.cn',    // 故宫博物院
  'https://www.sxhm.com',      // 陕西历史博物馆
  'https://www.kaogu.cn'       // 中国考古网
];

// WebP 编码参数
const WEBP_QUALITY: number = 90;
const CANVAS_SIZE: number = 96;
const DELAY_PRESETS: number[] = [120, 200, 500];
const LOOP_PRESETS: number[] = [0, 1, 3, 5];

常量体系围绕"长安古迹"这一地理主题展开。CITY_CENTER 定位西安钟楼坐标,作为 Map Kit 地图初始化的 targetsite.searchByTextlocation 基准点。6 处古迹标注点涵盖唐、明两个朝代,覆盖楼阁式塔、密檐式塔、城垣、石刻、宫殿遗址与墓塔六种文物类型。4 个快捷站点均为真实文博机构官网,为百科 Tab 的 Web 组件提供可信导航入口。WebP 编码参数中,帧延迟三档预设 120/200/500 毫秒均落在 [100, 65535] 的合法区间内,循环次数预设 0 表示不限。

4.3 五种老照片纹理

const TEXTURE_LIST: TextureItem[] = [
  { key: 'mottle', label: '斑驳', note: '对角斜纹' },
  { key: 'carve', label: '雕花', note: '棋盘格' },
  { key: 'rammed', label: '夯土', note: '横带层理' },
  { key: 'stele', label: '碑刻', note: '竖带刻痕' },
  { key: 'ring', label: '年轮', note: '同心环' }
];

五种纹理取材自文物表面常见质感:斑驳对应对角斜纹模拟铜锈剥落,雕花对应棋盘格模拟镂刻纹饰,夯土对应横带层理模拟版筑痕迹,碑刻对应竖带刻痕模拟石碑风化,年轮对应同心环模拟古木截面。这五种纹理在像素画生成阶段通过 pickTextureColor 函数区分着色,最终编码为 WebP 落盘供元数据读写使用。

五、工具函数

// 相关性分数 → 三档等级标签与颜色
function reliabilityScore(v: number): ScoreInfo {
  if (v >= 0.8) {
    return { label: '高相关', color: COLORS.gold };
  }
  if (v >= 0.5) {
    return { label: '中相关', color: COLORS.bronze };
  }
  return { label: '低相关', color: COLORS.text3 };
}

// 保护等级 → 徽章颜色
function levelColor(level: string): string {
  if (level === '全国重点文物保护单位') {
    return COLORS.red;
  }
  if (level === '省级文物保护单位') {
    return COLORS.bronze;
  }
  return COLORS.text3;
}

// '#RRGGBB' → 0xFFBBGGRR(RGBA_8888 缓冲按字节序排列)
function hexToRgba(hex: string): number {
  const r: number = parseInt(hex.slice(1, 3), 16);
  const g: number = parseInt(hex.slice(3, 5), 16);
  const b: number = parseInt(hex.slice(5, 7), 16);
  return 0xFF000000 | (b << 16) | (g << 8) | r;
}

// 元数据字段格式化:-1(undefined 的占位)→ '未提供'
function fmtField(v: number, unit: string): string {
  return v < 0 ? '未提供' : `${v}${unit}`;
}

// 字节数 → 可读体积文本
function formatSize(bytes: number): string {
  if (bytes >= 1048576) {
    return `${(bytes / 1048576).toFixed(1)}MB`;
  }
  if (bytes >= 1024) {
    return `${(bytes / 1024).toFixed(1)}KB`;
  }
  return `${bytes}B`;
}

// URL → 域名
function hostOf(url: string): string {
  const rest: string = url.replace('https://', '').replace('http://', '');
  return rest.split('/')[0];
}

// 当前时间(HH:mm:ss)
function nowTime(): string {
  const d: Date = new Date();
  return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}`;
}

七个工具函数各司其职,覆盖了 UI 渲染、数据格式化与色彩转换三大场景。reliabilityScore 将 [0, 1] 区间的相关性分数映射为高/中/低三档标签与颜色,阈值 0.8 与 0.5 的选择保证了搜索结果质量的视觉分级清晰可辨。levelColor 将保护等级三档映射为朱砂红/青铜绿/陶土灰,与色彩体系中徽章语义一致。hexToRgba 是像素画生成的核心转换函数,将 CSS 风格的 #RRGGBB 转换为 RGBA_8888 缓冲所需的 0xFFBBGGRR 整数——注意字节序为 B-G-R-A 而非 R-G-B-A,这是因为 Uint32Array 在小端序系统上以高位在前存储。fmtField 将 -1 占位符统一渲染为"未提供",处理 WebP 元数据中 undefined 字段的 UI 兜底。formatSize 实现字节数到 KB/MB 的智能格式化,hostOf 从完整 URL 提取域名用于快捷站点标签,nowTime 生成 HH:mm:ss 格式时间戳供事件日志与下载记录共用。

六、数据模型层

6.1 古迹名录实体 HeritageItem

@Observed export class HeritageItem {
  name: string;
  dynasty: string;
  level: string;
  region: string;
  score: number;

  constructor(name: string, dynasty: string, level: string, region: string, score: number) {
    this.name = name;
    this.dynasty = dynasty;
    this.level = level;
    this.region = region;
    this.score = score;
  }
}

@Observed 装饰器使 HeritageItem 的字段级变化可被 @State 感知,当通过 confirmEdit 修改 target.name 等字段时,UI 自动刷新对应卡片。Mock 数据包含 8 条长安古迹,覆盖全国重点、省级、市县级三档保护等级,文保分从 61 到 98 分布。

6.2 POI 搜索结果实体 SearchRecord

@Observed export class SearchRecord {
  name: string;
  address: string;
  distance: number;
  reliability: number;
  constructor(name: string, address: string, distance: number, reliability: number) {
    this.name = name;
    this.address = address;
    this.distance = distance;
    this.reliability = reliability;
  }
}

SearchRecord 对应 site.searchByText 返回的 site.Site 实体映射,reliability 字段是 Map Kit 的相关性分数,在 UI 层通过 reliabilityScore 函数转化为三档标签与进度条。当搜索失败时保留 Mock 数据并提示,保证演示链路完整。

6.3 长按事件日志实体 EventLog

@Observed export class EventLog {
  type: string;
  name: string;
  lat: number;
  lng: number;
  time: string;
  constructor(type: string, name: string, lat: number, lng: number) {
    this.type = type;
    this.name = name;
    this.lat = lat;
    this.lng = lng;
    this.time = nowTime();
  }
}

EventLogtype 字段取值为 MarkerPOI系统,在事件日志行中根据类型着不同颜色徽标。构造函数自动调用 nowTime() 填充时间戳,实现"事件发生即记录"。

6.4 下载记录实体 DownloadRecord

@Observed export class DownloadRecord {
  fileName: string;
  fileSize: number;
  finishTime: string;
  originalUrl: string;
  referrerUrl: string;
  constructor(fileName: string, fileSize: number, finishTime: string,
              originalUrl: string, referrerUrl: string) {
    this.fileName = fileName;
    this.fileSize = fileSize;
    this.finishTime = finishTime;
    this.originalUrl = originalUrl;
    this.referrerUrl = referrerUrl;
  }
}

DownloadRecordoriginalUrlreferrerUrl 双字段是 6.1.1 新特性核心——getOriginalUrl() 返回文件的真实下载地址,getReferrerUrl() 返回触发下载的网页地址。这两个字段构成了文物资源版权溯源的双链凭证,Mock 数据中每条记录的原始 URL 与引用页 URL 均来自不同页面路径,真实模拟了"从文物局首页跳转至资源页下载"的溯源场景。

6.5 WebP 元数据快照与操作日志

@Observed export class WebpMetaSnapshot {
  canvasWidth: number;        // 画布宽(px),-1=未提供
  canvasHeight: number;       // 画布高(px),-1=未提供
  delayTime: number;          // 钳制后帧延迟(ms),-1=未提供
  unclampedDelayTime: number; // 未钳制帧延迟(ms),-1=未提供
  loopCount: number;          // 循环次数,-1=未提供(0=不限)
  constructor(w: number, h: number, d: number, u: number, l: number) {
    this.canvasWidth = w;
    this.canvasHeight = h;
    this.delayTime = d;
    this.unclampedDelayTime = u;
    this.loopCount = l;
  }
}

@Observed export class MetaOpLog {
  op: string;
  detail: string;
  time: string;
  constructor(op: string, detail: string) {
    this.op = op;
    this.detail = detail;
    this.time = nowTime();
  }
}

WebpMetaSnapshot 封装了 WebP 元数据五字段:画布宽高、钳制帧延迟、未钳制帧延迟与循环次数。undefined 字段统一用 -1 占位,渲染时通过 fmtField 转为"未提供"。metaSnapshot 存储读取结果,verifySnapshot 存储写入后回读结果,两者通过 metaCard Builder 共用渲染逻辑,后者传入 highlight=true 使用 dark 背景色高亮区分。MetaOpLog 记录生成/读取/写入/回读四类操作的时间戳与详情,构成完整的元数据操作审计链。

七、组件主体与生命周期

@Entry
@Component
struct Page1280 {
  // --- Tab 与弹窗状态 ---
  @State currentTab: number = 0;
  @State addModal: boolean = false;
  @State editModal: boolean = false;
  @State delModal: boolean = false;
  @State editIdx: number = -1;
  @State delIdx: number = -1;
  @State breath: boolean = false;
  timer: number = -1;

  // --- 古迹业务数据与面板输入 ---
  @State heritageList: HeritageItem[] = HERITAGE_LIST.slice();
  @State inputName: string = '';
  @State inputDynasty: string = '';
  @State inputLevel: string = '';
  @State inputRegion: string = '';

  // --- Map Kit 状态 ---
  private mapOptions: mapCommon.MapOptions = { position: { target: CITY_CENTER, zoom: 12 } };
  private mapCallback?: AsyncCallback<map.MapComponentController>;
  private mapController?: map.MapComponentController;
  private mapEventManager?: map.MapEventManager;
  @State eventLogs: EventLog[] = [];
  @State markerListenOn: boolean = true;
  @State poiListenOn: boolean = true;

  // --- POI 搜索状态 ---
  @State queryInput: string = '古迹';
  @State searchRecords: SearchRecord[] = SEARCH_LIST.slice();
  @State searchState: string = '待搜索';

  // --- ArkWeb 状态 ---
  private webController: webview.WebviewController = new webview.WebviewController();
  private downloadDelegate: webview.WebDownloadDelegate = new webview.WebDownloadDelegate();
  @State urlInput: string = QUICK_SITES[0];
  @State webUrl: string = QUICK_SITES[0];
  @State dlName: string = '';
  @State dlPercent: number = 0;
  @State dlState: string = '空闲';
  @State downloadRecords: DownloadRecord[] = DOWNLOAD_LIST.slice();

  // --- WebP 元数据状态 ---
  @State pixelMap: image.PixelMap | undefined = undefined;
  @State webpPath: string = '';
  @State genState: string = '待生成';
  @State textureSel: string = 'mottle';
  @State metaSnapshot: WebpMetaSnapshot | undefined = undefined;
  @State writeDelay: number = 120;
  @State writeLoop: number = 3;
  @State verifySnapshot: WebpMetaSnapshot | undefined = undefined;
  @State opLogs: MetaOpLog[] = [];

  aboutToAppear() {
    this.timer = setInterval(() => {
      this.breath = !this.breath;
    }, 1000);
    this.setupMapCallback();
    this.setupDownloadDelegate();
  }

  aboutToDisappear() {
    clearInterval(this.timer);
  }
  // ... 方法群
}

组件状态分为六大组:Tab 与弹窗状态控制页面导航与模态交互;古迹业务数据驱动首页双列卡片与弹窗表单;Map Kit 状态管理地图初始化、双长按监听与事件日志;POI 搜索状态驱动搜索 Tab 的查询输入与结果列表;ArkWeb 状态管理浏览地址、下载委托与溯源记录;WebP 元数据状态覆盖样图生成、读写快照与操作日志。

breath 状态配合 setInterval 每秒翻转一次布尔值,驱动统计格数字颜色在鎏金与绢帛黄之间交替、柱状图柱高微波动、我的 Tab 数字在鎏金与宣纸白间切换,实现全局"呼吸感"动画效果。aboutToAppear 中三步初始化——启动呼吸定时器、装配地图回调、绑定下载委托;aboutToDisappear 清理定时器防止内存泄漏。

八、头部详解

@Builder
headerMain() {
  Row({ space: 10 }) {
    Text('🏛️').fontSize(26)
    Column({ space: 2 }) {
      Text('文物地图').fontSize(18).fontWeight(FontWeight.Bold).fontColor(COLORS.title)
      Text('文化遗产探索地图 · 长安篇').fontSize(10).fontColor(COLORS.sub)
    }
    .alignItems(HorizontalAlign.Start)
    .layoutWeight(1)
    Text(`收录 ${this.heritageList.length}`)
      .fontSize(10).fontColor(COLORS.gold)
      .padding({ left: 8, right: 8, top: 4, bottom: 4 })
      .borderRadius(10).backgroundColor(COLORS.dark)
    Text('+')
      .fontSize(16).fontColor(COLORS.tabOn)
      .width(30).height(30).textAlign(TextAlign.Center)
      .borderRadius(15).backgroundColor(COLORS.bronze)
      .onClick(() => {
        this.clearInputs();
        this.addModal = true;
      })
  }
  .width('100%')
  .padding({ left: 14, right: 14, top: 10, bottom: 10 })
}

头部采用 Row 水平布局,左侧 emoji 图标与品牌名/副标题纵向排列,右侧收录徽章与快捷收录按钮。收录徽章使用 dark 背景色搭配 gold 鎏金文字,实时反映 heritageList.length 变化。快捷收录按钮为圆形(宽高 30、圆角 15),背景取青铜绿主色,点击触发 clearInputs 清空面板输入后打开新增弹窗。整个头部无动画,保持文博应用应有的沉稳气质。

九、各 Tab 深度分析

9.1 首页 Tab:统计行 + 双列古迹卡 + 月度柱状图

首页由三段构成。顶部统计行通过 statCell Builder 生成三格——收录古迹数、地图标注数、探索里程,每格的数值颜色随 breath 在鎏金与绢帛黄间交替。中部使用 Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween }) 实现双列卡片布局,每张 heritageCard 宽度 49%,朝代标签用鎏金、保护等级标签用 levelColor 着色,底部文保分配青铜绿,编辑按钮用绢帛黄、删除按钮用朱砂红。底部 chartCard 绘制近 6 个月探访热度柱状图,6 根柱子通过 ForEach 渲染,偶数月用青铜绿、奇数月用青铜深色,柱高随 breath 微波动。

9.2 地图 Tab:双长按监听 + MapComponent + 事件日志流

地图 Tab 是 Map Kit 双长按特性的核心展示区。顶部两个 Toggle 开关分别控制 Marker 长按与 POI 长按监听的开启/关闭,开关状态通过 markerListenOn/poiListenOn 双布尔值管理。中间 MapComponentmapOptions(西安钟楼、zoom 12)初始化,mapCallback 在组件 aboutToAppear 时装配。

地图回调 setupMapCallback 的执行链路为:err 判空 → 获取 controller → 获取 eventManager → 批量添加 6 处古迹 Marker → 注册双长按监听。Marker 添加使用 for...of 循环逐个 await controller.addMarker(markerOptions),每个 Marker 的 anchorU: 0.5, anchorV: 1 确保锚点在底部中心。双长按监听中,onMarkerLongClick 回调接收 map.Marker 对象,通过 marker.getPosition() 读取经纬度并记录到 eventLogsonPoiLongClick 回调接收 mapCommon.Poi 对象,仅含 id/name/position 三字段,poi.name 可能为空故用 ?? '未命名POI' 兜底。

toggleMarkerListentogglePoiListen 两个开关方法实现监听的动态切换:关闭时调用 offMarkerLongClick()/offPoiLongClick()(不传参即清除该类型全部订阅),开启时重新注册回调。每次切换均向 eventLogs 插入一条系统日志,形成完整的操作审计。

底部事件日志流使用 List 渲染 eventLogs 数组,每行 eventLogRow 根据类型着色——Marker 用青铜绿、POI 用鎏金、系统用陶土灰,经纬度以等宽字体 monospace 显示,时间戳右对齐。空列表时显示引导文案"暂无事件:长按地图 Marker 或 POI 试一试"。

9.3 搜索 Tab:searchByText + reliability 分数列表

搜索 Tab 展示 site.searchByText 的 POI 相关性分数能力。顶部搜索框与搜索按钮构成查询入口,queryInput 默认值"古迹"。搜索状态行显示三列信息:左侧 searchByText 函数名(等宽字体)、中部 searchState 动态文案、右侧"半径 5000m · zh"参数提示。

runSearch 方法的执行链路为:设置"搜索中"状态 → 构造 SearchByTextParams(query/location/radius/language)→ await site.searchByText(params) → 读取 result.sites 数组 → 映射为 SearchRecord 列表。映射时 s.names.formatAddresss.distances.reliability 均用 ?? 兜底。无结果时保留当前推荐并提示,异常时保留 Mock 数据并显示错误码,保证演示链路在任何网络条件下均完整。

结果列表中每张 searchResultCard 包含四行:名称与相关性等级标签(通过 reliabilityScore 着色)、地址、距离与"POI 命中"标记、Progress 线性进度条配合 reliability * 100 的分数可视化。进度条用青铜绿、背景用 dark 色,分数值以鎏金显示。

9.4 百科 Tab:地址栏 + 快捷站点 + Web 组件 + 主动下载

百科 Tab 是 ArkWeb 特性的核心展示区。顶部地址栏 TextInput 与"前往"按钮构成 URL 输入入口,loadUrl 方法对无 http(s):// 前缀的输入自动补 https://urlInputwebUrl 双状态分离——前者绑定输入框实时更新,后者仅在点击"前往"后赋值,避免输入过程中 Web 组件频繁刷新。

快捷站点横滑行通过 Scroll + Row 横向排列 4 个文博站点域名标签(通过 hostOf 提取),选中态用 dark 背景配鎏金文字,点击同步更新 urlInputwebUrlWeb 组件以 webController 控制器初始化,layoutWeight(1) 占满剩余高度。

底部"触发下载"区域展示应用侧主动发起下载的能力。点击"触发"按钮调用 triggerDownload(RESOURCE_DL_URL),该方法内部调用 this.webController.startDownload(url),无需网页内点击即可启动下载任务。下载进度与完成记录通过 setupDownloadDelegate 注册的四个回调实时更新。

setupDownloadDelegate 的四回调链路为:onBeforeDownload 内通过 getHostContext().filesDir 获取沙箱路径并调用 item.start() 提供完整文件路径(否则任务停在 PENDING);onDownloadUpdated 内通过 getPercentComplete() 刷新进度条;onDownloadFailed 内更新状态文案;onDownloadFinish 内通过 getOriginalUrl()getReferrerUrl() 读取双溯源 URL 并构造 DownloadRecord 插入记录列表。最后通过 setDownloadDelegate 绑定到 controller,确保网页触发的下载也进入回调。

9.5 下载 Tab:进度任务卡 + 双 URL 溯源列表

下载 Tab 分为进行中任务卡与完成记录列表两部分。进行中任务卡顶部显示文件名与下载状态文案,右侧百分比以鎏金粗体显示;中部 Progress 线性进度条用青铜绿;底部两行提示文案说明触发方式与"delegate 四回调"。完成记录列表通过 ForEach 渲染 downloadRecords 数组,每张 downloadRecordCard 展示文件信息与双溯源 URL——原始 URL 标注 getOriginalUrl() 用鎏金标签,引用页 URL 标注 getReferrerUrl() 用青铜绿标签,URL 本身以等宽字体显示并用 maxLines(1) + Ellipsis 防溢出。

9.6 工坊 Tab:WebP 元数据四区块

工坊 Tab 是 ImageKit WebP 元数据特性的完整展示区,纵向排布四个区块。

区块一·老照片样图生成:五种纹理选择器通过 ForEach 渲染 TEXTURE_LIST,选中态用 dark 背景配鎏金文字。参数行显示画布尺寸、编码质量、像素格式与纹理类型。预览区根据 pixelMap 是否存在分别显示 Image 组件或占位框。点击"生成"按钮调用 genWebpFile,该方法执行链路为:释放旧 PixelMap → 按纹理生成 RGBA_8888 像素画(pickTextureColor 逐像素着色)→ image.createPixelMap 创建 PixelMap → ImagePacker.packToData 编码为 WebP 字节流 → 写入沙箱文件 heritage_photo.webp → 重置读写快照 → 记录操作日志。

区块二·五字段元数据读取:点击"读取"按钮调用 readMeta,该方法通过 fileIo.openSync 以 READ_WRITE 模式打开 WebP 文件,image.createImageSource(file.fd) 创建 ImageSource,readImageMetadataByType([WEBP_METADATA], 0) 类型化读取(index=0 适用于静态 WebP),五字段判空兜底后构造 WebpMetaSnapshot 存入 metaSnapshot。静态 WebP 通常仅提供画布尺寸,帧延迟与循环次数显示"未提供"。

区块三·写入控制台:帧延迟三档预设与循环次数四档预设通过 ForEach 渲染为可点选标签。点击"写入并回读校验"调用 writeMeta,该方法以字面量构造 image.WebPMetadata(canvasWidth/canvasHeight/delayTime/unclampedDelayTime/loopCount 五字段),挂载到 ImageMetadata 上后调用 source.writeImageMetadata(meta) 写回文件,写入成功后立即调用 verifyRead 回读校验。

区块四·回读校验与操作日志verifyRead 重建 ImageSource(先 release 再重开 fd)回读元数据,构造 verifySnapshot 与写入值比对 delayTime 与 loopCount,一致则日志记录"已生效",不一致则记录差异值。操作日志区域通过 ForEach 渲染 opLogs 数组,每行显示操作类型、详情与时间戳,高度 150 限制滚动区域。

9.7 我的 Tab:渐变大卡 + 足迹清单 + 说明行

我的 Tab 顶部为探访者渐变大卡,使用 linearGradient({ angle: 135, colors: [[COLORS.bronzeD, 0.0], [COLORS.dark, 1.0]] }) 从青铜深色到次级容器色的 135 度渐变。卡内包含等级名称"长安拾遗人"、青铜级探访者副标题、Lv.5 等级标识(鎏金粗体)、三格统计(收录/踏访/里程,数字随 breath 在鎏金与宣纸白间切换)、进度条与距下一等级提示。

足迹清单通过 ForEach 渲染 6 处古迹标注点,每行 footRow 展示古迹名称与朝代标签,右侧"已踏访"用青铜绿。底部说明行通过 ForEach 渲染 ABOUT_ROWS,声明三大特性的权限与版本信息:地图数据需 INTERNET 权限与签名、百科与下载支持双 URL 溯源、工坊沙箱支持 WebP 元数据读写、当前版本 HarmonyOS 6.1.1 API 24。

十、图表卡片设计

@Builder
chartCard() {
  Column({ space: 8 }) {
    Row() {
      Text('近 6 个月探访热度').fontSize(13).fontWeight(FontWeight.Bold)
        .fontColor(COLORS.title).layoutWeight(1)
      Text('次/月').fontSize(10).fontColor(COLORS.text3)
    }
    .width('100%')
    Row({ space: 10 }) {
      ForEach(MONTH_IDX, (i: number) => {
        Column({ space: 4 }) {
          Text(`${VISIT_VAL[i]}`).fontSize(8).fontColor(COLORS.sub)
          Column()
            .width('100%')
            .height(this.breath ? VISIT_VAL[i] * 0.62 + 8 : VISIT_VAL[i] * 0.62)
            .borderRadius({ topLeft: 5, topRight: 5 })
            .backgroundColor(i % 2 === 0 ? COLORS.bronze : COLORS.bronzeD)
          Text(MONTH_NAME[i]).fontSize(9).fontColor(COLORS.text3)
        }
        .layoutWeight(1)
        .alignItems(HorizontalAlign.Center)
        .justifyContent(FlexAlign.End)
      }, (i: number) => `m_${i}`)
    }
    .height(150).alignItems(VerticalAlign.Bottom).width('100%')
  }
  .width('100%').padding(12).borderRadius(12).backgroundColor(COLORS.card)
  .alignItems(HorizontalAlign.Start)
}

柱状图采用纯 ArkUI 组件绘制,无需 Canvas。6 根柱子通过 ForEach(MONTH_IDX) 渲染,每根柱子是一个 Column 容器:顶部数值文本、中间有色 Column 柱体、底部月份标签。柱体高度按 VISIT_VAL[i] * 0.62 缩放(最大值 216 对应约 134px,在 150px 容器内合理),breath 为 true 时额外加 8px 实现呼吸波动。偶数月用青铜绿、奇数月用青铜深色,形成色彩交替的视觉节奏。整个 Row 使用 VerticalAlign.Bottom 对齐,确保所有柱子底部齐平。

十一、底部 Tab 栏

@Builder
tabBar() {
  Row() {
    ForEach(TAB_LIST, (t: TabMeta, idx: number) => {
      Column({ space: 3 }) {
        Text(t.icon).fontSize(18)
        Text(t.label).fontSize(9)
          .fontColor(this.currentTab === idx ? COLORS.tabOn : COLORS.text3)
      }
      .layoutWeight(1)
      .justifyContent(FlexAlign.Center)
      .onClick(() => {
        this.currentTab = idx;
      })
    }, (t: TabMeta, idx: number) => `${t.label}_${idx}`)
  }
  .width('100%').height(58).backgroundColor(COLORS.card)
  .padding({ top: 6, bottom: 6 })
}

底部 Tab 栏使用 Row 等分 7 个 Tab 项,每项 layoutWeight(1) 确保等宽。选中态文字使用 tabOn(鎏金),非选中态使用 text3(陶土灰),仅文字变色而图标不变色,保持了视觉克制。整个 Tab 栏高度 58px、背景色取卡片色,是全页布局的底部锚点。点击切换 currentTab 后,内容区根据新值切换对应 Builder 方法。

十二、弹窗系统

12.1 通用遮罩与输入行

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

@Builder
fieldRow(label: string, value: string, placeholder: string, onInput: (v: string) => void) {
  Column({ space: 4 }) {
    Text(label).fontSize(11).fontColor(COLORS.text3)
    TextInput({ text: value, placeholder: placeholder })
      .height(38).fontSize(12)
      .backgroundColor(COLORS.dark).fontColor(COLORS.title)
      .onChange((v: string) => {
        onInput(v);
      })
  }
  .width('100%').alignItems(HorizontalAlign.Start)
}

modalOverlay 是弹窗遮罩的通用 Builder,全屏覆盖半透黑遮罩,点击空白处触发 onClose 回调关闭弹窗。fieldRow 是弹窗内输入行的通用 Builder,上方标签用陶土灰、下方 TextInputdark 背景配宣纸白文字,onChange 回调将输入值通过函数参数传出,实现表单数据的双向绑定。

12.2 新增弹窗 panelAdd

新增弹窗以 Stack 层叠遮罩与内容卡片,内容区包含标题"收录新古迹"与四个 fieldRow(古迹名称、朝代、保护等级、所在区域),底部取消与确认收录按钮。点击"确认收录"调用 confirmAdd,该方法校验名称非空后将新古迹 unshift 到名录顶部,朝代与区域空值分别兜底为"未考"和"待考订",保护等级通过 normLevel 归一化为三档标准名称。

12.3 编辑弹窗 panelEdit

编辑弹窗复用 fieldRow 结构,但 placeholder 改为"留空保持原值"并增加说明文案。openEdit 方法在打开时回填当前条目字段到输入状态,confirmEdit 方法按面板输入覆盖选中条目——空值字段保持原值不变,保护等级同样走 normLevel 归一化。编辑完成后重置 editIdx 为 -1。

12.4 删除弹窗 panelDel

删除弹窗尺寸更小(宽度 72%),内容简洁——标题"移出名录"、确认文案(通过 delName() 获取目标条目名)、"再想想"与"确认移出"两按钮。确认按钮使用朱砂红背景,与色彩体系中"删除=朱砂红"的语义一致。点击确认调用 confirmDel,该方法校验 delIdx 有效后 splice 删除选中条目并重置索引。

十三、功能模块对比表

功能模块 核心特性 关键接口 状态变量 数据模型 UI 组件
地图双长按 Marker/POI 长按分离监听 onMarkerLongClick / onPoiLongClick / offMarkerLongClick / offPoiLongClick markerListenOn / poiListenOn / eventLogs EventLog MapComponent + Toggle + List
POI 搜索 reliability 相关性分数 site.searchByText queryInput / searchRecords / searchState SearchRecord TextInput + Progress + ForEach
百科浏览 地址栏与 Web 组件联动 webview.WebviewController urlInput / webUrl Web + TextInput + Scroll
下载溯源 双 URL 追踪(原始+引用页) WebDownloadDelegate 四回调 + getOriginalUrl + getReferrerUrl dlName / dlPercent / dlState / downloadRecords DownloadRecord Progress + ForEach
主动下载 应用侧触发下载 webController.startDownload Button + Progress
WebP 生成 像素画编码落盘 image.createPixelMap + ImagePacker.packToData + fileIo.writeSync pixelMap / webpPath / genState / textureSel Image + ForEach + Button
元数据读取 类型化五字段读取 readImageMetadataByType + MetadataType.WEBP_METADATA metaSnapshot WebpMetaSnapshot metaCard + Button
元数据写入 字面量构造写回 writeImageMetadata writeDelay / writeLoop ForEach + Button
回读校验 重建 ImageSource 比对 readImageMetadataByType(二次调用) verifySnapshot / opLogs WebpMetaSnapshot + MetaOpLog metaCard(highlight) + ForEach
古迹管理 收录/编辑/删除 CRUD confirmAdd / confirmEdit / confirmDel heritageList / inputName / inputDynasty / inputLevel / inputRegion / editIdx / delIdx HeritageItem panelAdd / panelEdit / panelDel
呼吸动画 全局状态驱动色彩波动 setInterval + breath breath / timer statCell / chartCard / mineStat
色彩体系 青铜绿+鎏金主题 ColorPalette 接口 全局 backgroundColor / fontColor

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

布局方式与数据流

非遗文博页面既要体现文化内容,也要维护年代、类别、来源与处理记录。素材馆或首页负责概览,地图和搜索提供空间探索,网页与下载承接外部资料,工坊和元数据页面支持数字化加工。逐段分析应关注朝代筛选、等级配色、双列卡片、地图事件和元数据日志之间的数据关系,避免只解释组件属性而忽略文化资产的流转。

页面根结构通常由头部、内容区和底部 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、图表、弹窗和系统能力协同工作的原理。

十四、总结与展望

本文深度解析了基于 HarmonyOS ArkUI 框架构建的文物地图文化遗产探索平台。该平台以"青铜锈绿+鎏金朱砂"的色彩长卷为视觉基调,通过 7 Tab 单排布局实现了首页统计、地图探索、POI 搜索、百科浏览、下载溯源、WebP 工坊与个人中心七大功能的职责解耦。在技术架构层面,三大 HarmonyOS 6.1.1 前沿特性在同文件内有机叠加:Map Kit 的 Marker/POI 双长按监听让地图交互事件可追溯可审计,site.searchByText 的 reliability 相关性分数让搜索结果质量可量化可分级;ArkWeb 的 WebDownloadDelegate 四回调委托配合 getOriginalUrl/getReferrerUrl 双 URL 溯源,为文物数字资源的版权链路提供了从下载到存档的完整凭证;ImageKit 的 WebP 元数据读写校验链——从像素画生成到类型化读取、字面量写回到重建回读——实现了老照片数字化存档的全流程可控。

在工程实践层面,该平台展示了若干值得借鉴的架构模式。状态变量按功能域分组声明(Tab/弹窗/业务数据/Map Kit/搜索/ArkWeb/WebP 六组),跨 Tab 共享而不耦合;breath 状态配合 setInterval 实现全局呼吸动画,以最小成本驱动统计格、柱状图与渐变大卡的色彩波动;urlInput/webUrl 双状态分离避免了 Web 组件在输入过程中的频繁刷新;normLevel 归一化函数将面板简写输入映射到三档标准名称,保证了数据一致性;fmtField 的 -1 占位兜底策略统一处理了 WebP 元数据中 undefined 字段的 UI 渲染问题;verifyRead 的重建 ImageSource 模式确保了元数据写入的真实生效。

展望未来,该平台可在以下方向持续演进。其一,地图标注可引入聚合渲染与自定义 Marker 图标,在大量古迹数据场景下优化视觉密度与品牌识别度。其二,下载溯源可扩展为区块链存证,将原始 URL 与引用页 URL 哈希上链,实现文物数字资源的不可篡改版权凭证。其三,WebP 元数据操作可支持批量处理与 EXIF 元数据互通,实现老照片数字化资产的批量化标注与跨格式迁移。其四,POI 搜索可引入语义相似度排序与个性化推荐,基于用户踏访历史动态调整搜索结果权重。其五,色彩体系可引入用户自定义主题色与暗色/亮色双模式,适配不同展览场景的光照条件。在 HarmonyOS 全场景分布式架构的支撑下,该平台有望成为文博文旅数字化的标杆级应用范式。

附录: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.1 Release ✅ 已安装

界面顶部提示:“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 24 6.1.1.100 Release ✅ 已安装
API Version 23 6.1.0.28 Beta1 未安装
API Version 22 6.0.2.112 Release 未安装

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

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

在这里插入图片描述


三、小结

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

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


Logo

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

更多推荐