> 来源：https://mac-cms.com/cms/cms-template-dev
> 简介：结合 MacCMS V10 真实源码讲清模板二次开发：template 目录结构、{maccms:...} 自定义标签引擎（基于 ThinkPHP5 TagLib）、静态资源引用、后台模板配置，再给出一套可复用的「Skill 策略」把模板改造沉淀成 AI 可执行的结构化指令。
> 更新：2026-11-02

# MacCMS 模板二次开发实战 +「Skill 策略」

想给自建的 MacCMS 站点换个样子——改配色、加一块推荐榜、把列表改成瀑布流、顺手适配一下手机——其实都是在动「模板」。本文结合 MacCMS V10（`magicblack/maccms10`）真实源码，讲清模板到底长什么样、那些 `{maccms:...}` 标签怎么来的、改完怎么生效；再给出一套把这些经验沉淀成「Skill 策略」的方法，让 AI 助手按规矩帮你改模板。

::: tip 读之前先了解整体架构
本文只聚焦「模板二开」。MacCMS 的整体架构（ThinkPHP 5.0.24、三入口、请求生命周期、API 两套）在[源码结构分析](/cms/cms-source-analysis)里讲过，本文不重复，涉及底层时直接引用那篇。
:::

::: warning 结论来自实际读源码
下面所有路径、标签名、配置项都来自实际阅读 `d:\maccms10` 源码（只读，未改动任何源码文件）。不同发行版的目录可能略有增减，以你本地源码为准。
:::

## 一、模板目录长什么样

MacCMS 的前台模板都放在源码根目录的 `template/` 下。源码自带一套名为 `default` 的模板集：

```
template/
└── default/                 # 一套完整模板集（可以有多套，如 template/1、template/2）
    ├── info.ini             # 模板信息文件
    ├── config.defaults.json.example  # 模板主题配置示例
    ├── asset/               # 该模板自带的静态资源
    │   ├── css/  js/  img/  fonts/  language/ ...
    ├── images/              # 模板图片
    └── html/                # 模板文件主目录（真正要改的在这里）
        ├── index/           # 首页
        ├── vod/             # 视频模块（详情/播放/分类/搜索…）
        ├── art/             # 文章模块
        ├── user/            # 用户中心
        ├── public/          # 全站公共片段（头、尾、引入文件、分页…）
        ├── module/ widget/  # 可复用的页面区块
        ├── topic/ actor/ role/ manga/ website/ plot/ ...  # 其它内容模块
        ├── comment/ gbook/  # 评论、留言本
        ├── map/ rss/        # sitemap、RSS
        └── ads/             # 广告位片段
```

规律很清晰：**一套模板 = 一个 `template/模板名/` 目录**，里面 `html/` 按「业务模块」分子目录，`asset/`（或 `css`/`js`/`images`）放这套模板自己的样式脚本。想做一套全新皮肤，常见做法是复制 `default` 为 `template/mytheme`，在副本里改，不动原版。

`html/` 下几个最常打交道的目录：

| 目录 | 放什么 |
|------|--------|
| `index/` | 首页 `index.html` |
| `vod/` | 视频相关页：`detail.html`（详情）、`play.html`（播放）、`type.html`（分类）、`show.html`（筛选）、`search.html`（搜索）等 |
| `art/` | 文章相关页，结构与 vod 对应 |
| `user/` | 用户中心各页（登录、注册、收藏、充值…） |
| `public/` | 全站公共片段：`include.html`（引入 js/css）、`head.html`（头部）、`foot.html`（尾部）、`paging.html`（分页）等 |
| `module/`、`widget/` | 可被各页 `include` 的可复用区块（如首页榜单、标签墙） |
| `ads/` | 广告位片段 |

::: tip 自带标签速查表就在源码里
源码 `说明文档/标签说明.txt` 是一份完整的模板文件清单 + 标签字段表（视频/文章/演员/专题等每个模块能用哪些字段、列表标签支持哪些筛选参数）。改模板时把它当字典查最省事——但**标签的实际行为以源码为准**（见下一节）。
:::

## 二、模板标签引擎：{maccms:…} 是怎么来的

MacCMS 的模板不是纯 HTML，而是在 **ThinkPHP5 原生模板引擎**之上扩展了一套自有标签。两层关系要分清：

### 底层：ThinkPHP5 的 Think 模板引擎

`application/config.php` 的 `template` 配置段里写死了引擎类型和标记：

