插件开发文档

ZhiCms 发布于 阅读:228 开发动态

ZhiCms 插件开发文档

本指南介绍如何为 ZhiCms 开发标准(Native)格式插件。插件通过钩子(Hook)挂载到系统运行流程中,实现「不改动框架代码」地扩展功能。

兼容性:ZhiCms 同时兼容 Z-Blog / WordPress / Emlog 三种历史格式插件(通过 ZhiCms\base\compat\Compat 兼容层),本文仅针对官方推荐的 Native 格式


0. 先选类型:你要写哪种插件?

ZhiCms 的插件按「是否需要接管前台页面 / 是否依赖数据库」分为三大类,开发方式差异很大,请先对号入座:

类型 典型场景 依赖数据库 是否接管前台页 核心入口
A. 普通插件(不依赖数据库) 页脚加公告、注入统计代码、改写已有模板内容、SEO 追加 register() 挂钩子 / 过滤器
B. 依赖数据库的插件 留言板、访问日志、自定义表单、积分、独立数据表业务 否(或仅后台页) install.sql 建表 + register() 钩子 + Setting.php
C. 模板化插件(接管前台) 导购站、论坛、商城风格整站、把插件设为站点首页 通常也依赖数据库 plugin.jsonrewrite + Plugin::displayPage() + 自有控制器/视图

本文第 1–4 章讲通用结构(三类插件都要遵守),第 5–7 章分别用完整示例讲三类插件怎么写,第 8 章讲后台菜单 / 设置页 / 首页接管,第 9 章讲打包与安装,第 10 章讲注意事项。


1. 插件目录结构(通用约定)

插件统一存放于 网站根目录 plugins/{alias}/,别名(alias)即目录名,只能包含 字母、数字、下划线、连字符

1.1 最小结构(类型 A/B 够用)

plugins/
└── my_plugin/                # 插件目录(alias = my_plugin)
    ├── plugin.json           # 元信息(必需)
    ├── Plugin.php            # 插件入口类(必需,继承 BasePlugin)
    ├── Setting.php           # 设置组件(可选,hasSetting=true 时需要)
    ├── install.sql           # 安装时执行的建表 SQL(可选,类型 B 需要)
    ├── uninstall.sql         # 卸载时执行的 SQL(可选)
    └── view/                 # 插件私有视图(可选,后台设置页 / 片段)
        └── setting.php

1.2 模板化插件结构(类型 C 推荐)

plugins/
└── demo/                      # alias = demo
    ├── plugin.json            # 必须声明 type=template + rewrite 规则
    ├── Plugin.php             # 入口:displayPage() 调度到控制器
    ├── controller/
    │   ├── DemoController.php    # 插件控制器基类(assign/display/isMobile)
    │   └── SiteController.php    # 业务:index/detail/list... 真实写在哪
    ├── view/                  # 模板目录(与控制器里的 view_path 对应)
    │   ├── header.html        # 插件页头(复用或自建)
    │   ├── footer.html
    │   ├── sidebar.html
    │   ├── index.html         # 首页
    │   ├── detail.html        # 详情页
    │   ├── list.html
    │   └── ...
    ├── static/                # 插件自有静态资源
    │   ├── css/style.css
    │   └── js/main.js
    ├── install.sql            # 若插件自身有表(可选)
    └── Setting.php            # 可选后台设置

目录命名建议:controller/(业务控制器)、view/(模板)、static/(资源)是社区约定,框架本身不强制,只要 Plugin.php / plugin.json 在根目录即可。关键点view/ 必须是 Plugin::displayPage() 或控制器 display() 实际指向的模板根目录。


2. plugin.json 元信息(三类插件通用)

{
    "alias": "my_plugin",
    "name": "我的插件",
    "version": "1.0.0",
    "author": "你的名字",
    "description": "插件功能描述",
    "hasSetting": true,
    "type": "common",
    "menu": [
        { "title": "插件设置", "url": "index.php?r=manage/plugin/setting&alias=my_plugin" }
    ],
    "requires": "5.0.0",
    "rewrite": {
        "plug-<alias>.html": "index/plug/view/alias=<alias>",
        "plug-<alias>-<id>.html": "index/plug/view/alias=<alias>/id=<id>"
    }
}

字段说明:

字段 必填 说明
alias 插件别名,必须与目录名一致
name 插件显示名称
version 版本号
type common(默认,普通插件)/ template(模板化插件,可被设为站点首页)。template 同时需要 rewrite 里含 plug-<alias>.html 规则
hasSetting true 时后台出现「设置」入口,需提供 Setting.php
menu 后台侧边栏「扩展功能」分组下注入的菜单项
rewrite 模板化插件必填。伪静态规则(见第 7 章)
requires 依赖的最低系统版本

模板化插件判定逻辑(源码 PluginManager::getTemplatePlugins()):rewrite 中存在 plug-<alias>.htmlPlugin::displayPage() 方法存在,即被视为可被设为主页的模板化插件。后台「系统设置 → 站点设置 → 主页展示插件」下拉里会列出这些插件。


3. 插件入口类 Plugin.php(三类插件通用)

入口类必须继承 ZhiCms\base\plugin\BasePlugin,命名空间为 plugins\{alias},类名固定为 Plugin

<?php
namespace plugins\my_plugin;

use ZhiCms\base\plugin\BasePlugin;
use ZhiCms\base\Hook;

class Plugin extends BasePlugin
{
    /**
     * 钩子注册:插件「已启用」时,每次请求都会调用。
     * 在此通过 Hook::add() 把回调挂到系统钩子上。
     */
    public function register()
    {
        Hook::add('appBegin', array($this, 'myHook'));
    }

    /** 前台展示页入口(仅模板化插件 C 需要覆写) */
    public function displayPage($params = array()) {}

    /** 首次安装:建目录 / 初始化数据(install.sql 已自动执行) */
    public function install() {}

    /** 卸载:清理数据 / 删除文件 */
    public function uninstall() {}

    /** 启用 / 停用 */
    public function enable() {}
    public function disable() {}
}

3.1 生命周期方法(BasePlugin 可覆写)

方法 触发时机 典型用途
register() 每次请求,插件处于启用状态时 注册钩子(最重要)
install() 首次安装(install.sql 已执行后) 创建目录、初始化数据
uninstall() 卸载插件时 清理建的表 / 生成的文件
enable() 从停用变为启用时 恢复数据 / 建目录
disable() 从启用变为停用时 停用运行 / 暂停任务
displayPage($params) 访问 plug-<alias>*.htmlindex.php?r=index/plug/view 模板化插件输出前台页

注意:BasePlugin 没有 boot() 方法(历史文档里提到的 boot() 实际是 register() 的别名用途,请以 register() 为准)。系统启动时 PluginManager::boot() 负责合并插件 rewrite 规则并 register() 钩子,插件无需自己写 boot()

3.2 BasePlugin 内置可用方法

方法 可见性 说明
getConfig() protected 读插件配置(来自 yun_plug.config 的 JSON),返回数组
setConfig($data) protected 保存插件配置(自动 JSON 化写入 yun_plug.config
displayPage($params) public 模板化插件前台页入口(见第 7 章)
pageUrl($params) public 生成插件展示页链接(伪静态优先,动态兜底)
render($tpl, $vars) protected 渲染插件私有模板 plugins/{alias}/view/{tpl}.html(复用 think-template 引擎,返回字符串)

3.3 使用框架能力(三类插件通用)

use ZhiCms\base\Config;

// 读数据库({pre} 自动替换为表前缀 yun_)
$rows = obj('api/ApiData')->thisQuery("SELECT * FROM {pre}my_table WHERE id = ?", array($id));

// 读站点配置
$siteName = obj('base/Base')->SiteConfig('sitename');
$cfg = \app\common\ConfigStore::load('site');        // 站点配置数组

// 写操作日志
\ZhiCms\ext\AdminLog::write('my_plugin', '做了某事');

// 读/写插件自身配置
$conf = $this->getConfig();
$this->setConfig(array_merge($conf, array('foo' => 'bar')));

4. 钩子(Hook)系统(类型 A/B 的核心)

4.1 注册钩子

Hook::add($tag, $callable, $priority = 10);

4.2 系统内置钩子

钩子名 触发时机 参数
appBegin 框架启动 (无)
appEnd 框架结束 (无)
appError 框架错误 $e
routeParseUrl 路由解析 $rewriteRule, $rewriteOn
actionBefore / actionAfter 控制器方法执行前后 $obj, $action
templateParse 模板编译前 $template(模板内容,可改写)
dbQueryBegin / dbQueryEnd / dbExecuteBegin / dbExecuteEnd / dbException SQL 执行前后 $sql, $params / $data / $affectedRows / $err

业务代码也可通过 Hook::listen($tag, $args) 自定义触发任意钩子名,插件可自由挂载。

4.3 过滤器(改写数据)

// 插件注册过滤器
Hook::add('article_content', function ($content) {
    return $content . "\n<!-- by plugin -->";
});
// 业务代码触发(返回值被依次改写)
$content = Hook::filter('article_content', $content, array($article));

全局助手 do_action() / apply_filters()ZhiCms/core.php)分别对应 Hook::listen() / Hook::filter()


