浏览代码

企业微信通知

lizhen_gitee 1 年之前
父节点
当前提交
39c2422040
共有 45 个文件被更改,包括 3086 次插入0 次删除
  1. 0 0
      addons/vbot/.addonrc
  2. 216 0
      addons/vbot/Vbot.php
  3. 48 0
      addons/vbot/config.php
  4. 15 0
      addons/vbot/controller/Index.php
  5. 10 0
      addons/vbot/info.ini
  6. 109 0
      addons/vbot/install.sql
  7. 228 0
      addons/vbot/library/VbotLib.php
  8. 78 0
      application/admin/controller/vbot/Example.php
  9. 74 0
      application/admin/controller/vbot/Msglog.php
  10. 60 0
      application/admin/controller/vbot/Robot.php
  11. 217 0
      application/admin/controller/vbot/Template.php
  12. 50 0
      application/admin/controller/vbot/Variable.php
  13. 15 0
      application/admin/lang/zh-cn/vbot/msglog.php
  14. 17 0
      application/admin/lang/zh-cn/vbot/robot.php
  15. 31 0
      application/admin/lang/zh-cn/vbot/template.php
  16. 17 0
      application/admin/lang/zh-cn/vbot/variable.php
  17. 52 0
      application/admin/model/VbotMsglog.php
  18. 41 0
      application/admin/model/VbotRobot.php
  19. 60 0
      application/admin/model/VbotTemplate.php
  20. 44 0
      application/admin/model/VbotVariable.php
  21. 27 0
      application/admin/validate/VbotMsglog.php
  22. 27 0
      application/admin/validate/VbotRobot.php
  23. 29 0
      application/admin/validate/VbotTemplate.php
  24. 27 0
      application/admin/validate/VbotVariable.php
  25. 46 0
      application/admin/view/vbot/msglog/add.html
  26. 46 0
      application/admin/view/vbot/msglog/edit.html
  27. 45 0
      application/admin/view/vbot/msglog/index.html
  28. 25 0
      application/admin/view/vbot/msglog/recyclebin.html
  29. 57 0
      application/admin/view/vbot/robot/add.html
  30. 56 0
      application/admin/view/vbot/robot/edit.html
  31. 35 0
      application/admin/view/vbot/robot/index.html
  32. 25 0
      application/admin/view/vbot/robot/recyclebin.html
  33. 143 0
      application/admin/view/vbot/template/add.html
  34. 142 0
      application/admin/view/vbot/template/edit.html
  35. 35 0
      application/admin/view/vbot/template/index.html
  36. 25 0
      application/admin/view/vbot/template/recyclebin.html
  37. 67 0
      application/admin/view/vbot/variable/add.html
  38. 62 0
      application/admin/view/vbot/variable/edit.html
  39. 35 0
      application/admin/view/vbot/variable/index.html
  40. 25 0
      application/admin/view/vbot/variable/recyclebin.html
  41. 3 0
      application/extra/addons.php
  42. 132 0
      public/assets/js/backend/vbot/msglog.js
  43. 165 0
      public/assets/js/backend/vbot/robot.js
  44. 235 0
      public/assets/js/backend/vbot/template.js
  45. 190 0
      public/assets/js/backend/vbot/variable.js

文件差异内容过多而无法显示
+ 0 - 0
addons/vbot/.addonrc


+ 216 - 0
addons/vbot/Vbot.php

@@ -0,0 +1,216 @@
+<?php
+
+namespace addons\vbot;
+
+use addons\vbot\library\VbotLib;
+use app\common\library\Menu;
+use think\Addons;
+use think\Db;
+
+/**
+ * 微信通知机器人
+ */
+class Vbot extends Addons
+{
+
+    /**
+     * 通过模板发送通知消息
+     * @param  string $template 消息模板Id或Code
+     * @param  array $tpl_data 要覆盖的模板数据 (参见:\application\admin\controller\dinghorn\Example.php 的 example2 方法)
+     * @param  array $tpl_variable 模板变量键值对,若模板变量已预先定义,则会自动获取值,无需在此传递
+     * @return array                 发送结果
+     */
+    public function vbotSendMsg($template, $tpl_data = array(), $tpl_variable = array())
+    {
+        $now_time = time();
+
+        // 查询对应模板
+        $template = Db::name('vbot_template')
+            ->where('id = :tpl OR code = :tpl_code', ['tpl' => $template, 'tpl_code' => $template])
+            ->where('deletetime', null)
+            ->find();
+
+        if (!$template || !$template['openswitch']) {
+
+            $log_arr = [
+                'robot_id'      => 0,
+                'template_id'   => 0,
+                'template_data' => 0,
+                'errmsg'        => '消息模板未找到或已禁用!(-2)',
+                'status'        => '0',
+                'createtime'    => $now_time
+            ];
+
+            Db::name('vbot_msglog')->insert($log_arr);
+            return array('errcode' => -2, 'errmsg' => '消息模板未找到或已禁用!');
+        }
+
+        $VbotLib = new VbotLib($template, $tpl_data, $tpl_variable);
+
+        // 获取模板内的机器人
+        $robot_ids = isset($tpl_data['robot_ids']) ? $tpl_data['robot_ids'] : $template['robot_ids'];
+        $robots    = Db::name('vbot_robot')
+            ->whereIn('id', $robot_ids)
+            ->where('openswitch', 1)
+            ->where('deletetime', null)
+            ->select();
+
+        // 组装消息数据
+        $msg_data = $VbotLib->deal_with_msg();
+
+        $log_arr     = [];//要插入的日志
+        $vbot_config = get_addon_config('vbot');
+        $is_err      = false;//是否有错误,用于判定本次操作的返回值
+
+        // 循环所有要发送的机器人
+        foreach ($robots as $key => $value) {
+            $res = $VbotLib->msgSend($value['webhook'], $msg_data);
+
+            if ($res['errcode'] == 0 && $vbot_config['success_log']) {
+                $log_arr[] = array(
+                    'robot_id'      => $value['id'],
+                    'template_id'   => $template['id'],
+                    'template_data' => json_encode($msg_data),
+                    'errmsg'        => '',
+                    'status'        => '1',
+                    'createtime'    => $now_time
+                );
+            } elseif ($res['errcode'] != 0) {
+                $is_err = true;
+
+                if ($vbot_config['error_log']) {
+                    $log_arr[] = array(
+                        'robot_id'      => $value['id'],
+                        'template_id'   => $template['id'],
+                        'template_data' => json_encode($msg_data),
+                        'errmsg'        => $res['errmsg'] . '(' . $res['errcode'] . ')',
+                        'status'        => '0',
+                        'createtime'    => $now_time
+                    );
+                }
+            }
+        }
+
+        if ($log_arr) {
+            Db::startTrans();
+            try {
+                Db::name('vbot_msglog')->insertAll($log_arr);
+                Db::commit();
+            } catch (\Exception $e) {
+                Db::rollback();
+            }
+        }
+
+        return !$is_err ? ['errcode' => 0] : ['errcode' => -3, 'errmsg' => '消息发送失败,请查看日志'];
+    }
+
+    /**
+     * 插件安装方法
+     * @return bool
+     */
+    public function install()
+    {
+        $menu = [
+            [
+                'name'    => 'vbot',
+                'title'   => '微信通知机器人',
+                'icon'    => 'fa fa-volume-up',
+                'sublist' => [
+                    [
+                        'name'    => 'vbot/robot',
+                        'title'   => '机器人管理',
+                        'icon'    => 'fa fa-circle-o',
+                        'sublist' => [
+                            ['name' => 'vbot/robot/index', 'title' => '查看'],
+                            ['name' => 'vbot/robot/add', 'title' => '添加'],
+                            ['name' => 'vbot/robot/edit', 'title' => '编辑'],
+                            ['name' => 'vbot/robot/del', 'title' => '删除'],
+                            ['name' => 'vbot/robot/multi', 'title' => '批量更新'],
+                            ['name' => 'vbot/robot/recyclebin', 'title' => '回收站'],
+                            ['name' => 'vbot/robot/destroy', 'title' => '真实删除'],
+                            ['name' => 'vbot/robot/restore', 'title' => '还原'],
+                        ]
+                    ],
+                    [
+                        'name'    => 'vbot/variable',
+                        'title'   => '模板变量管理',
+                        'icon'    => 'fa fa-circle-o',
+                        'remark'  => '在此处设置变量,随后可在模板通知内使用变量,变量值可来源于方法返回值或SQL查询结果',
+                        'sublist' => [
+                            ['name' => 'vbot/variable/index', 'title' => '查看'],
+                            ['name' => 'vbot/variable/add', 'title' => '添加'],
+                            ['name' => 'vbot/variable/edit', 'title' => '编辑'],
+                            ['name' => 'vbot/variable/del', 'title' => '删除'],
+                            ['name' => 'vbot/variable/multi', 'title' => '批量更新'],
+                            ['name' => 'vbot/variable/recyclebin', 'title' => '回收站'],
+                            ['name' => 'vbot/variable/destroy', 'title' => '真实删除'],
+                            ['name' => 'vbot/variable/restore', 'title' => '还原'],
+                            ['name' => 'vbot/variable/view_variable', 'title' => '计算变量值'],
+                        ]
+                    ],
+                    [
+                        'name'    => 'vbot/template',
+                        'title'   => '通知模板管理',
+                        'icon'    => 'fa fa-circle-o',
+                        'sublist' => [
+                            ['name' => 'vbot/template/index', 'title' => '查看'],
+                            ['name' => 'vbot/template/add', 'title' => '添加'],
+                            ['name' => 'vbot/template/edit', 'title' => '编辑'],
+                            ['name' => 'vbot/template/del', 'title' => '删除'],
+                            ['name' => 'vbot/template/multi', 'title' => '批量更新'],
+                            ['name' => 'vbot/template/recyclebin', 'title' => '回收站'],
+                            ['name' => 'vbot/template/destroy', 'title' => '真实删除'],
+                            ['name' => 'vbot/template/restore', 'title' => '还原'],
+                        ]
+                    ],
+                    [
+                        'name'    => 'vbot/msglog',
+                        'title'   => '通知发送日志管理',
+                        'icon'    => 'fa fa-circle-o',
+                        'sublist' => [
+                            ['name' => 'vbot/msglog/index', 'title' => '查看'],
+                            ['name' => 'vbot/msglog/edit', 'title' => '编辑'],
+                            ['name' => 'vbot/msglog/del', 'title' => '删除'],
+                            ['name' => 'vbot/msglog/multi', 'title' => '批量更新'],
+                            ['name' => 'vbot/msglog/recyclebin', 'title' => '回收站'],
+                            ['name' => 'vbot/msglog/destroy', 'title' => '真实删除'],
+                            ['name' => 'vbot/msglog/restore', 'title' => '还原'],
+                        ]
+                    ],
+                ]
+            ]
+        ];
+        Menu::create($menu);
+        return true;
+    }
+
+    /**
+     * 插件卸载方法
+     * @return bool
+     */
+    public function uninstall()
+    {
+        Menu::delete('vbot');
+        return true;
+    }
+
+    /**
+     * 插件启用方法
+     * @return bool
+     */
+    public function enable()
+    {
+        Menu::enable('vbot');
+        return true;
+    }
+
+    /**
+     * 插件禁用方法
+     * @return bool
+     */
+    public function disable()
+    {
+        Menu::disable('vbot');
+        return true;
+    }
+}

+ 48 - 0
addons/vbot/config.php

@@ -0,0 +1,48 @@
+<?php
+
+return array(
+    0 =>
+        array(
+            'name'    => 'success_log',
+            'title'   => '成功日志',
+            'type'    => 'radio',
+            'options' =>
+                array(
+                    1 => '记录',
+                    0 => '不记录',
+                ),
+            'content' =>
+                array(
+                    1 => '记录',
+                    0 => '不记录',
+                ),
+            'value'   => '0',
+            'rule'    => 'required',
+            'msg'     => '',
+            'tip'     => '机器人发送消息成功后,记录日志',
+            'ok'      => '',
+            'extend'  => '',
+        ),
+    1 =>
+        array(
+            'name'    => 'error_log',
+            'title'   => '失败日志',
+            'type'    => 'radio',
+            'options' =>
+                array(
+                    1 => '记录',
+                    0 => '不记录',
+                ),
+            'content' =>
+                array(
+                    1 => '记录',
+                    0 => '不记录',
+                ),
+            'value'   => '1',
+            'rule'    => 'required',
+            'msg'     => '',
+            'tip'     => '机器人发送消息失败时,记录日志',
+            'ok'      => '',
+            'extend'  => '',
+        ),
+);

+ 15 - 0
addons/vbot/controller/Index.php

@@ -0,0 +1,15 @@
+<?php
+
+namespace addons\vbot\controller;
+
+use think\addons\Controller;
+
+class Index extends Controller
+{
+
+    public function index()
+    {
+        $this->error("当前插件暂无前台页面");
+    }
+
+}

+ 10 - 0
addons/vbot/info.ini

@@ -0,0 +1,10 @@
+name = vbot
+title = 微信通知机器人
+intro = 利用企业微信,实时接受业务提醒,个人亦可接入、使用方便、免费的通知服务,支持发送多种消息类型
+author = 白衣素袖
+website = https://www.fastadmin.net
+version = 1.0.1
+state = 1
+url = /addons/vbot
+license = regular
+licenseto = 77758

+ 109 - 0
addons/vbot/install.sql