```php
'template' => [
    'type'         => 'Think',   // 用 TP5 自带的 Think 模板引擎
    'view_path'    => './template/',
    'view_suffix'  => 'html',
    'tpl_begin'    => '{',        // 普通标签 {$var}
    'tpl_end'      => '}',
    'taglib_begin' => '{',        // 标签库标签 {maccms:xxx}
    'taglib_end'   => '}',
    'taglib_pre_load' => 'app\common\taglib\Maccms',  // 预加载 MacCMS 标签库
],
```

所以模板里能直接用 TP5 原生语法：

- 输出变量：`{$maccms.site_name}`、`{$obj.vod_name}`
- 引入片段：`{include file="public/head"}`
- 条件判断：`{if condition="$obj.vod_jumpurl neq ''"}...{/if}`
- 循环：`{volist name="list" id="vo"}...{/volist}`
- 调用函数：`{:mac_url_vod_detail($obj)}`、`{:mac_url_img($vo.vod_pic)}`

### 上层：MacCMS 自己的标签库 TagLib

关键在那行 `taglib_pre_load`——它把 `app\common\taglib\Maccms` 预加载进了模板引擎。这个类（源码 `application/common/taglib/Maccms.php`）继承自 TP5 的 `think\template\TagLib`，用 `$tags` 数组声明了所有自定义标签和它们支持的属性：

```php
class Maccms extends Taglib {
    protected $tags = [
        'type'  => ['attr' => 'order,by,start,num,id,ids,...'],
        'vod'   => ['attr' => 'order,by,start,num,type,class,tag,area,year,level,...'],
        'art'   => ['attr' => '...'],
        'actor' => ['attr' => '...'],
        'topic' => ['attr' => '...'],
        'link'  => ['attr' => '...'],
        'foreach' => ['attr' => 'name,id,key'],
        // 还有 area / lang / year / class / version / comment / gbook / manga / website ...
    ];
    // 每个标签对应一个 tagXxx() 方法，把 {maccms:vod ...} 编译成底层 PHP + volist 循环
    public function tagVod($tag, $content) { ... }
}
```

工作原理：模板里写 `{maccms:vod num="6" order="desc" by="hits_month"}...{/maccms:vod}`，引擎在编译阶段调用 `tagVod()`，它内部把标签翻译成「查数据 + `{volist}` 循环」的底层代码。所以**`{maccms:xxx}` 本质是 TP5 TagLib 的语法糖**，循环体里再用 `{$vo.字段名}` 取每条记录的字段。

一个真实的首页热门推荐片段（取自 `template/default/html/index/index.html`）：

```html
<ul class="img-list">
    {maccms:vod num="6" level="1,2,3,4,5,6,7,8,9" order="desc" by="hits_month"}
        {notempty name="vo"}{include file="widget/vod_box"}{else}{include file="public/notempty"}{/notempty}
    {/maccms:vod}
</ul>
```

这段的意思：取月点击量最高的 6 条视频，循环里把每条交给 `widget/vod_box` 片段渲染；没有数据就渲染占位片段。

::: tip 列表标签的常用筛选属性
`{maccms:vod}` / `{maccms:art}` 这类列表标签支持一大把筛选属性：`num`（条数）、`order`（`asc`/`desc`）、`by`（`time`/`hits`/`hits_month`/`level`/`rnd`…）、`type`（分类）、`class`（扩展分类）、`area`/`lang`/`year`（地区/语言/年代）、`level`（推荐值）、`ids`（指定 id）、`not`（排除 id）、`paging="yes"`（翻页）等。完整清单看源码 `说明文档/标签说明.txt`。
:::

## 三、静态资源：asset 与 static_new 怎么分工

模板里引用静态资源，统一用全局变量拼路径，而不是写死域名：

- `{$maccms.path}`：网站根目录（带末尾斜杠），指向站点根。
- `{$maccms.path_tpl}`：当前模板目录，等价于 `template/当前模板名`。

实际源码里两类资源并存：

```html
<!-- 模板自带资源：跟着这套皮肤走 -->
<link rel="stylesheet" href="{$maccms.path}template/default/asset/css/index.css?v=xxx">
<img src="{$maccms.path_tpl}/images/la/home-icon10.png">

<!-- 全站共享资源：所有模板通用，如播放器库 -->
<script src="{$maccms.path}static_new/player/dplayer/flv.min.js"></script>
```

分工大致是：

| 位置 | 作用 |
|------|------|
| `template/模板名/asset`（或 `css`/`js`/`images`） | **这套模板专属**的样式、脚本、图片，换皮肤就跟着换 |
| `static` / `static_new` | **全站共享**的基础资源（播放器 DPlayer、layui、字体、公共 js 等），不随模板切换 |

