一、技术前言

在文博文旅数字化转型的浪潮中,文化遗产探索正从"纸质导览册"走向"沉浸式数字地图"。从大雁塔的楼阁式砖塔到小雁塔的密檐式砖塔,从西安城墙的明代城垣遗存到碑林博物馆的唐刻石经,每一处古迹都承载着朝代、保护等级、地理坐标和文保评分等多维度信息。传统文博应用面临三大工程挑战:地图标注点交互单一导致长按事件无法溯源、网页下载文件来源不可追溯导致资源真实性存疑、WebP 动画元数据读写链路断裂导致老照片修复参数丢失。

HarmonyOS ArkUI 框架为这些挑战提供了系统级解决方案。ArkUI 的声明式 UI 范式通过 @Component 封装可复用组件、@State 管理响应式状态、@Builder 拆分复杂 UI 结构,天然适合"地图-搜索-百科-工坊"多 Tab 架构。@Observed 装饰器让数据模型字段级变化被 UI 感知,实现"元数据写入即视图刷新"的流畅体验。@Entry 标注的根组件通过 build() 方法组装 Column 纵向布局,配合条件分支实现 7 个 Tab 的独立渲染路径。

本应用深度融合 HarmonyOS 6.1.1 的三大前沿特性。Map Kit 提供 MapComponent 嵌入式地图组件与 MapEventManager 事件管理体系——通过 onMarkerLongClickonPoiLongClick 双长按监听实现古迹标记与兴趣点的交互溯源,offMarkerLongClick/offPoiLongClick 不传参即清除该类型全部订阅;同时 site.searchByText 接口配合 SearchByTextParamsquery/location/radius/language 四参数实现基于相关性分数的 POI 检索。ArkWebWebviewControllerWebDownloadDelegate 四回调链路实现了下载双 URL 溯源——onBeforeDownload 中必须调用 item.start() 提供沙箱路径否则任务停在 PENDING,onDownloadFinish 内通过 getOriginalUrl()getReferrerUrl() 双字段记录文件来源链路,应用侧亦可主动调用 startDownload(url) 触发下载。ImageKitImageSource 类型化元数据读写实现了 WebP 五字段全生命周期管理——readImageMetadataByType 配合 MetadataType.WEBP_METADATAindex=0 读取 canvasWidth/canvasHeight/delayTime/unclampedDelayTime/loopCount 五字段,writeImageMetadata 以字面量构造 WebPMetadata 写回并通过重建 ImageSource 回读校验。

二、整体架构流程图

三大特性模块

内容区七Tab

Page1260 根组件

headerMain 品牌头部

内容区 7 Tab 条件切换

tabBar 底部导航栏

弹窗系统 add/edit/del

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

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

Tab2 搜索
searchByText+reliability分数列表

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

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

Tab5 工坊
像素画生成+元数据读写回读四区块

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

Map Kit
Marker/POI双长按监听

ArkWeb
下载双URL溯源

ImageKit
WebP元数据读写

panelAdd 新增古迹弹窗

panelEdit 编辑古迹弹窗

panelDel 删除确认弹窗

架构以根组件为中枢,使用 Column 容器纵向排列:顶部品牌头部、分割线、内容区和底部 Tab 栏。内容区通过 currentTab 状态变量在 7 个 @Builder 方法间条件分支切换,其中地图 Tab 和百科 Tab 全高不套 Scroll,其余 5 个 Tab 走 Scroll 滚动分支。三大特性分散在地图(Map Kit 双长按监听)、百科(ArkWeb 下载委托)、工坊(ImageKit WebP 元数据)三个 Tab 上,状态变量统一声明在组件顶层实现跨 Tab 共享。弹窗系统通过 addModal/editModal/delModal 三个布尔状态条件渲染,点击遮罩层即可关闭。

