插件开发详细教程

第一步

开启开发者模式
文件位置 config/shopxo.php is_develop的值由false改为trueattachments-2019-04-piaOMJT95cb9249499c28.png

开启开发者模式后,前端页面钩子也会出现

attachments-2019-04-4u0fwE5Q5cb936905b843.png

第二步

到后台管理,应用中心创建应用,当开启开发者模式后,插件会多出额外的功能,创建、编辑、打包下载attachments-2019-04-GVJ6w4xG5cb9257f4562b.png

第三步(我们来开发一个在线客服插件)

插件唯一标识符,格式支持以数字、字母、下划线。然后下一步

attachments-2019-04-weWsit5l5cb928d208421.png


第四步

1. 插件logo
2. 插件名称
3. 作者名称
4. 作者主页
5. 插件版本号
6. 插件描述
7. 适用终端
8. 适用系统版本
9. 是否有前端独立入口(一般用于插件在前端需要单独的首页的情况下勾选,也可以自己手动创建,当勾选后插件上会出现前端首页的入口)attachments-2019-04-zj6pexGv5cb9294b581e4.png

attachments-2019-04-ujKtIGCI5cb92a003e88e.png

attachments-2019-04-Qc3QMTds5cb92a942fd32.png

第五步(插件目录分布)

1. 后端控制器文件以 首字母大写格式创建文件(方法名称建议全部采用小写格式编写,避免造成大小写不规范造成不必要的问题)
2. 前端文件以后端文件名称消息创建目录,并在目录下创建对应方法的文件名称(文件名称全部以小写形式命名)
3. css/js 以后端控制器文件名称命名(全小写)具体到方法如  [ 控制器名称.方法名称.css ]    [ admin.saveinfo.css ]

attachments-2019-04-lKDzdmTc5cb92b3b03bd6.png

5.1 后端目录文件结构

application/plugins/service/
    Admin.php       插件后台管理入口文件
    Index.php       插件前端首页文件
    Hook.php        插件钩子入库文件
    config.php      插件配置文件

5.2 前端目录文件结构

名称跟随后端代码 Admin.php 文件名称,全部小写命名
application/plugins/view/service/
    admin
        index.html
        saveinfo.html
    index (同上)
        index.html
5.3 css/js目录文件结构
名称跟随后端代码 Admin.php 文件名称,全部小写命名,如果需要精确到 Admin.php中的Detail方法 可 admin.detail.css
按照此规则创建对应的 css/js 系统会自动引入
public/static/plugins/css/service/
    admin.css
    index.css

public/static/plugins/js/service/
    admin.js
    index.js

public/static/plugins/images/service/
    改目录为插件固已知附件存放位置

5.4 附件目录结构位置

附件为系统自动创建,位于 upload 目录下以 plugins_加当前插件唯一标识符命名
public/static/upload/images/plugins_service/


第六步(文件内容)

application/plugins/service/config.json

{
    "base":{
        "plugins":"service",
        "name":"客服",
        "logo":"\/static\/upload\/images\/plugins_service\/2019\/04\/19\/1555639002115911.png",
        "author":"Devil",
        "author_url":"http:\/\/gong.gg\/",
        "version":"1.0.0",
        "desc":"在线客服系统",
        "apply_terminal":[
            "pc",
            "h5"
        ],
        "apply_version":[
            "1.5.0"
        ],
        "is_home":false
    },
    "hook":{
        "plugins_css":[
            "app\\plugins\\service\\Hook"
        ],
        "plugins_js":[
            "app\\plugins\\service\\Hook"
        ],
        "plugins_view_common_bottom":[
            "app\\plugins\\service\\Hook"
        ]
    }
}

application/plugins/service/Admin.php

<?php
// +----------------------------------------------------------------------
// | ShopXO 国内领先企业级B2C免费开源电商系统
// +----------------------------------------------------------------------
// | Copyright (c) 2011~2019 http://shopxo.net All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: Devil
// +----------------------------------------------------------------------
namespace app\plugins\server;

use think\Controller;
use app\service\PluginsService;

/**
 * 在线客服 - 管理
 * @author   Devil
 * @blog     http://gong.gg/
 * @version  0.0.1
 * @datetime 2016-12-01T21:51:08+0800
 */
