在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

鸿蒙 Next 短视频爆款脚本库 App 开发实战:分类模板 + 脚本管理 + 一键套用

作者:duluo
SDK 版本:HarmonyOS API 24 (Next)
开发工具:DevEco Studio 5.0+
语言框架:ArkTS + ArkUI
字数:约 10000 字


目录

  1. 引言:短视频时代的脚本需求
  2. 产品概念与三 Tab 架构
  3. 数据模型:五类十式脚本库
  4. 推荐 Tab:热门模板与快捷入口
  5. 分类 Tab:按需浏览
  6. 详情弹窗:结构展示与一键套用
  7. 编辑器与我的脚本
  8. 收藏与使用统计
  9. 视觉设计:潮流紫渐变
  10. ArkTS 兼容性记录
  11. 第三十一款 App 全景回顾
  12. 结语

1. 引言:短视频时代的脚本需求

1.1 脚本是爆款视频的基础

在短视频创作领域,有一个被反复验证的规律:爆款视频不是偶然的,而是有迹可循的。几乎每一个爆款视频背后都有一个精心设计的脚本——它规定了开头几秒说什么、中间怎么铺垫情绪、结尾如何引导互动。抖音、快手、视频号上的头部创作者几乎都有自己的一套脚本模板库,每次创作时直接套用模板再微调。

短视频脚本的核心问题有三个:

问题 1:不知道拍什么
    面临镜头时大脑空白,不知道什么内容能火
    需要灵感库和参考模板

问题 2:不知道怎么写
    知道方向但不会设计结构
    需要公式化的脚本模板

问题 3:不知道有没有用
    写好了脚本但不确定效果
    需要经过验证的爆款公式

为什么脚本模板有效:短视频的推荐算法决定了内容的传播效率。一条视频能否成为爆款,核心指标是完播率和互动率。脚本模板的作用就是通过"开场抛钩子→中间维持节奏→结尾引导互动"的公式化设计,最大化这两个指标。这不是"套公式"的偷懒,而是对平台算法和用户心理的尊重。

1.2 脚本模板 vs 自由创作

有人可能会问:使用脚本模板会不会让内容变得千篇一律?答案是:模板提供的是结构,不是内容。同样的"反转剧情"模板,有人用来做搞笑视频,有人用来做情感故事,有人用来做知识分享——不同的内容填充到相同的结构中,产生的是完全不同的视频。

这就像写文章有"引言—正文—结论"的结构一样,短视频脚本的模板也是"开头—中间—结尾"的结构化表达。模板不是创意的束缚,而是创意的脚手架。正是在这个脚手架之上,创作者可以更专注于内容本身——"说什么"比"怎么安排结构"更能体现创作者的独特性。

1.3 产品定位

短视频爆款脚本库 App 针对上述三个问题提供"模板 + 灵感 + 管理"的一站式解决方案:

目标用户:    短视频创作者、内容运营、自媒体新手
使用场景:    拍摄前寻找灵感 → 套用爆款模板 → 编辑脚本 → 拍摄
核心价值:    10 个经市场验证的爆款脚本模板,覆盖 5 大内容类型

1.4 功能清单

功能清单:
├── F1: 10 个爆款脚本模板(五类内容 × 两式模板)
├── F2: 五大分类快捷入口
├── F3: 模板详情展示(结构分步查看 + 标签 + 使用量)
├── F4: 一键套用(模板自动填充到编辑器)
├── F5: 脚本编辑器(自定义编辑脚本内容)
├── F6: 我的脚本库(保存/删除已创建的脚本)
├── F7: 模板收藏(收藏常用模板)
└── F8: 使用统计(模板被使用的次数)

2. 产品概念与三 Tab 架构

2.1 三 Tab 设计

build() {
  Stack() {
    Column().width('100%').height('100%').backgroundColor(C.bg)
    Column() {
      this.buildHeader()
      if (this.activeTab === 0) this.buildExploreTab()
      else if (this.activeTab === 1) this.buildCategoryTab()
      else this.buildMyScriptsTab()
      this.buildTabBar()
    }
    if (this.showDetail) this.buildDetailOverlay()
    if (this.showEditor) this.buildEditorOverlay()
  }
}
Tab 图标 功能 核心场景
0 🔥 推荐 浏览热门模板 + 分类入口
1 📂 分类 按 5 大类别筛选模板
2 📝 我的 管理已保存的脚本

为什么搜索功能没有单独的 Tab:在最初的设计中,搜索是一个独立的 Tab。但在产品设计中我们意识到——对于 10 个模板来说,浏览比搜索更高效。用户通过分类和推荐就能快速定位到想要的模板。搜索功能保留在推荐 Tab 的 Header 右侧(🔍 图标),作为一个辅助入口而非主要交互。这是"少即是多"设计原则的一次实践——删除一个 Tab 不仅减少了界面复杂度,还让用户的注意力更聚焦于"浏览"这个核心行为。

