Commit 7d0bb14b authored by xuchentao's avatar xuchentao

Initial commit

parent 56bbab01
---
name: import-page-template
description: 当用户提供外部官网/页面模版(下载的 HTML 模版目录、zip 或单页 HTML)并希望导入、转换为 WebAgent 项目可用的官网外观模版(site-kit/page-templates 设计包)时使用。覆盖模版解析、Astro 设计包转换、token 视觉契约适配、契约体检与预览验证全流程。
---
# 导入外部页面模版和WebAgent官网外观模版
## 目的
把外部下载的 HTML 官网模版转换为 WebAgent 的「设计包式外观模版」(`site-kit/page-templates/<id>/<version>/manifest.json + src/`),使其可被创建页选择、预览、用于建站。
## 契约单一事实源
先读仓库契约文档 `docs/site-template-architecture.md` 的「模版格式」与「设计包契约」章节——契约以仓库当前版本为准,本技能只提供流程与经验。再读 `references/conversion-guide.md` 获取 token 变量全表、区块映射与代码示例。
## 工作流程
### 1. 解析源模版
- 定位入口:找到主 HTML(通常 index.html)、CSS、JS、图片资源目录;zip 先解压到工作区外临时目录。
- 识别区块:导航、首屏(hero)、服务/产品、优势、数据指标、关于、联系、页脚;记录多页结构(如有 about.html 等,只取首页 + 设计语言,不整站搬运)。
- 记录视觉特征:主色、背景色、文字色、边框色、字体栈——稍后映射为 token。
### 2. 定身份与默认视觉
- `id`:kebab-case(如 `aurora-consulting`),不得与 `site-kit/page-templates/` 现有目录重复;`version``1.0.0` 起。
- `name`/`description`/`industries`:面向创建页卡片展示的中文文案。
- `defaultThemeId`:从 `site-kit/theme-families.json` 选与模版主色最接近的变体 id;`defaultFontId`:从 `site-kit/fonts.json` 选气质最接近的(衬线选 elegant-serif,无衬线选 modern-sans)。
- `preview.accent/surface/mode`:卡片展示用,取模版主色、主背景色、明暗。
### 3. 生成设计包
`references/conversion-guide.md` 的区块映射与示例执行,硬性要求(违反会被物化拒绝或构建失败):
- 必备文件:`src/pages/index.astro``src/pages/articles/index.astro``src/pages/articles/[...id].astro``src/data/site.json``src/data/home.json`
- **禁止自带**`src/_platform/``src/content.config.ts``src/styles/theme.css``src/styles/fonts.css`、astro/package 构建配置(前两项物化直接报错;后两个样式文件会被平台生成物覆盖)。
- 样式 token 化:颜色一律 `var(--color-*)`、字体一律 `var(--font-heading/body)`,不写死色值与字体栈;`--gradient-*` 默认是纯色,不要依赖渐变效果。
- 文案外置到 `src/data/``site.json` 从内核 `astro-template/src/data/site.json` 拷贝后只改默认值(必须保留 seo/organization 嵌套结构);`home.json` 必须含 `services` 数组。
- 文章页:用 `../_platform/content/public-content``publishedArticles()` / `entryPath("articles", id)` 取数,与首页同一套 Header/Footer 和设计语言。
- SEO/GEO:`<head>` 用内核 `src/_platform/seo/` 的助手与 BaseLayout 契约,不自写 sitemap/robots/llms 端点。
- 图片:模版自带的装饰图放 `src/assets/``public/images/`(注意后者与平台文章图片目录 `public/images/` 的共存);产品/案例等占位图用灰色「宽×高」占位符,不搬运版权图。
### 4. 契约体检
运行体检脚本并修到全绿:
```bash
node .codebuddy/skills/import-page-template/scripts/validate-design-package.mjs site-kit/page-templates/<id>/<version>
```
### 5. 构建与预览验证
- manifest `status` 保持 `"published"`(目录与预览服务只认 published);尚未稳定前被终端用户选用的风险可忽略(本地环境)。
- 触发模版预览重建:site-kit 已变化,重启后端后 `TemplatePreviewService` 自动重建(6+N 站约 3-5 分钟),或等下次启动。
- 打开该模版的 `previewUrl`(4400+ 端口)与截图,逐项核对:首屏还原度、各区块、文章列表/详情页(设计语言一体化)、配色 token 生效(theme.css 是平台生成的)。
- 构建失败:看 `.runtime/template-previews/state.json` 里该条目 error,修复后删除该条目并重启触发重建。
### 6. 收尾
- 删除转换过程中的临时解压目录。
- 提示用户:模版一经被站点使用即不可原地修改,后续调整开新版本目录。
## 验收清单
- [ ] 体检脚本全绿
- [ ] 预览站首屏与源模版视觉一致(配色经 token,允许主题色差异)
- [ ] 文章列表/详情页与首页同设计语言,非隔离简易壳
- [ ] 切换配色方案/字体(改 defaultThemeId/defaultFontId 重建)样式不崩
# Example Asset File
This placeholder represents where asset files would be stored.
Replace with actual asset files (templates, images, fonts, etc.) or delete if not needed.
Asset files are NOT intended to be loaded into context, but rather used within
the output Claude produces.
Example asset files from other skills:
- Brand guidelines: logo.png, slides_template.pptx
- Frontend builder: hello-world/ directory with HTML/React boilerplate
- Typography: custom-font.ttf, font-family.woff2
- Data: sample_data.csv, test_dataset.json
## Common Asset Types
- Templates: .pptx, .docx, boilerplate directories
- Images: .png, .jpg, .svg, .gif
- Fonts: .ttf, .otf, .woff, .woff2
- Boilerplate code: Project directories, starter files
- Icons: .ico, .svg
- Data files: .csv, .json, .xml, .yaml
Note: This is a text placeholder. Actual assets can be any file type.
# Reference Documentation for Import Page Template
This is a placeholder for detailed reference documentation.
Replace with actual reference content or delete if not needed.
Example real reference docs from other skills:
- product-management/references/communication.md - Comprehensive guide for status updates
- product-management/references/context_building.md - Deep-dive on gathering context
- bigquery/references/ - API references and query examples
## When Reference Docs Are Useful
Reference docs are ideal for:
- Comprehensive API documentation
- Detailed workflow guides
- Complex multi-step processes
- Information too lengthy for main SKILL.md
- Content that's only needed for specific use cases
## Structure Suggestions
### API Reference Example
- Overview
- Authentication
- Endpoints with examples
- Error codes
- Rate limits
### Workflow Guide Example
- Prerequisites
- Step-by-step instructions
- Common patterns
- Troubleshooting
- Best practices
# HTML 模版 → 设计包转换指南
配合 `docs/site-template-architecture.md`(契约)使用;本文件提供变量全表、映射方法与可直接套用的代码骨架。内核组件 `astro-template/src/components/*.astro` 是「数据外置 + token 化」的参照实现。
## 1. token 变量全表
物化时平台生成 `src/styles/theme.css`(配色)与 `src/styles/fonts.css`(字体),设计包样式只能消费这些变量:
### 颜色(`var(--color-*)`)
| 变量 | 语义 | 源模版映射建议 |
| --- | --- | --- |
| `--color-primary` | 品牌主色(创建时可被用户品牌色覆盖) | 模版主色/按钮色 |
| `--color-primary-hover` | 主色 hover(自动加深 16%) | 不直接映射,用主色场景 |
| `--color-primary-light` | 主色变亮(自动 +24%) | 高亮/徽标点 |
| `--color-secondary` | 主题辅色 | 模版辅助强调色 |
| `--color-primary-dark` | 深底色(深色区块背景) | 模版深色 section/页脚 |
| `--color-background` | 页面主背景 | 模版 body 背景 |
| `--color-background-soft` | 浅色调背景(交替区块) | 模版浅灰/浅色 section |
| `--color-surface` | 卡片表面(恒 #ffffff) | 卡片/面板背景 |
| `--color-text-primary` | 主文字 | 标题/正文深色 |
| `--color-text-secondary` | 次文字 | 描述文字 |
| `--color-text-muted` | 弱文字 | 辅助说明/时间 |
| `--color-border` / `--color-divider` | 边框/分割线 | 卡片边、hairline |
### 渐变(`--gradient-primary/dark/soft`)
`gradientEmphasis` 默认关闭,此时三个变量都是**纯色**(分别等于 primary、primary-dark、background-soft)。样式必须假设它们是纯色:不要写「渐变才好看」的设计;用它们做按钮/深区块/浅区块背景即可。
### 字体
`--font-heading`(标题)、`--font-body`(正文)。不要 `@font-face` 或 font-family 写死。
### 常见写法
```css
.btn-primary { color: #fff; background: var(--gradient-primary); }
.section-alt { background: var(--color-background-soft); }
.card { background: var(--color-surface); border: 1px solid var(--color-border); color: var(--color-text-secondary); }
h1, h2, h3 { font-family: var(--font-heading); color: var(--color-text-primary); }
```
## 2. 区块映射与数据外置
| 源 HTML 区块 | 设计包组件 | 数据落点(src/data/) |
| --- | --- | --- |
| 顶部条幅 | Header.astro 内 announcement | `home.json``announcement` 字段(配合 `design.layout.announcementBar` 开关渲染) |
| 导航 | Header.astro | 公司名读 `site.json``name` |
| 首屏 | components/Hero.astro | `home.json`:eyebrow/heroTitle/heroDescription/primaryAction/secondaryAction/metrics |
| 服务/产品 | Services.astro | `home.json``services` 数组(**契约必填**,建站时被用户输入覆盖) |
| 优势/卖点 | Advantages.astro | `home.json`:advantages 数组 |
| 关于/简介 | About.astro | `site.json` 的 description 等 |
| 联系/CTA | Contact.astro | `site.json` 的 email/phone |
| 页脚 | Footer.astro | `site.json` |
| 装饰/点位样式 | styles/global.css | 只消费 token |
约定俗成:文案一律进 data JSON、组件只读 JSON 渲染(参照内核组件写法),不要在 .astro 里写死业务文案——这样 Agent 建站/定制时只改数据文件即可。
## 3. 骨架代码
### manifest.json
```json
{
"id": "<kebab-id>",
"version": "1.0.0",
"name": "中文展示名",
"description": "一句话风格与适用场景,创建页卡片展示。",
"industries": ["行业一", "行业二", "行业三"],
"status": "published",
"compatibleKernel": "1.x",
"preview": { "accent": "#模版主色", "surface": "#模版主背景", "mode": "light" },
"defaultThemeId": "<theme-families.json 里的变体 id>",
"defaultFontId": "modern-sans 或 elegant-serif"
}
```
### data/site.json
直接拷贝 `astro-template/src/data/site.json` 改默认值(结构一个字段都不能少,尤其 `seo``organization` 嵌套对象)。
### data/home.json
```json
{
"announcement": "一条展示用公告文案。",
"eyebrow": "SECTION KICKER",
"heroTitle": "首屏主标题,\n可含换行",
"heroDescription": "首屏描述。",
"primaryAction": "主按钮",
"secondaryAction": "次按钮",
"metrics": [{ "value": "10年+", "label": "行业积淀" }],
"services": [{ "number": "01", "title": "服务一", "description": "服务描述。" }],
"advantages": [{ "title": "卖点", "description": "卖点描述。" }]
}
```
`services` 数组契约:`{number,title,description}`(内核 Services.astro 的形态;自定义组件可改字段名,但数组名必须是 `services`)。
### pages/index.astro 模式
```astro
---
import BaseLayout from "../layouts/BaseLayout.astro";
import Header from "../components/Header.astro";
import Hero from "../components/Hero.astro";
// ... 其余区块组件
import Footer from "../components/Footer.astro";
import site from "../data/site.json";
---
<BaseLayout title={site.seo.defaultTitle} description={site.seo.description} canonicalPath="/">
<Header />
<main id="top">
<Hero />
<!-- Services / Advantages / About / Contact -->
</main>
<Footer />
</BaseLayout>
```
直接用内核的 `src/layouts/BaseLayout.astro`(不覆盖即回退内核):它已接好 SeoHead。注意其 `<body>` 会输出 `data-hero/density/radius` 属性,设计包模版这三个值为空——样式不要依赖这些属性;若需自己的 body 数据属性,拷贝 BaseLayout 进 `src/layouts/` 自行改造。
### pages/articles/index.astro 模式
```astro
---
import BaseLayout from "../../layouts/BaseLayout.astro";
import Header from "../../components/Header.astro";
import Footer from "../../components/Footer.astro";
import { entryPath, publishedArticles } from "../../_platform/content/public-content";
import { breadcrumbSchema, webPageSchema } from "../../_platform/seo/schema";
import { withBase } from "../../_platform/seo/metadata";
const articles = await publishedArticles();
const title = "文章";
const description = "行业洞察、实践经验与最新动态。";
---
<BaseLayout title={title} description={description} canonicalPath="/articles/" schemas={[
webPageSchema({ name: title, description, path: "/articles/" }),
breadcrumbSchema([{ name: "首页", path: "/" }, { name: title, path: "/articles/" }]),
]}>
<Header />
<main class="articles-main">
<div class="container">
<h1>{title}</h1>
{articles.length ? articles.map((article) => (
<article class="article-card">
<h2>{article.data.title}</h2><p>{article.data.summary}</p>
<a href={withBase(entryPath("articles", article.id))}>阅读全文 →</a>
</article>
)) : <p>文章正在准备中。</p>}
</div>
</main>
<Footer />
</BaseLayout>
```
### pages/articles/[...id].astro 模式
照抄内核 `astro-template/src/pages/articles/[...id].astro`,仅把 `ContentLayout` 换成自己的布局(含模版 Header/Footer),保留:`getStaticPaths` 的 id 去扩展名逻辑、`render(entry)``entry.data.seo.*` 优先级、articleSchema/breadcrumbSchema、publishedTime/updatedTime/noindex 传递。
### 文章 entry 数据结构(content 集合 schema 已定,直接用)
`entry.data``title, summary, cover?, author?, tags[], publishedAt?, updatedAt?, seo.{title?, description?, ogDescription?, ogImage?, noindex}`
## 4. 常见坑
1. **多页模版只取首页**:about/contact 等子页不搬运,把设计语言揉进首页区块与文章页。
2. **JS 交互**:Astro 默认零 JS。源模版的轮播/手风琴等交互,优先用 CSS/细节标签实现;必须 JS 时用 `<script>` 内联(is:inline),不要引外部构建管线。
3. **图片路径**`src/assets/` 走 Astro 构建优化;`public/images/` 为平台文章图片目录,装饰图可放但注意命名前缀(如 `deco-`)避免与文章图混。
4. **base 路径**:站内链接与 public 资源一律 `withBase()``../_platform/seo/metadata`),站点可能部署在子路径。
5. **主题切换自测**:改 `defaultThemeId` 为对比色(如暖色系)重建预览,确认没有写死色值漏网——写死主色是最常见违约。
6. **announcementBar**:预览默认开启公告栏展示,Header 需按内核模式用 `design.layout.announcementBar && home.announcement` 条件渲染,并采用「流式横幅 + 头部 `top: 100%`」结构(参照内核 Header.astro,勿用 absolute top:0 压盖)。
#!/usr/bin/env python3
"""
Example helper script for import-page-template
This is a placeholder script that can be executed directly.
Replace with actual implementation or delete if not needed.
Example real scripts from other skills:
- pdf/scripts/fill_fillable_fields.py - Fills PDF form fields
- pdf/scripts/convert_pdf_to_images.py - Converts PDF pages to images
"""
def main():
print("This is an example script for import-page-template")
# TODO: Add actual script logic here
# This could be data processing, file conversion, API calls, etc.
if __name__ == "__main__":
main()
#!/usr/bin/env node
// 设计包契约体检:node validate-design-package.mjs <模版版本目录>
// 例:node validate-design-package.mjs site-kit/page-templates/aurora-consulting/1.0.0
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
import path from "node:path";
const root = process.argv[2];
if (!root) {
console.error("用法:node validate-design-package.mjs <模版版本目录>");
process.exit(2);
}
if (!existsSync(root) || !statSync(root).isDirectory()) {
console.error(`目录不存在:${root}`);
process.exit(2);
}
const errors = [];
const warnings = [];
const fail = (message) => errors.push(message);
const warn = (message) => warnings.push(message);
const read = (file) => readFileSync(path.join(root, file), "utf8");
const has = (file) => existsSync(path.join(root, file));
// 1. manifest 必填与目录一致性
let manifest;
try {
manifest = JSON.parse(read("manifest.json"));
} catch {
fail("manifest.json 缺失或不是合法 JSON");
}
if (manifest) {
const required = ["id", "version", "name", "description", "industries", "status", "compatibleKernel", "defaultThemeId", "defaultFontId"];
for (const key of required) if (manifest[key] == null) fail(`manifest.json 缺少必填字段:${key}`);
for (const key of ["accent", "surface", "mode"]) if (manifest.preview?.[key] == null) fail(`manifest.json 缺少 preview.${key}`);
const segments = path.resolve(root).split(path.sep);
if (manifest.id && manifest.id !== segments.at(-2)) fail(`manifest.id(${manifest.id})与目录名(${segments.at(-2)})不一致`);
if (manifest.version && manifest.version !== segments.at(-1)) fail(`manifest.version(${manifest.version})与目录名(${segments.at(-1)})不一致`);
if (manifest.status && !["draft", "testing", "published", "retired"].includes(manifest.status)) fail(`manifest.status 非法:${manifest.status}`);
}
// 2. 必备文件
for (const file of ["src/pages/index.astro", "src/pages/articles/index.astro", "src/pages/articles/[...id].astro", "src/data/site.json", "src/data/home.json"]) {
if (!has(file)) fail(`缺少必备文件:${file}`);
}
// 3. 禁止自带
for (const entry of ["src/_platform", "src/content.config.ts", "src/styles/theme.css", "src/styles/fonts.css"]) {
if (has(entry)) fail(`禁止自带:${entry}(平台内核/生成物)`);
}
for (const entry of ["astro.config.mjs", "package.json"]) {
if (has(entry)) fail(`禁止自带构建配置:${entry}`);
}
// 4. 数据契约
try {
const site = JSON.parse(read("src/data/site.json"));
if (typeof site.seo !== "object" || site.seo === null) fail("src/data/site.json 缺少 seo 嵌套对象(物化会原地改写 seo.*)");
if (typeof site.organization !== "object" || site.organization === null) fail("src/data/site.json 缺少 organization 嵌套对象");
} catch (error) {
if (has("src/data/site.json")) fail(`src/data/site.json 不是合法 JSON:${error.message}`);
}
try {
const home = JSON.parse(read("src/data/home.json"));
if (!Array.isArray(home.services)) fail("src/data/home.json 必须含 services 数组(建站时注入用户核心服务)");
} catch (error) {
if (has("src/data/home.json")) fail(`src/data/home.json 不是合法 JSON:${error.message}`);
}
// 5. 默认视觉引用存在性(site-kit 根 = 版本目录的上三级)
const siteKitRoot = path.resolve(root, "../../..");
try {
const families = JSON.parse(readFileSync(path.join(siteKitRoot, "theme-families.json"), "utf8"));
const themeIds = families.flatMap((family) => family.variants.map((variant) => variant.id));
if (manifest?.defaultThemeId && !themeIds.includes(manifest.defaultThemeId)) fail(`defaultThemeId 不存在于 theme-families.json:${manifest.defaultThemeId}`);
} catch {
warn("未能读取 theme-families.json,跳过 defaultThemeId 校验");
}
try {
const fonts = JSON.parse(readFileSync(path.join(siteKitRoot, "fonts.json"), "utf8"));
if (manifest?.defaultFontId && !fonts.some((font) => font.id === manifest.defaultFontId)) fail(`defaultFontId 不存在于 fonts.json:${manifest.defaultFontId}`);
} catch {
warn("未能读取 fonts.json,跳过 defaultFontId 校验");
}
// 6. token 契约(启发式警告)
const walk = function* (dir) {
if (!existsSync(dir)) return;
for (const entry of readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) yield* walk(full);
else yield full;
}
};
const hexPattern = /#[0-9a-fA-F]{3,8}\b/;
const fontFamilyPattern = /font-family\s*:[^;]*?(?!var\()[A-Za-z"']/;
for (const file of walk(path.join(root, "src"))) {
if (!/\.(css|astro)$/.test(file)) continue;
const content = readFileSync(file, "utf8");
const rel = path.relative(root, file);
for (const [index, line] of content.split("\n").entries()) {
if (hexPattern.test(line) && !line.trim().startsWith("//") && !line.includes("var(")) {
warn(`${rel}:${index + 1} 疑似写死色值:${line.trim().slice(0, 80)}`);
}
if (fontFamilyPattern.test(line) && !line.includes("var(--font-")) {
warn(`${rel}:${index + 1} 疑似写死字体:${line.trim().slice(0, 80)}`);
}
}
}
// 输出
for (const message of warnings) console.warn(`⚠️ ${message}`);
if (errors.length) {
for (const message of errors) console.error(`❌ ${message}`);
console.error(`\n体检未通过:${errors.length} 个错误,${warnings.length} 个警告`);
process.exit(1);
}
console.log(`✅ 体检通过(${warnings.length} 个警告)`);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment