Zsens Admin 插件开发教程
Zsens Admin 插件开发教程
1. 插件是什么
每个插件是一个独立目录:plugins/<插件名>/。系统通过 plugin.json 发现插件,通过入口类完成安装、配置、钩子、路由与菜单。
核心服务:
app/service/PluginService.php:安装 / 卸载 / 升级 / 配置 / 静态资源app/service/PluginRouteRegistry.php:注册前后台路由app/service/HookService.php:渲染hook_slotapp/service/PluginMenuService.php:注册后台菜单
安装入口:后台「应用中心」→ 安装 / 升级 / 卸载。
2. 目录结构
plugins/hello/
├── plugin.json # 必填:插件元信息
├── HelloPlugin.php # 必填:入口类
├── icon.svg # 可选:图标
├── helpers.php # 可选:模板助手函数
├── routes.php # 可选:前台/后台路由
├── install.sql # 可选:建表 SQL(不要写表前缀)
├── static/ # 可选:静态资源(安装时复制到 public)
├── controller/ # 可选:控制器
│ ├── Index.php # 前台
│ └── admin/ # 后台
├── service/ # 可选:业务服务
├── model/ # 可选:模型
└── view/
├── front/ # 前台视图
└── admin/ # 后台视图
命名约定:
- 目录名 =
plugin.json的name(小写英文,如badge) - 入口类 =
plugins\<name>\<Entry>,例如plugins\badge\BadgePlugin - 命名空间遵循 Composer PSR-4:
plugins\→plugins/
3. plugin.json
{
"name": "hello",
"title": "Hello 示例",
"version": "1.0.0",
"author": "Zsens Admin",
"description": "演示最小可安装插件",
"icon": "mdi-hand-wave",
"tag": "示例",
"entry": "HelloPlugin"
}
字段说明:
name:插件唯一标识,需与目录名一致title:后台展示名称version:版本号,安装/升级写入数据库author:开发者description:简介icon:Material Design 图标名(可写mdi-xxx)tag:分类标签,如「内容」「运营」entry:入口类短名;省略时默认Ucfirst(name)Plugin
可选扩展字段(如 type、namespace、require)可写在 JSON 里作说明,核心不会自动做依赖校验,需要插件自己在代码里判断。
文件请使用 UTF-8 保存,避免中文乱码。
4. 入口类生命周期
最小可用入口(钩子型,参考 copyright):
<?php
declare(strict_types=1);
namespace plugins\hello;
use app\service\PluginService;
class HelloPlugin
{
public function name(): string
{
return 'hello';
}
public function defaultConfig(): array
{
return [
'enabled' => '1',
];
}
public function configSchema(): array
{
return [
[
'name' => 'enabled',
'title' => '启用插件',
'tip' => '关闭后前台不输出内容',
'type' => 'switch',
'value' => '1',
],
];
}
public function install(): void
{
// 可选:PluginService::runSqlFile(__DIR__ . '/install.sql');
}
public function uninstall(): void
{
// 可选:删表、清菜单
}
public static function enabled(): bool
{
return PluginService::isInstalled('hello')
&& (int) PluginService::getConfigValue('hello', 'enabled', 1) === 1;
}
public function renderSlot(string $slot, array $ctx = []): string
{
if ($slot !== 'plaza_feed_top' || !self::enabled()) {
return '';
}
return '<div class="hello-plugin">Hello,这是我的第一个插件</div>';
}
}
常见方法:
install()/uninstall():安装、卸载defaultConfig():默认配置(值建议用字符串)configSchema():后台配置表单;只有 schema 里的字段才会被保存renderSlot():前台插槽输出hookPriority($slot):同插槽排序(数字越小越靠前)renderAssets():全局 CSS/JSensureSchema():热路径补表/改表,兼容升级
启用有两层:
- 数据库
plugin.status = 1→PluginService::isInstalled('hello') - 配置项如
enabled→ 插件自己的enabled()判断前台是否输出
5. 配置项类型
configSchema 常见 type:
switch:开关(存"0"/"1")text/number/textarea/passwordselect/checkbox_group(选项放在extra的 JSON 字符串里)- 可用
tip、show_when控制说明与显隐
读取配置:
$cfg = PluginService::getConfig('hello');
$on = (int) PluginService::getConfigValue('hello', 'enabled', 1) === 1;
6. 钩子(推荐扩展方式)
模板中:
{:hook_slot('plaza_feed_top')}
{:hook_slot('topic_after_content', ['topic' => $topic])}
{:plugin_assets()}
系统会调用所有已安装插件的 renderSlot($slot, $ctx),拼成 HTML。
常用插槽:
plaza_feed_top/plaza_feed_slider:广场信息流顶部 / 轮播topic_after_content/topic_before_replies:帖子正文后 / 回复前compose_body/compose_extra/compose_assets:发帖编辑区reply_user_badges/topic_author_badges/user_home_badges:勋章展示member_nav_extra/member_asset_tabs:用户中心导航 / 资产 Tabbar_actions_before_post:顶栏发帖前按钮区auth_login_extra/auth_register_fields:登录/注册扩展admin_topic_form:后台主题表单扩展
参考实现:copyright(最小钩子)、badge(多插槽)、vote(发帖+详情)、ads(广告位)。
7. 路由
plugins/<name>/routes.php 示例:
<?php
use think\facade\Route;
return [
'front' => static function (): void {
Route::get('hello', '\plugins\hello\controller\Index@index')->completeMatch();
},
'admin' => static function (): void {
// 挂在 /admin 分组下,实际路径为 /admin/hello
Route::get('hello', '\plugins\hello\controller\admin\Index@index');
},
];
复杂前台路由建议做成 service/FrontRoute.php(参考 message、payment):
routes():命名路由 → 路径url($name, $vars):生成链接register():统一Route::get/post
前台控制器常继承 plugins\community\controller\FrontBase。
8. 后台页面
controller/admin/Base继承后台基类,视图引擎指向核心view/admin/- 用绝对路径加载插件 HTML:
plugins/<name>/view/admin/xxx.html - 列表接口返回 Layui 风格:
{code, msg, count, rows} - 用
PluginMenuService::register(...)挂到插件菜单下
9. 数据库
install.sql 写不带前缀的表名,安装时由 PluginService::applyPrefix() 自动加 DB_PREFIX。
CREATE TABLE IF NOT EXISTS `hello_log` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`content` varchar(255) NOT NULL DEFAULT '',
`create_time` int unsigned NOT NULL DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
- 升级可用
CREATE IF NOT EXISTS/ALTER(常放在ensureSchema()) - 卸载再做
DROP TABLE
10. 静态资源
- 源目录:
plugins/<name>/static/ - 安装/升级后复制到:
public/static/<name>/ - 页面引用:
/static/<name>/xxx.css
注意:改了 static/ 不会自动同步,需要后台「升级」插件,或手动复制到 public/static/<name>/。
11. helpers.php
<?php
if (!function_exists('hello_url')) {
function hello_url(string $path = ''): string
{
if (!\app\service\PluginService::isInstalled('hello')) {
return '#';
}
return '/hello' . ($path !== '' ? '/' . ltrim($path, '/') : '');
}
}
模板:{:hello_url()}。命名建议:{插件名}_{动作}。
helpers.php 即使未安装也可能被加载,必须加 function_exists 与 isInstalled 保护。
12. 五分钟最小插件
- 建目录
plugins/hello/ - 写好
plugin.json+HelloPlugin.php - 后台应用中心安装「Hello 示例」
- 打开社区广场,看
plaza_feed_top是否出现 Hello 区块 - 在配置里关掉「启用」,前台应不再输出
13. 对照现有插件
copyright:最小钩子插件ads:后台 CRUD + helpers + SQL + 菜单badge:前台+后台 + 多插槽 + ensureSchema + staticmessage/payment:FrontRoute + 命名路由 +*_urlvote:发帖扩展 + 详情插槽 + 前端静态资源community:宿主布局、插槽挂载点
14. 常见坑
- 目录名 /
name/ 入口类不一致 → 安装报「插件入口无效」 - 只拷了文件未安装 → 路由和钩子不生效
- 只改了
enabled,忘了检查isInstalled install.sql手写了表前缀 → 前缀可能被加两次- 改了 static 不升级 → 仍用旧的 public 资源
helpers.php无守卫 → 未安装也会执行报错- 配置不在
configSchema里 → 保存时被丢掉 plugin.json非 UTF-8 → 中文乱码require字段不会自动拦截安装,需代码自检- 前台 URL 后缀要用 FrontRoute 正确生成
15. 开发检查清单
plugin.json:name/title/version/author/entry齐全- 入口类命名空间、文件名正确
install/uninstall可重复、可干净卸载- 配置有
defaultConfig+configSchema - 前台输出同时判断「已安装」+「已启用」
- SQL 无表前缀;静态资源路径正确
- helpers 有保护;UTF-8 保存