@@ -0,0 +1,109 @@
+
+-- ---------------------------
+-- 机器人表
+-- ---------------------------
+CREATE TABLE IF NOT EXISTS `__PREFIX__vbot_robot` (
+  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
+  `name` varchar(50) NOT NULL DEFAULT '' COMMENT '机器人名称',
+  `logo_image` varchar(100) NOT NULL DEFAULT '' COMMENT '机器人logo',
+  `webhook` varchar(150) NOT NULL DEFAULT '' COMMENT 'Hook地址',
+  `weigh` int(10) NOT NULL DEFAULT '0' COMMENT '权重',
+  `openswitch` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否开启:0=否,1=是',
+  `updatetime` int(10) DEFAULT NULL COMMENT '更新时间',
+  `createtime` int(10) DEFAULT NULL COMMENT '创建时间',
+  `deletetime` int(10) DEFAULT NULL COMMENT '删除时间',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='微信机器人表';
+
+-- ----------------------------
+-- 插入机器人
+-- ----------------------------
+BEGIN;
+INSERT IGNORE INTO `__PREFIX__vbot_robot` VALUES ('1', '测试机器人', '', 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=92eb7c5b-4396-478d-a296-65e433a3568a', '1', '1', '1561962387', '1561962247', null);
+COMMIT;
+
+-- ----------------------------
+-- 通知发送日志表
+-- ----------------------------
+CREATE TABLE IF NOT EXISTS `__PREFIX__vbot_msglog` (
+  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
+  `robot_id` int(10) NOT NULL DEFAULT '0' COMMENT '发送机器人',
+  `template_id` int(10) NOT NULL DEFAULT '0' COMMENT '通知模板',
+  `template_data` text COMMENT '发送的数据',
+  `errmsg` varchar(255) NOT NULL DEFAULT '' COMMENT '错误消息',
+  `status` enum('1','0') NOT NULL DEFAULT '0' COMMENT '状态:0=失败,1=成功',
+  `createtime` int(10) DEFAULT NULL COMMENT '发送时间',
+  `deletetime` int(10) DEFAULT NULL COMMENT '删除时间',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='通知发送日志表';
+
+-- ----------------------------
+-- 通知消息模板表
+-- ----------------------------
+CREATE TABLE IF NOT EXISTS `__PREFIX__vbot_template` (
+  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
+  `code` varchar(50) NOT NULL DEFAULT '' COMMENT '模板Code',
+  `name` varchar(50) NOT NULL DEFAULT '' COMMENT '模板名称',
+  `robot_ids` varchar(100) NOT NULL DEFAULT '0' COMMENT '发送机器人',
+  `typelist` enum('news','image','markdown','text') NOT NULL DEFAULT 'text' COMMENT '消息类型:text=文本,markdown=markdown,image=图片,news=图文',
+  `content` text DEFAULT NULL COMMENT '消息内容',
+  `title` varchar(100) NOT NULL DEFAULT '' COMMENT '消息标题',
+  `picurl_image` varchar(255) NOT NULL DEFAULT '' COMMENT '图片URL',
+  `news` text DEFAULT NULL COMMENT '图文组',
+  `at_mobiles` varchar(100) NOT NULL DEFAULT '' COMMENT '被AT人的手机号',
+  `is_atall` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'AT所有人:0=否,1=是',
+  `openswitch` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否开启:0=否,1=是',
+  `updatetime` int(10) DEFAULT NULL COMMENT '修改时间',
+  `createtime` int(10) DEFAULT NULL COMMENT '创建时间',
+  `deletetime` int(10) DEFAULT NULL COMMENT '删除时间',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='通知消息模板表';
+
+-- ----------------------------
+-- 插入一个初始模板
+-- ----------------------------
+BEGIN;
+INSERT IGNORE INTO `__PREFIX__vbot_template` VALUES ('1', 'test_tpl', '测试模板', '1', 'markdown', '### 系统通知-测试消息\r\n###### ${test}\r\n###### 当前系统内有:${user_count}位用户\r\n###### Markdown 是一种标记语言\r\n###### Markdown 具有一系列衍生版本\r\n###### Markdown 的语法简洁明了\r\n###### 学习容易,而且功能比纯文本更强\r\n###### 调用时才赋值的变量值:${dynamic_variable}', '', '', '', '', '0', '1', '1562323284', '1562323284', null);
+COMMIT;
+-- ----------------------------
+-- 模板变量表
+-- ----------------------------
+CREATE TABLE IF NOT EXISTS `__PREFIX__vbot_variable` (
+  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
+  `name` varchar(50) NOT NULL DEFAULT '' COMMENT '变量名',
+  `value_source` tinyint(1) NOT NULL DEFAULT '0' COMMENT '变量值来源:0=SQL查询结果,1=方法返回值',
+  `sql` varchar(200) NOT NULL DEFAULT '' COMMENT 'SQL',
+  `namespace` varchar(200) NOT NULL DEFAULT '' COMMENT '命名空间',
+  `class` varchar(50) NOT NULL DEFAULT '' COMMENT '类',
+  `function` varchar(50) NOT NULL DEFAULT '' COMMENT '方法',
+  `params` varchar(100) NOT NULL DEFAULT '' COMMENT '参数',
+  `updatetime` int(10) DEFAULT NULL COMMENT '更新时间',
+  `createtime` int(10) DEFAULT NULL COMMENT '创建时间',
+  `deletetime` int(10) DEFAULT NULL COMMENT '删除时间',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COMMENT='模板变量表';
+
+-- ----------------------------
+-- 插入两个初始变量
+-- ----------------------------
+BEGIN;
+INSERT IGNORE INTO `__PREFIX__vbot_variable` VALUES ('1', 'user_count', '0', 'SELECT count(id) as id FROM __PREFIX__user', '', '', '', '', '1562132190', '1562123868', null);
+INSERT IGNORE INTO `__PREFIX__vbot_variable` VALUES ('2', 'test', '1', '', 'app\\admin\\controller\\vbot', 'Example', 'test', '我是传递过来的参数', '1562132323', '1562124050', null);
+COMMIT;
+
+-- ----------------------------
+-- 旧版本增加字段处理
+-- ----------------------------
+BEGIN;
+ALTER TABLE `__PREFIX__vbot_template` ADD COLUMN `code` varchar(50) NOT NULL DEFAULT '' COMMENT '模板Code' AFTER `id`;
+COMMIT;
+
+-- ----------------------------
+-- 旧版本修改字段处理
+-- ----------------------------
+BEGIN;
+ALTER TABLE `__PREFIX__vbot_template` MODIFY COLUMN `content` text DEFAULT NULL COMMENT '消息内容' AFTER `typelist`;
+ALTER TABLE `__PREFIX__vbot_template` MODIFY COLUMN `picurl_image` varchar(255) NOT NULL DEFAULT '' COMMENT '图片URL' AFTER `title`;
+ALTER TABLE `__PREFIX__vbot_msglog` MODIFY COLUMN `errmsg` varchar(255) NOT NULL DEFAULT '' COMMENT '错误消息' AFTER `template_data`;
+ALTER TABLE `__PREFIX__vbot_variable` MODIFY COLUMN `sql` varchar(255) NOT NULL DEFAULT '' COMMENT 'SQL' AFTER `value_source`;
+COMMIT;

+ 228 - 0
addons/vbot/library/VbotLib.php

@@ -0,0 +1,228 @@
+<?php
+
+namespace addons\vbot\library;
+
+/**
+ *
+ */
+class VbotLib
+{
+    // 模板数据
+    public $template;
+
+    // 要覆盖的模板数据 (参见:\application\admin\controller\dinghorn\Example.php 的 example2 方法)
+    public $tpl_data;
+
+    // 模板变量键值对,若模板变量已预先定义,则会自动获取值,无需在此传递
+    public $tpl_variable;
+
+    /**
+     * 构造方法
+     * @param array $template 模板数据
+     * @param array $tpl_data 要覆盖的模板数据
+     * @param array $tpl_variable 模板变量键值对
+     */
+    public function __construct($template = array(), $tpl_data = array(), $tpl_variable = array())
+    {
+        $this->template     = $template;
+        $this->tpl_data     = $tpl_data;
+        $this->tpl_variable = $tpl_variable;
+    }
+
+    /**
+     * 发起请求
+     *
+     * @param string $access_token 机器人的 access_token
+     * @param array post_data 请求数据
+     * @return array 请求结果
+     */
+    /**
+     * 发起请求
+     * @param  string $webhook 机器人的 WebHook地址
+     * @param  array $post_data 请求数据
+     * @return array             请求结果
+     */
+    public function msgSend($webhook, $post_data)
+    {
+
+        $ch = curl_init();
+        curl_setopt($ch, CURLOPT_URL, $webhook);
+        curl_setopt($ch, CURLOPT_POST, 1);
+        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
+        curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json;charset=utf-8'));
+        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($post_data));
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
+        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
+        $ret = curl_exec($ch);
+
+        if (false == $ret) {
+            // curl_exec failed
+            $result = ['errcode' => -2, 'errmsg' => curl_error($ch)];
+        } else {
+            $rsp = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+            if (200 != $rsp) {
+                $result = ['errcode' => -1, 'errmsg' => $rsp . ' ' . curl_error($ch)];
+            } else {
+                $result = json_decode($ret, true);
+                if (!$result) {
+                    $result = ['errcode' => -3, 'errmsg' => '转换请求结果数据失败!'];
+                }
+            }
+        }
+
+        curl_close($ch);
+        return $result;
+    }
+
+    /**
+     * 组装消息数据
+     * @return array 组装结果
+     */
+    public function deal_with_msg()
+    {
+        $msg_data = array();
+
+        $msgtype = $msg_data['msgtype'] = $this->template['typelist'];
+
+        $content = isset($this->tpl_data['content']) ? $this->analysis_variable($this->tpl_data['content']) : $this->analysis_variable($this->template['content']);
+
+        if ($msgtype == 'text') {
+
+            $msg_data['text'] = [
+                'content'               => $content,
+                'mentioned_mobile_list' => $this->deal_with_at($this->template)
+            ];
+
+        } elseif ($msgtype == 'markdown') {
+
+            $msg_data['markdown'] = [
+                'content' => $content
+            ];
+
+        } elseif ($msgtype == 'image') {
+
+            $picurl_image = isset($this->tpl_data['picurl_image']) ? $this->tpl_data['picurl_image'] : request()->domain() . $this->template['picurl_image'];
+
+            $base64 = base64_encode(@file_get_contents($picurl_image));
+
+            $msg_data['image'] = [
+                'base64' => $base64,
+                'md5'    => md5(@file_get_contents($picurl_image))
+            ];
+
+        } elseif ($msgtype == 'news') {
+            $news = isset($this->tpl_data['news']) ? $this->tpl_data['news'] : json_decode($this->template['news'], true);
+
+            foreach ($news as $key => $value) {
+                $news[$key]['title']       = $this->analysis_variable($news[$key]['title']);
+                $news[$key]['description'] = '';
+            }
+
+            $news[0]['description'] = $content;
+
+            $msg_data['news'] = array(
+                'articles' => $news
+            );
+        }
+
+        return $msg_data;
+    }
+
+    /**
+     * 分析字符串中的自定义变量
+     * @param  string $str 要分析的字符串
+     * @return string      解析过变量的字符串
+     */
+    public function analysis_variable($str)
+    {
+        if (!$str) {
+            return '';
+        }
+
+        $variables = array();
+        $match     = array();
+        preg_match_all('/\${(.*?)}/', $str, $match);// 匹配到所有变量
+
+        // 读取数据库中的模板变量
+        $variable_tmp = \think\Db::name('vbot_variable')->where('deletetime', null)->select();
+        foreach ($variable_tmp as $key => $value) {
+            $value['variable_type']       = 'predefined';// 标记为预定义
+            $variable_arr[$value['name']] = $value;
+        }
+        unset($variable_tmp);
+
+        // 准备传递过来的模板变量
+        foreach ($this->tpl_variable as $key => $value) {
+            $variable_arr[$key] = [
+                'variable_type'  => 'definition_now',// 标记为现在定义
+                'variable_value' => $value,
+            ];
+        }
+
+        foreach ($match[1] as $key => $value) {
+
+            if (array_key_exists($value, $variable_arr)) {
+
+                if ($variable_arr[$value]['variable_type'] == 'definition_now') {
+
+                    $str = str_replace($match[0][$key], $variable_arr[$value]['variable_value'], $str);
+                } else if ($variable_arr[$value]['variable_type'] == 'predefined') {
+
+                    $variable_value = $this->get_variable_value($variable_arr[$value]);
+                    $str            = str_replace($match[0][$key], $variable_value, $str);
+                }
+            }
+        }
+
+        return $str;
+    }
+
+    /**
+     * 获取数据库中的模板变量的值
+     * @param  string $var 变量数据
+     * @return string      变量值
+     */
+    public function get_variable_value($var)
+    {
+        if ($var['value_source'] == 0) {
+            $var['sql'] = str_replace('__PREFIX__', config('database.prefix'), $var['sql']);
+            $res        = \think\Db::query($var['sql']);
+
+            if ($res) {
+                if (is_array($res[0])) {
+                    foreach ($res[0] as $key => $value) {
+                        return $value;
+                    }
+                } else {
+                    return $res[0];
+                }
+            } else {
+                return false;
+            }
+        } elseif ($var['value_source'] == 1) {
+            return \think\Hook::exec($var['namespace'] . '\\' . $var['class'], $var['function'], $var['params']);
+        }
+    }
+
+    /**
+     * 组装at数据
+     * @return array 组装结果
+     */
+    public function deal_with_at()
+    {
+        if (isset($this->tpl_data['is_atall']) || isset($this->tpl_data['at_mobiles'])) {
+            $this->template['is_atall']   = $this->tpl_data['is_atall'];
+            $this->template['at_mobiles'] = $this->tpl_data['at_mobiles'];
+        }
+
+        if ($this->template['is_atall']) {
+
+            return ['', "@all"];
+        } else {
+            $at_mobiles = explode(',', rtrim($this->template['at_mobiles'], ','));
+
+            return $at_mobiles;
+        }
+    }
+}

+ 78 - 0
application/admin/controller/vbot/Example.php

@@ -0,0 +1,78 @@
+<?php
+
+namespace app\admin\controller\vbot;
+
+use think\Hook;
+
+/**
+ * 微信机器人调用例子
+ */
+class Example
+{
+
+    /*使用模板发送消息*/
+    public function example1()
+    {
+        $template_id = 1;//模板ID
+
+        Hook::listen('vbot_send_msg', $template_id);
+
+        // 方法二,listen方法的第4个参数设置为true,则返回值为一维数组        
+        /*$res = Hook::listen('vbot_send_msg',$template_id,[],true);
+		if ($res['errcode'] != 0) {
+        	echo $res['errmsg'];
+        }*/
+    }
+
+    /*
+     * 先实例化,再调用模板发送消息
+     * 此方式,可以传递第三个参数:模板变量键值对
+     * 若模板变量已预先定义,则会自动获取值,无需在此传递,若有传递,则会覆盖预定义的模板变量
+     * 若模板变量没有在后台预先定义,则会从您传递的参数三中查找
+     */
+    public function example3()
+    {
+        $template_id = 'test_tpl';// 模板ID或Code
+
+        $vbot = new \addons\vbot\Vbot();
+        $vbot->vbotSendMsg($template_id, [], [
+            'dynamic_variable' => '赋值成功~'
+        ]);
+    }
+
+    public function run()
+    {
+        return 'run';
+    }
+
+    /*此方法用于演示:模板变量的值设置为方法的情况*/
+    public function test($params)
+    {
+        return '我是 Example 下面的 test 方法 -> ' . $params;
+    }
+
+    /*
+     * 若您发现模板不能满足您的需求,可以模板为基础,再随时自定义消息的数据
+     * 通常在通知中的数据变化多端时使用
+     * 必须设置模板ID或Code,首先会调用模板内的所有数据,自定义数据再覆盖到模板数据上
+     * 以下字段根据消息类型不同,需要的字段也不尽相同,详见注释
+     */
+    public function example2()
+    {
+        $template_id = 1;//模板ID
+
+        // 覆盖数据,所有字段均为可选,不填则取模板的数据
+        Hook::listen('vbot_send_msg', $template_id, [
+            'robot_ids'    => '发送机器人',// 将机器人ID以小写逗号分隔格式如:1,2
+            'content'      => '自定义消息内容',// 文本、markdown、图文 拥有此属性
+            'picurl_image' => '',// 自定义图片URL,只图片拥有此属性
+            'news'         => [
+                'title'  => '自定义链接标题',
+                'url'    => '自定义链接URL',
+                'picurl' => '自定义链接图片URL'
+            ],// 自定义图文组,图文类型的消息,拥有此属性
+            'at_mobiles'   => '1388888888',// 要at的人的手机号,','号分隔,只文本拥有此属性
+            'is_atall'     => 1,// 是否at所有人,只文本拥有此属性
+        ]);
+    }
+}

+ 74 - 0
application/admin/controller/vbot/Msglog.php

@@ -0,0 +1,74 @@
+<?php
+
+namespace app\admin\controller\vbot;
+
+use app\common\controller\Backend;
+
+/**
+ * 通知发送日志管理
+ *
+ * @icon fa fa-circle-o
+ */
+class Msglog extends Backend
+{
+
+    /**
+     * VbotMsglog模型对象
+     * @var \app\admin\model\VbotMsglog
+     */
+    protected $model = null;
+
+    public function _initialize()
+    {
+        parent::_initialize();
+        $this->model = new \app\admin\model\VbotMsglog;
+        $this->view->assign("statusList", $this->model->getStatusList());
+    }
+
+    /**
+     * 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法、destroy/restore/recyclebin三个回收站方法
+     * 因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑
+     * 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改
+     */
+
+
+    /**
+     * 查看
+     */
+    public function index()
+    {
+        //当前是否为关联查询
+        $this->relationSearch = true;
+        //设置过滤方法
+        $this->request->filter(['strip_tags']);
+        if ($this->request->isAjax()) {
+            //如果发送的来源是Selectpage,则转发到Selectpage
+            if ($this->request->request('keyField')) {
+                return $this->selectpage();
+            }
+            list($where, $sort, $order, $offset, $limit) = $this->buildparams();
+            $total = $this->model
+                ->with(['vbotrobot', 'vbottemplate'])
+                ->where($where)
+                ->order($sort, $order)
+                ->count();
+
+            $list = $this->model
+                ->with(['vbotrobot', 'vbottemplate'])
+                ->where($where)
+                ->order($sort, $order)
+                ->limit($offset, $limit)
+                ->select();
+
+            foreach ($list as $row) {
+                $row->getRelation('vbotrobot')->visible(['name']);
+                $row->getRelation('vbottemplate')->visible(['name']);
+            }
+            $list   = collection($list)->toArray();
+            $result = array("total" => $total, "rows" => $list);
+
+            return json($result);
+        }
+        return $this->view->fetch();
+    }
+}

+ 60 - 0
application/admin/controller/vbot/Robot.php

@@ -0,0 +1,60 @@
+<?php
+
+namespace app\admin\controller\vbot;
+
+use app\common\controller\Backend;
+use think\Db;
+use think\Exception;
+use think\exception\PDOException;
+use think\exception\ValidateException;
+
+/**
+ * 微信机器人管理
+ *
+ * @icon fa fa-circle-o
+ */
+class Robot extends Backend
+{
+
+    /**
+     * VbotRobot模型对象
+     * @var \app\admin\model\VbotRobot
+     */
+    protected $model = null;
+
+    protected $multiFields = 'openswitch';
+
+    public function _initialize()
+    {
+        parent::_initialize();
+        $this->model = new \app\admin\model\VbotRobot;
+    }
+
+    /*验证机器人配置*/
+    public function msg_test()
+    {
+        $VbotLib = new \addons\vbot\library\VbotLib();
+
+        $webhook = $this->request->only('webhook');
+
+        if (!$webhook['webhook']) {
+            $this->error('请输入Hook地址!');
+        }
+
+        $data = array(
+            'msgtype' => 'text',
+            'text'    => [
+                'content'               => '这是一条测试消息!',
+                'mentioned_mobile_list' => ["@all"]
+            ]
+        );
+
+        $res = $VbotLib->msgSend($webhook['webhook'], $data);
+
+        if ($res['errcode'] == 0) {
+            $this->success('消息发送成功!');
+        } else {
+            $this->error($res['errmsg'] . '(' . $res['errcode'] . ')');
+        }
+    }
+}

+ 217 - 0
application/admin/controller/vbot/Template.php

@@ -0,0 +1,217 @@
+<?php
+
+namespace app\admin\controller\vbot;
+
+use app\common\controller\Backend;
+use think\Db;
+use think\Exception;
+use think\exception\PDOException;
+use think\exception\ValidateException;
+use think\Hook;
+
+/**
+ * 通知模板管理
+ *
+ * @icon fa fa-circle-o
+ */
+class Template extends Backend
+{
+
+    /**
+     * VbotTemplate模型对象
+     * @var \app\admin\model\VbotTemplate
+     */
+    protected $model = null;
+
+    protected $multiFields = 'openswitch';
+
+    protected $modelValidate = true; //开启Validate验证
+
+    public function _initialize()
+    {
+        parent::_initialize();
+        $this->model = new \app\admin\model\VbotTemplate;
+        $this->view->assign("typelistList", $this->model->getTypelistList());
+        $this->view->assign("isAtallList", $this->model->getIsAtallList());
+    }
+
+    public function msg_test()
+    {
+
+        $post_data = $this->request->only('ids');
+
+        /*$res       = \think\Hook::listen('vbot_send_msg', $post_data['ids'], [], true);
+
+        if ($res['errcode'] != 0) {
+            $this->error($res['errmsg'] . '(' . $res['errcode'] . ')');
+        } else {
+            $this->success('发送成功!');
+        }*/
+
+        $vbot = new \addons\vbot\Vbot();
+        $res  = $vbot->vbotSendMsg($post_data['ids'], [], [
+            'dynamic_variable' => '赋值成功~'
+        ]);
+
+        if ($res['errcode'] != 0) {
+            $this->error($res['errmsg'] . '(' . $res['errcode'] . ')');
+        } else {
+            $this->success('发送成功!');
+        }
+    }
+
+    /**
+     * 查看
+     */
+    public function index()
+    {
+        //当前是否为关联查询
+        $this->relationSearch = true;
+        //设置过滤方法
+        $this->request->filter(['strip_tags']);
+        if ($this->request->isAjax()) {
+            //如果发送的来源是Selectpage,则转发到Selectpage
+            if ($this->request->request('keyField')) {
+                return $this->selectpage();
+            }
+            list($where, $sort, $order, $offset, $limit) = $this->buildparams();
+            $total = $this->model
+                ->with(['vbotrobot'])
+                ->where($where)
+                ->order($sort, $order)
+                ->count();
+
+            $list = $this->model
+                ->with(['vbotrobot'])
+                ->where($where)
+                ->order($sort, $order)
+                ->limit($offset, $limit)
+                ->select();
+
+            $list = addtion($list, [
+                [
+                    'field' => 'robot_ids',
+                    'name'  => 'vbot_robot'
+                ]
+            ]);
+
+            $list   = collection($list)->toArray();
+            $result = array("total" => $total, "rows" => $list);
+
+            return json($result);
+        }
+        return $this->view->fetch();
+    }
+
+    /**
+     * 添加
+     */
+    public function add()
+    {
+        if ($this->request->isPost()) {
+            $params = $this->request->post("row/a");
+            if ($params) {
+                $params = method_exists($this, 'preExcludeFields') ? $this->preExcludeFields($params) : $params;
+
+                if ($params['code']) {
+                    $code_exist = Db::name('vbot_template')->where('code', $params['code'])->value('id');
+                    if ($code_exist) {
+                        $this->error(__('Code exist'));
+                    }
+                }
+
+                if ($this->dataLimit && $this->dataLimitFieldAutoFill) {
+                    $params[$this->dataLimitField] = $this->auth->id;
+                }
+                $result = false;
+                Db::startTrans();
+                try {
+                    //是否采用模型验证
+                    if ($this->modelValidate) {
+                        $name     = str_replace("\\model\\", "\\validate\\", get_class($this->model));
+                        $validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.add' : $name) : $this->modelValidate;
+                        $this->model->validateFailException(true)->validate($validate);
+                    }
+                    $result = $this->model->allowField(true)->save($params);
+                    Db::commit();
+                } catch (ValidateException $e) {
+                    Db::rollback();
+                    $this->error($e->getMessage());
+                } catch (PDOException $e) {
+                    Db::rollback();
+                    $this->error($e->getMessage());
+                } catch (Exception $e) {
+                    Db::rollback();
+                    $this->error($e->getMessage());
+                }
+                if ($result !== false) {
+                    $this->success();
+                } else {
+                    $this->error(__('No rows were inserted'));
+                }
+            }
+            $this->error(__('Parameter %s can not be empty', ''));
+        }
+        return $this->view->fetch();
+    }
+
+    /**
+     * 编辑
+     */
+    public function edit($ids = null)
+    {
+        $row = $this->model->get($ids);
+        if (!$row) {
+            $this->error(__('No Results were found'));
+        }
+        $adminIds = $this->getDataLimitAdminIds();
+        if (is_array($adminIds)) {
+            if (!in_array($row[$this->dataLimitField], $adminIds)) {
+                $this->error(__('You have no permission'));
+            }
+        }
+        if ($this->request->isPost()) {
+            $params = $this->request->post("row/a");
+            if ($params) {
+                $params = method_exists($this, 'preExcludeFields') ? $this->preExcludeFields($params) : $params;
+
+                if ($params['code'] && $params['code'] != $row['code']) {
+                    $code_exist = Db::name('vbot_template')->where('code', $params['code'])->value('id');
+                    if ($code_exist) {
+                        $this->error(__('Code exist'));
+                    }
+                }
+
+                $result = false;
+                Db::startTrans();
+                try {
+                    //是否采用模型验证
+                    if ($this->modelValidate) {
+                        $name     = str_replace("\\model\\", "\\validate\\", get_class($this->model));
+                        $validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.edit' : $name) : $this->modelValidate;
+                        $row->validateFailException(true)->validate($validate);
+                    }
+                    $result = $row->allowField(true)->save($params);
+                    Db::commit();
+                } catch (ValidateException $e) {
+                    Db::rollback();
+                    $this->error($e->getMessage());
+                } catch (PDOException $e) {
+                    Db::rollback();
+                    $this->error($e->getMessage());
+                } catch (Exception $e) {
+                    Db::rollback();
+                    $this->error($e->getMessage());
+                }
+                if ($result !== false) {
+                    $this->success();
+                } else {
+                    $this->error(__('No rows were updated'));
+                }
+            }
+            $this->error(__('Parameter %s can not be empty', ''));
+        }
+        $this->view->assign("row", $row);
+        return $this->view->fetch();
+    }
+}

+ 50 - 0
application/admin/controller/vbot/Variable.php

@@ -0,0 +1,50 @@
+<?php
+
+namespace app\admin\controller\vbot;
+
+use app\common\controller\Backend;
+
+/**
+ * 变量管理
+ *
+ * @icon fa fa-circle-o
+ */
+class Variable extends Backend
+{
+
+    /**
+     * vbotVariable模型对象
+     * @var \app\admin\model\vbotVariable
+     */
+    protected $model = null;
+
+    public function _initialize()
+    {
+        parent::_initialize();
+        $this->model = new \app\admin\model\VbotVariable;
+        $this->view->assign("valueSourceList", $this->model->getValueSourceList());
+    }
+
+    /*
+     * 获取变量值-用于管理员测试
+     */
+    public function view_variable($ids = null)
+    {
+        $row = $this->model->get($ids);
+        if (!$row) {
+            $this->error(__('No Results were found'));
+        }
+        $adminIds = $this->getDataLimitAdminIds();
+        if (is_array($adminIds)) {
+            if (!in_array($row[$this->dataLimitField], $adminIds)) {
+                $this->error(__('You have no permission'));
+            }
+        }
+
+        $VbotLib             = new \addons\vbot\library\VbotLib();
+        $row->variable_value = $VbotLib->get_variable_value($row->toArray());
+
+        $this->success(null, null, $row);
+    }
+
+}

+ 15 - 0
application/admin/lang/zh-cn/vbot/msglog.php

@@ -0,0 +1,15 @@
+<?php
+
+return [
+    'Id'             => 'ID',
+    'Robot_id'       => '发送机器人',
+    'Template_id'    => '通知模板',
+    'Template_data'  => '发送的数据',
+    'Errmsg'         => '错误消息',
+    'Status'         => '状态',
+    'Status 0'       => '失败',
+    'Status 1'       => '成功',
+    'Createtime'     => '发送时间',
+    'Deletetime'     => '删除时间',
+    'vbotrobot.name' => '机器人名称'
+];

+ 17 - 0
application/admin/lang/zh-cn/vbot/robot.php

@@ -0,0 +1,17 @@
+<?php
+
+return [
+    'Id'           => 'ID',
+    'Name'         => '机器人名称',
+    'Logo_image'   => '机器人logo',
+    'Webhook'      => 'WebHook地址',
+    'Weigh'        => '权重',
+    'Openswitch'   => '是否开启',
+    'Openswitch 0' => '否',
+    'Openswitch 1' => '是',
+    'Updatetime'   => '更新时间',
+    'Createtime'   => '创建时间',
+    'Deletetime'   => '删除时间',
+    'Set to open'  => '设为开启',
+    'Set to close' => '设为关闭',
+];

+ 31 - 0
application/admin/lang/zh-cn/vbot/template.php

@@ -0,0 +1,31 @@
+<?php
+
+return [
+    'Id'                => 'ID',
+    'Name'              => '模板名称',
+    'Code'              => '模板Code',
+    'Robot_ids'         => '发送机器人',
+    'Typelist'          => '消息类型',
+    'Typelist text'     => '文本',
+    'Typelist markdown' => 'markdown',
+    'Typelist image'    => '图片',
+    'Typelist news'     => '图文',
+    'Content'           => '消息内容',
+    'Title'             => '消息标题',
+    'Picurl_image'      => '图片',
+    'News'              => '图文组',
+    'At_mobiles'        => '被AT人的手机号',
+    'Is_atall'          => 'AT所有人',
+    'Is_atall 0'        => '否',
+    'Is_atall 1'        => '是',
+    'Openswitch'        => '是否开启',
+    'Openswitch 0'      => '否',
+    'Openswitch 1'      => '是',
+    'Updatetime'        => '修改时间',
+    'Createtime'        => '创建时间',
+    'Deletetime'        => '删除时间',
+    'vbotrobot.name'    => '机器人名称',
+    'Set to open'       => '设为开启',
+    'Set to close'      => '设为关闭',
+    'Code exist'        => 'Code 已经存在了哦~',
+];

+ 17 - 0
application/admin/lang/zh-cn/vbot/variable.php

@@ -0,0 +1,17 @@
+<?php
+
+return [
+    'Id'             => 'ID',
+    'Name'           => '变量名',
+    'Value_source'   => '变量值来源',
+    'Value_source 0' => 'SQL查询结果',
+    'Value_source 1' => '方法返回值',
+    'Sql'            => 'SQL',
+    'Namespace'      => '命名空间',
+    'Class'          => '类名',
+    'Function'       => '方法名',
+    'Params'         => '参数',
+    'Updatetime'     => '更新时间',
+    'Createtime'     => '创建时间',
+    'Deletetime'     => '删除时间'
+];

+ 52 - 0
application/admin/model/VbotMsglog.php

@@ -0,0 +1,52 @@
+<?php
+
+namespace app\admin\model;
+
+use think\Model;
+use traits\model\SoftDelete;
+
+class VbotMsglog extends Model
+{
+
+    use SoftDelete;
+
+    //数据库
+    protected $connection = 'database';
+    // 表名
+    protected $name = 'vbot_msglog';
+
+    // 自动写入时间戳字段
+    protected $autoWriteTimestamp = 'int';
+
+    // 定义时间戳字段名
+    protected $createTime = 'createtime';
+    protected $updateTime = false;
+    protected $deleteTime = 'deletetime';
+
+    // 追加属性
+    protected $append = [
+        'status_text'
+    ];
+
+    public function getStatusTextAttr($value, $data)
+    {
+        $value = $value ? $value : (isset($data['status']) ? $data['status'] : '');
+        $list  = $this->getStatusList();
+        return isset($list[$value]) ? $list[$value] : '';
+    }
+
+    public function getStatusList()
+    {
+        return ['0' => __('Status 0'), '1' => __('Status 1')];
+    }
+
+    public function vbotrobot()
+    {
+        return $this->belongsTo('VbotRobot', 'robot_id', 'id', [], 'LEFT')->setEagerlyType(0);
+    }
+
+    public function vbottemplate()
+    {
+        return $this->belongsTo('VbotTemplate', 'template_id', 'id', [], 'LEFT')->setEagerlyType(0);
+    }
+}

+ 41 - 0
application/admin/model/VbotRobot.php

@@ -0,0 +1,41 @@
+<?php
+
+namespace app\admin\model;
+
+use think\Model;
+use traits\model\SoftDelete;
+
+class VbotRobot extends Model
+{
+
+    use SoftDelete;
+
+    //数据库
+    protected $connection = 'database';
+    // 表名
+    protected $name = 'vbot_robot';
+
+    // 自动写入时间戳字段
+    protected $autoWriteTimestamp = 'int';
+
+    // 定义时间戳字段名
+    protected $createTime = 'createtime';
+    protected $updateTime = 'updatetime';
+    protected $deleteTime = 'deletetime';
+
+    // 追加属性
+    protected $append = [
+
+    ];
+
+
+    protected static function init()
+    {
+        self::afterInsert(function ($row) {
+            $pk = $row->getPk();
+            $row->getQuery()->where($pk, $row[$pk])->update(['weigh' => $row[$pk]]);
+        });
+    }
+
+
+}

+ 60 - 0
application/admin/model/VbotTemplate.php

@@ -0,0 +1,60 @@
+<?php
+
+namespace app\admin\model;
+
+use think\Model;
+use traits\model\SoftDelete;
+
+class VbotTemplate extends Model
+{
+
+    use SoftDelete;
+
+    //数据库
+    protected $connection = 'database';
+    // 表名
+    protected $name = 'vbot_template';
+
+    // 自动写入时间戳字段
+    protected $autoWriteTimestamp = 'int';
+
+    // 定义时间戳字段名
+    protected $createTime = 'createtime';
+    protected $updateTime = 'updatetime';
+    protected $deleteTime = 'deletetime';
+
+    // 追加属性
+    protected $append = [
+        'typelist_text',
+        'is_atall_text'
+    ];
+
+    public function getTypelistTextAttr($value, $data)
+    {
+        $value = $value ? $value : (isset($data['typelist']) ? $data['typelist'] : '');
+        $list  = $this->getTypelistList();
+        return isset($list[$value]) ? $list[$value] : '';
+    }
+
+    public function getTypelistList()
+    {
+        return ['text' => __('Typelist text'), 'markdown' => __('Typelist markdown'), 'image' => __('Typelist image'), 'news' => __('Typelist news')];
+    }
+
+    public function getIsAtallTextAttr($value, $data)
+    {
+        $value = $value ? $value : (isset($data['is_atall']) ? $data['is_atall'] : '');
+        $list  = $this->getIsAtallList();
+        return isset($list[$value]) ? $list[$value] : '';
+    }
+
+    public function getIsAtallList()
+    {
+        return ['0' => __('Is_atall 0'), '1' => __('Is_atall 1')];
+    }
+
+    public function vbotrobot()
+    {
+        return $this->belongsTo('VbotRobot', 'robot_ids', 'id', [], 'LEFT')->setEagerlyType(0);
+    }
+}

+ 44 - 0
application/admin/model/VbotVariable.php

@@ -0,0 +1,44 @@
+<?php
+
+namespace app\admin\model;
+
+use think\Model;
+use traits\model\SoftDelete;
+
+class VbotVariable extends Model
+{
+
+    use SoftDelete;
+
+    //数据库
+    protected $connection = 'database';
+    // 表名
+    protected $name = 'vbot_variable';
+
+    // 自动写入时间戳字段
+    protected $autoWriteTimestamp = 'int';
+
+    // 定义时间戳字段名
+    protected $createTime = 'createtime';
+    protected $updateTime = 'updatetime';
+    protected $deleteTime = 'deletetime';
+
+    // 追加属性
+    protected $append = [
+        'value_source_text'
+    ];
+
+    public function getValueSourceTextAttr($value, $data)
+    {
+        $value = $value ? $value : (isset($data['value_source']) ? $data['value_source'] : '');
+        $list  = $this->getValueSourceList();
+        return isset($list[$value]) ? $list[$value] : '';
+    }
+
+    public function getValueSourceList()
+    {
+        return ['0' => __('Value_source 0'), '1' => __('Value_source 1')];
+    }
+
+
+}

+ 27 - 0
application/admin/validate/VbotMsglog.php

@@ -0,0 +1,27 @@
+<?php
+
+namespace app\admin\validate;
+
+use think\Validate;
+
+class VbotMsglog extends Validate
+{
+    /**
+     * 验证规则
+     */
+    protected $rule = [
+    ];
+    /**
+     * 提示消息
+     */
+    protected $message = [
+    ];
+    /**
+     * 验证场景
+     */
+    protected $scene = [
+        'add'  => [],
+        'edit' => [],
+    ];
+
+}

+ 27 - 0
application/admin/validate/VbotRobot.php

@@ -0,0 +1,27 @@
+<?php
+
+namespace app\admin\validate;
+
+use think\Validate;
+
+class VbotRobot extends Validate
+{
+    /**
+     * 验证规则
+     */
+    protected $rule = [
+    ];
+    /**
+     * 提示消息
+     */
+    protected $message = [
+    ];
+    /**
+     * 验证场景
+     */
+    protected $scene = [
+        'add'  => [],
+        'edit' => [],
+    ];
+
+}

+ 29 - 0
application/admin/validate/VbotTemplate.php

@@ -0,0 +1,29 @@
+<?php
+
+namespace app\admin\validate;
+
+use think\Validate;
+
+class VbotTemplate extends Validate
+{
+    /**
+     * 验证规则
+     */
+    protected $rule = [
+        'code' => 'regex:^[a-zA-Z][a-zA-Z0-9_]*$',
+    ];
+    /**
+     * 提示消息
+     */
+    protected $message = [
+        'code.regex' => 'Code只能以字母开头',
+    ];
+    /**
+     * 验证场景
+     */
+    protected $scene = [
+        'add'  => [],
+        'edit' => [],
+    ];
+
+}

+ 27 - 0
application/admin/validate/VbotVariable.php

@@ -0,0 +1,27 @@
+<?php
+
+namespace app\admin\validate;
+
+use think\Validate;
+
+class VbotVariable extends Validate
+{
+    /**
+     * 验证规则
+     */
+    protected $rule = [
+    ];
+    /**
+     * 提示消息
+     */
+    protected $message = [
+    ];
+    /**
+     * 验证场景
+     */
+    protected $scene = [
+        'add'  => [],
+        'edit' => [],
+    ];
+
+}

+ 46 - 0
application/admin/view/vbot/msglog/add.html

@@ -0,0 +1,46 @@
+<form id="add-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
+
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Robot_id')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-robot_id" data-rule="required" data-source="robot/index" class="form-control selectpage" name="row[robot_id]" type="text" value="">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Template_id')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-template_id" data-rule="required" data-source="template/index" class="form-control selectpage" name="row[template_id]" type="text" value="">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Template_data')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-template_data" data-rule="required" class="form-control" name="row[template_data]" type="text" value="">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Errmsg')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-errmsg" data-rule="required" class="form-control" name="row[errmsg]" type="text" value="">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            
+            <div class="radio">
+            {foreach name="statusList" item="vo"}
+            <label for="row[status]-{$key}"><input id="row[status]-{$key}" name="row[status]" type="radio" value="{$key}" {in name="key" value="0"}checked{/in} /> {$vo}</label> 
+            {/foreach}
+            </div>
+
+        </div>
+    </div>
+    <div class="form-group layer-footer">
+        <label class="control-label col-xs-12 col-sm-2"></label>
+        <div class="col-xs-12 col-sm-8">
+            <button type="submit" class="btn btn-success btn-embossed disabled">{:__('OK')}</button>
+            <button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
+        </div>
+    </div>
+</form>

+ 46 - 0
application/admin/view/vbot/msglog/edit.html

@@ -0,0 +1,46 @@
+<form id="edit-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
+
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Robot_id')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-robot_id" data-rule="required" data-source="vbot/robot/index" class="form-control selectpage" name="row[robot_id]" type="text" value="{$row.robot_id|htmlentities}">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Template_id')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-template_id" data-rule="required" data-source="vbot/template/index" class="form-control selectpage" name="row[template_id]" type="text" value="{$row.template_id|htmlentities}">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Template_data')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <textarea rows="6" id="c-template_data" class="form-control" name="row[template_data]" type="text">{$row.template_data|htmlentities}</textarea>
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Errmsg')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-errmsg" class="form-control" name="row[errmsg]" type="text" value="{$row.errmsg|htmlentities}">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            
+            <div class="radio">
+            {foreach name="statusList" item="vo"}
+            <label for="row[status]-{$key}"><input id="row[status]-{$key}" name="row[status]" type="radio" value="{$key}" {in name="key" value="$row.status"}checked{/in} /> {$vo}</label> 
+            {/foreach}
+            </div>
+
+        </div>
+    </div>
+    <div class="form-group layer-footer">
+        <label class="control-label col-xs-12 col-sm-2"></label>
+        <div class="col-xs-12 col-sm-8">
+            <button type="submit" class="btn btn-success btn-embossed disabled">{:__('OK')}</button>
+            <button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
+        </div>
+    </div>
+</form>

+ 45 - 0
application/admin/view/vbot/msglog/index.html

@@ -0,0 +1,45 @@
+<div class="panel panel-default panel-intro">
+    
+    <div class="panel-heading">
+        {:build_heading(null,FALSE)}
+        <ul class="nav nav-tabs" data-field="status">
+            <li class="active"><a href="#t-all" data-value="" data-toggle="tab">{:__('All')}</a></li>
+            {foreach name="statusList" item="vo"}
+            <li><a href="#t-{$key}" data-value="{$key}" data-toggle="tab">{$vo}</a></li>
+            {/foreach}
+        </ul>
+    </div>
+
+
+    <div class="panel-body">
+        <div id="myTabContent" class="tab-content">
+            <div class="tab-pane fade active in" id="one">
+                <div class="widget-body no-padding">
+                    <div id="toolbar" class="toolbar">
+                        <a href="javascript:;" class="btn btn-primary btn-refresh" title="{:__('Refresh')}" ><i class="fa fa-refresh"></i> </a>
+                        <!-- <a href="javascript:;" class="btn btn-success btn-add {:$auth->check('vbot/msglog/add')?'':'hide'}" title="{:__('Add')}" ><i class="fa fa-plus"></i> {:__('Add')}</a> -->
+                        <a href="javascript:;" class="btn btn-success btn-edit btn-disabled disabled {:$auth->check('vbot/msglog/edit')?'':'hide'}" title="{:__('Edit')}" ><i class="fa fa-pencil"></i> {:__('Edit')}</a>
+                        <a href="javascript:;" class="btn btn-danger btn-del btn-disabled disabled {:$auth->check('vbot/msglog/del')?'':'hide'}" title="{:__('Delete')}" ><i class="fa fa-trash"></i> {:__('Delete')}</a>
+                        <!-- <a href="javascript:;" class="btn btn-danger btn-import {:$auth->check('vbot/msglog/import')?'':'hide'}" title="{:__('Import')}" id="btn-import-file" data-url="ajax/upload" data-mimetype="csv,xls,xlsx" data-multiple="false"><i class="fa fa-upload"></i> {:__('Import')}</a> -->
+
+                        <!-- <div class="dropdown btn-group {:$auth->check('vbot/msglog/multi')?'':'hide'}">
+                            <a class="btn btn-primary btn-more dropdown-toggle btn-disabled disabled" data-toggle="dropdown"><i class="fa fa-cog"></i> {:__('More')}</a>
+                            <ul class="dropdown-menu text-left" role="menu">
+                                <li><a class="btn btn-link btn-multi btn-disabled disabled" href="javascript:;" data-params="status=normal"><i class="fa fa-eye"></i> {:__('Set to normal')}</a></li>
+                                <li><a class="btn btn-link btn-multi btn-disabled disabled" href="javascript:;" data-params="status=hidden"><i class="fa fa-eye-slash"></i> {:__('Set to hidden')}</a></li>
+                            </ul>
+                        </div> -->
+
+                        <a class="btn btn-success btn-recyclebin btn-dialog {:$auth->check('vbot/msglog/recyclebin')?'':'hide'}" href="vbot/msglog/recyclebin" title="{:__('Recycle bin')}"><i class="fa fa-recycle"></i> {:__('Recycle bin')}</a>
+                    </div>
+                    <table id="table" class="table table-striped table-bordered table-hover table-nowrap"
+                           data-operate-edit="{:$auth->check('vbot/msglog/edit')}" 
+                           data-operate-del="{:$auth->check('vbot/msglog/del')}" 
+                           width="100%">
+                    </table>
+                </div>
+            </div>
+
+        </div>
+    </div>
+</div>

+ 25 - 0
application/admin/view/vbot/msglog/recyclebin.html

@@ -0,0 +1,25 @@
+<div class="panel panel-default panel-intro">
+    {:build_heading()}
+
+    <div class="panel-body">
+        <div id="myTabContent" class="tab-content">
+            <div class="tab-pane fade active in" id="one">
+                <div class="widget-body no-padding">
+                    <div id="toolbar" class="toolbar">
+                        {:build_toolbar('refresh')}
+                        <a class="btn btn-info btn-multi btn-disabled disabled {:$auth->check('vbot/msglog/restore')?'':'hide'}" href="javascript:;" data-url="vbot/msglog/restore" data-action="restore"><i class="fa fa-rotate-left"></i> {:__('Restore')}</a>
+                        <a class="btn btn-danger btn-multi btn-disabled disabled {:$auth->check('vbot/msglog/destroy')?'':'hide'}" href="javascript:;" data-url="vbot/msglog/destroy" data-action="destroy"><i class="fa fa-times"></i> {:__('Destroy')}</a>
+                        <a class="btn btn-success btn-restoreall {:$auth->check('vbot/msglog/restore')?'':'hide'}" href="javascript:;" data-url="vbot/msglog/restore" title="{:__('Restore all')}"><i class="fa fa-rotate-left"></i> {:__('Restore all')}</a>
+                        <a class="btn btn-danger btn-destroyall {:$auth->check('vbot/msglog/destroy')?'':'hide'}" href="javascript:;" data-url="vbot/msglog/destroy" title="{:__('Destroy all')}"><i class="fa fa-times"></i> {:__('Destroy all')}</a>
+                    </div>
+                    <table id="table" class="table table-striped table-bordered table-hover"
+                           data-operate-restore="{:$auth->check('vbot/msglog/restore')}"
+                           data-operate-destroy="{:$auth->check('vbot/msglog/destroy')}"
+                           width="100%">
+                    </table>
+                </div>
+            </div>
+
+        </div>
+    </div>
+</div>

+ 57 - 0
application/admin/view/vbot/robot/add.html

@@ -0,0 +1,57 @@
+<form id="add-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
+
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Name')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-name" data-rule="required" class="form-control" name="row[name]" type="text" value="">
+        </div>
+    </div>
+    <!-- <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Logo_image')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <div class="input-group">
+                <input id="c-logo_image" class="form-control" size="50" name="row[logo_image]" type="text" value="">
+                <div class="input-group-addon no-border no-padding">
+                    <span><button type="button" id="plupload-logo_image" class="btn btn-danger plupload" data-input-id="c-logo_image" data-mimetype="image/gif,image/jpeg,image/png,image/jpg,image/bmp" data-multiple="false" data-preview-id="p-logo_image"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
+                    <span><button type="button" id="fachoose-logo_image" class="btn btn-primary fachoose" data-input-id="c-logo_image" data-mimetype="image/*" data-multiple="false"><i class="fa fa-list"></i> {:__('Choose')}</button></span>
+                </div>
+                <span class="msg-box n-right" for="c-logo_image"></span>
+            </div>
+            <ul class="row list-inline plupload-preview" id="p-logo_image"></ul>
+            <span class="help-block">此logo不会自动设置为机器人头像,目前仅用于识别机器人</span>
+        </div>
+    </div> -->
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Webhook')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <textarea rows="4" id="c-webhook" data-rule="required" class="form-control" name="row[webhook]"></textarea>
+            <span class="help-block">
+                <a target='_blank' href="https://shimo.im/docs/4np3VlA8OlMqA4Rq">如何获取?</a> 
+                <a id="send_test_msg" class="text-success" href="javascript:void(0);">发送测试消息</a>
+            </span>
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Weigh')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-weigh" data-rule="required" class="form-control" name="row[weigh]" type="number" value="0">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Openswitch')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            
+            <input  id="c-openswitch" name="row[openswitch]" type="hidden" value="0">
+            <a href="javascript:;" data-toggle="switcher" class="btn-switcher" data-input-id="c-openswitch" data-yes="1" data-no="0" >
+                <i class="fa fa-toggle-on text-success fa-flip-horizontal text-gray fa-2x"></i>
+            </a>
+        </div>
+    </div>
+    <div class="form-group layer-footer">
+        <label class="control-label col-xs-12 col-sm-2"></label>
+        <div class="col-xs-12 col-sm-8">
+            <button type="submit" class="btn btn-success btn-embossed disabled">{:__('OK')}</button>
+            <button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
+        </div>
+    </div>
+</form>

+ 56 - 0
application/admin/view/vbot/robot/edit.html

@@ -0,0 +1,56 @@
+<form id="edit-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
+
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Name')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-name" data-rule="required" class="form-control" name="row[name]" type="text" value="{$row.name|htmlentities}">
+        </div>
+    </div>
+    <!-- <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Logo_image')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <div class="input-group">
+                <input id="c-logo_image" class="form-control" size="50" name="row[logo_image]" type="text" value="{$row.logo_image|htmlentities}">
+                <div class="input-group-addon no-border no-padding">
+                    <span><button type="button" id="plupload-logo_image" class="btn btn-danger plupload" data-input-id="c-logo_image" data-mimetype="image/gif,image/jpeg,image/png,image/jpg,image/bmp" data-multiple="false" data-preview-id="p-logo_image"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
+                    <span><button type="button" id="fachoose-logo_image" class="btn btn-primary fachoose" data-input-id="c-logo_image" data-mimetype="image/*" data-multiple="false"><i class="fa fa-list"></i> {:__('Choose')}</button></span>
+                </div>
+                <span class="msg-box n-right" for="c-logo_image"></span>
+            </div>
+            <ul class="row list-inline plupload-preview" id="p-logo_image"></ul>
+        </div>
+    </div> -->
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Webhook')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <textarea rows="4" id="c-webhook" data-rule="required" class="form-control" name="row[webhook]">{$row.webhook|htmlentities}</textarea>
+            <span class="help-block">
+                <a target='_blank' href="https://shimo.im/docs/4np3VlA8OlMqA4Rq">如何获取?</a> 
+                <a id="send_test_msg" class="text-success" href="javascript:void(0);">发送测试消息</a>
+            </span>
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Weigh')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-weigh" data-rule="required" class="form-control" name="row[weigh]" type="number" value="{$row.weigh|htmlentities}">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Openswitch')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            
+            <input  id="c-openswitch" name="row[openswitch]" type="hidden" value="{$row.openswitch}">
+            <a href="javascript:;" data-toggle="switcher" class="btn-switcher" data-input-id="c-openswitch" data-yes="1" data-no="0" >
+                <i class="fa fa-toggle-on text-success {eq name="$row.openswitch" value="0"}fa-flip-horizontal text-gray{/eq} fa-2x"></i>
+            </a>
+        </div>
+    </div>
+    <div class="form-group layer-footer">
+        <label class="control-label col-xs-12 col-sm-2"></label>
+        <div class="col-xs-12 col-sm-8">
+            <button type="submit" class="btn btn-success btn-embossed disabled">{:__('OK')}</button>
+            <button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
+        </div>
+    </div>
+</form>

