模块二开
mokuai
TSCMS模块开发教程
TSCMS的模块开发完全使用Thinkphp的方法即可,没有任何封装和强制写法,只需要安装Thinkphp5.1的规范去走即可。
一、模块目录结构
一个标准的 TScms 模块包含以下目录:
application/
└── {modulename}/ # 模块名称(小写英文)
├── controller/ # 控制器
│ ├── Xxx.php # 后台控制器(继承 Adminbase)
│ ├── Index.php # 前台控制器(继承 Indexbase)[可选]
│ └── Api.php # API 控制器(继承 Base)[可选]
├── model/ # 数据模型
│ └── Xxx.php # 模型文件
├── validate/ # 验证器 [可选]
│ └── Xxx.php # 验证器文件
├── taglib/ # 模板标签 [可选]
│ └── TagXxx.php # 标签解析类({ts:模块名})
├── view/ # 后台视图模板
│ └── xxx/
│ ├── index.html
│ └── add.html
├── config/ # 模块配置文件 [可选]
├── install/ # 安装 SQL 脚本 [可选]
│ └── install.sql
├── uninstall/ # 卸载 SQL 脚本 [可选]
│ └── uninstall.sql
└── ... # 其他辅助代码二、数据库表结构
务必了解的公共字段:
| 字段名 | 类型 | 说明 |
|---|---|---|
| id / 模块ID | int(11) | 主键,自增 |
| status | tinyint(1) | 状态:1=正常, 0=禁用/待审, -1=删除 |
| listorder | int(10) | 排序值(升序) |
| createtime | int(10) | 创建时间(Unix 时间戳) |
| updatetime | int(10) | 更新时间(Unix 时间戳) |
模块注册表(tc_module):
| 字段 | 说明 |
|---|---|
| module | 模块标识(小写英文,如 swiper) |
| name | 模块中文名称 |
| description | 模块描述 |
| version | 版本号 |
| setting | JSON 格式的模块配置 |
| status | 启用状态 |
权限菜单表(tc_menu)关键字段:
| 字段 | 说明 |
|---|---|
| id | 菜单/权限节点ID |
| parentid | 父菜单ID |
| app | 模块名(如 swiper) |
| controller | 控制器名 |
| action | 方法名 |
| name | 菜单名称 |
| status | 1=启用(参与权限校验) |
| level | 层级,用于导航和权限树显示 |
💡 权限节点 = 后台菜单。角色授权时,权限树由 tc_menu 表中 status=1 的菜单生成。
三、快速开始:开发一个"公告"模块
步骤 1:创建模块目录
application/notice/ ├── controller/ │ └── Notice.php ├── model/ │ └── Notice.php ├── validate/ │ └── Notice.php ├── view/ │ └── notice/ │ ├── index.html │ ├── add.html │ └── edit.html └── taglib/ └── TagNotice.php
步骤 2:创建数据表
CREATE TABLE `tc_notice` ( `id` int(11) NOT NULL AUTO_INCREMENT, `title` varchar(255) NOT NULL COMMENT '公告标题', `content` text COMMENT '公告内容', `status` tinyint(1) DEFAULT '1' COMMENT '状态', `listorder` int(10) DEFAULT '0' COMMENT '排序', `createtime` int(10) DEFAULT '0' COMMENT '创建时间', `updatetime` int(10) DEFAULT '0' COMMENT '更新时间', PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
步骤 3:注册模块
向 tc_module 表插入记录:
INSERT INTO `tc_module` (`module`, `name`, `description`, `version`, `status`)
VALUES ('notice', '公告管理', '网站公告管理模块', '1.0', 1);步骤 4:创建模型
namespace app\note\model;
use think\Model;
class Notice extends Model
{
protected $table = 'tc_notice';
protected $pk = 'id';
protected $autoWriteTimestamp = true;
protected $createTime = 'createtime';
protected $updateTime = 'updatetime';
/**
* 分页列表
*/
public function list s ($where = [], $page = 1, $limit = 15)
{
return $this->where($where)
->order('listorder ASC, id DESC')
->page($page, $limit)
->select();
}
/**
* 新增
*/
public function add ($data)
{
return self::create($data) ? true : false;
}
/**
* 详情
*/
public function detail($id)
{
return $this->where(['id' => $id])->find();
}步骤 5:创建后台控制器
namespace app\Notice\controller;
use app\common\controller\Adminbase;
use app\Notice\model\Notice as NoticeModel;
class Notice extends Adminbase
{
protected $noNeedLogin = [];
protected $noAuth = [];
protected function initialize()
{
parent::initialize();
$this->NoticeModel = new NoticeModel();
}
public function index()
{
if ($this->request->isAjax()) {
$param = $this->request->param();
$where = [];
$page = trim($param['page']) ?: 1;
$limit = $param['limit'];
$list = $this->NoticeModel->lists($where, $page, $limit);
$count = $this->NoticeModel->where($where)->count();
return json([
'code' => 0,
'msg' => '',
'count' => $count,
'data' => json_decode(json_encode($list)),
]);
}
return $this->fetch();
}
public function add()
{
if ($this->request->isPost()) {
$data = $this->request->post();
$result = $this->validate($data, 'app\note\validate\Notice.add');
if (true !== $result) {
return $this->error($result);
}
if ($this->NoticeModel->add($data)) {
$this->insertLog('新增公告');
return json(['code' => 1, 'msg' => '添加成功', 'data' => '', 'url' => '']);
}
return json(['code' => 0, 'msg' => '添加失败', 'data' => '', 'url' => '']);
}
return $this->fetch();
}
public function edit()
{
if ($this->request->isPost()) {
$data = $this->request->post();
$result = $this->validate($data, 'app\note\validate\Notice.edit');
if (true !== $result) {
return $this->error($result);
}
if ($this->NoticeModel->save($data, ['id' => $data['id']]) !== false) {
$this->insertLog('编辑公告');
return json(['code' => 1, 'msg' => '修改成功', 'data' => '', 'url' => '']);
}
return json(['code' => 0, 'msg' => '修改失败', 'data' => '', 'url' => '']);
} else {
$id = $this->request->param('id/d', 0);
$data = $this->NoticeModel->find($id);
$this->assign('data', $data);
return $this->fetch();
}
}
public function del()
{
if ($this->request->isAjax()) {
$ids = $this->request->param('id', null);
if (empty($ids)) return $this->error('参数错误!');
$ids = rtrim($ids, ',');
$where = strpos($ids, ',') !== false
? ['id', 'IN', $ids]
: ['id' => $ids];
if ($this->NoticeModel->where($where)->delete()) {
$this->insertLog('删除公告');
return json(['code' => 1, 'msg' => '删除成功', 'data' => '', 'url' => '']);
}
return json(['code' => 0, 'msg' => '删除失败', 'data' => '', 'url' => '']);
}
}
public function changeStatus()
{
$id = $this->request->param('id/d', 0);
$status = $this->request->param('status/d', 0);
if (empty($id)) return $this->error('请选择需要修改的内容!');
$result = $this->NoticeModel->isUpdate(true)->save(['status' => $status], ['id' => $id]);
if ($result) {
return json(['code' => 1, 'msg' => '修改成功', 'data' => '']);
}
return json(['code' => 0, 'msg' => '修改失败', 'data' => '']);
}
public function listorder()
{
$id = $this->request->param('id/d', 0);
$listorder = $this->request->param('listorder/d', 0);
if (empty($id)) return $this->error('请选择需要修改的内容!');
$result = $this->NoticeModel->isUpdate(true)->save(['listorder' => $listorder], ['id' => $id]);
return json($result
? ['code' => 1, 'msg' => '修改成功', 'data' => '']
: ['code' => 0, 'msg' => '修改失败', 'data' => '']
);
}步骤 6:创建验证器
namespace app\noticee\validate;
use think\Validate;
class Notice extends Validate
{
protected $rule = [
'title' => 'require|length:2,100',
];
protected $message = [
'title.require' => '标题不能为空',
'title.length' => '标题长度 2-100 个字符',
];
public function sceneAdd()
{
return $this->only(['title', 'content', 'status', 'listorder']);
}
public function sceneEdit()
{
return $this->remove('title', 'require');
}
}步骤 7:创建标签解析类
namespace app\note\taglib;
use think\Db;
class TagNotice
{
/**
* 公告列表标签
* 用法:{ts module="notice" action="lists" num="10"}
* 参数:num=数量, where=条件, order=排序
*/
public function list s ($data)
{
$num = isset($data['num']) && (int)$data['num'] > 0 ? (int)$data['num'] : 10;
$order = isset($data['order']) ? $data['order'] : 'listorder DESC, id DESC';
$where = isset($data['where']) ? $data['where'] : '';
$query = Db::name('notice')->where('status', 1);
if ($where) {
$query->whereRaw($where);
}
// 排序
if (is_string($order)) {
$query->orderRaw($order);
} else {
$query->order($order);
}
$result = $query->limit($num)->select();
if (!empty($result)) {
$result = is_object($result) ? $result->toArray() : $result;
}
return $result ?: [];
}
/**
* 公告详情标签
* 用法:{ts:notice action="info" id="1"}{$vo.title}{/ts}
*/
public function info($data)
{
$id = isset($data['id']) ? intval($data['id']) : 0;
if (!$id) return [];
$result = Db::name('notice')->where(['id' => $id, 'status' => 1])->find();
return $result ?: [];
}
}步骤 8:添加权限菜单
INSERT INTO `tc_menu` (`id`, `parentid`, `app`, `controller`, `action`, `name`, `status`, `level`) VALUES (NULL, 0, 'notice', 'admin', 'index', '公告管理', 1, 1), (NULL, last_insert_id(), 'notice', 'admin', 'index', '公告列表', 1, 2), (NULL, last_insert_id(), 'notice', 'admin', 'add', '添加公告', 1, 3), (NULL, last_insert_id(), 'notice', 'admin', 'edit', '编辑公告', 1, 3), (NULL, last_insert_id(), 'notice', 'admin', 'del', '删除公告', 1, 3);
⚠ 添加菜单后必须清除 runtime/cache 目录才能使新菜单生效,因为 adminMenu() 有 60 秒缓存。
步骤 9:前台模板中调用
{ts module="notice" action="lists" num="5"}
{$vo.title} - {date('Y-m-d', $vo.createtime)}{/ts}
{ts module="notice" action="info" id="1"}
{$vo.title} {$vo.content}{/ts}💡 标签调用格式:{ts module="模块名" action="方法名"},关闭标签统一用 {/ts}。
自动加载规则:Tag{首字母大写的模块名} → \app\{模块名}\taglib\Tag{模块名}
四、后台视图模板规范
列表页(index.html)
{extend name="../../common/view/main" /}
{block name="body"}{/block}表单页(add.html / edit.html)
{extend name="../../common/view/main" /}
{block name="body"} 标题 内容 {$data.content|default=''} 提交 {/block}五、内容模型开发(动态表)
内容模型使用主副表结构:
主表:
tc_{tablename}(如 tc_article)副表:
tc_{tablename}_data(如 tc_article_data)
内容模型查询示例
use app\content\model\Content as ContentModel; $cm = new ContentModel(); // 列表查询 $result = $cm->lists($tablename, $where, $field, $order, $page, $pagesize, $moreinfo); // 获取详情(联查副表) $detail = $cm->detail($id, $catid); // 条件查询内容 $content = $cm->getContent($catid, $modelid, $where, $order, $field, $moreinfo); // 新增内容 $data = [ 'title' => '标题', 'content' => '内容', 'catid' => 1, // ... ]; $id = $cm->add($data); // 自动处理数据(远程图片下载、摘要提取、缩略图生成) $processed = $cm->autoHandleData($data, $modelid); // 格式化输出字段(radio/select/checkbox/images 等) $formatted = $cm->formatData($data, $modelid);
六、可插拔模块规范
如果你想开发一个可以独立安装/卸载的模块(如 pay、collect 等),需要包含:
{modulename}/
├── install/
│ └── install.sql # 安装时执行的 SQL
├── uninstall/
│ └── uninstall.sql # 卸载时执行的 SQL
├── controller/
├── model/
├── validate/
├── taglib/ # [可选] 标签库
└── view/注册模块时,除了插入 tc_module 记录,还要确保:
安装 SQL 包含建表语句和默认数据
卸载 SQL 包含删除表的语句
后台控制器继承 Adminbase
七、继承体系说明
| 基类 | 继承 | 使用场景 |
|---|---|---|
| app\common\controller\Base | think\Controller | 所有控制器的祖先,提供增删改查方法 |
| app\common\controller\Adminbase | Base | 后台控制器,含登录校验/RBAC权限/日志 |
| app\common\controller\Indexbase | Base | 前台控制器,含模板路径设置/XSS过滤 |
| app\content\controller\Indexbase | - | 前台统一使用 Indexbase(common模块) |
后台控制器白名单
class YourController extends Adminbase
{
protected $noNeedLogin = ['login']; // 免登录
protected $noAuth = ['getList']; // 已登录但免权限
}八、路由规范
后台路由通常使用默认 TP5 路由:/admin/{controller}/{action}
新增后台菜单后,访问路径为:/admin/控制器名/方法名
前台路由注册在 route/route.php,URL 规则存储在 tc_urlrule 表:
// 手动注册示例
Route::get('notice/:id', 'notice/Index/detail')->pattern(['id' => '\d+']);九、常用工具函数
// 图片处理
$thumbPath = thumb($srcPath, 300, 200); // 生成缩略图
// 表单构建
$formHtml = buildForm($formid); // 自动生成表单HTML
// 系统配置
$siteName = getConfig('site_name'); // 读取系统配置
// 栏目相关
$catInfo = getCategory($catid); // 获取栏目信息
$catUrl = buildCatUrl($catid); // 栏目URL
$articleUrl = buildContentUrl($catid, $id); // 内容URL
$catPos = catpos($catid); // 面包屑
$seo = seo($catid, $title); // SEO信息
// 登录检测
if (is_admin_login()) { /* 后台已登录 */ }
if (is_user_login()) { /* 前台已登录 */ }
// 模型判断
if (module_exists('pay')) { /* pay模块已安装 */ }
// 缓存操作
cache('key', $value, 3600); // 写入缓存
cache('key', null); // 清除缓存
clear_cache(); // 清空runtime/cache 