::: tip 加版本号防缓存
源码里大量 `?v=202604169` 这样的版本参数，就是改完 css/js 后强制浏览器重新拉取。你自己改样式后，顺手把版本号加一，省得读者看到旧样式。
:::

## 四、后台怎么配置与编辑模板

### 选用哪套模板

当前用哪套模板、`html` 目录叫什么，存在 MacCMS 配置里（源码 `application/extra/maccms.php` 的 `site` 段）：

```php
'site' => [
    'template_dir'     => 'default',  // PC 端用的模板集目录名
    'html_dir'         => 'html',     // 模板文件子目录名
    'mob_status'       => '0',        // 是否启用独立手机模板
    'mob_template_dir' => 'default',  // 手机端模板集
    'mob_html_dir'     => 'html',
    'ads_dir'          => 'ads',      // 广告位目录
    // ...
],
```

这些值由后台「网站参数配置」里的模板选项写入，不建议手改配置文件。切换模板，本质就是把 `template_dir` 指到另一个 `template/xxx` 目录。`mob_status=1` 时手机端会走 `mob_template_dir` 那套独立模板。

### 在线编辑模板文件

后台自带一个模板文件管理器（源码 `application/admin/controller/Template.php`）：它读的就是「当前模板」目录（`config('maccms.site')` 里的 `template_dir` + `html_dir`），能在浏览器里直接浏览、编辑 `html/` 下的模板文件和 `ads/` 广告位。

