DaoKecms 开发者文档
面向第三方开发者的模板与插件开发调用规范。所有约定均与当前版本源码一致,按本文开发可确保资源顺利通过安装校验并上架应用中心。开发交流与上架事宜请联系官方。
环境要求:PHP 7.4 及以上(推荐 8.x)、MySQL / MariaDB。DaoKecms 不依赖 Composer 与 Node,模板与插件均为纯 PHP 目录结构。
1开发前必读
DaoKecms 由两部分组成,开发者只需要关心资源本身的开发,上架与分发由应用中心完成:
| 部分 | 说明 | 开发者相关点 |
|---|---|---|
| 主程序(CMS) | 内容管理核心,含后台与前台渲染框架 | 模板、插件都安装在主程序内运行 |
| 应用中心(本站) | 模板 / 插件的分发市场,含授权中心 | 资源打包后提交到这里上架分发 |
两套体系的目录对应关系:
tuanyucms/ 主程序
├── templates/{slug}/ 模板目录(模板开发的地方)
├── plugins/{slug}/ 插件目录(插件开发的地方)
└── market/ 应用中心(独立部署,与官网放在一起)
├── source/templates/{slug}/ 上架用模板源(打包发市场)
├── source/plugins/{slug}/ 上架用插件源(打包发市场)
└── tools/publish.php 上架打包工具
slug 命名规则(模板与插件通用):仅允许 a-z A-Z 0-9 _ -,全局唯一,是资源在市场与站点内的唯一标识。建议使用简短有辨识度的英文单词,如 corporate、data_export。
templates/、plugins/ 目录下调试;开发完成后放入 market/source/ 并登记清单,用打包工具生成 zip 上架(见第 5 章)。2模板开发
模板决定站点的前台外观与交互。一套模板就是一个目录:若干 PHP 页面 + 静态资源 + 元数据清单。
2.1 目录结构与必需文件
模板目录 templates/{slug}/,安装校验要求以下 7 个文件必需存在,缺失任何一个 zip 都会被拒绝安装:
| 文件 | 说明 |
|---|---|
index.php | 首页模板 |
header.php | 页头(全站共用,由框架自动加载) |
footer.php | 页脚(全站共用,由框架自动加载) |
article.php | 文章详情页 |
category.php | 分类列表页 |
page.php | 单页(关于我们等) |
404.php | 404 页 |
其余页面按需提供,前台按 mod 参数(页面类型)取 {mod}.php 渲染,可选 mod 共 10 个:
| mod | 页面 | mod | 页面 |
|---|---|---|---|
index | 首页(必需) | cps | 导购列表页 |
category | 分类页(必需) | cpsdetail | 导购详情页 |
article | 文章页(必需) | search | 搜索结果页 |
page | 单页(必需) | message | 留言页 |
tag | 标签聚合页 | like | 点赞列表页 |
未提供的页面自动回退到默认模板 templates/default/ 下的同名文件,不会报错。
单页型模板的 stub 写法
很多模板(发布页、表白页等)只有一个视觉首页,所有 mod 都渲染同一页面。此时把每个可选 mod 文件写成一行的 stub 即可:
<?php
/** {模板名} v1.0 · 文件:tag.php */
require __DIR__ . '/index.php';
即 tag.php、search.php、like.php 等全部转给 index.php 渲染,用户从任何入口进来看到的都是你的首页。
404.php 除外——它本身就是必需项,不可省略)。本地内置模板可以缺省 404.php 回退默认模板,但上架市场的 zip 会被校验拒绝。2.2 渲染机制:不要手动 include 页头页脚
框架渲染顺序固定为:
header.php → {mod}.php → footer.php
也就是说,页面模板文件(如 article.php)内不要再写 include 'header.php',否则页头页脚会重复输出两次。
框架在各 mod 分支已备好数据,模板内直接使用变量:
| mod | 可用变量 |
|---|---|
| 全部 | $site_name 站点名、$pages 单页列表、$categories 栏目、$page 当前页码、$pageSize 每页条数 |
| index | $banners 轮播、$articles 最新文章、$news、$stats |
| category | $cat 当前栏目、$list 文章列表、$total 总数 |
| article | $art 文章、$prev / $next 上下篇、$related 相关推荐 |
| page | $alias 别名、$pg 单页数据 |
| search | $keyword 搜索词、$list、$total |
2.3 template.json 元数据
模板根目录的 template.json 是安装与市场展示的元数据来源,字段如下:
{
"slug": "mytpl", // 唯一标识,上架 zip 必填;仅字母数字下划线
"title": "我的企业官网模板", // 显示名
"desc": "响应式企业官网模板……", // 一句话描述
"version": "1.0.0",
"author": "你的名字",
"preview": "preview.svg", // 预览图文件名(目录内,支持 svg/png/jpg)
"features": ["响应式", "多端适配"] // 特性标签(数组)
}
slug 由目录名决定可省略;上架市场的 zip 内 template.json 必须含 slug 字段,否则安装端直接拒绝。2.4 模板函数速查
模板中可直接调用的框架函数(均在主程序加载阶段就绪):
路径与资源
| 函数 | 说明 |
|---|---|
template_path($file) | 当前模板文件的绝对路径,缺失自动回退默认模板 |
template_url($file) | 当前模板静态资源网址(CSS / JS / 图片引用用它) |
url_asset($path) | 相对路径统一转可访问网址(上传图、市场源自动改写) |
tpl_root() | 站点根目录绝对路径 |
配置与输出
| 函数 | 说明 |
|---|---|
C($key, $default) | 读配置,空值回退默认值 |
e($str) | HTML 转义输出。一切动态值输出前必须过它 |
post($key) / get($key) | 取请求参数 |
str_cut($str, $len) | 去标签截断(列表摘要用) |
fmt_date($ts, $format) | 日期格式化 |
内容取数
| 函数 | 说明 |
|---|---|
get_articles($where, $params, $limit, $order) | 通用文章列表(只取已发布) |
get_articles_by_cat($catId, $limit, $offset) | 分类文章(含定时上下架) |
count_articles_by_cat($catId) | 分类文章总数 |
get_article($id) | 单篇文章(含可见性校验) |
get_article_prev_next($catId, $id, $dir) | 上一篇 / 下一篇 |
get_categories() / get_nav_categories() | 全部分类 / 导航分类 |
get_banners() / get_links() / get_ads($position) | 轮播 / 友链 / 广告位 |
get_page_by_alias($alias) / get_pages() | 单页取数 |
get_goods($platform, $limit, $order) | 导购商品列表 |
get_tags() / get_articles_by_tag(...) | 标签聚合 |
网址生成
| 函数 | 生成 |
|---|---|
url_index() | 首页 |
url_article($id) | 文章页 |
url_category($id, $page) | 分类页 |
url_page($alias) | 单页 |
url_search($kw, $page) | 搜索页 |
url_tag($slug, $page) | 标签页 |
SEO 与分页
| 函数 | 说明 |
|---|---|
seo_title() / seo_keywords() / seo_description() | 读当前页 SEO(有回退链) |
breadcrumb($items) | 面包屑,传 ['名称' => '网址'],网址为空输出纯文本 |
pagination($total, $page, $pageSize, $baseUrl, $show) | 分页条,网址用 {page} 占位符 |
2.5 后台配置页 admin-config.php
想让站长不改代码就能调整模板(换标题、换图、开关栏目),就提供一个后台配置页。文件名固定为 admin-config.php,放在模板根目录,首行必须是网关守卫:
<?php
/** {模板名} 后台配置页 */
if (!defined('DK_TPL_CFG_INC')) { http_response_code(403); exit('Access denied'); }
/* 保存(POST) */
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
/* 文件上传:走 save_upload() */
if (!empty($_FILES['mytpl_logo_file']['name'])
&& $_FILES['mytpl_logo_file']['error'] === UPLOAD_ERR_OK) {
$up = save_upload('mytpl_logo_file', 'uploads');
if ($up !== '') set_config('mytpl_logo', $up);
}
/* 文本字段 */
set_config('mytpl_title', trim((string) post('mytpl_title')));
/* 复杂结构存 JSON */
set_config('mytpl_links', json_encode($links, JSON_UNESCAPED_UNICODE));
/* 开关:checkbox 未勾选时必须显式存 '0' */
set_config('mytpl_show_title', post('mytpl_show_title') ? '1' : '0');
}
/* 读值渲染表单 */
$title = (string) C('mytpl_title', '');
$links = json_decode((string) C('mytpl_links', ''), true);
规则要点:
- 网关
admin/template_config.php?slug=你的slug会define('DK_TPL_CFG_INC', true)后 require 本文件,登录与 CSRF 校验已由网关完成。 - 配置键必须以模板 slug 开头(如
mytpl_title),避免与其他模板 / 插件冲突。 - 页面不要输出
<h1>、不要 include 后台头尾,网关已提供页面外壳;表单值输出一律e()转义。 - checkbox 类开关未勾选时 POST 里没有该字段,要显式写
'0',否则配置永远保持旧值。
前台模板文件用同款 C('mytpl_title', '默认值') 读取配置即可。
2.6 SEO 与静态资源
set_seo() 已经晚了——<head> 里的标题早已输出。需要按页面定制 SEO 时,一律在 header.php 顶部按 $mod 分支前置计算:<?php
/* header.php 顶部 */
$active_mod = $mod ?? 'index';
$seo_t = $GLOBALS['_seo']['title'] ?? null; // 先取框架默认
$seo_k = seo_keywords();
$seo_d = seo_description();
if ($active_mod === 'index') { // 再按 mod 覆盖
$seo_t = $mytplBrand . ' - ' . $mytplSlogan;
$seo_k = '企业建站,官网模板,' . $mytplBrand;
$seo_d = '一站式企业官网解决方案';
} elseif ($active_mod === 'article' && isset($art)) {
$seo_t = str_cut($art['title'], 60);
$seo_d = str_cut(strip_tags($art['content']), 120);
}
?>
<title><?php echo e($seo_t ?: $site_name); ?></title>
CSS / JS 版本号
静态资源一律经 template_url() 引用,并带文件修改时间做版本号,改版后浏览器自动刷新缓存:
<link rel="stylesheet" href="<?php echo template_url('static/style.css'); ?>?v=<?php echo (int) (@filemtime(template_path('static/style.css')) ?: 1); ?>">
输出安全
- 任何动态值输出到 HTML,一律
e()转义,无例外。 - 内联事件属性里嵌 PHP 值(如
onclick="go('<?php echo ...; ?>')")必须e();裸json_encode的双引号会提前终止属性,导致 JS 泄漏成页面可见文本。json_encode只允许用于<script>标签体内或经e()后放 data 属性。 - 用户可填的网址字段要做协议白名单(仅
http(s)://与mailto:),拒绝javascript:/data:。
2.7 在模板里调用插件(防白屏守卫)
插件可能被站长停用,模板直接调用插件函数会致命错误。标准守卫写法:
<?php
/* header.php:插件核心函数不存在时,手动 require 插件 helpers */
if (!function_exists('adslot') && function_exists('plugin_dir')) {
$asDir = plugin_dir('adslot'); // 目标插件目录(插件不存在返回空串)
if ($asDir !== '' && is_file($asDir . '/helpers/adslot-core.php')) {
require_once $asDir . '/helpers/adslot-core.php';
}
unset($asDir);
}
?>
<?php echo function_exists('adslot_head_html') ? adslot_head_html() : ''; ?>
header.php / footer.php 里显式调 adslot_head_html() / adslot_foot_html()(带上面的守卫)。3插件开发
插件用于扩展功能:挂内容钩子、加前台虚拟页、挂后台管理页、建自己的数据表。一个插件 = 一个目录 + 一个 JSON 清单。
3.1 目录结构与 plugin.json
插件目录 plugins/{slug}/,主入口默认 main.php(可经清单 main 字段改名),清单文件 plugin.json 字段全表:
| 字段 | 类型 | 说明 |
|---|---|---|
slug | string | 唯一标识,仅 a-zA-Z0-9_-,必填 |
title | string | 显示名(截断 100 字) |
desc | string | 描述(截断 255 字) |
version | string | 版本号,默认 1.0.0 |
author | string | 作者(截断 100 字) |
main | string | 主入口文件名,默认 main.php,须匹配 ^[a-zA-Z0-9_\-/]+\.php$ |
hooks | string[] | 声明使用的钩子名(供后台展示,如 ["head","footer"]) |
schema | string | 建表 SQL 文件相对路径(如 schema.sql,规则见 3.6) |
require.php | string | 最低 PHP 版本,如 "7.4" |
require.cms | string | 最低 DaoKecms 版本 |
features | string[] | 特性标签(市场页展示) |
builtin | bool | 仅官方内置插件使用,第三方勿填 |
完整示例:
{
"slug": "data_export",
"title": "数据导出",
"desc": "一键导出文章与栏目数据。",
"version": "1.0.0",
"author": "你的名字",
"main": "main.php",
"hooks": ["footer", "admin_menu"],
"schema": "schema.sql",
"require": { "php": "7.4", "cms": "1.0.0" },
"features": ["CSV 导出", "后台一键操作"]
}
3.2 入口与生命周期
框架每个请求会对所有已启用插件 require_once plugins/{slug}/main.php(按后台排序加载,单个插件抛异常不会拖垮整站)。main.php 的顶层代码就是注册点:
<?php
/** 数据导出插件入口 */
defined('ROOT') || exit('Access denied');
plugin_on('footer', 'myplg_footer_html'); // 挂钩子:返回字符串
plugin_mod('datadown', 'myplg_page'); // 注册虚拟页 ?mod=datadown
myplg_ensure_tables(); // 幂等建表(推荐 CREATE TABLE IF NOT EXISTS)
function myplg_footer_html() {
return '<div id="myplg-foot">...</div>'; // 返回字符串,勿 echo
}
function myplg_page($mod) { ... 自行输出并 exit; }
- 函数名一律带插件 slug 前缀(如
myplg_),避免与其他插件 / 框架函数撞名。 - 需要区分前台后台时,用
defined('DK_ADMIN')或检查当前脚本路径判断。 plugin_dir($slug)取插件绝对路径、plugin_url($slug, $file)取插件资源网址。
3.3 钩子清单
注册:plugin_on($name, $cb);触发:plugin_fire($name, $payload)(返回各回调返回值数组)。当前框架提供的钩子:
| 钩子名 | 触发时机 | 回调约定 |
|---|---|---|
head | 前台 </head> 之前 | 返回字符串追加进 head(不要 echo) |
footer | 前台 </body> 之前 | 同上 |
admin_menu | 后台菜单构建(预留扩展位) | 返回 [键 => [标题, 完整网址]] |
article_saved | 后台文章新增 / 更新后 | payload:['id'=>.., 'action'=>'create'|'update', 'data'=>..] |
article_deleted | 后台文章删除后 | payload:['id'=>.., 'ids'=>[..]] |
/* 钩子回调示例:文章保存后同步 */
plugin_on('article_saved', function ($payload) {
if (($payload['action'] ?? '') !== 'create') return;
DB::insert('myplg_index', [
'aid' => (int) $payload['id'],
'title' => (string) ($payload['data']['title'] ?? ''),
]);
});
3.4 前台虚拟页 ?mod=xxx
插件可注册前台独立页面(框架内置 mod 之外的地址),用户访问 ?mod=datadown 时插件完全接管输出:
plugin_mod('datadown', 'myplg_page'); // mod 名仅小写字母数字下划线
function myplg_page($mod) {
/* 需要模板壳可 include,并自行 exit */
include template_path('header.php');
echo '<div class="container">插件页面内容</div>';
include template_path('footer.php');
exit;
}
3.5 后台管理页
插件目录内的 admin.php、admin_config.php、admin_setting.php 及其余 admin_*.php(不含 admin_*_api.php)会被后台「插件管理」自动发现,出现「管理」按钮。页面经统一网关渲染:
admin/plugin_page.php?slug=data_export&f=admin.php
网关已做:登录校验、POST CSRF 校验、插件启用状态校验、文件名防穿越。页面内规则:
- 入口网址用
plugin_admin_page_url('data_export', 'admin.php')生成,不要手拼。 - 所有 POST 表单必须带:
<input type="hidden" name="_csrf" value="<?php echo e(csrf_token()); ?>">。 - 可定义
$pageTitle覆盖页标题(默认取 plugin.json 的 title)。 - 子页分发参数用
p(&p=slots),f被网关占用。
<?php
/** 插件后台页样例(admin.php,分发器模式) */
defined('ROOT') || exit('Access denied');
$selfUrl = plugin_admin_page_url('data_export', 'admin.php');
$p = get('p', 'list');
if ($p === 'list') require __DIR__ . '/admin_list.php';
elseif ($p === 'export') require __DIR__ . '/admin_export.php';
3.6 数据表
表名必须以 {数据库前缀}{插件slug}_ 开头,这是卸载清理与隔离的依据。建表 SQL 写在清单 schema 指向的文件里(如 schema.sql),安装 / 覆盖升级时自动执行:
-- schema.sql:仅允许 CREATE TABLE / ALTER TABLE,表名前缀用 {{prefix}} 占位
CREATE TABLE IF NOT EXISTS `{{prefix}}data_export_logs` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`aid` INT UNSIGNED NOT NULL DEFAULT 0,
`created_at` INT UNSIGNED NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
KEY `idx_aid` (`aid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
框架数据库类常用方法:
| 方法 | 说明 |
|---|---|
DB::row($sql, $params) / DB::rows(...) | 单行 / 多行(原生 SQL,表名自己拼 DB_PREFIX) |
DB::value($sql, $params) | 单值 |
DB::insert($table, $data) | 插入,返回自增 id。表名不带头(框架自动拼前缀) |
DB::update($table, $data, $where, $wparams) | 更新,返回影响行数 |
DB::delete($table, $where, $wparams) | 删除 |
DB::count($table, $where, $wparams) | 计数 |
?)传参,禁止字符串拼接用户输入。DB::insert/update/delete/count 传不带前缀的短表名,DB::row/rows/query/value 传完整 SQL 时才自己拼前缀。3.7 启用、停用与卸载
- 启用前自动语法预检:后台对整个插件目录跑
php -l,有语法错误直接拒绝启用——发布前请自查。 - 停用:
status=0,主入口不再被加载;模板等处直接调插件函数会白屏,所以调用方要按 2.7 的守卫写法防御。 - 熔断:站点存在
plugins/.disabled文件时所有插件一律不加载(排障开关)。 - 卸载:删除插件目录与注册行;勾选清理数据时
DROP所有{前缀}{slug}_开头的表——所以表名前缀必须严格,否则清理不到或误删别人数据。
4内置插件调用
系统随主程序分发六个内置插件:广告位(adslot)、用户中心(member)、VIP 会员(vip)、下载站(download)、接口对接 CPS(cps_connect)、APP 应用下载导航(appnav)。内置插件在 plugin.json 声明 "builtin": true,首次访问自动登记且默认停用,站长在后台按需启用;可停用、不可卸载,数据表始终保留(建表由插件自愈函数保证,不依赖安装流程)。模板调用它们的函数前,先按 2.7 的守卫写法确认函数存在。
本手册详述其中三个最常被模板调用的插件(adslot / appnav / cps_connect);member、vip、download 的前台函数(如 member_uid()、vip_is_vip())用法见各插件目录内的 README。另有网址导航 nav_grabber(4.4)为市场分发插件,调用方式一并收录。
4.1 广告位 adslot
全站广告统一管理:模板只声明广告位标识,投放内容全部由站长在后台配置,换广告、换图、调时间窗都不需要改模板。支持 8 种广告形式:
| 形式 | 模板要改吗 | 说明 |
|---|---|---|
| 横幅 Banner | 插一行 | 图片 / GIF / 多图轮播,最常用横幅位 |
| 文字链 | 插一行 | 纯文字链接,可设颜色加粗,适合友情推广位 |
| 图文混排 | 插一行 | 缩略图 + 标题 + 描述,适合推荐位 |
| 自定义代码 | 插一行 | 粘贴第三方代码(联盟、AdSense 等),万能兜底 |
| 弹窗 | 不用改 | 可设延迟、触发方式、每日次数、关闭后 N 天不再出现 |
| 底部固定条 | 不用改 | 底部常驻浮层,可关闭并记住状态 |
| 信息流 | 混排列表 | 混入文章列表,样式与列表内容一致 |
| 文中插入 | 包一层函数 | 正文第 N 段或第 N 张图之后插入,可重复插入 |
弹窗与底部固定条由页面底部钩子全站自动注入,后台建好即生效,模板无需任何改动。
模板调用
<?php adslot('home_banner'); ?> // 直接输出(最常用)
<?php echo adslot_html('home_banner'); ?> // 返回字符串,便于判断空
<?php echo adslot_inject($art['content'], 'art_inline'); ?> // 文中插入:输出处理后的正文
// 信息流:取出广告内容,自行混进文章列表(插入间隔读后台配置)
$feeds = adslot_feed('list_feed');
$slot = adslot_slot_by_code('list_feed');
$every = $slot ? max(1, (int) as_cfg(adslot_config($slot), 'every', 5)) : 5;
foreach ($list as $i => $a) {
// … 原有列表项输出 …
if ($feeds && ($i + 1) % $every === 0) {
$item = array_shift($feeds);
echo adslot_render_item($item, $slot, ['mode' => 'feed']);
}
}
| 函数 / 方法 | 说明 |
|---|---|
adslot($code) | 按广告位标识直接输出 HTML |
adslot_html($code) | 同上但返回字符串;位置无内容时返回空,不留空白色块 |
adslot_inject($content, $code) | 文中插入:传正文,返回处理后的正文 |
adslot_feed($code) | 取信息流广告内容数组,由模板混排 |
adslot_slot_by_code($code) | 取广告位配置;as_cfg(adslot_config($slot), 'every', 5) 读混排间隔 |
adslot_render_item($item, $slot, $opts) | 渲染单条广告内容(信息流混排用) |
adslot_head_html() / adslot_foot_html() | 虚拟页专用:在插件自治渲染页的页头 / 页尾各调一次(见下) |
window.adslotRefresh(root) | JS:AJAX 追加列表内容后重新绑定曝光、轮播与浮层 |
虚拟页接入
?mod=nav 这类插件自治渲染页不经过框架在 </head> 与 </body> 前的头尾注入,只靠钩子会出现「有广告没样式」。插件提供两个「同请求只输出一次」的函数,模板在页头页尾各调一次即可,与钩子自动去重:
// header.php 的 </head> 前
<?php echo function_exists('adslot_head_html') ? adslot_head_html() : ''; ?>
// footer.php 的 </body> 前
<?php echo function_exists('adslot_foot_html') ? adslot_foot_html() : ''; ?>
内置 default 模板已占用的标识
站长后台按标识新建广告位即可直接投放,模板无需改动:
| 标识 | 位置 |
|---|---|
home_banner / home_mid_banner / home_bottom_banner | 首页首屏下方、中部、页尾通栏 |
list_top_banner / list_feed / list_side_banner | 列表页顶部通栏、信息流混排、侧栏 |
art_top_banner / art_inline / art_side_banner | 文章页正文上方、文中插入、侧栏 |
自定义模板的标识可自定,建议同样带模板前缀(如 mytpl_side)避免撞名。曝光按广告进入可视区域计一次,点击走 ?mod=adslot&act=click 计数后 302 跳转,跳转带开放协议白名单。
4.2 APP 应用下载导航 appnav
为应用商店 / 下载导航类模板提供分类与应用数据:分类管理、应用发布(图标 / 名称 / 简介 / 版本 / 多下载链接)、广告位(顶部横幅 + 分类间穿插)、点击统计。本插件不渲染前台页面——只提供 appnav_*() 数据函数,模板取数后自行渲染;前台仅注册一个计数跳转入口 ?mod=appnavgo(点击先计数再 302 到真实地址,真实地址不外露,后台改地址不用改模板)。配套官方模板 apphub(手机端优先)可直接参考。
分类与列表
<?php if (function_exists('appnav_cats')): ?>
<?php
$cats = appnav_cats(); // 启用中的分类(sort DESC),每行含 app_count
$apps = appnav_apps([ // 应用列表,每行自动带 links 数组
'cat_id' => 0, // 按分类筛选(0=全部)
'kw' => '', // 按名称/简介搜索
'order' => 'sort', // sort 权重(默认)| hot 点击量 | new 最新
'per' => 24, 'page' => 1, // 每页数量(≤100)
]);
$app = appnav_app($id); // 单个应用(含 links)
// 应用行:id / cat_id / name / icon / intro / version / size / hits / sort / status / links
// links 行:id / app_id / title(按钮文字)/ url / hits / sort
?>
<?php endif; ?>
按分类分组(导航首页形态)
$sections = appnav_apps_by_cat(8); // 每分类取 8 个;0=不限;跳过空分类
foreach ($sections as $s) {
// $s['cat'] 分类行;$s['apps'] 应用行数组(含 links)
}
下载按钮(计数跳转)
foreach ($app['links'] as $l) {
echo '<a href="' . e(appnav_go_url($l['id'])) . '">' . e($l['title']) . '</a>';
}
// 点击 → ?mod=appnavgo&lid=xx → 记日志 + 应用/链接双计数 → 302 到真实地址
// 只配一条链接时 links 只有一行,模板自然只渲染一个下载按钮
广告位
$topAds = appnav_ads('top'); // 顶部横幅(多张时模板可轮播)
$midAds = appnav_ads('between'); // 分类间穿插广告(sort DESC)
// 广告行:id / title / image / url / position / cat_id / hits / sort / status
// between 行的 cat_id:>0 = 指定穿插在该分类区块之后;0 = 按顺序穿插
echo '<a href="' . e(appnav_ad_url($ad['id'])) . '"><img src="' . e($ad['image']) . '"></a>';
| 函数 | 说明 |
|---|---|
appnav_cats() | 启用中的分类列表,每行含 app_count 应用数 |
appnav_apps($opts) | 应用列表(cat_id / kw / order / per / page),每行含 links |
appnav_app($id) | 单个应用(含 links) |
appnav_count_apps($opts) | 应用总数(搜索分页用,参数同 appnav_apps) |
appnav_apps_by_cat($per) | 按分类分组取应用(导航首页形态) |
appnav_ads('top' | 'between') | 广告位内容;between 行的 cat_id 决定穿插位置 |
appnav_go_url($linkId) / appnav_ad_url($adId) | 计数跳转网址:点击先计数再 302,真实地址不外露 |
appnav_hits_h($n) | 次数人性化(如 1.2万) |
appnav_stat() | 概览统计:应用数 / 广告数 / 点击量 / 今日等 |
function_exists('appnav_cats') 判断或按 2.7 守卫写法处理,停用时显示空态引导即可。下载 / 广告链接入库前经协议白名单校验(拒绝 javascript: 等),图标与广告图走系统统一上传。4.3 接口对接(CPS)cps_connect
电商 CPS 变现的对接工具插件:配置造梦开放平台的 Bearer Token、接口路径与同步热词,一键同步热销榜(high / hot)、热词比价商品、本地生活活动,并管理商品缓存与同步日志。
?mod=cps 导购列表、?mod=cpsdetail 商品详情)由系统核心 includes/cps_api.php 与 includes/functions.php 提供,模板直接用第 2.4 节的 get_goods() 等函数取数即可。插件停用只关闭后台同步入口,前台频道照常展示已缓存数据。4.4 网址导航 nav_grabber(市场分发插件)
为导航站、网址大全、工具集类模板提供分类与链接管理,可从目标网址抓取标题、简介、图标与关键词,数据存插件私有表,不占用系统友链表。前台详情页固定为 ?mod=nav&id=X。本插件不再随主程序内置分发,从后台「插件管理 → 插件中心」在线安装。模板调用:
$cats = ng_get_cats(true); // 全部启用分类(含 item_count 链接数)
$cat = ng_get_cat_by_slug('tools'); // 按别名取分类
$items = ng_get_items($cat['id'], 24); // 取分类下的链接
$results = ng_search_items('GitHub', 30); // 按名称 / 简介 / 标签搜索
$detail = ng_detail_url($id); // 详情页地址
$logo = $item['logo'] ?: ng_logo_fallback($item['name']); // 无图标时首字母占位图
if (!ng_safe_url($url)) { /* 拒绝不安全的提交地址 */ }
| 函数 | 说明 |
|---|---|
ng_get_cats($onlyEnabled) | 全部分类,传 true 只取启用的,每项含 item_count |
ng_get_cat_by_slug($slug) | 按别名取分类 |
ng_get_items($catId, $limit) | 取分类下启用链接(按排序权重) |
ng_search_items($kw, $limit) | 按名称 / 简介 / 标签搜索 |
ng_detail_url($id) | 生成前台详情页网址 |
ng_logo_fallback($name) | 无图标时按名称首字母生成占位图地址 |
ng_safe_url($url) | 网址安全校验(表单提交地址先过它) |
插件停用后以上函数不存在,模板调用前先 function_exists('ng_get_cats') 判断,或按 2.7 守卫写法手动 require 插件核心文件。配套演示模板 navhub 含首页、分类页、搜索页、详情页与 404 页,可直接参考。
5上架应用中心
5.1 上架流程
资源从开发到出现在市场的完整链路:
- 本地开发调试:资源放主程序
templates/{slug}/或plugins/{slug}/,切后台模板 / 启用插件实测。 - 放入市场源:完整拷贝到
market/source/templates/{slug}/(或source/plugins/{slug}/)。 - 登记清单:在
market/tools/manifest.php的对应数组里登记 slug 条目(标题、描述、分类、详情富文本、价格等)。 - 打包上架:运营方执行打包工具生成 zip 并入库:
php market/tools/publish.php # 全部资源重新打包上架 php market/tools/publish.php tpl # 仅模板 php market/tools/publish.php tpl corporate,gov # 指定 slug php market/tools/publish.php --list # 只查看清单 php market/tools/publish.php --dry # 试运行不落库 - 审核发布:上架后经运营后台审核,通过即在市场前台展示,站长从后台「模板市场 / 插件中心」一键在线安装。
manifest.php 条目字段(市场侧资料,与包内 JSON 清单二者缺一回退):
| 字段 | 说明 |
|---|---|
title / desc / version / author / features | 基本信息,未填时回退包内 template.json / plugin.json |
category | 分类,必须在受控分类表内(模板:企业官网 / 政府机构 / 教育校园 / 资讯门户 / 资源下载 / 电商导购 / 深色科技 / 其他;插件另有分类表),否则归「其他」 |
content | 详情页富文本,仅允许 h3 / h4 / p / ul / li / strong / table 等基础标签 |
price / require_level | 价格与授权等级。require_level 非空即付费资源(见 5.3) |
featured / is_top | 官方精选 / 置顶推荐 |
meta_title / meta_desc / meta_kw | 详情页 SEO |
demo_url / status | 演示网址(默认隐藏)/ 上下架状态(默认 online) |
5.2 打包规则(安装端强校验)
- zip 内以
{slug}/为唯一根目录(也支持外层多包一层目录,安装端会自动定位清单文件)。 - 插件包必须含清单声明的主入口文件;模板包必须含 7 个必需页面文件与含
slug的 template.json。 - 扩展名白名单:php / html / css / js / json / txt / md / xml / sql / png / jpg / jpeg / gif / svg / webp / ico / eot / ttf / woff / woff2 / map。
- 单文件 ≤ 5MB,单包 ≤ 20MB;隐藏文件(. 开头)与
.git等版本库目录自动剔除。 - 解压全程路径穿越防护;PHP 文件在安装 / 启用前会做全量语法预检。
5.3 免费与付费资源
清单里 require_level 留空即免费资源,站长一键安装无任何限制。
require_level 非空即付费资源,打包时会自动在入口文件注入域名授权校验(license-guard.php):
- 站长安装后,资源首次运行时向应用中心核验「当前站点域名是否已登记授权」。
- 核验结果带签名缓存 15 天,断网宽限 45 天;前台访客不受任何影响(完全静默)。
- 未授权仅在站长后台「模板管理 / 插件管理」页显示风险徽标,并提供「验证授权」按钮联网复查;站长可到授权中心免费登记域名。
5.4 上架自检清单
- slug 合规(仅字母数字下划线连字符),且市场内无重名资源。
- 必需文件齐全:模板 7 件套 + 含 slug 的 template.json;插件 main.php + plugin.json。
- 全部 PHP 文件本地
php -l通过(否则安装 / 启用被拒)。 - 配置键、函数名、数据表均带自己的 slug 前缀,无冲突。
- 模板页面没有手动 include header / footer;SEO 分支写在 header.php;动态输出全部
e()。 - 插件 POST 表单带
_csrf;SQL 全占位符传参;删除数据禁止_/%模糊匹配。 - 预览图就位(目录内
preview.svg或preview.png,缺失会生成占位图)。 - manifest.php 已登记条目,分类正确,详情富文本只用了允许标签。