+ 35 - 0
application/admin/view/vbot/robot/index.html

@@ -0,0 +1,35 @@
+<div class="panel panel-default panel-intro">
+    {:build_heading()}
+
+    <div class="panel-body">
+        <div id="myTabContent" class="tab-content">
+            <div class="tab-pane fade active in" id="one">
+                <div class="widget-body no-padding">
+                    <div id="toolbar" class="toolbar">
+                        <a href="javascript:;" class="btn btn-primary btn-refresh" title="{:__('Refresh')}" ><i class="fa fa-refresh"></i> </a>
+                        <a href="javascript:;" class="btn btn-success btn-add {:$auth->check('vbot/robot/add')?'':'hide'}" title="{:__('Add')}" ><i class="fa fa-plus"></i> {:__('Add')}</a>
+                        <a href="javascript:;" class="btn btn-success btn-edit btn-disabled disabled {:$auth->check('vbot/robot/edit')?'':'hide'}" title="{:__('Edit')}" ><i class="fa fa-pencil"></i> {:__('Edit')}</a>
+                        <a href="javascript:;" class="btn btn-danger btn-del btn-disabled disabled {:$auth->check('vbot/robot/del')?'':'hide'}" title="{:__('Delete')}" ><i class="fa fa-trash"></i> {:__('Delete')}</a>
+                        <!-- <a href="javascript:;" class="btn btn-danger btn-import {:$auth->check('vbot/robot/import')?'':'hide'}" title="{:__('Import')}" id="btn-import-file" data-url="ajax/upload" data-mimetype="csv,xls,xlsx" data-multiple="false"><i class="fa fa-upload"></i> {:__('Import')}</a> -->
+
+                        <div class="dropdown btn-group {:$auth->check('vbot/robot/multi')?'':'hide'}">
+                            <a class="btn btn-primary btn-more dropdown-toggle btn-disabled disabled" data-toggle="dropdown"><i class="fa fa-cog"></i> {:__('More')}</a>
+                            <ul class="dropdown-menu text-left" role="menu">
+                                <li><a class="btn btn-link btn-multi btn-disabled disabled" href="javascript:;" data-params="openswitch=1"><i class="fa fa-eye"></i> {:__('Set to open')}</a></li>
+                                <li><a class="btn btn-link btn-multi btn-disabled disabled" href="javascript:;" data-params="openswitch=0"><i class="fa fa-eye-slash"></i> {:__('Set to close')}</a></li>
+                            </ul>
+                        </div>
+
+                        <a class="btn btn-success btn-recyclebin btn-dialog {:$auth->check('vbot/robot/recyclebin')?'':'hide'}" href="vbot/robot/recyclebin" title="{:__('Recycle bin')}"><i class="fa fa-recycle"></i> {:__('Recycle bin')}</a>
+                    </div>
+                    <table id="table" class="table table-striped table-bordered table-hover table-nowrap"
+                           data-operate-edit="{:$auth->check('vbot/robot/edit')}" 
+                           data-operate-del="{:$auth->check('vbot/robot/del')}" 
+                           width="100%">
+                    </table>
+                </div>
+            </div>
+
+        </div>
+    </div>
+</div>