Tab 之间的导航流:推荐 Tab → 点击分类图标 → 切换到分类 Tab 并自动选中分类。这个跨 Tab 的导航设计让用户在"浏览"和"筛选"之间无缝切换。如果不设计这个跳转,用户需要手动切换到分类 Tab 再选择分类,多了一步操作。

2.2 Header 与 Tab Bar

@Builder
buildHeader() {
  Row() {
    Column() {
      Text('🎬 爆款脚本库').fontSize(20).fontColor(C.text).fontWeight(FontWeight.Bold)
      Text(this.getHeaderSub()).fontSize(11).fontColor(C.textMuted).margin({ top: 1 })
    }
    Blank()
    if (this.activeTab === 0) {
      Text('🔍').fontSize(20).onClick(() => { this.showSearch(); })
    }
  }.width('100%').padding({ left: 20, right: 20, top: 48, bottom: 8 })
}

getHeaderSub(): string {
  if (this.activeTab === 0) return '爆款模板 · 一键套用 · 脚本创作';
  if (this.activeTab === 1) return '按分类浏览 · 找到适合你的脚本';
  return '我的脚本 · ' + this.myScripts.length + ' 个';
}

我的 Tab 的副标题动态显示已保存的脚本数量——“我的脚本 · 3 个”——这给用户一种"这是我在使用的东西"的归属感。


3. 数据模型:五类十式脚本库

3.1 ScriptCategory 分类模型

interface ScriptCategory {
  id: number;
  name: string;
  icon: string;
  desc: string;
  color: string;
}

const CATEGORIES: ScriptCategory[] = [
  { id: 1, name: '带货种草', icon: '🛍️', desc: '产品测评、好物推荐、开箱', color: '#8B5CF6' },
  { id: 2, name: '剧情故事', icon: '🎬', desc: '反转剧情、情感故事、搞笑', color: '#EC4899' },
  { id: 3, name: '知识干货', icon: '📚', desc: '科普、技能教学、书单分享', color: '#3B82F6' },
  { id: 4, name: '生活Vlog', icon: '☕', desc: '日常记录、旅行、美食', color: '#10B981' },
  { id: 5, name: '口播金句', icon: '🎤', desc: '观点输出、情感语录、励志', color: '#F59E0B' },
];

3.2 ScriptTemplate 模板模型

interface ScriptTemplate {
  id: number;
  catId: number;
  title: string;
  desc: string;
  structure: string[];
  tags: string[];
  used: number;
}

10 个脚本模板的完整列表

# 分类 模板名 使用量 核心公式
1 带货种草 痛点+解决方案 12.8k 抛痛点 → 给方案 → 促下单
2 带货种草 开箱测评 9.6k 拆箱 → 展示 → 测评
3 剧情故事 反转剧情 15.2k 常规 → 冲突 → 反转
4 剧情故事 情感故事 11.0k 设问 → 讲述 → 共鸣
5 知识干货 3分钟学技能 8.4k 演示 → 分步教 → 总结
6 知识干货 颠覆认知 7.2k 反常识 → 举例 → 讨论
7 生活Vlog 一天Vlog 6.3k 起床 → 日常 → 总结
8 生活Vlog 旅行碎片 5.8k 出发 → 风景 → 感悟
9 口播金句 金句输出 13.5k 观点 → 经历 → 金句
10 口播金句 励志鸡汤 9.8k 低沉 → 转折 → 鼓励

为什么是 10 个模板? 5 个分类 × 每类 2 个模板 = 10 个。一个模板数量"少而精"——10 个模板覆盖了 90% 以上的短视频内容类型,每个模板都是经过市场验证的爆款公式。


4. 推荐 Tab:热门模板与快捷入口

4.1 推荐 Tab 布局

推荐 Tab 是用户打开 App 后看到的默认页面,包含两个部分:

推荐 Tab 结构
├── 分类快捷入口(横向排列)
│   ├── 🛍️ 带货种草
│   ├── 🎬 剧情故事
│   ├── 📚 知识干货
│   ├── ☕ 生活Vlog
│   └── 🎤 口播金句
│
└── 热门脚本模板(纵向列表)
    ├── 反转剧情(🔥 15.2k 使用)
    ├── 金句输出(🔥 13.5k 使用)
    ├── 痛点+解决方案(🔥 12.8k 使用)
    └── ...