class Admin extends Controller
{
    /**
     * 首页
     * @author   Devil
     * @blog     http://gong.gg/
     * @version  1.0.0
     * @datetime 2019-02-07T08:21:54+0800
     * @param    [array]          $params [输入参数]
     */
    public function index($params = [])
    {
        $ret = PluginsService::PluginsData('server');
        if($ret['code'] == 0)
        {
            // 数据处理
            $ret['data']['online_service'] = str_replace("\n", '<br />', $ret['data']['online_service']);

            $this->assign('data', $ret['data']);
            return $this->fetch('../../../plugins/view/server/admin/index');
        } else {
            return $ret['msg'];
        }
    }

    /**
     * 编辑页面
     * @author   Devil
     * @blog     http://gong.gg/
     * @version  1.0.0
     * @datetime 2019-02-07T08:21:54+0800
     * @param    [array]          $params [输入参数]
     */
    public function saveinfo($params = [])
    {
        $ret = PluginsService::PluginsData('server');
        if($ret['code'] == 0)
        {
            // 是否
            $is_whether_list =  [
                0 => array('id' => 0, 'name' => '否'),
                1 => array('id' => 1, 'name' => '是', 'checked' => true),
            ];

            $this->assign('is_whether_list', $is_whether_list);
            $this->assign('data', $ret['data']);
            return $this->fetch('../../../plugins/view/server/admin/saveinfo');
        } else {
            return $ret['msg'];
        }
    }

    /**
     * 数据保存
     * @author   Devil
     * @blog     http://gong.gg/
     * @version  1.0.0
     * @datetime 2019-02-07T08:21:54+0800
     * @param    [array]          $params [输入参数]
     */
    public function save($params = [])
    {
        return PluginsService::PluginsDataSave(['plugins'=>'server', 'data'=>$params]);
    }
}
?>

application/plugins/service/Hook.php

<?php
// +----------------------------------------------------------------------
// | ShopXO 国内领先企业级B2C免费开源电商系统
// +----------------------------------------------------------------------
// | Copyright (c) 2011~2019 http://shopxo.net All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: Devil
// +----------------------------------------------------------------------
namespace app\plugins\server;

use think\Controller;
use app\service\PluginsService;

/**
 * 在线客服 - 钩子入口
 * @author   Devil
 * @blog     http://gong.gg/
 * @version  0.0.1
 * @datetime 2016-12-01T21:51:08+0800
 */
class Hook extends Controller
{
    /**
     * 应用响应入口
     * @author   Devil
     * @blog     http://gong.gg/
     * @version  1.0.0
     * @datetime 2019-02-09T14:25:44+0800
     * @param    [array]                    $params [输入参数]
     */
    public function run($params = [])
    {
        // 是否控制器钩子
        // is_backend 当前为后端业务处理
        // hook_name 钩子名称
        if(!empty($params['hook_name']))
        {
            switch($params['hook_name'])
            {
                case 'plugins_css' :
                    $ret = __MY_ROOT_PUBLIC__.'static/plugins/css/server/style.css';
                    break;

                case 'plugins_js' :
                    $ret = __MY_ROOT_PUBLIC__.'static/plugins/js/server/style.js';
                    break;

                case 'plugins_view_common_bottom' :
                    $ret = $this->html($params);
                    break;

                default :
                    $ret = '';
            }
            return $ret;
        }
        return '';
    }

    /**
     * 视图
     * @author   Devil
     * @blog     http://gong.gg/
     * @version  1.0.0
     * @datetime 2019-02-06T16:16:34+0800
     * @param    [array]          $params [输入参数]
     */
    public function html($params = [])
    {
        // 当前模块/控制器/方法
        $module_name = strtolower(request()->module());
        $controller_name = strtolower(request()->controller());
        $action_name = strtolower(request()->action());

        // 获取应用数据
        $ret = PluginsService::PluginsData('server', ['images']);
        if($ret['code'] == 0)
        {            
            // 非全局
            if($ret['data']['is_overall'] != 1)
            {
                // 非首页则空
                if($module_name.$controller_name.$action_name != 'indexindexindex')
                {
                    return '';
                }
            }

            // 客服
            $online_service = empty($ret['data']['online_service']) ? [] : explode("\n", $ret['data']['online_service']);
            $online_service_data = [];
            if(!empty($online_service))
            {
                foreach($online_service as $v)
                {
                    $items = explode('|', $v);
                    if(count($items) == 2)
                    {
                        $online_service_data[] = $items;
                    }
                }
            }
            $ret['data']['online_service'] = $online_service_data;

            $this->assign('data', $ret['data']);
            return $this->fetch('../../../plugins/view/server/index/content');
        } else {
            return $ret['msg'];
        }
    }
}
?>

