# 插件研发 * * * * * 插件的介绍及概念在之前相关章节已经介绍过了,此处直接带大家来实战,来做一个省市县三级联动插件,这个是非常常用滴,希望大家看完后可以自己动手写插件。 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) 大功告成,作者将会提交到源码的插件目录中供大家使用,也希望大家写出优秀的插件共享给大家。^_^。