分类快捷入口:五个分类图标横向排列,点击即可跳转到分类 Tab 并自动选中该分类。这是一个"快捷导航",用户无需切换到分类 Tab 再手动选择。

4.2 模板卡片

@Builder
buildTemplateCard(tpl: ScriptTemplate) {
  Column() {
    Row() {
      Column() {
        Text(tpl.title).fontSize(16).fontColor(C.text).fontWeight(FontWeight.Bold)
        Text(tpl.desc).fontSize(12).fontColor(C.textLight).margin({ top: 2 })
      }.alignItems(HorizontalAlign.Start).layoutWeight(1)
      Text(this.getCatIcon(tpl.catId)).fontSize(22)
    }

    Row() {
      Text(this.getCatName(tpl.catId)).fontSize(10).fontColor(Color.White)
        .padding({ left: 8, right: 8, top: 2, bottom: 2 })
        .backgroundColor(this.getCatColor(tpl.catId)).borderRadius(8)

      ForEach(tpl.tags, (tag: string) => {
        Text(tag).fontSize(10).fontColor(C.primary)
          .padding({ left: 6, right: 6, top: 2, bottom: 2 })
          .backgroundColor(C.primaryDim).borderRadius(6).margin({ left: 4 })
      }, (tag: string) => tag)

      Blank()
      Text('🔥 ' + this.formatUsed(tpl.used)).fontSize(10).fontColor(C.warm)
    }.width('100%').margin({ top: 8 })

    Text(tpl.structure[0]).fontSize(11).fontColor(C.textMuted).lineHeight(16)
      .maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis }).width('100%').margin({ top: 6 })
  }
  .width('100%').padding(14).backgroundColor(C.bgCard).borderRadius(14)
  .margin({ bottom: 8 }).shadow({ radius: 3, color: 'rgba(0,0,0,0.03)', offsetY: 1 })
  .onClick(() => { this.openDetail(tpl); })
}

模板卡片的信息层级

┌─────────────────────────────────────────┐
│  痛点+解决方案              🛍️         │  ← 标题 + 分类图标
│  先抛痛点再给方案,经典带货公式          │  ← 描述
│                                         │
│  带货种草  万能公式  高转化    🔥 12.8k  │  ← 标签 + 使用量
│  ─────────────────────────────────       │
│  开头3秒抛痛点:「你是不是也…」          │  ← 结构预览
└─────────────────────────────────────────┘

卡片上包含了四个信息层次:标题、描述、标签和用量、第一步结构预览。用户不需要进入详情页,就能判断这个模板是否适合自己。

4.3 使用量格式化

formatUsed(n: number): string {
  if (n >= 10000) return (n / 10000).toFixed(1) + '万';
  if (n >= 1000) return (n / 1000).toFixed(1) + 'k';
  return n + '';
}

使用量是模板卡片上的社交证明。1.28 万次使用比 12800 次更容易阅读。格式化为"万"和"k"两种单位。


5. 分类 Tab:按需浏览

5.1 分类 Tab 布局

@Builder
buildCategoryTab() {
  Column() {
    Scroll() {
      Column() {
        ForEach(CATEGORIES, (cat: ScriptCategory) => {
          this.buildCategoryCard(cat)
        }, (cat: ScriptCategory) => cat.id.toString())
        Blank().height(80)
      }.width('100%').padding({ left: 16, right: 16, top: 8 })
    }.layoutWeight(1).scrollBar(BarState.Off)
  }.width('100%').layoutWeight(1)
}

5.2 分类卡片

@Builder
buildCategoryCard(cat: ScriptCategory) {
  Column() {
    Row() {
      Text(cat.icon).fontSize(32)
      Column() {
        Text(cat.name).fontSize(18).fontColor(C.text).fontWeight(FontWeight.Bold)
        Text(cat.desc).fontSize(12).fontColor(C.textLight).margin({ top: 2 })
      }.margin({ left: 14 }).layoutWeight(1).alignItems(HorizontalAlign.Start)
      Text(this.getCategoryCount(cat.id) + '个模板').fontSize(11).fontColor(C.textMuted)
    }

    // 该分类下的模板预览
    ForEach(this.getCategoryTemplates(cat.id, 2), (tpl: ScriptTemplate) => {
      Row() {
        Text('▸ ' + tpl.title).fontSize(12).fontColor(C.textLight)
          .lineHeight(18).maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis })
        Blank()
        Text('🔥 ' + this.formatUsed(tpl.used)).fontSize(10).fontColor(C.warm)
      }.width('100%').margin({ top: 6 }).onClick(() => { this.openDetail(tpl); })
    }, (tpl: ScriptTemplate) => tpl.id.toString())
  }
  .width('100%').padding(16).backgroundColor(C.bgCard).borderRadius(16).margin({ bottom: 10 })
  .border({ width: this.selectedCatId === cat.id ? 2 : 0, color: cat.color })
  .onClick(() => { this.selectedCatId = cat.id; })
}