::: warning 这个入口只允许改模板目录
`Template::index()` 对路径做了限制：请求的 `path` 必须以 `.@template` 开头，并且会剔除 `/`、`\` 等字符、限制层级，防止越权读写模板目录以外的文件。也就是说后台模板编辑器**设计上只让你碰 `template/` 下的东西**，这是好事——别想着用它去改框架文件。
:::

## 五、实操：改一个页面

目标：把首页「热门推荐」默认取 6 条改成 10 条，并换个排序。

1. **定位文件**。首页在 `template/default/html/index/index.html`（`default` 换成你实际在用的 `template_dir`）。
2. **找到标签**。搜到那段 `{maccms:vod num="6" ... by="hits_month"}`。
3. **改属性**。把 `num="6"` 改成 `num="10"`，想按最新更新排就把 `by="hits_month"` 换成 `by="time"`：

   ```html
   {maccms:vod num="10" level="1,2,3,4,5,6,7,8,9" order="desc" by="time"}
       {notempty name="vo"}{include file="widget/vod_box"}{else}{include file="public/notempty"}{/notempty}
   {/maccms:vod}
   ```
4. **清缓存看效果**。MacCMS 会缓存编译后的模板和页面。改完如果前台没变化，去后台清一次缓存，或删掉 `runtime/` 下的缓存文件（`runtime` 目录可写，见[源码结构分析](/cms/cms-source-analysis#目录结构职责)），再刷新前台。

::: danger 两条红线
- **别动 `thinkphp/` 核心**：模板二开只该碰 `template/` 和它的 `asset`。改框架文件会在升级时被覆盖，还可能破坏安全补丁（见[安全加固](/cms/cms-security)）。
- **别破坏标签语法**：`{maccms:vod}` 必须成对闭合 `{/maccms:vod}`，`{if}`/`{volist}`/`{include}` 同理。少一个闭合标签，整页编译报错白屏。
:::

常见「改一个页面」的落点：

- 换配色/字体 → 改 `template/模板名/asset/css/*.css`，加版本号。
- 加一块推荐榜 → 在目标页复制一段 `{maccms:vod ...}{/maccms:vod}`，改 `by`/`type`/`num`。
- 改列表卡片样式 → 多半在 `widget/vod_box.html` 这类可复用片段里。
- 适配移动端 → 要么在现有模板写响应式 css，要么开 `mob_status` 用独立手机模板。

## 六、Skill 策略：把模板二开交给 AI 来做

上面这些改动有个共同点：**重复、套路化、边界清晰**。换配色、加版块、改卡片、适配移动端——每次的步骤几乎一样。这正适合沉淀成一套「Skill 策略」，让 AI 助手照着执行。

::: tip 这里说的「Skill」是方法论
本节的「Skill 策略」指的是**你写给 AI 的一套结构化指令/检查清单/提示词模板**，不是某个具体软件的插件。核心思想：把「怎么改 MacCMS 模板」这件事从你脑子里的经验，变成一份可复用、可交给 AI 执行的文档。
:::

### 什么是「Skill 策略」

一句话：**把一类重复的模板改造任务，抽象成结构化的指令集 + 检查清单**。好处是——

- 你不用每次从头交代背景，AI 一上来就知道 MacCMS 的目录和标签。
- 改动有固定步骤和验证环节，不容易漏（比如忘了清缓存）。
- 把「不能碰什么」写死，AI 不会手滑去改框架核心。

### 一套合格的模板二开 Skill 策略应包含的要素

| 要素 | 内容 |
|------|------|
| 目标描述 | 这次要达成什么效果（换色/加版块/改列表/移动端适配），尽量给出可验收的标准 |
| 模板定位方法 | 当前模板在 `template/{template_dir}/html/{模块}/`；公共片段在 `public/`、`module/`、`widget/` |
| 标签知识 | `{maccms:vod/art/...}` 列表标签 + `{$obj.字段}`；筛选属性查 `说明文档/标签说明.txt` |
| 改动步骤 | 定位文件 → 改标签/样式 → 加版本号 |
| 验证方法 | 清 `runtime` 缓存（或后台清缓存）→ 前台刷新核对效果 |
| 不可触碰边界 | 不改 `thinkphp/` 核心、不改 `vendor/`；标签必须成对闭合；不写死域名用 `{$maccms.path}` |

### 可直接套用的策略模板

**策略模板 A：通用「改一个前台页面」清单**

```text
# Skill：MacCMS 模板改一个页面

## 背景（固定）
- 站点：MacCMS V10（ThinkPHP5），模板引擎 Think + 自定义 TagLib。
- 当前模板目录：template/{template_dir}/html/，{template_dir} 默认 default。
- 资源路径用 {$maccms.path}（站点根）、{$maccms.path_tpl}（当前模板目录）。

## 输入（每次填）
- 页面：<首页 / 视频详情 / 分类页 / ...>
- 想要的改动：<一句话目标>

## 执行步骤
1. 按模块定位 html 文件（首页=index/index.html，视频详情=vod/detail.html…）。
2. 只改该文件内的标签/HTML/样式；复用区块优先改 widget/ 或 module/ 下的片段。
3. 用到数据列表时，用 {maccms:vod ...}{/maccms:vod} 并保证闭合；字段用 {$vo.字段名}。
4. 改 css/js 后在引用处把 ?v= 版本号 +1。

## 验证
- 清 runtime 缓存（或后台清缓存）→ 打开对应前台页面核对。
- 页面不白屏、标签全部成对闭合、无 PHP 报错。

## 禁止
- 不修改 thinkphp/、vendor/ 任何文件。
- 不写死协议/域名；不在模板里放密钥。
- 不破坏 {maccms:...}/{if}/{volist}/{include} 的成对语法。
```

**策略模板 B：「加一块推荐版块」最小提示词**

```text
在 template/{template_dir}/html/index/index.html 的 <目标位置> 新增一块「<版块名>」：
- 用 {maccms:vod num="10" type="<分类id或all>" order="desc" by="<time|hits_month|level>"} 取数据；
- 循环体复用现有卡片片段（如 {include file="widget/vod_box"}），没有则按同页其它卡片的 class 结构写；
- 标签必须 {/maccms:vod} 闭合；
- 新增/改动 css 放到 template/{template_dir}/asset/css/ 并在引用处加版本号；
- 完成后提示我清缓存并给出需要核对的前台 URL。
不要改 thinkphp/ 和 vendor/，不要写死域名。
```

### 安全与可维护（策略里务必写死）

把 AI 当成一个会照做的新手，边界必须显式写进策略：

- **输出转义防 XSS**：模板里凡是回显用户可控内容（评论、留言、搜索词），用转义输出，别直接 `{$var|raw}` 原样打印。
- **不在模板写死密钥**：接口 key、第三方令牌放配置，不要硬编码进 html。
- **改动走版本管理**：改模板前先 `git` 托管这套 `template/模板名/`，每次改动留提交记录，出问题能回滚。
- **先备份再大改**：换皮肤复制 `default` 为新目录改副本，别在原版上直接动刀。

## 相关文档

- [源码结构分析](/cms/cms-source-analysis)：ThinkPHP 5.0.24 架构、三入口、请求生命周期、目录职责。
- [安全加固](/cms/cms-security)：关调试、后台改名、权限收紧、ThinkPHP5 RCE 防护。
- [源码安全审计](/cms/cms-security-audit)：对本仓库源码的只读安全审计结论。
- [API接口说明](/cms/cms-api)：Provide / Vod 两套接口参数与返回字段。