三、色彩体系设计

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(青铜绿),这是因为金色在深褐背景上对比度更高,用户视觉定位更迅速。头部收录徽章使用 dark 底色配 gold 文字模拟青铜铭文的镶嵌效果。我的 Tab 渐变大卡使用 linearGradientbronzeDdark 的 135° 渐变,模拟青铜器从光照面到阴影面的色调过渡。朱砂红 red 专用于全国重点文物保护单位徽章和删除按钮,呼应中国传统印章的朱砂色调。

四、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 图标和中文标签。首页用建筑图标代表名录概览,地图用地图图标代表地理探索,搜索用放大镜代表 POI 检索,百科用地球代表网页浏览,下载用箭头代表资源管理,工坊用烧瓶代表 WebP 元数据实验,我的用人形代表探访者档案。

4.2 地图标注点与城市中心

const CITY_CENTER: mapCommon.LatLng = { latitude: 34.3416, longitude: 108.9398 };

interface SpotItem {
  name: string;
  lat: number;
  lng: number;
  tag: string;
}

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: '唐 · 玄奘墓塔' }
];

城市中心定位于西安钟楼一带(纬度 34.3416,经度 108.9398),作为 Map Kit 定位与搜索基准。6 处标注点均为长安真实古迹,每处携带名称、经纬度和朝代标签三字段。这些数据既用于 MapComponent 初始化时的 addMarker 批量标注,也在我的 Tab 足迹清单中复用展示。

4.3 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];

interface TextureItem {
  key: string;
  label: string;
  note: string;
}

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

WebP 编码质量设为 90(0-100 范围),画布边长 96px 以保证样图轻量。帧延迟三档预设 120/200/500ms 均在合法区间 [100, 65535] 内。循环次数四档预设 0(不限)/1/3/5 次。五种纹理各有独立的像素填充算法,斑驳用对角坐标取色模拟锈蚀斑驳,雕花用 8 像素分块模拟棋盘格纹,夯土用行号分块模拟夯土层理,碑刻用列号分块模拟竖向刻痕,年轮用同心圆距离模拟树木年轮。

4.4 月度探访数据与保护等级

const MONTH_IDX: number[] = [0, 1, 2, 3, 4, 5];
const MONTH_NAME: string[] = ['03月', '04月', '05月', '06月', '07月', '08月'];
const VISIT_VAL: number[] = [126, 98, 154, 183, 216, 172];

const LEVEL_OPTIONS: string[] = ['全国重点文物保护单位', '省级文物保护单位', '市县级文物保护单位'];

近 6 个月探访量数据用于首页柱状图渲染,7 月达到峰值 216 次。保护等级三档标准取值用于弹窗面板归一化,确保用户简写输入也能映射到规范名称。

五、工具函数

5.1 相关性分数分级

interface ScoreInfo {
  label: string;
  color: string;
}

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 };
}

reliabilityScore 函数将 POI 搜索返回的相关性分数(0-1 浮点值)映射为三档等级标签与颜色。分数大于等于 0.8 标记为"高相关"并用鎏金色突出显示,分数在 0.5 到 0.8 之间标记为"中相关"并用青铜绿表示,低于 0.5 则标记为"低相关"并用陶土灰弱化。这种分级策略让用户一眼区分精确命中与边缘匹配。

5.2 保护等级颜色映射

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

levelColor 函数将三档保护等级映射为徽章颜色。全国重点文物保护单位用朱砂红标识其最高文保级别,省级用青铜绿,市县级用陶土灰。颜色语义从重到轻递减,与文保等级的权威性一致。

5.3 颜色值转换与格式化

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;
}

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`;
}

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

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')}`;
}

hexToRgba#RRGGBB 格式色值转换为 0xFFBBGGRR 格式的 32 位整数,因为 RGBA_8888 缓冲区按字节序 R、G、B、A 排列,Alpha 通道固定 255。高八位 0xFF 即不透明 Alpha,低 24 位按 B、G、R 顺序移位组装。fmtField-1(undefined 的占位值)格式化为"未提供"文本,用于 WebP 元数据字段的可选值兜底。formatSize 按字节阈值切换 KB/MB 单位并保留一位小数。hostOf 从完整 URL 中提取域名用于快捷站点标签。nowTime 返回 HH:mm:ss 格式时间字符串,事件日志、下载记录和操作日志三处共用。