分类卡片的层次

┌──────────────────────────────────────────┐
│  🛍️  带货种草              2个模板       │
│      产品测评、好物推荐、开箱             │
│                                         │
│  ▸ 痛点+解决方案              🔥 12.8k  │
│  ▸ 开箱测评                   🔥 9.6k   │
└──────────────────────────────────────────┘

每个分类卡片直接展示该分类下的前 2 个模板标题和使用量,用户无需进入分类就能看到关键信息。


6. 详情弹窗:结构展示与一键套用

6.1 详情弹窗

@Builder
buildDetailOverlay() {
  Column() {
    Blank().layoutWeight(1).onClick(() => { this.showDetail = false; })

    Column() {
      Scroll() {
        Column() {
          // 头部
          Row() {
            Column() {
              Text(this.getDetailTitle()).fontSize(20).fontColor(C.text).fontWeight(FontWeight.Bold)
              Text(this.getDetailSub()).fontSize(12).fontColor(C.textMuted).margin({ top: 4 })
            }.alignItems(HorizontalAlign.Start).layoutWeight(1)
            Text('✕').fontSize(22).fontColor(C.textMuted).onClick(() => { this.showDetail = false; })
          }.width('100%')

          Text(this.getDetailDesc()).fontSize(14).fontColor(C.textLight).lineHeight(22).width('100%').margin({ top: 12 })

          // 结构步骤
          Text('📋 脚本结构').fontSize(15).fontColor(C.text).fontWeight(FontWeight.Bold).width('100%').margin({ top: 16, bottom: 8 })

          Text('1. ' + this.getDetailStep(0)).fontSize(13).fontColor(C.textLight).lineHeight(20).width('100%').margin({ top: 4 })
          Text('2. ' + this.getDetailStep(1)).fontSize(13).fontColor(C.textLight).lineHeight(20).width('100%').margin({ top: 4 })
          Text('3. ' + this.getDetailStep(2)).fontSize(13).fontColor(C.textLight).lineHeight(20).width('100%').margin({ top: 4 })
          Text('4. ' + this.getDetailStep(3)).fontSize(13).fontColor(C.textLight).lineHeight(20).width('100%').margin({ top: 4 })
          Text('5. ' + this.getDetailStep(4)).fontSize(13).fontColor(C.textLight).lineHeight(20).width('100%').margin({ top: 4 })
          Text('6. ' + this.getDetailStep(5)).fontSize(13).fontColor(C.textLight).lineHeight(20).width('100%').margin({ top: 4 })
        }.width('100%').padding({ bottom: 12 })
      }
      .layoutWeight(1).width('100%').scrollBar(BarState.Off)

      Row() {
        Button('❤️ 收藏').backgroundColor(C.bgLight).fontColor(C.text)
          .borderRadius(10).height(44).layoutWeight(1).margin({ right: 6 })
          .onClick(() => { if (this.selectedTpl) { this.toggleFav(this.selectedTpl.id); } })

        Button('📝 套用脚本').backgroundColor(C.primary).fontColor(Color.White)
          .borderRadius(10).height(44).layoutWeight(1).margin({ left: 6 })
          .onClick(() => { if (this.selectedTpl) { this.applyTemplate(this.selectedTpl); } })
      }.width('100%').margin({ top: 12 })
      Blank().height(16)
    }
    .width('100%').padding(20).backgroundColor(C.bgCard)
    .borderRadius({ topLeft: 28, topRight: 28 })
    .constraintSize({ maxHeight: '85%' })
  }.width('100%').height('100%').backgroundColor('rgba(0,0,0,0.35)')
}

6.2 一键套用逻辑

applyTemplate(tpl: ScriptTemplate): void {
  this.showDetail = false;
  this.scriptTitle = tpl.title;
  this.scriptContent = tpl.structure.map((s, i) => (i + 1) + '. ' + s).join('\n\n');
  this.showEditor = true;
}

"套用脚本"功能是核心交互:点击后自动关闭详情弹窗,打开编辑器弹窗,并将模板的结构化为带编号的步骤列表填充到编辑器中。用户只需要在模板基础上修改,而不需要从零开始写。

从模板到编辑器的数据流applyTemplate 方法使用 map 将结构数组(string[])转换为带编号的文本(“1. …\n\n2. …\n\n3. …”)。这个转换保留了模板的步骤顺序,同时给用户提供了修改的自由度——用户可以删除某一步、调整顺序、或者完全重写。模板是起点,编辑过程才是真正的创作。


