diff --git a/application/wechat/controller/News.php b/application/wechat/controller/News.php
index 047e2086b..4138e05f0 100644
--- a/application/wechat/controller/News.php
+++ b/application/wechat/controller/News.php
@@ -14,10 +14,14 @@
 
 namespace app\wechat\controller;
 
+use Exception;
+use think\Db;
+use think\Log;
+use think\response\View;
 use controller\BasicAdmin;
 use service\DataService;
+use service\FileService;
 use service\WechatService;
-use think\Db;
 
 /**
  * 微信图文管理
@@ -55,7 +59,7 @@ class News extends BasicAdmin {
 
     /**
      * 添加图文
-     * @return \think\response\View
+     * @return View
      */
     public function add() {
         if ($this->request->isGet()) {
@@ -75,7 +79,7 @@ class News extends BasicAdmin {
 
     /**
      * 编辑图文
-     * @return \think\response\View
+     * @return View
      */
     public function edit() {
         $id = $this->request->get('id', '');
@@ -118,13 +122,6 @@ class News extends BasicAdmin {
         return join(',', $ids);
     }
 
-    /**
-     * 图文推送
-     */
-    public function push() {
-        
-    }
-
     /**
      * 删除图文
      */
@@ -140,7 +137,7 @@ class News extends BasicAdmin {
                 Db::name('WechatNews')->where('id', $id)->delete();
                 Db::commit();
                 $isSuccess = true;
-            } catch (\Exception $e) {
+            } catch (Exception $e) {
                 Db::rollback();
             }
             (isset($isSuccess) && $isSuccess) && $this->success('图文删除成功!');
@@ -156,4 +153,115 @@ class News extends BasicAdmin {
         return '开发中';
     }
 
+    /**
+     * 推荐图文
+     * @return array|void
+     */
+    public function push() {
+        # 获取将要推送的粉丝列表
+        $params = $this->request->post('group', '');
+        $ids = explode(',', $params);
+        $fansDb = Db::name('WechatFans');
+        $news_id = $this->request->get('id', '');
+        switch (strtolower($this->request->get('action', ''))) {
+            case 'getgroup':
+                if (!in_array('0', $ids)) {
+                    $fansDb->where("concat(',',tagid_list,',') REGEXP '," . join(',|,', $ids) . ",'");
+                }
+                return ['code' => "SUCCESS", 'data' => $fansDb->where('subscribe', '1')->column('openid,nickname')];
+            case 'getuser':
+                if (!in_array('0', $ids)) {
+                    $fansDb->where("concat(',',tagid_list,',') REGEXP '," . join(',|,', $ids) . ",'");
+                }
+                return ['code' => "SUCCESS", 'data' => $fansDb->where('subscribe', '1')->column('openid,nickname')];
+            default :
+                // 显示及图文
+                $newsinfo = WechatService::getNewsById($news_id);
+                // Get 请求,显示选择器界面
+                if ($this->request->isGet()) {
+                    $fans_tags = Db::name('WechatFansTags')->select();
+                    array_unshift($fans_tags, [
+                        'id'    => 0,
+                        'name'  => '全部',
+                        'count' => Db::name('WechatFans')->where('subscribe', '1')->count(),
+                    ]);
+                    return view('push', ['vo' => $newsinfo, 'fans_tags' => $fans_tags]);
+                }
+                // Post 请求,执行图文推送操作
+                $post = $this->request->post();
+                empty($post['fans_tags']) && $this->error('还没有选择要粉丝对象!');
+                // 图文上传操作
+                !$this->_uploadWechatNews($newsinfo) && $this->error('图文上传失败,请稍候再试!');
+                // 数据拼装
+                $data = [];
+                if (in_array('0', $post['fans_tags'])) {
+                    $data['msgtype'] = 'mpnews';
+                    $data['filter'] = ['is_to_all' => true];
+                    $data['mpnews'] = ['media_id' => $newsinfo['media_id']];
+                } else {
+                    $data['msgtype'] = 'mpnews';
+                    $data['filter'] = ['is_to_all' => false, 'tag_id' => join(',', $post['fans_tags'])];
+                    $data['mpnews'] = ['media_id' => $newsinfo['media_id']];
+                }
+                $wechat = &load_wechat('Receive');
+                (FALSE !== $wechat->sendGroupMassMessage($data)) && $this->success('微信图文推送成功!');
+                $this->error("微信图文推送失败,{$wechat->errMsg} [{$wechat->errCode}]");
+        }
+    }
+
+    /**
+     * 上传永久图文
+     * @param type $newsinfo
+     * @return boolean
+     */
+    private function _uploadWechatNews(&$newsinfo) {
+        $self = $this;
+        foreach ($newsinfo['articles'] as &$article) {
+            $article['thumb_media_id'] = WechatService::uploadForeverMedia($article['local_url']);
+            $article['content'] = preg_replace_callback("/<img(.*?)src=['\"](.*?)['\"](.*?)\/?>/i", function ($matches) use ($self) {
+                $src = $self->_filterWechatImage($matches[2]);
+                return "<img{$matches[1]}src=\"{$src}\"{$matches[3]}/>";
+            }, htmlspecialchars_decode($article['content']));
+        }
+        $wechat = & load_wechat('media');
+        // 如果已经上传过,先删除之前的历史记录
+        !empty($newsinfo['media_id']) && $wechat->delForeverMedia($newsinfo['media_id']);
+        // 上传图文到微信服务器
+        $result = $wechat->uploadForeverArticles(['articles' => $newsinfo['articles']]);
+        if (isset($result['media_id'])) {
+            $newsinfo['media_id'] = $result['media_id'];
+            return Db::name('WechatNews')->where('id', $newsinfo['id'])->update(['media_id' => $result['media_id']]);
+        }
+        Log::error("上传永久图文失败, {$wechat->errMsg}[{$wechat->errCode}]");
+        return false;
+    }
+
+    /**
+     * 文章内容图片处理
+     * @param string $local_url
+     * @return string|null
+     */
+    private function _filterWechatImage($local_url = '') {
+        # 检测图片是否已经上传过了
+        $img = Db::name('WechatNewsImage')->where('local_url', $local_url)->find();
+        if (!empty($img) && isset($img['media_url'])) {
+            return $img['media_url'];
+        }
+        # 下载临时文件到本地
+        $filename = 'wechat/image/' . join('/', str_split(md5($local_url), 16)) . '.' . strtolower(pathinfo($local_url, 4));
+        $result = FileService::local($filename, file_get_contents($local_url));
+        if ($result && isset($result['file'])) {
+            # 上传图片到微信服务器
+            $wechat = &load_wechat('media');
+            $mediainfo = $wechat->uploadImg(['media' => "@{$result['file']}"]);
+            if (!empty($mediainfo)) {
+                $data = ['local_url' => $local_url, 'media_url' => $mediainfo['url'], 'md5' => md5($local_url)];
+                Db::name('WechatNewsImage')->insert($data);
+                return $mediainfo['url'];
+            }
+        }
+        Log::error("图片上传失败,请稍后再试!{$wechat->errMsg}[{$wechat->errCode}]");
+        return null;
+    }
+
 }
diff --git a/application/wechat/view/news.index.html b/application/wechat/view/news.index.html
index 1090373ce..88e007c9b 100644
--- a/application/wechat/view/news.index.html
+++ b/application/wechat/view/news.index.html
@@ -12,7 +12,7 @@
     {foreach $list as $vo}
     <div class="news_item">
         <div class='news_tools hide'>
-            <a href='javascript:alert("开发中...")'>推送</a>
+            <a data-modal="{:url('push')}?id={$vo.id}" href='javascript:void(0)'>推送</a>
             <a data-open='{:url("edit")}?id={$vo.id}' href='javascript:void(0)'>编辑</a>
             <a data-news-del="{$vo.id}" href='javascript:void(0)'>删除</a>
         </div>
diff --git a/application/wechat/view/news.push.html b/application/wechat/view/news.push.html
new file mode 100644
index 000000000..2bf5155ed
--- /dev/null
+++ b/application/wechat/view/news.push.html
@@ -0,0 +1,112 @@
+<form data-auto='true' action='__SELF__'>
+
+    <div class="col-xs-2 news-container">
+        <h5 class="text-center" style="margin:10px 0">微信图文</h5>
+        <div class="news-box">
+            {foreach $vo.articles as $key=>$value}
+            <div class="news-item transition" data-id="{$value.id}">
+                <div class="news-image" style='background-image:url({$value.local_url})'></div>
+                <span class="news-title">{$value.title}</span>
+            </div>
+            <hr/>
+            {/foreach}
+        </div>
+    </div>
+
+    <div class="col-xs-2 list-container">
+        <h5 class="text-center" style="margin:10px 0">指定粉丝标签推送 <a data-check-all> 全选 </a></h5>
+        <div class="list-item">
+
+            {foreach $fans_tags as $tag}
+            <label class="cuci-lable">
+                <input name="fans_tags[]" value='{$tag.id}' type="checkbox"/> {$tag.name} ({$tag.count})
+            </label>
+            {/foreach}
+
+            {literal}
+            <script id="push" type="text/template">
+                {{if data}}
+                {{each data as value}}
+                <label class="cuci-lable">
+                {{value.nickname}}
+                </label>
+                {{/each}}
+                {{if (data.length > 199)}}
+                <label class="cuci-lable">
+                ...
+                </label>
+                {{/if}}
+                {{else}}
+                <h5></h5>
+                {{/if}}
+            </script>
+            {/literal}
+
+            <script>
+                require(['jquery', 'template'], function () {
+                    var $allbtn = $('[data-check-all]').on('click', function () {
+                        var check_status = check_checked();
+                        /*重置和全选数据变化处理*/
+                        $('input[name*=fans_tags]').map(function () {
+                            this.checked = !check_status;
+                        });
+                        check_checked();
+                        postpush();
+                    });
+                    /*重置和全选的效果处理*/
+                    function check_checked() {
+                        var allcheck = true;
+                        $('input[name*=fans_tags]').map(function () {
+                            (!this.checked) && (allcheck = false);
+                        });
+                        return ((allcheck) ? $allbtn.html('重置') : $allbtn.html('全选')), allcheck;
+                    }
+
+                    /*点击每一个选项都触发事件*/
+                    $('input[name*=fans_tags]').on('click', function () {
+                        check_checked();
+                        postpush();
+                    });
+                    /*数据异步获取并显示出来*/
+                    function postpush() {
+                        var inp = $('input[name*=fans_tags]');
+                        var group = [];
+                        for (var i = 0; i < inp.length; i++) {
+                            if (inp[i].checked === true) {
+                                group.push(inp[i].value);
+                            }
+                        }
+                        $.post("{:url('wechat/news/push')}?action=getuser", {group: group.join(',')}, function (ret) {
+                            var html = template('push', ret);
+                            document.getElementById('push-tags').innerHTML = html;
+                        });
+                    }
+                });
+            </script>
+        </div>
+        <div id='push-tags' class="list-item"></div>
+    </div>
+    <div style="clear:both;height:60px"></div>
+
+    <div class="bottom-btn text-center">
+        <button class="layui-btn">立即推送图文</button>
+        <button type='button' data-close='' data-confirm='确定要取消推送图文吗?' class="layui-btn layui-btn-danger">取消推送图文</button>
+    </div>
+
+</form>
+
+<style>
+    body { min-width: 500px }
+    .bottom-btn { display: block; background: #F7F7F7; padding: 10px; position: absolute; position: fixed; bottom: 0; width: 100% }
+    .news-container { width: 200px; padding-right: 8px }
+    .list-container { width: 578px; padding-right: 8px; padding-left: 0 }
+    .list-container h5 a { font-size: 12px; float: right }
+    .list-container .list-item { border: 1px solid #eee; padding: 8px }
+    .list-container .list-item:after { content: ''; display: block; clear: both; width: 100% }
+    .list-container .list-item label { display: block; width: 25%; float: left; white-space: nowrap; overflow: hidden; text-overflow: ellipsis }
+    .news-container .news-box { border: 1px solid #eee; padding: 8px }
+    .news-container .news-box hr { margin: 4px }
+    .news-container .news-box .news-item { position: relative; border: 1px solid #cecece; border-radius: 2px; overflow: hidden; cursor: pointer }
+    .news-container .news-box .news-image {background-position:center center;background-size:100%;height:90px}
+    .news-container .news-box .news-title { position: absolute; background: rgba(0, 0, 0, 0.5); color: #fff; padding: 2px; margin: 0; bottom: 0; left: 0; right: 0; text-align: right; white-space: nowrap; text-overflow: ellipsis; overflow: hidden }
+</style>
\ No newline at end of file