欢迎光临
我们一直在努力

时间轨迹第 6 篇:模板页源代码拆解,收藏、自定义、最近使用与模板应用

时间轨迹第 6 篇:模板页源代码拆解,收藏、自定义、最近使用与模板应用

这一篇讲的是 TemplatePage.ets。它不是简单的模板列表,而是整套拍照水印系统的“样式中枢”。 用户在这里选的不是一张卡片,而是后续拍照时会被复用的表达方式。

在这里插入图片描述

系列导航

  • 第 1 篇:首页、路由、登录与启动闭环
  • 第 2 篇:时间地点页源代码拆解
  • 第 3 篇:相机页源代码拆解
  • 第 4 篇:工作记录页源码导览
  • 第 5 篇:工程底座源码导览
  • 在这里插入图片描述

摘要

在 HarmonyOS 应用开发中,模板选择功能往往被简化为拍照前的一个简单点击操作。本文深入剖析的 TemplatePage 组件,正是要将这一体验彻底升级——通过 ArkTS 语言构建的现代化 UI组件 体系,将模板选择从"拍照前随便点一下"的临时操作,转变为"可复用、可收藏、可最近使用、可自定义"的完整产品级流程。

TemplatePage 的核心价值在于为用户提供了一套完整的模板管理解决方案:

  • 智能筛选与搜索:基于模板分类的高效筛选机制,配合实时搜索功能,让用户在数百个模板中快速定位所需
  • 个性化收藏系统:支持用户收藏常用模板,打造专属的个性化水印库
  • 智能最近使用:自动记录用户使用历史,提供智能化的最近使用推荐
  • 灵活自定义能力:允许用户创建、编辑和删除自定义模板,真正实现系统的可扩展性
  • 无缝数据同步:选中模板后即时同步到相机页面,确保用户体验的连贯性

通过这一系列精心设计的 UI组件 交互流程,TemplatePage 不仅提升了功能实用性,更让整个水印系统在 HarmonyOS 生态中展现出专业的产品质感。当用户能够轻松管理、快速调用心仪模板时,他们会真切感受到:这不再是一个临时功能,而是一个真正为他们服务的智能工具。

本文将从 ArkTS 实现角度,详细拆解 TemplatePage 的架构设计、数据组织、交互逻辑与性能优化,为 HarmonyOS 开发者提供一套可复用的高质量模板管理组件解决方案。