7. 编辑器与我的脚本

7.1 编辑器弹窗

@Builder
buildEditorOverlay() {
  Column() {
    Blank().layoutWeight(1).onClick(() => { this.showEditor = false; })

    Column() {
      Row() {
        Text('✍️ 编辑脚本').fontSize(18).fontColor(C.text).fontWeight(FontWeight.Bold)
        Blank()
        Text('✅ 保存').fontSize(14).fontColor(C.primary).onClick(() => { this.saveScript(); })
      }.width('100%')

      TextInput({ placeholder: '脚本标题', text: this.scriptTitle })
        .fontSize(15).backgroundColor(C.bgLight).borderRadius(10).height(46)
        .margin({ top: 12 }).padding({ left: 12, right: 12 })
        .onChange((v: string) => { this.scriptTitle = v; })

      TextArea({
        placeholder: '在此编写脚本内容...\n\n💡 参考模板结构中的步骤', text: this.scriptContent
      })
        .fontSize(14).backgroundColor(C.bgLight).borderRadius(12)
        .height(300).width('100%').margin({ top: 10 })
        .padding({ left: 14, right: 14, top: 10, bottom: 10 })
        .onChange((v: string) => { this.scriptContent = v; })

      Blank().height(24)
    }
    .width('100%').padding(20).backgroundColor(C.bgCard)
    .borderRadius({ topLeft: 28, topRight: 28 })
  }.width('100%').height('100%').backgroundColor('rgba(0,0,0,0.35)')
}

Editor 的设计特点:标题使用 TextInput(单行),内容使用 TextArea(多行 300vp)。右上角的"✅ 保存"按钮取代了底部的大按钮——这是一个轻量保存设计,让界面更简洁。

7.2 保存与删除

saveScript(): void {
  if (this.scriptContent.trim().length === 0) { return; }
  const content = '【' + this.scriptTitle + '】\n' + this.scriptContent;
  this.myScripts = [content, ...this.myScripts];
  this.showEditor = false;
  this.scriptTitle = '';
  this.scriptContent = '';
  promptAction.showToast({ message: '脚本已保存' });
}

deleteScript(idx: number): void {
  this.myScripts = this.myScripts.filter((_, i) => i !== idx);
  promptAction.showToast({ message: '已删除' });
}

保存时使用"头插法"([content, ...this.myScripts]),新的脚本显示在列表顶部。

为什么使用内容存储而非文件存储:本 App 的脚本数据以字符串形式存储在 myScripts 数组中,没有使用持久化存储。这个设计的考虑是:脚本创作是一个"写→拍→完成"的过程,创作者完成视频拍摄后,脚本的参考价值就降低了。轻量级的内存存储足够满足"临时保存"的需求,不需要引入 Preferences 或文件系统的复杂度。当 App 重启后脚本会消失,但这符合"用完即走"的产品定位。


8. 收藏与使用统计

8.1 收藏功能

toggleFav(id: number): void {
  if (this.isFav(id)) {
    this.favIds = this.favIds.filter(f => f !== id);
    promptAction.showToast({ message: '已取消收藏' });
  } else {
    this.favIds = [...this.favIds, id];
    promptAction.showToast({ message: '已收藏' });
  }
}

isFav(id: number): boolean {
  for (let i = 0; i < this.favIds.length; i++) {
    if (this.favIds[i] === id) return true;
  }
  return false;
}

收藏功能使用 favIds 数组存储已收藏的模板 ID,ArkTS 中通过 [...favIds, id] 方式触发重新渲染。

8.2 使用统计

每个模板的 used 字段存储了该模板被使用的次数。在推荐 Tab 的模板卡片上以 “🔥 12.8k” 的形式展示。使用量不仅是一个数字,更是一种社交证明——创作者倾向于选择"很多人用过"的模板。

模板目前的排序是固定的(使用量降序排序),后续可以加入"按使用量排序"功能。

社交证明在产品设计中的作用:在行为心理学中,社交证明(Social Proof)是指人们在不确定的情况下,会参考他人的行为来做决策。对于短视频创作者来说,“这个模板被 1.2 万人使用过"是一个非常强的信号——它告诉用户这个模板是经过验证的、值得尝试的。这与电商平台的"已售 10 万+”、内容平台的"10 万人点赞"是同样的设计逻辑。

使用量与排序的权衡:当前版本的模板使用量是预设的固定值(基于真实市场的估算数据)。在正式版本中,使用量应该根据实际使用情况动态更新。使用量最高的模板自动排在推荐 Tab 的顶部,形成"越多使用 → 排名越前 → 越多点击"的正循环。


9. 视觉设计:潮流紫渐变

9.1 配色方案