application/plugins/view/service/admin/index.html

{{include file="public/header" /}}

<!-- right content start  -->
<div class="content-right">
    <div class="content">
        <legend>
            <span class="fs-16">在线客服</span>
            <a href="{{:MyUrl('admin/pluginsadmin/index')}}" class="fr fs-14 m-t-5 am-icon-mail-reply"> 返回</a>
        </legend>

        <div class="server-content">
            <div class="items">
                <label>标题</label>
                <div>
                    {{if !empty($data['title'])}}
                        {{$data.title}}
                    {{else /}}
                        未填写
                    {{/if}}
                </div>
            </div>
            <div class="items">
                <label>在线客服</label>
                <div>
                    {{if !empty($data['online_service'])}}
                        {{$data.online_service|raw}}
                    {{else /}}
                        未填写
                    {{/if}}
                </div>
            </div>
            <div class="items">
                <label>电话</label>
                <div>
                    {{if !empty($data['tel'])}}
                        {{$data.tel}}
                    {{else /}}
                        未填写
                    {{/if}}
                </div>
            </div>
            <div class="items">
                <label>是否全局</label>
                <div>
                    {{if isset($data['is_overall']) and $data['is_overall'] eq 1}}
                        是
                    {{else /}}
                        否
                    {{/if}}
                </div>
            </div>

            <div class="items">
                <label>背景色</label>
                <div class="bg-color-tag" {{if !empty($data['bg_color'])}}style="background:{{$data.bg_color}}"{{/if}}></div>
            </div>
            <div class="items">
                <label>距离顶部</label>
                <div>
                    {{if !empty($data['distance_top'])}}
                        {{$data.distance_top}}
                    {{else /}}
                        0
                    {{/if}}
                    %
                </div>
            </div>

            <a href="{{:PluginsAdminUrl('server', 'admin', 'saveinfo')}}" class="am-btn am-btn-secondary am-radius btn-loading-example am-btn-sm am-btn-block edit-submit">编辑</a>
        </div>
    </div>
</div>
<!-- right content end  -->
        
<!-- footer start -->
{{include file="public/footer" /}}
<!-- footer end -->

application/plugins/view/service/admin/saveinfo.html  (不存在的文件手动创建即可)

{{include file="public/header" /}}

