百木园-与人分享,
就是让自己快乐。

RXThinkCMF_TP6

一款基于ThinkPHP6、Layui、MySQL精心打造的权限(RBAC)及内容管理框架

项目介绍

RXThinkCMF_TP6 是一款基于 ThinkPHP6 +Layui2.5.6 开发权限(RBAC)及内容管理框架,框架中集成了权限管理、模块管理、插件管理、钩子管理、数据库管理、富文本编辑器(已集成ueditor,kindeditor),后台支持多主题切换、布局管理、广告管理、配置管理、字典管理、切图管理、CMS内容管理等常用功能模块,以方便开发者快速构建自己的应用。专注于为中小企业提供最佳的行业基础后台框架解决方案,执行效率、扩展性、稳定性值得信赖,操作体验流畅,使用非常优化,欢迎大家使用及进行二次开发。

  • 模块化:全新的架构和模块化的开发机制,便于灵活扩展和二次开发。
  • 模型/栏目/分类信息体系:通过栏目和模型绑定,以及不同的模型类型,不同栏目可以实现差异化的功能,轻松实现诸如资讯、下载、讨论和图片等功能。通过分类信息和栏目绑定,可以自动建立索引表,轻松实现复杂的信息检索。
  • 一套基于ThinkPHP6 + Layui 研发的后台开发框架。
  • 支持SQLServer、MySQL、Oracle、PostgreSQL、SQLite等多数据库类型。模块化设计,层次结构清晰。
  • AUTH权限认证,操作权限控制精密细致,对所有管理链接都进行权限验证,可控制到导航菜单、功能按钮。提高开发效率及质量。
  • 常用类封装,日志、缓存、验证、字典、文件(本地、七牛云)。等等,目前兼容浏览器(Chrome、Firefox、360浏览器等)
  • 适用范围:可以开发OA、ERP、BPM、CRM、WMS、TMS、MIS、BI、电商平台后台、物流管理系统、快递管理系统、教务管理系统等各类管理软件。

#环境要求:

  • PHP >= 7.0
  • PDO PHP Extension
  • MBstring PHP Extension
  • CURL PHP Extension
  • 开启静态重写
  • 要求环境支持pathinfo
  • 要求安装Zip扩展(插件/模块市场需要)

#功能特性

  • 严谨规范: 提供一套有利于团队协作的结构设计、编码、数据等规范。
  • 高效灵活: 清晰的分层设计、钩子行为扩展机制,解耦设计更能灵活应对需求变更。
  • 严谨安全: 清晰的系统执行流程,严谨的异常检测和安全机制,详细的日志统计,为系统保驾护航。
  • 组件化: 完善的组件化设计,丰富的表单组件,让开发列表和表单更得心应手。无需前端开发,省时省力。
  • 简单上手快: 结构清晰、代码规范、在开发快速的同时还兼顾性能的极致追求。
  • 自身特色: 权限管理、组件丰富、第三方应用多、分层解耦化设计和先进的设计思想。
  • 高级进阶: 分布式、负载均衡、集群、Redis、分库分表。
  • 命令行: 命令行功能,一键管理应用扩展

快速部署

#环境要求

ThinkPHP6.0的环境要求如下:

  • PHP >= 7.1.0

#更新依赖库

如果还没有安装 Composer,在 Linux 和 Mac OS X 中可以运行如下命令:

curl -sS https://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer

1
2

在 Windows 中,你需要下载并运行 《Composer-Setup.exe》 (opens new window)。 如果遇到任何问题或者想更深入地学习 Composer,请参考Composer 文档(英文文档,中文文档)。

框架本身已经全部安装好了第三方依赖包,无需再次安装,如果单独引入其他依赖包仍然需要您安装Composer手动去安装一下依赖;

更新之前需要更换镜像源 配置全局生效

composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/

1

取消全局配置

composer config -g --unset repos.packagist

1

重要通知:

TIP

鉴于近期网上下载的项目运行报错的问题,请大家自行更新一下第三方库,在命令行终端项目根目录输入:composer update 更新第三方依赖库

#开启调试

应用默认是部署模式,在开发阶段,可以修改环境变量APP_DEBUG开启调试模式,上线部署后切换到部署模式。

本地开发的时候可以在应用根目录下面定义.env文件。

#测试运行

现在只需要做最后一步来验证是否正常运行。 进入命令行下面,执行下面指令

php think run

在浏览器中输入地址:

http://localhost:8000/

会看到欢迎页面。恭喜你,现在已经完成ThinkPHP6.0的安装!

如果你本地80端口没有被占用的话,也可以直接使用

php think run -p 80

然后就可以直接访问:

http://localhost/

目录结构

#项目结构

根据实际场景的需要,框架在设计时直接采用了多模块应用架构,具体目录结构如下所示::

RXThinkCMF_TP6    
├── app                                   // 应用目录
|  └── admin                              // 后台管理层
│       └── command                       // 命名
│       └── config                        // 后端配置
│       └── controller                    // 控制器层
│       └── event                         // 事件
│       └── lang                          // 语言包
│       └── model                         // 模型层
│       └── route                         // 路由
│       └── service                       // 服务层
│       └── validate                      // 数据验证
│       └── view                          // 业务表单
│       └── widget                        // 后端组件
|  └── api                                // 接口层
|  └── common                             // 系统公共层
│       └── controller                    // 控制器层
│       └── lang                          // 语言包
│       └── middleware                    // 中间件
│       └── model                         // 模型层
│       └── service                       // 服务层
|  └── index                              // 前端PC站
|  └── m                                  // WAP站
|  └── script                             // 业务脚本
│  │
│  ├─BaseController.php                   // 默认基础控制器类
│  ├─ExceptionHandle.php                  // 应用异常定义文件
│  ├─common.php                           // 全局公共函数文件
│  ├─middleware.php                       // 全局中间件定义文件
│  ├─provider.php                         // 服务提供定义文件
│  ├─Request.php                          // 应用请求对象
│  └─event.php                            // 全局事件定义文件
├──config                                 // 配置目录
│  ├─app.php                              // 应用配置
│  ├─cache.php                            // 缓存配置
│  ├─console.php                          // 控制台配置
│  ├─cookie.php                           // Cookie配置
│  ├─database.php                         // 数据库配置
│  ├─filesystem.php                       // 文件磁盘配置
│  ├─lang.php                             // 多语言配置
│  ├─log.php                              // 日志配置
│  ├─middleware.php                       // 中间件配置
│  ├─route.php                            // URL和路由配置
│  ├─session.php                          // Session配置
│  ├─trace.php                            // Trace配置
│  └─view.php                             // 视图配置
├── extend                                // 系统扩展层
├── public                                // 资源文件
├── route                                 // 路由
├── runtime                               // 临时文件目录
├── vendor                                // 第三方依赖库

#主要新特性

  • 采用PHP7强类型(严格模式)
  • 支持更多的PSR规范
  • 多应用支持
  • ORM组件独立
  • 改进的中间件机制
  • 更强大和易用的查询
  • 全新的事件系统
  • 支持容器invoke回调
  • 模板引擎组件独立
  • 内部功能中间件化
  • SESSION机制改进
  • 缓存及日志支持多通道
  • 引入Filesystem组件
  • 对Swoole以及协程支持改进
  • 对IDE更加友好
  • 统一和精简大量用法

总结:本章节大概的表述了项目的目录结构及配置文件内容,下一章节《基础配置》我们将详细的解读配置文件中具体的参数及意义

基础配置

#概述

初次接触一个新项目时,会给人一种一筹莫展的感觉,尤其是一个全新的未知的项目,首页我们拿到一个新项目(或新框架),首页我们先看下项目框架的具体架构和设计思路,在上一章节《目录结构》中我们做了详细的介绍,这里我们不做扩展,本章节我们重点介绍下框架的基础配置;

#项目依赖

框架使用Composer作为依赖包管理工具,在开始使用项目前请务必安装好第三方依赖库,以便项目在实际运行过程中出现缺少依赖或者莫名的报错问题(切记),框架默认集成的依赖如下:

\"php\": \">=7.1.0\",
\"topthink/framework\": \"^6.0.0\",
\"topthink/think-image\": \"^1.0\",
\"topthink/think-captcha\": \"^3.0.3\",
\"topthink/think-orm\": \"^2.0\",
\"topthink/think-multi-app\": \"^1.0\",
\"topthink/think-view\": \"^1.0\",
\"overtrue/pinyin\": \"~4.0\",
\"phpmailer/phpmailer\": \"^6.0\",
\"phpoffice/phpexcel\": \"^1.8\",
\"phpoffice/phpword\": \"^0.14.0\",
\"aliyuncs/oss-sdk-php\": \"^2.3\",
\"qiniu/php-sdk\": \"^7.2\",
\"endroid/qrcode\": \"^3.5\",
\"jpush/jpush\": \"*\",
\"php-amqplib/php-amqplib\": \"2.7.*\",
\"alibabacloud/sdk\": \"^1.8\",
\"php-ffmpeg/php-ffmpeg\": \"^0.14.0\",
\"topthink/think-template\": \"^2.0\"

由于ThinkPHP6框架本身将以前的版本(如5.15.0)中集成的部分第三方依赖进行了剔除,所以我们在使用ThinkPHP6进行项目开发时需要手动将一些必备的依赖包进行自行加入,如模板支持依赖think-template、多应用支持依赖think-multi-app、视图绑定依赖think-view等等基础依赖,同时根据现实需求,我们还引入了阿里短信依赖alibabacloud/sdk、多媒体文件处理依赖php-ffmpeg/php-ffmpeg、消息中间件依赖php-amqplib/php-amqplib以及消息推送依赖jpush/jpush等等;如需其他依赖,请根据现实的业务做相应的依赖引入,然后安装即可;

#环境变量

系统环境变量配置文件.env,系统运行所需的参数如有需要都可以作为环境变量参数存在,内容如下:

APP_DEBUG = false

[APP]
DEFAULT_TIMEZONE = Asia/Shanghai
// HOST域名
host = www.demo.cn

// 数据库配置
[DATABASE]
TYPE = mysql
HOSTNAME = 127.0.0.1
DATABASE = rxthink.pro6
USERNAME = root
PASSWORD =
HOSTPORT = 3306
CHARSET = utf8mb4
PREFIX = think_
DEBUG = true

// 缓存配置
[CACHE]
TYPE = redis
HOST = 127.0.0.1
PORT = 6379
PASSWORD =
SELECT = 1
TIMEOUT = 0
EXPIRE = 0
PREFIX = PRO6_

[LANG]
default_lang = zh-cn

// 系统配置
[SYSTEM]
SITENAME = RXThinkCMF_TP6_PRO旗舰版
NICKNAME = 旗舰版v1.0.0
VERSION = v1.0.0

// 网址配置
[DOMAIN]
IMG_URL = http://images.demo.cn

备注:APP_DEBUG参数表示是否开启调试模式,host参数为网站主域名,如www.baidu.com,DATABASE为数据库连接相关配置,CACHE系统缓存配置,对应的配置文件是cache.php,SYSTEM是系统常规参数配置,定义了系统的名称及版本号等信息,DOMAIN主要设定系统访问需要的域名,如图片域名(需单独配置);

IMG_URL这里声明的是系统的图片域名,系统会读取当前参数做图片访问域名解析使用,在下文(《虚拟主机配置》)[]中会做详细说明;

#应用配置

系统公共应用配置文件app.php,应用配置文件中提供了基础全部参数的配置,系统架构采用的是多引用模式,域名和多应用模块目录的绑定也在此文件中配置,域名绑定参数为:domain_bind,域名及相关目录的绑定下面有更详细的说明,配置文件内容如下:

return [
    // 应用地址
    \'app_host\'         => env(\'app.host\', \'\'),
    // 应用的命名空间
    \'app_namespace\'    => \'\',
    // 是否启用路由
    \'with_route\'       => true,
    // 是否启用事件
    \'with_event\'       => true,
    // 默认应用
    \'default_app\'      => \'index\',
    // 默认时区
    \'default_timezone\' => \'Asia/Shanghai\',

    // 应用映射(自动多应用模式有效)
    \'app_map\'          => [],
    // 域名绑定(自动多应用模式有效)
    \'domain_bind\'      => [
        \'www\'          =>  \'index\',
        \'admin\'        =>  \'admin\',
        \'m\'            =>  \'m\',
        \'api\'          =>  \'api\',
        \'script\'       =>  \'script\',
    ],
    // 禁止URL访问的应用列表(自动多应用模式有效)
    \'deny_app_list\'    => [\'common\'],

    // 异常页面的模板文件
    \'exception_tmpl\'   => app()->getThinkPath() . \'tpl/think_exception.tpl\',

    // 错误显示信息,非调试模式有效
    \'error_message\'    => \'页面错误!请稍后再试~\',
    // 显示错误信息
    \'show_error_msg\'   => false,
];

此处需要详细说明的是domain_bind域名绑定配置参数:

  • 二级域名www绑定模块index;
  • 二级域名admin绑定模块admin;
  • 二级域名m绑定模块m;
  • 二级域名api绑定模块api;
  • 二级域名script绑定模块script;

同时上述域名绑定配置,此时我们及可以通过www.demo.cn域名访问前台网站模块,admin.demo.cn域名访问后台系统模块,m.demo.cn域名访问手机站(WAP站)模块,api.demo.cn访问接口模块,script.demo.cn访问脚本模块;图片(images)域名需要单独配置,环境变量配置文件中有具体的说明;

切记如果是本地部署时请在本地hosts文件中写入本地127.0.0.1解析,以上为例hosts配置为:127.0.0.1 www.demo.cn admin.demo.cn m.demo.cn api.demo.cn script.demo.cn images.demo.cn

#缓存配置

为了系统的性能提升和运行效率,框架底层已经友好的封装了继承了缓存功能,只需要开启即可,根据系统设计要求,框架目前支持两种缓存模式:文件缓存Redis缓存,缓存配置文件内容如下:

return [
    // 默认缓存驱动
    \'default\' => env(\'cache.driver\', \'redis\'),

    // 缓存连接方式配置
    \'stores\'  => [
        // File文件缓存
        \'file\' => [
            // 驱动方式
            \'type\'       => \'File\',
            // 缓存保存目录
            \'path\'       => \'\',
            // 缓存前缀
            \'prefix\'     => \'\',
            // 缓存有效期 0表示永久缓存
            \'expire\'     => 0,
            // 缓存标签前缀
            \'tag_prefix\' => \'tag:\',
            // 序列化机制 例如 [\'serialize\', \'unserialize\']
            \'serialize\'  => [],
        ],
        // Redis缓存配置
        \'redis\' => [
            // 驱动方式
            \'type\'       => \'redis\',
            \'host\'       => env(\'redis.host\', \'127.0.0.1\'),
            \'port\'       => env(\'redis.port\', 6379),
            \'password\'   => env(\'redis.password\', \'\'),
            \'select\'     => env(\'redis.select\', 0),
            \'timeout\'    => env(\'redis.timeout\', 0),
            // 全局缓存有效期(0为永久有效)
            \'expire\'     => env(\'redis.expire\', 0),
            \'persistent\' => env(\'redis.persistent\', \'\'),
            // 缓存前缀
            \'prefix\'     => env(\'redis.prefix\', \'TH_\'),
            \'tag_prefix\' => env(\'redis.tag_prefix\', \'tag:\'),
            \'serialize\'  => [],
        ],
        // 更多的缓存连接
    ],
];

如以上内容所示,缓存驱动的模式从环境变量中获取env(\'cache.driver\', \'redis\')默认系统使用的是redis缓存,所以再使用之前,需要开启Redis服务的支持,根据缓存模式的不同,各自的参数及配置值也是有所差异的,有关配置参数有不明白请查询相关资料;

#短信配置

在众多的场景中,我们或多或少的都会使用到短信发送的功能,因此在设计系统时已经把这块功能集成进去了,特地为短信发送单独建立一个配置文件sms.php,文件内容如下:

/**
 * 短信配置类
 */
return [
    // 阿里云短信
    \'aliyunsms\' => [
        \'accessKeyId\' => \'LTAIiiZGTbjvJRFMAE\',
        \'accessKeySecret\' => \'xkki8v10wV3uUx637mk4eNNykTaMzNFGH\',
        \'signName\' => \'研发中心\',
        \'templateCode\' => \'SMS_18122342354\',
        \'templateParam\' => \'\',
    ],
    // 更多短信平台账号请依次配置。。。
];

根据上述得知,实现短信发送功能需要安装阿里短信SDK依赖包alibabacloud/sdk,根据业务实现我们封装了短信发送工具类及发送短信方法sendSms,具体内容请移至框架扩展目录extend中的AliSms.php文件进行查询;

#布局配置

上述我们所描述的都是公共配置中的内容,当然我们也可以针对具体的业务层模块进行局部配置,此处我们以后台admin模块举例说明,根据系统架构设计需求我们开启多模板布局的功能,因此我们要针对模板布局配置一下参数,配置文件为config/view.php,内容如下:

return [
    // 是否开启模板编译缓存,设为false则每次都会重新编译
    \'tpl_cache\'          => false,
    // 布局模板开关
    \'layout_on\'          => true,
    // 布局模板入口文件
    \'layout_name\'        => \'public/layout\',
    // 布局模板的内容替换标识
    \'layout_item\'        => \'{__CONTENT__}\',
    // 视图输出字符串内容替换
    \'tpl_replace_string\' => [
        \'__STATIC__\'     => \'/static\',
        \'__ADMIN__\'      => \'/static/admin\',
        \'__JS__\'         => \'/static/admin/js\',
        \'__CSS__\'        => \'/static/admin/css\',
        \'__IMAGES__\'     => \'/static/admin/images\',
    ],
    \'taglib_build_in\'    => \'cx\',
    // 预先加载的标签库
    \'taglib_pre_load\'    => implode(\',\', [
        \\app\\admin\\widget\\Widget::class,
        \\app\\admin\\widget\\Common::class,
        \\app\\admin\\widget\\Upload::class,
        \\app\\admin\\widget\\Item::class,
        \\app\\admin\\widget\\Editor::class,
        \\app\\admin\\widget\\Layout::class,
        \\app\\admin\\widget\\Icon::class,
        \\app\\admin\\widget\\Checkbox::class,
        \\app\\admin\\widget\\City::class,
        \\app\\admin\\widget\\Date::class,
    ]),
];

参数分析:

  • 设计模板布局配置的参数主要有tpl_cachelayout_onlayout_namelayout_itemtpl_replace_string,其中layout_name是设置布局文件,默认是public/layout文件;参数tpl_replace_string是模板替换标签,如__ADMIN____IMAGES__声明之后可以全局使用,无需在写复杂的路径;
  • 由于ThinkPHP6取消了widget的支持,所以我们使用模板标签来封装框架的UI组件,因此这里需要配置对模板标签的支持(这里是后台的组件,如果是其他模块需要在各自模块内配置相关的模板标签支持),主要涉及参数有taglib_build_intaglib_pre_load,重点需要说明的是,所有的模板标签都需要在此处进行生命,否则模板标签无法正常解析,一定切记。对于参数的具体意思可以直接查看TP官方文档或者查阅相关资料,具体的组件我们会在《UI组件》章节中会做具体的解读;

同时在模块内部我们需要对一些常规的常量进行配置,ThinkPHP6已经为我们做了架构层面的解析,我们只需要在模块内建立config文件夹,以admin模块为例,我们在config局部配置目录里面新建文件admin.php,在此文件中我们写入常规的配置参数,使用系统本身的获取配置的方式即可获取,如config(\'admin.gender_list\'),系统admin.php详细内容如下:

<?php
// +----------------------------------------------------------------------
// | RXThinkCMF框架 [ RXThinkCMF ]
// +----------------------------------------------------------------------
// | 版权所有 2017~2020 南京RXThinkCMF研发中心
// +----------------------------------------------------------------------
// | 官方网站: http://www.rxthink.cn
// +----------------------------------------------------------------------
// | Author: 牧羊人 <1175401194@qq.com>
// +----------------------------------------------------------------------

/**
 * 后台配置类
 *
 * @author 牧羊人
 * @since 2020-04-21
 */
return [

    /**
     * 性别
     */
    \'gender_list\' => [
        1 => \'男\',
        2 => \'女\',
        3 => \'未知\',
    ],

    /**
     * 菜单类型
     */
    \'menu_type\' => [
        1 => \'模块\',
        2 => \'导航\',
        3 => \'菜单\',
        4 => \'节点\',
    ],

    /**
     * 菜单节点
     */
    \'menu_func\' => [
        1 => \'列表\',
        5 => \'添加\',
        10 => \'修改\',
        15 => \'删除\',
        20 => \'详情\',
        25 => \'状态\',
    ],

    /**
     * 配置类型
     */
    \'config_type\' => [
        \'hidden\' => \'隐藏\',
        \'readonly\' => \'只读文本\',
        \'number\' => \'数字\',
        \'text\' => \'单行文本\',
        \'textarea\' => \'多行文本\',
        \'array\' => \'数组\',
        \'password\' => \'密码\',
        \'radio\' => \'单选框\',
        \'checkbox\' => \'复选框\',
        \'select\' => \'下拉框\',
        \'icon\' => \'字体图标\',
        \'date\' => \'日期\',
        \'datetime\' => \'时间\',
        \'image\' => \'单张图片\',
        \'images\' => \'多张图片\',
        \'file\' => \'单个文件\',
        \'files\' => \'多个文件\',
        \'ueditor\' => \'富文本编辑器\',
        \'json\' => \'JSON\',
    ],

    /**
     * 友链类型
     */
    \'link_type\' => [
        1 => \'友情链接\',
        2 => \'合作伙伴\',
    ],

    /**
     * 友链形式
     */
    \'link_form\' => [
        1 => \'文字链接\',
        2 => \'图片链接\',
    ],

    /**
     * 友链平台
     */
    \'link_platform\' => [
        1 => \'PC站\',
        2 => \'WAP站\',
        3 => \'小程序\',
        4 => \'APP应用\',
    ],

    /**
     * 站点类型
     */
    \'item_type\' => [
        1 => \'普通站点\',
        2 => \'其他\',
    ],

    /**
     * 广告平台
     */
    \'ad_platform\' => [
        1 => \'PC站\',
        2 => \'WAP站\',
        3 => \'小程序\',
        4 => \'APP应用\',
    ],

    /**
     * 广告类型
     */
    \'ad_type\' => [
        1 => \'图片\',
        2 => \'文字\',
        3 => \'视频\',
        4 => \'其他\',
    ],

    /**
     * 布局推荐类型
     */
    \'layout_type\' => [
        1 => \'CMS文章\',
        2 => \'其他\',
    ],

    /**
     * 城市级别
     */
    \'city_level\' => [
        1 => \"省份\",
        2 => \"城市\",
        3 => \"区县\",
    ],

    /**
     * 行为类型
     */
    \'action_type\' => [
        1 => \'模块\',
        2 => \'插件\',
        3 => \'主题\',
    ],

    /**
     * 执行操作
     */
    \'action_execution\' => [
        1 => \'自定义操作\',
        2 => \'记录操作\',
    ],

    /**
     * 设备类型
     */
    \'user_device\' => [
        1 => \'苹果\',
        2 => \'安卓\',
        3 => \'WAP站\',
        4 => \'PC站\',
        5 => \'微信小程序\',
        6 => \'后台添加\',
    ],

    /**
     * 用户来源
     */
    \'user_source\' => [
        1 => \'注册会员\',
        2 => \'马甲会员\',
    ],

    /**
     * 定时任务类型
     */
    \'crontab_type\' => [
        1 => \'请求URL\',
        2 => \'执行SQL\',
        3 => \'执行Shell\',
    ],

    /**
     * 通知来源
     */
    \'notice_source\' => [
        1 => \'云平台\',
    ],

    /**
     * 通知状态
     */
    \'notice_status\' => [
        1 => \'草稿箱\',
        2 => \'立即发布\',
        3 => \'定时发布\',
    ],
];