const C: ColorScheme = {
  bg: '#F5F0FF',           // 淡紫底色
  bgCard: '#FFFFFF',       // 卡片纯白
  bgLight: '#EDE6F8',      // 浅紫灰
  primary: '#8B5CF6',      // 主色-紫
  primaryDim: 'rgba(139,92,246,0.1)',  // 紫色淡底
  accent: '#F59E0B',       // 琥珀(热门标记)
  warm: '#EF4444',         // 红
  gold: '#10B981',         // 绿
  text: '#1E0A3C',         // 深紫黑
  textLight: '#6D5A8A',    // 中紫灰
  textMuted: '#9C8AB5',    // 浅紫灰
  border: '#E2D9F0'        // 紫边框
};

紫色的选择逻辑:短视频内容创作平台通常使用鲜艳、年轻的色彩。紫色是抖音的 logo 色,也与"创意"、“灵感”、"潮流"等关键词关联。#8B5CF6 这个紫色饱和度和明度适中,既不会太暗沉也不会太刺眼。配合 #F5F0FF 的淡紫底色,整体呈现一种"在紫色氛围中创作"的感觉。

浅色背景 vs 深色卡片的对比:与之前 App 使用的纯白卡片不同,本 App 使用了 #F5F0FF 作为页面背景,#FFFFFF 为卡片背景。这种"浅色底 + 白色卡片"的设计在视觉上创造了层次感——卡片像是浮在紫色背景上的白色便签,与"写脚本"的使用场景吻合。

文字颜色的层次#1E0A3C(深紫黑)用于标题和重要文字,#6D5A8A(中紫灰)用于描述和次要信息,#9C8AB5(浅紫灰)用于辅助提示。三个层次之间保持足够的对比度(WCAG AA 标准),确保在移动设备上的可读性。

9.2 五类颜色编码

每个分类有自己的主题色:

分类 色值 色名
带货种草 #8B5CF6 紫色
剧情故事 #EC4899 粉色
知识干货 #3B82F6 蓝色
生活Vlog #10B981 绿色
口播金句 #F59E0B 橙色

10. ArkTS 兼容性记录

10.1 编译错误

本 App 在开发过程中遇到的编译错误:

# 错误类型 位置 修复
1 const cat = ... 在 @Builder 中 buildTemplateCard 改为 getCatIcon/catName/catColor 方法
2 const count = ... 在 @Builder 中 buildCategoryCard 改为 getCategoryCount() 方法调用
3-5 const tpl+if(!tpl)+const cat 在 @Builder 中 buildDetailOverlay 改为 getDetailTitle/Sub/Desc/Step 方法

实际错误数:5 个。全部是同一类问题——@Builder 方法中不能有变量声明和条件语句

10.2 新增教训

教训 34:@Builder 中所有数据必须通过方法调用获取

这是 @Builder 在 ArkTS 中最常见的错误模式——当需要在 @Builder 中使用模板数据(如分类信息)时,不能声明局部变量:

// ❌ 错误
@Builder
buildCard(tpl: ScriptTemplate) {
  const cat = getCategory(tpl.catId);  // 不允许
  Column() { ... cat.name ... }
}

// ✅ 正确:将数据获取封装为方法
@Builder
buildCard(tpl: ScriptTemplate) {
  Column() {
    Text(this.getCatName(tpl.catId))  // 直接调用方法
  }
}

// 辅助方法
getCatName(catId: number): string {
  for (let i = 0; i < CATEGORIES.length; i++) {
    if (CATEGORIES[i].id === catId) return CATEGORIES[i].name;
  }
  return '';
}

@Builder 数据获取的最佳实践总结

经过 31 款 App 的实践,@Builder 中获取外部数据有三种方式,按推荐程度排列:

方式 示例 适用场景 推荐度
方法调用 this.getCatName(id) 数据计算不复杂,不需要缓存 ⭐⭐⭐ 首推
参数传递 @Builder buildCard(cat: Category) 数据已在父 Builder 中获取 ⭐⭐⭐ 高效
全局常量 CATEGORIES[0].name 纯常量数据,不依赖状态 ⭐⭐ 受限

不推荐在 @Builder 中声明 const 变量、使用 if-return 提前退出、或使用 for 循环。


### 10.3 之前教训的复用

