app-store-screenshots

app-store-screenshots

热门

用于搭建 App Store 或 Google Play 截图生成页面、生成可导出的 iOS/Android 应用宣传营销截图,或使用 Next.js 脚手架快速构建截图编辑器。触发关键词:app store、play store、screenshots、marketing assets、html-to-image、phone mockup、android screenshots、feature graphic。

6024Star
0Fork
更新于 2026/7/11
SKILL.md
只读
名称
app-store-screenshots
描述

用于搭建 App Store 或 Google Play 截图生成页面、生成可导出的 iOS/Android 应用宣传营销截图,或使用 Next.js 脚手架快速构建截图编辑器。触发关键词:app store、play store、screenshots、marketing assets、html-to-image、phone mockup、android screenshots、feature graphic。

App Store & Google Play 截图生成器

概述

快速搭建预置的 Next.js + ShadCN 编辑器,帮助用户将 App Store Google Play 截图打造成广告宣传图(而非单纯的 UI 展示图)。编辑器已处理好所有底层繁重工作:

  • 连贯画布实时预览:按画布真实分辨率渲染(等比缩放适配屏幕)
  • 支持拖拽调整页面顺序、内联文本编辑、单页布局切换
  • 跨页样机设计:手机/设备边框、文案标题及图层元素可跨相邻页面拖拽移动,并自动裁剪导出
  • 拖拽上传截图(文件保存至 public/screenshots/uploaded/<hash>.png
  • 自动保存至项目根目录的 app-store-screenshots.json(可提交至 Git 追踪)+ localStorage 镜像备份
  • 便捷切换 iOS ↔ Android 平台 — 两套幻灯片画布独立并存
  • 基于 html-to-image 实现一键批量导出符合 Apple/Google 官方分辨率要求的 PNG 格式图片
  • 单页支持浅色/深色主题切换、预置主题色板及多语言选择
  • 针对旧版项目的引导式原位迁移:在用户主动开启“连贯画布(connected canvas)”前,迁移流程会将旧版画布保持隔离导出,确保兼容性

开箱即用的支持设备:

  • iPhone(竖屏)— Apple App Store
  • iPad(竖屏)— Apple App Store
  • Android Phone(竖屏)— Google Play
  • Android Tablet 7"(竖屏 + 横屏)— Google Play
  • Android Tablet 10"(竖屏 + 横屏)— Google Play
  • Feature Graphic(1024×500 宣传置顶图)— Google Play 商店页横幅

核心理念

应用截图是广告,而不是产品说明书。 每张截图只需推销一个核心卖点。如果你只是平铺直叙地展示 UI,那就做错了 — 你应该卖的是情感体验最终效果,或者是解决痛点。请直接利用本 Skill 提供的交互式编辑器快速迭代文案与布局,切勿从零手写页面。

本 Skill 的作用

  1. 将与 SKILL.md 同目录的 template/ 预置模板复制到用户的当前工作目录中。
  2. 使用用户的包管理器安装依赖项。
  3. 将用户的应用截图放入 public/screenshots/...,应用图标放入 public/
  4. (可选)在 app-store-screenshots.json 中预填应用名称、初始文案、截图路径以及连贯画布偏好设置,确保首次预览即有实际内容展示。
  5. 启动开发服务器,并提示用户在浏览器中打开编辑器。

不要手动编写 page.tsx、设备样机边框或导出逻辑,这些代码均已内置于模板中。

步骤 0:检测已有的截图项目

在步骤 1 询问新建项目问题之前,务必先检查当前工作目录是否存在已有的 app-store-screenshots 项目实现。

执行以下轻量级探测命令:

test -f package.json && sed -n '1,220p' package.json
test -f app-store-screenshots.json && sed -n '1,120p' app-store-screenshots.json
rg -n "app-store-screenshots|html-to-image|toPng|ScreenshotEditor|DeckCanvas|connectedCanvas|EXPORT_SIZES|mockup.png|PHONE_SCREEN" package.json src app public 2>/dev/null
find public -maxdepth 4 \( -path "*/screenshots*" -o -name "mockup.png" -o -name "app-icon.png" \) -print 2>/dev/null

当满足以下任一条件时,即视为旧版实现项目:

  • 存在 app-store-screenshots.json 但缺失 schemaVersion``、schemaVersion < 2,或未包含 connectedCanvas
  • 存在 src/components/editor/screenshot-editor.tsx 但编辑器中未引用 DeckCanvasconnectedCanvas
  • src/app/page.tsx 包含旧版的单文件生成器代码(包含 html-to-imagetoPngEXPORT_SIZESPHONE_SCREEN、硬编码的幻灯片数组/主题等)。
  • 仓库包含旧版的截图资源目录结构(public/mockup.pngpublic/screenshots...)以及截图生成器依赖包配置。

若检测到旧版实现,在执行任何其它操作之前,只需且必须询问用户这一个问题

我在此处检测到了旧版的 App Store 截图项目。是否需要将该项目迁移至全新的连贯画布(connected-canvas)编辑器?

  1. 是 — 将现有项目迁移至新版编辑器
  2. 否 — 采用其它方式配置或修改项目

如果用户选择 ,则不要询问步骤 1 的问卷,直接使用仓库中现有的文件执行下方迁移流程;如果用户选择 ,则继续执行步骤 1。

迁移路径(用户选择“是”时)

目标是完成 UI/模板的原位升级,而非重新设计。保留用户已有的应用名称、文案、截图路径、应用图标、已上传资源、多语言配置及设备画布组合。将旧版 UI 实现替换为当前模板,且除非项目之前已明确开启连贯画布,否则将旧版画布保持在独立导出模式(isolated export mode)。

迁移规则:

  1. 无需再询问产品/设计细节。 用户已有现有项目,直接从已有文件中推断相关信息,并在末尾汇总汇报不影响运行的遗漏点即可。
  2. 切勿删除用户资源。 妥善保留 public/screenshots/public/app-icon.png、已上传的截图以及现有的 app-store-screenshots.json
  3. 确保可恢复性。 若工作区(worktree)不干净,不要还原无关修改。在覆盖模板文件之前,请将替换掉的项目状态/资源/代码快照复制保存到仓库外的临时备份目录(例如 /tmp/app-store-screenshots-migration-<timestamp>/),并在最终回复中告知该备份路径。
  4. 优先使用结构化迁移工具。 使用 JSON 工具读取和写入 app-store-screenshots.json,切勿使用正则表达式编辑 JSON。
  5. 设置 schemaVersion: 2 并妥善处理旧版 connectedCanvas 若现有项目已显式包含布尔值 connectedCanvas,请予以保留;若项目为 v2 之前版本或缺失该标志,请写入 "connectedCanvas": false,防止旧版超屏/裁剪的样机泄漏溢出到相邻页面导出图中。新建项目仍默认使用连贯画布。
  6. 保持截图文件路径不变。 除非旧项目原本就依赖数字命名且迁移必须用到,否则不要重命名截图文件,使用现有静态路径即可。
  7. 静默处理自定义主题。 若旧项目引用了自定义 themeId,且能在代码中找到匹配的主题对象,请将其合并到新的 src/lib/constants.ts 中。若无法恢复,可在项目 JSON 中保留该 themeId,编辑器会回退至 clean-light 主题并给出警告,此时应提示用户需手动恢复自定义主题。
  8. 尽可能合并 package 元数据。 截图编辑器所需的模板依赖项和脚本优先级最高,但应保留无关的已有 dependenciesdevDependencies 及有用脚本,除非存在直接冲突。
  9. 真实迁移中不要导入模板示例画布。 若旧项目已有画布或截图,模板仅用于提供 UI/代码。勿将模板自带的示例截图/画布混入迁移后的项目中,避免用户的应用继承无关示例内容。
  10. 内测/试运行时使用临时副本。 若用户要求测试或审查迁移过程而非直接迁移项目,请将应用复制到临时目录或 worktree 中进行迁移验证。只有在用户明确要求真实迁移并回答 时,才操作真实项目目录。

推荐的迁移步骤:

# 1. 将有用旧文件快照备份到仓库外部
STAMP=$(date +%Y%m%d-%H%M%S)
BACKUP_DIR="/tmp/app-store-screenshots-migration-$STAMP"
mkdir -p "$BACKUP_DIR"
cp -R app-store-screenshots.json public src package.json tailwind.config.ts next.config.mjs "$BACKUP_DIR/" 2>/dev/null || true

# 2. 保留复制模板时必须保留的项目状态与资源
PRESERVE_DIR="$BACKUP_DIR/preserve"
mkdir -p "$PRESERVE_DIR"
cp app-store-screenshots.json "$PRESERVE_DIR/" 2>/dev/null || true
cp -R public/screenshots "$PRESERVE_DIR/screenshots" 2>/dev/null || true
cp public/app-icon.png "$PRESERVE_DIR/app-icon.png" 2>/dev/null || true

# 3. 将当前模板覆盖到旧版 UI 实现上
cp -R "<SKILL_DIR>/template/." "$PWD/"
cp app-store-screenshots.json "$BACKUP_DIR/template-app-store-screenshots.json" 2>/dev/null || true

# 4. 将保留的用户状态/资源还原并覆盖模板示例文件
cp "$PRESERVE_DIR/app-store-screenshots.json" app-store-screenshots.json 2>/dev/null || true
mkdir -p public
if [ -d "$PRESERVE_DIR/screenshots" ]; then
  mkdir -p "$BACKUP_DIR/template-samples/public"
  mv public/screenshots "$BACKUP_DIR/template-samples/public/screenshots" 2>/dev/null || true
  cp -R "$PRESERVE_DIR/screenshots" public/screenshots
else
  mkdir -p public/screenshots
fi
cp "$PRESERVE_DIR/app-icon.png" public/app-icon.png 2>/dev/null || true

复制完成后,升级或新建 app-store-screenshots.json。如果已存在项目配置文件,则进行就地适配。如果不存在项目文件,但旧幻灯片数据嵌套在 src/lib/defaults.tssrc/app/page.tsx 中,请尽最大努力将其提取并填入模板的项目 JSON 中,然后再考虑回退至初始默认页面。提取时优先识别名为 slidesscreensfeaturesdefaultSlidesappNametaglinetheme 的旧版数组或对象以及截图路径。如果旧实现中仅有图片文件,请将 public/screenshots/** 按照路径排序,并据此生成初始幻灯片数据。

使用如下小型 JSON 脚本完成最终的项目状态兼容修正:

BACKUP_DIR="$BACKUP_DIR" node <<'NODE'
const fs = require("fs");
const path = require("path");

const PROJECT_FILE = "app-store-screenshots.json";
const DEFAULT_LOCALE = "en";
const DEVICE_KEYS = ["iphone", "ipad", "android", "android-7", "android-10", "feature-graphic"];
const LAYOUTS = ["hero", "device-bottom", "device-top", "two-devices", "no-device", "split-landscape", "feature-graphic"];

function readJson(file) {
  try {
    return JSON.parse(fs.readFileSync(file, "utf8"));
  } catch {
    return null;
  }
}

const templateState =
  readJson(path.join(process.env.BACKUP_DIR || "", "template-app-store-screenshots.json")) ||
  readJson(PROJECT_FILE) ||
  {};
const existingState = readJson(PROJECT_FILE) || {};
const hasExplicitConnectedCanvas = typeof existingState.connectedCanvas === "boolean";
const existingDecks =
  existingState.slidesByDevice && typeof existingState.slidesByDevice === "object"
    ? existingState.slidesByDevice
    : {};
const hasExistingDecks = Object.keys(existingDecks).length > 0;
const state = {
  ...templateState,
  ...existingState,
  slidesByDevice: hasExistingDecks ? existingDecks : templateState.slidesByDevice || {},
};

const legacySlides =
  Array.isArray(existingState.slides) ? existingState.slides :
  Array.isArray(existingState.screens) ? existingState.screens :
  Array.isArray(existingState.features) ? existingState.features :
  null;

if (legacySlides && !hasExistingDecks) {
  state.slidesByDevice = {
    iphone: legacySlides,
  };
}

function localized(value) {
  if (typeof value === "string") return { [DEFAULT_LOCALE]: value };
  if (value && typeof value === "object") return value;
  return {};
}

function cleanTransform(value) {
  if (!value || typeof value !== "object") return undefined;
  const { x, y, width, height, rotation, zIndex } = value;
  if (![x, y, width, height].every((n) => typeof n === "number" && Number.isFinite(n))) return undefined;
  return {
    x,
    y,
    width: Math.max(1, width),
    height: Math.max(1, height),
    ...(typeof rotation === "number" && Number.isFinite(rotation) ? { rotation } : {}),
    ...(typeof zIndex === "number" && Number.isFinite(zIndex) ? { zIndex } : {}),
  };
}

function firstString(...values) {
  return values.find((value) => typ

<!-- truncated for translation batch; full body continues in source -->