# 插件研发
* * * * *
插件的介绍及概念在之前相关章节已经介绍过了,此处直接带大家来实战,来做一个省市县三级联动插件,这个是非常常用滴,希望大家看完后可以自己动手写插件。
1.分析插件需求,选择省或市后需要加载对应的数据,所以控制器肯定是必须要有的,这些逻辑都写在控制器中也可以,但是作者不希望大家都这样玩。希望尽量将逻辑封装在逻辑层供控制器调用,所以这里就再加一个逻辑层。data目录是存在sql语句的,所以必不可少。view 展示插件视图,三级联动肯定有HTML咯,有HTML的话应该也会有静态资源,前面有数据库操作那最后再加个数据模型。
2.通过上面的需求咱们将目录做出来了,如下图:
![](https://box.kancloud.cn/1113b30aa0a2652dfc376eb1f8a7e6b0_325x743.jpg)
相信大家也都能看的懂每个目录是干啥的,Region.php 实现了插件通用接口,咱们先将Region.php中的内容写完,代码如下:
~~~
<?php
// +---------------------------------------------------------------------+
// | OneBase | [ WE CAN DO IT JUST THINK ] |
// +---------------------------------------------------------------------+
// | Licensed | http://www.apache.org/licenses/LICENSE-2.0 ) |
// +---------------------------------------------------------------------+
// | Author | Bigotry <3162875@qq.com> |
// +---------------------------------------------------------------------+
// | Repository | https://gitee.com/Bigotry/OneBase |
// +---------------------------------------------------------------------+
namespace addon\region;
use app\common\controller\AddonBase;
use addon\AddonInterface;
/**
* 区域选择插件
*/
class Region extends AddonBase implements AddonInterface
{
/**
* 实现钩子
*/
public function RegionSelect($param = [])
{
$this->assign('addons_data', $param);
$this->assign('addons_config', $this->addonConfig($param));
return $this->fetch('index/index');
}
/**
* 插件安装
*/
public function addonInstall()
{
return [RESULT_SUCCESS, '安装成功'];
}
/**
* 插件卸载
*/
public function addonUninstall()
{
return [RESULT_SUCCESS, '卸载成功'];
}
/**
* 插件基本信息
*/
public function addonInfo()
{
return ['name' => 'Region', 'title' => '区域选择', 'describe' => '区域三级联动选择插件', 'author' => 'Bigotry', 'version' => '1.0'];
}
/**
* 插件配置信息
*/
public function addonConfig($param)
{
return $param;
}
}
~~~
3.没有数据啥都干不了,那么再将data目录下的安装与卸载脚本完善一下。
**安装脚本install.sql**
~~~
INSERT INTO `ob_hook` (`name`, `describe`, `addon_list`, `status`, `update_time`, `create_time`) VALUES ('RegionSelect', '区域选择', 'Region', '1', '0', '0');
INSERT INTO `ob_addon` (`name`, `title`, `describe`, `config`, `author`, `version`, `status`, `create_time` , `update_time`) VALUES ('Region', '区域选择', '区域选择插件', '', 'Bigotry', '1.0', '1', '0', '0');
-- ----------------------------
-- Table structure for `ob_region`
-- ----------------------------
DROP TABLE IF EXISTS `ob_region`;
CREATE TABLE `ob_region` (
`id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL DEFAULT '' COMMENT '地区名称',
`level` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '深度',
`upid` mediumint(8) unsigned NOT NULL DEFAULT '0' COMMENT '父级',
`status` tinyint(1) NOT NULL DEFAULT '1',
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=910007 DEFAULT CHARSET=utf8 COMMENT='省市县数据表';
-- ----------------------------
-- Records of ob_region
-- ----------------------------
INSERT INTO `ob_region` VALUES ('110000', '北京市', '1', '0', '1');
INSERT INTO `ob_region` VALUES ('120000', '天津市', '1', '0', '1');
INSERT INTO `ob_region` VALUES ('130000', '河北省', '1', '0', '1');
INSERT INTO `ob_region` VALUES ('140000', '山西省', '1', '0', '1');
INSERT INTO `ob_region` VALUES ('150000', '内蒙古', '1', '0', '1');
-- ----------------------------
-- ... 此处省略N行地区插入,因为太长咯,官方源码插件的data目录下有蛤
-- ----------------------------
~~~
**卸载脚本uninstall.sql**
~~~
DELETE FROM ob_hook WHERE `name` = 'RegionSelect';
DELETE FROM ob_addon WHERE `name` = 'Region';
DROP TABLE IF EXISTS `ob_region`;
~~~
4.然后来弄模板,这个插件主要用于后台研发带省市县联动选择的功能时使用,所以咱们目前只考虑兼容后台样式,当然也可以通过不同模块判断,加载不同的样式,这个有其他模块省市县选择需求时咱们加个样式判断就好咯。
模板 HTML代码如下:
~~~
<div>
<select name="province_{$addons_data.name}" id="province_{$addons_data.name}" class="form-control addon-form-group-select"></select>
<select name="city_{$addons_data.name}" id="city_{$addons_data.name}" class="form-control addon-form-group-select"></select>
<select name="county_{$addons_data.name}" id="county_{$addons_data.name}" class="form-control addon-form-group-select"></select>
</div>
~~~
为什么 name 和 id 中 有 变量({$addons_data.name})?
因为为了实现不同name 或 id 操作不能的数据,总不能一个页面只能用一个省市联动吧,作者当年就遇到过一个婚恋系统,一个资料页面 就 三四个省市县联动。。所以得支持这种一个页面 N 次复用,就用到了变量名称了。
这个HTML上的class form-control 是后台的样式,addon-form-group-select 是作者自己加的。
下面咱们在插件的静态资源目录 static 中创建一个样式文件 region_style.css 在里面写上一些调整HTML的样式,css代码如下:
~~~
@charset "utf-8";
.addon-form-group-select{
width: 33.33%;
float: left;
overflow: hidden;
margin-bottom: 10px;
}
~~~
5.模板也搞定了,现在该考虑怎么拿数据了。
在业务逻辑层新建文件 Index.php,先封装一个 getRegionList 方法,参数为查询条件,这样就可以在控制器层进行多次查询复用,由于省市县这些基础数据一般不会经常变动,可以再做个根据查询条件缓存数据,这样就可以大大的降低系统开销。
数据也拿出来了,那么后面就该把数据展示出来,由于是三级联动 需要根据上层数据获取下层数据,带级联关系,但是万变不离其宗,封装一个通用的 option 生成方法。
下面是将 Index.php 写完的代码,如下:
~~~
<?php
// +---------------------------------------------------------------------+
// | OneBase | [ WE CAN DO IT JUST THINK ] |
// +---------------------------------------------------------------------+
// | Licensed | http://www.apache.org/licenses/LICENSE-2.0 ) |
// +---------------------------------------------------------------------+
// | Author | Bigotry <3162875@qq.com> |
// +---------------------------------------------------------------------+
// | Repository | https://gitee.com/Bigotry/OneBase |
// +---------------------------------------------------------------------+
namespace addon\region\logic;
use app\common\model\Addon;
/**
* 省市县三级联动插件逻辑
*/
class Index extends Addon
{
/**
* 组合下拉框选项信息
*/
public function combineOptions($id = 0, $list = [], $default_option_text = '')
{
$data = "<option value =''>$default_option_text</option>";
foreach ($list as $vo)
{
$data .= "<option ";
if ($id == $vo['id']) : $data .= " selected "; endif;
$data .= " value ='" . $vo['id'] . "'>" . $vo['name'] . "</option>";
}
return $data;
}
/**
* 获取区域列表
*/
public function getRegionList($where = [])
{
$cache_key = 'cache_region_' . md5(serialize($where));
$cache_list = cache($cache_key);
if (!empty($cache_list)) : return $cache_list; endif;
$list = $this->modelRegion->getList($where, true, 'id', false);
!empty($list) && cache($cache_key, $list);
return $list;
}
}
~~~
上面的代码实现了通用方法 getRegionList 与 combineOptions,然后看看控制器里面应该如何调用这两个逻辑层通用方法呢。
6.作者有强迫症,不希望出现两次以上重复代码块,于是将控制器层的省市县HTML获取也考虑写一个方法,那么一个方法怎么区分想获取哪些数据呢,肯定需要传一个上级id,咱们表里面有个 upid 实际上就是 上级id,那么上级id基本上可以满足查询数据的情况了,但是 一般省市县选择初始化的时候 会有一个提示 如 请选择省份,因为总不能帮用户自动选择某地区吧,嘿嘿。咱们看到表里面 有个 level 字段,省市县分别对应 1 2 3,那么咱们再传一个level 就可以知道默认提示什么文字描述咯。
这些可还是不够,因为不光要考虑 三级联动怎么生成,还得考虑 用户已经选择了,还得默认选择,所以 咱们再来一个 select_id 。
下面是将 Index.php 控制器 写完的代码,如下:
~~~
<?php
// +---------------------------------------------------------------------+
// | OneBase | [ WE CAN DO IT JUST THINK ] |
// +---------------------------------------------------------------------+
// | Licensed | http://www.apache.org/licenses/LICENSE-2.0 ) |
// +---------------------------------------------------------------------+
// | Author | Bigotry <3162875@qq.com> |
// +---------------------------------------------------------------------+
// | Repository | https://gitee.com/Bigotry/OneBase |
// +---------------------------------------------------------------------+
namespace addon\region\controller;
use app\common\controller\AddonBase;
/**
* 区域选择控制器
*/
class Index extends AddonBase
{
/**
* 获取选项信息
*/
public function getOptions()
{
$where['upid'] = input('upid', DATA_DISABLE);
$where['level'] = input('level', DATA_NORMAL);
$select_id = input('select_id', DATA_DISABLE);
$list = $this->logicIndex->getRegionList($where);
switch ($where['level'])
{
case 1: $default_option_text = "---请选择省份---"; break;
case 2: $default_option_text = "---请选择城市---"; break;
case 3: $default_option_text = "---请选择区县---"; break;
default: $this->error('省市县 level 不存在');
}
$data = $this->logicIndex->combineOptions($select_id, $list, $default_option_text);
return $this->result($data);
}
}
~~~
7.最后一步了,零件都造好了,就差最后一步 怎么组装起来呢,这时候js就派上用场了,下面是模板文件写入js代码后的完整代码:
~~~
<link rel="stylesheet" href="__STATIC__/region_style.css">
<div>
<select name="province_{$addons_data.name}" id="province_{$addons_data.name}" class="form-control addon-form-group-select"></select>
<select name="city_{$addons_data.name}" id="city_{$addons_data.name}" class="form-control addon-form-group-select"></select>
<select name="county_{$addons_data.name}" id="county_{$addons_data.name}" class="form-control addon-form-group-select"></select>
</div>
<script type="text/javascript">
$(function(){
var province_id = "{$addons_data['province']}";
var city_id = "{$addons_data['city']}";
var county_id = "{$addons_data['county']}";
var get_options_url = '{:addons_url("region://Index/getOptions")}';
function changeProvince(province_id = 0, select_id = 0)
{
$.get(get_options_url, {upid: province_id, select_id: select_id, level : 1}, function(result){ $("#province_{$addons_data.name}").html(result.data); });
}
function changeCity(city_id = 0, select_id = 0)
{
$.get(get_options_url, {upid: city_id, select_id: select_id, level : 2}, function(result){ $("#city_{$addons_data.name}").html(result.data); });
}
function changeCounty(county_id = 0, select_id = 0)
{
$.get(get_options_url, {upid: county_id, select_id: select_id, level : 3}, function(result){ $("#county_{$addons_data.name}").html(result.data); });
}
changeProvince(0, province_id);
changeCity(province_id, city_id);
changeCounty(city_id, county_id);
$("#province_{$addons_data.name}").change(function(){ changeCity($("#province_{$addons_data.name}").val());});
$("#city_{$addons_data.name}").change(function(){ changeCounty($("#city_{$addons_data.name}").val());});
});
</script>
~~~
到此为止省市县三级联动插件就制作好了,那么下面来看看应该怎么使用 ^_^。
* * * * *
### 插件使用
OneBase做的是基础架构,目前没有地方可以选择省市县,那么就到文章编辑模板里面测试下刚才写的插件是否好用。
文章列表测试三级联动代码,若带默认值则 province city county 对应省市县id:
~~~
<div class="col-md-6">
<div class="form-group">
<label>区域1</label>
<span class="">(请选择区域1)</span>
{:hook('RegionSelect', ['name' => 'address1', 'province' => 0, 'city' => 0, 'county' => 0])}
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label>区域2</label>
<span class="">(请选择区域2)</span>
{:hook('RegionSelect', ['name' => 'address2', 'province' => 0, 'city' => 0, 'county' => 0])}
</div>
</div>
~~~
效果:
![](https://box.kancloud.cn/4bde3b5ed8fa696263f874cda65def2b_1915x756.png)
看起来比较OK,再看下带默认值的效果:
![](https://box.kancloud.cn/20bf8736f94561be69f020d062ac5e19_1915x236.png)
大功告成,作者将会提交到源码的插件目录中供大家使用,也希望大家写出优秀的插件共享给大家。^_^。
- 序言
- 基础
- 安装环境
- 安装演示
- 规范
- 目录
- 介绍
- 后台介绍
- 后台首页
- 会员管理
- 系统管理
- 系统设置与配置管理
- 菜单管理
- 系统回收站
- 服务管理
- 插件管理
- 文章管理
- 接口管理
- 优化维护
- SEO管理
- 数据库
- 文件清理
- 行为日志
- 执行记录
- 统计分析
- 接口介绍
- 接口文档
- 错误码设计
- Token介绍
- 前台介绍
- 架构
- 架构总览
- 生命周期
- 入口文件
- 模块设计
- 依赖注入
- 控制器架构
- 逻辑架构
- 验证架构
- 服务架构
- 模型架构
- 行为架构
- 插件架构
- 配置
- 配置介绍
- 配置加载
- 配置扩展
- 请求
- 请求信息
- 日志
- 后台行为日志
- 系统执行日志
- 框架日志
- 数据
- 数据库设计
- 数据字典
- 数据库操作
- 事务控制
- 混合操作
- 实战
- 控制器
- 逻辑与验证
- 视图与模型
- 插件研发
- 服务研发
- 接口研发
- 杂项
- 数据导入导出
- 二维码条形码
- 邮件发送
- 云存储服务
- 支付服务
- 短信服务
- 微信分享
- 生成海报
- 聊天室
- PJAX
- Demo
- Widget
- 附录
- 常量参考
- 配置参考
- 函数参考
- 进阶
- Redis
- 自动缓存
- 全自动缓存
- 索引
- 数据签名
- 全自动事务
- 队列