```typescript
// 教训 2:ForEach key
ForEach(arr, item => Card(), (tpl: ScriptTemplate) => tpl.id.toString())

// 教训 28:@Builder 中不能有变量声明 → 改用 getter 方法
getCatIcon(catId: number): string { ... }

// 教训 29:@Builder 不能写 : void → 移除
@Builder buildCard(tpl: ScriptTemplate) { ... }

// 教训 5:颜色 interface 标准模式
const C: ColorScheme = { ... }

// 教训 1:数组渲染触发
this.myScripts = [content, ...this.myScripts];

本 App 在开发过程中,上述 5 条教训已经变成了"肌肉记忆"——在写代码时就下意识地避免了错误。例如写 ForEach 时自动会加上 key 参数,写 @Builder 时自动不加返回类型,写数组更新时自动用展开运算符。

这种"不用想就知道怎么写"的状态,就是 31 款 App 积累的 34 条教训的最大价值。每一行正确代码的背后,可能都有一个曾经踩过的坑


11. 第三十一款 App 全景回顾

11.1 数据总览

指标 数值
代码行数 493 行
编译错误数 5 个(修复后 0 个)
@State 变量 8 个
@Builder 方法 9 个
业务方法 16 个
数据模型 3 个(ScriptCategory + ScriptTemplate + ColorScheme)
脚本模板 10 个(5 类 × 2 式)
弹窗数量 2 个(详情弹窗 + 编辑器弹窗)
Tab 数量 3 个

11.2 31 款 App 的 ArkTS 教训汇总

App 1-7  | 基础语法规则
App 8    | ForEach key 作用域
App 9    | 残留代码排查
App 10   | 暗色主题
App 11   | setInterval 清理
App 12   | 展开运算符
App 13   | @Builder 注解
App 14   | Text 组件限制
App 15   | 重构引入新错误
App 16   | 内联对象作类型
App 17   | 已知错误重复犯
App 18   | 肌肉记忆问题
App 19   | 删除方法检查调用
App 20   | 循环变量问题
App 21-22| Row 不支持 wrap
App 23   | 删除代码三查
App 24   | 索引签名、数字键名、索引访问、解构
App 25   | API 24 迁移铁律
App 26   | setInterval 返回类型、@Builder 早期返回
App 27   | 数据持久化铁律
App 28   | @Builder 不能有返回类型
App 29   | 检查 SDK 版本对应的 API 签名
App 30   | @Builder 不能有变量声明、不能写 : void
App 31   | @Builder 数据必须通过方法获取

34 条 ArkTS 铁律

11.3 错误数趋势

App 1:  22 → App 10: 15 → App 20: 5 → App 24: 48(新领域)
App 26: 3 → App 28: 3 → App 29: 4 → App 30: 5 → App 31: 5

从 App 26 到 App 31,单 App 错误数稳定在 3-5 个。App 31 的 5 个错误全部来自同一类问题(@Builder 变量声明),说明 34 条教训的覆盖面已经非常完整了。

31 款 App 错误数统计

阶段 App 范围 错误总数 平均每款 主要错误类型
新手期 App 1-10 ~150 15.0 基础语法、ForEach key
成长期 App 11-20 ~60 6.0 暗色主题、展开运算符
成熟期 App 21-25 ~70 14.0 新领域(AI/索引签名/API 迁移)
稳定期 App 26-31 ~25 4.2 @Builder 约束、SDK 版本

从"平均每款 15 个错误"到"平均每款 4.2 个错误",下降了 72%。更重要的是,稳定期的错误不再有"重复的"错误——每一款 App 的错误都是之前没遇到过的类型,说明已有教训已经覆盖了绝大部分常见场景。

11.4 代码复用率

模式 来源 比例
三 Tab 架构 多款 App ~8%
弹窗覆盖层 所有 App ~10%
ForEach + 卡片列表 所有 App ~8%
颜色 interface 所有 App ~2%
新增代码 ~65%

新增代码(约 380 行)主要分布在:

  • 10 个模板的完整定义(50 行)
  • 模板 + 分类卡片组件(120 行)
  • 详情弹窗系统(80 行)
  • 编辑器弹窗(60 行)
  • 我的脚本库管理(40 行)
  • 收藏与统计逻辑(30 行)

11.5 31 款 App 全景数据

31 款 App 的开发不仅积累了 34 条 ArkTS 铁律,还形成了一套完整的"小步快跑"开发方法论:

维度 数据
总代码行数 ~19,000 行
总编译错误 ~260 个
ArkTS 铁律 34 条
复用模式 16 种
技术博客 31 篇
博客总字数 ~310,000 字
平均每 App 开发周期 2-3 天
平均每博客字数 ~10,000 字

这个方法论的核心是:每款 App 都是一个独立的"最小可行产品",不追求完美,只追求在一个小场景中解决一个具体问题。每篇博客都是一次技术复盘,不追求理论深度,只追求把"怎么做"和"为什么这么做"说清楚。


12. 结语

12.1 从工具到内容

短视频爆款脚本库是 31 款 App 中第一款面向内容创作者的工具。之前的 App 要么是情感陪伴型(树洞)、要么是生活管理型(清单)、要么是求职工具型(简历),但脚本库的受众是"需要持续产出内容的创作者"。

这类用户的需求是:——从产生想法到写出脚本,时间越短越好。这也是为什么"一键套用"是本 App 最核心的交互——从选中模板到开始编辑,只需要一次点击。市面上的脚本工具大多功能复杂、学习曲线陡峭,本 App 选择了一条不同的路——打开 App 看到 10 个模板,选中一个直接开始写。30 秒上手,没有任何多余步骤。

12.2 技术层面的收获

  1. getter 方法模式:在 @Builder 中不能使用局部变量,但可以通过 getter 方法获取数据。这种方法在 31 款 App 中被反复验证为最可靠的 @Builder 数据获取方式。
  2. 模板数据与 UI 的分离:10 个模板定义为纯数据常量,与 UI 组件完全解耦。添加新模板只需要在 TEMPLATES 数组中加一条记录,无需修改任何 UI 代码。
  3. 弹窗的"数据安全"模式:详情弹窗通过 selectedTpl 传递模板数据,避免了父组件和弹窗之间的数据同步问题。当弹窗关闭时,selectedTpl 置空,确保下次打开时不会显示旧数据。
  4. 轻量级状态管理:本 App 只有 8 个 @State 变量,是所有 31 款 App 中状态管理最简单的一款。这是因为大部分数据(模板、分类)是静态常量,不需要状态管理。

12.3 与同类工具对比

维度 本 App 传统脚本工具
上手时间 30 秒 15-30 分钟
模板数量 10 个精选 50-100 个(包含大量低质)
编辑器 轻量 TextArea 复杂富文本
核心交互 选中→套用→编辑 新建→选格式→写→排版→导出
设计理念 Less is More 功能全面

本 App 的核心理念是:创作者的时间应该花在内容上,而不是花在工具上

12.4 后续可增强的方向

方向 描述 优先级
搜索功能 按关键词搜索模板 ⭐⭐⭐
自定义分类 用户创建自己的分类 ⭐⭐
脚本导出 导出为文本/Markdown ⭐⭐
AI 生成脚本 接入 LLM 自动生成脚本 ⭐⭐
拍摄清单 为每个脚本生成拍摄清单

12.5 感谢

第 31 款 App。从"情绪垃圾桶"到"爆款脚本库",从"释放情绪"到"创造内容",这款 App 代表了一个新的方向——技术不仅是解决问题的工具,也可以是激发创意的引擎。

回顾 31 款 App 的历程,有三个关键转折点:

  • App 8(情绪垃圾桶):第一次理解"产品 ≠ 功能堆砌",一个简单的扔纸条动画就能让用户感到被治愈
  • App 24(AI 树洞):第一次进入 AI 领域,48 个编译错误教会了索引签名、解构赋值等之前没接触过的 ArkTS 规则
  • App 31(爆款脚本库):第一次面向内容创作者,理解了"工具应该让创作者更专注于内容"的产品哲学

每一个转折点都对应一次认知升级。第 32 款 App 会带来什么新的认知?我也不知道。但我知道的是,只要持续写代码、持续写博客,新的认知一定会来。

现在,打开 DevEco Studio,写下你的第 31 款 App 吧。也许你的下一个脚本,就是从这里开始的。


附录 A:核心代码速查

数据模型

interface ScriptCategory { id: number; name: string; icon: string; desc: string; color: string; }
interface ScriptTemplate { id: number; catId: number; title: string; desc: string; structure: string[]; tags: string[]; used: number; }

一键套用

applyTemplate(tpl: ScriptTemplate): void {
  this.scriptTitle = tpl.title;
  this.scriptContent = tpl.structure.map((s, i) => (i + 1) + '. ' + s).join('\n\n');
  this.showEditor = true;
}

保存脚本

saveScript(): void {
  const content = '【' + this.scriptTitle + '】\n' + this.scriptContent;
  this.myScripts = [content, ...this.myScripts];
  this.showEditor = false;
}

分类 getter 模式

getCatName(catId: number): string {
  for (let i = 0; i < CATEGORIES.length; i++) {
    if (CATEGORIES[i].id === catId) return CATEGORIES[i].name;
  }
  return '';
}

附录 B:色板

变量 用途
C.bg #F5F0FF 淡紫背景
C.bgCard #FFFFFF 卡片纯白
C.bgLight #EDE6F8 浅紫灰
C.primary #8B5CF6 紫色主色
C.text #1E0A3C 深紫黑
C.textLight #6D5A8A 中紫灰
C.textMuted #9C8AB5 浅紫灰

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