+ 25 - 0
application/admin/view/vbot/robot/recyclebin.html

@@ -0,0 +1,25 @@
+<div class="panel panel-default panel-intro">
+    {:build_heading()}
+
+    <div class="panel-body">
+        <div id="myTabContent" class="tab-content">
+            <div class="tab-pane fade active in" id="one">
+                <div class="widget-body no-padding">
+                    <div id="toolbar" class="toolbar">
+                        {:build_toolbar('refresh')}
+                        <a class="btn btn-info btn-multi btn-disabled disabled {:$auth->check('vbot/robot/restore')?'':'hide'}" href="javascript:;" data-url="vbot/robot/restore" data-action="restore"><i class="fa fa-rotate-left"></i> {:__('Restore')}</a>
+                        <a class="btn btn-danger btn-multi btn-disabled disabled {:$auth->check('vbot/robot/destroy')?'':'hide'}" href="javascript:;" data-url="vbot/robot/destroy" data-action="destroy"><i class="fa fa-times"></i> {:__('Destroy')}</a>
+                        <a class="btn btn-success btn-restoreall {:$auth->check('vbot/robot/restore')?'':'hide'}" href="javascript:;" data-url="vbot/robot/restore" title="{:__('Restore all')}"><i class="fa fa-rotate-left"></i> {:__('Restore all')}</a>
+                        <a class="btn btn-danger btn-destroyall {:$auth->check('vbot/robot/destroy')?'':'hide'}" href="javascript:;" data-url="vbot/robot/destroy" title="{:__('Destroy all')}"><i class="fa fa-times"></i> {:__('Destroy all')}</a>
+                    </div>
+                    <table id="table" class="table table-striped table-bordered table-hover"
+                           data-operate-restore="{:$auth->check('vbot/robot/restore')}"
+                           data-operate-destroy="{:$auth->check('vbot/robot/destroy')}"
+                           width="100%">
+                    </table>
+                </div>
+            </div>
+
+        </div>
+    </div>
+</div>