5. 类型 A:不依赖数据库的普通插件(示例:页脚公告 + 统计代码)

特征:只挂载钩子、注入 HTML、改写模板/内容,完全不碰数据库。

5.1 目录

plugins/hello/
├── plugin.json
└── Plugin.php

5.2 plugin.json

{
    "alias": "hello",
    "name": "Hello 公告插件",
    "version": "1.0.0",
    "author": "你",
    "description": "在页脚注入公告与统计代码(无数据库)",
    "hasSetting": true
}

5.3 Plugin.php

<?php
namespace plugins\hello;

use ZhiCms\base\plugin\BasePlugin;
use ZhiCms\base\Hook;

class Plugin extends BasePlugin
{
    public function register()
    {
        // 在 appEnd 钩子(框架输出结束前)注入页脚公告 + 统计代码
        Hook::add('appEnd', function () {
            $cfg = $this->getConfig();
            if (empty($cfg['enabled'])) return;

            $notice = $cfg['notice'] ?? '欢迎光临本站';
            echo '<div class="hello-footer-notice" style="text-align:center;padding:10px;background:#f5f5f5">'
               . htmlspecialchars($notice, ENT_QUOTES) . '</div>';

            if (!empty($cfg['stat_code'])) {
                echo $cfg['stat_code'];   // 统计代码一般是 <script>,不转义
            }
        });

        // 改写文章正文(追加署名)
        Hook::add('article_content', function ($content) {
            return $content . '<p style="color:#999">—— 本文由 Hello 插件标记 ——</p>';
        });
    }

    public function install() {}
    public function uninstall() {}
}

5.4 Setting.php(可选,用于后台配置公告内容/统计代码)

<?php
namespace plugins\hello;

class Setting
{
    protected $meta = array();
    public function __construct($meta = array()) { $this->meta = $meta; }

    public function view()
    {
        $config = \ZhiCms\base\PluginManager::getConfig('hello');
        $config = array_merge(array('enabled' => 0, 'notice' => '', 'stat_code' => ''), $config);
        ob_start();
        include __DIR__ . '/view/setting.php';
        return ob_get_clean();
    }

    public function save($data)
    {
        return array(
            'enabled'   => intval($data['enabled'] ?? 0),
            'notice'    => trim($data['notice'] ?? ''),
            'stat_code' => $data['stat_code'] ?? '',   // 原始值,不转义
        );
    }
}

后台访问 index.php?r=manage/plugin/setting&alias=hellosave() 返回的数组由系统自动写入 yun_plug.config,无需手动 setConfig()


6. 类型 B:依赖数据库的插件(示例:访问日志)

特征:需要自己的数据表(install.sql 建表),在钩子里读写数据,可配合后台设置页。

6.1 目录

plugins/visit_log/
├── plugin.json
├── Plugin.php
├── Setting.php
├── install.sql        # 建表
├── uninstall.sql      # 删表(可选,也可在 uninstall() 里 DROP)
└── view/setting.php

6.2 install.sql({pre} 会自动替换为 yun_)

CREATE TABLE IF NOT EXISTS `{pre}visit_log` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `url` varchar(255) NOT NULL DEFAULT '',
  `ip` varchar(45) NOT NULL DEFAULT '',
  `ua` varchar(255) NOT NULL DEFAULT '',
  `addtime` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  KEY `url` (`url`(191))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

6.3 Plugin.php

<?php
namespace plugins\visit_log;

use ZhiCms\base\plugin\BasePlugin;
use ZhiCms\base\Hook;

