[email protected]
美国加州 Alhambra·服务全球企业
首页/文档
技术文档

iHomepage CMS 文档

iHomepage CMS 官方技术文档。一套开放的网站内容管理底座:自由的内容模型、原生多语言、SEO 友好、模板化渲染,并支持 AI Agent 接入。涵盖快速开始、核心概念、设计思想、模板、路由、静态化、后台与 API、配置参考与上线清单。

iH
iHomepage CMS 团队
更新于 2026-09-28 · 21 个章节 · 44 分钟阅读

iHomepage CMS 是一套开放的网站内容管理底座。它只提供最基础、最稳定的机制——一张内容表、两种关系、一套模板规则——其余全部交给你定义,因此比同类 CMS 拥有更高的自由度。多语言、SEO 友好、模板化是它与生俱来的能力,而不是插件;统一、可推断的接口也让 AI Agent 可以直接接入,替你建站和维护内容。

本文面向两类读者:建设和运营网站的团队,以及替团队完成建站工作的 AI Agent。读完你将知道:如何从零搭起一个站点、内容应该怎样组织、模板怎么写、有哪些开箱即用的能力,以及使用这套 CMS 必须遵守的规范。

简介

iHomepage CMS 是什么

iHomepage CMS 是一套典型的网站内容管理系统,也是一个开放的底座:

  • 底座提供的能力:内容存储、页面路由、模板渲染、静态化、多语言、SEO 基础设施、用户登录、表单、评论、支付和内容管理后台。统一维护、持续升级。
  • 网站自己决定的部分:内容结构、模板、页面设计、配置,以及任何网站独有的功能。

你不需要部署服务器、设计数据库或编写后台,只需要定义内容、编写模板、录入内容。

核心特性

特性说明
高自由度内容类型、内容之间的关系、页面长什么样全部由你定义;新增一种内容不需要改数据库,也不需要写插件
原生多语言语言前缀、译文分组、hreflang、语言切换全部内置,配置一项即可启用
SEO 友好规范地址、canonical、路径式分页、文章目录锚点、静态化,默认就对搜索引擎友好
模板化每个页面都由 Twig 模板渲染,模板可以放在文件里,也可以在后台在线编辑
AI / Agent 接入内置 MCP 服务,Agent 登录授权后用自然语言管理网站;内容模型可推断,模板与内容都是纯文本
开箱即用用户、表单、评论、点赞收藏、邮件订阅、在线支付、内容管理后台均已内置

与传统 CMS 的区别

以 WordPress 这类传统 CMS 为参照:

传统 CMS(如 WordPress)iHomepage CMS
新增内容类型注册自定义文章类型,通常要写代码或安装插件起一个新的 taxonomy 值,配一个同名模板
内容之间的关系分类之外的关联通常依赖插件或自定义字段内置多对多关联,关系含义由两端类型决定
自定义字段常借助字段类插件meta_json,模板里直接读取
多语言需要安装多语言插件原生支持
SEO常借助 SEO 插件补全默认内置
页面外观在主题框架内定制模板完全由你编写,没有主题框架约束
性能常借助缓存插件内置静态化,页面以静态文件直接返回
AI 接入视插件而定内置 MCP 服务,OAuth 授权即可接入,文档同时面向 Agent 编写

适合做什么

  • 企业官网、品牌站、外贸独立站
  • 博客、资讯站、知识库、文档站
  • 产品目录、案例库、下载中心
  • 多语言站点
  • 带会员、表单收集和在线支付的轻电商站点

本文结构

部分内容
快速开始从零搭起一个可访问的站点
核心概念 / 设计思想理解这套 CMS 为什么这样设计
内容模型 / 模板 / 路由日常开发最常用的三块
多语言 / SEO / 静态化 / 用户与支付 / AI 接入内置特性
后台 / API / 配置参考查阅用的参考资料
安全 / 职责划分与规范 / 扩展使用这套 CMS 必须遵守的规则
上线清单 / 常见问题 / 术语表收尾与排查

快速开始

第一步:确定站点形态

不是每个网站都需要 CMS。先判断你的站点属于哪一种:

形态适合是否需要 CMS 内容库
纯静态站几个固定页面,很少更新不需要
静态页面 + 独立应用在线工具、计算器、SaaS 前台不需要
CMS 内容站文章、产品、案例、文档等持续增加、需要管理的内容需要
混合站首页和工具自己做,博客、产品、文档交给 CMS需要

混合站是最常见的形态:你自己写的页面和应用永远优先,CMS 负责承接内容类页面。

第二步:了解站点目录

每个网站有一个属于自己的站点目录:

你的站点/
├── public/          公开文件:静态页面、CSS、JS、图片、字体、前端应用的构建产物
├── templates/       (可选)Twig 文件模板
└── db/
    └── cms.db       (可选)站点的 CMS 内容库:内容、模板、配置、用户、表单、订单
  • public/ 里的文件由服务器直接返回,访问地址与文件路径一一对应。
  • 模板既可以放在 templates/ 目录,也可以存进内容库(在后台编辑),二选一即可。
  • 内容库使用平台提供的标准结构创建,不要用空文件或复制别的网站的库来代替。
  • db/ 下只有 cms.db 会被 CMS 当作内容库;你自己的业务数据库起别的名字,CMS 不会读取。

第三步:写基础配置

在后台「站点设置」中填写(或写入配置表):

配置示例说明
site_nameAcme站点名,会拼进页面标题
domainwww.acme.com规范域名
schemehttps协议
home_title / home_description首页的标题和描述

完整配置项见「配置参考」。

第四步:写第一个模板

创建一个页头片段 header.html:

<!doctype html>
<html lang="{{ SITE.html_lang|default('en') }}">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>{{ base.title }}</title>
  <meta name="description" content="{{ base.description|e }}">
  <link rel="canonical" href="{{ base.url }}">
  <link rel="stylesheet" href="/css/site.css">
</head>
<body>

首页模板 home.html:

{{ include('header.html') }}
<main>
  <h1>{{ SITE.site_name }}</h1>
  {% set latest = list_post({taxonomy: 'article'}, 6) %}
  {% for item in latest.list %}
    <a href="{{ item.url }}">{{ item.title }}</a>
  {% endfor %}
</main>
{{ include('footer.html') }}

文章模板 article.html:

{{ include('header.html') }}
<article>
  <h1>{{ the.title }}</h1>
  <div class="content">{{ the.body|raw }}</div>
</article>
{{ include('footer.html') }}

第五步:录入第一篇内容

在后台新建一条内容:

字段值
标题Hello World
类型(taxonomy)article
地址(url)/articles/hello-world/
状态已发布