+ 143 - 0
application/admin/view/vbot/template/add.html

@@ -0,0 +1,143 @@
+<form id="add-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
+
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Name')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-name" data-rule="required" class="form-control" name="row[name]" type="text" value="">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Code')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-code" data-rule="code_check" data-rule-code_check="[/^[a-zA-Z][a-zA-Z0-9_]*$/,'请以字母开头']" class="form-control" name="row[code]" type="text" value="">
+            <span class="help-block">作用等同ID,可用于在业务代码中调用此模板发送消息</span>
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Robot_ids')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-robot_ids" data-rule="required" data-source="vbot/robot/index" data-multiple="true" class="form-control selectpage" name="row[robot_ids]" type="text" value="">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Typelist')}:</label>
+        <div class="col-xs-12 col-sm-8">
+                        
+            <select  id="c-typelist" data-rule="required" class="form-control selectpicker" name="row[typelist]">
+                {foreach name="typelistList" item="vo"}
+                    <option value="{$key}" {in name="key" value="text"}selected{/in}>{$vo}</option>
+                {/foreach}
+            </select>
+
+        </div>
+    </div>
+
+    <div class="form-group msg_group msg_content">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Content')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <textarea placeholder="支持使用变量,格式:${变量名}" id="c-content" name="row[content]" class="form-control" rows="5"></textarea>
+            <p class="help-block msg_group markdown_help" style="display: none;">请输入markdown格式的内容 <a id="show_grammar" href="javascript:void();">显示语法</a></p>
+            <span class="help-block msg_group markdown_grammar" style="display: none;">
+                目前只支持这些元素<br />
+                标题:#到######<br />
+                链接:[锚](url)<br />
+                单行代码:`code`<br />引用:><br />
+                加粗:**<br />
+                字体颜色:<pre><xmp><font color="info">绿色</font></xmp>color可设置的值有 info:绿色、comment:灰色、warning:橙红色</pre>
+            </span>
+        </div>
+    </div>
+
+    <div class="form-group msg_group msg_picurl_image">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Picurl_image')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <div class="input-group">
+                <input id="c-picurl_image" class="form-control" size="50" name="row[picurl_image]" type="text" value="">
+                <div class="input-group-addon no-border no-padding">
+                    <span><button type="button" id="plupload-picurl_image" class="btn btn-danger plupload" data-input-id="c-picurl_image" data-mimetype="image/gif,image/jpeg,image/png,image/jpg,image/bmp" data-multiple="false" data-preview-id="p-picurl_image"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
+                    <span><button type="button" id="fachoose-picurl_image" class="btn btn-primary fachoose" data-input-id="c-picurl_image" data-mimetype="image/*" data-multiple="false"><i class="fa fa-list"></i> {:__('Choose')}</button></span>
+                </div>
+                <span class="msg-box n-right" for="c-picurl_image"></span>
+            </div>
+            <ul class="row list-inline plupload-preview" id="p-picurl_image"></ul>
+            <span class="help-block">超过2M的图片无法发送,支持JPG、PNG格式</span>
+        </div>
+    </div>
+
+    <div class="form-group msg_group msg_news">
+        <label class="control-label col-xs-12 col-sm-2">{:__('News')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <dl class="fieldlist" data-name="row[news]" data-template="newstpl">
+                <dd>
+                    <ins class="news_ins">标题</ins>
+                    <ins class="news_ins">链接URL</ins>
+                    <ins class="news_ins">链接图片URL</ins>
+                </dd>
+                <dd><a href="javascript:;" class="btn btn-sm btn-success btn-append"><i class="fa fa-plus"></i> {:__('Append')}</a></dd>
+                <textarea name="row[news]" class="form-control hide" cols="30" rows="5"></textarea>
+            </dl>
+            <!--定义模板-->
+            <div class="hide" id="newstpl">
+                <dd class="form-inline">
+                    <input type="text" name="row[<%=name%>][<%=index%>][title]" class="form-control news_input" value="<%=row['title']%>" size="10"> 
+                    <input type="text" name="row[<%=name%>][<%=index%>][url]" class="form-control news_input" value="<%=row['url']%>" size="30"> 
+                    <input type="text" name="row[<%=name%>][<%=index%>][picurl]" class="form-control news_input" value="<%=row['picurl']%>" size="30"> 
+                    <span class="btn btn-sm btn-danger btn-remove"><i class="fa fa-times"></i></span> <span class="btn btn-sm btn-primary btn-dragsort"><i class="fa fa-arrows"></i></span>
+                </dd>
+            </div>
+            <span class="help-block">标题支持使用变量,当只有一条图文时,才会显示消息内容,最多八条图文</span>
+        </div>
+    </div>
+
+    <div class="form-group msg_group msg_at">
+        <label class="control-label col-xs-12 col-sm-2">{:__('At_mobiles')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-at_mobiles" data-source="user/user/index" data-multiple="true" data-primary-key="mobile" data-field="mobile" class="form-control selectpage" name="row[at_mobiles]" type="text" value="">
+        </div>
+    </div>
+
+    <div class="form-group msg_group msg_at">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Is_atall')}:</label>
+        <div class="col-xs-12 col-sm-8">
+                        
+            <select  id="c-is_atall" class="form-control selectpicker" name="row[is_atall]">
+                {foreach name="isAtallList" item="vo"}
+                    <option value="{$key}" {in name="key" value="0"}selected{/in}>{$vo}</option>
+                {/foreach}
+            </select>
+
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Openswitch')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            
+            <input  id="c-openswitch" name="row[openswitch]" type="hidden" value="1">
+            <a href="javascript:;" data-toggle="switcher" class="btn-switcher" data-input-id="c-openswitch" data-yes="1" data-no="0" >
+                <i class="fa fa-toggle-on text-success fa-2x"></i>
+            </a>
+        </div>
+    </div>
+    <div class="form-group layer-footer">
+        <label class="control-label col-xs-12 col-sm-2"></label>
+        <div class="col-xs-12 col-sm-8">
+            <button type="submit" class="btn btn-success btn-embossed disabled">{:__('OK')}</button>
+            <button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
+        </div>
+    </div>
+</form>
+<style type="text/css">
+    .fieldlist dd input:nth-child(2),.fieldlist dd ins:nth-child(2) {
+        width: 58%;
+    }
+    .fieldlist dd input:first-child,.fieldlist dd ins:first-child {
+        width: 25%;
+    }
+
+    .fieldlist dd input:nth-child(2),.fieldlist dd ins:nth-child(2) {
+        width: 58%;
+    }
+    .fieldlist dd .news_input,.fieldlist .news_ins{
+        width: 27% !important;
+    }
+</style>

+ 142 - 0
application/admin/view/vbot/template/edit.html

@@ -0,0 +1,142 @@
+<form id="edit-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
+
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Name')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-name" data-rule="required" class="form-control" name="row[name]" type="text" value="{$row.name|htmlentities}">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Code')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-code" data-rule="code_check" data-rule-code_check="[/^[a-zA-Z][a-zA-Z0-9_]*$/,'请以字母开头']" class="form-control" name="row[code]" type="text" value="{$row.code|htmlentities}">
+            <span class="help-block">作用等同ID,可用于在业务代码中调用此模板发送消息</span>
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Robot_ids')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-robot_ids" data-rule="required" data-source="vbot/robot/index" data-multiple="true" class="form-control selectpage" name="row[robot_ids]" type="text" value="{$row.robot_ids|htmlentities}">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Typelist')}:</label>
+        <div class="col-xs-12 col-sm-8">
+                        
+            <select  id="c-typelist" data-rule="required" class="form-control selectpicker" name="row[typelist]">
+                {foreach name="typelistList" item="vo"}
+                    <option value="{$key}" {in name="key" value="$row.typelist"}selected{/in}>{$vo}</option>
+                {/foreach}
+            </select>
+
+        </div>
+    </div>
+
+    <div class="form-group msg_group msg_content">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Content')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <textarea id="c-content" placeholder="支持使用变量,格式:${变量名}" name="row[content]" class="form-control" rows="5">{$row.content|htmlentities}</textarea>
+            <p class="help-block msg_group markdown_help" style="display: none;">请输入markdown格式的内容 <a id="show_grammar" href="javascript:void();">显示语法</a></p>
+            <span class="help-block msg_group markdown_grammar" style="display: none;">
+                目前只支持这些元素<br />
+                标题:#到######<br />
+                链接:[锚](url)<br />
+                单行代码:`code`<br />引用:><br />
+                加粗:**<br />
+                字体颜色:<pre><xmp><font color="info">绿色</font></xmp>color可设置的值有 info:绿色、comment:灰色、warning:橙红色</pre>
+            </span>
+        </div>
+    </div>
+
+    <div class="form-group msg_group msg_picurl_image">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Picurl_image')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <div class="input-group">
+                <input id="c-picurl_image" class="form-control" size="50" name="row[picurl_image]" type="text" value="{$row.picurl_image|htmlentities}">
+                <div class="input-group-addon no-border no-padding">
+                    <span><button type="button" id="plupload-picurl_image" class="btn btn-danger plupload" data-input-id="c-picurl_image" data-mimetype="image/gif,image/jpeg,image/png,image/jpg,image/bmp" data-multiple="false" data-preview-id="p-picurl_image"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
+                    <span><button type="button" id="fachoose-picurl_image" class="btn btn-primary fachoose" data-input-id="c-picurl_image" data-mimetype="image/*" data-multiple="false"><i class="fa fa-list"></i> {:__('Choose')}</button></span>
+                </div>
+                <span class="msg-box n-right" for="c-picurl_image"></span>
+            </div>
+            <ul class="row list-inline plupload-preview" id="p-picurl_image"></ul>
+            <span class="help-block">超过2M的图片无法发送,支持JPG、PNG格式</span>
+        </div>
+    </div>
+
+    <div class="form-group msg_group msg_news">
+        <label class="control-label col-xs-12 col-sm-2">{:__('News')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <dl class="fieldlist" data-name="row[news]" data-template="newstpl">
+                <dd>
+                    <ins class="news_ins">标题</ins>
+                    <ins class="news_ins">链接URL</ins>
+                    <ins class="news_ins">链接图片URL</ins>
+                </dd>
+                <dd><a href="javascript:;" class="btn btn-sm btn-success btn-append"><i class="fa fa-plus"></i> {:__('Append')}</a></dd>
+                <textarea name="row[news]" class="form-control hide" cols="30" rows="5">{$row.news}</textarea>
+            </dl>
+            <!--定义模板-->
+            <div class="hide" id="newstpl">
+                <dd class="form-inline">
+                    <input type="text" name="row[<%=name%>][<%=index%>][title]" class="form-control news_input" value="<%=row['title']%>" size="10"> 
+                    <input type="text" name="row[<%=name%>][<%=index%>][url]" class="form-control news_input" value="<%=row['url']%>" size="30"> 
+                    <input type="text" name="row[<%=name%>][<%=index%>][picurl]" class="form-control news_input" value="<%=row['picurl']%>" size="30"> 
+                    <span class="btn btn-sm btn-danger btn-remove"><i class="fa fa-times"></i></span> <span class="btn btn-sm btn-primary btn-dragsort"><i class="fa fa-arrows"></i></span>
+                </dd>
+            </div>
+            <span class="help-block">标题支持使用变量,当只有一条图文时,才会显示消息内容,最多八条图文</span>
+        </div>
+    </div>
+    
+    <div class="form-group msg_group msg_at">
+        <label class="control-label col-xs-12 col-sm-2">{:__('At_mobiles')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-at_mobiles" data-source="user/user/index" data-multiple="true" data-primary-key="mobile" data-field="mobile" class="form-control selectpage" name="row[at_mobiles]" type="text" value="{$row.at_mobiles|htmlentities}">
+        </div>
+    </div>
+    <div class="form-group msg_group msg_at">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Is_atall')}:</label>
+        <div class="col-xs-12 col-sm-8">
+                        
+            <select  id="c-is_atall" class="form-control selectpicker" name="row[is_atall]">
+                {foreach name="isAtallList" item="vo"}
+                    <option value="{$key}" {in name="key" value="$row.is_atall"}selected{/in}>{$vo}</option>
+                {/foreach}
+            </select>
+
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Openswitch')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            
+            <input  id="c-openswitch" name="row[openswitch]" type="hidden" value="{$row.openswitch}">
+            <a href="javascript:;" data-toggle="switcher" class="btn-switcher" data-input-id="c-openswitch" data-yes="1" data-no="0" >
+                <i class="fa fa-toggle-on text-success {eq name="$row.openswitch" value="0"}fa-flip-horizontal text-gray{/eq} fa-2x"></i>
+            </a>
+        </div>
+    </div>
+    <div class="form-group layer-footer">
+        <label class="control-label col-xs-12 col-sm-2"></label>
+        <div class="col-xs-12 col-sm-8">
+            <button type="submit" class="btn btn-success btn-embossed disabled">{:__('OK')}</button>
+            <button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
+        </div>
+    </div>
+</form>
+<style type="text/css">
+    .fieldlist dd input:nth-child(2),.fieldlist dd ins:nth-child(2) {
+        width: 58%;
+    }
+    .fieldlist dd input:first-child,.fieldlist dd ins:first-child {
+        width: 25%;
+    }
+
+    .fieldlist dd input:nth-child(2),.fieldlist dd ins:nth-child(2) {
+        width: 58%;
+    }
+    .fieldlist dd .news_input,.fieldlist .news_ins{
+        width: 27% !important;
+    }
+</style>

+ 35 - 0
application/admin/view/vbot/template/index.html

@@ -0,0 +1,35 @@
+<div class="panel panel-default panel-intro">
+    {:build_heading()}
+
+    <div class="panel-body">
+        <div id="myTabContent" class="tab-content">
+            <div class="tab-pane fade active in" id="one">
+                <div class="widget-body no-padding">
+                    <div id="toolbar" class="toolbar">
+                        <a href="javascript:;" class="btn btn-primary btn-refresh" title="{:__('Refresh')}" ><i class="fa fa-refresh"></i> </a>
+                        <a href="javascript:;" class="btn btn-success btn-add {:$auth->check('vbot/template/add')?'':'hide'}" title="{:__('Add')}" ><i class="fa fa-plus"></i> {:__('Add')}</a>
+                        <a href="javascript:;" class="btn btn-success btn-edit btn-disabled disabled {:$auth->check('vbot/template/edit')?'':'hide'}" title="{:__('Edit')}" ><i class="fa fa-pencil"></i> {:__('Edit')}</a>
+                        <a href="javascript:;" class="btn btn-danger btn-del btn-disabled disabled {:$auth->check('vbot/template/del')?'':'hide'}" title="{:__('Delete')}" ><i class="fa fa-trash"></i> {:__('Delete')}</a>
+                        <!-- <a href="javascript:;" class="btn btn-danger btn-import {:$auth->check('vbot/template/import')?'':'hide'}" title="{:__('Import')}" id="btn-import-file" data-url="ajax/upload" data-mimetype="csv,xls,xlsx" data-multiple="false"><i class="fa fa-upload"></i> {:__('Import')}</a> -->
+
+                        <div class="dropdown btn-group {:$auth->check('vbot/template/multi')?'':'hide'}">
+                            <a class="btn btn-primary btn-more dropdown-toggle btn-disabled disabled" data-toggle="dropdown"><i class="fa fa-cog"></i> {:__('More')}</a>
+                            <ul class="dropdown-menu text-left" role="menu">
+                                <li><a class="btn btn-link btn-multi btn-disabled disabled" href="javascript:;" data-params="openswitch=1"><i class="fa fa-eye"></i> {:__('Set to open')}</a></li>
+                                <li><a class="btn btn-link btn-multi btn-disabled disabled" href="javascript:;" data-params="openswitch=0"><i class="fa fa-eye-slash"></i> {:__('Set to close')}</a></li>
+                            </ul>
+                        </div>
+
+                        <a class="btn btn-success btn-recyclebin btn-dialog {:$auth->check('vbot/template/recyclebin')?'':'hide'}" href="vbot/template/recyclebin" title="{:__('Recycle bin')}"><i class="fa fa-recycle"></i> {:__('Recycle bin')}</a>
+                    </div>
+                    <table id="table" class="table table-striped table-bordered table-hover table-nowrap"
+                           data-operate-edit="{:$auth->check('vbot/template/edit')}" 
+                           data-operate-del="{:$auth->check('vbot/template/del')}" 
+                           width="100%">
+                    </table>
+                </div>
+            </div>
+
+        </div>
+    </div>
+</div>

+ 25 - 0
application/admin/view/vbot/template/recyclebin.html

@@ -0,0 +1,25 @@
+<div class="panel panel-default panel-intro">
+    {:build_heading()}
+
+    <div class="panel-body">
+        <div id="myTabContent" class="tab-content">
+            <div class="tab-pane fade active in" id="one">
+                <div class="widget-body no-padding">
+                    <div id="toolbar" class="toolbar">
+                        {:build_toolbar('refresh')}
+                        <a class="btn btn-info btn-multi btn-disabled disabled {:$auth->check('vbot/template/restore')?'':'hide'}" href="javascript:;" data-url="vbot/template/restore" data-action="restore"><i class="fa fa-rotate-left"></i> {:__('Restore')}</a>
+                        <a class="btn btn-danger btn-multi btn-disabled disabled {:$auth->check('vbot/template/destroy')?'':'hide'}" href="javascript:;" data-url="vbot/template/destroy" data-action="destroy"><i class="fa fa-times"></i> {:__('Destroy')}</a>
+                        <a class="btn btn-success btn-restoreall {:$auth->check('vbot/template/restore')?'':'hide'}" href="javascript:;" data-url="vbot/template/restore" title="{:__('Restore all')}"><i class="fa fa-rotate-left"></i> {:__('Restore all')}</a>
+                        <a class="btn btn-danger btn-destroyall {:$auth->check('vbot/template/destroy')?'':'hide'}" href="javascript:;" data-url="vbot/template/destroy" title="{:__('Destroy all')}"><i class="fa fa-times"></i> {:__('Destroy all')}</a>
+                    </div>
+                    <table id="table" class="table table-striped table-bordered table-hover"
+                           data-operate-restore="{:$auth->check('vbot/template/restore')}"
+                           data-operate-destroy="{:$auth->check('vbot/template/destroy')}"
+                           width="100%">
+                    </table>
+                </div>
+            </div>
+
+        </div>
+    </div>
+</div>

+ 67 - 0
application/admin/view/vbot/variable/add.html

@@ -0,0 +1,67 @@
+<style type="text/css">
+    .variable_function{
+        display: none;
+    }
+</style>
+<form id="add-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
+
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Name')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-name" data-rule="required" class="form-control" name="row[name]" type="text" value="">
+            <span id="reference-name" class="help-block"> </span>
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Value_source')}:</label>
+        <div class="col-xs-12 col-sm-8">
+                        
+            <select  id="c-value_source" data-rule="required" class="form-control selectpicker" name="row[value_source]">
+                {foreach name="valueSourceList" item="vo"}
+                    <option value="{$key}" {in name="key" value="0"}selected{/in}>{$vo}</option>
+                {/foreach}
+            </select>
+
+        </div>
+    </div>
+    <div class="form-group variable_sql">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Sql')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <textarea id="c-sql" placeholder="" class="form-control" name="row[sql]" type="text"></textarea>
+            <span class="help-block">可使用 __PREFIX__ 表示数据库前缀,如:SELECT count(id) FROM __PREFIX__user</span>
+            <span class="help-block">将在需要时,自动使用 Db::query(SQL); 来获取变量值</span>
+        </div>
+    </div>
+    <div class="form-group variable_function">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Namespace')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-namespace" class="form-control" name="row[namespace]" type="text" value="">
+        </div>
+    </div>
+    <div class="form-group variable_function">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Class')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-class" class="form-control" name="row[class]" type="text" value="">
+        </div>
+    </div>
+    <div class="form-group variable_function">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Function')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-function" class="form-control" name="row[function]" type="text" value="">
+        </div>
+    </div>
+    <div class="form-group variable_function">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Params')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-params" class="form-control" name="row[params]" type="text" value="">
+            <span class="help-block">将使用 Hook::exec('命名空间\类名','方法名',参数); 来获取变量值</span>
+        </div>
+    </div>
+    <div class="form-group layer-footer">
+        <label class="control-label col-xs-12 col-sm-2"></label>
+        <div class="col-xs-12 col-sm-8">
+            <button type="submit" class="btn btn-success btn-embossed disabled">{:__('OK')}</button>
+            <button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
+        </div>
+    </div>
+</form>

+ 62 - 0
application/admin/view/vbot/variable/edit.html

@@ -0,0 +1,62 @@
+<form id="edit-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
+
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Name')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-name" data-rule="required" class="form-control" name="row[name]" type="text" value="{$row.name|htmlentities}">
+            <span id="reference-name" class="help-block">可在通知模板中引用,引用名称:${{$row.name|htmlentities}}</span>
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Value_source')}:</label>
+        <div class="col-xs-12 col-sm-8">
+                        
+            <select  id="c-value_source" data-rule="required" class="form-control selectpicker" name="row[value_source]">
+                {foreach name="valueSourceList" item="vo"}
+                    <option value="{$key}" {in name="key" value="$row.value_source"}selected{/in}>{$vo}</option>
+                {/foreach}
+            </select>
+
+        </div>
+    </div>
+    <div class="form-group variable_sql">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Sql')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <textarea id="c-sql" class="form-control" name="row[sql]" type="text">{$row.sql|htmlentities}</textarea>
+            <span class="help-block">可使用 __PREFIX__ 表示数据库前缀,如:SELECT count(id) FROM __PREFIX__user</span>
+            <span class="help-block">将在需要时,自动使用 Db::query(SQL); 来获取变量值</span>
+        </div>
+    </div>
+    <div class="form-group variable_function">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Namespace')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-namespace" class="form-control" name="row[namespace]" type="text" value="{$row.namespace|htmlentities}">
+        </div>
+    </div>
+    <div class="form-group variable_function">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Class')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-class" class="form-control" name="row[class]" type="text" value="{$row.class|htmlentities}">
+        </div>
+    </div>
+    <div class="form-group variable_function">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Function')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-function" class="form-control" name="row[function]" type="text" value="{$row.function|htmlentities}">
+        </div>
+    </div>
+    <div class="form-group variable_function">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Params')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-params" class="form-control" name="row[params]" type="text" value="{$row.params|htmlentities}">
+            <span class="help-block">将使用 Hook::exec('命名空间\类名','方法名',参数); 来获取变量值</span>
+        </div>
+    </div>
+    <div class="form-group layer-footer">
+        <label class="control-label col-xs-12 col-sm-2"></label>
+        <div class="col-xs-12 col-sm-8">
+            <button type="submit" class="btn btn-success btn-embossed disabled">{:__('OK')}</button>
+            <button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
+        </div>
+    </div>
+</form>

+ 35 - 0
application/admin/view/vbot/variable/index.html

@@ -0,0 +1,35 @@
+<div class="panel panel-default panel-intro">
+    {:build_heading()}
+
+    <div class="panel-body">
+        <div id="myTabContent" class="tab-content">
+            <div class="tab-pane fade active in" id="one">
+                <div class="widget-body no-padding">
+                    <div id="toolbar" class="toolbar">
+                        <a href="javascript:;" class="btn btn-primary btn-refresh" title="{:__('Refresh')}" ><i class="fa fa-refresh"></i> </a>
+                        <a href="javascript:;" class="btn btn-success btn-add {:$auth->check('vbot/variable/add')?'':'hide'}" title="{:__('Add')}" ><i class="fa fa-plus"></i> {:__('Add')}</a>
+                        <a href="javascript:;" class="btn btn-success btn-edit btn-disabled disabled {:$auth->check('vbot/variable/edit')?'':'hide'}" title="{:__('Edit')}" ><i class="fa fa-pencil"></i> {:__('Edit')}</a>
+                        <a href="javascript:;" class="btn btn-danger btn-del btn-disabled disabled {:$auth->check('vbot/variable/del')?'':'hide'}" title="{:__('Delete')}" ><i class="fa fa-trash"></i> {:__('Delete')}</a>
+                        <!-- <a href="javascript:;" class="btn btn-danger btn-import {:$auth->check('vbot/variable/import')?'':'hide'}" title="{:__('Import')}" id="btn-import-file" data-url="ajax/upload" data-mimetype="csv,xls,xlsx" data-multiple="false"><i class="fa fa-upload"></i> {:__('Import')}</a> -->
+
+                        <!-- <div class="dropdown btn-group {:$auth->check('vbot/variable/multi')?'':'hide'}">
+                            <a class="btn btn-primary btn-more dropdown-toggle btn-disabled disabled" data-toggle="dropdown"><i class="fa fa-cog"></i> {:__('More')}</a>
+                            <ul class="dropdown-menu text-left" role="menu">
+                                <li><a class="btn btn-link btn-multi btn-disabled disabled" href="javascript:;" data-params="status=normal"><i class="fa fa-eye"></i> {:__('Set to normal')}</a></li>
+                                <li><a class="btn btn-link btn-multi btn-disabled disabled" href="javascript:;" data-params="status=hidden"><i class="fa fa-eye-slash"></i> {:__('Set to hidden')}</a></li>
+                            </ul>
+                        </div> -->
+
+                        <a class="btn btn-success btn-recyclebin btn-dialog {:$auth->check('vbot/variable/recyclebin')?'':'hide'}" href="vbot/variable/recyclebin" title="{:__('Recycle bin')}"><i class="fa fa-recycle"></i> {:__('Recycle bin')}</a>
+                    </div>
+                    <table id="table" class="table table-striped table-bordered table-hover table-nowrap"
+                           data-operate-edit="{:$auth->check('vbot/variable/edit')}" 
+                           data-operate-del="{:$auth->check('vbot/variable/del')}" 
+                           width="100%">
+                    </table>
+                </div>
+            </div>
+
+        </div>
+    </div>
+</div>

+ 25 - 0
application/admin/view/vbot/variable/recyclebin.html

@@ -0,0 +1,25 @@
+<div class="panel panel-default panel-intro">
+    {:build_heading()}
+
+    <div class="panel-body">
+        <div id="myTabContent" class="tab-content">
+            <div class="tab-pane fade active in" id="one">
+                <div class="widget-body no-padding">
+                    <div id="toolbar" class="toolbar">
+                        {:build_toolbar('refresh')}
+                        <a class="btn btn-info btn-multi btn-disabled disabled {:$auth->check('vbot/variable/restore')?'':'hide'}" href="javascript:;" data-url="vbot/variable/restore" data-action="restore"><i class="fa fa-rotate-left"></i> {:__('Restore')}</a>
+                        <a class="btn btn-danger btn-multi btn-disabled disabled {:$auth->check('vbot/variable/destroy')?'':'hide'}" href="javascript:;" data-url="vbot/variable/destroy" data-action="destroy"><i class="fa fa-times"></i> {:__('Destroy')}</a>
+                        <a class="btn btn-success btn-restoreall {:$auth->check('vbot/variable/restore')?'':'hide'}" href="javascript:;" data-url="vbot/variable/restore" title="{:__('Restore all')}"><i class="fa fa-rotate-left"></i> {:__('Restore all')}</a>
+                        <a class="btn btn-danger btn-destroyall {:$auth->check('vbot/variable/destroy')?'':'hide'}" href="javascript:;" data-url="vbot/variable/destroy" title="{:__('Destroy all')}"><i class="fa fa-times"></i> {:__('Destroy all')}</a>
+                    </div>
+                    <table id="table" class="table table-striped table-bordered table-hover"
+                           data-operate-restore="{:$auth->check('vbot/variable/restore')}"
+                           data-operate-destroy="{:$auth->check('vbot/variable/destroy')}"
+                           width="100%">
+                    </table>
+                </div>
+            </div>
+
+        </div>
+    </div>
+</div>

+ 3 - 0
application/extra/addons.php

@@ -31,6 +31,9 @@ return [
         'sms_check' => [
             'qcloudsms',
         ],
+        'vbot_send_msg' => [
+            'vbot',
+        ],
     ],
     'route' => [
         '/qrcode$' => 'qrcode/index/index',

+ 132 - 0
public/assets/js/backend/vbot/msglog.js

@@ -0,0 +1,132 @@
+define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) {
+
+    var Controller = {
+        index: function () {
+            // 初始化表格参数配置
+            Table.api.init({
+                extend: {
+                    index_url: 'vbot/msglog/index' + location.search,
+                    add_url: 'vbot/msglog/add',
+                    edit_url: 'vbot/msglog/edit',
+                    del_url: 'vbot/msglog/del',
+                    multi_url: 'vbot/msglog/multi',
+                    table: 'vbot_msglog',
+                }
+            });
+
+            var table = $("#table");
+
+            // 初始化表格
+            table.bootstrapTable({
+                url: $.fn.bootstrapTable.defaults.extend.index_url,
+                pk: 'id',
+                sortName: 'id',
+                columns: [
+                    [
+                        {checkbox: true},
+                        {field: 'id', title: __('Id')},
+                        // {field: 'robot_id', title: __('Robot_id')},
+                        {field: 'vbotrobot.name', title: __('vbotrobot.name')},
+                        {field: 'vbottemplate.name', title: __('Template_id')},
+                        // {field: 'template_data', title: __('Template_data')},
+                        {
+                            field: 'status',
+                            title: __('Status'),
+                            searchList: {"0": __('Status 0'), "1": __('Status 1')},
+                            formatter: Table.api.formatter.status
+                        },
+                        {field: 'errmsg', title: __('Errmsg')},
+                        {
+                            field: 'createtime',
+                            title: __('Createtime'),
+                            operate: 'RANGE',
+                            addclass: 'datetimerange',
+                            formatter: Table.api.formatter.datetime
+                        },
+                        {
+                            field: 'operate',
+                            title: __('Operate'),
+                            table: table,
+                            events: Table.api.events.operate,
+                            formatter: Table.api.formatter.operate
+                        }
+                    ]
+                ]
+            });
+
+            // 为表格绑定事件
+            Table.api.bindevent(table);
+        },
+        recyclebin: function () {
+            // 初始化表格参数配置
+            Table.api.init({
+                extend: {
+                    'dragsort_url': ''
+                }
+            });
+
+            var table = $("#table");
+
+            // 初始化表格
+            table.bootstrapTable({
+                url: 'vbot/msglog/recyclebin' + location.search,
+                pk: 'id',
+                sortName: 'id',
+                columns: [
+                    [
+                        {checkbox: true},
+                        {field: 'id', title: __('Id')},
+                        {
+                            field: 'deletetime',
+                            title: __('Deletetime'),
+                            operate: 'RANGE',
+                            addclass: 'datetimerange',
+                            formatter: Table.api.formatter.datetime
+                        },
+                        {
+                            field: 'operate',
+                            width: '130px',
+                            title: __('Operate'),
+                            table: table,
+                            events: Table.api.events.operate,
+                            buttons: [
+                                {
+                                    name: 'Restore',
+                                    text: __('Restore'),
+                                    classname: 'btn btn-xs btn-info btn-ajax btn-restoreit',
+                                    icon: 'fa fa-rotate-left',
+                                    url: 'vbot/msglog/restore',
+                                    refresh: true
+                                },
+                                {
+                                    name: 'Destroy',
+                                    text: __('Destroy'),
+                                    classname: 'btn btn-xs btn-danger btn-ajax btn-destroyit',
+                                    icon: 'fa fa-times',
+                                    url: 'vbot/msglog/destroy',
+                                    refresh: true
+                                }
+                            ],
+                            formatter: Table.api.formatter.operate
+                        }
+                    ]
+                ]
+            });
+
+            // 为表格绑定事件
+            Table.api.bindevent(table);
+        },
+        add: function () {
+            Controller.api.bindevent();
+        },
+        edit: function () {
+            Controller.api.bindevent();
+        },
+        api: {
+            bindevent: function () {
+                Form.api.bindevent($("form[role=form]"));
+            }
+        }
+    };
+    return Controller;
+});

+ 165 - 0
public/assets/js/backend/vbot/robot.js

@@ -0,0 +1,165 @@
+define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) {
+
+    var Controller = {
+        index: function () {
+            // 初始化表格参数配置
+            Table.api.init({
+                extend: {
+                    index_url: 'vbot/robot/index' + location.search,
+                    add_url: 'vbot/robot/add',
+                    edit_url: 'vbot/robot/edit',
+                    del_url: 'vbot/robot/del',
+                    multi_url: 'vbot/robot/multi',
+                    table: 'vbot_robot',
+                }
+            });
+
+            var table = $("#table");
+
+            // 初始化表格
+            table.bootstrapTable({
+                url: $.fn.bootstrapTable.defaults.extend.index_url,
+                pk: 'id',
+                sortName: 'weigh',
+                columns: [
+                    [
+                        {checkbox: true},
+                        {field: 'id', title: __('Id')},
+                        {field: 'name', title: __('Name')},
+                        // {field: 'logo_image', title: __('Logo_image'), events: Table.api.events.image, formatter: Table.api.formatter.image},
+                        {field: 'webhook', title: __('Webhook')},
+                        {field: 'weigh', title: __('Weigh')},
+                        {
+                            field: 'openswitch',
+                            title: __('Openswitch'),
+                            searchList: {"0": __('Openswitch 0'), "1": __('Openswitch 1')},
+                            formatter: Table.api.formatter.toggle
+                        },
+                        {
+                            field: 'updatetime',
+                            title: __('Updatetime'),
+                            operate: 'RANGE',
+                            addclass: 'datetimerange',
+                            formatter: Table.api.formatter.datetime
+                        },
+                        {
+                            field: 'createtime',
+                            title: __('Createtime'),
+                            operate: 'RANGE',
+                            addclass: 'datetimerange',
+                            formatter: Table.api.formatter.datetime
+                        },
+                        {
+                            field: 'operate',
+                            title: __('Operate'),
+                            table: table,
+                            events: Table.api.events.operate,
+                            formatter: Table.api.formatter.operate
+                        }
+                    ]
+                ]
+            });
+
+            // 为表格绑定事件
+            Table.api.bindevent(table);
+        },
+        recyclebin: function () {
+            // 初始化表格参数配置
+            Table.api.init({
+                extend: {
+                    'dragsort_url': ''
+                }
+            });
+
+            var table = $("#table");
+
+            // 初始化表格
+            table.bootstrapTable({
+                url: 'vbot/robot/recyclebin' + location.search,
+                pk: 'id',
+                sortName: 'id',
+                columns: [
+                    [
+                        {checkbox: true},
+                        {field: 'id', title: __('Id')},
+                        {field: 'name', title: __('Name'), align: 'left'},
+                        {
+                            field: 'deletetime',
+                            title: __('Deletetime'),
+                            operate: 'RANGE',
+                            addclass: 'datetimerange',
+                            formatter: Table.api.formatter.datetime
+                        },
+                        {
+                            field: 'operate',
+                            width: '130px',
+                            title: __('Operate'),
+                            table: table,
+                            events: Table.api.events.operate,
+                            buttons: [
+                                {
+                                    name: 'Restore',
+                                    text: __('Restore'),
+                                    classname: 'btn btn-xs btn-info btn-ajax btn-restoreit',
+                                    icon: 'fa fa-rotate-left',
+                                    url: 'vbot/robot/restore',
+                                    refresh: true
+                                },
+                                {
+                                    name: 'Destroy',
+                                    text: __('Destroy'),
+                                    classname: 'btn btn-xs btn-danger btn-ajax btn-destroyit',
+                                    icon: 'fa fa-times',
+                                    url: 'vbot/robot/destroy',
+                                    refresh: true
+                                }
+                            ],
+                            formatter: Table.api.formatter.operate
+                        }
+                    ]
+                ]
+            });
+
+            // 为表格绑定事件
+            Table.api.bindevent(table);
+        },
+        add: function () {
+            Controller.api.bindevent();
+        },
+        edit: function () {
+            Controller.api.bindevent();
+        },
+        api: {
+            bindevent: function () {
+                Form.api.bindevent($("form[role=form]"));
+
+                $('#send_test_msg').on('click', function () {
+
+                    let hook_url = $('#c-webhook')[0].value;
+
+                    if (hook_url.length <= 0) {
+                        Fast.api.msg("请输入WebHook地址");
+                        return;
+                    }
+
+                    Fast.api.ajax({
+                            url: 'vbot/robot/msg_test',
+                            data: {
+                                webhook: hook_url
+                            }
+                        },
+                        function (data, ret) {
+                            Toastr.success(ret.msg, '', {});
+                            return false;
+                        },
+                        function (data, ret) {
+                            Toastr.error(ret.msg, '', {});
+                            return false;
+                        });
+
+                })
+            }
+        }
+    };
+    return Controller;
+});