class Plugin extends BasePlugin
{
    public function register()
    {
        Hook::add('appEnd', function () {
            $cfg = $this->getConfig();
            if (empty($cfg['enabled'])) return;

            $url = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '/';
            $ip  = $_SERVER['REMOTE_ADDR'] ?? '';
            $ua  = $_SERVER['HTTP_USER_AGENT'] ?? '';

            // 写入插件自有表(参数化防注入)
            obj('api/ApiData')->insertData('yun_visit_log', array(
                'url'     => $url,
                'ip'      => $ip,
                'ua'      => $ua,
                'addtime' => time(),
            ));
        }, 999); // 优先级大,最后执行,避免影响主流程
    }

    /** 后台查看日志(可选):配合菜单跳转或用 Setting 页输出列表 */
    public function uninstall()
    {
        // 卸载时删表(二选一:这里 DROP 或提供 uninstall.sql)
        try {
            obj('api/ApiData')->executeQuery("DROP TABLE IF EXISTS `{pre}visit_log`");
        } catch (\Throwable $e) {}
    }
}

6.4 数据库 CRUD 速查(类型 B 必读)

表名统一 yun_ 前缀,{pre} 在 SQL 中自动替换。推荐用封装方法:

// 增(返回自增 id)
$id = obj('api/ApiData')->insertData('yun_visit_log', array('url'=>$url,'ip'=>$ip,'addtime'=>time()));

// 批量增
obj('api/ApiData')->insertAllData('yun_visit_log', $dataList);

// 查(单条,无 $order 走 find() 返回一维数组)
$row = obj('api/ApiData')->dataSelect('yun_visit_log', array("`id` = $id"));

// 查(多条,传 $order 走 select() 返回二维数组)
$list = obj('api/ApiData')->dataSelect('yun_visit_log', array("1"), '`id` DESC LIMIT 0,20');

// 原生查询(参数化)
$rows = obj('api/ApiData')->thisQuery(
    "SELECT * FROM {pre}visit_log WHERE `ip` = ? ORDER BY id DESC LIMIT 10", array($ip)
);

// 改(注意 SET 与 WHERE 占位符不要同名冲突,建议用 :__id)
obj('api/ApiData')->dataUpdate('yun_visit_log', array('url'=>$newUrl), array("`id` = $id"));

// 删(参数化)
obj('api/ApiData')->deleteThis('yun_visit_log', "`id` = ?", array($id));

auto_increment 注意:插入时不要手填自增 id 字段,否则 MySQL 严格模式下 id 重复/类型错误会报错。让数据库自增即可。


7. 类型 C:模板化插件(接管前台页,可被设为站点首页)

特征:有自己的控制器 + 视图 + 静态资源,通过 plugin.jsonrewrite 规则暴露前台页,可整站接管首页(home_plug)。

本仓库 plugins/guangdiuplugins/kiees 就是这种插件,下面以「极简导购插件 demo」为例,结构照搬它们。

7.1 plugin.json(必须 type=template + rewrite)

{
    "alias": "demo",
    "type": "template",
    "name": "Demo 导购",
    "version": "1.0.0",
    "author": "你",
    "description": "模板化插件示例",
    "hasSetting": false,
    "rewrite": {
        "plug-<alias>-cheaps.html": "index/plug/view/alias=<alias>/mod=cheaps",
        "plug-<alias>-brand.html":  "index/plug/view/alias=<alias>/mod=brand",
        "plug-<alias>-<id>.html":   "index/plug/view/alias=<alias>/id=<id>",
        "plug-<alias>.html":        "index/plug/view/alias=<alias>"
    }
}

7.2 Plugin.php(displayPage 调度入口)

<?php
namespace plugins\demo;

use ZhiCms\base\plugin\BasePlugin;

class Plugin extends BasePlugin
{
    public function displayPage($params = array())
    {
        $controller = new \plugins\demo\controller\SiteController();
        $controller->run($params);   // run() 内部按 $params['mod'] 分发到 index/detail/...
        exit;                         // 必须 exit,避免框架再输出默认布局
    }
}

调度机制:框架 app\index\controller\PlugController::view() 收到请求后,把除 alias/r 外的所有参数(如 modidkwpage)收集进 $params,调用 Plugin::displayPage($params)。插件自己决定怎么分发。

7.3 控制器基类(assign / display / isMobile)