六、数据模型层

6.1 古迹名录实体

@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;
  }
}

HeritageItem 是古迹名录的核心实体,携带名称、朝代、保护等级、区域和文保评分五字段。@Observed 装饰器使其字段级变化被 UI 感知——当编辑弹窗修改某条古迹的朝代或等级时,首页双列卡片中的对应数据会自动刷新。Mock 数据包含 8 处长安古迹,从大雁塔(唐·国保·98 分)到高家大院(明·市县级·61 分),覆盖三档保护等级。

6.2 POI 搜索结果实体

@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 封装 POI 搜索结果,reliability 字段为 0-1 区间的相关性分数,直接驱动搜索结果卡片的等级标签与进度条渲染。当 site.searchByText 返回真实结果时,通过 sites.mapSite 对象映射为 SearchRecord;搜索失败时保留 Mock 数据保证演示链路完整。

6.3 长按事件日志实体

@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();
  }
}

EventLog 记录地图双长按事件的完整信息。type 字段区分三种事件来源:Marker 表示标注点长按、POI 表示兴趣点长按、系统 表示监听开关状态变更。构造时自动调用 nowTime() 填充时间戳,通过 unshift 插入日志数组头部实现最新事件置顶。

6.4 下载记录实体与双 URL 溯源

@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;
  }
}

DownloadRecord 是 6.1.1 双 URL 溯源特性的数据载体。除常规的文件名、字节数和完成时间外,originalUrl 记录文件的原始下载地址(直链),referrerUrl 记录触发下载的引用页地址(来源页)。这两个字段在 onDownloadFinish 回调中分别通过 getOriginalUrl()getReferrerUrl() 获取,让用户能追溯每个下载文件"从哪个页面、下载了哪个 URL"的完整链路。

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