访问 https://你的域名/articles/hello-world/,CMS 会用 article.html 渲染它。首页的列表里也会出现这一篇。

第六步:接入后台与用户面板

给登录入口加一个 class 即可:

<a href="#" class="user-panel-login-title">Login</a>

CMS 会在每个页面自动引入 /init.js,它发现页面上有用户面板的元素时就加载面板,不需要再手写脚本标签。/init.js 同时负责访问统计,统计结果显示在后台仪表盘。两项都可以用配置项调整:

配置项取值
cms_js_panelauto(默认,页面上有面板元素才加载)、on(每个页面都加载)、off(不加载)
cms_js_stats默认开启;设为 0 关闭访问统计

访客点击后弹出登录框;站点管理员登录后可以切换到内容管理后台。每个站点的第一个管理员由平台开通。

到这里,一个可以持续运营的内容站就搭好了。接下来建议按顺序读「核心概念」和「内容模型」。

核心概念

请求是怎样被处理的

访客请求 https://你的域名/某个地址
  ↓
站点 public/ 里有这个文件吗?
  ├─ 有:直接返回(静态文件,最快)
  ↓ 没有
交给 CMS
  → 是平台接口(如 /user/api/login)?交给对应接口
  → 站点有内容库吗?没有:返回 404
  → 这个地址设置了跳转?跳到目标地址
  → 按地址查找内容或模板,渲染页面返回

三个要点:

  1. 你的文件永远优先。 放进 public/ 的文件一定先被命中,CMS 不会覆盖它。
  2. CMS 是内容兜底,不是应用容器。 已经能独立运行的页面和应用,不需要为了“统一”迁入 CMS。
  3. 没有内容库的站点返回 404 是正常结果,不需要为了消除 404 去建一个空库。

一切皆内容

在 iHomepage CMS 里,文章、产品、栏目、标签、案例、下载、门店、历史版本……全部是“内容”,存在同一张内容表 post 里,用 taxonomy(类型)区分。

两种关系

关系基数字段例子
隶属(层级)一对一parent_id文章属于某个栏目;子栏目属于父栏目
关联多对多关联表 post_post文章有多个标签;产品在多个门店有售

模板决定外观

CMS 对每一条内容做完全相同的事:把内容本身、它的父级、它的关联、挂在它下面的列表都准备好,交给模板。这条内容渲染成详情页还是列表页,由模板决定,CMS 不做判断。

地址即入口

每条内容有一个规范地址(url)。访客、搜索引擎、静态化、站内链接都围绕这个地址工作。

设计思想

理解下面几条,你就能推断出绝大多数问题的答案。

一张表承载所有内容

新增一种内容类型、一种关系,都不需要修改数据库结构:新类型只是一个新的 taxonomy 值,新关系只是两种类型之间的一条关联。所以结构始终稳定,底座升级时你的网站直接受益。

机制归平台,语义归网站

平台只提供机制:一张内容表、两种关系、一套模板查找规则、一条渲染路径。“什么算栏目”“这条关联代表什么”“页面长什么样”全部由网站定义。平台不会也不应该认识你的业务。

网站优先

网站自己的文件、页面和应用始终优先于 CMS。CMS 只处理网站没有覆盖的地址。

通用性

底座只沉淀通用的能力。进入底座的每一项能力都必须具备通用性,并且默认不改变任何现有网站的行为。平台不提供只对某个网站生效的开关、例外或特殊分支。

向后兼容

平台升级默认不改变现有页面的输出。旧写法被新写法取代时,旧写法继续可用并标注为“已废弃”,不强制网站立即修改。

默认安全

富文本在保存时清洗、凭据不回显、匿名上传默认不公开、管理权限每次请求实时校验——安全措施做在平台里,网站不需要各自实现。

内容模型

taxonomy:内容类型

taxonomy 的取值由网站自由定义,平台不预设任何值。常见约定:

article / post   文章        news      新闻        blog      博客
product          产品        service   服务        project   项目
case             案例        document  文档        download  下载
dir              栏目        tag       标签        version   历史版本

新增一种内容类型 = 起一个新的 taxonomy 名 + 写一个同名模板。 不需要其它任何操作。

parent_id:层级

parent_id 指向这条内容的上一级,只表达一对一的隶属:

  • 栏目 → 上级栏目:多级菜单
  • 文章 → 所属栏目:面包屑导航
  • 历史版本 → 原内容:版本链

模板中用 parent_of(the) 取父级。

关联:多对多

post_post(post_id, child_post_id, weight)
  • 方向约定:post_id 是主体,child_post_id 是从属。 例如“栏目 → 文章”“文章 → 标签”。方向写反,同样的数据含义就反了。
  • 关系的含义由两端的类型决定,不需要单独记录关系类型:
(dir, article)       文章在这个栏目下        (article, tag)       文章的标签
(product, service)   产品可选的增值服务      (store, product)     门店在售的产品
(guide, download)    指南的配套下载          (course, teacher)    课程的讲师
  • weight 是这条内容在这组关系中的排序位次。同一篇文章可以在不同栏目里排不同的位置。

模板中用 related(the, 'tag') 取“它关联了谁”,用 list_related(id, …) 取“谁挂在它下面”。

一对一用 parent_id,多对多用关联。 不要把同一种关系同时写在两处。

组合示例

  • 企业站:dir 栏目 + article 文章(归入栏目)+ case 案例(关联 industry 行业)
  • 电商站:product 挂在 dir 下,打若干 tag,关联可选 service,在多个 store 有售
  • 文档站:guide 归入分类,关联 download 附件,用 version 记录历史版本
  • 社区:thread 属于 topic,关联 tag

标准字段

字段说明
title标题
description摘要,同时作为页面描述
keywords关键词
body正文 HTML
url规范地址,建议总是显式填写
taxonomy内容类型
template指定模板;为空时使用 taxonomy 同名模板
status2 为已发布;其它值前台不可见
parent_id父级内容
pic_url封面图
author作者
publish_time / update_time发布与更新时间
price售价,单位是分(填 5999 表示 $59.99)。购物车、下单、订单全链路统一按分
weight全局排序权重
is_top / is_headline / is_featured / is_home置顶、头条、推荐、首页展示标记
click_count / like_count / fav_count浏览、点赞、收藏计数
lang / slug多语言:语言码与译文分组键
meta_json扩展字段(JSON)

meta_json:扩展字段

标准字段覆盖不到的数据——产品规格、FAQ 列表、价格区间、外部链接、按钮文案——一律存进 meta_json:

{"specs": {"weight": "1.2kg", "size": "30×20cm"}, "faq": [{"q": "包邮吗?", "a": "满 99 包邮"}]}