因为模板化插件不继承框架前台控制器,需要自建一个轻量基类,提供 assign() / display() / isMobile() 等能力。可照搬 guangdiu 的 GuangdiuController

<?php
namespace plugins\demo\controller;

use ZhiCms\base\ThinkTemplate;   // 注意:插件默认用 think-template 引擎渲染自有模板

class DemoController
{
    protected $vars = array();
    protected $alias = 'demo';
    protected $isMobile = null;

    protected function isMobile()
    {
        if ($this->isMobile !== null) return $this->isMobile;
        // 简化:直接用 UA 判断(可参考 guangdiu 支持 ?pc=1 / ?m=1 强制切换)
        $ua = strtolower($_SERVER['HTTP_USER_AGENT'] ?? '');
        $this->isMobile = (bool) preg_match('/iphone|android|mobile|micromessenger/i', $ua);
        return $this->isMobile;
    }

    protected function assign($name, $value = null)
    {
        if (is_array($name)) { foreach ($name as $k=>$v) $this->vars[$k]=$v; }
        else { $this->vars[$name] = $value; }
        return $this;
    }

    /**
     * 渲染插件私有模板 plugins/demo/view/{tpl}.html
     * 注意:display() 内必须 exit,防止框架再输出。
     */
    protected function display($tpl = 'index', $extra = array())
    {
        $config = \ZhiCms\base\Config::get('TPL');
        $viewPath = \BASE_PATH . 'plugins/' . $this->alias . '/view/';

        $tplConfig = array_merge((array)$config, array(
            'TPL_PATH'     => $viewPath,
            'view_path'    => $viewPath,
            'TPL_SUFFIX'  => '.html',
            'view_suffix' => 'html',
        ));
        $engine = new ThinkTemplate($tplConfig);

        // 注入控制器 public 属性 + $this->vars + 公共站点变量
        foreach (get_object_vars($this) as $k => $v) {
            if ($k === 'vars') continue;
            $engine->assign($k, $v);
        }
        foreach ($this->vars as $k => $v) { $engine->assign($k, $v); }
        foreach ((array)$extra as $k => $v) { $engine->assign($k, $v); }

        // 注入常用站点变量,供模板使用
        $base = obj('base/Base');
        $engine->assign('site_name', $base->SiteConfig('sitename'));
        $engine->assign('copyright', $base->SiteConfig('banquan'));
        $engine->assign('plug_static', '/plugins/' . $this->alias . '/static');
        $engine->assign('is_mobile', $this->isMobile() ? 1 : 0);

        $engine->display($tpl);   // 渲染 plugins/demo/view/{tpl}.html
        exit;
    }

    protected function arg($name = null, $default = null)
    {
        static $args;
        if (!$args) { $args = array_merge((array)$_GET, (array)$_POST); }
        if ($name === null) return $args;
        if (!isset($args[$name])) return $default;
        $v = $args[$name];
        if (is_array($v)) { array_walk($v, function(&$x){ $x=trim(htmlspecialchars($x, ENT_QUOTES)); }); return $v; }
        return trim(htmlspecialchars($v, ENT_QUOTES));
    }

    protected function db() { return obj('api/ApiData'); }
}

7.4 业务控制器 SiteController(真实逻辑写这)

<?php
namespace plugins\demo\controller;

class SiteController extends DemoController
{
    /** 统一入口:按 $params['mod'] 分发 */
    public function run($params)
    {
        $mod = isset($params['mod']) ? $params['mod'] : '';
        switch ($mod) {
            case 'detail': $this->detail($params); break;
            case 'cheaps': $this->cheaps(); break;
            case 'brand':  $this->brand(); break;
            default:       $this->index();
        }
    }

    public function index()
    {
        $list = $this->db()->dataSelect('yun_article', array("`status` = 1"), '`id` DESC LIMIT 0,20');
        $this->assign('list', $list ?: array());
        $this->assign('page_title', '首页 - Demo');
        $this->display('index');
    }