@Observed export class WebpMetaSnapshot {
  canvasWidth: number;
  canvasHeight: number;
  delayTime: number;
  unclampedDelayTime: number;
  loopCount: number;

  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 五字段元数据的快照容器。canvasWidthcanvasHeight 为画布尺寸(像素),delayTime 为钳制后的帧延迟(毫秒),unclampedDelayTime 为未钳制帧延迟,loopCount 为循环次数(0 表示不限)。所有字段允许 -1 占位表示"未提供",渲染时通过 fmtField 转为友好文本。MetaOpLog 记录生成/读取/写入/回读四类操作日志,构造时自动填充时间戳。

七、组件主体与生命周期

7.1 状态变量声明

根组件声明了覆盖全部 Tab 和弹窗的状态变量体系。Tab 与弹窗状态包括 currentTab(当前选中 Tab 索引)、addModal/editModal/delModal(三个弹窗布尔开关)、editIdx/delIdx(编辑和删除的目标索引)、breath(呼吸动画布尔翻转量)。古迹业务数据包括 heritageList(名录数组,初始为 Mock 切片)和四个面板输入字段 inputName/inputDynasty/inputLevel/inputRegion

Map Kit 状态包括 mapOptions(地图初始化配置,目标定位城市中心 zoom 12)、mapCallback(地图初始化异步回调)、mapController(地图控制器)、mapEventManager(事件管理器)、eventLogs(长按事件日志数组)和双监听开关 markerListenOn/poiListenOn

ArkWeb 状态包括 webController(网页视图控制器)、downloadDelegate(下载代理)、urlInput/webUrl(地址栏输入值与实际加载值双状态分离)、dlName/dlPercent/dlState(下载任务名/进度百分比/状态文案)和 downloadRecords(完成记录数组)。

WebP 元数据状态包括 pixelMap(像素图对象)、webpPath(沙箱文件路径)、genState(生成状态文案)、textureSel(选中纹理键名)、metaSnapshot(读取快照)、writeDelay/writeLoop(写入预设值)、verifySnapshot(回读快照)和 opLogs(操作日志数组)。

7.2 生命周期方法

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

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

aboutToAppear 在组件即将出现时执行三项初始化:启动 1 秒间隔的定时器翻转 breath 状态驱动呼吸动画效果(统计格数值变色、柱状图柱高微波动、我的 Tab 数值变色);装配地图初始化回调函数;绑定下载代理到网页控制器。aboutToDisappear 清理定时器防止内存泄漏。

7.3 地图初始化与双长按监听

setupMapCallback 方法装配地图初始化的异步回调链路。回调首先对 err 判空,错误时打印日志并返回。成功时依次获取 MapComponentControllerMapEventManager。随后遍历 6 处古迹标注点,为每处构造 MarkerOptions(携带 position、clickable、visible、rotation、zIndex、alpha、anchorU、anchorV、draggable、flat 十字段全显式配置),逐个 await controller.addMarker 并 try-catch 捕获异常。

标注添加完成后注册两个 6.1.1 新特性监听。onMarkerLongClick 回调接收 map.Marker 对象,通过 marker.getPosition() 获取经纬度,通过 marker.getId() 获取标记 ID,构造 EventLog 插入日志头部。onPoiLongClick 回调接收 mapCommon.Poi 对象(仅 id/name/position 三字段),通过 poi.name ?? '未命名POI'poi.position 构造日志。poi.name 使用空值合并运算符确保名称为空时显示默认文本。

7.4 监听开关与搜索

toggleMarkerListentogglePoiListen 方法实现双长按监听的开关切换。开启时调用 onMarkerLongClick/onPoiLongClick 重新注册监听,关闭时调用 offMarkerLongClick/offPoiLongClick 不传参清除该类型全部订阅。每次切换都会向事件日志插入一条"系统"类型记录,让用户在日志流中看到监听状态变化。

runSearch 方法执行关键字 POI 搜索。构造 SearchByTextParams(query 搜索词、location 定位基准、radius 5000 米搜索半径、language 中文语言),调用 site.searchByText 异步获取 SearchByTextResult。成功时通过 sites.mapSite 数组映射为 SearchRecord 数组并用空值合并运算符兜底 name/formatAddress/distance/reliability 四字段。无结果时提示"保留当前推荐"。异常时捕获 BusinessError,提示错误码并保留 Mock 数据保证演示链路完整。

7.5 下载代理绑定与双 URL 溯源

setupDownloadDelegate 方法注册下载代理的四个回调。onBeforeDownload 中通过 getUIContext().getHostContext() 获取宿主上下文,取 filesDir 沙箱目录,调用 item.start() 提供完整沙箱路径——这一步必须执行,否则下载任务会卡在 PENDING 状态无法启动。onDownloadUpdated 回调刷新进度百分比。onDownloadFailed 回调设置失败状态文案。onDownloadFinish 回调是 6.1.1 双 URL 溯源的核心:通过 item.getOriginalUrl() 获取文件原始下载地址,通过 item.getReferrerUrl() 获取引用页地址,构造 DownloadRecord 插入记录数组头部。最后通过 setDownloadDelegate 将代理绑定到 webController,此后网页内触发的下载才会进入上述回调链路。

triggerDownload 方法允许应用侧主动发起下载,调用 webController.startDownload(url) 传入目标 URL,无需网页内点击。loadUrl 方法处理地址栏跳转,自动补全 https:// 前缀,并将 urlInput(输入框值)和 webUrl(实际加载值)双状态分离,避免输入过程中网页频繁重载。

7.6 WebP 生成与元数据读写全链路

genWebpFile 方法实现像素画到 WebP 文件的完整生成链路。首先释放旧的 PixelMap 防止内存增长。然后按当前选中纹理生成 96x96 的 RGBA_8888 像素画——创建 ArrayBuffer(总像素数乘 4 字节),用 Uint32Array 视图写入,每个像素通过 pickTextureColor 取色。接着用 image.createPixelMap 从缓冲区创建像素图,用 ImagePacker 以 quality 90 编码为 WebP 字节流。最后用 fileIo.openSync 以 READ_WRITE|CREATE|TRUNC 模式打开沙箱文件,写入字节流后关闭,重置读取和回读快照。

pickTextureColor 方法根据纹理键名返回五色调色板中的索引色。斑驳纹理用 (row + col) % palette.length 实现对角斜纹;雕花纹理用 8 像素分块 (Math.floor(row/8) + Math.floor(col/8)) % palette.length 实现棋盘格;夯土纹理用行号分块 Math.floor(row/8) % palette.length 实现横带层理;碑刻纹理用列号分块 Math.floor(col/8) % palette.length 实现竖带刻痕;年轮纹理用同心圆距离 Math.floor(Math.sqrt(dx*dx + dy*dy)) % palette.length 实现同心环。

readMeta 方法实现类型化元数据读取。以 READ_WRITE 模式打开 WebP 文件,用 image.createImageSource(file.fd) 创建 ImageSource,构造 MetadataType[] 数组传入 WEBP_METADATA,调用 readImageMetadataByType(types, 0) 读取(index=0 为多帧图的帧索引,静态 WebP 恒取 0)。从返回的 ImageMetadata 中取 webPMetadata,五字段均用 ?? -1 空值合并兜底,构造 WebpMetaSnapshot

writeMeta 方法实现元数据写回。以字面量构造 WebPMetadata 对象(canvasWidth/canvasHeight 设为画布尺寸,delayTime 和 unclampedDelayTime 设为用户选中的帧延迟,loopCount 设为用户选中的循环次数),挂载到 ImageMetadata 上调用 writeImageMetadata 写回。写入成功后立即调用 verifyRead 回读校验。

verifyRead 方法重建 ImageSource(先 release 再重开 fd)回读元数据,将回读快照与写入值逐字段比对。delayTime 和 loopCount 一致则记录"已生效",否则记录差异详情。这一步验证了元数据写回是否真正落盘。

7.7 古迹 CRUD 操作

confirmAdd 方法验证古迹名称非空后,通过 unshift 将新 HeritageItem 插入名录顶部。朝代为空时填"未考",区域为空时填"待考订",保护等级通过 normLevel 归一化。confirmEdit 方法按 editIdx 定位目标条目,非空字段覆盖原值,空字段保持原值不变。confirmDel 方法通过 splice 删除目标索引条目。normLevel 方法将用户简写输入(如"全国"或"省级")映射到三档标准名称,默认返回市县级。

八、头部详解

@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 水平布局,从左到右依次排列品牌图标、品牌信息列、收录徽章和快捷收录按钮。品牌图标用 26px 字号的建筑 Emoji。品牌信息列包含主标题"文明寻迹"(18px 粗体宣纸暖白)和副标题"文化遗产探索地图 · 长安篇"(10px 绢帛黄),左对齐并占据剩余宽度。收录徽章用鎏金色文字显示当前名录数量,dark 底色圆角胶囊模拟青铜铭牌。快捷收录按钮为 30x30 圆形青铜绿底鎏金"+"号,点击时清空面板输入并打开新增弹窗。

九、各 Tab 分析

9.1 首页 Tab

首页由统计行、双列古迹卡列表和月度柱状图三部分组成。统计行通过三个 statCell 展示收录古迹数、地图标注数和探索里程。statCell 的数值颜色受 breath 状态联动,每秒在鎏金和绢帛黄之间切换,赋予数据"呼吸"的生命感。

双列古迹卡使用 Flex 弹性布局 FlexWrap.Wrap 换行,SpaceBetween 对齐实现两列等宽排布。每张 heritageCard 内部从上到下排列朝代标签行(鎏金朝代 + 保护等级徽章)、古迹名称(粗体宣纸白)、区域信息(陶土灰)和底部操作行(文保分 + 编/删按钮)。保护等级徽章颜色由 levelColor 函数动态决定,国保朱砂红、省保青铜绿、市县保陶土灰。编辑和删除按钮为 22x22 圆形深色底,分别用绢帛黄和朱砂红文字。

9.2 地图 Tab

地图 Tab 是全高布局(不套 Scroll),从上到下依次排列双长按 Toggle 开关行、地图信息提示文本、MapComponent 地图组件和长按事件日志流面板。

双长按 Toggle 行包含两个 Toggle 开关,分别控制 Marker 长按监听和 POI 长按监听的开关状态。开关的 isOn 初始值绑定 markerListenOnpoiListenOn 状态变量,onChange 时调用对应的 toggle 方法。

MapComponent 接收 mapOptions(地图配置,定位城市中心 zoom 12)和 mapCallback(初始化回调),占据 layoutWeight(1) 的剩余高度,圆角 12 裁剪。地图初始化完成后自动添加 6 处古迹 Marker 并注册双长按监听。

长按事件日志流面板在空列表时显示引导文本"暂无事件:长按地图 Marker 或 POI 试一试…",有日志时用 List 渲染每条 eventLogRow。每行包含类型徽标(Marker 青铜绿/POI 鎏金/系统 陶土灰)、名称、经纬度(等宽字体)和时间戳。List 高度固定 116px,超出部分滚动。

9.3 搜索 Tab

搜索 Tab 由搜索框行、状态信息行和搜索结果列表三部分组成。搜索框行为 TextInput + Button 组合,输入框绑定 queryInput 状态(初始值"古迹"),搜索按钮触发 runSearch。状态信息行展示 searchByText 方法名(等宽字体)、搜索状态文案和搜索半径/语言参数。

搜索结果列表通过 ForEach 渲染 searchResultCard。每张卡片包含名称行(古迹名 + 相关性等级标签,颜色由 reliabilityScore 决定)、地址行、距离行(直线距离 + "POI 命中"标识)和相关性分数行(Progress 进度条 + 分数文本)。进度条颜色为青铜绿,背景为深色,分数文本用鎏金色突出。

9.4 百科 Tab

百科 Tab 是全高布局(不套 Scroll),从上到下排列地址栏、快捷站点横滑栏、Web 组件和主动下载触发区。

地址栏为 TextInput + Button 组合,输入框绑定 urlInput,前往按钮触发 loadUrl(自动补全 https 前缀,分离输入值与加载值)。快捷站点栏用水平 Scroll 渲染四个文博真实站点(国家文物局、故宫博物院、陕西历史博物馆、中国考古网),通过 hostOf 提取域名作为标签,当前选中站点用鎏金色和深色底标识。Web 组件接收 webUrlwebController,占据剩余高度,圆角 12 裁剪。主动下载触发区展示 startDownload 方法说明和触发按钮,点击调用 triggerDownload 传入资源下载 URL。

9.5 下载 Tab

下载 Tab 由进行中任务卡和完成溯源列表两部分组成。进行中任务卡展示当前下载文件名(空时显示"暂无进行中任务")、状态文案和进度百分比,下方 Progress 进度条实时刷新,底部提示触发方式和代理回调数。

完成溯源列表通过 ForEach 渲染 downloadRecordCard。每张卡片包含文件信息行(文件图标 + 文件名 + 大小/完成时间)、原始 URL 区块(鎏金标签 + 等宽字体 URL)和引用页 URL 区块(青铜绿标签 + 等宽字体 URL),完整呈现双 URL 溯源信息。

9.6 工坊 Tab

工坊 Tab 纵向排布四个区块。区块一是老照片样图生成,包含纹理选择器(五种纹理标签切换)、参数信息行(画布尺寸/编码质量/像素格式/纹理类型)、预览区(已生成时显示 Image 像素图,未生成时显示深色占位框)和生成按钮。区块二是五字段元数据读取,点击读取按钮后展示 metaCard 元数据快照卡或提示静态 WebP 通常仅提供画布尺寸。区块三是写入控制台,包含帧延迟三档预设选择器、循环次数四档预设选择器和写入按钮。区块四是回读校验与操作日志,展示回读快照卡和 MetaOpLog 操作日志列表。

metaCard 是元数据快照卡的可复用 Builder,接收标题、快照对象和高亮标志三个参数。内部五行分别展示 canvasWidth、canvasHeight、delayTime、unclampedDelayTime 和 loopCount 五字段的标签和值,值通过 fmtField 格式化(-1 显示"未提供")。高亮标志控制背景色——回读校验卡用 dark 底色区分于读取卡。

9.7 我的 Tab

我的 Tab 由渐变大卡、足迹清单和权限说明行三部分组成。渐变大卡使用 linearGradientbronzeDdark 的 135° 渐变背景,内部排列探访者信息行(建筑图标 + 青铜级探访者 + 等级 Lv.5)、三个 mineStat 统计格(收录/踏访/里程,数值受 breath 联动变色)和等级进度条(鎏金色,64% 进度,提示距离白银级还差 2 处踏访)。

足迹清单通过 ForEach 渲染 footRow,每行展示已踏访的古迹名称和朝代标签。权限说明行通过 ForEach 渲染 ABOUT_ROWS,声明三大特性(Map Kit 需 INTERNET 权限与签名、ArkWeb 双 URL 溯源、ImageKit 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)
}