目录

  • 模板页在系统里的位置
  • 模板数据怎么组织
  • 筛选和搜索为什么重要
  • 收藏、自定义和删除
  • 应用模板到相机页
  • 页面结构和交互节奏
  • 实操检查清单
  • 参考资料
  • 一、模板页在整套链路中的位置

    #mermaid-svg-lPLEol6FYVyXJp2Q{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-lPLEol6FYVyXJp2Q .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-lPLEol6FYVyXJp2Q .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-lPLEol6FYVyXJp2Q .error-icon{fill:#552222;}#mermaid-svg-lPLEol6FYVyXJp2Q .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-lPLEol6FYVyXJp2Q .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-lPLEol6FYVyXJp2Q .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-lPLEol6FYVyXJp2Q .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-lPLEol6FYVyXJp2Q .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-lPLEol6FYVyXJp2Q .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-lPLEol6FYVyXJp2Q .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-lPLEol6FYVyXJp2Q .marker{fill:#333333;stroke:#333333;}#mermaid-svg-lPLEol6FYVyXJp2Q .marker.cross{stroke:#333333;}#mermaid-svg-lPLEol6FYVyXJp2Q svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-lPLEol6FYVyXJp2Q p{margin:0;}#mermaid-svg-lPLEol6FYVyXJp2Q .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-lPLEol6FYVyXJp2Q .cluster-label text{fill:#333;}#mermaid-svg-lPLEol6FYVyXJp2Q .cluster-label span{color:#333;}#mermaid-svg-lPLEol6FYVyXJp2Q .cluster-label span p{background-color:transparent;}#mermaid-svg-lPLEol6FYVyXJp2Q .label text,#mermaid-svg-lPLEol6FYVyXJp2Q span{fill:#333;color:#333;}#mermaid-svg-lPLEol6FYVyXJp2Q .node rect,#mermaid-svg-lPLEol6FYVyXJp2Q .node circle,#mermaid-svg-lPLEol6FYVyXJp2Q .node ellipse,#mermaid-svg-lPLEol6FYVyXJp2Q .node polygon,#mermaid-svg-lPLEol6FYVyXJp2Q .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-lPLEol6FYVyXJp2Q .rough-node .label text,#mermaid-svg-lPLEol6FYVyXJp2Q .node .label text,#mermaid-svg-lPLEol6FYVyXJp2Q .image-shape .label,#mermaid-svg-lPLEol6FYVyXJp2Q .icon-shape .label{text-anchor:middle;}#mermaid-svg-lPLEol6FYVyXJp2Q .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-lPLEol6FYVyXJp2Q .rough-node .label,#mermaid-svg-lPLEol6FYVyXJp2Q .node .label,#mermaid-svg-lPLEol6FYVyXJp2Q .image-shape .label,#mermaid-svg-lPLEol6FYVyXJp2Q .icon-shape .label{text-align:center;}#mermaid-svg-lPLEol6FYVyXJp2Q .node.clickable{cursor:pointer;}#mermaid-svg-lPLEol6FYVyXJp2Q .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-lPLEol6FYVyXJp2Q .arrowheadPath{fill:#333333;}#mermaid-svg-lPLEol6FYVyXJp2Q .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-lPLEol6FYVyXJp2Q .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-lPLEol6FYVyXJp2Q .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lPLEol6FYVyXJp2Q .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-lPLEol6FYVyXJp2Q .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lPLEol6FYVyXJp2Q .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-lPLEol6FYVyXJp2Q .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-lPLEol6FYVyXJp2Q .cluster text{fill:#333;}#mermaid-svg-lPLEol6FYVyXJp2Q .cluster span{color:#333;}#mermaid-svg-lPLEol6FYVyXJp2Q div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-lPLEol6FYVyXJp2Q .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-lPLEol6FYVyXJp2Q rect.text{fill:none;stroke-width:0;}#mermaid-svg-lPLEol6FYVyXJp2Q .icon-shape,#mermaid-svg-lPLEol6FYVyXJp2Q .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lPLEol6FYVyXJp2Q .icon-shape p,#mermaid-svg-lPLEol6FYVyXJp2Q .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-lPLEol6FYVyXJp2Q .icon-shape .label rect,#mermaid-svg-lPLEol6FYVyXJp2Q .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lPLEol6FYVyXJp2Q .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-lPLEol6FYVyXJp2Q .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-lPLEol6FYVyXJp2Q :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    HomePage / CameraPage 进入模板页

    TemplatePage

    筛选 / 搜索 / 收藏

    创建自定义模板

    写入 recentTemplateIds

    同步 cameraWatermarkTemplate

    CameraPage 读取并应用

    这张图说明的是:模板页不是孤立页面,它和相机页是一对前后链路。

    下面是更详细的「模板应用数据流」序列图,展示从用户点击模板到CameraPage渲染水印的完整时序过程:

    CameraPage

    持久化存储

    AppStorage

    模板页

    用户

    CameraPage

    持久化存储

    AppStorage

    模板页

    用户

    #mermaid-svg-oLK9CmqNiL4np8rs{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-oLK9CmqNiL4np8rs .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-oLK9CmqNiL4np8rs .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-oLK9CmqNiL4np8rs .error-icon{fill:#552222;}#mermaid-svg-oLK9CmqNiL4np8rs .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-oLK9CmqNiL4np8rs .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-oLK9CmqNiL4np8rs .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-oLK9CmqNiL4np8rs .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-oLK9CmqNiL4np8rs .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-oLK9CmqNiL4np8rs .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-oLK9CmqNiL4np8rs .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-oLK9CmqNiL4np8rs .marker{fill:#333333;stroke:#333333;}#mermaid-svg-oLK9CmqNiL4np8rs .marker.cross{stroke:#333333;}#mermaid-svg-oLK9CmqNiL4np8rs svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-oLK9CmqNiL4np8rs p{margin:0;}#mermaid-svg-oLK9CmqNiL4np8rs .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-oLK9CmqNiL4np8rs text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-oLK9CmqNiL4np8rs .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-oLK9CmqNiL4np8rs .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-oLK9CmqNiL4np8rs .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-oLK9CmqNiL4np8rs .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-oLK9CmqNiL4np8rs #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-oLK9CmqNiL4np8rs .sequenceNumber{fill:white;}#mermaid-svg-oLK9CmqNiL4np8rs #sequencenumber{fill:#333;}#mermaid-svg-oLK9CmqNiL4np8rs #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-oLK9CmqNiL4np8rs .messageText{fill:#333;stroke:none;}#mermaid-svg-oLK9CmqNiL4np8rs .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-oLK9CmqNiL4np8rs .labelText,#mermaid-svg-oLK9CmqNiL4np8rs .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-oLK9CmqNiL4np8rs .loopText,#mermaid-svg-oLK9CmqNiL4np8rs .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-oLK9CmqNiL4np8rs .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-oLK9CmqNiL4np8rs .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-oLK9CmqNiL4np8rs .noteText,#mermaid-svg-oLK9CmqNiL4np8rs .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-oLK9CmqNiL4np8rs .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-oLK9CmqNiL4np8rs .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-oLK9CmqNiL4np8rs .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-oLK9CmqNiL4np8rs .actorPopupMenu{position:absolute;}#mermaid-svg-oLK9CmqNiL4np8rs .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-oLK9CmqNiL4np8rs .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-oLK9CmqNiL4np8rs .actor-man circle,#mermaid-svg-oLK9CmqNiL4np8rs line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-oLK9CmqNiL4np8rs :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    用户返回CameraPage

    点击选择模板

    写入 cameraWatermarkTemplate

    保存模板数据

    更新 recentTemplateIds

    保存最近使用记录

    返回模板选择结果

    读取 cameraWatermarkTemplate

    读取模板配置

    解析模板数据

    渲染水印图层

    显示带水印的相机预览

    这个序列图清晰地展示了:

  • 用户交互阶段:用户在模板页点击选择模板
  • 数据写入阶段:模板数据同时写入AppStorage(内存)和持久化存储
  • 数据同步阶段:更新最近使用记录
  • 数据读取阶段:CameraPage从AppStorage和持久化读取模板数据
  • 渲染阶段:CameraPage解析并渲染水印图层
  • 二、模板数据的组织方式很成熟

    const TEMPLATE_THEMES: TemplateTheme[] = [
    { bg: '#EEF4FF', badgeBg: '#0D47A1', badgeLabel: '今日现场', decorIcon: '🏗', infoLine1: '📍 地 点:深圳市 · 南山区', infoLine2: '备 注:点击输入今日备注' },
    { bg: '#E8F5ED', badgeBg: '#1B5E20', badgeLabel: '工程巡检', decorIcon: '✅', infoLine1: '📍 地 点:施工区域 · A栋3层', infoLine2: '备 注:安全巡检内容' },
    { bg: '#FFF3E8', badgeBg: '#E65100', badgeLabel: '考勤打卡', decorIcon: '📍', infoLine1: '📍 地 点:深圳市 · 南山区', infoLine2: '备 注:负责人签到打卡' }
    ];

    模板页不是依赖随机样式,而是有一组预设主题。

    这种设计的好处是:

    • 视觉稳定
    • 每类模板都能快速识别
    • 后面新增自定义模板时不会破坏整体风格

    三、筛选和搜索不是装饰,是降低选择成本

    在这里插入图片描述

    private getFilteredTemplates(): WatermarkTemplate[] {
    let list = this.selectedCategory === '全部'
    ? this.templates
    : this.templates.filter((t: WatermarkTemplate) => t.category === this.getCategoryKey(this.selectedCategory));
    const q = this.searchText.trim();
    if (q.length > 0) {
    list = list.filter((t: WatermarkTemplate) =>
    t.name.indexOf(q) >= 0 || t.description.indexOf(q) >= 0);
    }
    return list;
    }

    这段代码说明模板页已经不是“堆模板”,而是“帮用户找到合适模板”。

    特别是在模板数量变多以后:

    • 分类可以缩小范围
    • 搜索可以快速定位
    • 用户不会被一屏模板淹没

    四、收藏和最近使用,决定了模板页是否真能高频使用

    1. 收藏模板

    private toggleFavorite(item: WatermarkTemplate): void {

    this.favIds = JSON.stringify(newIds);
    }

    收藏不是装饰,它是在告诉系统:

    这个模板以后会被频繁用到。

    2. 最近使用

    this.recentIds = JSON.stringify([item.id, ids.filter((id: string) => id !== item.id)].slice(0, 10));

    这段逻辑很小,但很关键:

    • 最近使用会自动排前
    • 旧项会被挤到后面
    • 用户再次进入时可以快速回到惯用模板

    五、自定义模板让系统真正可扩展

    private addCustomTemplate(): void {
    const name = this.newTplName.trim();
    if (name.length === 0) {
    promptAction.showToast({ message: '请输入模板名称', duration: 1200 });
    return;
    }
    const newId = Date.now().toString() + '_c';
    const newTpl: WatermarkTemplate = {
    id: newId, name: name,
    category: catKey === 'all' ? 'custom' : catKey,
    description: this.newTplDesc.trim() || '自定义模板',
    isFavorite: false
    };
    }

    这说明模板页已经不是一个封闭列表,而是允许用户把自己的场景塞进来。

    最典型的场景有:

    • 工程现场
    • 考勤打卡
    • 旅行记录
    • 生活记录 在这里插入图片描述

    这类自定义能力,是文章能不能显得“像产品”而不是“像 demo”的关键。

    六、应用模板时要同时处理页面返回和数据写入

    private applyTemplate(item: WatermarkTemplate): void {
    this.selectedCameraTemplate = this.getWatermarkIndex(item);
    this.wmSelectedTemplateName = item.id.endsWith('_c') ? item.name : '';
    this.wmSelectedTemplateDesc = item.id.endsWith('_c') ? item.description : '';

    if (this.fromCamera) {
    this.onBack?.();
    } else {
    this.onGoCamera?.();
    }
    }

    这里的逻辑其实分成两层:

  • 把模板信息写到共享状态里
  • 决定要不要立刻回到相机页
  • 这很重要,因为模板页既可以从首页进入,也可以从相机页反向进入。

    七、页面结构上,它在追求“快速决策”

    Text('水印模板')
    Text('选择合适的模板,记录你的现场')
    Text(this.showSearch ? '✕' : '🔍')

    模板页顶部只需要解决三个问题:

    • 现在在哪个模板分类里
    • 是否在搜索
    • 是否准备返回相机页

    所以页面设计上要让“挑模板”这件事尽量快。

    八、这页最值得写进文章的 3 个点

  • 模板页把模板从静态列表变成可管理资产
  • 收藏和最近使用能显著提升高频场景效率
  • 自定义模板让水印系统可以适配更多业务
  • 九、实操检查清单

    • 分类筛选是否能正确过滤模板
    • 搜索是否能命中模板名称和描述
    • 收藏后是否会写回收藏列表
    • 自定义模板是否能持久化
    • 从相机页返回后是否会立刻应用模板
    • 最近使用是否会自动排到前面 在这里插入图片描述

    结语

    模板页的真正价值,不是展示了多少种卡片,而是它让用户在拍照前就完成了“风格决策”。

    在这个应用里,模板选择不是一个点,而是整条水印链路的起点。

    参考资料

    • 华为 HarmonyOS 多设备开发最佳实践
    • 博客质量分计算——发布 version 5.0
    赞(0)
    未经允许不得转载:171主机测评 » 时间轨迹第 6 篇:模板页源代码拆解,收藏、自定义、最近使用与模板应用
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址