    public function detail($params)
    {
        $id = isset($params['id']) ? intval($params['id']) : 0;
        $row = $this->db()->dataSelect('yun_article', array("`id` = $id", "`status` = 1"));
        if (empty($row)) { header('HTTP/1.1 404 Not Found'); echo '内容不存在'; exit; }

        // 浏览量自增(访客去重)
        $vk = 'demo_view_' . $id;
        if (empty($_COOKIE[$vk])) {
            $this->db()->executeQuery("UPDATE `{pre}article` SET `view`=`view`+1 WHERE `id`=?", array($id));
            if (isset($row['view'])) $row['view'] = (int)$row['view'] + 1;
            setcookie($vk, '1', time()+3600, '/');
        }

        $this->assign('article', $row);
        $this->assign('page_title', $row['title']);
        $this->display('detail');
    }

    public function cheaps() { $this->assign('list', array()); $this->display('cheaps'); }
    public function brand()  { $this->assign('list', array()); $this->display('brand'); }
}

7.5 模板(plugins/demo/view/)

index.html

{include file="plugins/demo/view/header"}
<div class="container">
  {foreach $list as $item}
    <div class="gd-post">
      <a href="{plug_url}-{$item.id}.html">{$item.title}</a>
      <span>人气:{$item.view}</span>
    </div>
  {/foreach}
</div>
{include file="plugins/demo/view/footer"}

插件模板用 相对于插件根目录的 include 路径plugins/demo/view/header),与框架前台模板 app/index/view/public/header 区分开。模板语法用 think-template 引擎({foreach} / {$x.y} / {include}),与 TEMPLATE_DEV.md 第 3 章通用语法一致。

7.6 设为站点首页(home_plug)

后台「系统设置 → 站点设置 → 主页展示插件」下拉选择 demo 后,系统把 site.home_plug 存为 demo。框架 ZhiCms\base\App::applyHomePlug() 在访问 //index.php/index.html 时,自动把路由改写为 index/plug/view/alias=demoURL 不变,用户看到的还是首页地址)。

前提:

  1. plugin.jsontype=templaterewriteplug-<alias>.html
  2. 插件已启用。
  3. 站点伪静态 REWRITE_ON=1(仅伪静态访问需要;动态 index.php 始终可走)。

7.7 伪静态规则注意

7.8 链接生成

// 插件内
echo $this->pageUrl(array('id' => 123));   // plug-demo-123.html(伪静态开)/ index.php?r=index/plug/view&alias=demo&id=123
echo $this->pageUrl(array('mod' => 'cheaps')); // plug-demo-cheaps.html

// 模板里(已由 display() 注入 plug_url 变量)
<a href="{$plug_url}-{$item.id}.html">详情</a>

8. 后台菜单 / 设置页 / 首页接管 汇总

8.1 后台菜单注入

plugin.jsonmenu 配置后,插件启用时菜单自动注入后台侧边栏「扩展功能」:

"menu": [ { "title": "插件设置", "url": "index.php?r=manage/plugin/setting&alias=my_plugin" } ]

8.2 设置页(Setting.php)

hasSetting: true 时提供 Setting.phpplugins\{alias}\Setting),含 view()(返回 HTML 字符串)与 save($data)(返回要落库的数组)。系统自动把 save() 结果写入 yun_plug.config

8.3 首页接管(仅模板化)

见第 7.6 节。


9. 打包与安装

  1. 将插件目录打成 ZIP,压缩包根目录应直接包含 {alias}/ 文件夹(包内路径 demo/plugin.json)。
  2. 后台「系统 → 插件管理 → 上传插件」,选 ZIP。
  3. 上传后点「安装」:执行 install.sql → 调 install() → 写 yun_plug 注册表 → 启用。
  4. 可「启用 / 停用 / 卸载」(卸载可选删除文件)。

支持 .zba(Z-Blog 格式)与 __MACOSX 元目录自动排除。ZIP 多层嵌套会被规范化。

9.1 插件注册表

插件信息存于 {pre}plug 表:

字段 说明
alias 插件别名
name / version / author 元信息
status 1=启用 0=停用
installed 1=已安装
config 插件配置(JSON 文本,Setting::save 写入)
addtime 安装时间

10. 开发建议与注意事项

10.1 通用

10.2 模板化插件(类型 C)特有

10.3 数据库插件(类型 B)特有

10.4 最小可运行插件清单


本文档基于 ZhiCms 插件系统实现(参考 plugins/guangdiuplugins/kiees 真实代码)编写,覆盖三类插件(无数据库 / 依赖数据库 / 模板化接管前台)的目录结构、写法、伪静态、首页接管与打包安装。

请先 登录 再评论