月度探访量柱状图采用纯 Column + ForEach 方案实现,无需 Canvas 绘制。外层 Column 包含标题行和数据行。数据行为 Row 底部对齐(VerticalAlign.Bottom),高度 150px,内含 6 个等宽 Column 柱体。每个柱体由数值标签、柱条和月份标签三部分组成。柱条为空 Column,高度按 VISIT_VAL[i] * 0.62 比例计算,圆角顶部裁剪。breath 状态联动时柱高增加 8px 微波动,模拟数据实时复盘的动态效果。偶数月用 bronze 青铜绿,奇数月用 bronzeD 青铜深色,交替配色增强视觉节奏感。

十一、底部 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 项,总高 58px,深木色背景。每个 Tab 项为 Column(图标 18px + 标签 9px),居中对齐,layoutWeight(1) 等分宽度。选中态标签用鎏金色(tabOn),非选中态用陶土灰。点击时更新 currentTab 触发内容区条件分支切换。7 Tab 单排设计让所有功能入口一目了然,无需二级菜单展开。

十二、弹窗系统

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,全屏半透黑背景,点击空白处触发关闭回调。fieldRow 是通用输入行 Builder,包含标签文本和 TextInput,通过闭包回调将输入值传递给调用方。这两个 Builder 被三个弹窗复用,实现统一的遮罩交互和输入样式。

