模块二开mokuai

TSCMS模块开发教程

时间:2026-07-27 22:46 阅读:300 来源:腾石科技

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 / 模块IDint(11)主键,自增
statustinyint(1)状态:1=正常, 0=禁用/待审, -1=删除
listorderint(10)排序值(升序)
createtimeint(10)创建时间(Unix 时间戳)
updatetimeint(10)更新时间(Unix 时间戳)

模块注册表(tc_module):

字段说明
module模块标识(小写英文,如 swiper)
name模块中文名称
description模块描述
version版本号
settingJSON 格式的模块配置
status启用状态

权限菜单表(tc_menu)关键字段:

字段说明
id菜单/权限节点ID
parentid父菜单ID
app模块名(如 swiper)
controller控制器名
action方法名
name菜单名称
status1=启用(参与权限校验)
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 记录,还要确保:

  1. 安装 SQL 包含建表语句和默认数据

  2. 卸载 SQL 包含删除表的语句

  3. 后台控制器继承 Adminbase

七、继承体系说明

基类继承使用场景
app\common\controller\Basethink\Controller所有控制器的祖先,提供增删改查方法
app\common\controller\AdminbaseBase后台控制器,含登录校验/RBAC权限/日志
app\common\controller\IndexbaseBase前台控制器,含模板路径设置/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
加入收藏