<!-- right content start  -->
<div class="content-right">
    <div class="content">
        <!-- form start -->
        <form class="am-form form-validation view-save" action="{{:PluginsAdminUrl('server', 'admin', 'save')}}" method="POST" request-type="ajax-url" request-value="{{:PluginsAdminUrl('server', 'admin', 'index')}}" enctype="multipart/form-data">
            <legend>
                <span class="fs-16">在线客服</span>
                <a href="{{:PluginsAdminUrl('server', 'admin', 'index')}}" class="fr fs-14 m-t-5 am-icon-mail-reply"> 返回</a>
            </legend>

            <div class="am-form-group">
                <label>标题</label>
                <input type="text" name="title" placeholder="标题" minlength="2" maxlength="16" data-validation-message="标题格式 2~16 个字符之间" class="am-radius" value="{{if !empty($data)}}{{$data.title}}{{/if}}" />
            </div>

            <div class="am-form-group am-form-file">
                <label class="block">在线客服<span class="am-form-group-label-tips">一行一个、以竖线 | 分割、如:客服一|123456</span></label>
                <textarea rows="5" name="online_service" class="am-radius am-field-valid" placeholder="在线客服" data-validation-message="请填写在线客服" required>{{if !empty($data['online_service'])}}{{$data.online_service}}{{/if}}</textarea>
            </div>

            <div class="am-form-group">
                <label>电话</label>
                <input type="text" name="tel" placeholder="电话" data-validation-message="请填写电话" class="am-radius" value="{{if !empty($data)}}{{$data.tel}}{{/if}}" />
            </div>

            <div class="am-form-group">
                <label>是否全局<span class="am-form-group-label-tips">否则只在首页显示,是则全局展示</span></label>
                <div>
                    {{foreach $is_whether_list as $v}}
                        <label class="am-radio-inline m-r-10">
                            <input type="radio" name="is_overall" value="{{$v.id}}" {{if isset($data['is_overall']) and $data['is_overall'] eq $v['id']}}checked="checked"{{else /}}{{if !isset($data['is_overall']) and isset($v['checked']) and $v['checked'] eq true}}checked="checked"{{/if}}{{/if}} data-am-ucheck /> {{$v.name}}
                        </label>
                    {{/foreach}}
                </div>
            </div>

            <div class="am-form-group">
                <label>背景色</label>
                <input type="hidden" name="bg_color" value="{{if !empty($data['bg_color'])}}{{$data.bg_color}}{{/if}}" />
                <button class="am-btn am-btn-default am-btn-xs colorpicker-submit bg-color-tag am-btn-block bk-cr-white t-r am-radius" type="button" data-input-tag="button.bg-color-tag" data-color-tag="input[name='bg_color']" data-color-style="background-color" {{if !empty($data['bg_color'])}}style="background:{{$data.bg_color}}"{{/if}}>
                    <img src="{{$attachment_host}}/static/common/images/colorpicker.png" />
                </button>
            </div>

            <div class="am-form-group">
                <label>距离顶部<span class="am-form-group-label-tips">默认距离顶部30%</span></label>
                <div class="am-input-group am-input-radius am-input-group-sm">
                    <input type="number" name="distance_top" placeholder="距离顶部" min="0" max="100" data-validation-message="距离顶部 0~100" class="am-form-field" value="{{if isset($data['distance_top'])}}{{$data.distance_top}}{{else /}}30{{/if}}" required />
                    <span class="am-input-group-label">%</span>
                </div>
            </div>

            <div class="am-form-group am-form-group-refreshing">
                <button type="submit" class="am-btn am-btn-primary am-radius btn-loading-example am-btn-sm w100" data-am-loading="{loadingText:'处理中...'}">保存</button>
            </div>
        </form>
        <!-- form end -->
    </div>
</div>
<!-- right content end  -->
        
<!-- footer start -->
{{include file="public/footer" /}}
<!-- footer end -->

application/plugins/view/service/index/content.html  (不存在的文件手动创建即可)

<div class="server" {{if isset($data['distance_top'])}} style="top:{{$data.distance_top}}%"{{/if}}>
    <div class="float-left" style="{{if !empty($data['bg_color'])}}background: {{$data.bg_color}};{{/if}}">
        <a class="btn-open" title="查看在线客服" href="javascript:void(0);">展开</a>
        <a class="btn-ctn" title="关闭在线客服" href="javascript:void(0);">收缩</a>
    </div>
    <div class="content" style="{{if !empty($data['bg_color'])}}background: {{$data.bg_color}};{{/if}}">
        <div class="cn">
            {{if !empty($data['title'])}}
                <h3 class="title">{{$data.title}}</h3>
            {{/if}}
            {{if !empty($data['online_service'])}}
                <ul>
                    {{foreach name=$data.online_service as $v}}
                        <li>
                            <span>{{$v[0]}}</span>
                            {{if IsMobile()}}
                                <a target="_blank" href="mqqwpa://im/chat?chat_type=wpa&uin={{$v[1]}}&version=1&src_type=web&web_src=lvlingseeds.com">
                            {{else /}}
                                <a target="_blank" href="https://wpa.qq.com/msgrd?v=3&amp;uin={{$v[1]}}&amp;site=qq&amp;menu=yes">
                            {{/if}}
                                <img border="0" src="https://pub.idqqimg.com/qconn/wpa/button/button_111.gif" alt="点击这里给我发消息" title="点击这里给我发消息">
                            </a>
                        </li>
                    {{/foreach}}
                    {{if !empty($data['title'])}}
                        <li>
                            <span>电话:</span>
                            <a href="tel:{{$data.tel}}">{{$data.tel}}</a>
                        </li>
                    {{/if}}
                </ul>
            {{/if}}
        </div>
    </div>