12.2 新增弹窗

新增弹窗使用 Stack 层叠遮罩层和面板 Column,面板宽 86% 居中显示。面板内排列标题、四个 fieldRow(古迹名称/朝代/保护等级/所在区域)和操作按钮行(取消 + 确认收录)。取消按钮用深色底绢帛黄文字,确认按钮用青铜绿底,点击确认时调用 confirmAdd 并关闭弹窗。

12.3 编辑弹窗

编辑弹窗结构与新增弹窗一致,但多了一条"留空的字段将保持原值不变"提示文本,且各输入框的 placeholder 改为"留空保持原值"。打开时通过 openEdit 方法回填目标条目字段值。确认时调用 confirmEdit 覆盖非空字段,空字段保持原值。

12.4 删除确认弹窗

删除确认弹窗面板较窄(72%),标题"移出名录",内容文本展示待删除古迹名称(通过 delName 方法获取)。操作按钮行为"再想想"(深色底)和"确认移出"(朱砂红底),确认时调用 confirmDel 通过 splice 删除目标索引条目。红色按钮强化删除操作的不可逆语义。

十三、功能模块对比表

特性模块所属 Tab核心 API关键字段/参数数据实体6.1.1 新增能力
Map Kit 地图标注地图 TabMapComponent + addMarkerMarkerOptions 十字段全显式SpotItem 6 处古迹Marker 长按监听 onMarkerLongClick
Map Kit POI 搜索搜索 Tabsite.searchByTextSearchByTextParams 四参数SearchRecord 含 reliabilityPOI 长按监听 onPoiLongClick
ArkWeb 网页浏览百科 TabWeb + WebviewControllerurlInput/webUrl 双状态分离主动下载 startDownload(url)
ArkWeb 下载溯源下载 TabWebDownloadDelegate 四回调filesDir 沙箱路径DownloadRecord 双 URLgetOriginalUrl + getReferrerUrl
ImageKit 像素画生成工坊 TabcreatePixelMap + ImagePackerRGBA_8888 四字节/像素PixelMap + 纹理算法五种纹理像素填充
ImageKit 元数据读取工坊 TabreadImageMetadataByTypeWEBP_METADATA + index=0WebpMetaSnapshot 五字段类型化读取 API
ImageKit 元数据写回工坊 TabwriteImageMetadata字面量构造 WebPMetadataverifySnapshot 回读写后重建 ImageSource 校验
古迹名录 CRUD首页 Tab + 弹窗unshift/覆盖/splicenormLevel 等级归一化HeritageItem 五字段@Observed 字段级响应

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