+ 235 - 0
public/assets/js/backend/vbot/template.js

@@ -0,0 +1,235 @@
+define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) {
+
+    var Controller = {
+        index: function () {
+            // 初始化表格参数配置
+            Table.api.init({
+                extend: {
+                    index_url: 'vbot/template/index' + location.search,
+                    add_url: 'vbot/template/add',
+                    edit_url: 'vbot/template/edit',
+                    del_url: 'vbot/template/del',
+                    multi_url: 'vbot/template/multi',
+                    table: 'vbot_template',
+                }
+            });
+
+            var table = $("#table");
+
+            // 初始化表格
+            table.bootstrapTable({
+                url: $.fn.bootstrapTable.defaults.extend.index_url,
+                pk: 'id',
+                sortName: 'id',
+                columns: [
+                    [
+                        {checkbox: true},
+                        {field: 'id', title: __('Id')},
+                        {field: 'name', title: __('Name'), operate: 'LIKE'},
+                        {field: 'code', title: __('Code')},
+                        {
+                            field: 'robot_names',
+                            title: __('Robot_ids'),
+                            formatter: Controller.api.formatter.robot_names,
+                            operate: false
+                        },
+                        {
+                            field: 'typelist',
+                            title: __('Typelist'),
+                            searchList: {
+                                "text": __('Typelist text'),
+                                "markdown": __('Typelist markdown'),
+                                "image": __('Typelist image'),
+                                "news": __('Typelist news')
+                            },
+                            formatter: Table.api.formatter.normal
+                        },
+                        {
+                            field: 'content',
+                            title: __('Content'),
+                            placeholder: '模糊查找',
+                            operate: 'LIKE', 
+                            formatter: Controller.api.formatter.content_names
+                        },
+                        {field: 'title', title: __('Title'), placeholder: '模糊查找', operate: 'LIKE'},
+                        {field: 'at_mobiles', title: __('At_mobiles'), operate: 'LIKE', placeholder: '模糊查找'},
+                        {
+                            field: 'is_atall',
+                            title: __('Is_atall'),
+                            searchList: {"0": __('Is_atall 0'), "1": __('Is_atall 1')},
+                            formatter: Table.api.formatter.normal
+                        },
+                        {
+                            field: 'openswitch',
+                            title: __('Openswitch'),
+                            searchList: {"0": __('Openswitch 0'), "1": __('Openswitch 1')},
+                            formatter: Table.api.formatter.toggle
+                        },
+                        {
+                            field: 'updatetime',
+                            title: __('Updatetime'),
+                            operate: 'RANGE',
+                            addclass: 'datetimerange',
+                            formatter: Table.api.formatter.datetime
+                        },
+                        {
+                            field: 'createtime',
+                            title: __('Createtime'),
+                            operate: 'RANGE',
+                            addclass: 'datetimerange',
+                            formatter: Table.api.formatter.datetime
+                        },
+                        {
+                            field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate,
+                            buttons: [
+                                {
+                                    name: 'test_msg',
+                                    title: __('发送通知消息'),
+                                    classname: 'btn btn-xs btn-success btn-magic btn-ajax',
+                                    icon: 'fa fa-send-o',
+                                    url: 'vbot/template/msg_test',
+                                    confirm: '确认发送?',
+                                    success: function (data, ret) {
+                                        Fast.api.msg(ret.msg);
+                                    },
+                                    error: function (data, ret) {
+                                        Fast.api.msg(ret.msg);
+                                        return false;
+                                    }
+                                }
+                            ],
+                            formatter: Table.api.formatter.operate
+                        }
+                    ]
+                ]
+            });
+
+            // 为表格绑定事件
+            Table.api.bindevent(table);
+        },
+        recyclebin: function () {
+            // 初始化表格参数配置
+            Table.api.init({
+                extend: {
+                    'dragsort_url': ''
+                }
+            });
+
+            var table = $("#table");
+
+            // 初始化表格
+            table.bootstrapTable({
+                url: 'vbot/template/recyclebin' + location.search,
+                pk: 'id',
+                sortName: 'id',
+                columns: [
+                    [
+                        {checkbox: true},
+                        {field: 'id', title: __('Id')},
+                        {
+                            field: 'content',
+                            title: __('Content'),
+                            placeholder: '模糊查找',
+                            formatter: Controller.api.formatter.content_names
+                        },
+                        {
+                            field: 'deletetime',
+                            title: __('Deletetime'),
+                            operate: 'RANGE',
+                            addclass: 'datetimerange',
+                            formatter: Table.api.formatter.datetime
+                        },
+                        {
+                            field: 'operate',
+                            width: '130px',
+                            title: __('Operate'),
+                            table: table,
+                            events: Table.api.events.operate,
+                            buttons: [
+                                {
+                                    name: 'Restore',
+                                    text: __('Restore'),
+                                    classname: 'btn btn-xs btn-info btn-ajax btn-restoreit',
+                                    icon: 'fa fa-rotate-left',
+                                    url: 'vbot/template/restore',
+                                    refresh: true
+                                },
+                                {
+                                    name: 'Destroy',
+                                    text: __('Destroy'),
+                                    classname: 'btn btn-xs btn-danger btn-ajax btn-destroyit',
+                                    icon: 'fa fa-times',
+                                    url: 'vbot/template/destroy',
+                                    refresh: true
+                                }
+                            ],
+                            formatter: Table.api.formatter.operate
+                        }
+                    ]
+                ]
+            });
+
+            // 为表格绑定事件
+            Table.api.bindevent(table);
+        },
+        add: function () {
+            Controller.api.bindevent();
+        },
+        edit: function () {
+            Controller.api.bindevent();
+        },
+        api: {
+            bindevent: function (e) {
+                Form.api.bindevent($("form[role=form]"));
+
+                function need_show(value) {
+
+                    $('.msg_group').hide();
+
+                    if (value == 'text') {
+                        $('.msg_content,.msg_at').show(200);
+                    } else if (value == 'markdown') {
+                        $('.msg_title,.msg_content,.markdown_help').show(200);
+                    } else if (value == 'image') {
+                        $('.msg_picurl_image').show(200);
+                    } else if (value == 'news') {
+                        $('.msg_content,.msg_news').show(200);
+                    }
+                }
+
+                need_show($('#c-typelist')[0].value);
+
+                // 切换消息类型时,显示不同的输入框
+                $('#c-typelist').on('change', function () {
+                    need_show(this.value);
+                })
+
+                $('#show_grammar').on('click', function () {
+                    $('.markdown_grammar').show(200);
+                    $('.markdown_help').hide();
+                })
+            },
+            formatter: {
+                robot_names: function (value, row) {
+                    var value = value.split(',');
+
+                    var btn_html = '';
+
+                    for (let i in value) {
+                        btn_html += ' <a href="javascript:;" title="' + value[i] + '" class="btn btn-xs btn-info robot_names">' + value[i] + '</a> ';
+                    }
+
+                    return btn_html;
+                },
+                content_names: function (value, row) {
+                    if (value.length > 30) {
+                        return value.substring(0, 30) + '...';
+                    }
+
+                    return value;
+                }
+            }
+        }
+    };
+    return Controller;
+});