模板中直接读取,无需解析:

{{ the.meta_json.specs.weight }}
{% for item in the.meta_json.faq %}<dt>{{ item.q }}</dt><dd>{{ item.a }}</dd>{% endfor %}

所有名称以 _json 结尾的字段都会被自动解析。空值解析为空数组,格式错误的 JSON 保留为原字符串,页面不会因此报错。

多图:后台内容编辑器里的「多图」(常用作内容页轮播图)存在 meta_json.gallery,是图片地址的数组,其它键不受影响:

{% for src in the.meta_json.gallery %}<img src="{{ src }}" alt="">{% endfor %}

不要为了一个新字段去修改数据库结构。 如果某个字段所有网站都会用到,请按「扩展与需求反馈」提出。

内容状态

status含义
2已发布:可访问、出现在列表中、会被静态化
0 等其它值草稿或下线:前台返回 404,已生成的静态页自动移除
-1已删除(可恢复,关联关系保留)

模板

模板使用 Twig 语法。

模板来源

按以下顺序查找,找到即停止:

  1. 站点 templates/ 目录下的 {名称}
  2. 站点 templates/ 目录下的 {名称}.html
  3. 内容库模板表中名称为 {名称} 或 {名称}.html 的模板

同一个模板不要两处都放:文件模板会遮住后台里的同名模板,你在后台怎么改都不会生效。后台模板的引擎必须是 twig。

模板选择规则

页面使用的模板
首页 /home
搜索页 /searchsearch
内容页内容的 template 字段 → 否则 taxonomy 同名模板 → 否则 post
找不到页面404(可选;没有时输出纯文本 404)

布局与片段

方式一:include 片段。 每个页面模板自己引入页头页尾:

{{ include('header.html') }}
……页面内容……
{{ include('footer.html') }}

方式二:base_layout。 创建 base_layout.html,用字面占位符 {{BODY}} 表示页面模板插入的位置:

<!doctype html>
<html lang="{{ SITE.html_lang }}">
<head><title>{{ base.title }}</title></head>
<body>
  {{ include('header.html') }}
  {{BODY}}
  {{ include('footer.html') }}
</body>
</html>

存在 base_layout.html 时,页面模板自动套进去;页面模板本身已经是完整 HTML 文档时则不套。

模板变量

变量内容
SITE站点配置:site_name、domain、scheme、logo、menu、URL_ENDING、home_title、home_description;多语言站点另有 lang、lang_prefix、html_lang、languages
base当前页的 title、description、keywords、url(canonical 完整地址,分页页已带页码)
the当前内容,*_json 字段已解析
the.term父级内容
the.taglist关联的全部内容
the._toc / the._toc_html正文二级标题生成的目录(数组 / 现成 HTML),正文中已插入对应锚点
the.comments已审核的评论
LIST挂在当前内容下的内容,已分页:{list, total}
TERMS地址查询参数中的筛选项
GET当前请求的查询参数
PAGE当前页码;路径式分页 /blog/page/2/ 不在查询参数里,只能从这里取

模板函数

函数作用
include(name, vars)引入模板片段;第二个参数是传给它的额外变量
list_post(where, limit, page, order)按条件查询内容,返回 {list, total}
list_related(ids, limit, page, order, where)挂在指定内容下面的内容;多个 id 取交集,分组写法见下
related(item, taxonomy)这条内容关联了哪些内容,可按类型过滤
parent_of(item)父级内容
translations(item)这条内容的各语言版本
make_paginate(total, page, size)生成分页 HTML;page 不传就用当前页码
get_options(name)读取一项站点配置

示例:

{# 栏目页:列出栏目下的文章并分页 #}
{% for item in LIST.list %}
  <a href="{{ item.url }}">{{ item.title }}</a>
{% endfor %}
{{ make_paginate(LIST.total) }}

{# 首页:6 个推荐案例,按权重排序 #}
{% set cases = list_post({taxonomy: 'case', is_featured: 1}, 6, 1, 'weight') %}

{# 详情页:标签与面包屑 #}
{% for tag in related(the, 'tag') %}<a href="{{ tag.url }}">{{ tag.title }}</a>{% endfor %}
{% set parent = parent_of(the) %}
{% if parent %}<a href="{{ parent.url }}">{{ parent.title }}</a>{% endif %}

{# 多重筛选:同时属于栏目 3 和标签 7 的内容 #}
{% set items = list_related([3, 7], 12, 1) %}

{# 分组筛选:属于领域 101 或 102,并且属于片区 205 的中文内容 #}
{% set items = list_related([[101, 102], [205]], 12, 1, [], {taxonomy: 'firm', lang: 'cn'}) %}

list_related 的 ids 有两种写法:扁平的 [3, 7] 表示同时挂在每一个下面;分组的[[101, 102], [205]] 表示组内「或」、组间「且」,两种可以混用。空组会被忽略。第五个参数 where 是附加条件,写法与 list_post 相同。

排序

list_post、list_related 的第四个参数用于排序,支持四种写法:

{{ list_related(8, 10, 1, 'weight') }}                               {# 单字段,默认降序 #}
{{ list_related(8, 10, 1, ['weight', 'publish_time']) }}             {# 多字段 #}
{{ list_post({taxonomy: 'product'}, 12, 1, {'price': 'ASC'}) }}      {# 指定方向 #}
{{ list_related(8, 10, 1, [{'weight': 'DESC'}, {'title': 'ASC'}]) }} {# 有序列表 #}
  • 默认排序:list_post 为发布时间倒序;list_related 为创建顺序倒序。需要确定顺序时请显式指定。
  • 字段名或方向写错的排序条目会被忽略并回到默认排序,页面不会报错——写完请检查结果。

模板编写规范

  1. 不要写死内容 id。 用 taxonomy、slug 或 url 定位。id 失效时页面不会报错,只会悄悄显示空列表。
  2. 正确转义。 模板默认不自动转义。编辑器保存的正文用 {{ the.body|raw }};访客输入、查询参数等不可信数据不要加 |raw,必要时用 |e。
  3. 资源使用根路径:/css/site.css,不要写 css/site.css,否则多级地址下资源会 404。
  4. SEO 标签使用 base,不要在页头写死标题和描述。
  5. CSS、JS、robots.txt 可以存为模板:名称以 / 开头的模板(如 /app.css、/robots.txt)按原文输出,并会被静态化。站点地图不需要自己写,CMS 会自动生成(见「SEO」)。templates/ 目录中只有 css、js、json、xml、txt、ico 文件能被直接访问,布局片段不会暴露在网上。
  6. 站点名称、Logo、导航与联系方式从配置读取:用 SITE.site_name、SITE.logo、SITE.menu 和 get_options('contact_info'),不要写死在模板里。这样在后台或通过 Agent 修改设置时,全站一次生效。

路由与 URL

路由规则

请求结果
在后台「地址跳转」中启用了规则的地址跳转到目标地址(优先于下面所有规则,平台接口除外)
/首页,home 模板
/index.html301 跳转到 /
/search?keyword=关键词搜索已发布内容的标题和摘要,search 模板
/uploads/…附件
/.well-known/…域名验证与协议发现文件,在后台「.well-known」维护
/user/api/…、/user/pay/…、/cms/api/…、/mcp平台接口
/init.js页面入口脚本,CMS 自动注入每个页面
与名称以 / 开头的模板相同的地址输出该模板
既不以 / 结尾也不以 .html 结尾的地址301 补上结尾的 /
与某条内容的 url 相同渲染该内容
/{taxonomy}/{id} 等非规范地址301 跳转到该内容的规范地址
/{列表地址}/page/{n}/列表第 n 页
其它404

/mcp、/.well-known/、/init.js、/js/analysis2.js 以及上表中平台接口的地址由平台保留,不要给内容或模板使用。

URL 规范

  • 每条内容一个规范地址,写进 url 字段;其它能找到它的地址一律 301 到规范地址。
  • 全站统一一种风格:目录式 /blog/my-post/ 或文件式 /blog/my-post.html。配置项 URL_ENDING 决定未填写 url 时自动生成的地址结尾。
  • 列表页地址以 / 结尾(如 /blog/),才能使用可静态化的路径式分页。
  • 不要让目录段带 .html(如 /design/index.html/26.html)。
  • 不要用查询参数区分内容。带查询参数的地址不会被静态化。

地址跳转

内容改版、地址调整后,旧地址不要直接 404,在后台「地址跳转」里把它跳到新地址。

字段说明
来源地址站内路径,如 /old/page.html;按路径精确匹配,不含域名和查询参数,同一地址只能有一条规则
目标地址站内路径(/new/page/)或完整的 http(s):// 地址
状态码301(默认)、302、307、308
状态启用 / 停用;停用的规则不生效
备注自由填写,如改版原因

后台同时显示每条规则的命中次数和最后命中时间,方便判断旧地址是否还有流量。

  • 优先级最高:只要规则启用,这个地址就一定跳转,即使它仍对应一条已发布内容或一个模板。首页 / 和平台接口地址不能设置跳转。
  • 查询参数会带过去:访问 /old?utm_source=x 会跳到 /new?utm_source=x;目标地址自己带了查询参数时不再追加。
  • 静态文件:启用规则时,CMS 会删除该地址上由它生成的静态文件,并且之后不再为这个地址生成;你手工放在 public/ 里的同名文件不会被删除,后台会提示冲突——它不删掉,跳转就不会生效。
  • 不要形成链条或循环:目标地址本身又设置了跳转时,后台会给出警告,请直接指向最终地址。
  • 301/308 是永久跳转,浏览器和搜索引擎会长期记住;只是临时调整请用 302/307。
  • 地址跳转解决的是「这个地址已经不用了」。同一条内容的非规范地址(如 /{taxonomy}/{id})CMS 已经自动 301 到规范地址,不需要另建规则。

分页

  • 推荐路径式:/blog/page/2/;查询式 ?page=2 同样可用。
  • /blog/page/1/ 会 301 到 /blog/;超出范围的页码返回 404,不会生成空列表页。
  • 每页条数由配置项 page_size 决定(默认 15)。make_paginate 不传第三个参数时自动与之一致。
  • make_paginate 的第二个参数(当前页)也可以不传。写成 GET.page|default(1) 在路径式分页地址上会永远算成第 1 页。
  • 带筛选参数(如 ?dir=3&tag=7)时,分页链接自动使用 ?page=N。

多语言

多语言是可选特性。只有配置了 languages 的站点启用,单语言站点不受任何影响。

配置

{"default": "en", "available": {"en": "English", "cn": "中文", "es": "Español"}, "hreflang": {"cn": "zh-CN"}}

也可以简写为 ["en", "cn", "es"],第一个为默认语言。语言码只能包含小写字母、数字和 -。

URI 约定

同一篇内容在各语言下地址完全相同,只差一个语言前缀。默认语言不带前缀。

/guides/getting-started/        English(默认语言)
/cn/guides/getting-started/     中文
/es/guides/getting-started/     Español

这是多语言能正常工作的前提:语言切换和 hreflang 都直接按这条规则推导,不需要查询。

  • 各语言页面请成套创建。
  • 某个语言确实缺少这一篇时,访问带该语言前缀的地址会 302 到该语言首页,而不是 404。

内容字段

  • lang:这条内容的语言码
  • slug:译文分组键。同类型、同 slug、不同 lang 的内容互为译文

渲染一条内容时,按它自己的 lang 决定页面语言,而不是按地址前缀。<html lang>、hreflang、语言切换器和 SITE.lang_prefix 都由它推导,所以 lang 填错,这些会整页一起错。

字段约束

启用了多语言的站点,凡是有 url、会渲染成页面的内容,必须满足:

  1. lang 必填,且必须是 available 里的语言码。 默认语言也要写明(如 en),不要留空。留空的内容虽然按默认语言渲染,但 list_post({lang: SITE.lang}) 这类按语言过滤的列表会漏掉它。
  2. lang 与地址前缀一致。 非默认语言的地址以 /{lang}/ 开头,默认语言的地址不带前缀。lang 为空或写成默认语言、地址却在 /cn/ 下,页面会被当成默认语言渲染,语言切换和 hreflang 会拼出 /cn/cn/... 这样的错误地址。
  3. 同一组译文 taxonomy 相同、slug 相同,去掉语言前缀后地址相同。 slug 在同一 taxonomy、同一 lang 内唯一。

没有 url、只作为筛选项或数据使用的内容不受第 2 条约束,但建议同样填写 lang,便于按语言取用。

单语言站点(未配置 languages)的 lang、slug 保持为空即可。

以后给单语言站点启用多语言时,先把已有内容的 lang 补成默认语言码,再上线其他语言。

模板写法

<html lang="{{ SITE.html_lang }}">

{# hreflang #}
{% for t in translations(the) %}
  <link rel="alternate" hreflang="{{ t.hreflang }}" href="{{ t.url }}">
{% endfor %}

{# 语言切换器 #}
{% for l in SITE.languages %}
  {% if l.current %}<strong>{{ l.label }}</strong>{% else %}<a href="{{ l.prefix }}/">{{ l.label }}</a>{% endif %}
{% endfor %}

{# 按当前语言列内容 #}
{{ list_post({taxonomy: 'guide', lang: SITE.lang}, 10) }}

{# 站内链接带语言前缀 #}
<a href="{{ SITE.lang_prefix }}/about/">About</a>

单语言站点中 SITE.lang 和 SITE.lang_prefix 为空、translations() 返回空数组,同一份模板可以同时用于单语言和多语言站点。

SEO

iHomepage CMS 把搜索引擎友好作为默认行为,而不是需要额外安装的插件。

内置能力

能力说明
标题、描述、关键词每个页面自动生成 base.title / base.description / base.keywords;内容页标题为「标题 | 站点名」
Canonicalbase.url 是完整规范地址,分页页自动带页码,避免第 2 页起被视为重复页
唯一地址非规范地址 301 到规范地址;缺少结尾 / 的地址 301 补全;/index.html 301 到 /
路径式分页/blog/page/2/ 可被抓取、可静态化
无空页面超出范围的分页返回 404,不产生无限的空列表页
多语言hreflang、<html lang>、语言前缀约定
文章目录正文二级标题自动生成目录与锚点(the._toc_html)
静态化页面预先生成为静态文件,响应快
站点地图自动生成 /sitemap.xml,每天刷新,见下文
404 记录找不到的地址会被记录,可在后台查看并修复
附件图片等附件拥有稳定的 /uploads/ 地址

你需要做的

  • 页头使用 base.title、base.description、base.url,不要写死。
  • 每条内容填写 title、description,并显式填写简洁、稳定的 url。
  • 正文用二级、三级标题组织结构;一个页面只有一个 <h1>(通常由模板输出标题)。
  • 图片填写 alt。
  • 通过名称为 /robots.txt 的模板提供 robots,并在其中写上 Sitemap: https://你的域名/sitemap.xml。
  • 多语言站点输出 hreflang。
  • 地址一经发布不要随意更改;必须更改时保证旧地址有跳转。

站点地图

CMS 按内容生成站点地图文件,不需要安装插件或手工维护:

  • /sitemap.xml 是索引,指向 /sitemap_1.xml、/sitemap_2.xml……每个分片最多 50000 个地址。
  • 收录首页和全部已发布内容(含栏目、标签等所有内容类型)的地址;每条带 <lastmod>,取内容的更新时间。
  • 不收录:有跳转规则的地址、指向站外的地址、分页页。
  • 每天自动重新生成一次,与是否开启静态化无关。内容改动想立即反映,在后台「工具」页点「重建站点地图」。
  • 站点地图是生成好的文件,不在访问时临时计算——内容量很大的站点也不会因为抓取 sitemap 拖慢网站。新站在第一次生成之前访问 /sitemap.xml 会得到 404。
  • 站点不得写 /sitemap.xml 与 /sitemap_{数字}.xml:这两类文件名归 CMS。站点脚本、模板或上传写进去的同名文件都会被 CMS 直接覆盖,同名模板也不会生效。
  • 内容之外的页面(例如站点自己生成的聚合页)如果需要站点地图,请站点自己用别的文件名生成(如 /sitemap_brands.xml),并自己提交给搜索引擎(在 robots.txt 里加一行 Sitemap:,或在站长平台提交)。CMS 不管这些文件,也不会把它们挂进 /sitemap.xml。

静态化与性能

工作方式

CMS 会把页面预先生成为静态文件放进站点的 public/,访问时由服务器直接返回,不经过数据库和模板渲染。

  • 后台保存时自动更新:保存一条内容,会重新生成它的新旧地址(含分页)、首页、它的父级,以及关联到它的主体页面。
  • 全站重建:生成全站所有可推导的页面。
  • 访客访问时补上缺的页面:一个已发布的地址还没有静态文件时,第一次访问由 CMS 动态生成并同时落盘,之后由服务器直接返回。
  • 三种方式生成的文件完全相同:它们用的是同一个发布方法、同一套渲染。

开启

  • 将配置项 static_publish 设为 yes(未设置即关闭)。
  • 站点被屏蔽(site_blocked 为 yes)时不会生成。

生成范围

  • 首页
  • 每条已发布内容的地址,以及它们的路径式分页
  • 名称以 / 开头的资源模板(CSS、JS、robots.txt 等)
  • 允许公开访问的附件

不在范围内:带查询参数的地址(搜索、多重筛选组合),它们始终动态生成。

需要注意

  • 修改模板或站点设置后,只有首页会自动更新,其它页面需要一次全站重建才会生效。
  • CMS 只管理自己生成的文件。 你手工放置的同名文件、或手工改过的生成文件,CMS 会跳过而不会覆盖。反过来,请不要手工修改 CMS 生成的静态页——改了之后它不会再被更新。
  • 设置了跳转的地址不会被生成,已生成的文件在启用跳转时删除(见「地址跳转」)。

用户、表单与支付

以下能力由平台提供,网站直接调用即可,不需要也不应该自行实现一套。

用户

  • 注册、登录(账号 / 邮箱 / 手机)、Google 登录、退出
  • 个人资料、修改密码、忘记密码(邮箱验证码)
  • 换绑手机与邮箱(验证码)
  • 登录状态使用第一方 cookie,接口与站点同域,无需跨域配置

互动

  • 评论:登录用户提交,后台审核后显示在 the.comments
  • 点赞与收藏:幂等,计数自动维护
  • 邮件订阅:按邮箱去重

表单

/user/api/form_submit 接收任意字段的表单(联系我们、询盘、报名……):

  • 提交内容保存在后台「表单」中
  • 自动给站长(配置项 contact_info.email)发送通知邮件
  • 可选给提交人发送回执(配置项 form_receipt)。回执优先用本站的 sendmail_smtp 发送,没配置就用平台邮箱
  • 邮件发送失败不影响提交成功

文件上传

  • 支持 jpg、jpeg、png、gif、webp、pdf,不支持 svg
  • 登录用户上传的文件立即可访问;匿名上传的文件默认不公开

下单

不接在线支付也可以下单,适合「先下单、后人工确认」的站点:

  1. 前端调用 /user/api/order_submit,传入商品明细(标题、单价、数量,可带内容 id)与联系人、收货信息
  2. 订单以待处理(pending)状态保存,可在后台「订单」中查看和改状态
  3. 站长收到新订单通知邮件,下单人收到回执(同表单回执的配置项 form_receipt)
  4. 用户在「我的订单」中查看

订单明细是下单当时的商品快照(标题、单价、数量):商品事后改名、改价、下架都不影响历史订单。明细里的内容 id 只作为备查线索,用来统计某个商品卖了多少,可以没有。传了内容 id 时,单价以服务端 post.price 为准;与前端传来的价格不一致时按服务端价格计算,并把差异记在订单备注里。

配置项 order_auto_pay 设为 yes 表示下单后直接进入支付流程(在线支付分支尚未开放);缺省或 no 时就是上面这套「等待处理」的流程,不会跳转支付。

支付

基于 Stripe Checkout(金额同样以分为单位):

  1. 前端调用 /user/pay/order_create,传入商品(内容 id 与数量)
  2. 价格由服务端按内容的 price 计算,不接受前端传价
  3. 跳转到返回的 Stripe 收银台地址
  4. Stripe 回调 /user/pay/stripe_webhook 后订单变为已支付,并写入实际金额、税费和收货信息
  5. 用户在「我的订单」中查看

需要在配置项 stripe_config 中填写密钥,并在 Stripe 后台注册 Webhook 地址 https://你的域名/user/pay/stripe_webhook。税费由 Stripe Tax 按收货地址计算,需在 Stripe 后台开通。

AI 与 Agent 接入

iHomepage CMS 在设计上就考虑了 AI Agent 的使用:结构简单、行为确定、接口统一,Agent 不需要猜。

为什么适合 Agent

  • 模型可推断:一张内容表、两种关系、一条模板选择规则。读完「核心概念」,就能推断出任何页面从哪来、内容该怎么放。
  • 接口统一:所有接口使用同一种响应格式,HTTP 状态码与 code 一致;列表接口共用同一套分页、排序和筛选参数。
  • 内容与模板都是纯文本:正文是 HTML,模板是 Twig,扩展字段是 JSON,Agent 可以直接读写和比对。
  • 行为可预期:写错的筛选或排序条件会被忽略而不是报错;非规范地址统一 301;删除是可恢复的软删除。
  • 文档即规范:本文同时写给团队和 Agent 阅读,并提供中英两个版本。

Agent 能做什么

任务做法
修改网站设置(电话、Logo、菜单)update_settings;改之前先用 find_text 确认这段文字是否还写死在模板里
新增栏目与页面create_category_with_items 一步建好栏目和其中的条目
撰写、修改与翻译内容create_content、update_content、translate_content(地址与语言按多语言约定自动生成)
修改模板update_template,保存前自动检查 Twig 语法
维护 SEO通过内容工具维护标题、描述、地址与译文
读取公开内容/user/api/post_list、/user/api/post_get,无需登录

接入方式

站点内置 MCP 服务(Model Context Protocol,Claude、ChatGPT、Cursor 等主流 Agent 都支持的接入标准),不需要复制任何密钥:

  1. 管理员在后台「Agent 接入」页点一次「生成元数据」
  2. 在 Agent 里添加自定义连接器(MCP 服务器),地址填 https://你的域名/mcp
  3. Agent 会打开本站的授权页:用管理员账号登录,并勾选允许的权限
  4. 之后直接用自然语言下达任务,例如「联系电话改成 626-000-0000」「新增一个服务栏目,介绍我们的三项服务」
权限允许 Agent 做什么
读取(必需)查看内容、模板和网站设置
管理内容新建、修改、删除内容与栏目,创建译文
修改设置网站名称、Logo、联系方式、菜单
修改模板编辑数据库模板,保存前检查语法
重新生成页面按地址重新生成静态页

Agent 的修改立即生效,效果与管理员在后台保存完全相同:同样的清洗、关联同步和静态化。每次调用都会重新校验授权人的管理员身份;在后台「Agent 接入」里可以随时查看和撤销授权,撤销立即生效。短信、邮件、支付等凭据类配置对 Agent 始终不可见。

内容管理后台

进入后台

  1. 页面接入用户面板脚本(见「快速开始」第六步)
  2. 以管理员身份登录
  3. 在面板中切换到「网站内容管理」

权限:只有本站管理员可以进入。管理员身份每次请求实时校验,撤销立即生效;在 A 站登录的管理员只能管理 A 站。第一个管理员由平台开通,之后管理员可以在后台授予或收回其他用户的管理员身份。

功能

模块能力
仪表盘近 30 天访问量、访客数、新增用户、新增内容、爬虫访问
内容新建、编辑、删除(可恢复)、设置层级与关联及其排序
模板在线编辑 Twig 模板
站点设置基础信息、SEO、联系方式、各类配置
附件上传、编辑信息、删除
评论审核、编辑
表单查看、处理、删除
订单查看、更新状态
用户新建、编辑、停用、授予管理员
404 日志找不到的地址及来源
页面更新按地址重新生成静态页
数据结构检查并升级本站内容库结构
Agent 接入接入地址、生成授权元数据、查看与撤销已授权的应用
.well-known管理域名验证与协议发现文件(JSON 或纯文本)
地址跳转旧地址跳转到新地址:新建、编辑、启停、删除,查看命中次数

API 参考

约定

  • 接口地址使用相对路径,在你自己的域名下调用:https://你的域名/user/api/login
  • 参数可以通过查询串、表单或 JSON 请求体传递(JSON 优先)
  • 响应格式统一:
{"code": 200, "data": { }}
{"code": 401, "error": "login_required", "message": "Login Required"}

HTTP 状态码与 code 一致。失败时:

  • error 是稳定的错误码,程序据此判断出了什么错、怎样提示用户(比如按网站语言翻译);
  • message 是默认的英文说明,可以直接展示,但措辞可能调整,不要拿它做判断。

每个失败响应都带 error。没有专门错误码的情况按状态码给通用码:bad_request(400)、unauthorized(401)、forbidden(403)、not_found(404)、not_allowed(405)、payload_too_large(413)、unsupported_media_type(415)、too_many_requests(429)、server_error(500 等)、service_unavailable(503)。

用户接口 /user/api/* 的错误码:

分类错误码
登录状态login_required 未登录、session_expired 登录已过期、too_many_requests 请求太频繁
账号invalid_credentials 账号或密码错误、account_disabled 账号不可用、account_create_failed 创建失败、user_not_found、email_taken、mobile_taken、userid_taken、old_password_incorrect、password_too_short
验证码verification_code_required 缺验证码、sms_code_invalid、email_code_invalid、invalid_email、unsupported_area_code 不支持的手机区号
发送通道sms_not_configured、sms_send_failed、email_not_configured、email_send_failed
Google 登录google_not_configured、google_unavailable、google_credential_invalid、google_email_missing、google_email_unverified
内容与互动post_not_found、comment_empty
表单、订单与支付form_unavailable、order_unavailable、order_not_found、order_create_failed、invalid_order_items、product_unavailable、payment_not_configured、payment_failed
上传upload_failed、file_too_large、file_type_not_allowed
参数missing_parameter 缺少参数、invalid_parameter 参数不合法(具体哪一项看 message)

用户接口 /user/api/*

接口登录说明
session否当前登录状态与用户信息
join否注册
login / logout否登录 / 退出
login_google否Google 登录
get_profile / edit_profile是读取 / 修改资料
edit_passwd是修改密码
password_reset否用邮箱验证码重置密码
send_email_code / send_sms_code否发送验证码
update_email / update_mobile是换绑邮箱 / 手机
comments_submit是提交评论
like / unlike / favorite / unfavorite是点赞、收藏
like_status否批量查询点赞收藏状态与计数
subscribe否邮件订阅
form_submit否提交表单
order_submit是下单(不经在线支付,订单状态为待处理)
put_file否上传文件
post_list / post_get否已发布内容的列表与详情
orders_list / orders_get是我的订单

内容列表示例:

GET /user/api/post_list?where[taxonomy]=product&keyword=chair&page=1&page_size=20

post_list 参数:

参数说明
where按 taxonomy、parent_id、lang、slug 和推荐标记精确过滤,其它字段忽略
keyword匹配标题和描述
related可选,按关联筛选,写法同模板 list_related 的分组写法:[[101, 102], [205]] 表示挂在 101 或 102 下面,并且挂在 205 下面。空组忽略;最多 10 组、共 100 个 id
order可选,写法同模板函数的排序参数;显式指定时末尾自动补 id 倒序
page / page_size分页,page_size 最大 100

where、order、related 三种传法等价:JSON 请求体;查询串方括号写法(where[taxonomy]=firm);查询串里的 JSON 字符串(where={"taxonomy":"firm"})。JSON 写错时返回 400,不会忽略条件后返回全部内容。

传 related 时,默认顺序与模板 list_related 相同,列表页用它做勾选筛选、Ajax 刷新,结果与页面首屏同序:

POST /user/api/post_list
{"where": {"taxonomy": "firm", "lang": "cn"}, "related": [[101, 102], [205]], "page": 1, "page_size": 15}
{"code": 200, "data": {"list": [], "total": 0, "page": 1, "page_size": 20}}

支付接口 /user/pay/*

接口鉴权说明
order_create登录创建订单并返回 Stripe 收银台地址 checkout_url
stripe_webhookStripe 签名Stripe 回调

管理接口 /cms/api/*

需要本站管理员身份,供内容管理后台使用。涵盖内容、模板、配置、附件、评论、表单、订单、用户、地址跳转、统计与静态页更新。列表类接口共用分页、排序和筛选参数:

{"_page": 1, "_page_size": 20, "_order": {"id": "DESC"}, "_filter": {"status": 2, "title[~]": "关键词"}}
  • _page_size 最大 200
  • 筛选运算符:等于(无后缀)、!、>、<、>=、<=、~(包含)、!~、<>(区间)
  • 不存在的字段或不合法的条件会被忽略

配置参考

站点配置在后台「站点设置」中维护。JSON 类配置填写 JSON 文本。

基础

配置项说明默认
site_name站点名称
domain规范域名当前访问域名
scheme协议https
logoLogo 地址
menu菜单数据,供模板使用
home_title / home_description首页标题与描述
URL_ENDING自动生成地址的结尾,/ 或 .html/
page_size列表每页条数15
site_blockedyes 时屏蔽全站:所有地址只输出 site_message,相当于关站/维护模式no
site_message屏蔽时显示的公告;只在 site_blocked 为 yes 时生效,留空则显示一句通用的暂不可访问提示

特性开关

配置项说明默认
languages多语言配置(JSON),见「多语言」未启用
static_publish设为 yes 开启静态化关闭
join_need_check设为 1 时新注册用户需审核直接可用

联系与通知

配置项格式说明
contact_info{"email": "...", "name": "...", "mobile": "...", "address": "..."}联系方式;email 接收表单通知
sendmail_smtp{"host","port","secure","username","password","email","name"}本站发信账号:验证码、表单回执
form_receipt{"enabled": true, "content": "..."}表单提交后给提交人发送回执
sms_config{"apiUrl","apiKey","tplId"}短信验证码。不填就用平台共享的短信通道,只有自带通道时才需要填
user_agreement_url站内路径注册框里「用户协议」的链接,默认 /user-agreement/
privacy_policy_url站内路径注册框里「隐私政策」的链接,默认 /privacy-policy/
panel_hide_registeryes / noyes 时登录框里不显示「去注册」。给「注册前必须先走完站点自己的流程」的网站用——注册界面只由页面上的注册按钮唤起,访客不能从登录框绕过那一步。缺省允许

支付与登录

配置项格式说明
order_auto_payyes / noyes 表示下单后直接进入支付流程;缺省或 no 时订单等待人工处理,只发通知邮件
stripe_config{"secret_key","webhook_secret","success_url","cancel_url","tax_countries"}Stripe 支付;跳转地址可含 {ORDER_NO} 占位符
social_loginJSON社交登录配置

凭据类配置(SMTP、短信、Stripe、社交登录)保存后不会在后台明文回显。

安全

平台已内置以下保护,网站需要配合遵守对应的要求。

平台保护你需要做的
正文保存时按白名单清洗:script、style、iframe、form 等被移除,事件属性被去掉交互和样式写在模板里,不要放进正文。正文用 class 挂模板里的样式(排版类标签都保留 class);折叠内容用原生 <details>/<summary>
模板不自动转义不可信数据不要使用 |raw
凭据只存本站配置,不回显不要把密钥写进模板、前端代码或公开文件
管理权限每次请求实时校验,站点之间天然隔离及时收回不再需要的管理员
上传使用扩展名白名单,拒绝 SVG,匿名上传默认不公开不要另建上传接口
支付价格由服务端计算,Webhook 校验签名不要在前端计算或传递价格
登录、注册、验证码、表单接口限流不要另建登录或表单接口
删除为软删除,关联保留需要恢复时改回状态即可
静态化只写入安全的文件类型和路径不要手工修改生成的静态页

职责划分与规范

CMS 底座和基于它建设的网站各自持续迭代。清晰的边界让底座可以放心升级,也让网站可以放心开发。

边界原则

  • 平台范围内的事,网站不干涉。 内核、数据结构、渲染与路由规则、静态化机制、平台接口由平台维护。
  • 网站范围内的事,平台不干涉,只给建议。 内容、模板、设计、配置、独立功能由网站决定。
  • 拿不准一件事归谁时,先沟通,再动手。

谁负责什么

事项负责方
内容数据库的结构(表、字段、索引)平台
页面渲染、路由规则、模板函数平台
平台接口(用户、支付、管理)平台
静态化机制及其生成的文件平台
内容库里的数据:内容、模板、配置、用户、表单、订单网站
站点 public/ 中网站自己放置的文件网站
站点 templates/ 目录网站
网站自己的数据库、脚本和独立应用网站

一句话:内容库的结构归平台,内容库的数据归网站。

平台的承诺

  • 不修改网站的内容、模板和配置
  • 不覆盖、不删除不是平台生成的文件
  • 数据结构只增加不删除,升级前完整备份
  • 升级默认不改变现有页面输出;有影响时提前通知受影响的网站,并说明需要做什么
  • 发现网站的问题时给出建议,由网站决定是否修改

网站的规范

可以自由做:

  • 定义任意内容类型和内容结构(taxonomy + parent_id + 关联 + meta_json)
  • 编写任意模板,设计任意页面
  • 在 public/ 中放置自己的页面、资源和前端应用
  • 建设自己的数据库和独立功能
  • 决定是否启用静态化、多语言,以及各项配置

不要做:

  • ❌ 修改内容库的数据结构(加字段、改字段、建同名表)
  • ❌ 在模板中写死内容 id
  • ❌ 对不可信数据使用 |raw
  • ❌ 手工修改平台生成的静态页
  • ❌ 自行实现与平台接口同名的地址,或重复实现登录、表单、上传、支付
  • ❌ 把同一种关系同时写进 parent_id 和关联
  • ❌ 把凭据写进模板或前端代码
  • ❌ 为了消除 404 创建空的内容库
  • ❌ 绕过平台接口直接写内容库。新增、修改、删除内容一律通过后台接口或 Agent 接入;直接写库不经过发布,页面不会随之更新,由此造成的不一致由网站自行负责

扩展与需求反馈

先试试现有能力

绝大多数需求不需要平台改动:

需求做法
增加字段meta_json
增加内容类型新 taxonomy + 同名模板
增加关系类型新类型组合 + 关联
某个页面特殊排版在内容的 template 字段指定专用模板
复杂列表组合 list_post / list_related 的条件和排序
独立功能在站点 public/ 中实现独立应用

通用性原则

平台不会为单个网站调整。一项需求要进入平台,需要同时满足:

  1. 多个不同类型的网站都会用到
  2. 可以不提任何具体网站或业务来描述它
  3. 不改变任何现有网站的行为(默认关闭,或对不使用者完全透明)
  4. 它是一种机制,而不是业务判断——平台可以提供“按任意字段排序”,不会提供“把爆款排在前面”

如何提出需求

请说明:

  • 要解决什么问题(而不是想好的实现方式)
  • 为什么用现有能力做不到,或代价过高
  • 还有哪些类型的网站会需要
  • 期望的模板写法或接口形式
  • 目前的临时方案

你会得到的答复

  • 采纳:抽象为通用能力加入平台,更新本文档。
  • 不采纳:说明原因,并给出用现有能力实现的具体建议。

平台不会为某一个网站增加专属开关。

上线检查清单

页面

  • ☐ 首页、各类内容页、搜索页、404 页正常
  • ☐ 所有页面的标题、描述、canonical 来自 base
  • ☐ <html lang> 正确;多语言站点 hreflang 完整
  • ☐ 列表页地址以 / 结尾,分页正常,超范围页码 404
  • ☐ 多级地址下 CSS、JS、图片无 404
  • ☐ 手机和桌面显示正常,浏览器控制台无错误

内容

  • ☐ 公开内容均为已发布状态,url 显式填写且唯一
  • ☐ 层级与关联方向正确
  • ☐ 模板中没有写死的 id
  • ☐ 扩展数据都在 meta_json
  • ☐ robots.txt 与 sitemap.xml 可访问

功能与安全

  • ☐ 用户面板可登录,管理员可进入后台
  • ☐ 表单能提交,站长能收到通知
  • ☐ 不可信数据没有使用 |raw
  • ☐ 凭据只在站点配置中
  • ☐ 开启静态化的站点已完成全站重建,修改模板后已重新构建

常见问题

修改了模板,页面没有变化

静态化站点中,模板修改后只有首页自动更新,其它页面需要全站重建。如果仍未生效,检查 templates/ 目录中是否有同名文件模板遮住了后台模板。

列表是空的

依次检查:内容状态是否为已发布;关联是否存在且方向正确(主体在前);模板里是否写死了已失效的 id;排序字段是否写错。

访问内容时被 301 跳转到另一个地址

你访问的不是它的规范地址。这是为了避免重复收录的正常行为。

想让同一篇内容在不同栏目中排序不同

使用关联的 weight,模板中 list_related(栏目id, 10, 1, 'weight')。

想给内容加一个字段

使用 meta_json。如果所有网站都需要,请按「扩展与需求反馈」提出。

页面提示模板不存在或模板引擎错误

检查模板名称是否与内容的 template 或 taxonomy 一致(.html 后缀可以省略);后台模板的引擎是否为 twig。

页面里有 {{ 或 {% 时报错

模板中的这两组符号会被当作 Twig 语法。需要原样输出时使用 {% verbatim %}…{% endverbatim %}。正文内容中的这两组符号不受影响。

没有内容库的站点访问不存在的地址返回 404

正常行为,不需要处理。

多语言站点切换语言后跳到了首页

该语言下没有这篇内容的译文。按 URI 约定补齐译文即可。

术语表

术语含义
内容(post)CMS 中的一切数据单元:文章、产品、栏目、标签……
taxonomy内容类型,由网站自由定义
层级(parent_id)一对一的隶属关系
关联(post_post)多对多的关系,含主体端、从属端和排序权重
主体 / 从属关联的两端,如“栏目 → 文章”中栏目是主体
meta_json内容的扩展字段
规范地址内容的 url,唯一对外地址
模板决定内容如何展示的 Twig 文件或后台模板
base_layout可选的全站页面外壳
静态化把页面预先生成为静态文件
全站重建重新生成全站所有静态页面
站点配置(options)站点级设置项

本页本身就是用 iHomepage CMS 建成的:它是官网内容库中类型为 document 的内容,中英文两个版本共用同一个 URI(/documents/ 与 /cn/documents/),由同名模板 document.html 渲染,左侧目录来自 the._toc,并已静态化。

从一个已经验证过的结构开始

iHomepage CMS 的每一套模板都建立在本文介绍的内容模型之上——选一套适合你行业的模板,直接开始发布内容。

浏览模板