布局方式与数据流

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

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

十四、总结与展望

本文深度解析了文明寻迹文化遗产探索地图的组件化架构设计。应用以青铜绿与鎏金为核心色彩语言,通过 7 个布局完全独立的 Tab 承载文博文旅的完整功能链路。首页双列古迹卡配合月度柱状图呈现名录概览与探访热度,地图 Tab 嵌入 MapComponent 实现 Marker 群标注与双长按事件溯源,搜索 Tab 通过 site.searchByText 获取 POI 相关性分数列表,百科 Tab 集成 Web 组件与下载代理实现网页浏览与主动下载,下载 Tab 展示双 URL 溯源记录,工坊 Tab 纵向四区块串联 WebP 元数据的生成-读取-写入-回读全生命周期,我的 Tab 用渐变大卡呈现探访者档案与特性声明。

三大特性模块各具技术深度。Map Kit 的双长按监听通过 MapEventManageron/off 对称设计实现灵活订阅管理,off 不传参清除全部订阅的策略避免了逐个取消的繁琐。ArkWeb 的下载代理四回调链路从 onBeforeDownload 的沙箱路径提供到 onDownloadFinish 的双 URL 记录,完整覆盖下载生命周期的每个节点,应用侧 startDownload 的主动触发能力让资源获取不再依赖网页内点击。ImageKit 的类型化元数据读写以 readImageMetadataByTypewriteImageMetadata 为核心,五字段全可选设计配合 -1 占位兜底确保了静态 WebP 与动态 WebP 的统一处理,写后重建 ImageSource 回读校验的闭环验证机制保证了元数据写入的可靠性。

展望未来,文化遗产探索地图可在以下方向持续演进。其一,地图 Tab 可接入 MapControlleranimateCamera 实现古迹间的飞行视角切换,配合 Marker 的 setIcon 自定义文保等级色标。其二,搜索 Tab 可引入 site.searchByCategory 按文保类型分类检索,结合 reliability 分数实现智能排序。其三,工坊 Tab 可扩展 PNG_METADATAEXIF_METADATA 两种元数据类型的读写,支持更多图片格式的元数据管理。其四,下载 Tab 可接入 WebDownloadManager 的暂停/恢复/取消能力,实现下载任务的精细化控制。其五,我的 Tab 可引入 rank 排行榜和 achievement 成就系统,通过游戏化设计激励探访者持续踏访古迹。这些方向的实现将进一步发挥 HarmonyOS 6.1.1 的系统能力,让文化遗产的数字化探索更加沉浸、智能、可信。

附录: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 应用的功能开发。


Logo

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

更多推荐