Browse Source

增加微信自动回复

邹景立 4 years ago
parent
commit
3ceefe05e2

+ 34 - 3
SQL01-数据表结构.sql

@@ -11,7 +11,7 @@
  Target Server Version : 80018
  File Encoding         : 65001
 
- Date: 08/04/2021 10:07:25
+ Date: 10/04/2021 17:27:30
 */
 
 SET NAMES utf8mb4;
@@ -807,7 +807,7 @@ CREATE TABLE `system_menu`  (
   `create_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
   PRIMARY KEY (`id`) USING BTREE,
   INDEX `idx_system_menu_status`(`status`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 98 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统-菜单' ROW_FORMAT = COMPACT;
+) ENGINE = InnoDB AUTO_INCREMENT = 99 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统-菜单' ROW_FORMAT = COMPACT;
 
 -- ----------------------------
 -- Table structure for system_oplog
@@ -822,7 +822,7 @@ CREATE TABLE `system_oplog`  (
   `username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '操作人用户名',
   `create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 202 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统-日志' ROW_FORMAT = COMPACT;
+) ENGINE = InnoDB AUTO_INCREMENT = 207 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统-日志' ROW_FORMAT = COMPACT;
 
 -- ----------------------------
 -- Table structure for system_queue
@@ -882,6 +882,37 @@ CREATE TABLE `system_user`  (
 ) ENGINE = InnoDB AUTO_INCREMENT = 10001 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统-用户' ROW_FORMAT = COMPACT;
 
 -- ----------------------------
+-- Table structure for wechat_auto
+-- ----------------------------
+DROP TABLE IF EXISTS `wechat_auto`;
+CREATE TABLE `wechat_auto`  (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '类型(text,image,news)',
+  `time` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '延迟时间',
+  `code` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '消息编号',
+  `appid` char(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '公众号APPID',
+  `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '文本内容',
+  `image_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '图片链接',
+  `voice_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '语音链接',
+  `music_title` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '音乐标题',
+  `music_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '音乐链接',
+  `music_image` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '缩略图片',
+  `music_desc` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '音乐描述',
+  `video_title` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '视频标题',
+  `video_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '视频URL',
+  `video_desc` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '视频描述',
+  `news_id` bigint(20) UNSIGNED NULL DEFAULT NULL COMMENT '图文ID',
+  `status` tinyint(1) UNSIGNED NULL DEFAULT 1 COMMENT '状态(0禁用,1启用)',
+  `create_by` bigint(20) UNSIGNED NULL DEFAULT 0 COMMENT '创建人',
+  `create_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
+  PRIMARY KEY (`id`) USING BTREE,
+  INDEX `idx_wechat_auto_type`(`type`) USING BTREE,
+  INDEX `idx_wechat_auto_keys`(`time`) USING BTREE,
+  INDEX `idx_wechat_auto_code`(`code`) USING BTREE,
+  INDEX `idx_wechat_auto_appid`(`appid`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '微信-回复' ROW_FORMAT = COMPACT;
+
+-- ----------------------------
 -- Table structure for wechat_fans
 -- ----------------------------
 DROP TABLE IF EXISTS `wechat_fans`;

+ 5 - 3
SQL02-数据初始化.sql

@@ -11,7 +11,7 @@
  Target Server Version : 80018
  File Encoding         : 65001
 
- Date: 07/04/2021 14:33:12
+ Date: 10/04/2021 17:27:54
 */
 
 SET NAMES utf8mb4;
@@ -3841,7 +3841,7 @@ CREATE TABLE `system_menu`  (
   `create_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
   PRIMARY KEY (`id`) USING BTREE,
   INDEX `idx_system_menu_status`(`status`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 98 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统-菜单' ROW_FORMAT = COMPACT;
+) ENGINE = InnoDB AUTO_INCREMENT = 99 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统-菜单' ROW_FORMAT = COMPACT;
 
 -- ----------------------------
 -- Records of system_menu
@@ -3889,6 +3889,8 @@ INSERT INTO `system_menu` VALUES (93, 90, '用户提现管理', 'layui-icon layu
 INSERT INTO `system_menu` VALUES (94, 68, '页面内容管理', 'layui-icon layui-icon-read', 'data/base.config/pagehome', 'data/base.config/pagehome', '', '_self', 20, 0, '2021-02-24 08:49:16');
 INSERT INTO `system_menu` VALUES (95, 68, '邀请二维码设置', 'layui-icon layui-icon-cols', 'data/base.config/cropper', 'data/base.config/cropper', '', '_self', 0, 1, '2021-03-01 09:53:59');
 INSERT INTO `system_menu` VALUES (97, 90, '用户返利管理', 'layui-icon layui-icon-transfer', 'data/user.rebate/index', 'data/user.rebate/index', '', '_self', 600, 1, '2021-03-12 10:06:49');
+INSERT INTO `system_menu` VALUES (98, 0, '首 页', '', 'data/total.portal/index', 'data/total.portal/index', '', '_self', 400, 1, '2021-04-10 13:43:19');
+INSERT INTO `system_menu` VALUES (99, 60, '关注自动回复', 'layui-icon layui-icon-release', 'wechat/auto/index', 'wechat/auto/index', '', '_self', 0, 1, '2021-04-10 15:56:54');
 
 -- ----------------------------
 -- Table structure for system_user
@@ -3921,6 +3923,6 @@ CREATE TABLE `system_user`  (
 -- ----------------------------
 -- Records of system_user
 -- ----------------------------
-INSERT INTO `system_user` VALUES (10000, 'admin', '21232f297a57a5a743894a0e4a801fc3', '系统管理员', 'https://xhtwxapp.cdn.xiaoding.shop/cf/23526f451784ff137f161b8fe18d5a.png', ',,', '', '', '', '127.0.0.1', '2021-04-07 11:27:31', 141, '', 1, 0, 0, '2015-11-13 15:14:22');
+INSERT INTO `system_user` VALUES (10000, 'admin', '21232f297a57a5a743894a0e4a801fc3', '系统管理员', 'https://xhtwxapp.cdn.xiaoding.shop/cf/23526f451784ff137f161b8fe18d5a.png', ',,', '', '', '', '127.0.0.1', '2021-04-10 13:42:51', 142, '', 1, 0, 0, '2015-11-13 15:14:22');
 
 SET FOREIGN_KEY_CHECKS = 1;

+ 16 - 10
app/data/controller/total/Portal.php

@@ -23,17 +23,23 @@ class Portal extends Controller
         $this->orderTotal = $this->app->db->name('ShopOrder')->cache(true, 60)->whereRaw('status >= 4')->count();
         $this->amountTotal = $this->app->db->name('ShopOrder')->cache(true, 60)->whereRaw('status >= 4')->sum('amount_total');
         // 近七天用户及交易趋势
-        $this->days = [];
-        for ($i = 15; $i >= 0; $i--) {
-            $date = date('Y-m-d', strtotime("-{$i}days"));
-            $this->days[] = [
-                date('m-d', strtotime("-{$i}days")),
-                $this->app->db->name('DataUser')->cache(true, 60)->whereLike('create_at', "{$date}%")->count(),
-                $this->app->db->name('ShopOrder')->cache(true, 60)->whereLike('create_at', "{$date}%")->whereRaw('status>=4')->count(),
-                $this->app->db->name('ShopOrder')->cache(true, 60)->whereLike('create_at', "{$date}%")->whereRaw('status>=4')->sum('amount_total'),
-                $this->app->db->name('DataUserRebate')->cache(true, 60)->whereLike('create_at', "{$date}%")->sum('amount'),
-            ];
+        $this->days = $this->app->cache->get('portals', []);
+        if (empty($this->days)) {
+            for ($i = 15; $i >= 0; $i--) {
+                $date = date('Y-m-d', strtotime("-{$i}days"));
+                $this->days[] = [
+                    '当天日期' => date('m-d', strtotime("-{$i}days")),
+                    '增加用户' => $this->app->db->name('DataUser')->cache(true, 60)->whereLike('create_at', "{$date}%")->count(),
+                    '订单数量' => $this->app->db->name('ShopOrder')->cache(true, 60)->whereLike('create_at', "{$date}%")->whereRaw('status>=4')->count(),
+                    '订单金额' => $this->app->db->name('ShopOrder')->cache(true, 60)->whereLike('create_at', "{$date}%")->whereRaw('status>=4')->sum('amount_total'),
+                    '返利金额' => $this->app->db->name('DataUserRebate')->cache(true, 60)->whereLike('create_at', "{$date}%")->sum('amount'),
+                    '充值余额' => $this->app->db->name('DataUserBalance')->cache(true, 60)->whereLike('create_at', "{$date}%")->whereRaw('amount>0 and deleted=0')->sum('amount'),
+                    '消费余额' => $this->app->db->name('DataUserBalance')->cache(true, 60)->whereLike('create_at', "{$date}%")->whereRaw('amount<0 and deleted=0')->sum('amount'),
+                ];
+            }
+            $this->app->cache->set('portals', $this->days, 60);
         }
+
         // 会员级别分布统计
         $levels = $this->app->db->name('BaseUserUpgrade')->where(['status' => 1])->order('number asc')->column('number code,name,0 count', 'number');
         foreach ($this->app->db->name('DataUser')->field('count(1) count,vip_code level')->group('vip_code')->cursor() as $vo) {

+ 159 - 94
app/data/view/total/portal/index.html

@@ -38,11 +38,8 @@
     </div>
 </div>
 
-<div class="think-box-shadow margin-top-15">
-    <div id="main1" style="width:100%;height:400px"></div>
-</div>
-
 <div class="layui-row layui-col-space15 margin-top-10">
+
     <div class="layui-col-xs12 layui-col-md6">
         <div class="think-box-shadow">
             <div id="main2" style="width:100%;height:350px"></div>
@@ -50,9 +47,29 @@
     </div>
     <div class="layui-col-xs12 layui-col-md6">
         <div class="think-box-shadow">
+            <div id="main4" style="width:100%;height:350px"></div>
+        </div>
+    </div>
+    <div class="layui-col-xs12 layui-col-md6">
+        <div class="think-box-shadow">
+            <div id="main5" style="width:100%;height:350px"></div>
+        </div>
+    </div>
+    <div class="layui-col-xs12 layui-col-md6">
+        <div class="think-box-shadow">
+            <div id="main6" style="width:100%;height:350px"></div>
+        </div>
+    </div>
+    <div class="layui-col-xs12 layui-col-md6">
+        <div class="think-box-shadow">
             <div id="main3" style="width:100%;height:350px"></div>
         </div>
     </div>
+    <div class="layui-col-xs12 layui-col-md6">
+        <div class="think-box-shadow">
+            <div id="main7" style="width:100%;height:350px"></div>
+        </div>
+    </div>
 </div>
 
 <label class="layui-hide">
@@ -64,71 +81,8 @@
 
     require(['echarts'], function (echarts) {
         var data1 = JSON.parse($('#jsondata1').html());
-        var list1 = data1.map(function (item) {
-            return item[0];
-        });
-        var chart1 = echarts.init(document.getElementById('main1'));
-        window.addEventListener("resize", function () {
-            chart1.resize()
-        });
-
-        chart1.setOption({
-            title: [
-                {left: '12%', text: '近十天用户数量趋势'},
-                {left: '45%', text: '近十天订单数量趋势'},
-                {left: '78%', text: '近十天交易金额趋势'}
-            ],
-            tooltip: {trigger: 'axis', show: true, axisPointer: {type: 'cross', label: {}}},
-            xAxis: [
-                {data: list1, gridIndex: 0},
-                {data: list1, gridIndex: 1},
-                {data: list1, gridIndex: 2}
-            ],
-            yAxis: [
-                {
-                    splitLine: {show: false}, gridIndex: 0, type: 'value', axisLabel: {
-                        formatter: '{value} 人'
-                    }
-                },
-                {
-                    splitLine: {show: false}, gridIndex: 1, type: 'value', axisLabel: {
-                        formatter: '{value} 单'
-                    }
-                },
-                {
-                    splitLine: {show: false}, gridIndex: 2, type: 'value', axisLabel: {
-                        formatter: '{value} 元'
-                    }
-                }
-            ],
-            grid: [
-                {left: '04%', right: '67%', top: '25%'},
-                {left: '37%', right: '34%', top: '25%'},
-                {left: '70%', right: '01%', top: '25%'}
-            ],
-            series: [
-                {
-                    type: 'line', showSymbol: true, xAxisIndex: 0, yAxisIndex: 0,
-                    label: {normal: {position: 'top', formatter: '{c} 人', show: true}},
-                    data: data1.map(function (item) {
-                        return item[1];
-                    }),
-                },
-                {
-                    type: 'line', showSymbol: true, xAxisIndex: 1, yAxisIndex: 1,
-                    label: {normal: {position: 'top', formatter: '{c} 单', show: true}},
-                    data: data1.map(function (item) {
-                        return item[2];
-                    }),
-                },
-                {
-                    type: 'line', showSymbol: true, xAxisIndex: 2, yAxisIndex: 2,
-                    label: {normal: {position: 'top', formatter: '{c} 元', show: true}},
-                    data: data1.map(function (item) {
-                        return item[3];
-                    }),
-                }
-            ]
+        var days = data1.map(function (item) {
+            return item['当天日期'];
         });
 
         var data2 = JSON.parse($('#jsondata2').html());
@@ -160,33 +114,144 @@
             ]
         });
 
-        var chart3 = echarts.init(document.getElementById('main3'));
-        window.addEventListener("resize", function () {
-            chart3.resize()
-        });
+        (function (charts) {
+            window.addEventListener("resize", function () {
+                charts.resize()
+            });
+            charts.setOption({
+                title: [{left: 'center', text: '近十天代理收益统计'}],
+                tooltip: {trigger: 'axis', show: true, axisPointer: {type: 'cross', label: {}}},
+                xAxis: [{data: days, gridIndex: 0}],
+                yAxis: [
+                    {
+                        splitLine: {show: true}, gridIndex: 0, type: 'value', axisLabel: {
+                            formatter: '{value} 元'
+                        }
+                    }
+                ],
+                grid: [{left: '10%', right: '3%', top: '25%'}],
+                series: [
+                    {
+                        type: 'line', showSymbol: true, xAxisIndex: 0, yAxisIndex: 0,
+                        label: {normal: {position: 'top', formatter: '{c} 元', show: true}},
+                        data: data1.map(function (item) {
+                            return item['返利金额'];
+                        }),
+                    }
+                ]
+            });
+        })(echarts.init(document.getElementById('main3')));
 
-        chart3.setOption({
-            title: [{left: 'center', text: '近十天代理收益统计'}],
-            tooltip: {trigger: 'axis', show: true, axisPointer: {type: 'cross', label: {}}},
-            xAxis: [{data: list1, gridIndex: 0}],
-            yAxis: [
-                {
-                    splitLine: {show: false}, gridIndex: 0, type: 'value', axisLabel: {
-                        formatter: '{value} 元'
+        (function (charts) {
+            window.addEventListener("resize", function () {
+                charts.resize()
+            });
+            charts.setOption({
+                title: [{left: 'center', text: '近十天用户增涨趋势'}],
+                tooltip: {trigger: 'axis', show: true, axisPointer: {type: 'cross', label: {}}},
+                xAxis: [{data: days, gridIndex: 0}],
+                yAxis: [
+                    {
+                        splitLine: {show: true}, gridIndex: 0, type: 'value', axisLabel: {
+                            formatter: '{value} 人'
+                        }
                     }
-                }
-            ],
-            grid: [{left: '10%', right: '3%', top: '25%'}],
-            series: [
-                {
-                    type: 'line', showSymbol: true, xAxisIndex: 0, yAxisIndex: 0,
-                    label: {normal: {position: 'top', formatter: '{c} 元', show: true}},
-                    data: data1.map(function (item) {
-                        return item[4];
-                    }),
-                }
-            ]
-        });
+                ],
+                grid: [{left: '10%', right: '3%', top: '25%'}],
+                series: [
+                    {
+                        type: 'line', showSymbol: true, xAxisIndex: 0, yAxisIndex: 0,
+                        label: {normal: {position: 'top', formatter: '{c} 人', show: true}},
+                        data: data1.map(function (item) {
+                            return item['增加用户'];
+                        }),
+                    }
+                ]
+            });
+        })(echarts.init(document.getElementById('main4')));
+
+        (function (charts) {
+            window.addEventListener("resize", function () {
+                charts.resize()
+            });
+            charts.setOption({
+                title: [{left: 'center', text: '近十天订单数量趋势'}],
+                tooltip: {trigger: 'axis', show: true, axisPointer: {type: 'cross', label: {}}},
+                xAxis: [{data: days, gridIndex: 0}],
+                yAxis: [
+                    {
+                        splitLine: {show: true}, gridIndex: 0, type: 'value', axisLabel: {
+                            formatter: '{value} 单'
+                        }
+                    }
+                ],
+                grid: [{left: '10%', right: '3%', top: '25%'}],
+                series: [
+                    {
+                        type: 'line', showSymbol: true, xAxisIndex: 0, yAxisIndex: 0,
+                        label: {normal: {position: 'top', formatter: '{c} 单', show: true}},
+                        data: data1.map(function (item) {
+                            return item['订单数量'];
+                        }),
+                    }
+                ]
+            });
+        })(echarts.init(document.getElementById('main5')));
+
+        (function (charts) {
+            window.addEventListener("resize", function () {
+                charts.resize()
+            });
+            charts.setOption({
+                title: [{left: 'center', text: '近十天交易金额趋势'}],
+                grid: [{left: '10%', right: '3%', top: '25%'}],
+                tooltip: {
+                    trigger: 'axis',
+                },
+                xAxis: [{data: days, gridIndex: 0}],
+                yAxis: [{type: 'value', splitLine: {show: true}, gridIndex: 0, axisLabel: {formatter: '{value} 元'}}],
+                series: [
+                    {
+                        type: 'line', showSymbol: true, xAxisIndex: 0, yAxisIndex: 0,
+                        label: {position: 'top', formatter: '{c} 元', show: true},
+                        data: data1.map(function (item) {
+                            return item['订单金额'];
+                        }),
+                    }
+                ]
+            });
+        })(echarts.init(document.getElementById('main6')));
+
+        (function (charts) {
+            window.addEventListener("resize", function () {
+                charts.resize()
+            });
+            charts.setOption({
+                title: [{text: '近十天账户余额趋势'}],
+                legend: {data: ['充值余额', '消费余额']},
+                tooltip: {trigger: 'axis'},
+                xAxis: [{data: days, gridIndex: 0}],
+                yAxis: [{type: 'value', splitLine: {show: true}, gridIndex: 0, axisLabel: {formatter: '{value} 元'}}],
+                series: [
+                    {
+                        name: '充值余额',
+                        type: 'line',
+                        label: {position: 'top', formatter: '{c} 元', show: true},
+                        data: data1.map(function (item) {
+                            return item['充值余额'];
+                        }),
+                    },
+                    {
+                        name: '消费余额',
+                        type: 'line',
+                        label: {formatter: '{c} 元', show: true},
+                        data: data1.map(function (item) {
+                            return item['消费余额'];
+                        }),
+                    },
+                ]
+            });
+        })(echarts.init(document.getElementById('main7')));
     });
 </script>
 

+ 6 - 0
app/index/controller/Index.php

@@ -15,6 +15,7 @@
 
 namespace app\index\controller;
 
+use app\wechat\service\AutoService;
 use think\admin\Controller;
 
 /**
@@ -27,4 +28,9 @@ class Index extends Controller
     {
         $this->redirect(sysuri('admin/login/index'));
     }
+
+    public function test()
+    {
+        dump(AutoService::instance()->parseTimeString('00小时00分01秒'));
+    }
 }

+ 135 - 0
app/wechat/command/Auto.php

@@ -0,0 +1,135 @@
+<?php
+
+namespace app\wechat\command;
+
+use app\wechat\service\MediaService;
+use app\wechat\service\WechatService;
+use think\admin\Command;
+use think\console\Input;
+use think\console\input\Argument;
+use think\console\Output;
+
+/**
+ * 向指定用户推送消息
+ * Class Auto
+ * @package app\wechat\command
+ */
+class Auto extends Command
+{
+    /** @var string */
+    private $openid;
+
+    /**
+     * 配置消息指令
+     */
+    protected function configure()
+    {
+        $this->setName('xadmin:fanauto');
+        $this->addArgument('openid', Argument::OPTIONAL, 'wechat user openid', '');
+        $this->addArgument('autocode', Argument::OPTIONAL, 'wechat auto message', '');
+        $this->setDescription('Wechat Users Push AutoMessage for ThinkAdmin');
+    }
+
+    /**
+     * @param Input $input
+     * @param Output $output
+     * @return void
+     * @throws \WeChat\Exceptions\InvalidResponseException
+     * @throws \WeChat\Exceptions\LocalCacheException
+     * @throws \think\admin\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    protected function execute(Input $input, Output $output)
+    {
+        $code = $input->getArgument('autocode');
+        $this->openid = $input->getArgument('openid');
+        if (empty($code)) $this->setQueueError("Message Code cannot be empty");
+        if (empty($this->openid)) $this->setQueueError("Wechat Openid cannot be empty");
+
+        // 查询微信消息对象
+        $map = ['code' => $code, 'status' => 1];
+        $data = $this->app->db->name('WechatAuto')->where($map)->find();
+        if (empty($data)) $this->setQueueError("Message Data Query failed");
+
+        // 发送微信客服消息
+        $this->_buildMessage($data);
+    }
+
+    /**
+     * 关键字处理
+     * @param array $data
+     * @return void
+     * @throws \WeChat\Exceptions\InvalidResponseException
+     * @throws \WeChat\Exceptions\LocalCacheException
+     * @throws \think\admin\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    private function _buildMessage(array $data)
+    {
+        $type = strtolower($data['type']);
+        $result = [0, '待发货的消息不符合规则'];
+        if ($type === 'text' && !empty($data['content'])) {
+            $result = $this->_sendMessage('text', ['content' => $data['content']]);
+        }
+        if ($type === 'voice' && !empty($data['voice_url'])) {
+            if ($mediaId = MediaService::instance()->upload($data['voice_url'], 'voice')) {
+                $result = $this->_sendMessage('voice', ['media_id' => $mediaId]);
+            }
+        }
+        if ($type === 'image' && !empty($data['image_url'])) {
+            if ($mediaId = MediaService::instance()->upload($data['image_url'], 'image')) {
+                $result = $this->_sendMessage('image', ['media_id' => $mediaId]);
+            }
+        }
+        if ($type === 'news') {
+            [$item, $news] = [MediaService::instance()->news($data['news_id']), []];
+            if (!empty($item['articles'])) {
+                foreach ($item['articles'] as $vo) array_push($news, [
+                    'url'   => url("@wechat/api.view/item/id/{$vo['id']}", [], false, true)->build(),
+                    'title' => $vo['title'], 'picurl' => $vo['local_url'], 'description' => $vo['digest'],
+                ]);
+                $result = $this->_sendMessage('news', ['articles' => $news]);
+            }
+        }
+        if ($type === 'music' && !empty($data['music_url']) && !empty($data['music_title']) && !empty($data['music_desc'])) {
+            $mediaId = $data['music_image'] ? MediaService::instance()->upload($data['music_image'], 'image') : '';
+            $result = $this->_sendMessage('music', [
+                'hqmusicurl'  => $data['music_url'], 'musicurl' => $data['music_url'],
+                'description' => $data['music_desc'], 'title' => $data['music_title'], 'thumb_media_id' => $mediaId,
+            ]);
+        }
+        if ($type === 'video' && !empty($data['video_url']) && !empty($data['video_desc']) && !empty($data['video_title'])) {
+            $video = ['title' => $data['video_title'], 'introduction' => $data['video_desc']];
+            if ($mediaId = MediaService::instance()->upload($data['video_url'], 'video', $video)) {
+                $result = $this->_sendMessage('video', ['media_id' => $mediaId, 'title' => $data['video_title'], 'description' => $data['video_desc']]);
+            }
+        }
+        if (empty($result[0])) {
+            $this->setQueueError($result[1]);
+        } else {
+            $this->setQueueSuccess($result[1]);
+        }
+    }
+
+    /**
+     * 推送客服消息
+     * @param string $type 消息类型
+     * @param array $data 消息对象
+     * @return array
+     */
+    private function _sendMessage(string $type, array $data): array
+    {
+        try {
+            WechatService::WeChatCustom()->send([
+                $type => $data, 'touser' => $this->openid, 'msgtype' => $type,
+            ]);
+            return [1, '微信消息推送成功'];
+        } catch (\Exception $exception) {
+            return [0, $exception->getMessage()];
+        }
+    }
+}

+ 133 - 0
app/wechat/controller/Auto.php

@@ -0,0 +1,133 @@
+<?php
+
+namespace app\wechat\controller;
+
+use think\admin\Controller;
+use think\admin\extend\CodeExtend;
+
+/**
+ * 关注自动回复
+ * Class Auto
+ * @package app\wechat\controller
+ */
+class Auto extends Controller
+{
+    /**
+     * 绑定数据表
+     * @var string
+     */
+    private $table = 'WechatAuto';
+
+    /**
+     * 消息类型
+     * @var array
+     */
+    public $types = [
+        'text'  => '文字', 'news' => '图文',
+        'image' => '图片', 'music' => '音乐',
+        'video' => '视频', 'voice' => '语音',
+    ];
+
+    /**
+     * 关注自动回复
+     * @auth true
+     * @menu true
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    public function index()
+    {
+        $this->title = '关注自动回复';
+        $query = $this->_query($this->table)->like('code,type');
+        $query->equal('status')->dateBetween('create_at')->order('time asc')->page();
+    }
+
+    /**
+     * 列表数据处理
+     * @param array $data
+     */
+    protected function _index_page_filter(array &$data)
+    {
+        foreach ($data as &$vo) {
+            $vo['type'] = $this->types[$vo['type']] ?? $vo['type'];
+        }
+    }
+
+    /**
+     * 添加自动回复
+     * @auth true
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    public function add()
+    {
+        $this->title = '添加自动回复';
+        $this->_form($this->table, 'form');
+    }
+
+    /**
+     * 编辑自动回复
+     * @auth true
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    public function edit()
+    {
+        $this->title = '编辑自动回复';
+        $this->_form($this->table, 'form');
+    }
+
+    /**
+     * 添加数据处理
+     * @param array $data
+     */
+    protected function _form_filter(array &$data)
+    {
+        if (empty($data['code'])) {
+            $data['code'] = CodeExtend::uniqidNumber(16, 'M');
+        }
+        if ($this->request->isGet()) {
+            $public = dirname($this->request->basefile(true));
+            $this->defaultImage = "{$public}/static/theme/img/image.png";
+        }
+    }
+
+    /**
+     * 表单结果处理
+     * @param boolean $result
+     */
+    protected function _form_result(bool $result)
+    {
+        if ($result !== false) {
+            $this->success('恭喜, 关键字保存成功!', 'javascript:history.back()');
+        } else {
+            $this->error('关键字保存失败, 请稍候再试!');
+        }
+    }
+
+    /**
+     * 修改规则状态
+     * @auth true
+     * @throws \think\db\exception\DbException
+     */
+    public function state()
+    {
+        $this->_save($this->table, $this->_vali([
+            'status.in:0,1'  => '状态值范围异常!',
+            'status.require' => '状态值不能为空!',
+        ]));
+    }
+
+    /**
+     * 删除自动回复
+     * @auth true
+     * @throws \think\db\exception\DbException
+     */
+    public function remove()
+    {
+        $this->_delete($this->table);
+    }
+}

+ 2 - 1
app/wechat/controller/api/Push.php

@@ -156,9 +156,10 @@ class Push extends Controller
     {
         switch (strtolower($this->receive['event'])) {
             case 'unsubscribe':
+                $this->app->event->trigger('WechatFansUnSubscribe', $this->openid);
                 return $this->_setUserInfo(false);
             case 'subscribe':
-                $this->_setUserInfo(true);
+                [$this->app->event->trigger('WechatFansSubscribe', $this->openid), $this->_setUserInfo(true)];
                 if (isset($this->receive['eventkey']) && is_string($this->receive['eventkey'])) {
                     if (($key = preg_replace('/^qrscene_/i', '', $this->receive['eventkey']))) {
                         return $this->_keys("WechatKeys#keys#{$key}", false, true);

+ 45 - 0
app/wechat/service/AutoService.php

@@ -0,0 +1,45 @@
+<?php
+
+namespace app\wechat\service;
+
+use think\admin\Service;
+use think\admin\service\QueueService;
+
+/**
+ * 关注自动回复服务
+ * Class AutoService
+ * @package app\wechat\service
+ */
+class AutoService extends Service
+{
+    /**
+     * 注册微信用户推送任务
+     * @param string $openid
+     * @throws \think\admin\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    public function register(string $openid)
+    {
+        foreach ($this->app->db->name('WechatAuto')->where(['status' => 1])->order('time asc')->cursor() as $vo) {
+            $time = $this->parseTimeString($vo['time']);
+            $name = "延迟向 {$openid} 推送 {$vo['code']} 客服消息";
+            QueueService::instance()->register($name, "xadmin:fanauto {$openid} {$vo['code']}", $time);
+        }
+    }
+
+    /**
+     * 解析配置时间格式
+     * @param string $time
+     * @return int
+     */
+    private function parseTimeString(string $time): int
+    {
+        if (preg_match('|^.*?(\d{2}).*?(\d{2}).*?(\d{2}).*?$|', $time, $vars)) {
+            return intval($vars[1]) * 3600 * intval($vars[2]) * 60 + intval($vars[3]);
+        } else {
+            return 0;
+        }
+    }
+}

+ 8 - 1
app/wechat/sys.php

@@ -13,10 +13,17 @@
 // | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
 // +----------------------------------------------------------------------
 
+use app\wechat\command\Auto;
+use app\wechat\command\Fans;
+use app\wechat\service\AutoService;
 use think\Console;
 
 if (app()->request->isCli()) {
     Console::starting(function (Console $console) {
-        $console->addCommand('app\wechat\command\Fans');
+        $console->addCommands([Fans::class, Auto::class]);
+    });
+} else {
+    app()->event->listen('WechatFansSubscribe', function ($openid) {
+        AutoService::instance()->register($openid);
     });
 }

+ 266 - 0
app/wechat/view/auto/form.html

@@ -0,0 +1,266 @@
+{extend name="../../admin/view/main"}
+
+{block name="style"}
+<style>
+    .keys-container .layui-card {
+        width: 580px;
+        height: 578px;
+        position: absolute;
+        border: 1px solid #ccc
+    }
+
+    .keys-container .layui-card .layui-card-body {
+        height: 495px;
+        padding-right: 50px
+    }
+
+    .keys-container .layui-card .layui-card-body [data-tips-image] {
+        width: 112px;
+        height: auto
+    }
+
+    .keys-container .layui-card .layui-card-body .layui-form-label {
+        width: 60px;
+        color: #6c6c6c;
+        font-weight: 700;
+    }
+
+    .keys-container .layui-card .layui-card-body .layui-form-label + .layui-input-block {
+        margin-left: 100px;
+    }
+</style>
+{/block}
+
+{block name="content"}
+<div class="nowrap think-box-shadow" style="width:910px">
+    <div class='mobile-preview inline-block'>
+        <div class='mobile-header'>公众号</div>
+        <div class='mobile-body' data-iframe-box></div>
+    </div>
+    <div class="keys-container inline-block absolute margin-left-10 margin-right-15">
+        <form class="layui-form" onsubmit="return false" autocomplete="off" data-auto="true" action="{:request()->url()}" method="post">
+            <div class="layui-card relative">
+                <div class="layui-card-header layui-bg-gray text-center">编辑关键字</div>
+                <div class="layui-card-body">
+                    <div class="layui-form-item margin-top-10">
+                        <label class="layui-form-label">延迟时间</label>
+                        <div class="layui-input-block">
+                            <input required readonly placeholder='请输入延迟时间' maxlength='20' id="timeInput" name='time' class="layui-input" value='{$vo.time|default="00小时00分00秒"}'>
+                            <script>layui.laydate.render({elem: '#timeInput', type: "time", format: 'HH小时mm分ss秒', btns: ['confirm']});</script>
+                        </div>
+                    </div>
+                    <div class="layui-form-item">
+                        <label class="layui-form-label label-required">规则状态</label>
+                        <div class="layui-input-block">
+                            {foreach ['1'=>'启用','0'=>'禁用'] as $k=>$v}
+                            <label class="think-radio">
+                                <!--{if (!isset($vo.status) and $k eq '1') or (isset($vo.status) and $vo.status eq $k)}-->
+                                <input type="radio" checked name="status" value="{$k}"> {$v}
+                                <!--{else}-->
+                                <input type="radio" name="status" value="{$k}"> {$v}
+                                <!--{/if}-->
+                            </label>
+                            {/foreach}
+                        </div>
+                    </div>
+
+                    <div class="layui-form-item">
+                        <label class="layui-form-label label-required">消息类型</label>
+                        <div class="layui-input-block">
+                            {foreach $types as $k=>$v}
+                            <label class="think-radio">
+                                <!--{if (!isset($vo.type) and $k eq 'text') or (isset($vo.type) and$vo.type eq $k)}-->
+                                <input name="type" checked type="radio" value="{$k}"> {$v}
+                                <!--{else}-->
+                                <input name="type" type="radio" value="{$k}"> {$v}
+                                <!--{/if}-->
+                            </label>
+                            {/foreach}
+                        </div>
+                    </div>
+
+                    <div class="layui-form-item" data-keys-type='text'>
+                        <label class="layui-form-label">回复文字</label>
+                        <div class="layui-input-block">
+                            <textarea name="content" required placeholder="请输入回复文字" maxlength="10000" class="layui-textarea">{$vo.content|raw|default='说点什么吧'}</textarea>
+                        </div>
+                    </div>
+
+                    <div class="layui-form-item" data-keys-type='news'>
+                        <label class="layui-form-label label-required">选取图文</label>
+                        <div class="layui-input-block">
+                            <input type="hidden" name="news_id" value="{$vo.news_id|default=0}">
+                            <a class="layui-btn layui-btn-sm layui-btn-primary" data-title="选择图文" data-iframe="{:url('wechat/news/select')}?field={:encode('news_id')}">选择图文</a>
+                        </div>
+                    </div>
+
+                    <div class="layui-form-item" data-keys-type='image'>
+                        <label class="layui-form-label label-required">图片地址</label>
+                        <div class="layui-input-block">
+                            <input class="layui-input padding-right-30" onchange="$(this).nextAll('img').attr('src', this.value)" value="{$vo.image_url|default=$defaultImage}" name="image_url" required placeholder="请上传图片或输入图片URL地址  ">
+                            <a data-file="btn" data-type="bmp,png,jpeg,jpg,gif" data-field="image_url" class="input-right-icon"><i class="layui-icon layui-icon-upload"></i></a>
+                            <p class="help-block">文件最大2Mb,支持bmp/png/jpeg/jpg/gif格式</p>
+                            <img data-tips-image src='{$vo.image_url|default=$defaultImage}' alt="img">
+                        </div>
+                    </div>
+
+                    <div class="layui-form-item" data-keys-type='voice'>
+                        <label class="layui-form-label">上传语音</label>
+                        <div class="layui-input-block">
+                            <input class='layui-input padding-right-30' value="{$vo.voice_url|default=''}" name="voice_url" required title="请上传语音文件或输入语音URL地址  ">
+                            <a data-file="btn" data-type="mp3,wma,wav,amr" data-field="voice_url" class="input-right-icon"><i class="layui-icon layui-icon-upload"></i></a>
+                            <p class="help-block">文件最大2Mb,播放长度不超过60s,mp3/wma/wav/amr格式</p>
+                        </div>
+                    </div>
+
+                    <div class="layui-form-item" data-keys-type='music'>
+                        <label class="layui-form-label">音乐标题</label>
+                        <div class="layui-input-block">
+                            <input class='layui-input' value="{$vo.music_title|default='音乐标题'}" name="music_title" required title="请输入音乐标题">
+                        </div>
+                    </div>
+
+                    <div class="layui-form-item" data-keys-type='music'>
+                        <label class="layui-form-label label-required">上传音乐</label>
+                        <div class="layui-input-block">
+                            <input class='layui-input padding-right-30' value="{$vo.music_url|default=''}" name="music_url" required title="请上传音乐文件或输入音乐URL地址  ">
+                            <a data-file="btn" data-type="mp3,wma,wav,amr" data-field="music_url" class="input-right-icon"><i class="layui-icon layui-icon-upload"></i></a>
+                        </div>
+                    </div>
+
+                    <div class="layui-form-item" data-keys-type='music'>
+                        <label class="layui-form-label">音乐描述</label>
+                        <div class="layui-input-block">
+                            <input name="music_desc" class="layui-input" value="{$vo.music_desc|default='音乐描述'|raw}">
+                        </div>
+                    </div>
+
+                    <div class="layui-form-item" data-keys-type='music'>
+                        <label class="layui-form-label">音乐图片</label>
+                        <div class="layui-input-block">
+                            <input class="layui-input padding-right-30" value="{$vo.music_image|default=$defaultImage}" name="music_image" required title="请上传音乐图片或输入音乐图片URL地址  ">
+                            <a data-file="btn" data-type="jpg,png" data-field="music_image" class="input-right-icon"><i class="layui-icon layui-icon-upload"></i></a>
+                            <p class="help-block">文件最大64KB,只支持JPG格式</p>
+                        </div>
+                    </div>
+
+                    <div class="layui-form-item" data-keys-type='video'>
+                        <label class="layui-form-label">视频标题</label>
+                        <div class="layui-input-block">
+                            <input class='layui-input' value="{$vo.video_title|default='视频标题'}" name="video_title" required placeholder="请输入视频标题">
+                        </div>
+                    </div>
+
+                    <div class="layui-form-item" data-keys-type='video'>
+                        <label class="layui-form-label">上传视频</label>
+                        <div class="layui-input-block">
+                            <input class='layui-input padding-right-30' value="{$vo.video_url|default=''}" name="video_url" required title="请上传视频或输入音乐视频URL地址  ">
+                            <a data-file="btn" data-type="mp4" data-field="video_url" class="input-right-icon"><i class="layui-icon layui-icon-upload"></i></a>
+                            <p class="help-block">文件最大10MB,只支持MP4格式</p>
+                        </div>
+                    </div>
+
+                    <div class="layui-form-item" data-keys-type='video'>
+                        <label class="layui-form-label">视频描述</label>
+                        <div class="layui-input-block">
+                            <input value="{$vo.video_desc|default='视频描述'}" name="video_desc" maxlength="50" class="layui-input">
+                        </div>
+                    </div>
+
+                    <div class="text-center padding-bottom-10 absolute full-width" style="bottom:0;margin-left:-15px">
+
+                        <div class="hr-line-dashed margin-top-10 margin-bottom-10"></div>
+                        {if isset($vo['id'])}<input type='hidden' value='{$vo.id}' name='id'>{/if}
+                        {if isset($vo['code'])}<input type='hidden' value='{$vo.code}' name='code'>{/if}
+
+                        <button class="layui-btn menu-submit">保存数据</button>
+                        <button data-history-back class="layui-btn layui-btn-danger" type='button'>取消编辑</button>
+                    </div>
+                </div>
+            </div>
+        </form>
+    </div>
+</div>
+{/block}
+
+{block name="script"}
+<script>
+    (function ($body) {
+
+        /*! 刷新预览显示 */
+        function showReview(params, location) {
+            if (params['type'] === 'news') {
+                location = '{:url("@wechat/api.view/news")}?id=_id_'.replace('_id_', params.content);
+            } else {
+                location = '{:url("@wechat/api.view/_type_")}?'.replace('_type_', params.type) + $.param(params || {});
+            }
+            var iframe = '<iframe id="phone-preview" frameborder="0" marginheight="0" marginwidth="0"></iframe>';
+            $('[data-iframe-box]').empty().append($(iframe).attr('src', location));
+        }
+
+        $body.off('change', '[name="news_id"]').on('change', '[name="news_id"]', function () {
+            /*! 图文显示预览 */
+            showReview({type: 'news', content: this.value});
+        }).off('change', '[name="content"]').on('change', '[name="content"]', function () {
+            /*! 文字显示预览 */
+            showReview({type: 'text', content: this.value});
+        }).off('change', '[name="image_url"]').on('change', '[name="image_url"]', function () {
+            /*! 图片显示预览 */
+            showReview({type: 'image', content: this.value});
+        }).off('change', '[name="voice_url"]').on('change', '[name="voice_url"]', function () {
+            /*! 语音显示预览 */
+            showReview({type: 'voice', content: this.value});
+        });
+
+        /*! 音乐显示预览 */
+        var musicSelector = '[name="music_url"],[name="music_title"],[name="music_desc"],[name="music_image"]';
+        $body.off('change', musicSelector).on('change', musicSelector, function () {
+            var params = {type: 'music'}, $parent = $(this).parents('form');
+            params.url = $parent.find('[name="music_url"]').val();
+            params.desc = $parent.find('[name="music_desc"]').val();
+            params.title = $parent.find('[name="music_title"]').val();
+            params.image = $parent.find('[name="music_image"]').val();
+            showReview(params);
+        });
+
+        /*! 视频显示预览 */
+        var videoSelector = '[name="video_title"],[name="video_url"],[name="video_desc"]';
+        $body.off('change', videoSelector).on('change', videoSelector, function () {
+            var params = {type: 'video'}, $parent = $(this).parents('form');
+            params.url = $parent.find('[name="video_url"]').val();
+            params.desc = $parent.find('[name="video_desc"]').val();
+            params.title = $parent.find('[name="video_title"]').val();
+            showReview(params);
+        });
+
+        /*! 默认类型事件 */
+        $body.off('click', 'input[name=type]').on('click', 'input[name=type]', function () {
+            var value = $(this).val(), $form = $(this).parents('form');
+            if (value === 'customservice') value = 'text';
+            var $current = $form.find('[data-keys-type="' + value + '"]').removeClass('layui-hide');
+            $form.find('[data-keys-type]').not($current).addClass('layui-hide');
+            switch (value) {
+                case 'news':
+                    return $('[name="news_id"]').trigger('change');
+                case 'text':
+                case 'customservice':
+                    return $('[name="content"]').trigger('change');
+                case 'image':
+                    return $('[name="image_url"]').trigger('change');
+                case 'video':
+                    return $('[name="video_url"]').trigger('change');
+                case 'music':
+                    return $('[name="music_url"]').trigger('change');
+                case 'voice':
+                    return $('[name="voice_url"]').trigger('change');
+            }
+        });
+
+        /*! 默认事件触发 */
+        $('input[name=type]:checked').map(function () {
+            $(this).trigger('click');
+        });
+
+    })($('body'));
+</script>
+{/block}

+ 82 - 0
app/wechat/view/auto/index.html

@@ -0,0 +1,82 @@
+{extend name="../../admin/view/main"}
+
+{block name="button"}
+<!--{if auth("add")}-->
+<button data-open="{:url('add')}" class='layui-btn layui-btn-sm layui-btn-primary'>添加规则</button>
+<!--{/if}-->
+
+<!--{if auth("remove")}-->
+<button data-action='{:url("remove")}' data-rule="id#{key}" data-csrf="{:systoken('remove')}" data-confirm="确定要删除这些规则吗?" class='layui-btn layui-btn-sm layui-btn-primary'>删除规则</button>
+<!--{/if}-->
+{/block}
+
+{block name='content'}
+<div class="think-box-shadow">
+    {include file='auto/index_search'}
+    <table class="layui-table margin-top-10" lay-skin="line">
+        {notempty name='list'}
+        <thead>
+        <tr>
+            <th class='list-table-check-td think-checkbox'>
+                <label><input data-auto-none data-check-target='.list-check-box' type='checkbox'></label>
+            </th>
+            <th class="text-left nowrap">消息编号</th>
+            <th class="text-left nowrap">延迟时间</th>
+            <th class="text-left nowrap">消息类型</th>
+            <th class="text-left nowrap">预览</th>
+            <th class="text-left nowrap">添加时间</th>
+            <th class="text-left nowrap">状态</th>
+            <th></th>
+        </tr>
+        </thead>
+        {/notempty}
+        <tbody>
+        {foreach $list as $key=>$vo}
+        <tr>
+            <td class='list-table-check-td think-checkbox'>
+                <label><input class="list-check-box" value='{$vo.id}' type='checkbox'></label>
+            </td>
+            <td class="text-left nowrap">{$vo.code}</td>
+            <td class="text-left nowrap">{$vo.time}</td>
+            <td class="text-left nowrap">{$vo.type}</td>
+            <td class="text-left nowrap notselect">
+                {if $vo.type eq '音乐'}
+                <a data-phone-view='{:url("@wechat/api.view/music")}?title={$vo.music_title|urlencode}&desc={$vo.music_desc|urlencode}'>预览 <i class="fa fa-eye"></i></a>
+                {elseif in_array($vo.type,['文字','转客服'])}
+                <a data-phone-view='{:url("@wechat/api.view/text")}?content={$vo.content|urlencode}'>预览 <i class="fa fa-eye"></i></a>
+                {elseif $vo.type eq '图片'}
+                <a data-phone-view='{:url("@wechat/api.view/image")}?content={$vo.image_url|urlencode}'>预览 <i class="fa fa-eye"></i></a>
+                {elseif $vo.type eq '图文'}
+                <a data-phone-view='{:url("@wechat/api.view/news")}?id={$vo.news_id}'>预览 <i class="fa fa-eye"></i></a>
+                {elseif $vo.type eq '视频'}
+                <a data-phone-view='{:url("@wechat/api.view/video")}?title={$vo.video_title|urlencode}&desc={$vo.video_desc|urlencode}&url={$vo.video_url|urlencode}'>预览 <i class="fa fa-eye"></i></a>
+                {elseif $vo.type eq '语音'}
+                <a data-phone-view='{:url("@wechat/api.view/voice")}?content={$vo.voice_url|urlencode}'>预览 <i class="fa fa-eye"></i></a>
+                {else} {$vo.content} {/if}
+            </td>
+            <td class="text-left nowrap">{$vo.create_at|format_datetime}</td>
+            <td class='text-left nowrap'>{if $vo.status eq 0}<span class="color-red">已禁用</span>{elseif $vo.status eq 1}<span class="color-green">已激活</span>{/if}</td>
+            <td class='text-left nowrap'>
+
+                <!--{if auth("edit")}-->
+                <a class="layui-btn layui-btn-sm" data-open='{:url("edit")}?id={$vo.id}'>编 辑</a>
+                <!--{/if}-->
+
+                <!--{if auth("state") and $vo.status eq 1}-->
+                <a class="layui-btn layui-btn-sm layui-btn-warm" data-action="{:url('state')}" data-value="id#{$vo.id};status#0" data-csrf="{:systoken('state')}">禁 用</a>
+                <!--{elseif auth("state") and $vo.status eq 0}-->
+                <a class="layui-btn layui-btn-sm layui-btn-warm" data-action="{:url('state')}" data-value="id#{$vo.id};status#1" data-csrf="{:systoken('state')}">激 活</a>
+                <!--{/if}-->
+
+                <!--{if auth("remove")}-->
+                <a class="layui-btn layui-btn-sm layui-btn-danger" data-confirm="确定要删除该规则吗?" data-action="{:url('remove')}" data-value="id#{$vo.id}" data-csrf="{:systoken('remove')}">删 除</a>
+                <!--{/if}-->
+
+            </td>
+        </tr>
+        {/foreach}
+        </tbody>
+    </table>
+    {empty name='list'}<span class="notdata">没有记录哦</span>{else}{$pagehtml|raw|default=''}{/empty}
+</div>
+{/block}

+ 54 - 0
app/wechat/view/auto/index_search.html

@@ -0,0 +1,54 @@
+<fieldset>
+    <legend>条件搜索</legend>
+    <form class="layui-form layui-form-pane form-search" action="{:request()->url()}" onsubmit="return false" method="get" autocomplete="off">
+
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">消息编号</label>
+            <div class="layui-input-inline">
+                <input name="code" value="{:input('code','')}" placeholder="请输入消息编号" class="layui-input">
+            </div>
+        </div>
+
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">规则类型</label>
+            <div class="layui-input-inline">
+                <select class="layui-select" name="type">
+                    <option value="">-- 全部 --</option>
+                    {foreach $types as $k => $v}{if $k.'' eq input('type')}
+                    <option selected value="{$k}">{$v}</option>
+                    {else}
+                    <option value="{$k}">{$v}</option>
+                    {/if}{/foreach}
+                </select>
+            </div>
+        </div>
+
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">使用状态</label>
+            <div class="layui-input-inline">
+                <select class="layui-select" name="status">
+                    <option value="">-- 全部 --</option>
+                    {foreach ['显示已禁止的规则','显示已激活的规则'] as $k=>$v}
+                    {if $k.'' eq input('status')}
+                    <option selected value="{$k}">{$v}</option>
+                    {else}
+                    <option value="{$k}">{$v}</option>
+                    {/if}{/foreach}
+                </select>
+            </div>
+        </div>
+
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">添加时间</label>
+            <div class="layui-input-inline">
+                <input data-date-range name="create_at" value="{:input('create_at','')}" placeholder="请选择添加时间" class="layui-input">
+            </div>
+        </div>
+
+        <div class="layui-form-item layui-inline">
+            <button class="layui-btn layui-btn-primary"><i class="layui-icon">&#xe615;</i> 搜 索</button>
+        </div>
+    </form>
+    <script>window.form.render()</script>
+</fieldset>
+

File diff suppressed because it is too large
+ 3 - 0
public/static/plugs/echarts/echarts.min.js


Some files were not shown because too many files changed in this diff