+ 190 - 0
public/assets/js/backend/vbot/variable.js

@@ -0,0 +1,190 @@
+define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) {
+
+    var Controller = {
+        index: function () {
+            // 初始化表格参数配置
+            Table.api.init({
+                extend: {
+                    index_url: 'vbot/variable/index' + location.search,
+                    add_url: 'vbot/variable/add',
+                    edit_url: 'vbot/variable/edit',
+                    del_url: 'vbot/variable/del',
+                    multi_url: 'vbot/variable/multi',
+                    view_variable_url:'vbot/variable/view_variable',
+                    table: 'vbot_variable',
+                }
+            });
+
+            var table = $("#table");
+
+            // 初始化表格
+            table.bootstrapTable({
+                url: $.fn.bootstrapTable.defaults.extend.index_url,
+                pk: 'id',
+                sortName: 'id',
+                columns: [
+                    [
+                        {checkbox: true},
+                        {field: 'id', title: __('Id')},
+                        {field: 'name', title: __('Name')},
+                        {
+                            field: 'value_source',
+                            title: __('Value_source'),
+                            searchList: {"0": __('Value_source 0'), "1": __('Value_source 1')},
+                            formatter: Table.api.formatter.normal
+                        },
+                        {field: 'sql', title: __('Sql'), formatter: Controller.api.formatter.sql_formatter},
+                        {field: 'namespace', title: __('Namespace')},
+                        {field: 'class', title: __('Class')},
+                        {field: 'function', title: __('Function')},
+                        {field: 'params', title: __('Params')},
+                        {
+                            field: 'updatetime',
+                            title: __('Updatetime'),
+                            operate: 'RANGE',
+                            addclass: 'datetimerange',
+                            formatter: Table.api.formatter.datetime
+                        },
+                        {
+                            field: 'createtime',
+                            title: __('Createtime'),
+                            operate: 'RANGE',
+                            addclass: 'datetimerange',
+                            formatter: Table.api.formatter.datetime
+                        },
+                        {
+                            field: 'operate',
+                            title: __('Operate'),
+                            table: table,
+                            events: Table.api.events.operate,
+                            formatter: Table.api.formatter.operate,
+                            buttons:[
+                                {
+                                    name:'view variable',
+                                    title:'查看变量值',
+                                    classname:'btn btn-xs btn-info btn-ajax',
+                                    icon:'fa fa-magic',
+                                    url:$.fn.bootstrapTable.defaults.extend.view_variable_url,
+                                    confirm: '确定要计算并查看变量的当前值吗?',
+                                    success: function (data, ret) {
+                                        Layer.alert(data.variable_value,{
+                                            'title':'计算结果'
+                                        });
+                                    },
+                                    error: function (data, ret) {
+                                        Layer.alert(ret.msg);
+                                        return false;
+                                    }
+                                }
+                            ]
+                        }
+                    ]
+                ]
+            });
+
+            // 为表格绑定事件
+            Table.api.bindevent(table);
+        },
+        recyclebin: function () {
+            // 初始化表格参数配置
+            Table.api.init({
+                extend: {
+                    'dragsort_url': ''
+                }
+            });
+
+            var table = $("#table");
+
+            // 初始化表格
+            table.bootstrapTable({
+                url: 'vbot/variable/recyclebin' + location.search,
+                pk: 'id',
+                sortName: 'id',
+                columns: [
+                    [
+                        {checkbox: true},
+                        {field: 'id', title: __('Id')},
+                        {field: 'name', title: __('Name'), align: 'left'},
+                        {
+                            field: 'deletetime',
+                            title: __('Deletetime'),
+                            operate: 'RANGE',
+                            addclass: 'datetimerange',
+                            formatter: Table.api.formatter.datetime
+                        },
+                        {
+                            field: 'operate',
+                            width: '130px',
+                            title: __('Operate'),
+                            table: table,
+                            events: Table.api.events.operate,
+                            buttons: [
+                                {
+                                    name: 'Restore',
+                                    text: __('Restore'),
+                                    classname: 'btn btn-xs btn-info btn-ajax btn-restoreit',
+                                    icon: 'fa fa-rotate-left',
+                                    url: 'vbot/variable/restore',
+                                    refresh: true
+                                },
+                                {
+                                    name: 'Destroy',
+                                    text: __('Destroy'),
+                                    classname: 'btn btn-xs btn-danger btn-ajax btn-destroyit',
+                                    icon: 'fa fa-times',
+                                    url: 'vbot/variable/destroy',
+                                    refresh: true
+                                }
+                            ],
+                            formatter: Table.api.formatter.operate
+                        }
+                    ]
+                ]
+            });
+
+            // 为表格绑定事件
+            Table.api.bindevent(table);
+        },
+        add: function () {
+            Controller.api.bindevent();
+        },
+        edit: function () {
+            Controller.api.bindevent();
+        },
+        api: {
+            bindevent: function () {
+                Form.api.bindevent($("form[role=form]"));
+
+                function need_show(value) {
+                    if (value == 1) {
+                        $('.variable_function').show(200);
+                        $('.variable_sql').hide();
+                    } else {
+                        $('.variable_sql').show(200);
+                        $('.variable_function').hide();
+                    }
+                }
+
+                need_show($('#c-value_source')[0].value);
+
+                $('#c-name').on('change', function () {
+                    $('#reference-name').html('可在通知模板中引用,引用名称:${' + this.value + '}');
+                })
+
+                $('#c-value_source').on('change', function (argument) {
+                    need_show(this.value);
+                })
+            },
+            formatter: {
+                sql_formatter: function (value, row) {
+                    if (value.length > 30) {
+                        return value.substring(0, 30) + '...';
+                    }
+
+                    return value;
+                }
+            }
+        }
+    };
+    return Controller;
+});

部分文件因为文件数量过多而无法显示