系统加载完成后,当前配置文件会默认加载到系统中,此时我们可以使用框架自带的获取配置参数的方式获取参数值,如:config(\'admin.gender_list\')等等,以此类推;

#虚拟主机

上面我们详细的介绍了系统中的全局配置文件内容及参数设定和各自模块中如何设置模块内的配置文件,下面我们给大家介绍下虚拟主机的配置方案,要想项目可以正常的访问,配置虚拟主机是必不可少的步骤:

  • Apache虚拟主机配置
<VirtualHost *:80>
    ServerAdmin rxthinkcmf@163.com
	ServerName tp6.pro
	ServerAlias www.tp6.pro admin.tp6.pro m.tp6.pro api.tp6.pro script.tp6.pro
    DocumentRoot \"D:\\RXThinkCMF\\RXThinkCMF_TP6_PRO\\public\"
    ErrorLog \"logs/tp6.pro-error.log\"
    CustomLog \"logs/tp6.pro-access.log\" common
</VirtualHost>

<VirtualHost *:80>
    ServerAdmin rxthinkcmf@163.com
	ServerName images.tp6.pro
    DocumentRoot \"D:\\RXThinkCMF\\RXThinkCMF_TP6_PRO\\public\\uploads\"
    ErrorLog \"logs/tp6.pro-error.log\"
    CustomLog \"logs/tp6.pro-access.log\" common
</VirtualHost>

第一个虚拟主机是配置各模块的域名解析,第二个域名是专门为图片域名images.demo.cn配置的,指向指定的图片存放目录;

  • Nginx虚拟主机配置
  1. 模块域名配置
server {
    listen       80;
    server_name  www.tp6.pro admin.tp6.pro m.tp6.pro api.tp6.pro script.tp6.pro;
    root    /www/webroot/RXThinkCMF_TP6_PRO/public;
    index   index.php;
    charset utf-8;

    # redirect server error pages to the static page /50x.html
    error_page   500 502 503 504  /50x.html;

    location = /50x.html {
        root   html;
    }
    error_log /www/logs/nginx/admin.tp6.pro.error.log;
    access_log /www/logs/nginx/admin.tp6.pro.access.log;


    if (!-e $request_filename) {
        rewrite  ^(.*)$  /index.php?s=$1  last;
        break;
    }
    location ~ \\.php$ {
        fastcgi_pass   127.0.0.1:9000;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include        fastcgi_params;
    }

    # deny access to .htaccess files, if Apache\'s document root
    location ~ /\\.ht {
        deny  all;
    }
}

  1. 图片域名配置
server {
    listen 80;
    server_name images.tp6.pro;
    root /www/webroot/RXThinkCMF_TP6_PRO/public/uploads;
    location ~ \\.ico|jpg|JPG|PNG|GIF|JPEG|jpeg|gif|png|js|css|woff2|ttf$ {
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Request-Methods GET,POST,PUT,DELETE,OPTIONS;
        #expires 1h;
    }
}

开发规范

#命名规范

框架本身基于ThinkPHP6官方框架研发,规范也遵从于ThinkPHP6框架的规范,遵循PSR-2命名规范和PSR-4自动加载规范,并且注意如下规范:

  1. 目录和文件
  • 目录使用小写+下划线;
  • 类库、函数文件统一以.php为后缀;
  • 类的文件名均以命名空间定义,并且命名空间的路径和类库文件所在路径一致;
  • 类(包含接口和Trait)文件采用驼峰法命名(首字母大写),其它文件采用小写+下划线命名;
  • 类名(包括接口和Trait)和文件名保持一致,统一采用驼峰法命名(首字母大写);
  1. 函数和类、属性命名
  • 类的命名采用驼峰法(首字母大写),例如 User、UserType;
  • 函数的命名使用小写字母和下划线(小写字母开头)的方式,例如 get_client_ip;
  • 方法的命名使用驼峰法(首字母小写),例如 getUserName;
  • 属性的命名使用驼峰法(首字母小写),例如 tableName、instance;
  • 特例:以双下划线__打头的函数或方法作为魔术方法,例如 __call 和 __autoload;
  1. 常量和配置
  • 常量以大写字母和下划线命名,例如 APP_PATH;
  • 配置参数以小写字母和下划线命名,例如 url_route_on 和url_convert;
  • 环境变量定义使用大写字母和下划线命名,例如APP_DEBUG;
  1. 数据表和字段 数据表和字段采用小写加下划线方式命名,并注意字段名不要以下划线开头,例如 think_user 表和 user_name字段,不建议使用驼峰和中文作为数据表及字段命名。

请理解并尽量遵循以上命名规范,可以减少在开发过程中出现不必要的错误。

TIP

请避免使用PHP保留字《保留字列表参见》 (opens new window)作为常量、类名和方法名,以及命名空间的命名,否则会造成系统错误。

更新日志

#统计报表(2020.08.31)

  • 新增 新增统计报表模块;
  • 修复 修复近期系统使用过程中发现的BUG,优化底层架构;

#代码生成器(2020.07.27)

  • 完成 完成代码生成器的研发,可以根据数据表直接生成对应完整的模块;
  • 完成 生成器生成代码时自动创建当前模块的菜单权限节点,杜赋予权限后即可直接运行当前模块,实现增删改查;

#自定义模板(2020.06.15)

  • 完成 完成代码生成器自定义模板的制作,主要包括:控制器、模型、服务类、表单模板制作、模块JS模板制作;

#业务模块(2020.05.30)

  • 完成 完成系统其它主要模块的研发,如:CMS管理模块、会员模块、数据库管理模块,基础配置模块;

#模块开发(2020.04.20)

  • 完成 完成基础模块的研发,包括:字典管理、字典类型管理、配置分组管理、配置管理、广告管理模块、布局推荐管理模块;
  • 完成 完成当前模块UI组件的调用以及调优;

#UI组件(2020.03.25)

  • 完成 完成框架基础UI组件的开发,主要包括:上传单图、上传图集、下拉选择、开关组件、富文本编辑器组件等等;
  • 完成 完成各个UI组件的测试及调优,使用多场景的使用;

#权限架构(2020.02.28)

  • 完成 主要完整系统权限架构的编写,初期包括角色权限、人员独立权限。

#框架搭建(2020.02.10)

  • 搭建 采用多应用模式来研发这套体系。
  • 模板 采用Layui模板引擎做UI前端部分,采用Layout模板布局的方式对整体页面进行拆分,以便简化开发,提交效率。

#项目选型(2020.01.20)

  • 技术选型 ThinkPhp6、Layui、MySQL、Redis。
  • UI选择 经过反复思考,最终从几个UI框架中选择了目前体验和兼容性比较好的UI框架。

组件介绍

#概述

  • 组件化开发是框架的一大特点,那么为什么我们要进行组件化开发?为什么要费时费力的去开发一个个功能独立的小组件呢?看过市面上各种各样的框架,无一不是采用传统的方式去研发一个项目,随便打开一个页面,首先映入眼帘的都是一堆杂乱无章的HTMLCSSJS代码,首先看着心里就不舒服,另外大量的代码进行堆砌,导致项目开发完成后维护成本也非常高,改个BUG或者新增一些功能要去代码里面去找,尤其是当前你开发完的项目让后后面的人再去维护的时候,会有一种让人奔溃的感觉,特别是对新手来说,那更是心里面万马奔腾,一万个不愿意。那么我们是否有办法解决这个传统开发中的痛点,让代码变得简洁的同时又降低了维护成本,再者让后来者在看你代码的时候有种欣赏的感觉,简洁明了,易于上手呢?
  • 办法肯定有的,那就是组件化开发,我们将项目中经常使用的功能点进行拆解包装,封装一个个独立的小组件,当然组件中可以包括独立的HTMLCSSJS,功能完全独立,组件可以理解为一个独立元素,他是功能模块的细胞一个重要的组成部分,最后有一个个单独个性化的细胞组件共同组成一个完整的模块界面。
  • 举例说明,上传图片在项目开发中是必不可少需要实现的功能,传统的项目开发中我们都会在表单中写入上传文件html代码、css样式、js请求处理等等,如果页面中只有一个上传图片还有,如果页面中有很多需要上传图片的,那么可以想象,单单一个上传功能得有多少代码量,同时还有保证每个上传参数的独立性,这时封装一个上传图片的组件就是最好的选择,简简单单一行代码就可以解决上传文件一系列复杂的功能,后期哪怕需要升级上传文件的功能,我们只需要优化和更新组件即可,不用去每个页面去找然后一个个的去修改。

#组件原理

组件是一个独立而又富有生命力的单体结构,包括HTMLCSSJS数据渲染数据处理等等,每个组件都需要单独去建立文件,定义好组件名称、组件参数以及组件功能,开发组件也是一件非常费力而又复杂的功能,因为首先要保证能够实现预期的功能,同时还要保证组件具有灵活性独立性高效性可维护性,当需要时可以直接引入UI表单界面,不需要时直接移除组件即可,方便快捷,我们仍以上传图片这个组件为例,看看页面是如何调用的。

  • 组件使用方式: <widget:uploadSingleImage name=\"avatar|头像|90x90|admin|建议上传尺寸450x450\" th:value=\"${info[\'avatar\']}\"/>这个就是我们框架自定义封装的组件,一个复杂的上传图片的功能,就直接实现了完成的上传图片的功能了,是不是不可思议,非常的简单,其实这里只是做了组件的引入,一系列复杂的功能都交由组件去完成了,我们不用再写那么多冗余而又复杂的代码,只是需要时会使用即可。
  • 效果展示:
  1. 单图片上传

  2. 多图片上传

总结:当前上传图片组件中还包括图片裁剪,详细的使用我们将在下面的《图片上传》一栏中做详细的表述;

代码生成器

模板介绍

#概述

  • 代码生成器是目前项目开发中使用非常频繁的工具,极大了解放了人力,避免了每次手动创建文件的繁琐,提高了开发效率,非常受用,这也是为什么很多人对代码生成器爱不释手的原因,那么我们是否可以二次开发代码生成器这套工具,增强他的功能,使得更加方便和高效的为我们所用,当然可以!!
  • 本着约定高于规范的原则,我们决定对代码生成工具进行了自定义开发,在主流Thymeleaf、FreeMarker模板引擎中,最终我们选择了FreeMarker,根据我们所规划分框架特性和规范,我们自定义了自己独特的文件模板,包括:控制器模板模型模板服务层模板数据列表页模板编辑表单页模板JS模板、等等,每个模板文件我们都进行了精心的编写和调优,使得在各场景的使用过程中可以完美的结合实际动态的生成模块文件,下一章节《模板介绍》我们将详细的描述每个模板的具体内容。

#实现原理

模板文件只是静态的标签,如果需要生成具体的模块文件就是如何去获得数据,并且调用模板文件进行标签填充、替换,生成相关的模块文件了,制作好模板文件只是我们的第一步,最重要的异步就是如何动态获取数据源并绑定模板标签的匹配最终形成我们自己所需要的模块文件并且让生成好的文件自动放入到指定的目录中去,因此我们专门写了一个模板文件生成类,内容如下:

/**
 * 生成模型
 * @param $author 作者
 * @param $moduleName 模块名
 * @param $moduleTitle 模块标题
 * @param $tableName 数据表名
 * @author 牧羊人
 * @since 2020/7/15
 */
public function generateModel($author, $moduleName, $moduleTitle, $tableName)
{
    // 判断是否有图片
    $moduleImage = false;
    // 获取数据列表
    $columnList = $this->getColumnList(DB_PREFIX . \"{$tableName}\");
    if ($columnList) {
        foreach ($columnList as &$val) {
            // 图片字段处理
            if (strpos($val[\'columnName\'], \"cover\") !== false ||
                strpos($val[\'columnName\'], \"avatar\") !== false ||
                strpos($val[\'columnName\'], \"image\") !== false ||
                strpos($val[\'columnName\'], \"logo\") !== false ||
                strpos($val[\'columnName\'], \"pic\") !== false) {
                $val[\'columnImage\'] = true;
                $moduleImage = true;
            }
        }
    }
    // 参数
    $param = [
        \'author\' => $author,
        \'since\' => date(\'Y/m/d\', time()),
        \'moduleName\' => $moduleName,
        \'moduleTitle\' => $moduleTitle,
        \'tableName\' => $tableName,
        \'columnList\' => $columnList,
        \'moduleImage\' => $moduleImage,
    ];
    // 存储目录
    $FILE_PATH = APP_PATH . \'/admin/model/\';
    if (!is_dir($FILE_PATH)) {
        // 创建目录并赋予权限
        mkdir($FILE_PATH, 0777, true);
    }
    // 文件名
    $filename = $FILE_PATH . \"/{$moduleName}.php\";
    // 拆解参数
    extract($param);
    // 开启缓冲区
    ob_start();
    // 引入模板文件
    require(APP_PATH . \'/admin/view/tpl/model.tpl.php\');
    // 获取缓冲区内容
    $out = ob_get_clean();
    // 打开文件
    $f = fopen($filename, \'w\');
    // 写入内容
    fwrite($f, \"<?php \" . $out);
    // 关闭
    fclose($f);
}

/**
 * 生成服务类
 * @param $author 作者
 * @param $moduleName 模块名
 * @param $moduleTitle 模块标题
 * @param $tableName 数据表
 * @since 2020/7/15
 * @author 牧羊人
 */
public function generateService($author, $moduleName, $moduleTitle, $tableName)
{
    // 判断是否有图片
    $moduleImage = false;
    // 查询条件
    $queryList = [];
    // 获取数据列表
    $columnList = $this->getColumnList(DB_PREFIX . \"{$tableName}\");
    if ($columnList) {
        foreach ($columnList as &$val) {
            // 图片字段处理
            if (strpos($val[\'columnName\'], \"cover\") !== false ||
                strpos($val[\'columnName\'], \"avatar\") !== false ||
                strpos($val[\'columnName\'], \"image\") !== false ||
                strpos($val[\'columnName\'], \"logo\") !== false ||
                strpos($val[\'columnName\'], \"pic\") !== false) {
                $val[\'columnImage\'] = true;
                $moduleImage = true;
            }
            // 下拉筛选
            if (isset($val[\'columnValue\'])) {
                $queryList[] = $val;
            }
            // 名称
            if ($val[\'columnName\'] == \"name\") {
                $queryList[] = $val;
            }
            // 标题
            if ($val[\'columnName\'] == \"title\") {
                $queryList[] = $val;
            }
        }
    }

    // 参数
    $param = [
        \'author\' => $author,
        \'since\' => date(\'Y/m/d\', time()),
        \'moduleName\' => $moduleName,
        \'moduleTitle\' => $moduleTitle,
        \'columnList\' => $columnList,
        \'moduleImage\' => $moduleImage,
        \'queryList\' => $queryList,
    ];
    // 存储目录
    $FILE_PATH = APP_PATH . \'/admin/service/\';
    if (!is_dir($FILE_PATH)) {
        // 创建目录并赋予权限
        mkdir($FILE_PATH, 0777, true);
    }
    // 文件名
    $filename = $FILE_PATH . \"/{$moduleName}Service.php\";
    // 拆解参数
    extract($param);
    // 开启缓冲区
    ob_start();
    // 引入模板文件
    require(APP_PATH . \'/admin/view/tpl/service.tpl.php\');
    // 获取缓冲区内容
    $out = ob_get_clean();
    // 打开文件
    $f = fopen($filename, \'w\');
    // 写入内容
    fwrite($f, \"<?php \" . $out);
    // 关闭
    fclose($f);
}

/**
 * 生成控制器
 * @param $author 作者
 * @param $moduleName 模块名
 * @param $moduleTitle 模块标题
 * @param $tableName 数据表名
 * @since 2020/7/15
 * @author 牧羊人
 */
public function generateController($author, $moduleName, $moduleTitle, $tableName)
{
    // 获取数据列表
    $columnList = $this->getColumnList(DB_PREFIX . \"{$tableName}\");
    // 参数
    $param = [
        \'author\' => $author,
        \'since\' => date(\'Y/m/d\', time()),
        \'moduleName\' => $moduleName,
        \'moduleTitle\' => $moduleTitle,
        \'columnList\' => $columnList,
    ];
    // 存储目录
    $FILE_PATH = APP_PATH . \'/admin/controller/\';
    if (!is_dir($FILE_PATH)) {
        // 创建目录并赋予权限
        mkdir($FILE_PATH, 0777, true);
    }
    // 文件名
    $filename = $FILE_PATH . \"/{$param[\'moduleName\']}.php\";
    // 拆解参数
    extract($param);
    // 开启缓冲区
    ob_start();
    // 引入模板文件
    require(APP_PATH . \'/admin/view/tpl/controller.tpl.php\');
    // 获取缓冲区内容
    $out = ob_get_clean();
    // 打开文件
    $f = fopen($filename, \'w\');
    // 写入内容
    fwrite($f, \"<?php \" . $out);
    // 关闭
    fclose($f);
}

/**
 * 生成列表文件
 * @param $moduleTitle 模块标题
 * @param $tableName 数据表名
 * @author 牧羊人
 * @since 2020/7/15
 */
public function generateIndex($moduleTitle, $tableName)
{
    // 获取数据列表
    $columnList = $this->getColumnList(DB_PREFIX . \"{$tableName}\");
    $queryList = [];
    if ($columnList) {
        foreach ($columnList as $val) {
            // 下拉筛选
            if (isset($val[\'columnValue\'])) {
                $queryList[] = $val;
            }
            // 名称
            if ($val[\'columnName\'] == \"name\") {
                $queryList[] = $val;
            }
            // 标题
            if ($val[\'columnName\'] == \"title\") {
                $queryList[] = $val;
            }
        }
    }

    // 参数
    $param = [
        \'moduleTitle\' => $moduleTitle,
        \'queryList\' => $queryList,
    ];
    // 存储目录
    if (strpos($tableName, \"_\") !== false) {
        $tableName = str_replace(\'_\', null, $tableName);
    }
    $FILE_PATH = APP_PATH . \'/admin/view/\' . strtolower($tableName);
    if (!is_dir($FILE_PATH)) {
        // 创建目录并赋予权限
        mkdir($FILE_PATH, 0777, true);
    }
    // 文件名
    $filename = $FILE_PATH . \"/index.html\";
    // 拆解参数
    extract($param);
    // 开启缓冲区
    ob_start();
    // 引入模板文件
    require(APP_PATH . \'/admin/view/tpl/index.tpl.php\');
    // 获取缓冲区内容
    $out = ob_get_clean();
    // 打开文件
    $f = fopen($filename, \'w\');
    // 写入内容
    fwrite($f, $out);
    // 关闭
    fclose($f);
}

/**
 * 生成表单编辑页
 * @param $tableName 数据表名
 * @since 2020/7/15
 * @author 牧羊人
 */
public function generateEdit($tableName)
{
    // 获取数据列表
    $columnList = $this->getColumnList(DB_PREFIX . \"{$tableName}\");
    // 剔除非表单呈现字段
    $arrayList = [];
    $tempList = [];
    $rowList = [];
    $columnSplit = false;
    if ($columnList) {
        foreach ($columnList as $val) {
            // 记录ID
            if ($val[\'columnName\'] == \"id\") {
                continue;
            }
            // 创建人
            if ($val[\'columnName\'] == \"create_user\") {
                continue;
            }
            // 创建时间
            if ($val[\'columnName\'] == \"create_time\") {
                continue;
            }
            // 更新人
            if ($val[\'columnName\'] == \"update_user\") {
                continue;
            }
            // 更新时间
            if ($val[\'columnName\'] == \"update_time\") {
                continue;
            }
            // 有效标识
            if ($val[\'columnName\'] == \"mark\") {
                continue;
            }
            // 图片字段处理
            if (strpos($val[\'columnName\'], \"cover\") !== false ||
                strpos($val[\'columnName\'], \"avatar\") !== false ||
                strpos($val[\'columnName\'], \"image\") !== false ||
                strpos($val[\'columnName\'], \"logo\") !== false ||
                strpos($val[\'columnName\'], \"pic\") !== false) {
                $val[\'columnUpload\'] = true;
                $tempList[] = $val;
                continue;
            }
            // 多行文本输入框
            if (strpos($val[\'columnName\'], \"note\") !== false ||
                strpos($val[\'columnName\'], \"content\") !== false ||
                strpos($val[\'columnName\'], \"description\") !== false ||
                strpos($val[\'columnName\'], \"intro\") !== false) {
                $val[\'columnRow\'] = true;
                $rowList[] = $val;
                continue;
            }
            // 由于目前时间字段采用int类型,所以这里根据字段描述模糊确定是否是时间选择
            if (strpos($val[\'columnComment\'], \"时间\") !== false) {
                $val[\'dataType\'] = \'datetime\';
            } elseif (strpos($val[\'columnComment\'], \"日期\") !== false) {
                $val[\'dataType\'] = \'date\';
            }
            $arrayList[] = $val;
        }
    }
    if (count($arrayList) + count($tempList) + count($rowList) > 5) {
        $dataList = [];
        // 分两个一组
        $dataList = array_chunk($arrayList, 2);
        // 图片
        if (count($tempList) > 0) {
            array_unshift($dataList, $tempList);
        }
        // 多行文本
        if (count($rowList) > 0) {
            foreach ($rowList as $val) {
                $dataList[][] = $val;
            }
        }
        $columnList = $dataList;
        $columnSplit = true;
    } else {
        $dataList = $arrayList;
        // 图片
        if (count($tempList) > 0) {
            array_unshift($dataList, $tempList);
        }
        // 多行文本
        if (count($rowList) > 0) {
            foreach ($rowList as $val) {
                $dataList[][] = $val;
            }
        }
        $columnList = $dataList;
        $columnSplit = false;
    }

    // 参数
    $param = [
        \'columnList\' => $columnList,
    ];
    // 存储目录
    if (strpos($tableName, \"_\") !== false) {
        $tableName = str_replace(\'_\', null, $tableName);
    }
    $FILE_PATH = APP_PATH . \'/admin/view/\' . strtolower($tableName);
    if (!is_dir($FILE_PATH)) {
        // 创建目录并赋予权限
        mkdir($FILE_PATH, 0777, true);
    }
    // 文件名
    $filename = $FILE_PATH . \"/edit.html\";
    // 拆解参数
    extract($param);
    // 开启缓冲区
    ob_start();
    // 引入模板文件
    if ($columnSplit) {
        require(APP_PATH . \'/admin/view/tpl/edit2.tpl.php\');
    } else {
        require(APP_PATH . \'/admin/view/tpl/edit.tpl.php\');
    }
    // 获取缓冲区内容
    $out = ob_get_clean();
    // 打开文件
    $f = fopen($filename, \'w\');
    // 写入内容
    fwrite($f, $out);
    // 关闭
    fclose($f);
}

/**
 * 生成JS文件
 * @param $author 作者
 * @param $moduleName 模块名
 * @param $moduleTitle 模块标题
 * @param $tableName 数据表名
 * @author 牧羊人
 * @since 2020/7/15
 */
public function generateJs($author, $moduleName, $moduleTitle, $tableName)
{
    // 获取数据列表
    $columnList = $this->getColumnList(DB_PREFIX . \"{$tableName}\");
    if ($columnList) {
        foreach ($columnList as &$val) {
            // 图片字段处理
            if (strpos($val[\'columnName\'], \"cover\") !== false ||
                strpos($val[\'columnName\'], \"avatar\") !== false ||
                strpos($val[\'columnName\'], \"image\") !== false ||
                strpos($val[\'columnName\'], \"logo\") !== false ||
                strpos($val[\'columnName\'], \"pic\") !== false) {
                $val[\'columnImage\'] = true;
            }
        }
    }
    // 参数
    $param = [
        \'author\' => $author,
        \'since\' => date(\'Y/m/d\', time()),
        \'moduleTitle\' => $moduleTitle,
        \'columnList\' => $columnList,
        \'columnSplit\' => count($columnList) > 11 ? true : false,
    ];
    // 存储目录
    $FILE_PATH = PUBLIC_PATH . \'/static/admin/module/\';
    if (!is_dir($FILE_PATH)) {
        // 创建目录并赋予权限
        mkdir($FILE_PATH, 0777, true);
    }
    // 文件名
    $filename = $FILE_PATH . \"/think_\" . strtolower($moduleName) . \".js\";
    // 拆解参数
    extract($param);
    // 开启缓冲区
    ob_start();
    // 引入模板文件
    require(APP_PATH . \'/admin/view/tpl/js.tpl.php\');
    // 获取缓冲区内容
    $out = ob_get_clean();
    // 打开文件
    $f = fopen($filename, \'w\');
    // 写入内容
    fwrite($f, $out);
    // 关闭
    fclose($f);
}

/**
 * 获取表字段列表
 * @param $tableName 数据表名
 * @return array
 * @author 牧羊人
 * @since 2020/7/15
 */
public function getColumnList($tableName)
{
    // 获取表列字段信息
    $columnList = Db::query(\"SELECT COLUMN_NAME,COLUMN_DEFAULT,DATA_TYPE,COLUMN_TYPE,COLUMN_COMMENT FROM information_schema.`COLUMNS` where TABLE_NAME like \'{$tableName}\'\");
    $fields = [];
    if ($columnList) {
        foreach ($columnList as $val) {
            $column = [];
            // 列名称
            $column[\'columnName\'] = $val[\'COLUMN_NAME\'];
            // 列默认值
            $column[\'columnDefault\'] = $val[\'COLUMN_DEFAULT\'];
            // 数据类型
            $column[\'dataType\'] = $val[\'DATA_TYPE\'];
            // 列描述
            if (strpos($val[\'COLUMN_COMMENT\'], \':\') !== false) {
                $item = explode(\":\", $val[\'COLUMN_COMMENT\']);
                $column[\'columnComment\'] = $item[0];

                // 拆解字段描述
                $param = explode(\" \", $item[1]);
                $columnValue = [];
                $columnValueList = [];
                foreach ($param as $vo) {
                    // 键值
                    $key = preg_replace(\'/[^0-9]/\', \'\', $vo);
                    // 键值内容
                    $value = str_replace($key, null, $vo);
                    $columnValue[] = \"{$key}={$value}\";
                    $columnValueList[] = $value;
                }
                $column[\'columnValue\'] = implode(\',\', $columnValue);
                if ($val[\'COLUMN_NAME\'] == \"status\" || substr($val[\'COLUMN_NAME\'], 0, 3) == \"is_\") {
                    $column[\'columnSwitch\'] = true;
                    $column[\'columnSwitchValue\'] = implode(\'|\', $columnValueList);
                    $column[\'columnSwitchName\'] = \'set\' . str_replace(\' \', null, ucwords(strtolower(str_replace(\'_\', \' \', $val[\'COLUMN_NAME\']))));
                } else {
                    $column[\'columnSwitch\'] = false;
                }
            } else {
                $column[\'columnComment\'] = $val[\'COLUMN_COMMENT\'];
            }
            $fields[] = $column;
        }
    }
    return $fields;
}

模板文件

根据框架设计和高可用性要求,我们自研了一套精细化的模板,基于FreeMarker编写,遵循FreeMarker书写规范,如需根据自己需要二次开发的小伙伴可以直接修改,根据层级结构的划分,框架所用到的模板文件包括:控制器模板模型模板服务层模板数据列表页模板编辑表单页模板JS模板,下面逐个完整的诠释每个模板文件的内容:

#控制器模板


// +----------------------------------------------------------------------
// | RXThinkCMF框架 [ RXThinkCMF ]
// +----------------------------------------------------------------------
// | 版权所有 2017~2020 南京RXThinkCMF研发中心
// +----------------------------------------------------------------------
// | 官方网站: http://www.rxthink.cn
// +----------------------------------------------------------------------
// | Author: 牧羊人 <1175401194@qq.com>
// +----------------------------------------------------------------------

namespace app\\admin\\controller;


use app\\admin\\service\\<?php echo $moduleName?>Service;
use app\\common\\controller\\Backend;

/**
 * <?php echo $moduleTitle?>管理-控制器
 * @author <?php echo $author?>

 * @since: <?php echo $since?>

 * Class <?php echo $moduleName?>

 * @package app\\admin\\controller
 */
class <?php echo $moduleName?> extends Backend
{
    /**
     * 初始化方法
     * @author <?php echo $author?>

     * @since: <?php echo $since?>

     */
    public function initialize()
    {
        parent::initialize(); // TODO: Change the autogenerated stub
        $this->model = new \\app\\admin\\model\\<?php echo $moduleName?>();
        $this->service = new <?php echo $moduleName?>Service();
    }
	
	<?php if ($columnList) {?>
<?php foreach ($columnList as $val) { ?>
    <?php if (isset($val[\'columnSwitch\']) && $val[\'columnSwitch\']) {?>
	
	/**
	 * 设置<?php echo $val[\'columnComment\']?>
	 
	 * @return mixed
	 * @since <?php echo $since?>
	 
	 * @author <?php echo $author?>
	 
	 */
	public function <?php echo $val[\'columnSwitchName\']?>()
	{
		if (IS_POST) {
			$result = $this->service-><?php echo $val[\'columnSwitchName\']?>();
			return $result;
		}
	}
	<?php } ?>
<?php } ?>
<?php } ?>
}

 

#模型模板


// +----------------------------------------------------------------------
// | RXThinkCMF框架 [ RXThinkCMF ]
// +----------------------------------------------------------------------
// | 版权所有 2017~2020 南京RXThinkCMF研发中心
// +----------------------------------------------------------------------
// | 官方网站: http://www.rxthink.cn
// +----------------------------------------------------------------------
// | Author: 牧羊人 <1175401194@qq.com>
// +----------------------------------------------------------------------

namespace app\\admin\\model;


use app\\common\\model\\BaseModel;

/**
 * <?php echo $moduleTitle?>-模型
 * @author <?php echo $author?>

 * @since: <?php echo $since?>

 * Class <?php echo $moduleName?>

 * @package app\\admin\\model
 */
class <?php echo $moduleName?> extends BaseModel
{
    // 设置数据表名
    protected $name = \"<?php echo $tableName?>\";

    /**
     * 获取缓存信息
     * @param int $id 记录ID
     * @return \\app\\common\\model\\数据信息|mixed
     * @throws \\think\\db\\exception\\DataNotFoundException
     * @throws \\think\\db\\exception\\ModelNotFoundException
     * @author <?php echo $author?>

     * @since: <?php echo $since?>

     */
    public function getInfo($id)
    {
        $info = parent::getInfo($id); // TODO: Change the autogenerated stub
        if ($info) {
    <?php if ($columnList) {?>
    <?php foreach ($columnList as $val) { ?>
        <?php if (isset($val[\'columnImage\']) && $val[\'columnImage\']) {?>

            // <?php echo $val[\'columnComment\']?>

            $info[\'<?php echo $val[\'columnName\']?>\'] = get_image_url($info[\'<?php echo $val[\'columnName\']?>\']);
        <?php } ?>
    <?php } ?>
    <?php } ?>

        }
        return $info;
    }
}

 

#服务层模板


// +----------------------------------------------------------------------
// | RXThinkCMF框架 [ RXThinkCMF ]
// +----------------------------------------------------------------------
// | 版权所有 2017~2020 南京RXThinkCMF研发中心
// +----------------------------------------------------------------------
// | 官方网站: http://www.rxthink.cn
// +----------------------------------------------------------------------
// | Author: 牧羊人 <1175401194@qq.com>
// +----------------------------------------------------------------------

namespace app\\admin\\service;


use app\\admin\\model\\<?php echo $moduleName?>;
use app\\common\\service\\BaseService;

/**
 * <?php echo $moduleTitle?>管理-服务类
 * @author <?php echo $author?>

 * @since: <?php echo $since?>

 * Class <?php echo $moduleName?>Service
 * @package app\\admin\\service
 */
class <?php echo $moduleName?>Service extends BaseService
{
    /**
     * 构造函数
     * LevelService constructor.
     */
    public function __construct()
    {
        $this->model = new <?php echo $moduleName?>();
    }
	
	/**
     * 获取数据列表
     * @return array
     * @since <?php echo $since?>
     * @author <?php echo $author?>
     */
    public function getList()
    {
        $param = request()->param();

        // 查询条件
        $map = [];
		
<?php foreach ($queryList as $val) {?>
	<?php if (isset($val[\'columnValue\'])) {?>

	    // <?php echo $val[\'columnComment\'];?>

        $<?php echo $val[\'columnName\']?> = isset($param[\'<?php echo $val[\'columnName\']?>\']) ? (int)$param[\'<?php echo $val[\'columnName\']?>\'] : 0;
        if ($<?php echo $val[\'columnName\']?>) {
            $map[] = [\'<?php echo $val[\'columnName\']?>\', \'=\', $<?php echo $val[\'columnName\']?>];
        }
	<?php } else {?>

	    // <?php echo $val[\'columnComment\'];?>

        $<?php echo $val[\'columnName\']?> = isset($param[\'<?php echo $val[\'columnName\']?>\']) ? trim($param[\'<?php echo $val[\'columnName\']?>\']) : \'\';
        if ($<?php echo $val[\'columnName\']?>) {
            $map[] = [\'<?php echo $val[\'columnName\']?>\', \'like\', \"%{$<?php echo $val[\'columnName\']?>}%\"];
        }
	<?php } ?>
<?php } ?>

        return parent::getList($map); // TODO: Change the autogenerated stub
    }

<?php if ($moduleImage) {?>
	/**
     * 添加或编辑
     * @return array
     * @since <?php echo $since?>

     * @author <?php echo $author?>

     */
    public function edit()
    {
        // 参数
        $data = request()->param();
	<?php foreach ($columnList as $val) { ?>
        <?php if (isset($val[\'columnImage\']) && $val[\'columnImage\']) {?>

		// <?php echo $val[\'columnComment\']?>处理
        $<?php echo $val[\'columnName\']?> = trim($data[\'<?php echo $val[\'columnName\']?>\']);
        if (strpos($<?php echo $val[\'columnName\']?>, \"temp\")) {
            $data[\'<?php echo $val[\'columnName\']?>\'] = save_image($<?php echo $val[\'columnName\']?>, \'<?php echo strtolower($moduleName)?>\');
        } else {
            $data[\'<?php echo $val[\'columnName\']?>\'] = str_replace(IMG_URL, \"\", $data[\'<?php echo $val[\'columnName\']?>\']);
        }
        <?php } ?>
    <?php } ?>
        
        return parent::edit($data); // TODO: Change the autogenerated stub
    }
<?php } ?>

<?php if ($columnList) {?>
<?php foreach ($columnList as $val) { ?>
    <?php if (isset($val[\'columnSwitch\']) && $val[\'columnSwitch\']) {?>
	
	/**
     * 设置<?php echo $val[\'columnComment\']?>
     
	 * @return array
     * @throws \\think\\db\\exception\\BindParamException
     * @throws \\think\\exception\\PDOException
     * @since <?php echo $since?>
     
	 * @author <?php echo $author?>
     
	 */
    public function <?php echo $val[\'columnSwitchName\']?>()
    {
		// 参数
        $data = request()->param();
        if (!$data[\'id\']) {
            return message(\'记录ID不能为空\', false);
        }
		if (!$data[\'<?php echo $val[\'columnName\']?>\']) {
            return message(\'记录<?php echo $val[\'columnComment\']?>不能为空\', false);
        }
        $error = \'\';
        $rowId = $this->model->edit($data, $error);
        if (!$rowId) {
            return message($error, false);
        }
        return message();
    }
	<?php } ?>
<?php } ?>
<?php } ?>

}

 

#数据列表页模板


<!-- 引入基类模板 -->
{extend name=\'public/base\' /}

<!-- 主体部分 -->
{block name=\'content\'}

    <!-- 功能操作区一 -->
    <form class=\"layui-form toolbar\">
        <div class=\"layui-form-item\">
        <?php foreach ($queryList as $val) {?>
            <?php if (isset($val[\'columnValue\'])) {?>

            <!-- <?php echo $val[\'columnComment\'];?> -->
            <div class=\"layui-inline\">
                <label class=\"layui-form-label w-auto\"><?php echo $val[\'columnComment\']?>:</label>
                <div class=\"layui-input-inline\">
                    {common:select param=\"<?php echo $val[\'columnName\']?>|0|<?php echo $val[\'columnComment\']?>|name|id\" data=\"<?php echo $val[\'columnValue\']?>\" value=\"0\"}
                </div>
            </div>
            <?php } else {?>

            <!-- <?php echo $val[\'columnComment\'];?> -->
            <div class=\"layui-inline\">
                <label class=\"layui-form-label w-auto\"><?php echo $val[\'columnComment\']?>:</label>
                <div class=\"layui-input-inline\">
                    <input type=\"text\" name=\"<?php echo $val[\'columnName\']?>\" placeholder=\"请输入<?php echo $val[\'columnComment\']?>\" autocomplete=\"off\" class=\"layui-input\">
                </div>
            </div>
            <?php } ?>
        <?php } ?>

            <div class=\"layui-inline\">
                <div class=\"layui-input-inline\" style=\"width: auto;\">
                    {widget:query name=\"查询\"}
                    {widget:add name=\"添加<?php echo $moduleTitle?>\"}
                    {widget:dall name=\"批量删除\"}
                </div>
            </div>
        </div>
    </form>

    <!-- TABLE渲染区 -->
    <table class=\"layui-hide\" id=\"tableList\" lay-filter=\"tableList\"></table>

    <!-- 操作功能区二 -->
    <script type=\"text/html\" id=\"toolBar\">
        {widget:edit name=\"编辑\"}
        {widget:delete name=\"删除\"}
    </script>
<?php foreach ($queryList as $val) {?>
<?php if (isset($val[\'columnSwitch\']) && $val[\'columnSwitch\']){ ?>

    <!-- <?php echo $val[\'columnComment\']?> -->
    <script type=\"text/html\" id=\"<?php echo $val[\'columnName\']?>Tpl\">
        <input type=\"checkbox\" name=\"<?php echo $val[\'columnName\']?>\" value=\"{literal}{{ d.id }}{/literal}\" lay-skin=\"switch\" lay-text=\"<?php echo $val[\'columnSwitchValue\']?>\" lay-filter=\"<?php echo $val[\'columnName\']?>\" {literal}{{ d.<?php echo $val[\'columnName\']?> == 1 ? \'checked\' : \'\' }}{/literal} >
    </script>
<?php } ?>
<?php } ?>

{/block}

 

#编辑表单页模板(一行一列)

<form class=\"layui-form model-form\" action=\"\">
    <input name=\"id\" id=\"id\" type=\"hidden\" value=\"{$info.id|default=0}\">
<?php if ($columnList) {?>
    <?php foreach ($columnList as $val) { ?>
    <?php if (isset($val[\'columnUpload\'])) {?>

    <div class=\"layui-form-item\">
        <label class=\"layui-form-label\"><?php echo $val[\'columnComment\']?>:</label>
        {upload:image name=\"<?php echo $val[\'columnName\']?>|<?php echo $val[\'columnComment\']?>|90x90|建议上传尺寸450x450|450x450\" value=\"isset($info[\'<?php echo $val[\'columnName\']?>\']) ? $info[\'<?php echo $val[\'columnName\']?>\'] : \'\'\"}
    </div>
    <?php } elseif (isset($val[\'columnRow\'])) { ?>

    <div class=\"layui-form-item layui-form-text\" style=\"width:625px;\">
        <label class=\"layui-form-label\"><?php echo $val[\'columnComment\']?>:</label>
        <div class=\"layui-input-block\">
            <textarea name=\"<?php echo $val[\'columnName\']?>\" placeholder=\"请输入<?php echo $val[\'columnComment\']?>\" class=\"layui-textarea\">{$info[\'<?php echo $val[\'columnName\']?>\']|default=\'\'}</textarea>
            <?php if ($val[\'dataType\']==\"text\") {?>

                {editor:kindeditor name=\"<?php echo $val[\'columnName\']?>\" type=\"default\" width=\"100%\" height=\"350\"}
            <?php } ?>

        </div>
    </div>
    <?php } else {?>

    <div class=\"layui-form-item\">
        <label class=\"layui-form-label\"><?php echo $val[\'columnComment\']?>:</label>
        <div class=\"layui-input-block\">
    <?php if (isset($val[\'columnValue\'])) {?>
        <?php if (isset($val[\'columnSwitch\']) && $val[\'columnSwitch\']) {?>

            {common:switch name=\"<?php echo $val[\'columnName\']?>\" title=\"<?php echo $val[\'columnSwitchValue\']?>\" value=\"isset($info[\'<?php echo $val[\'columnName\']?>\']) ? $info[\'<?php echo $val[\'columnName\']?>\'] : <?php echo $val[\'columnDefault\']?>\"}
        <?php } else {?>

            {common:select param=\"<?php echo $val[\'columnName\']?>|1|<?php echo $val[\'columnComment\']?>|name|id\" data=\"<?php echo $val[\'columnValue\']?>\" value=\"isset($info[\'<?php echo $val[\'columnName\']?>\']) ? $info[\'<?php echo $val[\'columnName\']?>\'] : <?php echo $val[\'columnDefault\']?>\"}
        <?php } ?>
    <?php } elseif ($val[\'dataType\'] == \'bigint\' || $val[\'dataType\'] == \'int\' || $val[\'dataType\'] == \'smallint\' || $val[\'dataType\'] == \'tinyint\') {?>

        <input name=\"<?php echo $val[\'columnName\']?>\" value=\"{$info[\'<?php echo $val[\'columnName\']?>\']|default=0}\" lay-verify=\"required|number\" autocomplete=\"off\" placeholder=\"请输入<?php echo $val[\'columnComment\']?>\" class=\"layui-input\" type=\"text\">
    <?php } elseif ($val[\'dataType\'] == \'date\' || $val[\'dataType\'] == \'datetime\') {?>

        {date:select param=\"<?php echo $val[\'columnName\']?>|<?php echo $val[\'columnComment\']?>|<?php echo $val[\'dataType\']?>\" value=\"$info[\'<?php echo $val[\'columnName\']?>\']|default=\'\'\"}
    <?php } else {?>
        <input name=\"<?php echo $val[\'columnName\']?>\" value=\"{$info[\'<?php echo $val[\'columnName\']?>\']|default=\'\'}\" lay-verify=\"required\" autocomplete=\"off\" placeholder=\"请输入<?php echo $val[\'columnComment\']?>\" class=\"layui-input\" type=\"text\">
    <?php } ?>

        </div>
    </div>
    <?php } ?>
    <?php } ?>
<?php } ?>

    {widget:submit name=\"submit|立即保存,close|关闭\"}
</form>

#编辑表单页模板(一行两列)

<form class=\"layui-form model-form\" action=\"\">
    <input name=\"id\" id=\"id\" type=\"hidden\" value=\"{$info.id|default=0}\">
<?php if ($columnList) {?>
    <?php foreach ($columnList as $val) { ?>
    <?php if (isset($val[0][\'columnUpload\'])) {?>

    <div class=\"layui-form-item\">
        <label class=\"layui-form-label\"><?php echo $val[0][\'columnComment\']?>:</label>
        {upload:image name=\"<?php echo $val[0][\'columnName\']?>|<?php echo $val[0][\'columnComment\']?>|90x90|建议上传尺寸450x450|450x450\" value=\"isset($info[\'<?php echo $val[0][\'columnName\']?>\']) ? $info[\'<?php echo $val[0][\'columnName\']?>\'] : \'\'\"}
    </div>
    <?php } elseif (isset($val[0][\'columnRow\'])) { ?>

    <div class=\"layui-form-item layui-form-text\" style=\"width:625px;\">
        <label class=\"layui-form-label\"><?php echo $val[0][\'columnComment\']?>:</label>
        <div class=\"layui-input-block\">
            <textarea name=\"<?php echo $val[0][\'columnName\']?>\" placeholder=\"请输入<?php echo $val[0][\'columnComment\']?>\" class=\"layui-textarea\">{$info[\'<?php echo $val[0][\'columnName\']?>\']|default=\'\'}</textarea>
            <?php if ($val[0][\'dataType\']==\"text\") {?>

            {editor:kindeditor name=\"<?php echo $val[0][\'columnName\']?>\" type=\"default\" width=\"100%\" height=\"350\"}
            <?php } ?>

        </div>
    </div>
    <?php } else {?>

    <div class=\"layui-form-item\">
        <div class=\"layui-inline\">
            <label class=\"layui-form-label\"><?php echo $val[0][\'columnComment\']?>:</label>
            <div class=\"layui-input-inline\">
            <?php if (isset($val[0][\'columnValue\'])) {?>
                <?php if (isset($val[0][\'columnSwitch\']) && $val[0][\'columnSwitch\']) {?>

                {common:switch name=\"<?php echo $val[0][\'columnName\']?>\" title=\"<?php echo $val[0][\'columnSwitchValue\']?>\" value=\"isset($info[\'<?php echo $val[0][\'columnName\']?>\']) ? $info[\'<?php echo $val[0][\'columnName\']?>\'] : <?php echo $val[0][\'columnDefault\']?>\"}
                <?php } else {?>

                {common:select param=\"<?php echo $val[0][\'columnName\']?>|1|<?php echo $val[0][\'columnComment\']?>|name|id\" data=\"<?php echo $val[0][\'columnValue\']?>\" value=\"isset($info[\'<?php echo $val[0][\'columnName\']?>\']) ? $info[\'<?php echo $val[0][\'columnName\']?>\'] : <?php echo $val[0][\'columnDefault\']?>\"}
                <?php } ?>
            <?php } elseif ($val[0][\'dataType\'] == \'bigint\' || $val[0][\'dataType\'] == \'int\' || $val[0][\'dataType\'] == \'smallint\' || $val[0][\'dataType\'] == \'tinyint\') {?>

                <input name=\"<?php echo $val[0][\'columnName\']?>\" value=\"{$info[\'<?php echo $val[0][\'columnName\']?>\']|default=0}\" lay-verify=\"required|number\" autocomplete=\"off\" placeholder=\"请输入<?php echo $val[0][\'columnComment\']?>\" class=\"layui-input\" type=\"text\">
            <?php } elseif ($val[0][\'dataType\'] == \'date\' || $val[0][\'dataType\'] == \'datetime\') {?>

                {date:select param=\"<?php echo $val[0][\'columnName\']?>|<?php echo $val[0][\'columnComment\']?>|<?php echo $val[0][\'dataType\']?>\" value=\"$info[\'<?php echo $val[0][\'columnName\']?>\']|default=\'\'\"}
            <?php } else {?>

                <input name=\"<?php echo $val[0][\'columnName\']?>\" value=\"{$info[\'<?php echo $val[0][\'columnName\']?>\']|default=\'\'}\" lay-verify=\"required\" autocomplete=\"off\" placeholder=\"请输入<?php echo $val[0][\'columnComment\']?>\" class=\"layui-input\" type=\"text\">
            <?php } ?>

            </div>
        </div>
        <?php if (isset($val[1])) {?>
<div class=\"layui-inline\">
            <label class=\"layui-form-label\"><?php echo $val[1][\'columnComment\']?>:</label>
            <div class=\"layui-input-inline\">
            <?php if (isset($val[1][\'columnValue\'])) {?>
                <?php if (isset($val[1][\'columnSwitch\']) && $val[1][\'columnSwitch\']) {?>

                {common:switch name=\"<?php echo $val[1][\'columnName\']?>\" title=\"<?php echo $val[1][\'columnSwitchValue\']?>\" value=\"isset($info[\'<?php echo $val[1][\'columnName\']?>\']) ? $info[\'<?php echo $val[1][\'columnName\']?>\'] : <?php echo $val[1][\'columnDefault\']?>\"}
            <?php } else {?>

                {common:select param=\"<?php echo $val[1][\'columnName\']?>|1|<?php echo $val[1][\'columnComment\']?>|name|id\" data=\"<?php echo $val[1][\'columnValue\']?>\" value=\"isset($info[\'<?php echo $val[1][\'columnName\']?>\']) ? $info[\'<?php echo $val[1][\'columnName\']?>\'] : <?php echo $val[1][\'columnDefault\']?>\"}
            <?php } ?>
            <?php } elseif ($val[1][\'dataType\'] == \'bigint\' || $val[1][\'dataType\'] == \'int\' || $val[1][\'dataType\'] == \'smallint\' || $val[1][\'dataType\'] == \'tinyint\') {?>

                <input name=\"<?php echo $val[1][\'columnName\']?>\" value=\"{$info[\'<?php echo $val[1][\'columnName\']?>\']|default=0}\" lay-verify=\"required|number\" autocomplete=\"off\" placeholder=\"请输入<?php echo $val[1][\'columnComment\']?>\" class=\"layui-input\" type=\"text\">
            <?php } elseif ($val[1][\'dataType\'] == \'date\' || $val[1][\'dataType\'] == \'datetime\') {?>

                {date:select param=\"<?php echo $val[1][\'columnName\']?>|<?php echo $val[1][\'columnComment\']?>|<?php echo $val[1][\'dataType\']?>\" value=\"$info[\'<?php echo $val[1][\'columnName\']?>\']|default=\'\'\"}
            <?php } else {?>

                <input name=\"<?php echo $val[1][\'columnName\']?>\" value=\"{$info[\'<?php echo $val[1][\'columnName\']?>\']|default=\'\'}\" lay-verify=\"required\" autocomplete=\"off\" placeholder=\"请输入<?php echo $val[1][\'columnComment\']?>\" class=\"layui-input\" type=\"text\">
            <?php } ?>

            </div>
        </div>
        <?php } ?>

    </div>
    <?php } ?>
    <?php } ?>
<?php } ?>

    {widget:submit name=\"submit|立即保存,close|关闭\"}
</form>

 

#JS文件模板

/**
 * <?php echo $moduleTitle;?>管理
 * @author <?php echo $author?>

 * @since <?php echo $since?>

 */
layui.use([\'function\'], function () {
    //声明变量
    var func = layui.function
        , $ = layui.$;

    if (A == \'index\') {
        //【TABLE列数组】
        var cols = [
            {type: \'checkbox\', fixed: \'left\'}
            , {field: \'id\', width: 80, title: \'ID\', align: \'center\', sort: true, fixed: \'left\'}
<?php foreach ($columnList as $val) {?>
<?php if ($val[\'columnName\'] != \'id\' && $val[\'columnName\'] != \'create_user\' && $val[\'columnName\'] != \'create_time\' && $val[\'columnName\'] != \'update_time\' && $val[\'columnName\'] != \'mark\') {?>
<?php if (isset($val[\'columnSwitch\']) && $val[\'columnSwitch\']) {?>
            , {field: \'<?php echo $val[\'columnName\']?>\', width: 100, title: \'<?php echo $val[\'columnComment\']?>\', align: \'center\', templet: \'#<?php echo $val[\'columnName\']?>Tpl\'}
<?php } elseif (isset($val[\'columnImage\']) && $val[\'columnImage\']) {?>
            , {field: \'<?php echo $val[\'columnName\']?>\', width: 60, title: \'<?php echo $val[\'columnComment\']?>\', align: \'center\', templet: function (d) {
                var <?php echo $val[\'columnName\']?> = \"\";
                if (d.<?php echo $val[\'columnName\']?>) {
                    <?php echo $val[\'columnName\']?> = \'<a href=\"\' + d.<?php echo $val[\'columnName\']?> + \'\" target=\"_blank\"><img src=\"\' + d.<?php echo $val[\'columnName\']?> + \'\" height=\"26\" /></a>\';
                }
                return <?php echo $val[\'columnName\']?>;
                }
            }
<?php } else {?>
            , {field: \'<?php echo $val[\'columnName\']?>\', width: 100, title: \'<?php echo $val[\'columnComment\']?>\', align: \'center\'}
<?php } ?>
<?php } ?>
<?php } ?>
            , {field: \'create_user_name\', width: 100, title: \'创建人\', align: \'center\'}
            , {field: \'create_time\', width: 180, title: \'创建时间\', align: \'center\', sort: true}
            , {field: \'update_time\', width: 180, title: \'更新时间\', align: \'center\', sort: true}
            , {fixed: \'right\', width: 150, title: \'功能操作\', align: \'center\', toolbar: \'#toolBar\'}
        ];

        //【渲染TABLE】
        func.tableIns(cols, \"tableList\");

        //【设置弹框】
<?php if ($columnSplit) {?>
        func.setWin(\"<?php echo $moduleTitle;?>\");
<?php } else {?>
        func.setWin(\"<?php echo $moduleTitle;?>\", 500, 400);
<?php } ?>

<?php foreach ($columnList as $val) {?>
    <?php if (isset($val[\'columnSwitch\']) && $val[\'columnSwitch\']) {?>

        //【设置<?php echo $val[\'columnComment\']?>】
        func.formSwitch(\'<?php echo $val[\'columnName\']?>\', null, function (data, res) {
            console.log(\"开关回调成功\");
        });

    <?php } ?>
<?php } ?>

    }
});

模块生成

#案例概述

在前面章节《模板介绍》中我们详细的说明了获取数据源的方式以及封装了生成每个模块文件的具体方法,绑定数据源进行模板标签替换后生成对应的模块文件并移入指定的目录中,实现的原理是获取数据表的列详细信息,包装成一个有效数据源的数组绑定到模板并进行替换,下面我们将以示例模块讲述具体的实现流程:

#代码生成

登录后台管理系统后,打开系统管理=>系统工具=>代码生成管理模块,此时我们所看到的便是框架所集成的代码生成器模块,点击代码生成菜单,呈现出来的是当前数据库中的所有数据表,我们可以选择所需要生成模块的数据表点击一键生成模块确定后便可自动调用代码生成程序生成当前数据表所对应的模块,如下图所示:

  • 选择数据表

#模块生成

上一步已经选择所需生成模块的数据表,这一步就可以生成模块文件了,点击同意生成确认框之后,程序会自动创建所有模块文件,包括:控制器文件模型文件服务类文件数据列表文件编辑表单文件模块JS文件,文件会按照设置路径存放值指定的目录,我们已数据表think_test为例生成模块文件,数据表脚本如下:

CREATE TABLE `think_demo`  (
  `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT \'唯一性标识\',
  `name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT \'职级名称\',
  `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT \'头像\',
  `status` tinyint(1) UNSIGNED NULL DEFAULT 1 COMMENT \'状态:1正常 2停用\',
  `type` int(10) UNSIGNED NULL DEFAULT 1 COMMENT \'类型:1京东 2淘宝 3拼多多 4唯品会\',
  `is_vip` tinyint(1) UNSIGNED NULL DEFAULT 2 COMMENT \'是否VIP:1是 2否\',
  `sort` smallint(5) UNSIGNED NULL DEFAULT 125 COMMENT \'显示顺序\',
  `create_user` int(10) UNSIGNED NULL DEFAULT 0 COMMENT \'添加人\',
  `create_time` int(10) UNSIGNED NULL DEFAULT 0 COMMENT \'添加时间\',
  `update_user` int(10) UNSIGNED NULL DEFAULT 0 COMMENT \'更新人\',
  `update_time` int(10) UNSIGNED NULL DEFAULT 0 COMMENT \'更新时间\',
  `mark` tinyint(1) UNSIGNED NULL DEFAULT 1 COMMENT \'有效标识\',
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `name`(`name`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = \'演示管理表\' ROW_FORMAT = Dynamic;

生成的模块文件如下:

  • 控制器文件(Demo.php)
<?php 
// +----------------------------------------------------------------------
// | RXThinkCMF框架 [ RXThinkCMF ]
// +----------------------------------------------------------------------
// | 版权所有 2017~2020 南京RXThinkCMF研发中心
// +----------------------------------------------------------------------
// | 官方网站: http://www.rxthink.cn
// +----------------------------------------------------------------------
// | Author: 牧羊人 <1175401194@qq.com>
// +----------------------------------------------------------------------

namespace app\\admin\\controller;


use app\\admin\\service\\DemoService;
use app\\common\\controller\\Backend;

/**
 * 演示管理-控制器
 * @author 牧羊人
 * @since: 2020/07/17
 * Class Demo
 * @package app\\admin\\controller
 */
class Demo extends Backend
{
    /**
     * 初始化方法
     * @author 牧羊人
     * @since: 2020/07/17
     */
    public function initialize()
    {
        parent::initialize(); // TODO: Change the autogenerated stub
        $this->model = new \\app\\admin\\model\\Demo();
        $this->service = new DemoService();
    }
	
	                	
	/**
	 * 设置状态	 
	 * @return mixed
	 * @since 2020/07/17	 
	 * @author 牧羊人	 
	 */
	public function setStatus()
	{
		if (IS_POST) {
			$result = $this->service->setStatus();
			return $result;
		}
	}
	        	
	/**
	 * 设置是否VIP	 
	 * @return mixed
	 * @since 2020/07/17	 
	 * @author 牧羊人	 
	 */
	public function setIsVip()
	{
		if (IS_POST) {
			$result = $this->service->setIsVip();
			return $result;
		}
	}
}

 

  • 模型文件(Demo.php)
<?php 
// +----------------------------------------------------------------------
// | RXThinkCMF框架 [ RXThinkCMF ]
// +----------------------------------------------------------------------
// | 版权所有 2017~2020 南京RXThinkCMF研发中心
// +----------------------------------------------------------------------
// | 官方网站: http://www.rxthink.cn
// +----------------------------------------------------------------------
// | Author: 牧羊人 <1175401194@qq.com>
// +----------------------------------------------------------------------

namespace app\\admin\\model;


use app\\common\\model\\BaseModel;

/**
 * 演示-模型
 * @author 牧羊人
 * @since: 2020/07/17
 * Class Demo
 * @package app\\admin\\model
 */
class Demo extends BaseModel
{
    // 设置数据表名
    protected $name = \"demo\";

    /**
     * 获取缓存信息
     * @param int $id 记录ID
     * @return \\app\\common\\model\\数据信息|mixed
     * @throws \\think\\db\\exception\\DataNotFoundException
     * @throws \\think\\db\\exception\\ModelNotFoundException
     * @author 牧羊人
     * @since: 2020/07/17
     */
    public function getInfo($id)
    {
        $info = parent::getInfo($id); // TODO: Change the autogenerated stub
        if ($info) {
                                        
            // 头像
            $info[\'avatar\'] = get_image_url($info[\'avatar\']);
                                                                                                                            
        }
        return $info;
    }
}

 

  • 服务类文件(DemoService.php)
<?php 
// +----------------------------------------------------------------------
// | RXThinkCMF框架 [ RXThinkCMF ]
// +----------------------------------------------------------------------
// | 版权所有 2017~2020 南京RXThinkCMF研发中心
// +----------------------------------------------------------------------
// | 官方网站: http://www.rxthink.cn
// +----------------------------------------------------------------------
// | Author: 牧羊人 <1175401194@qq.com>
// +----------------------------------------------------------------------

namespace app\\admin\\service;


use app\\admin\\model\\Demo;
use app\\common\\service\\BaseService;

/**
 * 演示管理-服务类
 * @author 牧羊人
 * @since: 2020/07/17
 * Class DemoService
 * @package app\\admin\\service
 */
class DemoService extends BaseService
{
    /**
     * 构造函数
     * LevelService constructor.
     */
    public function __construct()
    {
        $this->model = new Demo();
    }
	
	/**
     * 获取数据列表
     * @return array
     * @since 2020/07/17
     * @author 牧羊人
     */
    public function getList()
    {
        $param = request()->param();

        // 查询条件
        $map = [];
		
	
	    // 职级名称
        $name = isset($param[\'name\']) ? trim($param[\'name\']) : \'\';
        if ($name) {
            $map[] = [\'name\', \'like\', \"%{$name}%\"];
        }
		
	    // 状态
        $status = isset($param[\'status\']) ? (int)$param[\'status\'] : 0;
        if ($status) {
            $map[] = [\'status\', \'=\', $status];
        }
		
	    // 类型
        $type = isset($param[\'type\']) ? (int)$param[\'type\'] : 0;
        if ($type) {
            $map[] = [\'type\', \'=\', $type];
        }
		
	    // 是否VIP
        $is_vip = isset($param[\'is_vip\']) ? (int)$param[\'is_vip\'] : 0;
        if ($is_vip) {
            $map[] = [\'is_vip\', \'=\', $is_vip];
        }
	
        return parent::getList($map); // TODO: Change the autogenerated stub
    }

	/**
     * 添加或编辑
     * @return array
     * @since 2020/07/17
     * @author 牧羊人
     */
    public function edit()
    {
        // 参数
        $data = request()->param();
	                                
		// 头像处理
        $avatar = trim($data[\'avatar\']);
        if (strpos($avatar, \"temp\")) {
            $data[\'avatar\'] = save_image($avatar, \'demo\');
        } else {
            $data[\'avatar\'] = str_replace(IMG_URL, \"\", $data[\'avatar\']);
        }
                                                                                                                                
        return parent::edit($data); // TODO: Change the autogenerated stub
    }

                	
	/**
     * 设置状态     
	 * @return array
     * @throws \\think\\db\\exception\\BindParamException
     * @throws \\think\\exception\\PDOException
     * @since 2020/07/17     
	 * @author 牧羊人     
	 */
    public function setStatus()
    {
		// 参数
        $data = request()->param();
        if (!$data[\'id\']) {
            return message(\'记录ID不能为空\', false);
        }
		if (!$data[\'status\']) {
            return message(\'记录状态不能为空\', false);
        }
        $error = \'\';
        $rowId = $this->model->edit($data, $error);
        if (!$rowId) {
            return message($error, false);
        }
        return message();
    }
	        	
	/**
     * 设置是否VIP     
	 * @return array
     * @throws \\think\\db\\exception\\BindParamException
     * @throws \\think\\exception\\PDOException
     * @since 2020/07/17     
	 * @author 牧羊人     
	 */
    public function setIsVip()
    {
		// 参数
        $data = request()->param();
        if (!$data[\'id\']) {
            return message(\'记录ID不能为空\', false);
        }
		if (!$data[\'is_vip\']) {
            return message(\'记录是否VIP不能为空\', false);
        }
        $error = \'\';
        $rowId = $this->model->edit($data, $error);
        if (!$rowId) {
            return message($error, false);
        }
        return message();
    }
	                        
}

 

  • 数据列表页文件(index.html)

<!-- 引入基类模板 -->
{extend name=\'public/base\' /}

<!-- 主体部分 -->
{block name=\'content\'}

    <!-- 功能操作区一 -->
    <form class=\"layui-form toolbar\">
        <div class=\"layui-form-item\">
                    
            <!-- 职级名称 -->
            <div class=\"layui-inline\">
                <label class=\"layui-form-label w-auto\">职级名称:</label>
                <div class=\"layui-input-inline\">
                    <input type=\"text\" name=\"name\" placeholder=\"请输入职级名称\" autocomplete=\"off\" class=\"layui-input\">
                </div>
            </div>
                                
            <!-- 状态 -->
            <div class=\"layui-inline\">
                <label class=\"layui-form-label w-auto\">状态:</label>
                <div class=\"layui-input-inline\">
                    {common:select param=\"status|0|状态|name|id\" data=\"1=正常,2=停用\" value=\"0\"}
                </div>
            </div>
                                
            <!-- 类型 -->
            <div class=\"layui-inline\">
                <label class=\"layui-form-label w-auto\">类型:</label>
                <div class=\"layui-input-inline\">
                    {common:select param=\"type|0|类型|name|id\" data=\"1=京东,2=淘宝,3=拼多多,4=唯品会\" value=\"0\"}
                </div>
            </div>
                                
            <!-- 是否VIP -->
            <div class=\"layui-inline\">
                <label class=\"layui-form-label w-auto\">是否VIP:</label>
                <div class=\"layui-input-inline\">
                    {common:select param=\"is_vip|0|是否VIP|name|id\" data=\"1=是,2=否\" value=\"0\"}
                </div>
            </div>
                    
            <div class=\"layui-inline\">
                <div class=\"layui-input-inline\" style=\"width: auto;\">
                    {widget:query name=\"查询\"}
                    {widget:add name=\"添加演示\"}
                    {widget:dall name=\"批量删除\"}
                </div>
            </div>
        </div>
    </form>

    <!-- TABLE渲染区 -->
    <table class=\"layui-hide\" id=\"tableList\" lay-filter=\"tableList\"></table>

    <!-- 操作功能区二 -->
    <script type=\"text/html\" id=\"toolBar\">
        {widget:edit name=\"编辑\"}
        {widget:delete name=\"删除\"}
    </script>

    <!-- 状态 -->
    <script type=\"text/html\" id=\"statusTpl\">
        <input type=\"checkbox\" name=\"status\" value=\"{literal}{{ d.id }}{/literal}\" lay-skin=\"switch\" lay-text=\"正常|停用\" lay-filter=\"status\" {literal}{{ d.status == 1 ? \'checked\' : \'\' }}{/literal} >
    </script>

    <!-- 是否VIP -->
    <script type=\"text/html\" id=\"is_vipTpl\">
        <input type=\"checkbox\" name=\"is_vip\" value=\"{literal}{{ d.id }}{/literal}\" lay-skin=\"switch\" lay-text=\"是|否\" lay-filter=\"is_vip\" {literal}{{ d.is_vip == 1 ? \'checked\' : \'\' }}{/literal} >
    </script>

{/block}

 

  • 编辑表单文件
<form class=\"layui-form model-form\" action=\"\">
    <input name=\"id\" id=\"id\" type=\"hidden\" value=\"{$info.id|default=0}\">
        
    <div class=\"layui-form-item\">
        <label class=\"layui-form-label\">头像:</label>
        {upload:image name=\"avatar|头像|90x90|建议上传尺寸450x450|450x450\" value=\"isset($info[\'avatar\']) ? $info[\'avatar\'] : \'\'\"}
    </div>
            
    <div class=\"layui-form-item\">
        <div class=\"layui-inline\">
            <label class=\"layui-form-label\">职级名称:</label>
            <div class=\"layui-input-inline\">
            
                <input name=\"name\" value=\"{$info[\'name\']|default=\'\'}\" lay-verify=\"required\" autocomplete=\"off\" placeholder=\"请输入职级名称\" class=\"layui-input\" type=\"text\">
            
            </div>
        </div>
        <div class=\"layui-inline\">
            <label class=\"layui-form-label\">状态:</label>
            <div class=\"layui-input-inline\">
                            
                {common:switch name=\"status\" title=\"正常|停用\" value=\"isset($info[\'status\']) ? $info[\'status\'] : 1\"}
                        
            </div>
        </div>
        
    </div>
            
    <div class=\"layui-form-item\">
        <div class=\"layui-inline\">
            <label class=\"layui-form-label\">类型:</label>
            <div class=\"layui-input-inline\">
                            
                {common:select param=\"type|1|类型|name|id\" data=\"1=京东,2=淘宝,3=拼多多,4=唯品会\" value=\"isset($info[\'type\']) ? $info[\'type\'] : 1\"}
                            
            </div>
        </div>
        <div class=\"layui-inline\">
            <label class=\"layui-form-label\">是否VIP:</label>
            <div class=\"layui-input-inline\">
                            
                {common:switch name=\"is_vip\" title=\"是|否\" value=\"isset($info[\'is_vip\']) ? $info[\'is_vip\'] : 2\"}
                        
            </div>
        </div>
        
    </div>
            
    <div class=\"layui-form-item\">
        <div class=\"layui-inline\">
            <label class=\"layui-form-label\">显示顺序:</label>
            <div class=\"layui-input-inline\">
            
                <input name=\"sort\" value=\"{$info[\'sort\']|default=0}\" lay-verify=\"required|number\" autocomplete=\"off\" placeholder=\"请输入显示顺序\" class=\"layui-input\" type=\"text\">
            
            </div>
        </div>
        
    </div>
        
    {widget:submit name=\"submit|立即保存,close|关闭\"}
</form>

 

  • 模块JS文件
/**
 * 演示管理
 * @author 牧羊人
 * @since 2020/07/17
 */
layui.use([\'function\'], function () {
    //声明变量
    var func = layui.function
        , $ = layui.$;

    if (A == \'index\') {
        //【TABLE列数组】
        var cols = [
            {type: \'checkbox\', fixed: \'left\'}
            , {field: \'id\', width: 80, title: \'ID\', align: \'center\', sort: true, fixed: \'left\'}
            , {field: \'name\', width: 100, title: \'职级名称\', align: \'center\'}
            , {field: \'avatar\', width: 60, title: \'头像\', align: \'center\', templet: function (d) {
                var avatar = \"\";
                if (d.avatar) {
                    avatar = \'<a href=\"\' + d.avatar + \'\" target=\"_blank\"><img src=\"\' + d.avatar + \'\" height=\"26\" /></a>\';
                }
                return avatar;
                }
            }
            , {field: \'status\', width: 100, title: \'状态\', align: \'center\', templet: \'#statusTpl\'}
            , {field: \'type\', width: 100, title: \'类型\', align: \'center\'}
            , {field: \'is_vip\', width: 100, title: \'是否VIP\', align: \'center\', templet: \'#is_vipTpl\'}
            , {field: \'sort\', width: 100, title: \'显示顺序\', align: \'center\'}
            , {field: \'update_user\', width: 100, title: \'更新人\', align: \'center\'}
            , {field: \'create_user_name\', width: 100, title: \'创建人\', align: \'center\'}
            , {field: \'create_time\', width: 180, title: \'创建时间\', align: \'center\', sort: true}
            , {field: \'update_time\', width: 180, title: \'更新时间\', align: \'center\', sort: true}
            , {fixed: \'right\', width: 150, title: \'功能操作\', align: \'center\', toolbar: \'#toolBar\'}
        ];

        //【渲染TABLE】
        func.tableIns(cols, \"tableList\");

        //【设置弹框】
        func.setWin(\"演示\");

                
        //【设置状态】
        func.formSwitch(\'status\', null, function (data, res) {
            console.log(\"开关回调成功\");
        });

            
        //【设置是否VIP】
        func.formSwitch(\'is_vip\', null, function (data, res) {
            console.log(\"开关回调成功\");
        });

                            
    }
});

 

#运行效果

以上便是生成的模块文件的内容,代码生成器在生成模块的同时,我们也同时创建了当前模块对应的菜单和权限节点,菜单默认是放入系统工具导航下面,如果变更层级请至菜单管理中修改,这里的前提是我们要赋予当前生成模块的菜单角色权限,如下图所示:

  • 菜单权限设置

打开角色管理菜单,选择管理员角色,点击角色权限,弹出的便是权限菜单列表,把我们刚生成的模块菜单进行勾选,提交即可;

  • 模板预览

设置好菜单权限后,我们刷新当前系统,在左侧的菜单系统工具中卞可以看到模块菜单,如下图所示:


来源:https://blog.csdn.net/qq_23836533/article/details/123097204
本站部分图文来源于网络,如有侵权请联系删除。

未经允许不得转载:百木园 » RXThinkCMF_TP6

相关推荐

  • 暂无文章