</div>

public/static/plugins/css/service/style.css  (不存在的文件手动创建即可)

.server .float-left, .server .content {
    background:#d2364c;
}
.server {
    font-size: 12px;
    position: fixed;
    top: 30%;
    right: 0px;
    z-index: 1001;
}
.server .float-left {
    width: 35px;
    float:left;
    position: relative;
    z-index:1;
    margin-top: 0;
    height: 120px;
}
.server .float-left a {
    font-size:0;
    text-indent: -999em;
}
.server .content {
    float: left;
    padding: 5px;
    overflow:hidden;
    width: 140px;
    margin-right:-150px;
}
.server .content .cn {
    background:#F7F7F7;
}
.server .cn .title {
    font-size: 14px;
    color: #333;
    font-weight:600;
    line-height:24px;
    padding:5px;
    text-align:center;
    margin: 0;
}
.server .cn ul {
    padding:0px;
    margin: 0;
}
.server .cn ul li {
    line-height: 38px;
    height:38px;
    border-bottom: solid 1px #E6E4E4;
    overflow: hidden;
    text-align:center;
}
.server .cn ul li:last-child {
    border: 0;
}
.server .cn ul li span, .server .cn ul li a {
    color: #777;
}
.server .cn ul li img {
    vertical-align: middle;
}
.server .btn-open, .server .btn-ctn {
    position: relative;
    z-index:9;
    top:0;
    left: 0;
    background-image: url(../../images/server/btn-ctn.png);
    background-repeat: no-repeat;
    display:block;
    height: 120px;
    background-size: 55px;
}
.server .btn-open {
    background-position: 3px 10px;
}
.server .btn-ctn {
    background-position: -24px 10px;
    display: none;
}
.server ul li.top {
    border-bottom: solid #ACE5F9 1px;
}
.server ul li.bot {
    border-bottom: none;
}

public/static/plugins/css/service/admin.css

/**
 * 首页
 */
.server-content .items {
    margin: 10px 0 20px 0;
    border-bottom: 1px dashed #f1f1f1;
    padding-bottom: 20px;
}
.server-content .items .immages-tag {
    border: 1px solid #eee;
    text-align: center;
    max-width: 100px;
    padding: 5px;
}
.server-content .items .immages-tag img {
    max-width: 100%;
}
.server-content .items .bg-color-tag {
    width: 50px;
    height: 50px;
    border: 1px solid #eee;
}
.server-content .edit-submit {
    margin-bottom: 20px;
}

public/static/plugins/js/service/style.js

$(function()
{
  // 在线客服
  $('.server .btn-open').click(function()
    {
        $('.server .content').animate({'margin-right':'0px'}, 300);
        $('.server .btn-open').css('display', 'none');
        $('.server .btn-ctn').css('display', 'block');        
    });

    $('.server .btn-ctn').click(function()
    {
        $('.server .content').animate({'margin-right':'-150px'}, 300);
        $('.server .btn-open').css('display', 'block');
        $('.server .btn-ctn').css('display', 'none');  
    });
});

public/static/plugins/images/service/ 附件

附件下载地址

https://demo.shopxo.net/static/plugins/images/commononlineservice/btn-ctn.png

第七步

到这里,在线客服插件就开发完成了。

具体源码可以参考官网在线客服插件

百度网盘链接: https://pan.baidu.com/s/1U_03NLuYAWBH3cgsQWFgRA
提取码: bn6y
  • 发表于 2019-04-19 10:39
  • 阅读 ( 1633 )
  • 分类:使用文档

0 条评论

请先 登录 后评论
不写代码的码农
admin

33 篇文章

作家榜 »

  1. admin 33 文章
  2. 蝈蝈 10 文章
  3. 提问 3 文章
  4. VeryQ 3 文章
  5. 淘咪哆 3 文章
  6. Iron Egg 1 文章
  7. xunhuweb 1 文章
  8. pony 1 文章